mirror of
https://github.com/rclone/rclone.git
synced 2025-01-13 20:38:12 +02:00
sftp: remember entered password in AskPass mode
As reported in https://github.com/rclone/rclone/issues/4660#issuecomment-705502792 After switching to a password callback function, if the ssh connection aborts and needs to be reconnected then the user is-reprompted for their password. Instead we now remember the password they entered and just give that back. We do lose the ability for them to correct mistakes, but that's the situation from before switching to callbacks. We keep the benefits of not asking for passwords until the SSH connection succeeds (right known_hosts entry, for example). This required a small refactor of how `f := &Fs{}` was built, so we can store the saved password in the Fs object
This commit is contained in:
parent
7428e47ebc
commit
bbddadbd04
@ -235,6 +235,7 @@ type Fs struct {
|
|||||||
poolMu sync.Mutex
|
poolMu sync.Mutex
|
||||||
pool []*conn
|
pool []*conn
|
||||||
pacer *fs.Pacer // pacer for operations
|
pacer *fs.Pacer // pacer for operations
|
||||||
|
savedpswd string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
|
// Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
|
||||||
@ -413,6 +414,10 @@ func (f *Fs) putSftpConnection(pc **conn, err error) {
|
|||||||
// NewFs creates a new Fs object from the name and root. It connects to
|
// NewFs creates a new Fs object from the name and root. It connects to
|
||||||
// the host specified in the config file.
|
// the host specified in the config file.
|
||||||
func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
||||||
|
// This will hold the Fs object. We need to create it here
|
||||||
|
// so we can refer to it in the SSH callback, but it's populated
|
||||||
|
// in NewFsWithConnection
|
||||||
|
f := &Fs{}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// Parse config into Options struct
|
// Parse config into Options struct
|
||||||
opt := new(Options)
|
opt := new(Options)
|
||||||
@ -566,33 +571,42 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
|||||||
// Config for password if none was defined and we're allowed to
|
// Config for password if none was defined and we're allowed to
|
||||||
// We don't ask now; we ask if the ssh connection succeeds
|
// We don't ask now; we ask if the ssh connection succeeds
|
||||||
if opt.Pass == "" && opt.AskPassword {
|
if opt.Pass == "" && opt.AskPassword {
|
||||||
sshConfig.Auth = append(sshConfig.Auth, ssh.PasswordCallback(getPass))
|
sshConfig.Auth = append(sshConfig.Auth, ssh.PasswordCallback(f.getPass))
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewFsWithConnection(ctx, name, root, m, opt, sshConfig)
|
return NewFsWithConnection(ctx, f, name, root, m, opt, sshConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're in password mode and ssh connection succeeds then this
|
// If we're in password mode and ssh connection succeeds then this
|
||||||
// callback is called
|
// callback is called. First time around we ask the user, and then
|
||||||
func getPass() (string, error) {
|
// save it so on reconnection we give back the previous string.
|
||||||
_, _ = fmt.Fprint(os.Stderr, "Enter SFTP password: ")
|
// This removes the ability to let the user correct a mistaken entry,
|
||||||
return config.ReadPassword(), nil
|
// but means that reconnects are transparent.
|
||||||
|
// We'll re-use config.Pass for this, 'cos we know it's not been
|
||||||
|
// specified.
|
||||||
|
func (f *Fs) getPass() (string, error) {
|
||||||
|
for f.savedpswd == "" {
|
||||||
|
_, _ = fmt.Fprint(os.Stderr, "Enter SFTP password: ")
|
||||||
|
f.savedpswd = config.ReadPassword()
|
||||||
|
}
|
||||||
|
return f.savedpswd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFsWithConnection creates a new Fs object from the name and root and an ssh.ClientConfig. It connects to
|
// NewFsWithConnection creates a new Fs object from the name and root and an ssh.ClientConfig. It connects to
|
||||||
// the host specified in the ssh.ClientConfig
|
// the host specified in the ssh.ClientConfig
|
||||||
func NewFsWithConnection(ctx context.Context, name string, root string, m configmap.Mapper, opt *Options, sshConfig *ssh.ClientConfig) (fs.Fs, error) {
|
func NewFsWithConnection(ctx context.Context, f *Fs, name string, root string, m configmap.Mapper, opt *Options, sshConfig *ssh.ClientConfig) (fs.Fs, error) {
|
||||||
f := &Fs{
|
// Populate the Filesystem Object
|
||||||
name: name,
|
f.name = name
|
||||||
root: root,
|
f.root = root
|
||||||
absRoot: root,
|
f.absRoot = root
|
||||||
opt: *opt,
|
f.opt = *opt
|
||||||
m: m,
|
f.m = m
|
||||||
config: sshConfig,
|
f.config = sshConfig
|
||||||
url: "sftp://" + opt.User + "@" + opt.Host + ":" + opt.Port + "/" + root,
|
f.url = "sftp://" + opt.User + "@" + opt.Host + ":" + opt.Port + "/" + root
|
||||||
mkdirLock: newStringLock(),
|
f.mkdirLock = newStringLock()
|
||||||
pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
|
f.pacer = fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant)))
|
||||||
}
|
f.savedpswd = ""
|
||||||
|
|
||||||
f.features = (&fs.Features{
|
f.features = (&fs.Features{
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
SlowHash: true,
|
SlowHash: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user