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 }