diff options
| author | Jakob Unterwurzacher | 2019-03-03 13:25:30 +0100 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2019-03-03 13:25:30 +0100 | 
| commit | cf27037f20723e934320edeff7f8aa356bdca467 (patch) | |
| tree | b12428d172325fcfb517af26f7985651ef70e902 | |
| parent | 61940a9c0666eba8be21de4f1cd182912f74f929 (diff) | |
Allow multiple -extpass arguments
To support arguments containing spaces, -extpass can now
be passed multiple times.
https://github.com/rfjakob/gocryptfs/issues/289
| -rw-r--r-- | Documentation/MANPAGE.md | 15 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | cli_args.go | 20 | ||||
| -rw-r--r-- | gocryptfs-xray/xray_main.go | 2 | ||||
| -rw-r--r-- | init_dir.go | 4 | ||||
| -rw-r--r-- | internal/readpassword/extpass_test.go | 34 | ||||
| -rw-r--r-- | internal/readpassword/passfile_test.go | 1 | ||||
| -rw-r--r-- | internal/readpassword/passfile_test_files/file with spaces.txt | 1 | ||||
| -rw-r--r-- | internal/readpassword/read.go | 19 | ||||
| -rw-r--r-- | main.go | 4 | ||||
| -rw-r--r-- | masterkey.go | 2 | 
11 files changed, 79 insertions, 25 deletions
| diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index 5ebb0b4..5802fb3 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -89,8 +89,19 @@ Enable (`-exec`) or disable (`-noexec`) executables in a gocryptfs mount  #### -extpass string  Use an external program (like ssh-askpass) for the password prompt.  The program should return the password on stdout, a trailing newline is -stripped by gocryptfs. Using something like "cat /mypassword.txt" allows -one to mount the gocryptfs filesystem without user interaction. +stripped by gocryptfs. If you just want to read from a password file, see `-passfile`. + +When `-extpass` is specified once, the string argument will be split on spaces. +For example, `-extpass "md5sum my password.txt"` will be executed as +`"md5sum" "my" "password.txt"`, which is NOT what you want. + +Specify `-extpass` twice or more to use the string arguments as-is. +For example, you DO want to call `md5sum` like this: +`-extpass "md5sum" -extpass "my password.txt"`. + +If you want to prevent splitting on spaces but don't want to pass arguments +to your program, use `"--"`, which is accepted by most programs: +`-extpass "my program" -extpass "--"`  #### -fg, -f  Stay in the foreground instead of forking away. Implies "-nosyslog". @@ -182,6 +182,8 @@ v1.7, in progress (v1.7-beta1: 2019-01-03, v1.7-rc1: 2019-01-04)    ([#320](https://github.com/rfjakob/gocryptfs/issues/320)).    Prevents trouble in the unlikely case that gocryptfs is called with    stdin,stdout and/or stderr closed. +* `-extpass` now can be specified multiple times to support arguments containing spaces +  ([#289](https://github.com/rfjakob/gocryptfs/issues/289))  v1.6.1, 2018-12-12  * Fix "Operation not supported" chmod errors on Go 1.11 diff --git a/cli_args.go b/cli_args.go index a4da85c..425730a 100644 --- a/cli_args.go +++ b/cli_args.go @@ -32,9 +32,11 @@ type argContainer struct {  	sharedstorage, devrandom, fsck, trezor bool  	// Mount options with opposites  	dev, nodev, suid, nosuid, exec, noexec, rw, ro bool -	masterkey, mountpoint, cipherdir, cpuprofile, extpass, +	masterkey, mountpoint, cipherdir, cpuprofile,  	memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string -	// For reverse mode, --exclude is available. It can be specified multiple times. +	// -extpass can be passed multiple times +	extpass multipleStrings +	// For reverse mode, -exclude is available. It can be specified multiple times.  	exclude multipleStrings  	// Configuration file name override  	config             string @@ -62,6 +64,11 @@ func (s *multipleStrings) Set(val string) error {  	return nil  } +func (s *multipleStrings) Empty() bool { +	s2 := []string(*s) +	return len(s2) == 0 +} +  var flagSet *flag.FlagSet  // prefixOArgs transform options passed via "-o foo,bar" into regular options @@ -179,7 +186,6 @@ func parseCliOpts() (args argContainer) {  	flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")  	flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")  	flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf") -	flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt")  	flagSet.StringVar(&args.passfile, "passfile", "", "Read password from file")  	flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list")  	flagSet.StringVar(&args.ctlsock, "ctlsock", "", "Create control socket at specified path") @@ -190,6 +196,8 @@ func parseCliOpts() (args argContainer) {  	// -e, --exclude  	flagSet.Var(&args.exclude, "e", "Alias for -exclude")  	flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view") +	// -extpass +	flagSet.Var(&args.extpass, "extpass", "Use external program for the password prompt")  	flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+  		"successful mount - used internally for daemonization") @@ -248,7 +256,7 @@ func parseCliOpts() (args argContainer) {  		args.allow_other = false  		args.ko = "noexec"  	} -	if args.extpass != "" && args.passfile != "" { +	if !args.extpass.Empty() && args.passfile != "" {  		tlog.Fatal.Printf("The options -extpass and -passfile cannot be used at the same time")  		os.Exit(exitcodes.Usage)  	} @@ -256,11 +264,11 @@ func parseCliOpts() (args argContainer) {  		tlog.Fatal.Printf("The options -passfile and -masterkey cannot be used at the same time")  		os.Exit(exitcodes.Usage)  	} -	if args.extpass != "" && args.masterkey != "" { +	if !args.extpass.Empty() && args.masterkey != "" {  		tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")  		os.Exit(exitcodes.Usage)  	} -	if args.extpass != "" && args.trezor { +	if !args.extpass.Empty() && args.trezor {  		tlog.Fatal.Printf("The options -extpass and -trezor cannot be used at the same time")  		os.Exit(exitcodes.Usage)  	} diff --git a/gocryptfs-xray/xray_main.go b/gocryptfs-xray/xray_main.go index 73b1e18..df92f2d 100644 --- a/gocryptfs-xray/xray_main.go +++ b/gocryptfs-xray/xray_main.go @@ -68,7 +68,7 @@ func main() {  func dumpMasterKey(fn string) {  	tlog.Info.Enabled = false -	pw := readpassword.Once("", "", "") +	pw := readpassword.Once(nil, "", "")  	masterkey, _, err := configfile.LoadAndDecrypt(fn, pw)  	if err != nil {  		fmt.Fprintln(os.Stderr, err) diff --git a/init_dir.go b/init_dir.go index ecfec9d..c3aa4b5 100644 --- a/init_dir.go +++ b/init_dir.go @@ -68,7 +68,7 @@ func initDir(args *argContainer) {  		}  	}  	// Choose password for config file -	if args.extpass == "" { +	if args.extpass.Empty() {  		tlog.Info.Printf("Choose a password for protecting your files.")  	}  	{ @@ -80,7 +80,7 @@ func initDir(args *argContainer) {  			password = readpassword.Trezor(trezorPayload)  		} else {  			// Normal password entry -			password = readpassword.Twice(args.extpass, args.passfile) +			password = readpassword.Twice([]string(args.extpass), args.passfile)  			readpassword.CheckTrailingGarbage()  		}  		creator := tlog.ProgramName + " " + GitVersion diff --git a/internal/readpassword/extpass_test.go b/internal/readpassword/extpass_test.go index 037d111..b4ca8fa 100644 --- a/internal/readpassword/extpass_test.go +++ b/internal/readpassword/extpass_test.go @@ -18,7 +18,7 @@ func TestMain(m *testing.M) {  func TestExtpass(t *testing.T) {  	p1 := "ads2q4tw41reg52" -	p2 := string(readPasswordExtpass("echo " + p1)) +	p2 := string(readPasswordExtpass([]string{"echo " + p1}))  	if p1 != p2 {  		t.Errorf("p1=%q != p2=%q", p1, p2)  	} @@ -26,7 +26,33 @@ func TestExtpass(t *testing.T) {  func TestOnceExtpass(t *testing.T) {  	p1 := "lkadsf0923rdfi48rqwhdsf" -	p2 := string(Once("echo "+p1, "", "")) +	p2 := string(Once([]string{"echo " + p1}, "", "")) +	if p1 != p2 { +		t.Errorf("p1=%q != p2=%q", p1, p2) +	} +} + +// extpass with two arguments +func TestOnceExtpass2(t *testing.T) { +	p1 := "foo" +	p2 := string(Once([]string{"echo", p1}, "", "")) +	if p1 != p2 { +		t.Errorf("p1=%q != p2=%q", p1, p2) +	} +} + +// extpass with three arguments +func TestOnceExtpass3(t *testing.T) { +	p1 := "foo bar baz" +	p2 := string(Once([]string{"echo", "foo", "bar", "baz"}, "", "")) +	if p1 != p2 { +		t.Errorf("p1=%q != p2=%q", p1, p2) +	} +} + +func TestOnceExtpassSpaces(t *testing.T) { +	p1 := "mypassword" +	p2 := string(Once([]string{"cat", "passfile_test_files/file with spaces.txt"}, "", ""))  	if p1 != p2 {  		t.Errorf("p1=%q != p2=%q", p1, p2)  	} @@ -34,7 +60,7 @@ func TestOnceExtpass(t *testing.T) {  func TestTwiceExtpass(t *testing.T) {  	p1 := "w5w44t3wfe45srz434" -	p2 := string(Once("echo "+p1, "", "")) +	p2 := string(Once([]string{"echo " + p1}, "", ""))  	if p1 != p2 {  		t.Errorf("p1=%q != p2=%q", p1, p2)  	} @@ -46,7 +72,7 @@ func TestTwiceExtpass(t *testing.T) {  // https://talks.golang.org/2014/testing.slide#23 .  func TestExtpassEmpty(t *testing.T) {  	if os.Getenv("TEST_SLAVE") == "1" { -		readPasswordExtpass("echo") +		readPasswordExtpass([]string{"echo"})  		return  	}  	cmd := exec.Command(os.Args[0], "-test.run=TestExtpassEmpty$") diff --git a/internal/readpassword/passfile_test.go b/internal/readpassword/passfile_test.go index 30f976f..cb7fa44 100644 --- a/internal/readpassword/passfile_test.go +++ b/internal/readpassword/passfile_test.go @@ -14,6 +14,7 @@ func TestPassfile(t *testing.T) {  		{"mypassword.txt", "mypassword"},  		{"mypassword_garbage.txt", "mypassword"},  		{"mypassword_missing_newline.txt", "mypassword"}, +		{"file with spaces.txt", "mypassword"},  	}  	for _, tc := range testcases {  		pw := readPassFile("passfile_test_files/" + tc.file) diff --git a/internal/readpassword/passfile_test_files/file with spaces.txt b/internal/readpassword/passfile_test_files/file with spaces.txt new file mode 100644 index 0000000..48d23cf --- /dev/null +++ b/internal/readpassword/passfile_test_files/file with spaces.txt @@ -0,0 +1 @@ +mypassword diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go index 0378e53..060100b 100644 --- a/internal/readpassword/read.go +++ b/internal/readpassword/read.go @@ -24,11 +24,11 @@ const (  // Once tries to get a password from the user, either from the terminal, extpass  // or stdin. Leave "prompt" empty to use the default "Password: " prompt. -func Once(extpass string, passfile string, prompt string) []byte { +func Once(extpass []string, passfile string, prompt string) []byte {  	if passfile != "" {  		return readPassFile(passfile)  	} -	if extpass != "" { +	if len(extpass) != 0 {  		return readPasswordExtpass(extpass)  	}  	if prompt == "" { @@ -42,11 +42,11 @@ func Once(extpass string, passfile string, prompt string) []byte {  // Twice is the same as Once but will prompt twice if we get the password from  // the terminal. -func Twice(extpass string, passfile string) []byte { +func Twice(extpass []string, passfile string) []byte {  	if passfile != "" {  		return readPassFile(passfile)  	} -	if extpass != "" { +	if len(extpass) != 0 {  		return readPasswordExtpass(extpass)  	}  	if !terminal.IsTerminal(int(os.Stdin.Fd())) { @@ -99,9 +99,14 @@ func readPasswordStdin(prompt string) []byte {  // readPasswordExtpass executes the "extpass" program and returns the first line  // of the output.  // Exits on read error or empty result. -func readPasswordExtpass(extpass string) []byte { -	tlog.Info.Println("Reading password from extpass program") -	parts := strings.Split(extpass, " ") +func readPasswordExtpass(extpass []string) []byte { +	var parts []string +	if len(extpass) == 1 { +		parts = strings.Split(extpass[0], " ") +	} else { +		parts = extpass +	} +	tlog.Info.Printf("Reading password from extpass program %q, arguments: %q\n", parts[0], parts[1:])  	cmd := exec.Command(parts[0], parts[1:]...)  	cmd.Stderr = os.Stderr  	pipe, err := cmd.StdoutPipe() @@ -53,7 +53,7 @@ func loadConfig(args *argContainer) (masterkey []byte, cf *configfile.ConfFile,  		pw = readpassword.Trezor(cf.TrezorPayload)  	} else {  		// Normal password entry -		pw = readpassword.Once(args.extpass, args.passfile, "") +		pw = readpassword.Once([]string(args.extpass), args.passfile, "")  	}  	tlog.Info.Println("Decrypting master key")  	masterkey, err = cf.DecryptMasterKey(pw) @@ -93,7 +93,7 @@ func changePassword(args *argContainer) {  			log.Panic("empty masterkey")  		}  		tlog.Info.Println("Please enter your new password.") -		newPw := readpassword.Twice(args.extpass, args.passfile) +		newPw := readpassword.Twice([]string(args.extpass), args.passfile)  		readpassword.CheckTrailingGarbage()  		confFile.EncryptKey(masterkey, newPw, confFile.ScryptObject.LogN())  		for i := range newPw { diff --git a/masterkey.go b/masterkey.go index 332a673..8392bc6 100644 --- a/masterkey.go +++ b/masterkey.go @@ -43,7 +43,7 @@ func getMasterKey(args *argContainer) (masterkey []byte, confFile *configfile.Co  	masterkeyFromStdin := false  	// "-masterkey=stdin"  	if args.masterkey == "stdin" { -		args.masterkey = string(readpassword.Once("", "", "Masterkey")) +		args.masterkey = string(readpassword.Once(nil, "", "Masterkey"))  		masterkeyFromStdin = true  	}  	// "-masterkey=941a6029-3adc6a1c-..." | 
