server

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 10, 2026 License: MIT, Unlicense Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const MaxCommandLength = 4096

MaxCommandLength is the maximum length of a command line.

Variables

View Source
var (
	// LegacyCommands contains deprecated X* command variants from RFC 775.
	// These are legacy aliases that modern FTP clients don't need.
	//
	// Commands: XCWD, XCUP, XPWD, XMKD, XRMD
	//
	// Use case: Disable legacy commands to simplify server implementation
	// and reduce attack surface.
	LegacyCommands = []string{
		"XCWD",
		"XCUP",
		"XPWD",
		"XMKD",
		"XRMD",
	}

	// ActiveModeCommands contains commands for active mode data connections.
	//
	// Commands: PORT, EPRT
	//
	// Use case: Disable these for alternative transports (e.g., QUIC) that only
	// support passive mode, or for security hardening.
	ActiveModeCommands = []string{
		"PORT",
		"EPRT",
	}

	// WriteCommands contains all commands that modify the filesystem.
	//
	// Commands: STOR, APPE, STOU, DELE, RMD, XRMD, MKD, XMKD, RNFR, RNTO
	//
	// Use case: Disable these to create a read-only FTP server for distribution
	// of files without allowing uploads or modifications.
	//
	// Note: For per-user read-only access, use the FSDriver's authenticator
	// function to return readOnly=true for specific users instead.
	WriteCommands = []string{
		"STOR",
		"APPE",
		"STOU",
		"DELE",
		"RMD",
		"XRMD",
		"MKD",
		"XMKD",
		"RNFR",
		"RNTO",
	}

	// SiteCommands contains SITE administrative commands.
	//
	// Commands: SITE
	//
	// Use case: Disable to restrict administrative operations like SITE CHMOD.
	SiteCommands = []string{
		"SITE",
	}
)

Predefined command groups for use with WithDisableCommands. These provide convenient ways to disable categories of FTP commands.

Example usage:

// Disable active mode for QUIC transport
srv, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithListenerFactory(&QuicListenerFactory{...}),
    server.WithDisableCommands(server.ActiveModeCommands...),
)

// Create a read-only server
srv, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithDisableCommands(server.WriteCommands...),
)
View Source
var ErrServerClosed = errors.New("ftp: Server closed")

ErrServerClosed is returned by the Server's Serve, ServeTLS, ListenAndServe, and ListenAndServeTLS methods after a call to Shutdown or Close.

Functions

func ListenAndServe added in v1.2.3

func ListenAndServe(addr string, rootPath string, options ...Option) error

ListenAndServe acts as a high-level helper to start a simple filesystem-based FTP server. It creates an FSDriver rooted at rootPath and starts the server on addr.

Defaults:

  • Anonymous login allowed (read-only)
  • Standard timeouts

Example:

log.Fatal(server.ListenAndServe(":21", "/var/ftp"))

Types

type ClientContext

type ClientContext interface {
	// ChangeDir changes the current working directory.
	// Returns os.ErrNotExist if the directory doesn't exist.
	ChangeDir(path string) error

	// GetWd returns the current working directory.
	GetWd() (string, error)

	// MakeDir creates a new directory.
	// Returns os.ErrExist if the directory already exists.
	MakeDir(path string) error

	// RemoveDir removes a directory and its contents.
	// Returns os.ErrNotExist if the directory doesn't exist.
	RemoveDir(path string) error

	// DeleteFile removes a file.
	// Returns os.ErrNotExist if the file doesn't exist.
	DeleteFile(path string) error

	// Rename moves or renames a file or directory.
	// Returns os.ErrNotExist if the source doesn't exist.
	Rename(fromPath, toPath string) error

	// ListDir returns a list of files in the specified directory.
	// Returns os.ErrNotExist if the directory doesn't exist.
	ListDir(path string) ([]os.FileInfo, error)

	// OpenFile opens a file for reading or writing.
	// The flag parameter uses os.O_* constants (os.O_RDONLY, os.O_WRONLY|os.O_CREATE, etc.).
	// Returns os.ErrNotExist if the file doesn't exist (for reading).
	OpenFile(path string, flag int) (io.ReadWriteCloser, error)

	// GetFileInfo returns file or directory metadata.
	// Returns os.ErrNotExist if the path doesn't exist.
	GetFileInfo(path string) (os.FileInfo, error)

	// GetHash calculates the hash of a file using the specified algorithm.
	// Supported algorithms: "SHA-256", "SHA-512", "SHA-1", "MD5", "CRC32".
	// Returns an error if the algorithm is unsupported or the file doesn't exist.
	GetHash(path string, algo string) (string, error)

	// SetTime sets the modification time of a file.
	// Used by the MFMT command.
	// Returns os.ErrNotExist if the file doesn't exist.
	SetTime(path string, t time.Time) error

	// Chmod changes the mode of the file.
	// Used by the SITE CHMOD command.
	// Returns os.ErrNotExist if the file doesn't exist.
	Chmod(path string, mode os.FileMode) error

	// Close releases any resources associated with this context.
	// Called when the client disconnects.
	Close() error

	// GetSettings returns the session settings for passive mode configuration.
	// May return nil if no special settings are needed.
	GetSettings() *Settings
}

