diff options
Diffstat (limited to 'mount.go')
-rw-r--r-- | mount.go | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/mount.go b/mount.go new file mode 100644 index 0000000..56f1dc1 --- /dev/null +++ b/mount.go @@ -0,0 +1,221 @@ +package main + +import ( + "encoding/json" + "log/syslog" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" + "github.com/hanwen/go-fuse/fuse/pathfs" + + "github.com/rfjakob/gocryptfs/internal/configfile" + "github.com/rfjakob/gocryptfs/internal/cryptocore" + "github.com/rfjakob/gocryptfs/internal/fusefrontend" + "github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse" + "github.com/rfjakob/gocryptfs/internal/tlog" +) + +// doMount mounts an encrypted directory. +// Called from main. +func doMount(args *argContainer) int { + // Check mountpoint + if flagSet.NArg() != 2 { + tlog.Fatal.Printf("Usage: %s [OPTIONS] CIPHERDIR MOUNTPOINT", tlog.ProgramName) + os.Exit(ErrExitUsage) + } + var err error + args.mountpoint, err = filepath.Abs(flagSet.Arg(1)) + if err != nil { + tlog.Fatal.Printf("Invalid mountpoint: %v", err) + os.Exit(ErrExitMountPoint) + } + if args.nonempty { + err = checkDir(args.mountpoint) + } else { + err = checkDirEmpty(args.mountpoint) + } + if err != nil { + tlog.Fatal.Printf("Invalid mountpoint: %v", err) + os.Exit(ErrExitMountPoint) + } + // Get master key + var masterkey []byte + var confFile *configfile.ConfFile + if args.masterkey != "" { + // "-masterkey" + tlog.Info.Printf("Using explicit master key.") + masterkey = parseMasterKey(args.masterkey) + tlog.Info.Printf(tlog.ColorYellow + + "THE MASTER KEY IS VISIBLE VIA \"ps ax\" AND MAY BE STORED IN YOUR SHELL HISTORY!\n" + + "ONLY USE THIS MODE FOR EMERGENCIES." + tlog.ColorReset) + } else if args.zerokey { + // "-zerokey" + tlog.Info.Printf("Using all-zero dummy master key.") + tlog.Info.Printf(tlog.ColorYellow + + "ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING." + + tlog.ColorReset) + masterkey = make([]byte, cryptocore.KeyLen) + } else { + // Load master key from config file + masterkey, confFile = loadConfig(args) + printMasterKey(masterkey) + } + // Initialize FUSE server + tlog.Debug.Printf("cli args: %v", args) + srv := initFuseFrontend(masterkey, args, confFile) + tlog.Info.Println(tlog.ColorGreen + "Filesystem mounted and ready." + tlog.ColorReset) + // We have been forked into the background, as evidenced by the set + // "notifypid". + if args.notifypid > 0 { + // Chdir to the root directory so we don't block unmounting the CWD + os.Chdir("/") + // Switch to syslog + if !args.nosyslog { + tlog.Info.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_INFO) + tlog.Debug.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_DEBUG) + tlog.Warn.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_WARNING) + tlog.SwitchLoggerToSyslog(syslog.LOG_USER | syslog.LOG_WARNING) + // Daemons should close all fds (and we don't want to get killed by + // SIGPIPE if any of those get closed on the other end) + os.Stderr.Close() + os.Stdout.Close() + os.Stdin.Close() + } + // Send SIGUSR1 to our parent + sendUsr1(args.notifypid) + } + // Wait for SIGINT in the background and unmount ourselves if we get it. + // This prevents a dangling "Transport endpoint is not connected" + // mountpoint if the user hits CTRL-C. + handleSigint(srv, args.mountpoint) + // Jump into server loop. Returns when it gets an umount request from the kernel. + srv.Serve() + return 0 +} + +// initFuseFrontend - initialize gocryptfs/fusefrontend +// Calls os.Exit on errors +func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfFile) *fuse.Server { + // Reconciliate CLI and config file arguments into a fusefrontend.Args struct + // that is passed to the filesystem implementation + cryptoBackend := cryptocore.BackendGoGCM + if args.openssl { + cryptoBackend = cryptocore.BackendOpenSSL + } + if args.aessiv { + cryptoBackend = cryptocore.BackendAESSIV + } + frontendArgs := fusefrontend.Args{ + Cipherdir: args.cipherdir, + Masterkey: key, + PlaintextNames: args.plaintextnames, + LongNames: args.longnames, + CryptoBackend: cryptoBackend, + } + // confFile is nil when "-zerokey" or "-masterkey" was used + if confFile != nil { + // Settings from the config file override command line args + frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames) + if confFile.IsFeatureFlagSet(configfile.FlagAESSIV) { + frontendArgs.CryptoBackend = cryptocore.BackendAESSIV + } else if args.reverse { + tlog.Fatal.Printf("AES-SIV is required by reverse mode, but not enabled in the config file") + os.Exit(ErrExitUsage) + } + } + // If allow_other is set and we run as root, try to give newly created files to + // the right user. + if args.allow_other && os.Getuid() == 0 { + frontendArgs.PreserveOwner = true + } + jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t") + tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes)) + var finalFs pathfs.FileSystem + if args.reverse { + finalFs = fusefrontend_reverse.NewFS(frontendArgs) + } else { + finalFs = fusefrontend.NewFS(frontendArgs) + } + pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true} + pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts) + fuseOpts := &nodefs.Options{ + // These options are to be compatible with libfuse defaults, + // making benchmarking easier. + NegativeTimeout: time.Second, + AttrTimeout: time.Second, + EntryTimeout: time.Second, + } + conn := nodefs.NewFileSystemConnector(pathFs.Root(), fuseOpts) + var mOpts fuse.MountOptions + mOpts.AllowOther = false + if args.allow_other { + tlog.Info.Printf(tlog.ColorYellow + "The option \"-allow_other\" is set. Make sure the file " + + "permissions protect your data from unwanted access." + tlog.ColorReset) + mOpts.AllowOther = true + // Make the kernel check the file permissions for us + mOpts.Options = append(mOpts.Options, "default_permissions") + } + if args.nonempty { + mOpts.Options = append(mOpts.Options, "nonempty") + } + // Set values shown in "df -T" and friends + // First column, "Filesystem" + mOpts.Options = append(mOpts.Options, "fsname="+args.cipherdir) + // Second column, "Type", will be shown as "fuse." + Name + mOpts.Name = "gocryptfs" + if args.reverse { + mOpts.Name += "-reverse" + } + + // The kernel enforces read-only operation, we just have to pass "ro". + // Reverse mounts are always read-only + if args.ro || args.reverse { + mOpts.Options = append(mOpts.Options, "ro") + } + // Add additional mount options (if any) after the stock ones, so the user has + // a chance to override them. + if args.o != "" { + parts := strings.Split(args.o, ",") + tlog.Debug.Printf("Adding -o mount options: %v", parts) + mOpts.Options = append(mOpts.Options, parts...) + } + srv, err := fuse.NewServer(conn.RawFS(), args.mountpoint, &mOpts) + if err != nil { + tlog.Fatal.Printf("Mount failed: %v", err) + os.Exit(ErrExitMount) + } + srv.SetDebug(args.fusedebug) + + // All FUSE file and directory create calls carry explicit permission + // information. We need an unrestricted umask to create the files and + // directories with the requested permissions. + syscall.Umask(0000) + + return srv +} + +func handleSigint(srv *fuse.Server, mountpoint string) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + signal.Notify(ch, syscall.SIGTERM) + go func() { + <-ch + err := srv.Unmount() + if err != nil { + tlog.Warn.Print(err) + tlog.Info.Printf("Trying lazy unmount") + cmd := exec.Command("fusermount", "-u", "-z", mountpoint) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + } + os.Exit(1) + }() +} |