Help us shape the future of Go! Take the 2026 Go Developer Survey

sftpsrv

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 44 Imported by: 0

Documentation

Overview

Package sftpsrv serves SFTP: filex, seen by every client that already knows how to talk to a file server over SSH.

Why this exists

The rule for this whole family of work is symmetry: filex can CONNECT to an SFTP server (internal/storage/drivers/sftp), so it must be connectable AS one. Somebody with a scanner, a backup job or a WinSCP habit should be able to point it at filex and have it work — with the same identity, the same permissions, the same trash and the same audit trail as the web app.

The fact that shapes the design

`sftp.NewRequestServer` fans READ and WRITE packets out to eight concurrent workers that SHARE one handle, with no per-handle lock in the library. Multiply that by OpenSSH's own defaults (64 outstanding requests, 32 KiB each) and ReadAt/WriteAt are called concurrently, at unordered offsets, for a perfectly sequential `put`. Since OpenSSH 9.0 `scp` speaks SFTP too, so this is every transfer rather than an edge case. The handles in file.go are written for that from the first line; the failure mode of not doing so is "works on a 1 MB test file, corrupts a 2 GB upload".

What it does not own

Identity (internal/protocolauth), post-write bookkeeping (internal/protocolsync), the trash (internal/trash) and the read door (internal/filebody) are shared with every other protocol. This package owns the SSH transport, the SFTP verbs, and the mapping between them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fingerprint

func Fingerprint(key ssh.PublicKey) string

Fingerprint is the SHA256 fingerprint OpenSSH prints, without the `SHA256:` prefix — the form stored in the ssh_keys table.

func ParseAuthorizedKey

func ParseAuthorizedKey(line string) (fingerprint, normalized, comment string, err error)

ParseAuthorizedKey accepts one line in authorized_keys form and returns its fingerprint and normalised wire form.

⚠ It refuses a line with anything before the key type (an authorized_keys OPTIONS field: `command=`, `permitopen=`, `no-pty`). Those options mean something to OpenSSH and nothing here; storing a key with options filex silently ignores would hand somebody a restriction they believe is enforced.

Types

type Config

type Config struct {
	// Enabled — FILEX_SFTP kill switch. OFF by default, unlike /dav and S3:
	// this one opens a TCP port of its own, and a port nobody asked for is not
	// something to switch on for them.
	Enabled bool
	// Addr is the listen address. 2022 by convention (sftpgo and
	// `rclone serve sftp` both use it; 2222 means "SSH in a container").
	Addr string
	// HostKeyDir is where the host keys live.
	//
	// ⚠ It must be a PERSISTENT directory. Regenerating host keys on a
	// container rebuild gives every user REMOTE HOST IDENTIFICATION HAS
	// CHANGED, which is indistinguishable from an attack and which most people
	// resolve by deleting their known_hosts entry — training them to ignore the
	// one warning that matters.
	HostKeyDir string
	// Banner is shown before authentication. Empty sends none.
	Banner string

	Store db.Store
	// Auth is the shared credential door. Required.
	Auth *protocolauth.Resolver
	ACL  *acl.Resolver
	// E2EPolicy is who may encrypt (internal/e2epolicy): a write that CREATES
	// an encrypted folder's key file or a `.fxe` asks it
	// (protoperm.EncryptionAllowed). The router's own, handed over like ACL.
	// nil: New builds one over Store (protoperm.EncryptionPolicy), so a
	// lost wiring line never switches the rule off.
	E2EPolicy *e2epolicy.Service
	// Resolver returns the live driver for a storage id.
	Resolver func(int64) (storage.Driver, error)
	// Body is the one read door — it knows whether the bytes are on the driver
	// or still in the staging area of an upload that has not landed.
	Body *filebody.Resolver
	// Quota is the per-user ceiling, checked before bytes land.
	Quota *quota.Service
	// Index and Thumbs feed the shared post-write bookkeeping.
	Index  *search.Index
	Thumbs *thumb.Pipeline
	// SpoolDir is where uploads are spooled before they are committed. Empty
	// uses the OS temp dir.
	SpoolDir string
	// MaxSpool caps one upload's spool file. 0 uses defaultMaxSpool.
	//
	// ⚠ An uncapped spool on a 40 GB object fills the disk, and the disk it
	// fills is the one the storages are on.
	MaxSpool int64
	// MultiTenant mirrors config.MultiTenant.
	MultiTenant bool
}

Config wires the server to the shared services.

type Server

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

Server is a running SFTP endpoint.

func New

func New(cfg Config) (*Server, error)

New builds a server. It does not listen yet.

func (*Server) Addr

func (s *Server) Addr() string

Addr is the address actually bound, for tests and for logging.

func (*Server) Close

func (s *Server) Close() error

Close stops the listener and waits for the sessions in flight.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe starts accepting connections and blocks until Close.

Jump to

Keyboard shortcuts

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