ClientContext is the interface that must be implemented by a driver to handle file system operations for a specific client session.

It isolates the operations to the user's view of the filesystem (e.g., handling chroots). All paths are relative to the user's root directory and use forward slashes.

Error handling:

  • Return os.ErrNotExist when files/directories don't exist
  • Return os.ErrPermission for permission denied errors
  • Return os.ErrExist when files/directories already exist
  • The server will translate these to appropriate FTP response codes

Implementations must be safe for concurrent use by a single session.

type DefaultListenerFactory added in v1.5.0

type DefaultListenerFactory struct{}

DefaultListenerFactory uses net.Listen for TCP connections.

func (*DefaultListenerFactory) Listen added in v1.5.0

func (d *DefaultListenerFactory) Listen(network, address string) (net.Listener, error)

type Driver

type Driver interface {
	// Authenticate validates the user and password.
	// The host parameter contains the value from the HOST command (RFC 7151),
	// which can be used for virtual hosting. It may be empty if not provided.
	// The remoteIP parameter contains the client's IP address for IP-based access control.
	//
	// Returns:
	//   - ClientContext: A session-specific context for file operations
	//   - error: Authentication error (use os.ErrPermission for invalid credentials)
	Authenticate(user, pass, host string, remoteIP net.IP) (ClientContext, error)
}

Driver is the interface that must be implemented by an FTP driver. It is responsible for authenticating users and providing a session-specific ClientContext for file operations.

Implementations should:

  • Validate user credentials (user, pass)
  • Use the host parameter for virtual hosting (optional)
  • Return a ClientContext that isolates the user's file operations
  • Return os.ErrPermission or similar errors for authentication failures

Example implementation:

type MyDriver struct{}

func (d *MyDriver) Authenticate(user, pass, host string, remoteIP net.IP) (ClientContext, error) {
    if !validateCredentials(user, pass) {
        return nil, os.ErrPermission
    }
    return newMyContext(user), nil
}

To implement a custom backend (e.g., S3, Database, Memory), implement this interface.

type FSDriver

type FSDriver struct {
	// contains filtered or unexported fields
}

FSDriver implements Driver using the local filesystem.

Security Model:

  • All file operations are confined to the root path using os.Root
  • Path traversal attacks (../) are prevented by path validation
  • Read-only mode is enforced at the operation level
  • Each user session gets an isolated ClientContext

The driver uses os.Root (Go 1.24+) to safely jail file operations within the root directory. This provides kernel-level protection against directory traversal attacks.

Default behavior (no options):

  • Allows anonymous login ("ftp" or "anonymous" users only)
  • Anonymous users have read-only access
  • All operations are confined to the root path

func NewFSDriver

func NewFSDriver(rootPath string, options ...FSDriverOption) (*FSDriver, error)

NewFSDriver creates a new filesystem driver with the given root path and options. The root path is the directory that will serve as the root for all FTP operations. Returns an error if the root path does not exist or is not a directory.

Default behavior:

  • Allows anonymous login ("ftp" or "anonymous" users)
  • Anonymous users have read-only access (unless WithAnonWrite(true) is used)
  • All operations are confined to the root path

Basic usage:

