ironport

package module
v1.0.18 Latest Latest
Warning

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

Go to latest
Published: May 15, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

README

ironport

Go Report Card codecov Build Status

A production-ready, embeddable SFTP server and FTP server library for Go with a security-first design. The production-ready claim applies to the library API; the command under cmd/ironport-demo is only a runnable demo.

Features

  • SFTP and FTP/FTPS in one binary — a single server hosts both protocols against the same user database, jails, and permission flags. FTPS (explicit TLS via AUTH TLS, RFC 4217) opt-in with FtpTLSConfig; pair with FtpRequireTLS to refuse plaintext logins entirely
  • SSH public-key and password authentication — both methods use constant-time comparisons to prevent username enumeration via timing side-channels
  • Per-user jail (chroot) — each user is confined to a configurable root directory. Every filesystem operation is performed via Linux openat2 with RESOLVE_IN_ROOT | RESOLVE_NO_SYMLINKS, so the kernel itself rejects path traversal and any symlink anywhere in the lookup
  • Fine-grained permissions — independent CanRead / CanWrite flags per user
  • Dynamic user management — add, remove, and update users and their authorized keys at runtime without restarting the server
  • Upload notifications — a buffered CompletedUploads() stream delivers a CompletedUpload struct (protocol, username, full on-disk path, jail-relative path, and client IP) for every successfully closed upload
  • Auth notifications — a buffered AuthEvents() stream delivers LoginSuccess, LoginFailed, and Logout events for SFTP and FTP sessions
  • Temp-file aware completion — optionally set TempExtensions on the config (for example, .tmp, .writing) to suppress completion notifications for temporary upload names and emit the notification when the file is renamed to a non-temp name
  • Graceful shutdownClose() stops the listener immediately and lets in-flight sessions finish on their own. Shutdown(ctx) stops the listener AND waits for in-flight sessions to finish, force-closing any that remain when ctx expires
  • Thread-safe runtime APIs — user-management helpers and listener lifecycle methods are safe to call while the server is running
  • Handshake timeout — connections that do not complete the SSH handshake within 30 seconds are dropped
  • SSH algorithm pinning — optionally constrain SSH key exchange, ciphers, MACs, and public-key auth signature algorithms
  • Idle-session timeout — configurable via IdleTimeout on the config (default 15 minutes); inactive authenticated SFTP sessions are reaped
  • Empty-password protection — users whose stored Password is empty cannot authenticate via password, and empty supplied passwords are always rejected
  • Chown opt-inSetstat/Fsetstat requests that try to change file ownership (uid/gid) are rejected with a permission error unless SftpAllowChown is explicitly set to true on the config. Symlink creation by clients is always refused, and Setstat/Fsetstat requests that try to change access/modification times (Chtimes) are likewise rejected.

Platform support

This package is Linux-only. The path-containment guarantee depends on the openat2 syscall with RESOLVE_IN_ROOT | RESOLVE_NO_SYMLINKS, available since Linux 5.6. ListenAndServe probes for openat2 at startup and returns an error on older kernels rather than silently degrading the policy.

Project policy

  • Security reports: see SECURITY.md.
  • Release notes: see CHANGELOG.md.
  • Compatibility: release versions follow SemVer. While the major version is v1, exported Go APIs are backward compatible across minor and patch releases; breaking API changes require a major version bump.
  • Contributions: see CONTRIBUTING.md.

Quick start

package main

import (
    "log"

    "github.com/define42/ironport"
)

func main() {
    users := map[string]ironport.UserInfo{
        "alice": {Password: "alicepw", Root: "/srv/sftp/alice", CanRead: true, CanWrite: true},
        "bob":   {Password: "bobpw",   Root: "/srv/sftp/bob",   CanRead: true, CanWrite: false},
    }

    // Load a stable host key from disk. If this is left unset, ListenAndServe
    // generates an ephemeral in-memory host key.
    signer, err := ironport.NewSignerFromFile("/etc/ssh/sftp_host_key")
    if err != nil {
        log.Printf("host key unavailable, using ephemeral key: %v", err)
    }

    // FtpAddr is "" by default, disabling the (plaintext) FTP listener.
    config := ironport.DefaultConfig()
    config.SftpAddr = ":2022"
    config.Users = users
    if signer != nil {
        config.SftpSigner = signer
    }
    srv := ironport.NewServer(config)

    // Drain upload notifications in the background.
    go func() {
        for ev := range srv.CompletedUploads() {
            log.Printf("upload complete: protocol=%q user=%q ip=%q path=%q full=%q",
                ev.Protocol, ev.Username, ev.ClientIP, ev.FilePath, ev.FullFilePath)
        }
    }()

    // Drain auth/session notifications in the background.
    go func() {
        for ev := range srv.AuthEvents() {
            log.Printf("auth event: type=%q protocol=%q user=%q ip=%q",
                ev.Type, ev.Protocol, ev.Username, ev.ClientIP)
        }
    }()

    log.Fatal(srv.ListenAndServe())
}
Configuring the upload-notification buffer size

