Allow multiple -extpass arguments

To support arguments containing spaces, -extpass can now
be passed multiple times.

https://github.com/rfjakob/gocryptfs/issues/289
This commit is contained in:
Jakob Unterwurzacher 2019-03-03 13:25:30 +01:00
parent 61940a9c06
commit cf27037f20
11 changed files with 79 additions and 25 deletions

View File

@ -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".

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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$")

View File

@ -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)

View File

@ -0,0 +1 @@
mypassword

View File

@ -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()

View File

@ -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 {

View File

@ -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-..."