ftpserverlib

repository·main·Indexed 19 days ago

https://github.com/fclairamb/ftpserverlib

A Golang library for building customizable, fully-featured FTP servers using the afero filesystem abstraction as a backend. It provides interfaces like MainDriver for server lifecycle and authentication, and ClientDriver for filesystem operations. The library supports TLS configuration, various transfer modes (ASCII, Binary, Deflate), and specialized FTP commands such as ALLO, AVBL, SITE SYMLINK, and HASH.

Tokens
9K
Snippets
31
Records
42
Agent score
66%

What's inside ftpserverlib

  1. Enable Deflate Mode (MODE Z) for FTP transfers

    main

    The library supports FTP transfer compression using the MODE Z command (deflate mode) as specified in draft-preston-ftpext-deflate-04. This allows clients to request compressed data transfers to reduce bandwidth usage.

    To use this feature, a client must:

    1. Send the FEAT command to verify that MODE Z is advertised in the response.
    2. Send the MODE Z command to switch the transfer mode to deflate.

    Note: The REST (restart) command is disallowed when using MODE Z because deflate is a streaming compression format and cannot easily resume from a specific byte offset in the compressed stream.

    FEAT -> 211 MODE Z
    MODE Z -> 200 Using deflate mode
  2. Extend ClientDriver with specialized FTP commands

    main

    The base ClientDriver (which implements afero.Fs) can be extended to support specific FTP commands that are not part of standard filesystem operations. If your ClientDriver implements these interfaces, the server will automatically enable the corresponding commands:

    • ALLO (File Allocation): Implement ClientDriverExtensionAllocate to use AllocateSpace(size int) error.
    • AVBL (Available Space): Implement ClientDriverExtensionAvailableSpace to use GetAvailableSpace(dirName string) (int64, error).
    • SITE SYMLINK (Symbolic Links): Implement ClientDriverExtensionSymlink to use Symlink(oldname, newname string) error.
    • HASH (File Hashing): Implement ClientDriverExtensionHasher to use ComputeHash(name string, algo HASHAlgo, startOffset, endOffset int64) (string, error). Note: EnableHASH must be set to true in Settings for this to work.
  3. Configure global logging using slog

    main

    The library uses Go's standard library log/slog for structured logging. To customize the log output (e.g., to use JSON format or change the log level), you should configure the global slog logger before initializing the FTP server. This allows you to control how the library's internal logs are formatted and where they are sent.

    // Example: Configure JSON structured logging globally
    handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelDebug,
    })
    slog.SetDefault(slog.New(handler))
    
    // Then create the FTP server
    server, err := ftpserver.NewFtpServer(driver)
  4. Detect and handle interrupted file transfers

    main

    Because the FTP protocol does not provide a length header for STOR uploads, the server cannot always distinguish between a successful upload and a client that simply stopped sending data.

    To handle this, implement the FileTransferError interface on the object returned by your afero.Fs OpenFile or Create methods. The TransferError(err error) method is called before Close() if an interruption (like ABOR, a dropped connection, or an I/O error) is detected.

    Use this pattern to manage partial files:

    type monitoredFile struct {
        afero.File
        transferErr error
    }
    
    // TransferError is called before Close whenever the transfer did not
    // complete normally (ABOR, broken connection, copy error, ...).
    func (f *monitoredFile) TransferError(err error) {
        f.transferErr = err
    }
    
    func (f *monitoredFile) Close() error {
        err := f.File.Close()
        if f.transferErr != nil {
            // upload was interrupted - run your "discard partial file" logic
        } else {
            // upload completed - run your "publish file" logic
        }
        return err
    }
  5. Handle Passive Mode connection timeouts

    main

    The passiveTransferHandler manages the lifecycle of a passive data connection. When a client initiates a transfer, the server waits for a connection on a listener. The duration the server waits is determined by the ConnectionTimeout setting in the server's Settings object.

    If a connection is not established within this timeout, the Open() method (which calls ConnectionWait) will return an error.

  6. Extend MainDriver behavior with extensions

    main

    The MainDriver can be augmented by implementing several optional extension interfaces to customize specific parts of the connection lifecycle:

    • MainDriverExtensionTLSVerifier: Verify TLS connections during the USER command.
    • MainDriverExtensionPassiveWrapper: Wrap the listener used for passive data connections.
    • MainDriverExtensionUserVerifier: Control user access after the username is known but before authentication.
    • MainDriverExtensionPostAuthMessage: Send a custom message immediately after successful authentication.
    • MainDriverExtensionQuitMessage: Define the message displayed when a user quits.
  7. Manage client state with clientHandler

    main

    The clientHandler (internal to the package but accessible via the ClientDriver interface) manages the lifecycle of an FTP client connection. It tracks the current working directory (Path), the last used list path (ListPath), and TLS requirements.

    Key methods for interacting with a client's state include:

    • Path(): Returns the current working directory.
    • SetPath(value string): Changes the current working directory.
    • ID(): Returns the unique client ID.
    • RemoteAddr() / LocalAddr(): Returns the network addresses.
    • SetExtra(extra any) / Extra(): Allows attaching application-specific data to the client session.
  8. Understand the FtpServer lifecycle and error handling

    main

    The FtpServer manages client connections concurrently. When a connection is accepted via Serve(), the server increments a client counter and spawns a new clientHandler in a separate goroutine to handle the FTP command loop.

    Common Errors:

    • ErrNotListening: Returned when attempting to Stop() a server that hasn't started listening.
    • newNetworkError: Returned when network-level failures occur (e.g., failing to bind to a port).
    • newDriverError: Returned when the provided MainDriver fails to provide necessary configuration like Settings or TLSConfig.
  9. Configure server settings with the Settings struct

    main

    The Settings struct defines the behavior of the FTP server. You can configure networking (address, passive port ranges), security (TLS requirements, connection timeouts), and feature availability (disabling MLSD, MLST, or SITE commands).

    type Settings struct {
    	Listener                 net.Listener     // (Optional) To provide an already initialized listener
    	ListenAddr               string           // Listening address
    	PublicHost               string           // Public IP to expose (only an IP address is accepted at this stage)
    	PublicIPResolver         PublicIPResolver // (Optional) To fetch a public IP lookup
    	PassiveTransferPortRange PasvPortGetter   // (Optional) Port Range for data connections. Random if not specified
    	PassiveTransferPortMultiplexing bool      // Allow different client IPs to share passive listener ports
    	ActiveTransferPortNon20  bool             // Do not impose the port 20 for active data transfer (#88, RFC 1579)
    	IdleTimeout              int              // Maximum inactivity time before disconnecting (#58)
    	ConnectionTimeout        int              // Maximum time to establish passive or active transfer connections
    	DisableMLSD              bool             // Disable MLSD support
    	DisableMLST              bool             // Disable MLST support
    	DisableMFMT              bool             // Disable MFMT support (modify file mtime)
    	Banner                   string           // Banner to use in server status response
    	TLSRequired              TLSRequirement   // defines the TLS mode
    	DisableLISTArgs          bool             // Disable ls like options (-a,-la etc.) for directory listing
    	DisableSite              bool             // Disable SITE command
    	DisableActiveMode        bool             // Disable Active FTP
    	EnableHASH               bool             // Enable support for calculating hash value of files
    	DisableSTAT              bool             // Disable Server STATUS, STAT on files and directories will still work
    	DisableSYST              bool             // Disable SYST
    	EnableCOMB               bool             // Enable COMB support
    	DefaultTransferType      TransferType     // Transfer type to use if the client don't send the TYPE command
    	ActiveConnectionsCheck DataConnectionRequirement
    	PasvConnectionsCheck DataConnectionRequirement
    }
  10. Implement the ClientDriver interface

    main

    The ClientDriver is the interface used to provide the filesystem backend for an authenticated client. It is directly based on afero.Fs. When a user authenticates via MainDriver.AuthUser, you return an implementation of ClientDriver that allows the client to perform standard file operations (upload, download, list, etc.) on your chosen backend.

    type ClientDriver interface {
    	afero.Fs
    }