Set CompletedUploadSize on the server config:

config := ironport.DefaultConfig()
config.Users = users
config.SftpSigner = signer
config.CompletedUploadSize = 256
srv := ironport.NewServer(config)

Read upload notifications from the receive-only CompletedUploads() stream. The underlying channel is internal, so callers cannot send to it, close it, or replace it while the server is running. To change the buffer capacity, set a different CompletedUploadSize before calling NewServer.

Configuring the auth-notification buffer size

Set AuthEventSize on the server config:

config := ironport.DefaultConfig()
config.Users = users
config.SftpSigner = signer
config.AuthEventSize = 256
srv := ironport.NewServer(config)

Read authentication and logout notifications from the receive-only AuthEvents() stream. The underlying channel is internal and follows the same non-blocking, caller-drained behavior as CompletedUploads().

Deferring completion notifications until final rename

Many clients upload to a temporary filename first (for example file.txt.tmp) and rename to the final filename only after the upload is fully complete. Configure TempExtensions to emit CompletedUploads() events at that final rename boundary:

config := ironport.DefaultConfig()
config.Users = users
config.SftpSigner = signer
config.TempExtensions = []string{".tmp", ".writing"}
srv := ironport.NewServer(config)

With this setting:

  • uploads that close with a temp extension are not announced yet
  • renaming from a temp extension to a non-temp name emits the completion event
Pinning SSH algorithms

Set SSH algorithm fields on the config before constructing the server to restrict SSH negotiation. Nil fields keep the defaults from golang.org/x/crypto/ssh; non-nil fields are used as allow-lists in the order supplied:

config := ironport.DefaultConfig()
config.Users = users
config.SftpSigner = signer
config.SSHKeyExchanges = []string{ssh.KeyExchangeCurve25519}
config.SSHCiphers = []string{ssh.CipherAES256CTR}
config.SSHMACs = []string{ssh.HMACSHA256}
config.SSHPublicKeyAuthAlgorithms = []string{
    ssh.KeyAlgoED25519,
    ssh.KeyAlgoRSASHA256,
}
srv := ironport.NewServer(config)

For RSA host-key signature pinning, pass a signer already restricted with ssh.NewSignerWithAlgorithms.

FTP support (opt-in)

This package also exposes an FTP listener that shares the SFTP user database, jails, and permission flags. The listener is disabled by default — set FtpAddr to enable it. Without FtpTLSConfig, FTP transmits credentials and data in the clear, so it should only be used on a trusted network segment where you control all clients and intermediate hops. To require encryption, set FtpTLSConfig and FtpRequireTLS = true (see FTPS support below).

config := ironport.DefaultConfig()
config.Users = users
config.SftpSigner = signer
config.FtpAddr = ":2121"
config.FtpPassivePortRange = "5000-5010"
config.FtpDataAcceptTimeout = 30 * time.Second // zero selects this default
config.FtpAllowActiveMode = false              // opt in to PORT/EPRT when needed
srv := ironport.NewServer(config)

When FTP is enabled, passive mode (PASV / EPSV) is supported by default. Active mode (PORT / EPRT) is refused unless FtpAllowActiveMode is true. Even then, the server only dials back to the same IP as the control connection to prevent FTP bounce behavior. Passive data connections are checked against the control connection IP to prevent passive-port stealing. Passive listeners and active dials wait up to FtpDataAcceptTimeout; set it negative to disable that deadline.

FTPS support (RFC 4217, explicit TLS)

