From 58592330dc33e194e55fa942d3f375124839f058 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 14 Nov 2015 21:25:10 +0100 Subject: [PATCH] Refactor cli argument handling Also, add the "-config" option for storing gocryptfs.conf outside of CIPHERDIR. --- MANPAGE.md | 2 +- checkdir.go | 2 + daemonize.go | 15 ++-- main.go | 245 +++++++++++++++++++++++++++++---------------------- masterkey.go | 3 +- 5 files changed, 153 insertions(+), 114 deletions(-) diff --git a/MANPAGE.md b/MANPAGE.md index 3dde852..7ed5e1e 100644 --- a/MANPAGE.md +++ b/MANPAGE.md @@ -23,7 +23,7 @@ gocryptfs [OPTIONS] CIPHERDIR MOUNTPOINT Change password --------------- -gocryptfs -passwd CIPHERDIR +gocryptfs -passwd [OPTIONS] CIPHERDIR DESCRIPTION =========== diff --git a/checkdir.go b/checkdir.go index 9614d7d..6f7f75d 100644 --- a/checkdir.go +++ b/checkdir.go @@ -6,6 +6,7 @@ import ( "os" ) +// checkDirEmpty - check if "dir" exists and is an empty directory func checkDirEmpty(dir string) error { err := checkDir(dir) if err != nil { @@ -21,6 +22,7 @@ func checkDirEmpty(dir string) error { return fmt.Errorf("directory %s not empty", dir) } +// checkDir - check if "dir" exists and is a directory func checkDir(dir string) error { fi, err := os.Stat(dir) if err != nil { diff --git a/daemonize.go b/daemonize.go index 4f09164..a65bd11 100644 --- a/daemonize.go +++ b/daemonize.go @@ -9,17 +9,18 @@ import ( ) // The child sends us USR1 if the mount was successful -func waitForUsr1() { +func exitOnUsr1() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGUSR1) <-c os.Exit(0) } -// daemonize - execute ourselves once again, this time with the "-f" flag, and -// wait for SIGUSR1. -func daemonize() { - go waitForUsr1() +// forkChild - execute ourselves once again, this time with the "-f" flag, and +// wait for SIGUSR1 or child exit. +// This is a workaround for the missing true fork function in Go. +func forkChild() { + go exitOnUsr1() name := os.Args[0] notifyArg := fmt.Sprintf("-notifypid=%d", os.Getpid()) newArgs := []string{"-f", notifyArg} @@ -30,7 +31,7 @@ func daemonize() { c.Stdin = os.Stdin err := c.Start() if err != nil { - fmt.Printf("daemonize: starting %s failed: %v\n", name, err) + fmt.Printf("forkChild: starting %s failed: %v\n", name, err) os.Exit(1) } err = c.Wait() @@ -40,7 +41,7 @@ func daemonize() { os.Exit(waitstat.ExitStatus()) } } - fmt.Printf("daemonize: wait returned an unknown error: %v\n", err) + fmt.Printf("forkChild: wait returned an unknown error: %v\n", err) os.Exit(1) } // The child exited with 0 - let's do the same. diff --git a/main.go b/main.go index 0f3fb4f..ece032e 100644 --- a/main.go +++ b/main.go @@ -36,19 +36,16 @@ const ( // GitVersion will be set by the build script "build.bash" var GitVersion = "[version not set - please compile using ./build.bash]" -func initDir(dirArg string, plaintextNames bool) { - dir, _ := filepath.Abs(dirArg) - - err := checkDirEmpty(dir) +func initDir(args *argContainer) { + err := checkDirEmpty(args.cipherdir) if err != nil { fmt.Printf("Invalid CIPHERDIR: %v\n", err) os.Exit(ERREXIT_INIT) } - confName := filepath.Join(dir, cryptfs.ConfDefaultName) cryptfs.Info.Printf("Choose a password for protecting your files.\n") password := readPasswordTwice() - err = cryptfs.CreateConfFile(confName, password, plaintextNames) + err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames) if err != nil { fmt.Println(err) os.Exit(ERREXIT_INIT) @@ -58,23 +55,71 @@ func initDir(dirArg string, plaintextNames bool) { } func usageText() { - fmt.Fprintf(os.Stderr, "Usage: %s -init [OPTIONS] CIPHERDIR\n", PROGRAM_NAME) - fmt.Fprintf(os.Stderr, " %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", PROGRAM_NAME) - fmt.Fprintf(os.Stderr, "\nOptions:\n") + printVersion() + fmt.Printf("\n") + fmt.Printf("Usage: %s -init|-passwd [OPTIONS] CIPHERDIR\n", PROGRAM_NAME) + fmt.Printf(" or %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", PROGRAM_NAME) + fmt.Printf("\nOptions:\n") flagSet.PrintDefaults() } type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, foreground, version, plaintextnames, quiet bool - masterkey, mountpoint, cipherdir, cpuprofile string + masterkey, mountpoint, cipherdir, cpuprofile, config string notifypid int } var flagSet *flag.FlagSet +// loadConfig - load the config file "filename", prompting the user for the password +func loadConfig(filename string) (masterkey []byte, confFile *cryptfs.ConfFile) { + // Check if the file exists at all before prompting for a password + _, err := os.Stat(filename) + if err != nil { + fmt.Print(err) + os.Exit(ERREXIT_LOADCONF) + } + fmt.Printf("Password: ") + pw := readPassword() + cryptfs.Info.Printf("Decrypting master key... ") + cryptfs.Warn.Disable() // Silence DecryptBlock() error messages on incorrect password + masterkey, confFile, err = cryptfs.LoadConfFile(filename, pw) + cryptfs.Warn.Enable() + if err != nil { + fmt.Println(err) + fmt.Println("Wrong password.") + os.Exit(ERREXIT_LOADCONF) + } + cryptfs.Info.Printf("done.\n") + + return masterkey, confFile +} + +// changePassword - change the password of config file "filename" +func changePassword(filename string) { + masterkey, confFile := loadConfig(filename) + fmt.Printf("Please enter your new password.\n") + newPw := readPasswordTwice() + confFile.EncryptKey(masterkey, newPw) + err := confFile.WriteFile() + if err != nil { + fmt.Println(err) + os.Exit(ERREXIT_INIT) + } + cryptfs.Info.Printf("Password changed.\n") + os.Exit(0) +} + +// printVersion - print a version string like +// "gocryptfs v0.3.1-31-g6736212-dirty; on-disk format 2" +func printVersion() { + fmt.Printf("%s %s; on-disk format %d\n", PROGRAM_NAME, GitVersion, cryptfs.HEADER_CURRENT_VERSION) +} + func main() { runtime.GOMAXPROCS(4) + var err error // Parse command line arguments var args argContainer @@ -93,20 +138,54 @@ func main() { flagSet.BoolVar(&args.quiet, "q", false, "Quiet - silence informational messages") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") + flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf") flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ "successful mount - used internally for daemonization") flagSet.Parse(os.Args[1:]) + if args.debug { + cryptfs.Debug.Enable() + cryptfs.Debug.Printf("Debug output enabled\n") + } + // By default, let the child handle everything. + // The parent *could* handle operations that do not require backgrounding by + // itself, but that would make the code paths more complicated. + if !args.foreground { + forkChild() // does not return + } + // Getting here means we *are* the child + // "-v" if args.version { - fmt.Printf("%s %s; on-disk format %d\n", PROGRAM_NAME, GitVersion, cryptfs.HEADER_CURRENT_VERSION) + printVersion() os.Exit(0) } + // Every operation below requires CIPHERDIR. Check that we have it. + if flagSet.NArg() >= 1 { + args.cipherdir, _ = filepath.Abs(flagSet.Arg(0)) + err := checkDir(args.cipherdir) + if err != nil { + fmt.Printf("Invalid CIPHERDIR: %v\n", err) + os.Exit(ERREXIT_CIPHERDIR) + } + } else { + usageText() + os.Exit(ERREXIT_USAGE) + } + // "-q" if args.quiet { cryptfs.Info.Disable() } - if !args.foreground { - daemonize() // does not return + // "-config" + if args.config != "" { + args.config, err = filepath.Abs(args.config) + if err != nil { + fmt.Printf("Invalid \"-config\" setting: %v\n", err) + } + cryptfs.Info.Printf("Using config file at custom location %s\n", args.config) + } else { + args.config = filepath.Join(args.cipherdir, cryptfs.ConfDefaultName) } + // "-cpuprofile" if args.cpuprofile != "" { f, err := os.Create(args.cpuprofile) if err != nil { @@ -117,123 +196,79 @@ func main() { pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } - if args.debug { - cryptfs.Debug.Enable() - cryptfs.Debug.Printf("Debug output enabled\n") - } + // "-openssl" if args.openssl == false { cryptfs.Info.Printf("Openssl disabled\n") } + // Operation flags: init, passwd or mount + // "-init" if args.init { - if flagSet.NArg() != 1 && args.plaintextnames == false { - fmt.Printf("Usage: %s --init [--plaintextnames] CIPHERDIR\n", PROGRAM_NAME) + if flagSet.NArg() > 1 { + fmt.Printf("Usage: %s -init [OPTIONS] CIPHERDIR\n", PROGRAM_NAME) os.Exit(ERREXIT_USAGE) } - initDir(flagSet.Arg(0), args.plaintextnames) // does not return + initDir(&args) // does not return } + // "-passwd" if args.passwd { - if flagSet.NArg() != 1 { - fmt.Printf("Usage: %s --passwd CIPHERDIR\n", PROGRAM_NAME) + if flagSet.NArg() > 1 { + fmt.Printf("Usage: %s -passwd [OPTIONS] CIPHERDIR\n", PROGRAM_NAME) os.Exit(ERREXIT_USAGE) } - } else { - // Normal mount - if flagSet.NArg() < 2 { - usageText() - os.Exit(ERREXIT_USAGE) - } - args.mountpoint, _ = filepath.Abs(flagSet.Arg(1)) - err := checkDirEmpty(args.mountpoint) - if err != nil { - fmt.Printf("Invalid MOUNTPOINT: %v\n", err) - os.Exit(ERREXIT_MOUNTPOINT) - } + changePassword(args.config) // does not return } - args.cipherdir, _ = filepath.Abs(flagSet.Arg(0)) - err := checkDir(args.cipherdir) + // Mount + // Check mountpoint + if flagSet.NArg() < 2 { + usageText() + os.Exit(ERREXIT_USAGE) + } + args.mountpoint, err = filepath.Abs(flagSet.Arg(1)) if err != nil { - fmt.Printf("Invalid CIPHERDIR: %v\n", err) - os.Exit(ERREXIT_CIPHERDIR) + fmt.Printf("Invalid MOUNTPOINT: %v\n", err) + os.Exit(ERREXIT_MOUNTPOINT) } - - var plaintextNames bool - var cf *cryptfs.ConfFile - var currentPassword string - key := make([]byte, cryptfs.KEY_LEN) - if args.zerokey { - cryptfs.Info.Printf("Zerokey mode active: using all-zero dummy master key.\n") - plaintextNames = args.plaintextnames - } else if len(args.masterkey) > 0 { - key = parseMasterKey(args.masterkey) + err = checkDirEmpty(args.mountpoint) + if err != nil { + fmt.Printf("Invalid MOUNTPOINT: %v\n", err) + os.Exit(ERREXIT_MOUNTPOINT) + } + // Get master key + var masterkey []byte + if args.masterkey != "" { + // "-masterkey" cryptfs.Info.Printf("Using explicit master key.\n") - } else { - // Load config file - cfname := filepath.Join(args.cipherdir, cryptfs.ConfDefaultName) - _, err = os.Stat(cfname) - if err != nil { - fmt.Printf("Error: %s not found in CIPHERDIR\n", cryptfs.ConfDefaultName) - fmt.Printf("Please run \"%s --init %s\" first\n", os.Args[0], flagSet.Arg(0)) - os.Exit(ERREXIT_LOADCONF) - } - if args.passwd == true { - fmt.Printf("Old password: ") - } else { - fmt.Printf("Password: ") - } - currentPassword = readPassword() - cryptfs.Info.Printf("Decrypting master key... ") - cryptfs.Warn.Disable() // Silence DecryptBlock() error messages on incorrect password - key, cf, err = cryptfs.LoadConfFile(cfname, currentPassword) - cryptfs.Warn.Enable() - if err != nil { - fmt.Println(err) - os.Exit(ERREXIT_LOADCONF) - } - cryptfs.Info.Printf("done.\n") - } - if args.passwd == true { - fmt.Printf("Please enter the new password.\n") - newPassword := readPasswordTwice() - if newPassword == currentPassword { - fmt.Printf("New and old passwords are identical\n") - os.Exit(ERREXIT_PASSWORD) - } - cf.EncryptKey(key, newPassword) - err := cf.WriteFile() - if err != nil { - fmt.Println(err) - os.Exit(ERREXIT_INIT) - } - cryptfs.Info.Printf("Password changed.\n") - os.Exit(0) - } - - if cf != nil { - plaintextNames = cf.PlaintextNames() - } - srv := pathfsFrontend(key, args.cipherdir, args.mountpoint, args.fusedebug, args.openssl, plaintextNames) - - if args.zerokey == false && len(args.masterkey) == 0 { - printMasterKey(key) - } else if args.zerokey == true { - cryptfs.Info.Printf("ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING.\n") - } else if len(args.masterkey) > 0 { + masterkey = parseMasterKey(args.masterkey) cryptfs.Info.Printf("THE MASTER KEY IS VISIBLE VIA \"ps -auxwww\", ONLY USE THIS MODE FOR EMERGENCIES.\n") + } else if args.zerokey { + // "-zerokey" + cryptfs.Info.Printf("Using all-zero dummy master key.\n") + cryptfs.Info.Printf("ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING.\n") + masterkey = make([]byte, cryptfs.KEY_LEN) + } else { + // Load master key from config file + var confFile *cryptfs.ConfFile + masterkey, confFile = loadConfig(args.config) + printMasterKey(masterkey) + args.plaintextnames = confFile.PlaintextNames() } - + // Initialize FUSE server + srv := pathfsFrontend(masterkey, args.cipherdir, args.mountpoint, args.fusedebug, args.openssl, args.plaintextnames) cryptfs.Info.Println("Filesystem ready.") - // Send USR1 notification + // We are ready - send USR1 signal to our parent if args.notifypid > 0 { sendUsr1(args.notifypid) } - // Wait for SIGING in the background and unmount ourselves if we get it - // This prevents a dangling "Transport endpoint is not connected" mountpoint + // Wait for SIGINT in the background and unmount ourselves if we get it. + // This prevents a dangling "Transport endpoint is not connected" mountpoint. handleSigint(srv, args.mountpoint) // Jump into server loop. Returns when it gets an umount request from the kernel. srv.Serve() - // main returns with code 0 + // main exits with code 0 } +// pathfsFrontend - initialize FUSE server based on go-fuse's PathFS +// Calls os.Exit on errors func pathfsFrontend(key []byte, cipherdir string, mountpoint string, debug bool, openssl bool, plaintextNames bool) *fuse.Server { diff --git a/masterkey.go b/masterkey.go index a1889b4..2062997 100644 --- a/masterkey.go +++ b/masterkey.go @@ -37,7 +37,8 @@ paper and store it in a drawer. `, hChunked) } -// Parse a hex-encoded master key that was passed on the command line +// parseMasterKey - Parse a hex-encoded master key that was passed on the command line +// Calls os.Exit on failure func parseMasterKey(masterkey string) []byte { masterkey = strings.Replace(masterkey, "-", "", -1) key, err := hex.DecodeString(masterkey)