driver, err := server.NewFSDriver("/tmp/ftp")
if err != nil {
    log.Fatal(err)
}

With custom authentication:

driver, err := server.NewFSDriver("/tmp/ftp",
    server.WithAuthenticator(func(user, pass, host string, remoteIP net.IP) (string, bool, error) {
        if user == "admin" && pass == "secret" {
            return "/tmp/ftp", false, nil // read-write access
        }
        if user == "guest" && pass == "guest" {
            return "/tmp/ftp/public", true, nil // read-only, restricted dir
        }
        return "", false, os.ErrPermission
    }))

With per-user directories:

driver, err := server.NewFSDriver("/home",
    server.WithAuthenticator(func(user, pass, host string, remoteIP net.IP) (string, bool, error) {
        if validateUser(user, pass) {
            userDir := filepath.Join("/home", user)
            return userDir, false, nil
        }
        return "", false, os.ErrPermission
    }))

func (*FSDriver) Authenticate

func (d *FSDriver) Authenticate(user, pass, host string, remoteIP net.IP) (ClientContext, error)

Authenticate returns a new FSContext for the user. It uses the authenticator hook if provided. Otherwise, it enforces strict anonymous-only, read-only access rooted at the root path.

type FSDriverOption

type FSDriverOption func(*FSDriver)

FSDriverOption is a functional option for configuring an FSDriver.

func WithAnonWrite added in v1.2.3

func WithAnonWrite(enable bool) FSDriverOption

WithAnonWrite enables write access for anonymous users. Default is false (read-only). Use this with caution.

func WithAuthenticator

func WithAuthenticator(fn func(user, pass, host string, remoteIP net.IP) (string, bool, error)) FSDriverOption

WithAuthenticator sets a custom authentication function.

The function receives:

  • user: username from USER command
  • pass: password from PASS command
  • host: hostname from HOST command (may be empty)
  • remoteIP: client IP address (net.IP) for IP-based access control

The function should return:

  • rootPath: the root directory for this user (must exist)
  • readOnly: true to restrict user to read-only operations
  • error: authentication error (use os.ErrPermission for invalid credentials)

Example with database lookup:

server.WithAuthenticator(func(user, pass, host string, remoteIP net.IP) (string, bool, error) {
    dbUser, err := db.ValidateUser(user, pass)
    if err != nil {
        return "", false, os.ErrPermission
    }
    return dbUser.HomeDir, dbUser.ReadOnly, nil
})

func WithDisableAnonymous

func WithDisableAnonymous(disable bool) FSDriverOption

WithDisableAnonymous disables anonymous login. When enabled, only users authenticated via a custom Authenticator are allowed.

This option only affects the default authentication behavior. If you provide a custom Authenticator, it has full control over authentication.

Example:

driver, _ := server.NewFSDriver("/tmp/ftp",
    server.WithDisableAnonymous(true),
    server.WithAuthenticator(validateUser),
)

func WithSettings

func WithSettings(settings *Settings) FSDriverOption

WithSettings sets server-specific settings for the driver. These settings configure passive mode behavior and other server features.

Example:

settings := &server.Settings{
    PublicHost:  "ftp.example.com",
    PasvMinPort: 30000,
    PasvMaxPort: 30100,
}
driver, _ := server.NewFSDriver("/tmp/ftp",
    server.WithSettings(settings),
)

type ListenerFactory added in v1.5.0

type ListenerFactory interface {
	Listen(network, address string) (net.Listener, error)
}

ListenerFactory creates listeners for passive mode data connections. This allows custom transport implementations (e.g., QUIC).

type MetricsCollector added in v1.2.0

type MetricsCollector interface {
	// RecordCommand records metrics for an FTP command execution.
	// cmd is the command name (e.g., "RETR", "STOR", "LIST").
	// success indicates whether the command completed successfully.
	// duration is how long the command took to execute.
	RecordCommand(cmd string, success bool, duration time.Duration)

	// RecordTransfer records metrics for a file transfer operation.
	// operation is either "RETR" (download) or "STOR" (upload).
	// bytes is the number of bytes transferred.
	// duration is how long the transfer took.
	RecordTransfer(operation string, bytes int64, duration time.Duration)

	// RecordConnection records metrics for connection attempts.
	// accepted indicates whether the connection was accepted.
	// reason provides context (e.g., "global_limit_reached", "per_ip_limit_reached", "accepted").
	RecordConnection(accepted bool, reason string)

	// RecordAuthentication records metrics for authentication attempts.
	// success indicates whether authentication succeeded.
	// user is the username that attempted to authenticate.
	RecordAuthentication(success bool, user string)
}