Set FtpTLSConfig to a *tls.Config that carries the server certificate to advertise AUTH TLS, PBSZ, and PROT over the FTP listener. Set FtpRequireTLS = true to refuse USER/PASS until the control connection has been upgraded — this is the recommended mode for any FTP listener that faces an untrusted network.

cert, err := tls.LoadX509KeyPair("ftps.crt", "ftps.key")
if err != nil {
    log.Fatal(err)
}
config := ironport.DefaultConfig()
config.Users = users
config.SftpSigner = signer
config.FtpAddr = ":2121"
config.FtpTLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
config.FtpRequireTLS = true
srv := ironport.NewServer(config)

The implementation covers the three FTPS implementation traps that are easy to get wrong:

  • Buffered-bytes injection — after replying 234 to AUTH TLS, the server verifies the receive buffer is empty before starting the TLS handshake. A man-in-the-middle on the plaintext segment cannot pipeline attacker-controlled commands behind the legitimate AUTH TLS line.
  • Data-channel bindingPROT P data connections are required to resume the TLS session from the control channel; the server's data-connection tls.Config enforces DidResume == true via VerifyConnection. A peer that hijacks the data port cannot mount a fresh handshake with its own certificate.
  • Clean close_notify on transfers — every TLS-wrapped data connection is half-closed with CloseWrite before the underlying socket is closed, so the client sees a proper TLS EOF and can distinguish a complete download from a truncated one.

Only explicit FTPS is supported; implicit FTPS (port 990, TLS from byte zero) is intentionally not implemented. The CCC command is refused — once TLS is negotiated, the session stays encrypted.

Public-key authentication

Add one or more public keys to a user's AuthorizedKeys field at construction time, or use the AddUserKey / RemoveUserKey helpers at runtime:

// At construction.
users["alice"] = ironport.UserInfo{
    Root:           "/srv/sftp/alice",
    CanRead:        true,
    CanWrite:       true,
    AuthorizedKeys: []ssh.PublicKey{alicePubKey},
}

// At runtime (safe to call while the server is running).
srv.AddUserKey("alice", newKey)
srv.RemoveUserKey("alice", oldKey)

Dynamic user management

// Add or replace a user.
srv.AddUser("carol", ironport.UserInfo{
    Password: "carolpw",
    Root:     "/srv/sftp/carol",
    CanRead:  true,
    CanWrite: true,
})

// Remove a user (active sessions for that user are not terminated).
srv.RemoveUser("carol")

// Remove all users without deleting any on-disk user data.
srv.RemoveAllUsers()

Graceful shutdown

Shutdown(ctx) stops the listeners so no new connections are accepted, then waits for every in-flight handler to return. If ctx expires first, the remaining tracked connections are force-closed and ctx.Err() is returned. After Shutdown returns, ListenAndServe will have returned nil; the same Server can be started again by calling ListenAndServe.

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

Use Close() when you want the legacy behavior of closing the listener without waiting for sessions to drain. The same Server can be started again after Close returns; existing sessions from the previous run continue until they finish or a later Shutdown(ctx) drains them.

Host key

Use NewSignerFromFile to load a PEM-encoded RSA, ECDSA, or Ed25519 private key:

signer, err := ironport.NewSignerFromFile("/etc/ssh/sftp_host_key")

If config.SftpSigner is nil, ListenAndServe generates an ephemeral in-memory RSA-3072 host key and stores it on the server. This is convenient for demos, but not suitable for production because clients will see a different host key after each process restart.

Running the demo binary

go run ./cmd/ironport-demo -host-key /path/to/host_key

cmd/ironport-demo is intentionally not an operator tool. It hard-codes example users, uses basic logging, has no metrics, config file, readiness/healthcheck endpoint, or service-manager integration, and is meant to show library wiring rather than production deployment.

If -host-key is omitted, ListenAndServe generates a fresh RSA-3072 key on every start. This is not suitable for production, as clients will see a different host key each time. The demo binary also accepts comma-separated -ssh-key-exchanges, -ssh-ciphers, -ssh-macs, and -ssh-public-key-auth-algorithms flags.

License

See LICENSE for details.

Documentation

Overview

Package ironport provides an embeddable, security-hardened SFTP and FTP server.

