serve sftp: add --stdio flag to serve via stdio - fixes #5311

This commit is contained in:
Tom 2021-05-28 12:40:32 +00:00 committed by GitHub
parent 0574ebf44a
commit 04a1f673f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 13 deletions

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"os"
"regexp" "regexp"
"strings" "strings"
@ -14,7 +15,9 @@ import (
"github.com/pkg/sftp" "github.com/pkg/sftp"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/terminal"
"github.com/rclone/rclone/vfs" "github.com/rclone/rclone/vfs"
"github.com/rclone/rclone/vfs/vfsflags"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -225,19 +228,8 @@ func (c *conn) handleChannel(newChannel ssh.NewChannel) {
// Wait for either subsystem "sftp" or "exec" request // Wait for either subsystem "sftp" or "exec" request
if <-isSFTP { if <-isSFTP {
fs.Debugf(c.what, "Starting SFTP server") if err := serveChannel(channel, c.handlers, c.what); err != nil {
server := sftp.NewRequestServer(channel, c.handlers) fs.Errorf(c.what, "Failed to serve SFPT: %v", err)
defer func() {
err := server.Close()
if err != nil && err != io.EOF {
fs.Debugf(c.what, "Failed to close server: %v", err)
}
}()
err = server.Serve()
if err == io.EOF || err == nil {
fs.Debugf(c.what, "exited session")
} else {
fs.Errorf(c.what, "completed with error: %v", err)
} }
} else { } else {
var rc = uint32(0) var rc = uint32(0)
@ -263,3 +255,54 @@ func (c *conn) handleChannels(chans <-chan ssh.NewChannel) {
go c.handleChannel(newChannel) go c.handleChannel(newChannel)
} }
} }
func serveChannel(rwc io.ReadWriteCloser, h sftp.Handlers, what string) error {
fs.Debugf(what, "Starting SFTP server")
server := sftp.NewRequestServer(rwc, h)
defer func() {
err := server.Close()
if err != nil && err != io.EOF {
fs.Debugf(what, "Failed to close server: %v", err)
}
}()
err := server.Serve()
if err != nil && err != io.EOF {
return errors.Wrap(err, "completed with error")
}
fs.Debugf(what, "exited session")
return nil
}
func serveStdio(f fs.Fs) error {
if terminal.IsTerminal(int(os.Stdout.Fd())) {
return errors.New("refusing to run SFTP server directly on a terminal. Please let sshd start rclone, by connecting with sftp or sshfs")
}
sshChannel := &stdioChannel{
stdin: os.Stdin,
stdout: os.Stdout,
}
handlers := newVFSHandler(vfs.New(f, &vfsflags.Opt))
return serveChannel(sshChannel, handlers, "stdio")
}
type stdioChannel struct {
stdin *os.File
stdout *os.File
}
func (c *stdioChannel) Read(data []byte) (int, error) {
return c.stdin.Read(data)
}
func (c *stdioChannel) Write(data []byte) (int, error) {
return c.stdout.Write(data)
}
func (c *stdioChannel) Close() error {
err1 := c.stdin.Close()
err2 := c.stdout.Close()
if err1 != nil {
return err1
}
return err2
}

View File

@ -27,6 +27,7 @@ type Options struct {
User string // single username User string // single username
Pass string // password for user Pass string // password for user
NoAuth bool // allow no authentication on connections NoAuth bool // allow no authentication on connections
Stdio bool // serve on stdio
} }
// DefaultOpt is the default values used for Options // DefaultOpt is the default values used for Options
@ -47,6 +48,7 @@ func AddFlags(flagSet *pflag.FlagSet, Opt *Options) {
flags.StringVarP(flagSet, &Opt.User, "user", "", Opt.User, "User name for authentication.") flags.StringVarP(flagSet, &Opt.User, "user", "", Opt.User, "User name for authentication.")
flags.StringVarP(flagSet, &Opt.Pass, "pass", "", Opt.Pass, "Password for authentication.") flags.StringVarP(flagSet, &Opt.Pass, "pass", "", Opt.Pass, "Password for authentication.")
flags.BoolVarP(flagSet, &Opt.NoAuth, "no-auth", "", Opt.NoAuth, "Allow connections with no authentication if set.") flags.BoolVarP(flagSet, &Opt.NoAuth, "no-auth", "", Opt.NoAuth, "Allow connections with no authentication if set.")
flags.BoolVarP(flagSet, &Opt.Stdio, "stdio", "", Opt.Stdio, "Run an sftp server on run stdin/stdout")
} }
func init() { func init() {
@ -90,6 +92,11 @@ reachable externally then supply "--addr :2022" for example.
Note that the default of "--vfs-cache-mode off" is fine for the rclone Note that the default of "--vfs-cache-mode off" is fine for the rclone
sftp backend, but it may not be with other SFTP clients. sftp backend, but it may not be with other SFTP clients.
If --stdio is specified, rclone will serve SFTP over stdio, which can
be used with sshd via ~/.ssh/authorized_keys, for example:
restrict,command="rclone serve sftp --stdio ./photos" ssh-rsa ...
` + vfs.Help + proxy.Help, ` + vfs.Help + proxy.Help,
Run: func(command *cobra.Command, args []string) { Run: func(command *cobra.Command, args []string) {
var f fs.Fs var f fs.Fs
@ -100,6 +107,9 @@ sftp backend, but it may not be with other SFTP clients.
cmd.CheckArgs(0, 0, command, args) cmd.CheckArgs(0, 0, command, args)
} }
cmd.Run(false, true, command, func() error { cmd.Run(false, true, command, func() error {
if Opt.Stdio {
return serveStdio(f)
}
s := newServer(context.Background(), f, &Opt) s := newServer(context.Background(), f, &Opt)
err := s.Serve() err := s.Serve()
if err != nil { if err != nil {