From 65afeba19a1aff2cfc26ff5cb8b8bcd202a6ae53 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 12 Sep 2016 14:08:51 +0200 Subject: [PATCH] Add option to read the password from a file --- src/cmds/restic/cmd_init.go | 5 +++- src/cmds/restic/cmd_key.go | 18 ++++++++--- src/cmds/restic/global.go | 60 +++++++++++++++++++++++++------------ src/cmds/restic/main.go | 1 - 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/cmds/restic/cmd_init.go b/src/cmds/restic/cmd_init.go index 39b8cd2fd..967a8cd10 100644 --- a/src/cmds/restic/cmd_init.go +++ b/src/cmds/restic/cmd_init.go @@ -20,9 +20,12 @@ func (cmd CmdInit) Execute(args []string) error { } if cmd.global.password == "" { - cmd.global.password = cmd.global.ReadPasswordTwice( + cmd.global.password, err = cmd.global.ReadPasswordTwice( "enter password for new backend: ", "enter password again: ") + if err != nil { + return err + } } s := repository.New(be) diff --git a/src/cmds/restic/cmd_key.go b/src/cmds/restic/cmd_key.go index 848018150..f609ad2d1 100644 --- a/src/cmds/restic/cmd_key.go +++ b/src/cmds/restic/cmd_key.go @@ -56,9 +56,9 @@ func (cmd CmdKey) listKeys(s *repository.Repository) error { return tab.Write(cmd.global.stdout) } -func (cmd CmdKey) getNewPassword() string { +func (cmd CmdKey) getNewPassword() (string, error) { if cmd.newPassword != "" { - return cmd.newPassword + return cmd.newPassword, nil } return cmd.global.ReadPasswordTwice( @@ -67,7 +67,12 @@ func (cmd CmdKey) getNewPassword() string { } func (cmd CmdKey) addKey(repo *repository.Repository) error { - id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key()) + pw, err := cmd.getNewPassword() + if err != nil { + return err + } + + id, err := repository.AddKey(repo, pw, repo.Key()) if err != nil { return errors.Fatalf("creating new key failed: %v\n", err) } @@ -92,7 +97,12 @@ func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error { } func (cmd CmdKey) changePassword(repo *repository.Repository) error { - id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key()) + pw, err := cmd.getNewPassword() + if err != nil { + return err + } + + id, err := repository.AddKey(repo, pw, repo.Key()) if err != nil { return errors.Fatalf("creating new key failed: %v\n", err) } diff --git a/src/cmds/restic/global.go b/src/cmds/restic/global.go index ee4255f7b..b2a19d30e 100644 --- a/src/cmds/restic/global.go +++ b/src/cmds/restic/global.go @@ -3,6 +3,7 @@ package main import ( "fmt" "io" + "io/ioutil" "os" "restic" "runtime" @@ -28,11 +29,12 @@ var compiledAt = "unknown time" // GlobalOptions holds all those options that can be set for every command. type GlobalOptions struct { - Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` - CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` - Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"` - NoLock bool ` long:"no-lock" default:"false" description:"Do not lock the repo, this allows some operations on read-only repos."` - Options []string `short:"o" long:"option" description:"Specify options in the form 'foo.key=value'"` + Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` + PasswordFile string `short:"p" long:"password-file" description:"Read the repository password from a file"` + CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` + Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"` + NoLock bool ` long:"no-lock" default:"false" description:"Do not lock the repo, this allows some operations on read-only repos."` + Options []string `short:"o" long:"option" description:"Specify options in the form 'foo.key=value'"` password string stdout io.Writer @@ -185,7 +187,7 @@ func readPassword(in io.Reader) (password string, err error) { buf = buf[:n] if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF { - return "", err + return "", errors.Wrap(err, "ReadFull") } return strings.TrimRight(string(buf), "\r\n"), nil @@ -199,15 +201,25 @@ func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password s buf, err := terminal.ReadPassword(int(in.Fd())) fmt.Fprintln(out) if err != nil { - return "", err + return "", errors.Wrap(err, "ReadPassword") } password = string(buf) return password, nil } -// ReadPassword reads the password from stdin. -func (o GlobalOptions) ReadPassword(prompt string) string { +// ReadPassword reads the password from a password file, the environment +// variable RESTIC_PASSWORD or prompts the user. +func (o GlobalOptions) ReadPassword(prompt string) (string, error) { + if o.PasswordFile != "" { + s, err := ioutil.ReadFile(o.PasswordFile) + return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile") + } + + if pwd := os.Getenv("RESTIC_PASSWORD"); pwd != "" { + return pwd, nil + } + var ( password string err error @@ -220,26 +232,33 @@ func (o GlobalOptions) ReadPassword(prompt string) string { } if err != nil { - o.Exitf(2, "unable to read password: %v", err) + return "", errors.Wrap(err, "unable to read password") } if len(password) == 0 { - o.Exitf(1, "an empty password is not a password") + return "", errors.Fatal("an empty password is not a password") } - return password + return password, nil } // ReadPasswordTwice calls ReadPassword two times and returns an error when the // passwords don't match. -func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) string { - pw1 := o.ReadPassword(prompt1) - pw2 := o.ReadPassword(prompt2) - if pw1 != pw2 { - o.Exitf(1, "passwords do not match") +func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) (string, error) { + pw1, err := o.ReadPassword(prompt1) + if err != nil { + return "", err + } + pw2, err := o.ReadPassword(prompt2) + if err != nil { + return "", err } - return pw1 + if pw1 != pw2 { + return "", errors.Fatal("passwords do not match") + } + + return pw1, nil } const maxKeys = 20 @@ -258,7 +277,10 @@ func (o GlobalOptions) OpenRepository() (*repository.Repository, error) { s := repository.New(be) if o.password == "" { - o.password = o.ReadPassword("enter password for repository: ") + o.password, err = o.ReadPassword("enter password for repository: ") + if err != nil { + return nil, err + } } err = s.SearchKey(o.password, maxKeys) diff --git a/src/cmds/restic/main.go b/src/cmds/restic/main.go index 6477c1e62..c28f51eeb 100644 --- a/src/cmds/restic/main.go +++ b/src/cmds/restic/main.go @@ -28,7 +28,6 @@ func main() { // defer profile.Start(profile.MemProfileRate(100000), profile.ProfilePath(".")).Stop() // defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop() globalOpts.Repo = os.Getenv("RESTIC_REPOSITORY") - globalOpts.password = os.Getenv("RESTIC_PASSWORD") debug.Log("restic", "main %#v", os.Args)