Core features:

  • Per-user jail roots enforced via path resolution and symlink checks.
  • Password and SSH public-key authentication with constant-time comparisons.
  • Fine-grained CanRead / CanWrite per-user permission flags.
  • Runtime user management (AddUser, RemoveUser, RemoveAllUsers, AddUserKey, RemoveUserKey).
  • Optional SSH algorithm pinning for key exchange, ciphers, MACs, and public-key auth signatures.
  • Graceful shutdown via Close; upload-completion notifications via CompletedUploads; authentication/session notifications via AuthEvents.
  • Optional FTP listener sharing the same users, jails, permissions, temp-extension handling, CompletedUploads stream, and AuthEvents stream as SFTP. FTP uses passive mode by default; active mode can be enabled explicitly.
  • Optional FTPS (RFC 4217 explicit TLS via AUTH TLS) over the FTP listener. Set Config.FtpTLSConfig to enable it and Config.FtpRequireTLS to refuse plaintext logins.

Typical usage:

cfg := ironport.DefaultConfig()
cfg.SftpAddr = ":2022"
cfg.FtpAddr = ":2121"
cfg.Users = users
cfg.SftpSigner = signer
srv := ironport.NewServer(cfg)
log.Fatal(srv.ListenAndServe())

Index

Constants

View Source
const (
	// CompletedUploadProtocolSFTP identifies an upload completed through SFTP.
	CompletedUploadProtocolSFTP = "SFTP"
	// CompletedUploadProtocolFTP identifies an upload completed through FTP.
	CompletedUploadProtocolFTP = "FTP"
)

Variables

This section is empty.

Functions

func NewSignerFromFile

func NewSignerFromFile(path string) (ssh.Signer, error)

NewSignerFromFile reads a PEM-encoded private key from the given file path and returns an ssh.Signer suitable for use as a server host key. It supports any key type accepted by ssh.ParsePrivateKey (RSA, ECDSA, Ed25519).

Types

type AuthEvent added in v0.1.7

type AuthEvent struct {
	// Type is the authentication event kind.
	Type AuthEventType
	// Username is the username supplied by the client for this event.
	Username string
	// ClientIP is the remote IP address of the client, without the port. It is
	// empty if the address could not be parsed.
	ClientIP string
	// Protocol is the file-transfer protocol used for the event.
	// It is either CompletedUploadProtocolSFTP or CompletedUploadProtocolFTP.
	Protocol string
}

AuthEvent describes an authentication or session-lifecycle event. It is the payload delivered on the server's AuthEvents stream.

type AuthEventType added in v0.1.7

type AuthEventType string

AuthEventType identifies the kind of authentication event delivered on the AuthEvents channel (login success, login failure, or logout).

const (
	// AuthEventLoginSuccess identifies a successful user login.
	AuthEventLoginSuccess AuthEventType = "LoginSuccess"
	// AuthEventLoginFailed identifies a rejected user login attempt.
	AuthEventLoginFailed AuthEventType = "LoginFailed"
	// AuthEventLogout identifies the end of an authenticated session.
	AuthEventLogout AuthEventType = "Logout"
)

type CompletedUpload

type CompletedUpload struct {
	// Username is the authenticated SFTP/FTP user that performed the upload.
	Username string
	// FullFilePath is the absolute path of the uploaded file on the server's
	// local filesystem (i.e. resolved through the user's jail root).
	FullFilePath string
	// FilePath is the file path as seen by the client, relative to the
	// user's jail root (e.g. "/incoming/foo.txt").
	FilePath string
	// ClientIP is the remote IP address of the client that performed
	// the upload, without the port. It is empty if the address could not
	// be parsed.
	ClientIP string
	// Protocol is the file-transfer protocol used for the upload.
	// It is either CompletedUploadProtocolSFTP or CompletedUploadProtocolFTP.
	Protocol string
}

CompletedUpload describes a file upload that has finished successfully. It is the payload delivered on the server's CompletedUploads stream.

type Config added in v0.1.17

