mirror of
https://github.com/rclone/rclone.git
synced 2025-04-19 18:31:10 +08:00
refactor: streamline password handling and command execution in crypt backend
This commit is contained in:
parent
ffdf140bbd
commit
8c88d5046a
@ -2,15 +2,11 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -192,20 +188,31 @@ func newCipherForConfig(opt *Options) (*Cipher, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
password, err := evalPassword(opt.Password, opt.PasswordCommand)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to eval password: %w", err)
|
||||
}
|
||||
if password == "" {
|
||||
return nil, errors.New("password or password_command not set in config file")
|
||||
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
|
||||
password2, err := evalPassword(opt.Password2, opt.Password2Command)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to eval password2: %w", err)
|
||||
}
|
||||
if password2 != "" {
|
||||
salt = password2
|
||||
if opt.Password2 != "" {
|
||||
salt, err = obscure.Reveal(opt.Password2)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
@ -220,38 +227,6 @@ func newCipherForConfig(opt *Options) (*Cipher, error) {
|
||||
return cipher, nil
|
||||
}
|
||||
|
||||
func evalPassword(password string, passwordCommand string) (string, error) {
|
||||
if password != "" {
|
||||
revealed, err := obscure.Reveal(password)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decrypt password: %w", err)
|
||||
}
|
||||
return revealed, nil
|
||||
}
|
||||
if passwordCommand != "" {
|
||||
var stdout bytes.Buffer
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", passwordCommand)
|
||||
default:
|
||||
cmd = exec.Command("sh", "-c", passwordCommand)
|
||||
}
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("failed to run password command: %w", err)
|
||||
}
|
||||
pass := strings.TrimSpace(stdout.String())
|
||||
if pass == "" {
|
||||
return "", errors.New("password command returned empty string")
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// NewCipher constructs a Cipher for the given config
|
||||
func NewCipher(m configmap.Mapper) (*Cipher, error) {
|
||||
// Parse config into Options struct
|
||||
@ -342,20 +317,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"`
|
||||
PasswordCommand string `config:"password_command"`
|
||||
Password2 string `config:"password2"`
|
||||
Password2Command string `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"`
|
||||
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
|
||||
|
@ -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,30 @@ func (gl *genericList) scan(sep rune, s fmt.ScanState, ch rune) error {
|
||||
}
|
||||
return gl.set(sep, bytes.TrimSpace(token))
|
||||
}
|
||||
|
||||
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: no output")
|
||||
}
|
||||
return pass, nil
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user