github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/sftp/sftp.go (about)

     1  // Package sftp provides a filesystem interface using github.com/pkg/sftp
     2  
     3  // +build !plan9
     4  
     5  package sftp
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/user"
    15  	"path"
    16  	"regexp"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/ncw/rclone/fs"
    23  	"github.com/ncw/rclone/fs/config"
    24  	"github.com/ncw/rclone/fs/config/configmap"
    25  	"github.com/ncw/rclone/fs/config/configstruct"
    26  	"github.com/ncw/rclone/fs/config/obscure"
    27  	"github.com/ncw/rclone/fs/fshttp"
    28  	"github.com/ncw/rclone/fs/hash"
    29  	"github.com/ncw/rclone/lib/env"
    30  	"github.com/ncw/rclone/lib/readers"
    31  	"github.com/pkg/errors"
    32  	"github.com/pkg/sftp"
    33  	sshagent "github.com/xanzy/ssh-agent"
    34  	"golang.org/x/crypto/ssh"
    35  	"golang.org/x/time/rate"
    36  )
    37  
    38  const (
    39  	connectionsPerSecond = 10 // don't make more than this many ssh connections/s
    40  )
    41  
    42  var (
    43  	currentUser = readCurrentUser()
    44  )
    45  
    46  func init() {
    47  	fsi := &fs.RegInfo{
    48  		Name:        "sftp",
    49  		Description: "SSH/SFTP Connection",
    50  		NewFs:       NewFs,
    51  		Options: []fs.Option{{
    52  			Name:     "host",
    53  			Help:     "SSH host to connect to",
    54  			Required: true,
    55  			Examples: []fs.OptionExample{{
    56  				Value: "example.com",
    57  				Help:  "Connect to example.com",
    58  			}},
    59  		}, {
    60  			Name: "user",
    61  			Help: "SSH username, leave blank for current username, " + currentUser,
    62  		}, {
    63  			Name: "port",
    64  			Help: "SSH port, leave blank to use default (22)",
    65  		}, {
    66  			Name:       "pass",
    67  			Help:       "SSH password, leave blank to use ssh-agent.",
    68  			IsPassword: true,
    69  		}, {
    70  			Name: "key_file",
    71  			Help: "Path to PEM-encoded private key file, leave blank or set key-use-agent to use ssh-agent.",
    72  		}, {
    73  			Name: "key_file_pass",
    74  			Help: `The passphrase to decrypt the PEM-encoded private key file.
    75  
    76  Only PEM encrypted key files (old OpenSSH format) are supported. Encrypted keys
    77  in the new OpenSSH format can't be used.`,
    78  			IsPassword: true,
    79  		}, {
    80  			Name: "key_use_agent",
    81  			Help: `When set forces the usage of the ssh-agent.
    82  
    83  When key-file is also set, the ".pub" file of the specified key-file is read and only the associated key is
    84  requested from the ssh-agent. This allows to avoid ` + "`Too many authentication failures for *username*`" + ` errors
    85  when the ssh-agent contains many keys.`,
    86  			Default: false,
    87  		}, {
    88  			Name:    "use_insecure_cipher",
    89  			Help:    "Enable the use of the aes128-cbc cipher and diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1 key exchange. Those algorithms are insecure and may allow plaintext data to be recovered by an attacker.",
    90  			Default: false,
    91  			Examples: []fs.OptionExample{
    92  				{
    93  					Value: "false",
    94  					Help:  "Use default Cipher list.",
    95  				}, {
    96  					Value: "true",
    97  					Help:  "Enables the use of the aes128-cbc cipher and diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1 key exchange.",
    98  				},
    99  			},
   100  		}, {
   101  			Name:    "disable_hashcheck",
   102  			Default: false,
   103  			Help:    "Disable the execution of SSH commands to determine if remote file hashing is available.\nLeave blank or set to false to enable hashing (recommended), set to true to disable hashing.",
   104  		}, {
   105  			Name:     "ask_password",
   106  			Default:  false,
   107  			Help:     "Allow asking for SFTP password when needed.",
   108  			Advanced: true,
   109  		}, {
   110  			Name:    "path_override",
   111  			Default: "",
   112  			Help: `Override path used by SSH connection.
   113  
   114  This allows checksum calculation when SFTP and SSH paths are
   115  different. This issue affects among others Synology NAS boxes.
   116  
   117  Shared folders can be found in directories representing volumes
   118  
   119      rclone sync /home/local/directory remote:/directory --ssh-path-override /volume2/directory
   120  
   121  Home directory can be found in a shared folder called "home"
   122  
   123      rclone sync /home/local/directory remote:/home/directory --ssh-path-override /volume1/homes/USER/directory`,
   124  			Advanced: true,
   125  		}, {
   126  			Name:     "set_modtime",
   127  			Default:  true,
   128  			Help:     "Set the modified time on the remote if set.",
   129  			Advanced: true,
   130  		}},
   131  	}
   132  	fs.Register(fsi)
   133  }
   134  
   135  // Options defines the configuration for this backend
   136  type Options struct {
   137  	Host              string `config:"host"`
   138  	User              string `config:"user"`
   139  	Port              string `config:"port"`
   140  	Pass              string `config:"pass"`
   141  	KeyFile           string `config:"key_file"`
   142  	KeyFilePass       string `config:"key_file_pass"`
   143  	KeyUseAgent       bool   `config:"key_use_agent"`
   144  	UseInsecureCipher bool   `config:"use_insecure_cipher"`
   145  	DisableHashCheck  bool   `config:"disable_hashcheck"`
   146  	AskPassword       bool   `config:"ask_password"`
   147  	PathOverride      string `config:"path_override"`
   148  	SetModTime        bool   `config:"set_modtime"`
   149  }
   150  
   151  // Fs stores the interface to the remote SFTP files
   152  type Fs struct {
   153  	name         string
   154  	root         string
   155  	opt          Options      // parsed options
   156  	features     *fs.Features // optional features
   157  	config       *ssh.ClientConfig
   158  	url          string
   159  	mkdirLock    *stringLock
   160  	cachedHashes *hash.Set
   161  	poolMu       sync.Mutex
   162  	pool         []*conn
   163  	connLimit    *rate.Limiter // for limiting number of connections per second
   164  }
   165  
   166  // Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
   167  type Object struct {
   168  	fs      *Fs
   169  	remote  string
   170  	size    int64       // size of the object
   171  	modTime time.Time   // modification time of the object
   172  	mode    os.FileMode // mode bits from the file
   173  	md5sum  *string     // Cached MD5 checksum
   174  	sha1sum *string     // Cached SHA1 checksum
   175  }
   176  
   177  // readCurrentUser finds the current user name or "" if not found
   178  func readCurrentUser() (userName string) {
   179  	usr, err := user.Current()
   180  	if err == nil {
   181  		return usr.Username
   182  	}
   183  	// Fall back to reading $USER then $LOGNAME
   184  	userName = os.Getenv("USER")
   185  	if userName != "" {
   186  		return userName
   187  	}
   188  	return os.Getenv("LOGNAME")
   189  }
   190  
   191  // dial starts a client connection to the given SSH server. It is a
   192  // convenience function that connects to the given network address,
   193  // initiates the SSH handshake, and then sets up a Client.
   194  func (f *Fs) dial(network, addr string, sshConfig *ssh.ClientConfig) (*ssh.Client, error) {
   195  	dialer := fshttp.NewDialer(fs.Config)
   196  	conn, err := dialer.Dial(network, addr)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	c, chans, reqs, err := ssh.NewClientConn(conn, addr, sshConfig)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	fs.Debugf(f, "New connection %s->%s to %q", c.LocalAddr(), c.RemoteAddr(), c.ServerVersion())
   205  	return ssh.NewClient(c, chans, reqs), nil
   206  }
   207  
   208  // conn encapsulates an ssh client and corresponding sftp client
   209  type conn struct {
   210  	sshClient  *ssh.Client
   211  	sftpClient *sftp.Client
   212  	err        chan error
   213  }
   214  
   215  // Wait for connection to close
   216  func (c *conn) wait() {
   217  	c.err <- c.sshClient.Conn.Wait()
   218  }
   219  
   220  // Closes the connection
   221  func (c *conn) close() error {
   222  	sftpErr := c.sftpClient.Close()
   223  	sshErr := c.sshClient.Close()
   224  	if sftpErr != nil {
   225  		return sftpErr
   226  	}
   227  	return sshErr
   228  }
   229  
   230  // Returns an error if closed
   231  func (c *conn) closed() error {
   232  	select {
   233  	case err := <-c.err:
   234  		return err
   235  	default:
   236  	}
   237  	return nil
   238  }
   239  
   240  // Open a new connection to the SFTP server.
   241  func (f *Fs) sftpConnection() (c *conn, err error) {
   242  	// Rate limit rate of new connections
   243  	err = f.connLimit.Wait(context.Background())
   244  	if err != nil {
   245  		return nil, errors.Wrap(err, "limiter failed in connect")
   246  	}
   247  	c = &conn{
   248  		err: make(chan error, 1),
   249  	}
   250  	c.sshClient, err = f.dial("tcp", f.opt.Host+":"+f.opt.Port, f.config)
   251  	if err != nil {
   252  		return nil, errors.Wrap(err, "couldn't connect SSH")
   253  	}
   254  	c.sftpClient, err = sftp.NewClient(c.sshClient)
   255  	if err != nil {
   256  		_ = c.sshClient.Close()
   257  		return nil, errors.Wrap(err, "couldn't initialise SFTP")
   258  	}
   259  	go c.wait()
   260  	return c, nil
   261  }
   262  
   263  // Get an SFTP connection from the pool, or open a new one
   264  func (f *Fs) getSftpConnection() (c *conn, err error) {
   265  	f.poolMu.Lock()
   266  	for len(f.pool) > 0 {
   267  		c = f.pool[0]
   268  		f.pool = f.pool[1:]
   269  		err := c.closed()
   270  		if err == nil {
   271  			break
   272  		}
   273  		fs.Errorf(f, "Discarding closed SSH connection: %v", err)
   274  		c = nil
   275  	}
   276  	f.poolMu.Unlock()
   277  	if c != nil {
   278  		return c, nil
   279  	}
   280  	return f.sftpConnection()
   281  }
   282  
   283  // Return an SFTP connection to the pool
   284  //
   285  // It nils the pointed to connection out so it can't be reused
   286  //
   287  // if err is not nil then it checks the connection is alive using a
   288  // Getwd request
   289  func (f *Fs) putSftpConnection(pc **conn, err error) {
   290  	c := *pc
   291  	*pc = nil
   292  	if err != nil {
   293  		// work out if this is an expected error
   294  		underlyingErr := errors.Cause(err)
   295  		isRegularError := false
   296  		switch underlyingErr {
   297  		case os.ErrNotExist:
   298  			isRegularError = true
   299  		default:
   300  			switch underlyingErr.(type) {
   301  			case *sftp.StatusError, *os.PathError:
   302  				isRegularError = true
   303  			}
   304  		}
   305  		// If not a regular SFTP error code then check the connection
   306  		if !isRegularError {
   307  			_, nopErr := c.sftpClient.Getwd()
   308  			if nopErr != nil {
   309  				fs.Debugf(f, "Connection failed, closing: %v", nopErr)
   310  				_ = c.close()
   311  				return
   312  			}
   313  			fs.Debugf(f, "Connection OK after error: %v", err)
   314  		}
   315  	}
   316  	f.poolMu.Lock()
   317  	f.pool = append(f.pool, c)
   318  	f.poolMu.Unlock()
   319  }
   320  
   321  // NewFs creates a new Fs object from the name and root. It connects to
   322  // the host specified in the config file.
   323  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   324  	ctx := context.Background()
   325  	// Parse config into Options struct
   326  	opt := new(Options)
   327  	err := configstruct.Set(m, opt)
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  	if opt.User == "" {
   332  		opt.User = currentUser
   333  	}
   334  	if opt.Port == "" {
   335  		opt.Port = "22"
   336  	}
   337  	sshConfig := &ssh.ClientConfig{
   338  		User:            opt.User,
   339  		Auth:            []ssh.AuthMethod{},
   340  		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
   341  		Timeout:         fs.Config.ConnectTimeout,
   342  		ClientVersion:   "SSH-2.0-" + fs.Config.UserAgent,
   343  	}
   344  
   345  	if opt.UseInsecureCipher {
   346  		sshConfig.Config.SetDefaults()
   347  		sshConfig.Config.Ciphers = append(sshConfig.Config.Ciphers, "aes128-cbc")
   348  		sshConfig.Config.KeyExchanges = append(sshConfig.Config.KeyExchanges, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
   349  	}
   350  
   351  	keyFile := env.ShellExpand(opt.KeyFile)
   352  	// Add ssh agent-auth if no password or file specified
   353  	if (opt.Pass == "" && keyFile == "") || opt.KeyUseAgent {
   354  		sshAgentClient, _, err := sshagent.New()
   355  		if err != nil {
   356  			return nil, errors.Wrap(err, "couldn't connect to ssh-agent")
   357  		}
   358  		signers, err := sshAgentClient.Signers()
   359  		if err != nil {
   360  			return nil, errors.Wrap(err, "couldn't read ssh agent signers")
   361  		}
   362  		if keyFile != "" {
   363  			pubBytes, err := ioutil.ReadFile(keyFile + ".pub")
   364  			if err != nil {
   365  				return nil, errors.Wrap(err, "failed to read public key file")
   366  			}
   367  			pub, _, _, _, err := ssh.ParseAuthorizedKey(pubBytes)
   368  			if err != nil {
   369  				return nil, errors.Wrap(err, "failed to parse public key file")
   370  			}
   371  			pubM := pub.Marshal()
   372  			found := false
   373  			for _, s := range signers {
   374  				if bytes.Equal(pubM, s.PublicKey().Marshal()) {
   375  					sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(s))
   376  					found = true
   377  					break
   378  				}
   379  			}
   380  			if !found {
   381  				return nil, errors.New("private key not found in the ssh-agent")
   382  			}
   383  		} else {
   384  			sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signers...))
   385  		}
   386  	}
   387  
   388  	// Load key file if specified
   389  	if keyFile != "" {
   390  		key, err := ioutil.ReadFile(keyFile)
   391  		if err != nil {
   392  			return nil, errors.Wrap(err, "failed to read private key file")
   393  		}
   394  		clearpass := ""
   395  		if opt.KeyFilePass != "" {
   396  			clearpass, err = obscure.Reveal(opt.KeyFilePass)
   397  			if err != nil {
   398  				return nil, err
   399  			}
   400  		}
   401  		signer, err := ssh.ParsePrivateKeyWithPassphrase(key, []byte(clearpass))
   402  		if err != nil {
   403  			return nil, errors.Wrap(err, "failed to parse private key file")
   404  		}
   405  		sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
   406  	}
   407  
   408  	// Auth from password if specified
   409  	if opt.Pass != "" {
   410  		clearpass, err := obscure.Reveal(opt.Pass)
   411  		if err != nil {
   412  			return nil, err
   413  		}
   414  		sshConfig.Auth = append(sshConfig.Auth, ssh.Password(clearpass))
   415  	}
   416  
   417  	// Ask for password if none was defined and we're allowed to
   418  	if opt.Pass == "" && opt.AskPassword {
   419  		_, _ = fmt.Fprint(os.Stderr, "Enter SFTP password: ")
   420  		clearpass := config.ReadPassword()
   421  		sshConfig.Auth = append(sshConfig.Auth, ssh.Password(clearpass))
   422  	}
   423  
   424  	return NewFsWithConnection(ctx, name, root, opt, sshConfig)
   425  }
   426  
   427  // NewFsWithConnection creates a new Fs object from the name and root and a ssh.ClientConfig. It connects to
   428  // the host specified in the ssh.ClientConfig
   429  func NewFsWithConnection(ctx context.Context, name string, root string, opt *Options, sshConfig *ssh.ClientConfig) (fs.Fs, error) {
   430  	f := &Fs{
   431  		name:      name,
   432  		root:      root,
   433  		opt:       *opt,
   434  		config:    sshConfig,
   435  		url:       "sftp://" + opt.User + "@" + opt.Host + ":" + opt.Port + "/" + root,
   436  		mkdirLock: newStringLock(),
   437  		connLimit: rate.NewLimiter(rate.Limit(connectionsPerSecond), 1),
   438  	}
   439  	f.features = (&fs.Features{
   440  		CanHaveEmptyDirectories: true,
   441  	}).Fill(f)
   442  	// Make a connection and pool it to return errors early
   443  	c, err := f.getSftpConnection()
   444  	if err != nil {
   445  		return nil, errors.Wrap(err, "NewFs")
   446  	}
   447  	f.putSftpConnection(&c, nil)
   448  	if root != "" {
   449  		// Check to see if the root actually an existing file
   450  		remote := path.Base(root)
   451  		f.root = path.Dir(root)
   452  		if f.root == "." {
   453  			f.root = ""
   454  		}
   455  		_, err := f.NewObject(ctx, remote)
   456  		if err != nil {
   457  			if err == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile {
   458  				// File doesn't exist so return old f
   459  				f.root = root
   460  				return f, nil
   461  			}
   462  			return nil, err
   463  		}
   464  		// return an error with an fs which points to the parent
   465  		return f, fs.ErrorIsFile
   466  	}
   467  	return f, nil
   468  }
   469  
   470  // Name returns the configured name of the file system
   471  func (f *Fs) Name() string {
   472  	return f.name
   473  }
   474  
   475  // Root returns the root for the filesystem
   476  func (f *Fs) Root() string {
   477  	return f.root
   478  }
   479  
   480  // String returns the URL for the filesystem
   481  func (f *Fs) String() string {
   482  	return f.url
   483  }
   484  
   485  // Features returns the optional features of this Fs
   486  func (f *Fs) Features() *fs.Features {
   487  	return f.features
   488  }
   489  
   490  // Precision is the remote sftp file system's modtime precision, which we have no way of knowing. We estimate at 1s
   491  func (f *Fs) Precision() time.Duration {
   492  	return time.Second
   493  }
   494  
   495  // NewObject creates a new remote sftp file object
   496  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   497  	o := &Object{
   498  		fs:     f,
   499  		remote: remote,
   500  	}
   501  	err := o.stat()
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  	return o, nil
   506  }
   507  
   508  // dirExists returns true,nil if the directory exists, false, nil if
   509  // it doesn't or false, err
   510  func (f *Fs) dirExists(dir string) (bool, error) {
   511  	if dir == "" {
   512  		dir = "."
   513  	}
   514  	c, err := f.getSftpConnection()
   515  	if err != nil {
   516  		return false, errors.Wrap(err, "dirExists")
   517  	}
   518  	info, err := c.sftpClient.Stat(dir)
   519  	f.putSftpConnection(&c, err)
   520  	if err != nil {
   521  		if os.IsNotExist(err) {
   522  			return false, nil
   523  		}
   524  		return false, errors.Wrap(err, "dirExists stat failed")
   525  	}
   526  	if !info.IsDir() {
   527  		return false, fs.ErrorIsFile
   528  	}
   529  	return true, nil
   530  }
   531  
   532  // List the objects and directories in dir into entries.  The
   533  // entries can be returned in any order but should be for a
   534  // complete directory.
   535  //
   536  // dir should be "" to list the root, and should not have
   537  // trailing slashes.
   538  //
   539  // This should return ErrDirNotFound if the directory isn't
   540  // found.
   541  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   542  	root := path.Join(f.root, dir)
   543  	ok, err := f.dirExists(root)
   544  	if err != nil {
   545  		return nil, errors.Wrap(err, "List failed")
   546  	}
   547  	if !ok {
   548  		return nil, fs.ErrorDirNotFound
   549  	}
   550  	sftpDir := root
   551  	if sftpDir == "" {
   552  		sftpDir = "."
   553  	}
   554  	c, err := f.getSftpConnection()
   555  	if err != nil {
   556  		return nil, errors.Wrap(err, "List")
   557  	}
   558  	infos, err := c.sftpClient.ReadDir(sftpDir)
   559  	f.putSftpConnection(&c, err)
   560  	if err != nil {
   561  		return nil, errors.Wrapf(err, "error listing %q", dir)
   562  	}
   563  	for _, info := range infos {
   564  		remote := path.Join(dir, info.Name())
   565  		// If file is a symlink (not a regular file is the best cross platform test we can do), do a stat to
   566  		// pick up the size and type of the destination, instead of the size and type of the symlink.
   567  		if !info.Mode().IsRegular() {
   568  			oldInfo := info
   569  			info, err = f.stat(remote)
   570  			if err != nil {
   571  				if !os.IsNotExist(err) {
   572  					fs.Errorf(remote, "stat of non-regular file/dir failed: %v", err)
   573  				}
   574  				info = oldInfo
   575  			}
   576  		}
   577  		if info.IsDir() {
   578  			d := fs.NewDir(remote, info.ModTime())
   579  			entries = append(entries, d)
   580  		} else {
   581  			o := &Object{
   582  				fs:     f,
   583  				remote: remote,
   584  			}
   585  			o.setMetadata(info)
   586  			entries = append(entries, o)
   587  		}
   588  	}
   589  	return entries, nil
   590  }
   591  
   592  // Put data from <in> into a new remote sftp file object described by <src.Remote()> and <src.ModTime(ctx)>
   593  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   594  	err := f.mkParentDir(src.Remote())
   595  	if err != nil {
   596  		return nil, errors.Wrap(err, "Put mkParentDir failed")
   597  	}
   598  	// Temporary object under construction
   599  	o := &Object{
   600  		fs:     f,
   601  		remote: src.Remote(),
   602  	}
   603  	err = o.Update(ctx, in, src, options...)
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  	return o, nil
   608  }
   609  
   610  // PutStream uploads to the remote path with the modTime given of indeterminate size
   611  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   612  	return f.Put(ctx, in, src, options...)
   613  }
   614  
   615  // mkParentDir makes the parent of remote if necessary and any
   616  // directories above that
   617  func (f *Fs) mkParentDir(remote string) error {
   618  	parent := path.Dir(remote)
   619  	return f.mkdir(path.Join(f.root, parent))
   620  }
   621  
   622  // mkdir makes the directory and parents using native paths
   623  func (f *Fs) mkdir(dirPath string) error {
   624  	f.mkdirLock.Lock(dirPath)
   625  	defer f.mkdirLock.Unlock(dirPath)
   626  	if dirPath == "." || dirPath == "/" {
   627  		return nil
   628  	}
   629  	ok, err := f.dirExists(dirPath)
   630  	if err != nil {
   631  		return errors.Wrap(err, "mkdir dirExists failed")
   632  	}
   633  	if ok {
   634  		return nil
   635  	}
   636  	parent := path.Dir(dirPath)
   637  	err = f.mkdir(parent)
   638  	if err != nil {
   639  		return err
   640  	}
   641  	c, err := f.getSftpConnection()
   642  	if err != nil {
   643  		return errors.Wrap(err, "mkdir")
   644  	}
   645  	err = c.sftpClient.Mkdir(dirPath)
   646  	f.putSftpConnection(&c, err)
   647  	if err != nil {
   648  		return errors.Wrapf(err, "mkdir %q failed", dirPath)
   649  	}
   650  	return nil
   651  }
   652  
   653  // Mkdir makes the root directory of the Fs object
   654  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   655  	root := path.Join(f.root, dir)
   656  	return f.mkdir(root)
   657  }
   658  
   659  // Rmdir removes the root directory of the Fs object
   660  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   661  	// Check to see if directory is empty as some servers will
   662  	// delete recursively with RemoveDirectory
   663  	entries, err := f.List(ctx, dir)
   664  	if err != nil {
   665  		return errors.Wrap(err, "Rmdir")
   666  	}
   667  	if len(entries) != 0 {
   668  		return fs.ErrorDirectoryNotEmpty
   669  	}
   670  	// Remove the directory
   671  	root := path.Join(f.root, dir)
   672  	c, err := f.getSftpConnection()
   673  	if err != nil {
   674  		return errors.Wrap(err, "Rmdir")
   675  	}
   676  	err = c.sftpClient.RemoveDirectory(root)
   677  	f.putSftpConnection(&c, err)
   678  	return err
   679  }
   680  
   681  // Move renames a remote sftp file object
   682  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   683  	srcObj, ok := src.(*Object)
   684  	if !ok {
   685  		fs.Debugf(src, "Can't move - not same remote type")
   686  		return nil, fs.ErrorCantMove
   687  	}
   688  	err := f.mkParentDir(remote)
   689  	if err != nil {
   690  		return nil, errors.Wrap(err, "Move mkParentDir failed")
   691  	}
   692  	c, err := f.getSftpConnection()
   693  	if err != nil {
   694  		return nil, errors.Wrap(err, "Move")
   695  	}
   696  	err = c.sftpClient.Rename(
   697  		srcObj.path(),
   698  		path.Join(f.root, remote),
   699  	)
   700  	f.putSftpConnection(&c, err)
   701  	if err != nil {
   702  		return nil, errors.Wrap(err, "Move Rename failed")
   703  	}
   704  	dstObj, err := f.NewObject(ctx, remote)
   705  	if err != nil {
   706  		return nil, errors.Wrap(err, "Move NewObject failed")
   707  	}
   708  	return dstObj, nil
   709  }
   710  
   711  // DirMove moves src, srcRemote to this remote at dstRemote
   712  // using server side move operations.
   713  //
   714  // Will only be called if src.Fs().Name() == f.Name()
   715  //
   716  // If it isn't possible then return fs.ErrorCantDirMove
   717  //
   718  // If destination exists then return fs.ErrorDirExists
   719  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   720  	srcFs, ok := src.(*Fs)
   721  	if !ok {
   722  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   723  		return fs.ErrorCantDirMove
   724  	}
   725  	srcPath := path.Join(srcFs.root, srcRemote)
   726  	dstPath := path.Join(f.root, dstRemote)
   727  
   728  	// Check if destination exists
   729  	ok, err := f.dirExists(dstPath)
   730  	if err != nil {
   731  		return errors.Wrap(err, "DirMove dirExists dst failed")
   732  	}
   733  	if ok {
   734  		return fs.ErrorDirExists
   735  	}
   736  
   737  	// Make sure the parent directory exists
   738  	err = f.mkdir(path.Dir(dstPath))
   739  	if err != nil {
   740  		return errors.Wrap(err, "DirMove mkParentDir dst failed")
   741  	}
   742  
   743  	// Do the move
   744  	c, err := f.getSftpConnection()
   745  	if err != nil {
   746  		return errors.Wrap(err, "DirMove")
   747  	}
   748  	err = c.sftpClient.Rename(
   749  		srcPath,
   750  		dstPath,
   751  	)
   752  	f.putSftpConnection(&c, err)
   753  	if err != nil {
   754  		return errors.Wrapf(err, "DirMove Rename(%q,%q) failed", srcPath, dstPath)
   755  	}
   756  	return nil
   757  }
   758  
   759  // Hashes returns the supported hash types of the filesystem
   760  func (f *Fs) Hashes() hash.Set {
   761  	if f.cachedHashes != nil {
   762  		return *f.cachedHashes
   763  	}
   764  
   765  	if f.opt.DisableHashCheck {
   766  		return hash.Set(hash.None)
   767  	}
   768  
   769  	c, err := f.getSftpConnection()
   770  	if err != nil {
   771  		fs.Errorf(f, "Couldn't get SSH connection to figure out Hashes: %v", err)
   772  		return hash.Set(hash.None)
   773  	}
   774  	defer f.putSftpConnection(&c, err)
   775  	session, err := c.sshClient.NewSession()
   776  	if err != nil {
   777  		return hash.Set(hash.None)
   778  	}
   779  	sha1Output, _ := session.Output("echo 'abc' | sha1sum")
   780  	expectedSha1 := "03cfd743661f07975fa2f1220c5194cbaff48451"
   781  	_ = session.Close()
   782  
   783  	session, err = c.sshClient.NewSession()
   784  	if err != nil {
   785  		return hash.Set(hash.None)
   786  	}
   787  	md5Output, _ := session.Output("echo 'abc' | md5sum")
   788  	expectedMd5 := "0bee89b07a248e27c83fc3d5951213c1"
   789  	_ = session.Close()
   790  
   791  	sha1Works := parseHash(sha1Output) == expectedSha1
   792  	md5Works := parseHash(md5Output) == expectedMd5
   793  
   794  	set := hash.NewHashSet()
   795  	if !sha1Works && !md5Works {
   796  		set.Add(hash.None)
   797  	}
   798  	if sha1Works {
   799  		set.Add(hash.SHA1)
   800  	}
   801  	if md5Works {
   802  		set.Add(hash.MD5)
   803  	}
   804  
   805  	_ = session.Close()
   806  	f.cachedHashes = &set
   807  	return set
   808  }
   809  
   810  // About gets usage stats
   811  func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
   812  	c, err := f.getSftpConnection()
   813  	if err != nil {
   814  		return nil, errors.Wrap(err, "About get SFTP connection")
   815  	}
   816  	session, err := c.sshClient.NewSession()
   817  	f.putSftpConnection(&c, err)
   818  	if err != nil {
   819  		return nil, errors.Wrap(err, "About put SFTP connection")
   820  	}
   821  
   822  	var stdout, stderr bytes.Buffer
   823  	session.Stdout = &stdout
   824  	session.Stderr = &stderr
   825  	escapedPath := shellEscape(f.root)
   826  	if f.opt.PathOverride != "" {
   827  		escapedPath = shellEscape(path.Join(f.opt.PathOverride, f.root))
   828  	}
   829  	if len(escapedPath) == 0 {
   830  		escapedPath = "/"
   831  	}
   832  	err = session.Run("df -k " + escapedPath)
   833  	if err != nil {
   834  		_ = session.Close()
   835  		return nil, errors.Wrap(err, "About invocation of df failed. Your remote may not support about.")
   836  	}
   837  	_ = session.Close()
   838  
   839  	usageTotal, usageUsed, usageAvail := parseUsage(stdout.Bytes())
   840  	usage := &fs.Usage{}
   841  	if usageTotal >= 0 {
   842  		usage.Total = fs.NewUsageValue(usageTotal)
   843  	}
   844  	if usageUsed >= 0 {
   845  		usage.Used = fs.NewUsageValue(usageUsed)
   846  	}
   847  	if usageAvail >= 0 {
   848  		usage.Free = fs.NewUsageValue(usageAvail)
   849  	}
   850  	return usage, nil
   851  }
   852  
   853  // Fs is the filesystem this remote sftp file object is located within
   854  func (o *Object) Fs() fs.Info {
   855  	return o.fs
   856  }
   857  
   858  // String returns the URL to the remote SFTP file
   859  func (o *Object) String() string {
   860  	if o == nil {
   861  		return "<nil>"
   862  	}
   863  	return o.remote
   864  }
   865  
   866  // Remote the name of the remote SFTP file, relative to the fs root
   867  func (o *Object) Remote() string {
   868  	return o.remote
   869  }
   870  
   871  // Hash returns the selected checksum of the file
   872  // If no checksum is available it returns ""
   873  func (o *Object) Hash(ctx context.Context, r hash.Type) (string, error) {
   874  	var hashCmd string
   875  	if r == hash.MD5 {
   876  		if o.md5sum != nil {
   877  			return *o.md5sum, nil
   878  		}
   879  		hashCmd = "md5sum"
   880  	} else if r == hash.SHA1 {
   881  		if o.sha1sum != nil {
   882  			return *o.sha1sum, nil
   883  		}
   884  		hashCmd = "sha1sum"
   885  	} else {
   886  		return "", hash.ErrUnsupported
   887  	}
   888  
   889  	if o.fs.opt.DisableHashCheck {
   890  		return "", nil
   891  	}
   892  
   893  	c, err := o.fs.getSftpConnection()
   894  	if err != nil {
   895  		return "", errors.Wrap(err, "Hash get SFTP connection")
   896  	}
   897  	session, err := c.sshClient.NewSession()
   898  	o.fs.putSftpConnection(&c, err)
   899  	if err != nil {
   900  		return "", errors.Wrap(err, "Hash put SFTP connection")
   901  	}
   902  
   903  	var stdout, stderr bytes.Buffer
   904  	session.Stdout = &stdout
   905  	session.Stderr = &stderr
   906  	escapedPath := shellEscape(o.path())
   907  	if o.fs.opt.PathOverride != "" {
   908  		escapedPath = shellEscape(path.Join(o.fs.opt.PathOverride, o.remote))
   909  	}
   910  	err = session.Run(hashCmd + " " + escapedPath)
   911  	if err != nil {
   912  		_ = session.Close()
   913  		fs.Debugf(o, "Failed to calculate %v hash: %v (%s)", r, err, bytes.TrimSpace(stderr.Bytes()))
   914  		return "", nil
   915  	}
   916  
   917  	_ = session.Close()
   918  	str := parseHash(stdout.Bytes())
   919  	if r == hash.MD5 {
   920  		o.md5sum = &str
   921  	} else if r == hash.SHA1 {
   922  		o.sha1sum = &str
   923  	}
   924  	return str, nil
   925  }
   926  
   927  var shellEscapeRegex = regexp.MustCompile(`[^A-Za-z0-9_.,:/@\n-]`)
   928  
   929  // Escape a string s.t. it cannot cause unintended behavior
   930  // when sending it to a shell.
   931  func shellEscape(str string) string {
   932  	safe := shellEscapeRegex.ReplaceAllString(str, `\$0`)
   933  	return strings.Replace(safe, "\n", "'\n'", -1)
   934  }
   935  
   936  // Converts a byte array from the SSH session returned by
   937  // an invocation of md5sum/sha1sum to a hash string
   938  // as expected by the rest of this application
   939  func parseHash(bytes []byte) string {
   940  	return strings.Split(string(bytes), " ")[0] // Split at hash / filename separator
   941  }
   942  
   943  // Parses the byte array output from the SSH session
   944  // returned by an invocation of df into
   945  // the disk size, used space, and avaliable space on the disk, in that order.
   946  // Only works when `df` has output info on only one disk
   947  func parseUsage(bytes []byte) (spaceTotal int64, spaceUsed int64, spaceAvail int64) {
   948  	spaceTotal, spaceUsed, spaceAvail = -1, -1, -1
   949  	lines := strings.Split(string(bytes), "\n")
   950  	if len(lines) < 2 {
   951  		return
   952  	}
   953  	split := strings.Fields(lines[1])
   954  	if len(split) < 6 {
   955  		return
   956  	}
   957  	spaceTotal, err := strconv.ParseInt(split[1], 10, 64)
   958  	if err != nil {
   959  		spaceTotal = -1
   960  	}
   961  	spaceUsed, err = strconv.ParseInt(split[2], 10, 64)
   962  	if err != nil {
   963  		spaceUsed = -1
   964  	}
   965  	spaceAvail, err = strconv.ParseInt(split[3], 10, 64)
   966  	if err != nil {
   967  		spaceAvail = -1
   968  	}
   969  	return spaceTotal * 1024, spaceUsed * 1024, spaceAvail * 1024
   970  }
   971  
   972  // Size returns the size in bytes of the remote sftp file
   973  func (o *Object) Size() int64 {
   974  	return o.size
   975  }
   976  
   977  // ModTime returns the modification time of the remote sftp file
   978  func (o *Object) ModTime(ctx context.Context) time.Time {
   979  	return o.modTime
   980  }
   981  
   982  // path returns the native path of the object
   983  func (o *Object) path() string {
   984  	return path.Join(o.fs.root, o.remote)
   985  }
   986  
   987  // setMetadata updates the info in the object from the stat result passed in
   988  func (o *Object) setMetadata(info os.FileInfo) {
   989  	o.modTime = info.ModTime()
   990  	o.size = info.Size()
   991  	o.mode = info.Mode()
   992  }
   993  
   994  // statRemote stats the file or directory at the remote given
   995  func (f *Fs) stat(remote string) (info os.FileInfo, err error) {
   996  	c, err := f.getSftpConnection()
   997  	if err != nil {
   998  		return nil, errors.Wrap(err, "stat")
   999  	}
  1000  	absPath := path.Join(f.root, remote)
  1001  	info, err = c.sftpClient.Stat(absPath)
  1002  	f.putSftpConnection(&c, err)
  1003  	return info, err
  1004  }
  1005  
  1006  // stat updates the info in the Object
  1007  func (o *Object) stat() error {
  1008  	info, err := o.fs.stat(o.remote)
  1009  	if err != nil {
  1010  		if os.IsNotExist(err) {
  1011  			return fs.ErrorObjectNotFound
  1012  		}
  1013  		return errors.Wrap(err, "stat failed")
  1014  	}
  1015  	if info.IsDir() {
  1016  		return errors.Wrapf(fs.ErrorNotAFile, "%q", o.remote)
  1017  	}
  1018  	o.setMetadata(info)
  1019  	return nil
  1020  }
  1021  
  1022  // SetModTime sets the modification and access time to the specified time
  1023  //
  1024  // it also updates the info field
  1025  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
  1026  	if !o.fs.opt.SetModTime {
  1027  		return nil
  1028  	}
  1029  	c, err := o.fs.getSftpConnection()
  1030  	if err != nil {
  1031  		return errors.Wrap(err, "SetModTime")
  1032  	}
  1033  	err = c.sftpClient.Chtimes(o.path(), modTime, modTime)
  1034  	o.fs.putSftpConnection(&c, err)
  1035  	if err != nil {
  1036  		return errors.Wrap(err, "SetModTime failed")
  1037  	}
  1038  	err = o.stat()
  1039  	if err != nil {
  1040  		return errors.Wrap(err, "SetModTime stat failed")
  1041  	}
  1042  	return nil
  1043  }
  1044  
  1045  // Storable returns whether the remote sftp file is a regular file (not a directory, symbolic link, block device, character device, named pipe, etc)
  1046  func (o *Object) Storable() bool {
  1047  	return o.mode.IsRegular()
  1048  }
  1049  
  1050  // objectReader represents a file open for reading on the SFTP server
  1051  type objectReader struct {
  1052  	sftpFile   *sftp.File
  1053  	pipeReader *io.PipeReader
  1054  	done       chan struct{}
  1055  }
  1056  
  1057  func newObjectReader(sftpFile *sftp.File) *objectReader {
  1058  	pipeReader, pipeWriter := io.Pipe()
  1059  	file := &objectReader{
  1060  		sftpFile:   sftpFile,
  1061  		pipeReader: pipeReader,
  1062  		done:       make(chan struct{}),
  1063  	}
  1064  
  1065  	go func() {
  1066  		// Use sftpFile.WriteTo to pump data so that it gets a
  1067  		// chance to build the window up.
  1068  		_, err := sftpFile.WriteTo(pipeWriter)
  1069  		// Close the pipeWriter so the pipeReader fails with
  1070  		// the same error or EOF if err == nil
  1071  		_ = pipeWriter.CloseWithError(err)
  1072  		// signal that we've finished
  1073  		close(file.done)
  1074  	}()
  1075  
  1076  	return file
  1077  }
  1078  
  1079  // Read from a remote sftp file object reader
  1080  func (file *objectReader) Read(p []byte) (n int, err error) {
  1081  	n, err = file.pipeReader.Read(p)
  1082  	return n, err
  1083  }
  1084  
  1085  // Close a reader of a remote sftp file
  1086  func (file *objectReader) Close() (err error) {
  1087  	// Close the sftpFile - this will likely cause the WriteTo to error
  1088  	err = file.sftpFile.Close()
  1089  	// Close the pipeReader so writes to the pipeWriter fail
  1090  	_ = file.pipeReader.Close()
  1091  	// Wait for the background process to finish
  1092  	<-file.done
  1093  	return err
  1094  }
  1095  
  1096  // Open a remote sftp file object for reading. Seek is supported
  1097  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  1098  	var offset, limit int64 = 0, -1
  1099  	for _, option := range options {
  1100  		switch x := option.(type) {
  1101  		case *fs.SeekOption:
  1102  			offset = x.Offset
  1103  		case *fs.RangeOption:
  1104  			offset, limit = x.Decode(o.Size())
  1105  		default:
  1106  			if option.Mandatory() {
  1107  				fs.Logf(o, "Unsupported mandatory option: %v", option)
  1108  			}
  1109  		}
  1110  	}
  1111  	c, err := o.fs.getSftpConnection()
  1112  	if err != nil {
  1113  		return nil, errors.Wrap(err, "Open")
  1114  	}
  1115  	sftpFile, err := c.sftpClient.Open(o.path())
  1116  	o.fs.putSftpConnection(&c, err)
  1117  	if err != nil {
  1118  		return nil, errors.Wrap(err, "Open failed")
  1119  	}
  1120  	if offset > 0 {
  1121  		off, err := sftpFile.Seek(offset, io.SeekStart)
  1122  		if err != nil || off != offset {
  1123  			return nil, errors.Wrap(err, "Open Seek failed")
  1124  		}
  1125  	}
  1126  	in = readers.NewLimitedReadCloser(newObjectReader(sftpFile), limit)
  1127  	return in, nil
  1128  }
  1129  
  1130  // Update a remote sftp file using the data <in> and ModTime from <src>
  1131  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
  1132  	// Clear the hash cache since we are about to update the object
  1133  	o.md5sum = nil
  1134  	o.sha1sum = nil
  1135  	c, err := o.fs.getSftpConnection()
  1136  	if err != nil {
  1137  		return errors.Wrap(err, "Update")
  1138  	}
  1139  	file, err := c.sftpClient.Create(o.path())
  1140  	o.fs.putSftpConnection(&c, err)
  1141  	if err != nil {
  1142  		return errors.Wrap(err, "Update Create failed")
  1143  	}
  1144  	// remove the file if upload failed
  1145  	remove := func() {
  1146  		c, removeErr := o.fs.getSftpConnection()
  1147  		if removeErr != nil {
  1148  			fs.Debugf(src, "Failed to open new SSH connection for delete: %v", removeErr)
  1149  			return
  1150  		}
  1151  		removeErr = c.sftpClient.Remove(o.path())
  1152  		o.fs.putSftpConnection(&c, removeErr)
  1153  		if removeErr != nil {
  1154  			fs.Debugf(src, "Failed to remove: %v", removeErr)
  1155  		} else {
  1156  			fs.Debugf(src, "Removed after failed upload: %v", err)
  1157  		}
  1158  	}
  1159  	_, err = file.ReadFrom(in)
  1160  	if err != nil {
  1161  		remove()
  1162  		return errors.Wrap(err, "Update ReadFrom failed")
  1163  	}
  1164  	err = file.Close()
  1165  	if err != nil {
  1166  		remove()
  1167  		return errors.Wrap(err, "Update Close failed")
  1168  	}
  1169  	err = o.SetModTime(ctx, src.ModTime(ctx))
  1170  	if err != nil {
  1171  		return errors.Wrap(err, "Update SetModTime failed")
  1172  	}
  1173  	return nil
  1174  }
  1175  
  1176  // Remove a remote sftp file object
  1177  func (o *Object) Remove(ctx context.Context) error {
  1178  	c, err := o.fs.getSftpConnection()
  1179  	if err != nil {
  1180  		return errors.Wrap(err, "Remove")
  1181  	}
  1182  	err = c.sftpClient.Remove(o.path())
  1183  	o.fs.putSftpConnection(&c, err)
  1184  	return err
  1185  }
  1186  
  1187  // Check the interfaces are satisfied
  1188  var (
  1189  	_ fs.Fs          = &Fs{}
  1190  	_ fs.PutStreamer = &Fs{}
  1191  	_ fs.Mover       = &Fs{}
  1192  	_ fs.DirMover    = &Fs{}
  1193  	_ fs.Abouter     = &Fs{}
  1194  	_ fs.Object      = &Object{}
  1195  )