MetricsCollector is an optional interface for collecting server metrics. Implementations can send metrics to monitoring systems like Prometheus, StatsD, DataDog, etc.

All methods are called from various points in the server lifecycle and should be non-blocking. If a method takes significant time, it should dispatch the work asynchronously.

The server will check if the collector is nil before calling methods, so implementations don't need to handle nil receivers.

type Option

type Option func(*Server) error

Option is a functional option for configuring an FTP server.

func WithBandwidthLimit added in v1.3.0

func WithBandwidthLimit(global, perUser int64) Option

WithBandwidthLimit sets bandwidth limits for the server. global: maximum total bandwidth across all users (bytes/sec, 0 = unlimited) perUser: maximum bandwidth per user (bytes/sec, 0 = unlimited)

When both limits are set, the most restrictive limit applies.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithBandwidthLimit(10*1024*1024, 1024*1024), // 10 MB/s global, 1 MB/s per user
)

func WithDisableCommands added in v1.5.0

func WithDisableCommands(commands ...string) Option

WithDisableCommands disables specific FTP commands. The server responds with "502 Command not implemented" for disabled commands.

This is useful for:

  • Alternative transports that don't support certain features (e.g., disabling PORT/EPRT for QUIC)
  • Security hardening by disabling commands not deemed secure for your implementation
  • Creating read-only servers (disable STOR, DELE, RMD, etc.)
  • Restricting server capabilities for compliance or policy reasons

Predefined command groups are available for convenience:

  • server.LegacyCommands - Deprecated X* command variants
  • server.ActiveModeCommands - PORT and EPRT
  • server.WriteCommands - All filesystem modification commands
  • server.SiteCommands - SITE administrative commands

Example - Disable active mode for QUIC:

srv, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithListenerFactory(&QuicListenerFactory{...}),
    server.WithDisableCommands(server.ActiveModeCommands...),
)

Example - Create a read-only server:

srv, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithDisableCommands(server.WriteCommands...),
)

Example - Disable legacy commands:

srv, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithDisableCommands(server.LegacyCommands...),
)

func WithDisableMLSD

func WithDisableMLSD(disable bool) Option

WithDisableMLSD disables the MLSD command. This is primarily useful for compatibility testing with legacy clients.

Most users should not need this option. MLSD is a modern, standardized directory listing command (RFC 3659) that provides more reliable parsing than the legacy LIST command.

func WithDriver

func WithDriver(driver Driver) Option

WithDriver sets the backend driver for authentication and file operations. This option is required and can only be set once.

Example:

driver, _ := server.NewFSDriver("/tmp/ftp")
s, _ := server.NewServer(":21", server.WithDriver(driver))

func WithEnableDirMessage added in v1.2.3

func WithEnableDirMessage(enabled bool) Option

WithEnableDirMessage enables directory messages. When enabled, the server will check for a .message file in the directory upon entering it and display its content to the user.

func WithListenerFactory added in v1.5.0

func WithListenerFactory(factory ListenerFactory) Option

WithListenerFactory sets a custom listener factory for passive mode data connections. This enables alternative transports like QUIC.

Example:

srv, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithListenerFactory(&QuicListenerFactory{...}),
)

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom logger for the server. If not specified, slog.Default() is used.

Example with debug logging:

logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithLogger(logger),
)

func WithMaxConnections

func WithMaxConnections(max, maxPerIP int) Option

WithMaxConnections sets the maximum number of simultaneous connections. The first parameter (max) sets the global limit across all clients. The second parameter (maxPerIP) sets the per-IP limit. If either is 0, that limit is disabled.

When a limit is reached, new connections receive a "421 Too many users" response. Both control and data connections count toward these limits.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithMaxConnections(100, 10), // Max 100 total, 10 per IP
)

func WithMaxIdleTime

