Merge pull request #1270 from restic/sftp-allow-password-prompt

sftp: Allow password entry
This commit is contained in:
Alexander Neumann 2017-09-23 22:13:04 +02:00
commit 9c6b7f688e
7 changed files with 39 additions and 46 deletions

View file

@ -14,15 +14,25 @@ var cleanupHandlers struct {
sync.Mutex
list []func() error
done bool
sigintCh chan os.Signal
}
var stderr = os.Stderr
func init() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
cleanupHandlers.sigintCh = make(chan os.Signal)
go CleanupHandler(cleanupHandlers.sigintCh)
InstallSignalHandler()
}
go CleanupHandler(c)
// InstallSignalHandler listens for SIGINT and triggers the cleanup handlers.
func InstallSignalHandler() {
signal.Notify(cleanupHandlers.sigintCh, syscall.SIGINT)
}
// SuspendSignalHandler removes the signal handler for SIGINT.
func SuspendSignalHandler() {
signal.Reset(syscall.SIGINT)
}
// AddCleanupHandler adds the function f to the list of cleanup handlers so

View file

@ -470,7 +470,7 @@ func open(s string, opts options.Options) (restic.Backend, error) {
case "local":
be, err = local.Open(cfg.(local.Config))
case "sftp":
be, err = sftp.Open(cfg.(sftp.Config))
be, err = sftp.Open(cfg.(sftp.Config), SuspendSignalHandler, InstallSignalHandler)
case "s3":
be, err = s3.Open(cfg.(s3.Config))
case "gs":
@ -522,7 +522,7 @@ func create(s string, opts options.Options) (restic.Backend, error) {
case "local":
return local.Create(cfg.(local.Config))
case "sftp":
return sftp.Create(cfg.(sftp.Config))
return sftp.Create(cfg.(sftp.Config), SuspendSignalHandler, InstallSignalHandler)
case "s3":
return s3.Create(cfg.(s3.Config))
case "gs":

View file

@ -46,7 +46,7 @@ func TestLayout(t *testing.T) {
Command: fmt.Sprintf("%q -e", sftpServer),
Path: repo,
Layout: test.layout,
})
}, nil, nil)
if err != nil {
t.Fatal(err)
}

View file

@ -36,7 +36,7 @@ var _ restic.Backend = &SFTP{}
const defaultLayout = "default"
func startClient(program string, args ...string) (*SFTP, error) {
func startClient(preExec, postExec func(), program string, args ...string) (*SFTP, error) {
debug.Log("start client %v %v", program, args)
// Connect to a remote host and request the sftp subsystem via the 'ssh'
// command. This assumes that passwordless login is correctly configured.
@ -55,9 +55,6 @@ func startClient(program string, args ...string) (*SFTP, error) {
}
}()
// ignore signals sent to the parent (e.g. SIGINT)
cmd.SysProcAttr = ignoreSigIntProcAttr()
// get stdin and stdout
wr, err := cmd.StdinPipe()
if err != nil {
@ -68,11 +65,19 @@ func startClient(program string, args ...string) (*SFTP, error) {
return nil, errors.Wrap(err, "cmd.StdoutPipe")
}
if preExec != nil {
preExec()
}
// start the process
if err := cmd.Start(); err != nil {
return nil, errors.Wrap(err, "cmd.Start")
}
if postExec != nil {
postExec()
}
// wait in a different goroutine
ch := make(chan error, 1)
go func() {
@ -104,8 +109,9 @@ func (r *SFTP) clientError() error {
}
// Open opens an sftp backend as described by the config by running
// "ssh" with the appropriate arguments (or cfg.Command, if set).
func Open(cfg Config) (*SFTP, error) {
// "ssh" with the appropriate arguments (or cfg.Command, if set). The function
// preExec is run just before, postExec just after starting a program.
func Open(cfg Config, preExec, postExec func()) (*SFTP, error) {
debug.Log("open backend with config %#v", cfg)
cmd, args, err := buildSSHCommand(cfg)
@ -113,7 +119,7 @@ func Open(cfg Config) (*SFTP, error) {
return nil, err
}
sftp, err := startClient(cmd, args...)
sftp, err := startClient(preExec, postExec, cmd, args...)
if err != nil {
debug.Log("unable to start program: %v", err)
return nil, err
@ -225,15 +231,16 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
return cmd, args, nil
}
// Create creates an sftp backend as described by the config by running
// "ssh" with the appropriate arguments (or cfg.Command, if set).
func Create(cfg Config) (*SFTP, error) {
// Create creates an sftp backend as described by the config by running "ssh"
// with the appropriate arguments (or cfg.Command, if set). The function
// preExec is run just before, postExec just after starting a program.
func Create(cfg Config, preExec, postExec func()) (*SFTP, error) {
cmd, args, err := buildSSHCommand(cfg)
if err != nil {
return nil, err
}
sftp, err := startClient(cmd, args...)
sftp, err := startClient(preExec, postExec, cmd, args...)
if err != nil {
debug.Log("unable to start program: %v", err)
return nil, err
@ -261,7 +268,7 @@ func Create(cfg Config) (*SFTP, error) {
}
// open backend
return Open(cfg)
return Open(cfg, preExec, postExec)
}
// Location returns this backend's location (the directory name).

View file

@ -51,13 +51,13 @@ func newTestSuite(t testing.TB) *test.Suite {
// CreateFn is a function that creates a temporary repository for the tests.
Create: func(config interface{}) (restic.Backend, error) {
cfg := config.(sftp.Config)
return sftp.Create(cfg)
return sftp.Create(cfg, nil, nil)
},
// OpenFn is a function that opens a previously created temporary repository.
Open: func(config interface{}) (restic.Backend, error) {
cfg := config.(sftp.Config)
return sftp.Open(cfg)
return sftp.Open(cfg, nil, nil)
},
// CleanupFn removes data created during the tests.

View file

@ -1,13 +0,0 @@
// +build !windows
package sftp
import (
"syscall"
)
// ignoreSigIntProcAttr returns a syscall.SysProcAttr that
// disables SIGINT on parent.
func ignoreSigIntProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setsid: true}
}

View file

@ -1,11 +0,0 @@
package sftp
import (
"syscall"
)
// ignoreSigIntProcAttr returns a default syscall.SysProcAttr
// on Windows.
func ignoreSigIntProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{}
}