1
mirror of https://github.com/rclone/rclone synced 2024-12-05 15:55:18 +01:00
rclone/cmd/serve/sftp/handler.go
Nick Craig-Wood 50a3a96e27 serve sftp: fix crash on unsupported operations (eg Readlink)
Before this change the sftp handler returned a nil error for unknown
operations which meant the server crashed when one was encountered.

In particular the "Readlink" operations was causing problems.

After this change the handler returns ErrSshFxOpUnsupported which
signals to the remote end that we don't support that operation.

See: https://forum.rclone.org/t/rclone-serve-sftp-not-working-in-windows/12209
2019-10-09 16:12:21 +01:00

155 lines
3.0 KiB
Go

// +build !plan9
package sftp
import (
"io"
"os"
"syscall"
"time"
"github.com/pkg/sftp"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/vfs"
)
// vfsHandler converts the VFS to be served by SFTP
type vfsHandler struct {
*vfs.VFS
}
// vfsHandler returns a Handlers object with the test handlers.
func newVFSHandler(vfs *vfs.VFS) sftp.Handlers {
v := vfsHandler{VFS: vfs}
return sftp.Handlers{
FileGet: v,
FilePut: v,
FileCmd: v,
FileList: v,
}
}
func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
file, err := v.OpenFile(r.Filepath, os.O_RDONLY, 0777)
if err != nil {
return nil, err
}
return file, nil
}
func (v vfsHandler) Filewrite(r *sftp.Request) (io.WriterAt, error) {
file, err := v.OpenFile(r.Filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
return nil, err
}
return file, nil
}
func (v vfsHandler) Filecmd(r *sftp.Request) error {
switch r.Method {
case "Setstat":
node, err := v.Stat(r.Filepath)
if err != nil {
return err
}
attr := r.Attributes()
if attr.Mtime != 0 {
modTime := time.Unix(int64(attr.Mtime), 0)
err := node.SetModTime(modTime)
if err != nil {
return err
}
}
return nil
case "Rename":
err := v.Rename(r.Filepath, r.Target)
if err != nil {
return err
}
case "Rmdir", "Remove":
node, err := v.Stat(r.Filepath)
if err != nil {
return err
}
err = node.Remove()
if err != nil {
return err
}
case "Mkdir":
dir, leaf, err := v.StatParent(r.Filepath)
if err != nil {
return err
}
_, err = dir.Mkdir(leaf)
if err != nil {
return err
}
case "Symlink":
// FIXME
// _, err := v.fetch(r.Filepath)
// if err != nil {
// return err
// }
// link := newMemFile(r.Target, false)
// link.symlink = r.Filepath
// v.files[r.Target] = link
}
return sftp.ErrSshFxOpUnsupported
}
type listerat []os.FileInfo
// Modeled after strings.Reader's ReadAt() implementation
func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
var n int
if offset >= int64(len(f)) {
return 0, io.EOF
}
n = copy(ls, f[offset:])
if n < len(ls) {
return n, io.EOF
}
return n, nil
}
func (v vfsHandler) Filelist(r *sftp.Request) (l sftp.ListerAt, err error) {
var node vfs.Node
var handle vfs.Handle
switch r.Method {
case "List":
node, err = v.Stat(r.Filepath)
if err != nil {
return nil, err
}
if !node.IsDir() {
return nil, syscall.ENOTDIR
}
handle, err = node.Open(os.O_RDONLY)
if err != nil {
return nil, err
}
defer fs.CheckClose(handle, &err)
fis, err := handle.Readdir(-1)
if err != nil {
return nil, err
}
return listerat(fis), nil
case "Stat":
node, err = v.Stat(r.Filepath)
if err != nil {
return nil, err
}
return listerat([]os.FileInfo{node}), nil
case "Readlink":
// FIXME
// if file.symlink != "" {
// file, err = v.fetch(file.symlink)
// if err != nil {
// return nil, err
// }
// }
// return listerat([]os.FileInfo{file}), nil
}
return nil, sftp.ErrSshFxOpUnsupported
}