github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/cmd/serve/ftp/ftp.go (about)

     1  // Package ftp implements an FTP server for rclone
     2  
     3  //+build !plan9
     4  
     5  package ftp
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"os"
    13  	"os/user"
    14  	"runtime"
    15  	"strconv"
    16  	"sync"
    17  
    18  	"github.com/pkg/errors"
    19  	"github.com/rclone/rclone/cmd"
    20  	"github.com/rclone/rclone/cmd/serve/proxy"
    21  	"github.com/rclone/rclone/cmd/serve/proxy/proxyflags"
    22  	"github.com/rclone/rclone/fs"
    23  	"github.com/rclone/rclone/fs/accounting"
    24  	"github.com/rclone/rclone/fs/config/flags"
    25  	"github.com/rclone/rclone/fs/log"
    26  	"github.com/rclone/rclone/fs/rc"
    27  	"github.com/rclone/rclone/vfs"
    28  	"github.com/rclone/rclone/vfs/vfsflags"
    29  	"github.com/spf13/cobra"
    30  	"github.com/spf13/pflag"
    31  	ftp "goftp.io/server"
    32  )
    33  
    34  // Options contains options for the http Server
    35  type Options struct {
    36  	//TODO add more options
    37  	ListenAddr   string // Port to listen on
    38  	PublicIP     string // Passive ports range
    39  	PassivePorts string // Passive ports range
    40  	BasicUser    string // single username for basic auth if not using Htpasswd
    41  	BasicPass    string // password for BasicUser
    42  }
    43  
    44  // DefaultOpt is the default values used for Options
    45  var DefaultOpt = Options{
    46  	ListenAddr:   "localhost:2121",
    47  	PublicIP:     "",
    48  	PassivePorts: "30000-32000",
    49  	BasicUser:    "anonymous",
    50  	BasicPass:    "",
    51  }
    52  
    53  // Opt is options set by command line flags
    54  var Opt = DefaultOpt
    55  
    56  // AddFlags adds flags for ftp
    57  func AddFlags(flagSet *pflag.FlagSet) {
    58  	rc.AddOption("ftp", &Opt)
    59  	flags.StringVarP(flagSet, &Opt.ListenAddr, "addr", "", Opt.ListenAddr, "IPaddress:Port or :Port to bind server to.")
    60  	flags.StringVarP(flagSet, &Opt.PublicIP, "public-ip", "", Opt.PublicIP, "Public IP address to advertise for passive connections.")
    61  	flags.StringVarP(flagSet, &Opt.PassivePorts, "passive-port", "", Opt.PassivePorts, "Passive port range to use.")
    62  	flags.StringVarP(flagSet, &Opt.BasicUser, "user", "", Opt.BasicUser, "User name for authentication.")
    63  	flags.StringVarP(flagSet, &Opt.BasicPass, "pass", "", Opt.BasicPass, "Password for authentication. (empty value allow every password)")
    64  }
    65  
    66  func init() {
    67  	vfsflags.AddFlags(Command.Flags())
    68  	proxyflags.AddFlags(Command.Flags())
    69  	AddFlags(Command.Flags())
    70  }
    71  
    72  // Command definition for cobra
    73  var Command = &cobra.Command{
    74  	Use:   "ftp remote:path",
    75  	Short: `Serve remote:path over FTP.`,
    76  	Long: `
    77  rclone serve ftp implements a basic ftp server to serve the
    78  remote over FTP protocol. This can be viewed with a ftp client
    79  or you can make a remote of type ftp to read and write it.
    80  
    81  ### Server options
    82  
    83  Use --addr to specify which IP address and port the server should
    84  listen on, eg --addr 1.2.3.4:8000 or --addr :8080 to listen to all
    85  IPs.  By default it only listens on localhost.  You can use port
    86  :0 to let the OS choose an available port.
    87  
    88  If you set --addr to listen on a public or LAN accessible IP address
    89  then using Authentication is advised - see the next section for info.
    90  
    91  #### Authentication
    92  
    93  By default this will serve files without needing a login.
    94  
    95  You can set a single username and password with the --user and --pass flags.
    96  ` + vfs.Help + proxy.Help,
    97  	Run: func(command *cobra.Command, args []string) {
    98  		var f fs.Fs
    99  		if proxyflags.Opt.AuthProxy == "" {
   100  			cmd.CheckArgs(1, 1, command, args)
   101  			f = cmd.NewFsSrc(args)
   102  		} else {
   103  			cmd.CheckArgs(0, 0, command, args)
   104  		}
   105  		cmd.Run(false, false, command, func() error {
   106  			s, err := newServer(f, &Opt)
   107  			if err != nil {
   108  				return err
   109  			}
   110  			return s.serve()
   111  		})
   112  	},
   113  }
   114  
   115  // server contains everything to run the server
   116  type server struct {
   117  	f         fs.Fs
   118  	srv       *ftp.Server
   119  	opt       Options
   120  	vfs       *vfs.VFS
   121  	proxy     *proxy.Proxy
   122  	pendingMu sync.Mutex
   123  	pending   map[string]*Driver // pending Driver~s that haven't got their VFS
   124  }
   125  
   126  // Make a new FTP to serve the remote
   127  func newServer(f fs.Fs, opt *Options) (*server, error) {
   128  	host, port, err := net.SplitHostPort(opt.ListenAddr)
   129  	if err != nil {
   130  		return nil, errors.New("Failed to parse host:port")
   131  	}
   132  	portNum, err := strconv.Atoi(port)
   133  	if err != nil {
   134  		return nil, errors.New("Failed to parse host:port")
   135  	}
   136  
   137  	s := &server{
   138  		f:       f,
   139  		opt:     *opt,
   140  		pending: make(map[string]*Driver),
   141  	}
   142  	if proxyflags.Opt.AuthProxy != "" {
   143  		s.proxy = proxy.New(&proxyflags.Opt)
   144  	} else {
   145  		s.vfs = vfs.New(f, &vfsflags.Opt)
   146  	}
   147  
   148  	ftpopt := &ftp.ServerOpts{
   149  		Name:           "Rclone FTP Server",
   150  		WelcomeMessage: "Welcome to Rclone " + fs.Version + " FTP Server",
   151  		Factory:        s, // implemented by NewDriver method
   152  		Hostname:       host,
   153  		Port:           portNum,
   154  		PublicIp:       opt.PublicIP,
   155  		PassivePorts:   opt.PassivePorts,
   156  		Auth:           s, // implemented by CheckPasswd method
   157  		Logger:         &Logger{},
   158  		//TODO implement a maximum of https://godoc.org/goftp.io/server#ServerOpts
   159  	}
   160  	s.srv = ftp.NewServer(ftpopt)
   161  	return s, nil
   162  }
   163  
   164  // serve runs the ftp server
   165  func (s *server) serve() error {
   166  	fs.Logf(s.f, "Serving FTP on %s", s.srv.Hostname+":"+strconv.Itoa(s.srv.Port))
   167  	return s.srv.ListenAndServe()
   168  }
   169  
   170  // serve runs the ftp server
   171  func (s *server) close() error {
   172  	fs.Logf(s.f, "Stopping FTP on %s", s.srv.Hostname+":"+strconv.Itoa(s.srv.Port))
   173  	return s.srv.Shutdown()
   174  }
   175  
   176  //Logger ftp logger output formatted message
   177  type Logger struct{}
   178  
   179  //Print log simple text message
   180  func (l *Logger) Print(sessionID string, message interface{}) {
   181  	fs.Infof(sessionID, "%s", message)
   182  }
   183  
   184  //Printf log formatted text message
   185  func (l *Logger) Printf(sessionID string, format string, v ...interface{}) {
   186  	fs.Infof(sessionID, format, v...)
   187  }
   188  
   189  //PrintCommand log formatted command execution
   190  func (l *Logger) PrintCommand(sessionID string, command string, params string) {
   191  	if command == "PASS" {
   192  		fs.Infof(sessionID, "> PASS ****")
   193  	} else {
   194  		fs.Infof(sessionID, "> %s %s", command, params)
   195  	}
   196  }
   197  
   198  //PrintResponse log responses
   199  func (l *Logger) PrintResponse(sessionID string, code int, message string) {
   200  	fs.Infof(sessionID, "< %d %s", code, message)
   201  }
   202  
   203  // findID finds the connection ID of the calling program.  It does
   204  // this in an incredibly hacky way by looking in the stack trace.
   205  //
   206  // callerName should be the name of the function that we are looking
   207  // for with a trailing '('
   208  //
   209  // What is really needed is a change of calling protocol so
   210  // CheckPassword is called with the connection.
   211  func findID(callerName []byte) (string, error) {
   212  	// Dump the stack in this format
   213  	// github.com/rclone/rclone/vendor/goftp.io/server.(*Conn).Serve(0xc0000b2680)
   214  	// 	/home/ncw/go/src/github.com/rclone/rclone/vendor/goftp.io/server/conn.go:116 +0x11d
   215  	buf := make([]byte, 4096)
   216  	n := runtime.Stack(buf, false)
   217  	buf = buf[:n]
   218  
   219  	// look for callerName first
   220  	i := bytes.Index(buf, callerName)
   221  	if i < 0 {
   222  		return "", errors.Errorf("findID: caller name not found in:\n%s", buf)
   223  	}
   224  	buf = buf[i+len(callerName):]
   225  
   226  	// find next ')'
   227  	i = bytes.IndexByte(buf, ')')
   228  	if i < 0 {
   229  		return "", errors.Errorf("findID: end of args not found in:\n%s", buf)
   230  	}
   231  	buf = buf[:i]
   232  
   233  	// trim off first argument
   234  	// find next ','
   235  	i = bytes.IndexByte(buf, ',')
   236  	if i >= 0 {
   237  		buf = buf[:i]
   238  	}
   239  
   240  	return string(buf), nil
   241  }
   242  
   243  var connServeFunction = []byte("(*Conn).Serve(")
   244  
   245  // CheckPasswd handle auth based on configuration
   246  func (s *server) CheckPasswd(user, pass string) (ok bool, err error) {
   247  	var VFS *vfs.VFS
   248  	if s.proxy != nil {
   249  		VFS, _, err = s.proxy.Call(user, pass, false)
   250  		if err != nil {
   251  			fs.Infof(nil, "proxy login failed: %v", err)
   252  			return false, nil
   253  		}
   254  		id, err := findID(connServeFunction)
   255  		if err != nil {
   256  			fs.Infof(nil, "proxy login failed: failed to read ID from stack: %v", err)
   257  			return false, nil
   258  		}
   259  		s.pendingMu.Lock()
   260  		d := s.pending[id]
   261  		delete(s.pending, id)
   262  		s.pendingMu.Unlock()
   263  		if d == nil {
   264  			return false, errors.Errorf("proxy login failed: failed to find pending Driver under ID %q", id)
   265  		}
   266  		d.vfs = VFS
   267  	} else {
   268  		ok = s.opt.BasicUser == user && (s.opt.BasicPass == "" || s.opt.BasicPass == pass)
   269  		if !ok {
   270  			fs.Infof(nil, "login failed: bad credentials")
   271  			return false, nil
   272  		}
   273  	}
   274  	return true, nil
   275  }
   276  
   277  // NewDriver starts a new session for each client connection
   278  func (s *server) NewDriver() (ftp.Driver, error) {
   279  	log.Trace("", "Init driver")("")
   280  	d := &Driver{
   281  		s:   s,
   282  		vfs: s.vfs, // this can be nil if proxy set
   283  	}
   284  	return d, nil
   285  }
   286  
   287  //Driver implementation of ftp server
   288  type Driver struct {
   289  	s    *server
   290  	vfs  *vfs.VFS
   291  	lock sync.Mutex
   292  }
   293  
   294  //Init a connection
   295  func (d *Driver) Init(c *ftp.Conn) {
   296  	defer log.Trace("", "Init session")("")
   297  	if d.s.proxy != nil {
   298  		id := fmt.Sprintf("%p", c)
   299  		d.s.pendingMu.Lock()
   300  		d.s.pending[id] = d
   301  		d.s.pendingMu.Unlock()
   302  	}
   303  }
   304  
   305  //Stat get information on file or folder
   306  func (d *Driver) Stat(path string) (fi ftp.FileInfo, err error) {
   307  	defer log.Trace(path, "")("fi=%+v, err = %v", &fi, &err)
   308  	n, err := d.vfs.Stat(path)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  	return &FileInfo{n, n.Mode(), d.vfs.Opt.UID, d.vfs.Opt.GID}, err
   313  }
   314  
   315  //ChangeDir move current folder
   316  func (d *Driver) ChangeDir(path string) (err error) {
   317  	d.lock.Lock()
   318  	defer d.lock.Unlock()
   319  	defer log.Trace(path, "")("err = %v", &err)
   320  	n, err := d.vfs.Stat(path)
   321  	if err != nil {
   322  		return err
   323  	}
   324  	if !n.IsDir() {
   325  		return errors.New("Not a directory")
   326  	}
   327  	return nil
   328  }
   329  
   330  //ListDir list content of a folder
   331  func (d *Driver) ListDir(path string, callback func(ftp.FileInfo) error) (err error) {
   332  	d.lock.Lock()
   333  	defer d.lock.Unlock()
   334  	defer log.Trace(path, "")("err = %v", &err)
   335  	node, err := d.vfs.Stat(path)
   336  	if err == vfs.ENOENT {
   337  		return errors.New("Directory not found")
   338  	} else if err != nil {
   339  		return err
   340  	}
   341  	if !node.IsDir() {
   342  		return errors.New("Not a directory")
   343  	}
   344  
   345  	dir := node.(*vfs.Dir)
   346  	dirEntries, err := dir.ReadDirAll()
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	// Account the transfer
   352  	tr := accounting.GlobalStats().NewTransferRemoteSize(path, node.Size())
   353  	defer func() {
   354  		tr.Done(err)
   355  	}()
   356  
   357  	for _, file := range dirEntries {
   358  		err = callback(&FileInfo{file, file.Mode(), d.vfs.Opt.UID, d.vfs.Opt.GID})
   359  		if err != nil {
   360  			return err
   361  		}
   362  	}
   363  	return nil
   364  }
   365  
   366  //DeleteDir delete a folder and his content
   367  func (d *Driver) DeleteDir(path string) (err error) {
   368  	d.lock.Lock()
   369  	defer d.lock.Unlock()
   370  	defer log.Trace(path, "")("err = %v", &err)
   371  	node, err := d.vfs.Stat(path)
   372  	if err != nil {
   373  		return err
   374  	}
   375  	if !node.IsDir() {
   376  		return errors.New("Not a directory")
   377  	}
   378  	err = node.Remove()
   379  	if err != nil {
   380  		return err
   381  	}
   382  	return nil
   383  }
   384  
   385  //DeleteFile delete a file
   386  func (d *Driver) DeleteFile(path string) (err error) {
   387  	d.lock.Lock()
   388  	defer d.lock.Unlock()
   389  	defer log.Trace(path, "")("err = %v", &err)
   390  	node, err := d.vfs.Stat(path)
   391  	if err != nil {
   392  		return err
   393  	}
   394  	if !node.IsFile() {
   395  		return errors.New("Not a file")
   396  	}
   397  	err = node.Remove()
   398  	if err != nil {
   399  		return err
   400  	}
   401  	return nil
   402  }
   403  
   404  //Rename rename a file or folder
   405  func (d *Driver) Rename(oldName, newName string) (err error) {
   406  	d.lock.Lock()
   407  	defer d.lock.Unlock()
   408  	defer log.Trace(oldName, "newName=%q", newName)("err = %v", &err)
   409  	return d.vfs.Rename(oldName, newName)
   410  }
   411  
   412  //MakeDir create a folder
   413  func (d *Driver) MakeDir(path string) (err error) {
   414  	d.lock.Lock()
   415  	defer d.lock.Unlock()
   416  	defer log.Trace(path, "")("err = %v", &err)
   417  	dir, leaf, err := d.vfs.StatParent(path)
   418  	if err != nil {
   419  		return err
   420  	}
   421  	_, err = dir.Mkdir(leaf)
   422  	return err
   423  }
   424  
   425  //GetFile download a file
   426  func (d *Driver) GetFile(path string, offset int64) (size int64, fr io.ReadCloser, err error) {
   427  	d.lock.Lock()
   428  	defer d.lock.Unlock()
   429  	defer log.Trace(path, "offset=%v", offset)("err = %v", &err)
   430  	node, err := d.vfs.Stat(path)
   431  	if err == vfs.ENOENT {
   432  		fs.Infof(path, "File not found")
   433  		return 0, nil, errors.New("File not found")
   434  	} else if err != nil {
   435  		return 0, nil, err
   436  	}
   437  	if !node.IsFile() {
   438  		return 0, nil, errors.New("Not a file")
   439  	}
   440  
   441  	handle, err := node.Open(os.O_RDONLY)
   442  	if err != nil {
   443  		return 0, nil, err
   444  	}
   445  	_, err = handle.Seek(offset, io.SeekStart)
   446  	if err != nil {
   447  		return 0, nil, err
   448  	}
   449  
   450  	// Account the transfer
   451  	tr := accounting.GlobalStats().NewTransferRemoteSize(path, node.Size())
   452  	defer tr.Done(nil)
   453  
   454  	return node.Size(), handle, nil
   455  }
   456  
   457  //PutFile upload a file
   458  func (d *Driver) PutFile(path string, data io.Reader, appendData bool) (n int64, err error) {
   459  	d.lock.Lock()
   460  	defer d.lock.Unlock()
   461  	defer log.Trace(path, "append=%v", appendData)("err = %v", &err)
   462  	var isExist bool
   463  	node, err := d.vfs.Stat(path)
   464  	if err == nil {
   465  		isExist = true
   466  		if node.IsDir() {
   467  			return 0, errors.New("A dir has the same name")
   468  		}
   469  	} else {
   470  		if os.IsNotExist(err) {
   471  			isExist = false
   472  		} else {
   473  			return 0, err
   474  		}
   475  	}
   476  
   477  	if appendData && !isExist {
   478  		appendData = false
   479  	}
   480  
   481  	if !appendData {
   482  		if isExist {
   483  			err = node.Remove()
   484  			if err != nil {
   485  				return 0, err
   486  			}
   487  		}
   488  		f, err := d.vfs.OpenFile(path, os.O_RDWR|os.O_CREATE, 0660)
   489  		if err != nil {
   490  			return 0, err
   491  		}
   492  		defer closeIO(path, f)
   493  		bytes, err := io.Copy(f, data)
   494  		if err != nil {
   495  			return 0, err
   496  		}
   497  		return bytes, nil
   498  	}
   499  
   500  	of, err := d.vfs.OpenFile(path, os.O_APPEND|os.O_RDWR, 0660)
   501  	if err != nil {
   502  		return 0, err
   503  	}
   504  	defer closeIO(path, of)
   505  
   506  	_, err = of.Seek(0, os.SEEK_END)
   507  	if err != nil {
   508  		return 0, err
   509  	}
   510  
   511  	bytes, err := io.Copy(of, data)
   512  	if err != nil {
   513  		return 0, err
   514  	}
   515  
   516  	return bytes, nil
   517  }
   518  
   519  //FileInfo struct to hold file info for ftp server
   520  type FileInfo struct {
   521  	os.FileInfo
   522  
   523  	mode  os.FileMode
   524  	owner uint32
   525  	group uint32
   526  }
   527  
   528  //Mode return mode of file.
   529  func (f *FileInfo) Mode() os.FileMode {
   530  	return f.mode
   531  }
   532  
   533  //Owner return owner of file. Try to find the username if possible
   534  func (f *FileInfo) Owner() string {
   535  	str := fmt.Sprint(f.owner)
   536  	u, err := user.LookupId(str)
   537  	if err != nil {
   538  		return str //User not found
   539  	}
   540  	return u.Username
   541  }
   542  
   543  //Group return group of file. Try to find the group name if possible
   544  func (f *FileInfo) Group() string {
   545  	str := fmt.Sprint(f.group)
   546  	g, err := user.LookupGroupId(str)
   547  	if err != nil {
   548  		return str //Group not found default to numerical value
   549  	}
   550  	return g.Name
   551  }
   552  
   553  func closeIO(path string, c io.Closer) {
   554  	err := c.Close()
   555  	if err != nil {
   556  		log.Trace(path, "")("err = %v", &err)
   557  	}
   558  }