type Config struct {
	SftpAddr            string
	FtpAddr             string
	FtpPassivePortRange string
	// FtpDataAcceptTimeout bounds how long passive-mode FTP data listeners wait
	// for the client data connection after PASV/EPSV, and how long active-mode
	// FTP dials may take after PORT/EPRT. Zero selects the package default
	// (30 seconds); negative disables the deadline.
	FtpDataAcceptTimeout time.Duration
	// FtpAllowActiveMode enables FTP active mode (PORT/EPRT). It defaults to
	// false because active mode requires outbound connections from the server.
	// When enabled, this server only dials the same IP as the control connection.
	FtpAllowActiveMode bool
	// FtpTLSConfig, when non-nil, enables explicit FTPS (RFC 4217). The
	// AUTH TLS command upgrades the control connection using this config,
	// and PROT P wraps the data connection. A separate config is derived
	// internally for data connections to require session resumption from
	// the control channel; callers should populate Certificates (or a
	// GetCertificate callback) but otherwise leave session-ticket settings
	// at their defaults. Leave nil to disable FTPS.
	FtpTLSConfig *tls.Config
	// FtpRequireTLS, when true, refuses USER/PASS over the FTP control
	// connection until AUTH TLS has succeeded. Requires FtpTLSConfig to be
	// set; otherwise the FTP listener would have no path to authentication
	// and every login attempt would be rejected.
	FtpRequireTLS       bool
	Users               map[string]UserInfo
	SftpSigner          ssh.Signer
	CompletedUploadSize int
	AuthEventSize       int
	// SSHKeyExchanges, SSHCiphers, SSHMACs, and
	// SSHPublicKeyAuthAlgorithms optionally pin SSH negotiation and public-key
	// auth signature algorithms. Nil slices use golang.org/x/crypto/ssh
	// defaults.
	SSHKeyExchanges            []string
	SSHCiphers                 []string
	SSHMACs                    []string
	SSHPublicKeyAuthAlgorithms []string
	// TempExtensions marks file extensions that should defer completion
	// notifications until a later rename to a non-temp name. Matching is
	// case-insensitive.
	TempExtensions []string
	// IdleTimeout bounds authenticated SFTP connection inactivity and FTP
	// control-session inactivity between commands. Zero selects the package
	// default; negative disables the idle timeout.
	IdleTimeout time.Duration
	// TCPKeepAlivePeriod controls the SO_KEEPALIVE idle period applied to
	// accepted SFTP and FTP control connections. Zero selects the package
	// default (30 seconds); a negative value disables keepalive entirely.
	// Probes keep idle control connections alive through stateful
	// firewalls/NAT and surface half-open peers as read errors.
	TCPKeepAlivePeriod time.Duration
	// SftpAllowChown controls whether SFTP clients may change file ownership
	// inside their jail. It defaults to false.
	SftpAllowChown bool
}

Config holds the values used to construct a server.

func DefaultConfig added in v0.1.17

func DefaultConfig() *Config

DefaultConfig returns a fresh server configuration with package defaults applied. Callers should set Users before starting the server. Set SftpSigner to use a stable host key; when SftpSigner is nil, ListenAndServe generates an ephemeral in-memory host key.

type Server added in v0.1.18

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

Server is a self-contained SFTP server with optional FTP support.

func NewServer

func NewServer(config *Config) *Server

NewServer creates a new Server from config. Pass FtpAddr as "" to disable FTP. Leave FtpPassivePortRange empty to use OS-assigned passive data ports.

CompletedUploadSize sets the buffer capacity of the CompletedUploads channel. AuthEventSize sets the buffer capacity of the AuthEvents channel. A non-positive value falls back to the package default (64) for either stream:

cfg := ironport.DefaultConfig()
cfg.Users = users
cfg.SftpSigner = signer
cfg.CompletedUploadSize = 256
cfg.AuthEventSize = 256
srv := ironport.NewServer(cfg)

func (*Server) AddUser added in v0.1.18

func (s *Server) AddUser(username string, info UserInfo)

AddUser adds or replaces a user entry in the server's user map. It is safe to call concurrently with active connections.

func (*Server) AddUserKey added in v0.1.18

func (s *Server) AddUserKey(username string, key ssh.PublicKey)

AddUserKey appends key to the AuthorizedKeys of an existing user. If the key is already present (by wire-format equality) it is not added again. It is a no-op when username does not exist or key is nil. It is safe to call concurrently with active connections.

func (*Server) AuthEvents added in v0.1.18

func (s *Server) AuthEvents() <-chan AuthEvent

AuthEvents returns a receive-only stream of authentication/session notifications. Each AuthEvent includes the protocol that produced it. The channel is buffered; sends are non-blocking so a slow consumer never stalls authentication or logout handling. Callers should drain the stream continuously.