func WithMaxIdleTime(duration time.Duration) Option

WithMaxIdleTime sets the maximum time a connection can be idle before being closed. If not specified, defaults to 5 minutes.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithMaxIdleTime(10*time.Minute),
)

func WithMetricsCollector added in v1.2.0

func WithMetricsCollector(collector MetricsCollector) Option

WithMetricsCollector sets an optional metrics collector for monitoring. The collector will receive metrics about commands, transfers, connections, and authentication attempts.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithMetricsCollector(myPrometheusCollector),
)

func WithPathRedactor added in v1.2.0

func WithPathRedactor(redactor PathRedactor) Option

WithPathRedactor sets a custom path redaction function for privacy compliance. The function will be called for every path logged, allowing custom redaction logic.

Example - Redact middle components:

server.WithPathRedactor(func(path string) string {
    parts := strings.Split(path, "/")
    if len(parts) > 3 {
        for i := 2; i < len(parts)-1; i++ {
            parts[i] = "*"
        }
    }
    return strings.Join(parts, "/")
})

Example - Redact specific patterns:

server.WithPathRedactor(func(path string) string {
    return regexp.MustCompile(`/users/[^/]+/`).ReplaceAllString(path, "/users/*/")
})

func WithReadTimeout added in v1.2.0

func WithReadTimeout(duration time.Duration) Option

WithReadTimeout sets the deadline for read operations on connections. If 0 (default), no timeout is applied.

This prevents slow-read attacks and helps detect network issues. The timeout is reset after each successful read operation.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithReadTimeout(30*time.Second),
)

func WithRedactIPs added in v1.2.0

func WithRedactIPs(enabled bool) Option

WithRedactIPs enables IP address redaction in logs for privacy compliance. When enabled, the last octet of IPv4 addresses is replaced with "xxx".

Example: "192.168.1.100" becomes "192.168.1.xxx"

This helps comply with GDPR and other privacy regulations while maintaining enough information for network troubleshooting.

func WithServerName added in v1.2.0

func WithServerName(name string) Option

WithServerName sets the system type returned by the SYST command. If not specified, defaults to "UNIX Type: L8".

Common values:

  • "UNIX Type: L8" (default)
  • "Windows_NT"
  • "MACOS"

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithServerName("Windows_NT"),
)

func WithTLS

func WithTLS(config *tls.Config) Option

WithTLS enables TLS (FTPS) with the provided configuration. Supports both Explicit FTPS (AUTH TLS) and Implicit FTPS.

For Explicit FTPS (recommended, port 21):

cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithTLS(&tls.Config{
        Certificates: []tls.Certificate{cert},
        MinVersion:   tls.VersionTLS12,
    }),
)

For Implicit FTPS (legacy, port 990):

tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
ln, _ := tls.Listen("tcp", ":990", tlsConfig)
s.Serve(ln)

func WithTransferLog added in v1.2.3

func WithTransferLog(w io.Writer) Option

WithTransferLog sets a writer for standard FTP transfer logging (xferlog format). This is useful for integrating with log analyzers that expect the standard format.

Example:

logFile, _ := os.OpenFile("/var/log/xferlog", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithTransferLog(logFile),
)

func WithWelcomeMessage added in v1.2.0

func WithWelcomeMessage(message string) Option

WithWelcomeMessage sets a custom welcome banner sent to clients on connection. If not specified, defaults to "220 FTP Server Ready".

The message should be a complete FTP response. If it doesn't start with "220", it will be prepended automatically.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithWelcomeMessage("220 Welcome to My FTP Server"),
)

func WithWriteTimeout added in v1.2.0

func WithWriteTimeout(duration time.Duration) Option

WithWriteTimeout sets the deadline for write operations on connections. If 0 (default), no timeout is applied.

This prevents hanging on slow clients and helps detect network issues. The timeout is reset after each successful write operation.

Example:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithWriteTimeout(30*time.Second),
)

type PathRedactor added in v1.2.0

type PathRedactor func(path string) string

PathRedactor is a function type for custom path redaction in logs. It takes a file path and returns a redacted version for privacy.

Example implementations:

