mirror of
https://github.com/rclone/rclone.git
synced 2025-04-16 16:18:52 +08:00
Merge bb8c60a22293c7dfec12ade6ef609280132013bf into 0b9671313b14ffe839ecbd7dd2ae5ac7f6f05db8
This commit is contained in:
commit
77024d107b
@ -70,13 +70,20 @@ NB If filename_encryption is "off" then this option will do nothing.`,
|
||||
},
|
||||
}, {
|
||||
Name: "password",
|
||||
Help: "Password or pass phrase for encryption.",
|
||||
Help: "Password or pass phrase for encryption.\n\npassword or password command is required.",
|
||||
IsPassword: true,
|
||||
Required: true,
|
||||
}, {
|
||||
Name: "password_command",
|
||||
Help: "Command to retrieve the password or pass phrase for encryption.\n\npassword or password command is required.",
|
||||
IsPassword: false,
|
||||
}, {
|
||||
Name: "password2",
|
||||
Help: "Password or pass phrase for salt.\n\nOptional but recommended.\nShould be different to the previous password.",
|
||||
IsPassword: true,
|
||||
}, {
|
||||
Name: "password2_command",
|
||||
Help: "Command to retrieve the password or pass phrase for salt.\n\nOptional but recommended.\nShould be different to the previous password.",
|
||||
IsPassword: false,
|
||||
}, {
|
||||
Name: "server_side_across_configs",
|
||||
Default: false,
|
||||
@ -182,18 +189,30 @@ func newCipherForConfig(opt *Options) (*Cipher, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opt.Password == "" {
|
||||
return nil, errors.New("password not set in config file")
|
||||
}
|
||||
password, err := obscure.Reveal(opt.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt password: %w", err)
|
||||
var password string
|
||||
if opt.Password != "" {
|
||||
password, err = obscure.Reveal(opt.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt password: %w", err)
|
||||
}
|
||||
} else if len(opt.PasswordCommand) != 0 {
|
||||
password, err = fs.ExecCommand(opt.PasswordCommand)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("--crypt-password-command failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("--crypt-password or --crypt-password-command is required")
|
||||
}
|
||||
var salt string
|
||||
if opt.Password2 != "" {
|
||||
salt, err = obscure.Reveal(opt.Password2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt password2: %w", err)
|
||||
return nil, fmt.Errorf("failed to decrypt salt: %w", err)
|
||||
}
|
||||
} else if len(opt.Password2Command) != 0 {
|
||||
salt, err = fs.ExecCommand(opt.Password2Command)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("--crypt-password2-command failed: %w", err)
|
||||
}
|
||||
}
|
||||
enc, err := NewNameEncoding(opt.FilenameEncoding)
|
||||
@ -302,18 +321,20 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
|
||||
|
||||
// Options defines the configuration for this backend
|
||||
type Options struct {
|
||||
Remote string `config:"remote"`
|
||||
FilenameEncryption string `config:"filename_encryption"`
|
||||
DirectoryNameEncryption bool `config:"directory_name_encryption"`
|
||||
NoDataEncryption bool `config:"no_data_encryption"`
|
||||
Password string `config:"password"`
|
||||
Password2 string `config:"password2"`
|
||||
ServerSideAcrossConfigs bool `config:"server_side_across_configs"`
|
||||
ShowMapping bool `config:"show_mapping"`
|
||||
PassBadBlocks bool `config:"pass_bad_blocks"`
|
||||
FilenameEncoding string `config:"filename_encoding"`
|
||||
Suffix string `config:"suffix"`
|
||||
StrictNames bool `config:"strict_names"`
|
||||
Remote string `config:"remote"`
|
||||
FilenameEncryption string `config:"filename_encryption"`
|
||||
DirectoryNameEncryption bool `config:"directory_name_encryption"`
|
||||
NoDataEncryption bool `config:"no_data_encryption"`
|
||||
Password string `config:"password"`
|
||||
PasswordCommand fs.SpaceSepList `config:"password_command"`
|
||||
Password2 string `config:"password2"`
|
||||
Password2Command fs.SpaceSepList `config:"password2_command"`
|
||||
ServerSideAcrossConfigs bool `config:"server_side_across_configs"`
|
||||
ShowMapping bool `config:"show_mapping"`
|
||||
PassBadBlocks bool `config:"pass_bad_blocks"`
|
||||
FilenameEncoding string `config:"filename_encoding"`
|
||||
Suffix string `config:"suffix"`
|
||||
StrictNames bool `config:"strict_names"`
|
||||
}
|
||||
|
||||
// Fs represents a wrapped fs.Fs
|
||||
|
@ -95,6 +95,49 @@ func TestStandardBase32768(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPasswordCommand(t *testing.T) {
|
||||
if *fstest.RemoteName != "" {
|
||||
t.Skip("Skipping as -remote set")
|
||||
}
|
||||
tempdir := filepath.Join(os.TempDir(), "rclone-crypt-test-standard")
|
||||
name := "TestCrypt"
|
||||
fstests.Run(t, &fstests.Opt{
|
||||
RemoteName: name + ":",
|
||||
NilObject: (*crypt.Object)(nil),
|
||||
ExtraConfig: []fstests.ExtraConfigItem{
|
||||
{Name: name, Key: "type", Value: "crypt"},
|
||||
{Name: name, Key: "remote", Value: tempdir},
|
||||
{Name: name, Key: "password_command", Value: "echo potato"},
|
||||
{Name: name, Key: "filename_encryption", Value: "standard"},
|
||||
},
|
||||
UnimplementableFsMethods: []string{"OpenWriterAt", "OpenChunkWriter"},
|
||||
UnimplementableObjectMethods: []string{"MimeType"},
|
||||
QuickTestOK: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestPassword2Command(t *testing.T) {
|
||||
if *fstest.RemoteName != "" {
|
||||
t.Skip("Skipping as -remote set")
|
||||
}
|
||||
tempdir := filepath.Join(os.TempDir(), "rclone-crypt-test-standard")
|
||||
name := "TestCrypt"
|
||||
fstests.Run(t, &fstests.Opt{
|
||||
RemoteName: name + ":",
|
||||
NilObject: (*crypt.Object)(nil),
|
||||
ExtraConfig: []fstests.ExtraConfigItem{
|
||||
{Name: name, Key: "type", Value: "crypt"},
|
||||
{Name: name, Key: "remote", Value: tempdir},
|
||||
{Name: name, Key: "password_command", Value: "echo potato"},
|
||||
{Name: name, Key: "password2_command", Value: "echo potato"},
|
||||
{Name: name, Key: "filename_encryption", Value: "standard"},
|
||||
},
|
||||
UnimplementableFsMethods: []string{"OpenWriterAt", "OpenChunkWriter"},
|
||||
UnimplementableObjectMethods: []string{"MimeType"},
|
||||
QuickTestOK: true,
|
||||
})
|
||||
}
|
||||
|
||||
// TestOff runs integration tests against the remote
|
||||
func TestOff(t *testing.T) {
|
||||
if *fstest.RemoteName != "" {
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
@ -179,27 +178,9 @@ func GetPasswordCommand(ctx context.Context) (pass string, err error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command(ci.PasswordCommand[0], ci.PasswordCommand[1:]...)
|
||||
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
err = cmd.Run()
|
||||
pass, err = fs.ExecCommand(ci.PasswordCommand)
|
||||
if err != nil {
|
||||
// One does not always get the stderr returned in the wrapped error.
|
||||
fs.Errorf(nil, "Using --password-command returned: %v", err)
|
||||
if ers := strings.TrimSpace(stderr.String()); ers != "" {
|
||||
fs.Errorf(nil, "--password-command stderr: %s", ers)
|
||||
}
|
||||
return pass, fmt.Errorf("password command failed: %w", err)
|
||||
}
|
||||
pass = strings.Trim(stdout.String(), "\r\n")
|
||||
if pass == "" {
|
||||
return pass, errors.New("--password-command returned empty string")
|
||||
return "", fmt.Errorf("--password-command failed: %w", err)
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
@ -3,7 +3,11 @@ package fs
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CommaSepList is a comma separated config value
|
||||
@ -92,3 +96,32 @@ func (gl *genericList) scan(sep rune, s fmt.ScanState, ch rune) error {
|
||||
}
|
||||
return gl.set(sep, bytes.TrimSpace(token))
|
||||
}
|
||||
|
||||
// ExecCommand executes a command and returns the output as a string
|
||||
// It returns an error if the command fails or the output is empty
|
||||
func ExecCommand(l SpaceSepList) (pass string, err error) {
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command(l[0], l[1:]...)
|
||||
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
// One does not always get the stderr returned in the wrapped error.
|
||||
Errorf(nil, "Executing command %q failed: %v", l, err)
|
||||
if ers := strings.TrimSpace(stderr.String()); ers != "" {
|
||||
Errorf(nil, "stderr: %q", ers)
|
||||
}
|
||||
return pass, fmt.Errorf("executing command %q failed: %w", l, err)
|
||||
}
|
||||
pass = strings.Trim(stdout.String(), "\r\n")
|
||||
if pass == "" {
|
||||
return pass, errors.New("executing command %q failed: returned empty string")
|
||||
}
|
||||
return pass, nil
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user