diff --git a/fs/config.go b/fs/config.go index 738820551..d702fdb34 100644 --- a/fs/config.go +++ b/fs/config.go @@ -493,7 +493,7 @@ func loadConfigFile() (*goconfig.ConfigFile, error) { var out []byte for { if len(configKey) == 0 && envpw != "" { - err := setPassword(envpw) + err := setConfigPassword(envpw) if err != nil { fmt.Println("Using RCLONE_CONFIG_PASS returned:", err) envpw = "" @@ -505,7 +505,7 @@ func loadConfigFile() (*goconfig.ConfigFile, error) { if !*AskPassword { return nil, errors.New("unable to decrypt configuration and not allowed to ask for password - set RCLONE_CONFIG_PASS to your configuration password") } - getPassword("Enter configuration password:") + getConfigPassword("Enter configuration password:") } // Nonce is first 24 bytes of the ciphertext @@ -529,16 +529,57 @@ func loadConfigFile() (*goconfig.ConfigFile, error) { return goconfig.LoadFromReader(bytes.NewBuffer(out)) } -// getPassword will query the user for a password the +// checkPassword normalises and validates the password +func checkPassword(password string) (string, error) { + if !utf8.ValidString(password) { + return "", errors.New("password contains invalid utf8 characters") + } + // Remove leading+trailing whitespace + password = strings.TrimSpace(password) + // Normalize to reduce weird variations. + password = norm.NFKC.String(password) + if len(password) == 0 { + return "", errors.New("no characters in password") + } + return password, nil +} + +// GetPassword asks the user for a password with the prompt given. +func GetPassword(prompt string) string { + fmt.Println(prompt) + for { + fmt.Print("password:") + password := ReadPassword() + password, err := checkPassword(password) + if err == nil { + return password + } + fmt.Printf("Bad password: %v", err) + } +} + +// ChangePassword will query the user twice for the named password. If +// the same password is entered it is returned. +func ChangePassword(name string) string { + for { + a := GetPassword(fmt.Sprintf("Enter %s password:", name)) + b := GetPassword(fmt.Sprintf("Confirm %s password:", name)) + if a == b { + return a + } + fmt.Println("Passwords do not match!") + } +} + +// getConfigPassword will query the user for a password the // first time it is required. -func getPassword(q string) { +func getConfigPassword(q string) { if len(configKey) != 0 { return } for { - fmt.Println(q) - fmt.Print("password:") - err := setPassword(ReadPassword()) + password := GetPassword(q) + err := setConfigPassword(password) if err == nil { return } @@ -546,24 +587,17 @@ func getPassword(q string) { } } -// setPassword will set the configKey to the hash of +// setConfigPassword will set the configKey to the hash of // the password. If the length of the password is // zero after trimming+normalization, an error is returned. -func setPassword(password string) error { - if !utf8.ValidString(password) { - return errors.New("password contains invalid utf8 characters") - } - // Remove leading+trailing whitespace - password = strings.TrimSpace(password) - - // Normalize to reduce weird variations. - password = norm.NFKC.String(password) - if len(password) == 0 { - return errors.New("no characters in password") +func setConfigPassword(password string) error { + password, err := checkPassword(password) + if err != nil { + return err } // Create SHA256 has of the password sha := sha256.New() - _, err := sha.Write([]byte("[" + password + "][rclone-config]")) + _, err = sha.Write([]byte("[" + password + "][rclone-config]")) if err != nil { return err } @@ -571,6 +605,17 @@ func setPassword(password string) error { return nil } +// changeConfigPassword will query the user twice +// for a password. If the same password is entered +// twice the key is updated. +func changeConfigPassword() { + err := setConfigPassword(ChangePassword("NEW configuration")) + if err != nil { + fmt.Printf("Failed to set config password: %v\n", err) + return + } +} + // SaveConfig saves configuration file. // if configKey has been set, the file will be encrypted. func SaveConfig() { @@ -808,6 +853,9 @@ func RemoteConfig(name string) { // ChooseOption asks the user to choose an option func ChooseOption(o *Option) string { fmt.Println(o.Help) + if o.IsPassword { + return MustObscure(ChangePassword("the")) + } if len(o.Examples) > 0 { var values []string var help []string @@ -854,20 +902,21 @@ func NewRemote(name string) { SaveConfig() return } - EditRemote(name) + EditRemote(fs, name) } // EditRemote gets the user to edit a remote -func EditRemote(name string) { +func EditRemote(fs *RegInfo, name string) { ShowRemote(name) fmt.Printf("Edit remote\n") for { - for _, key := range ConfigFile.GetKeyList(name) { + for _, option := range fs.Options { + key := option.Name value := ConfigFile.MustValue(name, key) - fmt.Printf("Press enter to accept current value, or type in a new one\n") - fmt.Printf("%s = %s>", key, value) - newValue := ReadLine() - if newValue != "" { + fmt.Printf("Value %q = %q\n", key, value) + fmt.Printf("Edit? (y/n)>\n") + if Confirm() { + newValue := ChooseOption(&option) ConfigFile.SetValue(name, key, newValue) } } @@ -901,7 +950,11 @@ func EditConfig() { switch i := Command(what); i { case 'e': name := ChooseRemote() - EditRemote(name) + fs, err := Find(ConfigFile.MustValue(name, "type")) + if err != nil { + log.Fatalf("Failed to find fs: %v", err) + } + EditRemote(fs, name) case 'n': nameLoop: for { @@ -941,7 +994,7 @@ func SetPassword() { what := []string{"cChange Password", "uUnencrypt configuration", "qQuit to main menu"} switch i := Command(what); i { case 'c': - changePassword() + changeConfigPassword() SaveConfig() fmt.Println("Password changed") continue @@ -959,7 +1012,7 @@ func SetPassword() { what := []string{"aAdd Password", "qQuit to main menu"} switch i := Command(what); i { case 'a': - changePassword() + changeConfigPassword() SaveConfig() fmt.Println("Password set") continue @@ -970,25 +1023,6 @@ func SetPassword() { } } -// changePassword will query the user twice -// for a password. If the same password is entered -// twice the key is updated. -func changePassword() { - for { - configKey = nil - getPassword("Enter NEW configuration password:") - a := configKey - // re-enter password - configKey = nil - getPassword("Confirm NEW password:") - b := configKey - if bytes.Equal(a, b) { - return - } - fmt.Println("Passwords does not match!") - } -} - // Authorize is for remote authorization of headless machines. // // It expects 1 or 3 arguments diff --git a/fs/config_test.go b/fs/config_test.go index 868259907..5f69ebcb3 100644 --- a/fs/config_test.go +++ b/fs/config_test.go @@ -162,7 +162,7 @@ func TestConfigLoadEncrypted(t *testing.T) { }() // Set correct password - err = setPassword("asdf") + err = setConfigPassword("asdf") require.NoError(t, err) c, err := loadConfigFile() require.NoError(t, err) @@ -208,11 +208,11 @@ func TestPassword(t *testing.T) { }() var err error // Empty password should give error - err = setPassword(" \t ") + err = setConfigPassword(" \t ") require.Error(t, err) // Test invalid utf8 sequence - err = setPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc") + err = setConfigPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc") require.Error(t, err) // Simple check of wrong passwords @@ -230,11 +230,11 @@ func TestPassword(t *testing.T) { } func hashedKeyCompare(t *testing.T, a, b string, shouldMatch bool) { - err := setPassword(a) + err := setConfigPassword(a) require.NoError(t, err) k1 := configKey - err = setPassword(b) + err = setConfigPassword(b) require.NoError(t, err) k2 := configKey diff --git a/fs/fs.go b/fs/fs.go index ef6ce71d9..c377d2c69 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -66,10 +66,11 @@ type RegInfo struct { // Option is describes an option for the config wizard type Option struct { - Name string - Help string - Optional bool - Examples OptionExamples + Name string + Help string + Optional bool + IsPassword bool + Examples OptionExamples } // OptionExamples is a slice of examples