forked from TrueCloudLab/restic
sftp: Allow password entry
This was a bit tricky: We start the ssh binary, but we want it to ignore SIGINT. In contrast, restic itself should process SIGINT and clean up properly. Before, we used `setsid()` to give the ssh process its own process group, but that means it cannot prompt the user for a password because the tty is gone. So, now we're passing in two functions that ignore SIGINT just before the ssh process is started and re-install it after start.
This commit is contained in:
parent
45a09c76ff
commit
fb9729fdb9
6 changed files with 24 additions and 41 deletions
|
@ -470,7 +470,7 @@ func open(s string, opts options.Options) (restic.Backend, error) {
|
||||||
case "local":
|
case "local":
|
||||||
be, err = local.Open(cfg.(local.Config))
|
be, err = local.Open(cfg.(local.Config))
|
||||||
case "sftp":
|
case "sftp":
|
||||||
be, err = sftp.Open(cfg.(sftp.Config))
|
be, err = sftp.Open(cfg.(sftp.Config), SuspendSignalHandler, InstallSignalHandler)
|
||||||
case "s3":
|
case "s3":
|
||||||
be, err = s3.Open(cfg.(s3.Config))
|
be, err = s3.Open(cfg.(s3.Config))
|
||||||
case "gs":
|
case "gs":
|
||||||
|
@ -522,7 +522,7 @@ func create(s string, opts options.Options) (restic.Backend, error) {
|
||||||
case "local":
|
case "local":
|
||||||
return local.Create(cfg.(local.Config))
|
return local.Create(cfg.(local.Config))
|
||||||
case "sftp":
|
case "sftp":
|
||||||
return sftp.Create(cfg.(sftp.Config))
|
return sftp.Create(cfg.(sftp.Config), SuspendSignalHandler, InstallSignalHandler)
|
||||||
case "s3":
|
case "s3":
|
||||||
return s3.Create(cfg.(s3.Config))
|
return s3.Create(cfg.(s3.Config))
|
||||||
case "gs":
|
case "gs":
|
||||||
|
|
|
@ -46,7 +46,7 @@ func TestLayout(t *testing.T) {
|
||||||
Command: fmt.Sprintf("%q -e", sftpServer),
|
Command: fmt.Sprintf("%q -e", sftpServer),
|
||||||
Path: repo,
|
Path: repo,
|
||||||
Layout: test.layout,
|
Layout: test.layout,
|
||||||
})
|
}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ var _ restic.Backend = &SFTP{}
|
||||||
|
|
||||||
const defaultLayout = "default"
|
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)
|
debug.Log("start client %v %v", program, args)
|
||||||
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
||||||
// command. This assumes that passwordless login is correctly configured.
|
// 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
|
// get stdin and stdout
|
||||||
wr, err := cmd.StdinPipe()
|
wr, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,11 +65,19 @@ func startClient(program string, args ...string) (*SFTP, error) {
|
||||||
return nil, errors.Wrap(err, "cmd.StdoutPipe")
|
return nil, errors.Wrap(err, "cmd.StdoutPipe")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if preExec != nil {
|
||||||
|
preExec()
|
||||||
|
}
|
||||||
|
|
||||||
// start the process
|
// start the process
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return nil, errors.Wrap(err, "cmd.Start")
|
return nil, errors.Wrap(err, "cmd.Start")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if postExec != nil {
|
||||||
|
postExec()
|
||||||
|
}
|
||||||
|
|
||||||
// wait in a different goroutine
|
// wait in a different goroutine
|
||||||
ch := make(chan error, 1)
|
ch := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -104,8 +109,9 @@ func (r *SFTP) clientError() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens an sftp backend as described by the config by running
|
// Open opens an sftp backend as described by the config by running
|
||||||
// "ssh" with the appropriate arguments (or cfg.Command, if set).
|
// "ssh" with the appropriate arguments (or cfg.Command, if set). The function
|
||||||
func Open(cfg Config) (*SFTP, error) {
|
// 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)
|
debug.Log("open backend with config %#v", cfg)
|
||||||
|
|
||||||
cmd, args, err := buildSSHCommand(cfg)
|
cmd, args, err := buildSSHCommand(cfg)
|
||||||
|
@ -113,7 +119,7 @@ func Open(cfg Config) (*SFTP, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sftp, err := startClient(cmd, args...)
|
sftp, err := startClient(preExec, postExec, cmd, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("unable to start program: %v", err)
|
debug.Log("unable to start program: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -225,15 +231,16 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
|
||||||
return cmd, args, nil
|
return cmd, args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates an sftp backend as described by the config by running
|
// Create creates an sftp backend as described by the config by running "ssh"
|
||||||
// "ssh" with the appropriate arguments (or cfg.Command, if set).
|
// with the appropriate arguments (or cfg.Command, if set). The function
|
||||||
func Create(cfg Config) (*SFTP, error) {
|
// 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)
|
cmd, args, err := buildSSHCommand(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sftp, err := startClient(cmd, args...)
|
sftp, err := startClient(preExec, postExec, cmd, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("unable to start program: %v", err)
|
debug.Log("unable to start program: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -261,7 +268,7 @@ func Create(cfg Config) (*SFTP, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// open backend
|
// open backend
|
||||||
return Open(cfg)
|
return Open(cfg, preExec, postExec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Location returns this backend's location (the directory name).
|
// Location returns this backend's location (the directory name).
|
||||||
|
|
|
@ -51,13 +51,13 @@ func newTestSuite(t testing.TB) *test.Suite {
|
||||||
// CreateFn is a function that creates a temporary repository for the tests.
|
// CreateFn is a function that creates a temporary repository for the tests.
|
||||||
Create: func(config interface{}) (restic.Backend, error) {
|
Create: func(config interface{}) (restic.Backend, error) {
|
||||||
cfg := config.(sftp.Config)
|
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.
|
// OpenFn is a function that opens a previously created temporary repository.
|
||||||
Open: func(config interface{}) (restic.Backend, error) {
|
Open: func(config interface{}) (restic.Backend, error) {
|
||||||
cfg := config.(sftp.Config)
|
cfg := config.(sftp.Config)
|
||||||
return sftp.Open(cfg)
|
return sftp.Open(cfg, nil, nil)
|
||||||
},
|
},
|
||||||
|
|
||||||
// CleanupFn removes data created during the tests.
|
// CleanupFn removes data created during the tests.
|
||||||
|
|
|
@ -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}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package sftp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ignoreSigIntProcAttr returns a default syscall.SysProcAttr
|
|
||||||
// on Windows.
|
|
||||||
func ignoreSigIntProcAttr() *syscall.SysProcAttr {
|
|
||||||
return &syscall.SysProcAttr{}
|
|
||||||
}
|
|
Loading…
Reference in a new issue