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