// Redact middle components
func(path string) string {
    parts := strings.Split(path, "/")
    if len(parts) > 3 {
        for i := 2; i < len(parts)-1; i++ {
            parts[i] = "*"
        }
    }
    return strings.Join(parts, "/")
}

// Redact specific patterns
func(path string) string {
    return regexp.MustCompile(`/users/[^/]+/`).ReplaceAllString(path, "/users/*/")
}

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the FTP server.

It handles listening for incoming connections and dispatching them to client sessions. Each connection runs in its own goroutine.

Lifecycle:

  1. Create server with NewServer()
  2. Start with ListenAndServe() or Serve()
  3. Server runs until an error occurs or the listener is closed
  4. For graceful shutdown, close the listener from another goroutine

Basic example:

driver, _ := server.NewFSDriver("/tmp/ftp")
s, err := server.NewServer(":21", server.WithDriver(driver))
if err != nil {
    log.Fatal(err)
}
log.Fatal(s.ListenAndServe())

With graceful shutdown:

ln, _ := net.Listen("tcp", ":21")
go func() {
    <-shutdownChan
    ln.Close() // Stops accepting new connections
}()
s.Serve(ln)

func NewServer

func NewServer(addr string, options ...Option) (*Server, error)

NewServer creates a new FTP server with the given address and options. The address should be in the form ":port" or "host:port". The driver must be provided via the WithDriver option.

Default values:

  • Logger: slog.Default()
  • MaxIdleTime: 5 minutes
  • MaxConnections: 0 (unlimited)
  • TLS: disabled

Basic example:

driver, _ := server.NewFSDriver("/tmp/ftp")
s, err := server.NewServer(":21", server.WithDriver(driver))
if err != nil {
    log.Fatal(err)
}

With TLS (Explicit FTPS):

cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{cert},
    MinVersion:   tls.VersionTLS12,
}
s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithTLS(tlsConfig),
)

With connection limits:

s, _ := server.NewServer(":21",
    server.WithDriver(driver),
    server.WithMaxConnections(100, 10), // Max 100 total, 10 per IP
    server.WithMaxIdleTime(10*time.Minute),
)

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe starts the FTP server on the configured address. It blocks until the server stops or an error occurs.

This is a convenience method that creates a TCP listener and calls Serve(). For more control (e.g., graceful shutdown), use net.Listen() and Serve() directly.

func (*Server) Serve

func (s *Server) Serve(l net.Listener) error

Serve accepts incoming connections on the listener l. It blocks until the listener is closed or an error occurs.

Each connection is handled in a separate goroutine. The server enforces connection limits (if configured) and idle timeouts.

For graceful shutdown, close the listener from another goroutine:

ln, _ := net.Listen("tcp", ":21")
go func() {
    <-ctx.Done()
    ln.Close()
}()
s.Serve(ln)

func (*Server) Shutdown added in v1.2.0

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully stops the server.

It immediately stops accepting new connections by closing the listener, then waits for active connections to finish or until the context is cancelled.

If the context expires before all connections close, remaining connections are forcibly closed. Forcibly closing a connection will also cause any active data transfer for that session to be aborted.

Example with timeout:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := s.Shutdown(ctx); err != nil {
    log.Printf("Shutdown error: %v", err)
}

type Settings

type Settings struct {
	// PublicHost is the hostname or IP address advertised in PASV responses.
	// If set to a hostname, the server will resolve it once and use the first
	// IPv4 address found.
	// If empty, the server uses the control connection's local address.
	// Required when behind NAT or in containerized environments.
	PublicHost string

	// PasvMinPort is the minimum port number for passive data connections.
	// If 0, the OS assigns a random port.
	PasvMinPort int

	// PasvMaxPort is the maximum port number for passive data connections.
	// If 0, the OS assigns a random port.
	// Must be >= PasvMinPort if both are set.
	PasvMaxPort int

	// Umask is the file mode creation mask.
	// Common values: 022 (readable by all, writeable by owner), 077 (private).
	// Default (if 0) depends on implementation (often 0).
	// It is subtracted from the default permissions (0666 for files, 0777 for dirs).
	Umask int
}

Settings defines server configuration for passive mode and other features.

These settings are typically configured once and shared across all sessions, but can be customized per-user if needed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL