github.com/artpar/rclone@v1.67.3/backend/ftp/ftp.go (about) 1 // Package ftp interfaces with FTP servers 2 package ftp 3 4 import ( 5 "context" 6 "crypto/tls" 7 "errors" 8 "fmt" 9 "io" 10 "net" 11 "net/textproto" 12 "path" 13 "runtime" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/artpar/rclone/fs" 19 "github.com/artpar/rclone/fs/accounting" 20 "github.com/artpar/rclone/fs/config" 21 "github.com/artpar/rclone/fs/config/configmap" 22 "github.com/artpar/rclone/fs/config/configstruct" 23 "github.com/artpar/rclone/fs/config/obscure" 24 "github.com/artpar/rclone/fs/fserrors" 25 "github.com/artpar/rclone/fs/fshttp" 26 "github.com/artpar/rclone/fs/hash" 27 "github.com/artpar/rclone/lib/encoder" 28 "github.com/artpar/rclone/lib/env" 29 "github.com/artpar/rclone/lib/pacer" 30 "github.com/artpar/rclone/lib/proxy" 31 "github.com/artpar/rclone/lib/readers" 32 "github.com/jlaffaye/ftp" 33 ) 34 35 var ( 36 currentUser = env.CurrentUser() 37 ) 38 39 const ( 40 minSleep = 10 * time.Millisecond 41 maxSleep = 2 * time.Second 42 decayConstant = 2 // bigger for slower decay, exponential 43 ) 44 45 // Register with Fs 46 func init() { 47 fs.Register(&fs.RegInfo{ 48 Name: "ftp", 49 Description: "FTP", 50 NewFs: NewFs, 51 Options: []fs.Option{{ 52 Name: "host", 53 Help: "FTP host to connect to.\n\nE.g. \"ftp.example.com\".", 54 Required: true, 55 Sensitive: true, 56 }, { 57 Name: "user", 58 Help: "FTP username.", 59 Default: currentUser, 60 Sensitive: true, 61 }, { 62 Name: "port", 63 Help: "FTP port number.", 64 Default: 21, 65 }, { 66 Name: "pass", 67 Help: "FTP password.", 68 IsPassword: true, 69 }, { 70 Name: "tls", 71 Help: `Use Implicit FTPS (FTP over TLS). 72 73 When using implicit FTP over TLS the client connects using TLS 74 right from the start which breaks compatibility with 75 non-TLS-aware servers. This is usually served over port 990 rather 76 than port 21. Cannot be used in combination with explicit FTPS.`, 77 Default: false, 78 }, { 79 Name: "explicit_tls", 80 Help: `Use Explicit FTPS (FTP over TLS). 81 82 When using explicit FTP over TLS the client explicitly requests 83 security from the server in order to upgrade a plain text connection 84 to an encrypted one. Cannot be used in combination with implicit FTPS.`, 85 Default: false, 86 }, { 87 Name: "concurrency", 88 Help: strings.Replace(`Maximum number of FTP simultaneous connections, 0 for unlimited. 89 90 Note that setting this is very likely to cause deadlocks so it should 91 be used with care. 92 93 If you are doing a sync or copy then make sure concurrency is one more 94 than the sum of |--transfers| and |--checkers|. 95 96 If you use |--check-first| then it just needs to be one more than the 97 maximum of |--checkers| and |--transfers|. 98 99 So for |concurrency 3| you'd use |--checkers 2 --transfers 2 100 --check-first| or |--checkers 1 --transfers 1|. 101 102 `, "|", "`", -1), 103 Default: 0, 104 Advanced: true, 105 }, { 106 Name: "no_check_certificate", 107 Help: "Do not verify the TLS certificate of the server.", 108 Default: false, 109 Advanced: true, 110 }, { 111 Name: "disable_epsv", 112 Help: "Disable using EPSV even if server advertises support.", 113 Default: false, 114 Advanced: true, 115 }, { 116 Name: "disable_mlsd", 117 Help: "Disable using MLSD even if server advertises support.", 118 Default: false, 119 Advanced: true, 120 }, { 121 Name: "disable_utf8", 122 Help: "Disable using UTF-8 even if server advertises support.", 123 Default: false, 124 Advanced: true, 125 }, { 126 Name: "writing_mdtm", 127 Help: "Use MDTM to set modification time (VsFtpd quirk)", 128 Default: false, 129 Advanced: true, 130 }, { 131 Name: "force_list_hidden", 132 Help: "Use LIST -a to force listing of hidden files and folders. This will disable the use of MLSD.", 133 Default: false, 134 Advanced: true, 135 }, { 136 Name: "idle_timeout", 137 Default: fs.Duration(60 * time.Second), 138 Help: `Max time before closing idle connections. 139 140 If no connections have been returned to the connection pool in the time 141 given, rclone will empty the connection pool. 142 143 Set to 0 to keep connections indefinitely. 144 `, 145 Advanced: true, 146 }, { 147 Name: "close_timeout", 148 Help: "Maximum time to wait for a response to close.", 149 Default: fs.Duration(60 * time.Second), 150 Advanced: true, 151 }, { 152 Name: "tls_cache_size", 153 Help: `Size of TLS session cache for all control and data connections. 154 155 TLS cache allows to resume TLS sessions and reuse PSK between connections. 156 Increase if default size is not enough resulting in TLS resumption errors. 157 Enabled by default. Use 0 to disable.`, 158 Default: 32, 159 Advanced: true, 160 }, { 161 Name: "disable_tls13", 162 Help: "Disable TLS 1.3 (workaround for FTP servers with buggy TLS)", 163 Default: false, 164 Advanced: true, 165 }, { 166 Name: "shut_timeout", 167 Help: "Maximum time to wait for data connection closing status.", 168 Default: fs.Duration(60 * time.Second), 169 Advanced: true, 170 }, { 171 Name: "ask_password", 172 Default: false, 173 Help: `Allow asking for FTP password when needed. 174 175 If this is set and no password is supplied then rclone will ask for a password 176 `, 177 Advanced: true, 178 }, { 179 Name: "socks_proxy", 180 Default: "", 181 Help: `Socks 5 proxy host. 182 183 Supports the format user:pass@host:port, user@host:port, host:port. 184 185 Example: 186 187 myUser:myPass@localhost:9005 188 `, 189 Advanced: true, 190 }, { 191 Name: config.ConfigEncoding, 192 Help: config.ConfigEncodingHelp, 193 Advanced: true, 194 // The FTP protocol can't handle trailing spaces 195 // (for instance, pureftpd turns them into '_') 196 Default: (encoder.Display | 197 encoder.EncodeRightSpace), 198 Examples: []fs.OptionExample{{ 199 Value: "Asterisk,Ctl,Dot,Slash", 200 Help: "ProFTPd can't handle '*' in file names", 201 }, { 202 Value: "BackSlash,Ctl,Del,Dot,RightSpace,Slash,SquareBracket", 203 Help: "PureFTPd can't handle '[]' or '*' in file names", 204 }, { 205 Value: "Ctl,LeftPeriod,Slash", 206 Help: "VsFTPd can't handle file names starting with dot", 207 }}, 208 }}, 209 }) 210 } 211 212 // Options defines the configuration for this backend 213 type Options struct { 214 Host string `config:"host"` 215 User string `config:"user"` 216 Pass string `config:"pass"` 217 Port string `config:"port"` 218 TLS bool `config:"tls"` 219 ExplicitTLS bool `config:"explicit_tls"` 220 TLSCacheSize int `config:"tls_cache_size"` 221 DisableTLS13 bool `config:"disable_tls13"` 222 Concurrency int `config:"concurrency"` 223 SkipVerifyTLSCert bool `config:"no_check_certificate"` 224 DisableEPSV bool `config:"disable_epsv"` 225 DisableMLSD bool `config:"disable_mlsd"` 226 DisableUTF8 bool `config:"disable_utf8"` 227 WritingMDTM bool `config:"writing_mdtm"` 228 ForceListHidden bool `config:"force_list_hidden"` 229 IdleTimeout fs.Duration `config:"idle_timeout"` 230 CloseTimeout fs.Duration `config:"close_timeout"` 231 ShutTimeout fs.Duration `config:"shut_timeout"` 232 AskPassword bool `config:"ask_password"` 233 Enc encoder.MultiEncoder `config:"encoding"` 234 SocksProxy string `config:"socks_proxy"` 235 } 236 237 // Fs represents a remote FTP server 238 type Fs struct { 239 name string // name of this remote 240 root string // the path we are working on if any 241 opt Options // parsed options 242 ci *fs.ConfigInfo // global config 243 features *fs.Features // optional features 244 url string 245 user string 246 pass string 247 dialAddr string 248 poolMu sync.Mutex 249 pool []*ftp.ServerConn 250 drain *time.Timer // used to drain the pool when we stop using the connections 251 tokens *pacer.TokenDispenser 252 pacer *fs.Pacer // pacer for FTP connections 253 fGetTime bool // true if the ftp library accepts GetTime 254 fSetTime bool // true if the ftp library accepts SetTime 255 fLstTime bool // true if the List call returns precise time 256 } 257 258 // Object describes an FTP file 259 type Object struct { 260 fs *Fs 261 remote string 262 info *FileInfo 263 } 264 265 // FileInfo is the metadata known about an FTP file 266 type FileInfo struct { 267 Name string 268 Size uint64 269 ModTime time.Time 270 precise bool // true if the time is precise 271 IsDir bool 272 } 273 274 // ------------------------------------------------------------ 275 276 // Name of this fs 277 func (f *Fs) Name() string { 278 return f.name 279 } 280 281 // Root of the remote (as passed into NewFs) 282 func (f *Fs) Root() string { 283 return f.root 284 } 285 286 // String returns a description of the FS 287 func (f *Fs) String() string { 288 return f.url 289 } 290 291 // Features returns the optional features of this Fs 292 func (f *Fs) Features() *fs.Features { 293 return f.features 294 } 295 296 // Enable debugging output 297 type debugLog struct { 298 mu sync.Mutex 299 auth bool 300 } 301 302 // Write writes len(p) bytes from p to the underlying data stream. It returns 303 // the number of bytes written from p (0 <= n <= len(p)) and any error 304 // encountered that caused the write to stop early. Write must return a non-nil 305 // error if it returns n < len(p). Write must not modify the slice data, even 306 // temporarily. 307 // 308 // Implementations must not retain p. 309 // 310 // This writes debug info to the log 311 func (dl *debugLog) Write(p []byte) (n int, err error) { 312 dl.mu.Lock() 313 defer dl.mu.Unlock() 314 _, file, _, ok := runtime.Caller(1) 315 direction := "FTP Rx" 316 if ok && strings.Contains(file, "multi") { 317 direction = "FTP Tx" 318 } 319 lines := strings.Split(string(p), "\r\n") 320 if lines[len(lines)-1] == "" { 321 lines = lines[:len(lines)-1] 322 } 323 for _, line := range lines { 324 if !dl.auth && strings.HasPrefix(line, "PASS") { 325 fs.Debugf(direction, "PASS *****") 326 continue 327 } 328 fs.Debugf(direction, "%q", line) 329 } 330 return len(p), nil 331 } 332 333 // Return a *textproto.Error if err contains one or nil otherwise 334 func textprotoError(err error) (errX *textproto.Error) { 335 if errors.As(err, &errX) { 336 return errX 337 } 338 return nil 339 } 340 341 // returns true if this FTP error should be retried 342 func isRetriableFtpError(err error) bool { 343 if errX := textprotoError(err); errX != nil { 344 switch errX.Code { 345 case ftp.StatusNotAvailable, ftp.StatusTransfertAborted: 346 return true 347 } 348 } 349 return false 350 } 351 352 // shouldRetry returns a boolean as to whether this err deserve to be 353 // retried. It returns the err as a convenience 354 func shouldRetry(ctx context.Context, err error) (bool, error) { 355 if fserrors.ContextError(ctx, &err) { 356 return false, err 357 } 358 if isRetriableFtpError(err) { 359 return true, err 360 } 361 return fserrors.ShouldRetry(err), err 362 } 363 364 // Get a TLS config with a unique session cache. 365 // 366 // We can't share session caches between connections. 367 // 368 // See: https://github.com/artpar/rclone/issues/7234 369 func (f *Fs) tlsConfig() *tls.Config { 370 var tlsConfig *tls.Config 371 if f.opt.TLS || f.opt.ExplicitTLS { 372 tlsConfig = &tls.Config{ 373 ServerName: f.opt.Host, 374 InsecureSkipVerify: f.opt.SkipVerifyTLSCert, 375 } 376 if f.opt.TLSCacheSize > 0 { 377 tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(f.opt.TLSCacheSize) 378 } 379 if f.opt.DisableTLS13 { 380 tlsConfig.MaxVersion = tls.VersionTLS12 381 } 382 } 383 return tlsConfig 384 } 385 386 // Open a new connection to the FTP server. 387 func (f *Fs) ftpConnection(ctx context.Context) (c *ftp.ServerConn, err error) { 388 fs.Debugf(f, "Connecting to FTP server") 389 390 // tls.Config for this connection only. Will be used for data 391 // and control connections. 392 tlsConfig := f.tlsConfig() 393 394 // Make ftp library dial with fshttp dialer optionally using TLS 395 initialConnection := true 396 dial := func(network, address string) (conn net.Conn, err error) { 397 fs.Debugf(f, "dial(%q,%q)", network, address) 398 defer func() { 399 fs.Debugf(f, "> dial: conn=%T, err=%v", conn, err) 400 }() 401 baseDialer := fshttp.NewDialer(ctx) 402 if f.opt.SocksProxy != "" { 403 conn, err = proxy.SOCKS5Dial(network, address, f.opt.SocksProxy, baseDialer) 404 } else { 405 conn, err = baseDialer.Dial(network, address) 406 } 407 if err != nil { 408 return nil, err 409 } 410 // Connect using cleartext only for non TLS 411 if tlsConfig == nil { 412 return conn, nil 413 } 414 // Initial connection only needs to be cleartext for explicit TLS 415 if f.opt.ExplicitTLS && initialConnection { 416 initialConnection = false 417 return conn, nil 418 } 419 // Upgrade connection to TLS 420 tlsConn := tls.Client(conn, tlsConfig) 421 // Do the initial handshake - tls.Client doesn't do it for us 422 // If we do this then connections to proftpd/pureftpd lock up 423 // See: https://github.com/artpar/rclone/issues/6426 424 // See: https://github.com/jlaffaye/ftp/issues/282 425 if false { 426 err = tlsConn.HandshakeContext(ctx) 427 if err != nil { 428 _ = conn.Close() 429 return nil, err 430 } 431 } 432 return tlsConn, nil 433 } 434 ftpConfig := []ftp.DialOption{ 435 ftp.DialWithContext(ctx), 436 ftp.DialWithDialFunc(dial), 437 } 438 439 if f.opt.TLS { 440 // Our dialer takes care of TLS but ftp library also needs tlsConf 441 // as a trigger for sending PSBZ and PROT options to server. 442 ftpConfig = append(ftpConfig, ftp.DialWithTLS(tlsConfig)) 443 } else if f.opt.ExplicitTLS { 444 ftpConfig = append(ftpConfig, ftp.DialWithExplicitTLS(tlsConfig)) 445 } 446 if f.opt.DisableEPSV { 447 ftpConfig = append(ftpConfig, ftp.DialWithDisabledEPSV(true)) 448 } 449 if f.opt.DisableMLSD { 450 ftpConfig = append(ftpConfig, ftp.DialWithDisabledMLSD(true)) 451 } 452 if f.opt.DisableUTF8 { 453 ftpConfig = append(ftpConfig, ftp.DialWithDisabledUTF8(true)) 454 } 455 if f.opt.ShutTimeout != 0 && f.opt.ShutTimeout != fs.DurationOff { 456 ftpConfig = append(ftpConfig, ftp.DialWithShutTimeout(time.Duration(f.opt.ShutTimeout))) 457 } 458 if f.opt.WritingMDTM { 459 ftpConfig = append(ftpConfig, ftp.DialWithWritingMDTM(true)) 460 } 461 if f.opt.ForceListHidden { 462 ftpConfig = append(ftpConfig, ftp.DialWithForceListHidden(true)) 463 } 464 if f.ci.Dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpRequests|fs.DumpResponses) != 0 { 465 ftpConfig = append(ftpConfig, ftp.DialWithDebugOutput(&debugLog{auth: f.ci.Dump&fs.DumpAuth != 0})) 466 } 467 err = f.pacer.Call(func() (bool, error) { 468 c, err = ftp.Dial(f.dialAddr, ftpConfig...) 469 if err != nil { 470 return shouldRetry(ctx, err) 471 } 472 err = c.Login(f.user, f.pass) 473 if err != nil { 474 _ = c.Quit() 475 return shouldRetry(ctx, err) 476 } 477 return false, nil 478 }) 479 if err != nil { 480 err = fmt.Errorf("failed to make FTP connection to %q: %w", f.dialAddr, err) 481 } 482 return c, err 483 } 484 485 // Get an FTP connection from the pool, or open a new one 486 func (f *Fs) getFtpConnection(ctx context.Context) (c *ftp.ServerConn, err error) { 487 if f.opt.Concurrency > 0 { 488 f.tokens.Get() 489 } 490 accounting.LimitTPS(ctx) 491 f.poolMu.Lock() 492 if len(f.pool) > 0 { 493 c = f.pool[0] 494 f.pool = f.pool[1:] 495 } 496 f.poolMu.Unlock() 497 if c != nil { 498 return c, nil 499 } 500 c, err = f.ftpConnection(ctx) 501 if err != nil && f.opt.Concurrency > 0 { 502 f.tokens.Put() 503 } 504 return c, err 505 } 506 507 // Return an FTP connection to the pool 508 // 509 // It nils the pointed to connection out so it can't be reused 510 // 511 // if err is not nil then it checks the connection is alive using a 512 // NOOP request 513 func (f *Fs) putFtpConnection(pc **ftp.ServerConn, err error) { 514 if f.opt.Concurrency > 0 { 515 defer f.tokens.Put() 516 } 517 if pc == nil { 518 return 519 } 520 c := *pc 521 if c == nil { 522 return 523 } 524 *pc = nil 525 if err != nil { 526 // If not a regular FTP error code then check the connection 527 if tpErr := textprotoError(err); tpErr != nil { 528 nopErr := c.NoOp() 529 if nopErr != nil { 530 fs.Debugf(f, "Connection failed, closing: %v", nopErr) 531 _ = c.Quit() 532 return 533 } 534 } 535 } 536 f.poolMu.Lock() 537 f.pool = append(f.pool, c) 538 if f.opt.IdleTimeout > 0 { 539 f.drain.Reset(time.Duration(f.opt.IdleTimeout)) // nudge on the pool emptying timer 540 } 541 f.poolMu.Unlock() 542 } 543 544 // Drain the pool of any connections 545 func (f *Fs) drainPool(ctx context.Context) (err error) { 546 f.poolMu.Lock() 547 defer f.poolMu.Unlock() 548 if f.opt.IdleTimeout > 0 { 549 f.drain.Stop() 550 } 551 if len(f.pool) != 0 { 552 fs.Debugf(f, "closing %d unused connections", len(f.pool)) 553 } 554 for i, c := range f.pool { 555 if cErr := c.Quit(); cErr != nil { 556 err = cErr 557 } 558 f.pool[i] = nil 559 } 560 f.pool = nil 561 return err 562 } 563 564 // NewFs constructs an Fs from the path, container:path 565 func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (ff fs.Fs, err error) { 566 // defer fs.Trace(nil, "name=%q, root=%q", name, root)("fs=%v, err=%v", &ff, &err) 567 // Parse config into Options struct 568 opt := new(Options) 569 err = configstruct.Set(m, opt) 570 if err != nil { 571 return nil, err 572 } 573 pass := "" 574 if opt.AskPassword && opt.Pass == "" { 575 pass = config.GetPassword("FTP server password") 576 } else { 577 pass, err = obscure.Reveal(opt.Pass) 578 if err != nil { 579 return nil, fmt.Errorf("NewFS decrypt password: %w", err) 580 } 581 } 582 user := opt.User 583 if user == "" { 584 user = currentUser 585 } 586 port := opt.Port 587 if port == "" { 588 port = "21" 589 } 590 591 dialAddr := opt.Host + ":" + port 592 protocol := "ftp://" 593 if opt.TLS { 594 protocol = "ftps://" 595 } 596 if opt.TLS && opt.ExplicitTLS { 597 return nil, errors.New("implicit TLS and explicit TLS are mutually incompatible, please revise your config") 598 } 599 u := protocol + path.Join(dialAddr+"/", root) 600 ci := fs.GetConfig(ctx) 601 f := &Fs{ 602 name: name, 603 root: root, 604 opt: *opt, 605 ci: ci, 606 url: u, 607 user: user, 608 pass: pass, 609 dialAddr: dialAddr, 610 tokens: pacer.NewTokenDispenser(opt.Concurrency), 611 pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), 612 } 613 f.features = (&fs.Features{ 614 CanHaveEmptyDirectories: true, 615 PartialUploads: true, 616 }).Fill(ctx, f) 617 // set the pool drainer timer going 618 if f.opt.IdleTimeout > 0 { 619 f.drain = time.AfterFunc(time.Duration(opt.IdleTimeout), func() { _ = f.drainPool(ctx) }) 620 } 621 // Make a connection and pool it to return errors early 622 c, err := f.getFtpConnection(ctx) 623 if err != nil { 624 return nil, fmt.Errorf("NewFs: %w", err) 625 } 626 f.fGetTime = c.IsGetTimeSupported() 627 f.fSetTime = c.IsSetTimeSupported() 628 f.fLstTime = c.IsTimePreciseInList() 629 if !f.fLstTime && f.fGetTime { 630 f.features.SlowModTime = true 631 } 632 f.putFtpConnection(&c, nil) 633 if root != "" { 634 // Check to see if the root actually an existing file 635 remote := path.Base(root) 636 f.root = path.Dir(root) 637 if f.root == "." { 638 f.root = "" 639 } 640 _, err := f.NewObject(ctx, remote) 641 if err != nil { 642 if err == fs.ErrorObjectNotFound || errors.Is(err, fs.ErrorNotAFile) { 643 // File doesn't exist so return old f 644 f.root = root 645 return f, nil 646 } 647 return nil, err 648 } 649 // return an error with an fs which points to the parent 650 return f, fs.ErrorIsFile 651 } 652 return f, err 653 } 654 655 // Shutdown the backend, closing any background tasks and any 656 // cached connections. 657 func (f *Fs) Shutdown(ctx context.Context) error { 658 return f.drainPool(ctx) 659 } 660 661 // translateErrorFile turns FTP errors into rclone errors if possible for a file 662 func translateErrorFile(err error) error { 663 if errX := textprotoError(err); errX != nil { 664 switch errX.Code { 665 case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored: 666 err = fs.ErrorObjectNotFound 667 } 668 } 669 return err 670 } 671 672 // translateErrorDir turns FTP errors into rclone errors if possible for a directory 673 func translateErrorDir(err error) error { 674 if errX := textprotoError(err); errX != nil { 675 switch errX.Code { 676 case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored: 677 err = fs.ErrorDirNotFound 678 } 679 } 680 return err 681 } 682 683 // entryToStandard converts an incoming ftp.Entry to Standard encoding 684 func (f *Fs) entryToStandard(entry *ftp.Entry) { 685 // Skip . and .. as we don't want these encoded 686 if entry.Name == "." || entry.Name == ".." { 687 return 688 } 689 entry.Name = f.opt.Enc.ToStandardName(entry.Name) 690 entry.Target = f.opt.Enc.ToStandardPath(entry.Target) 691 } 692 693 // dirFromStandardPath returns dir in encoded form. 694 func (f *Fs) dirFromStandardPath(dir string) string { 695 // Skip . and .. as we don't want these encoded 696 if dir == "." || dir == ".." { 697 return dir 698 } 699 return f.opt.Enc.FromStandardPath(dir) 700 } 701 702 // findItem finds a directory entry for the name in its parent directory 703 func (f *Fs) findItem(ctx context.Context, remote string) (entry *ftp.Entry, err error) { 704 // defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err) 705 if remote == "" || remote == "." || remote == "/" { 706 // if root, assume exists and synthesize an entry 707 return &ftp.Entry{ 708 Name: "", 709 Type: ftp.EntryTypeFolder, 710 Time: time.Now(), 711 }, nil 712 } 713 714 c, err := f.getFtpConnection(ctx) 715 if err != nil { 716 return nil, fmt.Errorf("findItem: %w", err) 717 } 718 719 // returns TRUE if MLST is supported which is required to call GetEntry 720 if c.IsTimePreciseInList() { 721 entry, err := c.GetEntry(f.opt.Enc.FromStandardPath(remote)) 722 f.putFtpConnection(&c, err) 723 if err != nil { 724 err = translateErrorFile(err) 725 if err == fs.ErrorObjectNotFound { 726 return nil, nil 727 } 728 if errX := textprotoError(err); errX != nil { 729 switch errX.Code { 730 case ftp.StatusBadArguments: 731 err = nil 732 } 733 } 734 return nil, err 735 } 736 if entry != nil { 737 f.entryToStandard(entry) 738 } 739 return entry, nil 740 } 741 742 dir := path.Dir(remote) 743 base := path.Base(remote) 744 745 files, err := c.List(f.dirFromStandardPath(dir)) 746 f.putFtpConnection(&c, err) 747 if err != nil { 748 return nil, translateErrorFile(err) 749 } 750 for _, file := range files { 751 f.entryToStandard(file) 752 if file.Name == base { 753 return file, nil 754 } 755 } 756 return nil, nil 757 } 758 759 // NewObject finds the Object at remote. If it can't be found 760 // it returns the error fs.ErrorObjectNotFound. 761 func (f *Fs) NewObject(ctx context.Context, remote string) (o fs.Object, err error) { 762 // defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err) 763 entry, err := f.findItem(ctx, path.Join(f.root, remote)) 764 if err != nil { 765 return nil, err 766 } 767 if entry != nil && entry.Type != ftp.EntryTypeFolder { 768 o := &Object{ 769 fs: f, 770 remote: remote, 771 } 772 o.info = &FileInfo{ 773 Name: remote, 774 Size: entry.Size, 775 ModTime: entry.Time, 776 precise: f.fLstTime, 777 } 778 return o, nil 779 } 780 return nil, fs.ErrorObjectNotFound 781 } 782 783 // dirExists checks the directory pointed to by remote exists or not 784 func (f *Fs) dirExists(ctx context.Context, remote string) (exists bool, err error) { 785 entry, err := f.findItem(ctx, path.Join(f.root, remote)) 786 if err != nil { 787 return false, fmt.Errorf("dirExists: %w", err) 788 } 789 if entry != nil && entry.Type == ftp.EntryTypeFolder { 790 return true, nil 791 } 792 return false, nil 793 } 794 795 // List the objects and directories in dir into entries. The 796 // entries can be returned in any order but should be for a 797 // complete directory. 798 // 799 // dir should be "" to list the root, and should not have 800 // trailing slashes. 801 // 802 // This should return ErrDirNotFound if the directory isn't 803 // found. 804 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 805 // defer log.Trace(dir, "dir=%q", dir)("entries=%v, err=%v", &entries, &err) 806 c, err := f.getFtpConnection(ctx) 807 if err != nil { 808 return nil, fmt.Errorf("list: %w", err) 809 } 810 811 var listErr error 812 var files []*ftp.Entry 813 814 resultchan := make(chan []*ftp.Entry, 1) 815 errchan := make(chan error, 1) 816 go func() { 817 result, err := c.List(f.dirFromStandardPath(path.Join(f.root, dir))) 818 f.putFtpConnection(&c, err) 819 if err != nil { 820 errchan <- err 821 return 822 } 823 resultchan <- result 824 }() 825 826 // Wait for List for up to Timeout seconds 827 timer := time.NewTimer(f.ci.TimeoutOrInfinite()) 828 select { 829 case listErr = <-errchan: 830 timer.Stop() 831 return nil, translateErrorDir(listErr) 832 case files = <-resultchan: 833 timer.Stop() 834 case <-timer.C: 835 // if timer fired assume no error but connection dead 836 fs.Errorf(f, "Timeout when waiting for List") 837 return nil, errors.New("timeout when waiting for List") 838 } 839 840 // Annoyingly FTP returns success for a directory which 841 // doesn't exist, so check it really doesn't exist if no 842 // entries found. 843 if len(files) == 0 { 844 exists, err := f.dirExists(ctx, dir) 845 if err != nil { 846 return nil, fmt.Errorf("list: %w", err) 847 } 848 if !exists { 849 return nil, fs.ErrorDirNotFound 850 } 851 } 852 for i := range files { 853 object := files[i] 854 f.entryToStandard(object) 855 newremote := path.Join(dir, object.Name) 856 switch object.Type { 857 case ftp.EntryTypeFolder: 858 if object.Name == "." || object.Name == ".." { 859 continue 860 } 861 d := fs.NewDir(newremote, object.Time) 862 entries = append(entries, d) 863 default: 864 o := &Object{ 865 fs: f, 866 remote: newremote, 867 } 868 info := &FileInfo{ 869 Name: newremote, 870 Size: object.Size, 871 ModTime: object.Time, 872 precise: f.fLstTime, 873 } 874 o.info = info 875 entries = append(entries, o) 876 } 877 } 878 return entries, nil 879 } 880 881 // Hashes are not supported 882 func (f *Fs) Hashes() hash.Set { 883 return 0 884 } 885 886 // Precision shows whether modified time is supported or not depending on the 887 // FTP server capabilities, namely whether FTP server: 888 // - accepts the MDTM command to get file time (fGetTime) 889 // or supports MLSD returning precise file time in the list (fLstTime) 890 // - accepts the MFMT command to set file time (fSetTime) 891 // or non-standard form of the MDTM command (fSetTime, too) 892 // used by VsFtpd for the same purpose (WritingMDTM) 893 // 894 // See "mdtm_write" in https://security.appspot.com/vsftpd/vsftpd_conf.html 895 func (f *Fs) Precision() time.Duration { 896 if (f.fGetTime || f.fLstTime) && f.fSetTime { 897 return time.Second 898 } 899 return fs.ModTimeNotSupported 900 } 901 902 // Put in to the remote path with the modTime given of the given size 903 // 904 // May create the object even if it returns an error - if so 905 // will return the object and the error, otherwise will return 906 // nil and the error 907 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 908 // fs.Debugf(f, "Trying to put file %s", src.Remote()) 909 err := f.mkParentDir(ctx, src.Remote()) 910 if err != nil { 911 return nil, fmt.Errorf("Put mkParentDir failed: %w", err) 912 } 913 o := &Object{ 914 fs: f, 915 remote: src.Remote(), 916 } 917 err = o.Update(ctx, in, src, options...) 918 return o, err 919 } 920 921 // PutStream uploads to the remote path with the modTime given of indeterminate size 922 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 923 return f.Put(ctx, in, src, options...) 924 } 925 926 // getInfo reads the FileInfo for a path 927 func (f *Fs) getInfo(ctx context.Context, remote string) (fi *FileInfo, err error) { 928 // defer fs.Trace(remote, "")("fi=%v, err=%v", &fi, &err) 929 file, err := f.findItem(ctx, remote) 930 if err != nil { 931 return nil, err 932 } else if file != nil { 933 info := &FileInfo{ 934 Name: remote, 935 Size: file.Size, 936 ModTime: file.Time, 937 precise: f.fLstTime, 938 IsDir: file.Type == ftp.EntryTypeFolder, 939 } 940 return info, nil 941 } 942 return nil, fs.ErrorObjectNotFound 943 } 944 945 // mkdir makes the directory and parents using unrooted paths 946 func (f *Fs) mkdir(ctx context.Context, abspath string) error { 947 abspath = path.Clean(abspath) 948 if abspath == "." || abspath == "/" { 949 return nil 950 } 951 fi, err := f.getInfo(ctx, abspath) 952 if err == nil { 953 if fi.IsDir { 954 return nil 955 } 956 return fs.ErrorIsFile 957 } else if err != fs.ErrorObjectNotFound { 958 return fmt.Errorf("mkdir %q failed: %w", abspath, err) 959 } 960 parent := path.Dir(abspath) 961 err = f.mkdir(ctx, parent) 962 if err != nil { 963 return err 964 } 965 c, connErr := f.getFtpConnection(ctx) 966 if connErr != nil { 967 return fmt.Errorf("mkdir: %w", connErr) 968 } 969 err = c.MakeDir(f.dirFromStandardPath(abspath)) 970 f.putFtpConnection(&c, err) 971 if errX := textprotoError(err); errX != nil { 972 switch errX.Code { 973 case ftp.StatusRequestedFileActionOK: // some ftp servers apparently return 250 instead of 257 974 err = nil // see: https://forum.rclone.org/t/rclone-pop-up-an-i-o-error-when-creating-a-folder-in-a-mounted-ftp-drive/44368/ 975 case ftp.StatusFileUnavailable: // dir already exists: see issue #2181 976 err = nil 977 case 521: // dir already exists: error number according to RFC 959: issue #2363 978 err = nil 979 } 980 } 981 return err 982 } 983 984 // mkParentDir makes the parent of remote if necessary and any 985 // directories above that 986 func (f *Fs) mkParentDir(ctx context.Context, remote string) error { 987 parent := path.Dir(remote) 988 return f.mkdir(ctx, path.Join(f.root, parent)) 989 } 990 991 // Mkdir creates the directory if it doesn't exist 992 func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) { 993 // defer fs.Trace(dir, "")("err=%v", &err) 994 root := path.Join(f.root, dir) 995 return f.mkdir(ctx, root) 996 } 997 998 // Rmdir removes the directory (container, bucket) if empty 999 // 1000 // Return an error if it doesn't exist or isn't empty 1001 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 1002 c, err := f.getFtpConnection(ctx) 1003 if err != nil { 1004 return fmt.Errorf("Rmdir: %w", translateErrorFile(err)) 1005 } 1006 err = c.RemoveDir(f.dirFromStandardPath(path.Join(f.root, dir))) 1007 f.putFtpConnection(&c, err) 1008 return translateErrorDir(err) 1009 } 1010 1011 // Move renames a remote file object 1012 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 1013 srcObj, ok := src.(*Object) 1014 if !ok { 1015 fs.Debugf(src, "Can't move - not same remote type") 1016 return nil, fs.ErrorCantMove 1017 } 1018 err := f.mkParentDir(ctx, remote) 1019 if err != nil { 1020 return nil, fmt.Errorf("Move mkParentDir failed: %w", err) 1021 } 1022 c, err := f.getFtpConnection(ctx) 1023 if err != nil { 1024 return nil, fmt.Errorf("Move: %w", err) 1025 } 1026 err = c.Rename( 1027 f.opt.Enc.FromStandardPath(path.Join(srcObj.fs.root, srcObj.remote)), 1028 f.opt.Enc.FromStandardPath(path.Join(f.root, remote)), 1029 ) 1030 f.putFtpConnection(&c, err) 1031 if err != nil { 1032 return nil, fmt.Errorf("Move Rename failed: %w", err) 1033 } 1034 dstObj, err := f.NewObject(ctx, remote) 1035 if err != nil { 1036 return nil, fmt.Errorf("Move NewObject failed: %w", err) 1037 } 1038 return dstObj, nil 1039 } 1040 1041 // DirMove moves src, srcRemote to this remote at dstRemote 1042 // using server-side move operations. 1043 // 1044 // Will only be called if src.Fs().Name() == f.Name() 1045 // 1046 // If it isn't possible then return fs.ErrorCantDirMove 1047 // 1048 // If destination exists then return fs.ErrorDirExists 1049 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 1050 srcFs, ok := src.(*Fs) 1051 if !ok { 1052 fs.Debugf(srcFs, "Can't move directory - not same remote type") 1053 return fs.ErrorCantDirMove 1054 } 1055 srcPath := path.Join(srcFs.root, srcRemote) 1056 dstPath := path.Join(f.root, dstRemote) 1057 1058 // Check if destination exists 1059 fi, err := f.getInfo(ctx, dstPath) 1060 if err == nil { 1061 if fi.IsDir { 1062 return fs.ErrorDirExists 1063 } 1064 return fs.ErrorIsFile 1065 } else if err != fs.ErrorObjectNotFound { 1066 return fmt.Errorf("DirMove getInfo failed: %w", err) 1067 } 1068 1069 // Make sure the parent directory exists 1070 err = f.mkdir(ctx, path.Dir(dstPath)) 1071 if err != nil { 1072 return fmt.Errorf("DirMove mkParentDir dst failed: %w", err) 1073 } 1074 1075 // Do the move 1076 c, err := f.getFtpConnection(ctx) 1077 if err != nil { 1078 return fmt.Errorf("DirMove: %w", err) 1079 } 1080 err = c.Rename( 1081 f.dirFromStandardPath(srcPath), 1082 f.dirFromStandardPath(dstPath), 1083 ) 1084 f.putFtpConnection(&c, err) 1085 if err != nil { 1086 return fmt.Errorf("DirMove Rename(%q,%q) failed: %w", srcPath, dstPath, err) 1087 } 1088 return nil 1089 } 1090 1091 // ------------------------------------------------------------ 1092 1093 // Fs returns the parent Fs 1094 func (o *Object) Fs() fs.Info { 1095 return o.fs 1096 } 1097 1098 // String version of o 1099 func (o *Object) String() string { 1100 if o == nil { 1101 return "<nil>" 1102 } 1103 return o.remote 1104 } 1105 1106 // Remote returns the remote path 1107 func (o *Object) Remote() string { 1108 return o.remote 1109 } 1110 1111 // Hash returns the hash of an object returning a lowercase hex string 1112 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 1113 return "", hash.ErrUnsupported 1114 } 1115 1116 // Size returns the size of an object in bytes 1117 func (o *Object) Size() int64 { 1118 return int64(o.info.Size) 1119 } 1120 1121 // ModTime returns the modification time of the object 1122 func (o *Object) ModTime(ctx context.Context) time.Time { 1123 if !o.info.precise && o.fs.fGetTime { 1124 c, err := o.fs.getFtpConnection(ctx) 1125 if err == nil { 1126 path := path.Join(o.fs.root, o.remote) 1127 path = o.fs.opt.Enc.FromStandardPath(path) 1128 modTime, err := c.GetTime(path) 1129 if err == nil && o.info != nil { 1130 o.info.ModTime = modTime 1131 o.info.precise = true 1132 } 1133 o.fs.putFtpConnection(&c, err) 1134 } 1135 } 1136 return o.info.ModTime 1137 } 1138 1139 // SetModTime sets the modification time of the object 1140 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 1141 if !o.fs.fSetTime { 1142 fs.Debugf(o.fs, "SetModTime is not supported") 1143 return nil 1144 } 1145 c, err := o.fs.getFtpConnection(ctx) 1146 if err != nil { 1147 return err 1148 } 1149 path := path.Join(o.fs.root, o.remote) 1150 path = o.fs.opt.Enc.FromStandardPath(path) 1151 err = c.SetTime(path, modTime.In(time.UTC)) 1152 if err == nil && o.info != nil { 1153 o.info.ModTime = modTime 1154 o.info.precise = true 1155 } 1156 o.fs.putFtpConnection(&c, err) 1157 return err 1158 } 1159 1160 // Storable returns a boolean as to whether this object is storable 1161 func (o *Object) Storable() bool { 1162 return true 1163 } 1164 1165 // ftpReadCloser implements io.ReadCloser for FTP objects. 1166 type ftpReadCloser struct { 1167 rc io.ReadCloser 1168 c *ftp.ServerConn 1169 f *Fs 1170 err error // errors found during read 1171 } 1172 1173 // Read bytes into p 1174 func (f *ftpReadCloser) Read(p []byte) (n int, err error) { 1175 n, err = f.rc.Read(p) 1176 if err != nil && err != io.EOF { 1177 f.err = err // store any errors for Close to examine 1178 } 1179 return 1180 } 1181 1182 // Close the FTP reader and return the connection to the pool 1183 func (f *ftpReadCloser) Close() error { 1184 var err error 1185 errchan := make(chan error, 1) 1186 go func() { 1187 errchan <- f.rc.Close() 1188 }() 1189 // Wait for Close for up to 60 seconds by default 1190 closeTimeout := f.f.opt.CloseTimeout 1191 if closeTimeout == 0 { 1192 closeTimeout = fs.DurationOff 1193 } 1194 timer := time.NewTimer(time.Duration(closeTimeout)) 1195 select { 1196 case err = <-errchan: 1197 timer.Stop() 1198 case <-timer.C: 1199 // if timer fired assume no error but connection dead 1200 fs.Errorf(f.f, "Timeout when waiting for connection Close") 1201 f.f.putFtpConnection(nil, nil) 1202 return nil 1203 } 1204 // if errors while reading or closing, dump the connection 1205 if err != nil || f.err != nil { 1206 _ = f.c.Quit() 1207 f.f.putFtpConnection(nil, nil) 1208 } else { 1209 f.f.putFtpConnection(&f.c, nil) 1210 } 1211 // mask the error if it was caused by a premature close 1212 // NB StatusAboutToSend is to work around a bug in pureftpd 1213 // See: https://github.com/artpar/rclone/issues/3445#issuecomment-521654257 1214 if errX := textprotoError(err); errX != nil { 1215 switch errX.Code { 1216 case ftp.StatusTransfertAborted, ftp.StatusFileUnavailable, ftp.StatusAboutToSend: 1217 err = nil 1218 } 1219 } 1220 return err 1221 } 1222 1223 // Open an object for read 1224 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.ReadCloser, err error) { 1225 // defer fs.Trace(o, "")("rc=%v, err=%v", &rc, &err) 1226 path := path.Join(o.fs.root, o.remote) 1227 var offset, limit int64 = 0, -1 1228 for _, option := range options { 1229 switch x := option.(type) { 1230 case *fs.SeekOption: 1231 offset = x.Offset 1232 case *fs.RangeOption: 1233 offset, limit = x.Decode(o.Size()) 1234 default: 1235 if option.Mandatory() { 1236 fs.Logf(o, "Unsupported mandatory option: %v", option) 1237 } 1238 } 1239 } 1240 1241 var ( 1242 fd *ftp.Response 1243 c *ftp.ServerConn 1244 ) 1245 err = o.fs.pacer.Call(func() (bool, error) { 1246 c, err = o.fs.getFtpConnection(ctx) 1247 if err != nil { 1248 return false, err // getFtpConnection has retries already 1249 } 1250 fd, err = c.RetrFrom(o.fs.opt.Enc.FromStandardPath(path), uint64(offset)) 1251 if err != nil { 1252 o.fs.putFtpConnection(&c, err) 1253 } 1254 return shouldRetry(ctx, err) 1255 }) 1256 if err != nil { 1257 return nil, fmt.Errorf("open: %w", err) 1258 } 1259 1260 rc = &ftpReadCloser{rc: readers.NewLimitedReadCloser(fd, limit), c: c, f: o.fs} 1261 return rc, nil 1262 } 1263 1264 // Update the already existing object 1265 // 1266 // Copy the reader into the object updating modTime and size. 1267 // 1268 // The new object may have been created if an error is returned 1269 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 1270 // defer fs.Trace(o, "src=%v", src)("err=%v", &err) 1271 path := path.Join(o.fs.root, o.remote) 1272 // remove the file if upload failed 1273 remove := func() { 1274 // Give the FTP server a chance to get its internal state in order after the error. 1275 // The error may have been local in which case we closed the connection. The server 1276 // may still be dealing with it for a moment. A sleep isn't ideal but I haven't been 1277 // able to think of a better method to find out if the server has finished - ncw 1278 time.Sleep(1 * time.Second) 1279 removeErr := o.Remove(ctx) 1280 if removeErr != nil { 1281 fs.Debugf(o, "Failed to remove: %v", removeErr) 1282 } else { 1283 fs.Debugf(o, "Removed after failed upload: %v", err) 1284 } 1285 } 1286 c, err := o.fs.getFtpConnection(ctx) 1287 if err != nil { 1288 return fmt.Errorf("Update: %w", err) 1289 } 1290 err = c.Stor(o.fs.opt.Enc.FromStandardPath(path), in) 1291 // Ignore error 250 here - send by some servers 1292 if errX := textprotoError(err); errX != nil { 1293 switch errX.Code { 1294 case ftp.StatusRequestedFileActionOK: 1295 err = nil 1296 } 1297 } 1298 if err != nil { 1299 _ = c.Quit() // toss this connection to avoid sync errors 1300 // recycle connection in advance to let remove() find free token 1301 o.fs.putFtpConnection(nil, err) 1302 remove() 1303 return fmt.Errorf("update stor: %w", err) 1304 } 1305 o.fs.putFtpConnection(&c, nil) 1306 if err = o.SetModTime(ctx, src.ModTime(ctx)); err != nil { 1307 return fmt.Errorf("SetModTime: %w", err) 1308 } 1309 o.info, err = o.fs.getInfo(ctx, path) 1310 if err != nil { 1311 return fmt.Errorf("update getinfo: %w", err) 1312 } 1313 return nil 1314 } 1315 1316 // Remove an object 1317 func (o *Object) Remove(ctx context.Context) (err error) { 1318 // defer fs.Trace(o, "")("err=%v", &err) 1319 path := path.Join(o.fs.root, o.remote) 1320 // Check if it's a directory or a file 1321 info, err := o.fs.getInfo(ctx, path) 1322 if err != nil { 1323 return err 1324 } 1325 if info.IsDir { 1326 err = o.fs.Rmdir(ctx, o.remote) 1327 } else { 1328 c, err := o.fs.getFtpConnection(ctx) 1329 if err != nil { 1330 return fmt.Errorf("Remove: %w", err) 1331 } 1332 err = c.Delete(o.fs.opt.Enc.FromStandardPath(path)) 1333 o.fs.putFtpConnection(&c, err) 1334 } 1335 return err 1336 } 1337 1338 // Check the interfaces are satisfied 1339 var ( 1340 _ fs.Fs = &Fs{} 1341 _ fs.Mover = &Fs{} 1342 _ fs.DirMover = &Fs{} 1343 _ fs.PutStreamer = &Fs{} 1344 _ fs.Shutdowner = &Fs{} 1345 _ fs.Object = &Object{} 1346 )