The buffer capacity is set by Config.AuthEventSize. A non-positive value falls back to the package default (64).

func (*Server) Close added in v0.1.18

func (s *Server) Close() error

Close stops both listeners, causing ListenAndServe to return nil. It is safe to call concurrently with active connections; in-flight connections are not terminated, and the server can be started again after Close returns. Calling Close before ListenAndServe has been called, or after it has already returned, is a no-op.

func (*Server) CompletedUploads added in v0.1.18

func (s *Server) CompletedUploads() <-chan CompletedUpload

CompletedUploads returns a receive-only stream of successful upload notifications. Each CompletedUpload includes the protocol that produced it. The channel is buffered; sends are non-blocking so a slow consumer never stalls an upload. Callers should drain the stream continuously.

The buffer capacity is set by Config.CompletedUploadSize. A non-positive value falls back to the package default (64).

func (*Server) FTPListeningAddr added in v0.1.18

func (s *Server) FTPListeningAddr() net.Addr

FTPListeningAddr returns the actual FTP network address the server is listening on, or nil if FTP is disabled or not currently listening.

func (*Server) ListenAndServe added in v0.1.18

func (s *Server) ListenAndServe() error

ListenAndServe starts the SFTP server and, when configured, the FTP server too. It blocks until Close or Shutdown is called or an unexpected listener error occurs. It returns nil when stopped via Close or Shutdown.

A stopped server can be started again by calling ListenAndServe after Close or Shutdown returns. Concurrent ListenAndServe calls are rejected.

func (*Server) ListeningAddr added in v0.1.18

func (s *Server) ListeningAddr() net.Addr

ListeningAddr returns the actual SFTP network address the server is listening on, or nil if the SFTP listener is not currently listening. It is useful when the server was started with port 0 (OS-assigned port).

func (*Server) RemoveAllUsers added in v0.1.18

func (s *Server) RemoveAllUsers()

RemoveAllUsers removes every user entry from the server's user map and force-closes any connections currently authenticated as one of those users. Connections that have not yet completed authentication are left alone so they can either finish authenticating against the (now empty) user map or be rejected by it. No on-disk user data is removed. It is safe to call concurrently with active connections.

func (*Server) RemoveUser added in v0.1.18

func (s *Server) RemoveUser(username string)

RemoveUser removes a user entry from the server's user map and force-closes any connections currently authenticated as that user, so an in-flight session cannot keep operating after its credentials have been revoked. It is safe to call concurrently with active connections.

func (*Server) RemoveUserKey added in v0.1.18

func (s *Server) RemoveUserKey(username string, key ssh.PublicKey)

RemoveUserKey removes key from the AuthorizedKeys of an existing user. It is a no-op when username does not exist, the key is not found, or key is nil. It is safe to call concurrently with active connections.

func (*Server) Shutdown added in v0.1.18

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

Shutdown gracefully stops the server. It closes the listeners so no new connections are accepted, then waits for every in-flight handler goroutine to return.

If ctx is canceled or its deadline passes before all handlers finish, Shutdown forcibly closes every remaining tracked connection (which causes each handler's network I/O to fail and the handler to return), waits for those handlers to exit, and returns ctx.Err(). Passing context.Background() blocks until all handlers exit.

Shutdown is safe to call concurrently with Close and with itself. After Shutdown returns, ListenAndServe (if it was running) will have returned nil, and the server can be started again. Calling Shutdown before ListenAndServe has been started, or after it has already returned and all handlers have exited, returns immediately with nil.

type UserInfo

type UserInfo struct {
	Password       string
	AuthorizedKeys []ssh.PublicKey // public keys allowed for SFTP authentication; nil or empty means public-key auth is disabled for this user
	Root           string          // jail root on disk, e.g. /srv/sftp/alice
	CanRead        bool            // allow read/download/list operations
	CanWrite       bool            // allow write/upload/delete/rename operations
}

UserInfo holds the credentials and jail root for a single SFTP/FTP user.

Directories

Path Synopsis
cmd
ironport-demo command
Command ironport-demo is a runnable library demo for the ironport package.
Command ironport-demo is a runnable library demo for the ironport package.

Jump to

Keyboard shortcuts

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