diff --git a/main.go b/main.go index 9fc9b6b..467a143 100644 --- a/main.go +++ b/main.go @@ -1,29 +1,17 @@ package main import ( - "encoding/json" "fmt" - "log/syslog" "os" - "os/exec" - "os/signal" + "path/filepath" "runtime" "runtime/pprof" "strconv" - "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/contentenc" - "github.com/rfjakob/gocryptfs/internal/cryptocore" - "github.com/rfjakob/gocryptfs/internal/fusefrontend" - "github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse" "github.com/rfjakob/gocryptfs/internal/readpassword" "github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/tlog" @@ -62,7 +50,7 @@ Options: flagSet.PrintDefaults() } -// loadConfig - load the config file "filename", prompting the user for the password +// loadConfig loads the config file "args.config", prompting the user for the password func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.ConfFile) { // Check if the file can be opened at all before prompting for a password fd, err := os.Open(args.config) @@ -78,7 +66,6 @@ func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.Conf tlog.Fatal.Println(err) os.Exit(ErrExitLoadConf) } - return masterkey, confFile } @@ -227,196 +214,5 @@ func main() { changePassword(&args) // does not return } // Mount - // Check mountpoint - if flagSet.NArg() != 2 { - tlog.Fatal.Printf("Usage: %s [OPTIONS] CIPHERDIR MOUNTPOINT", tlog.ProgramName) - os.Exit(ErrExitUsage) - } - 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() - // main exits with code 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) - }() + os.Exit(doMount(&args)) } 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) + }() +}