aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/MANPAGE.md15
-rw-r--r--README.md2
-rw-r--r--cli_args.go20
-rw-r--r--gocryptfs-xray/xray_main.go2
-rw-r--r--init_dir.go4
-rw-r--r--internal/readpassword/extpass_test.go34
-rw-r--r--internal/readpassword/passfile_test.go1
-rw-r--r--internal/readpassword/passfile_test_files/file with spaces.txt1
-rw-r--r--internal/readpassword/read.go19
-rw-r--r--main.go4
-rw-r--r--masterkey.go2
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".
diff --git a/README.md b/README.md
index 3a002cb..f61d49e 100644
--- a/README.md
+++ b/README.md
@@ -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()
diff --git a/main.go b/main.go
index a376356..09c8ed7 100644
--- a/main.go
+++ b/main.go
@@ -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-..."