server

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package server provides a minimal but extensible SMB2/3 server intended for hosting a file server, capturing NetNTLM credentials from coerced authentication, and (later) relaying authentication to a third-party target.

Most behavior is customizable via function-field hooks on ServerConfig, modeled on net/http.Server.

Index

Constants

View Source
const (
	FileNotifyChangeFileName    uint32 = 0x00000001
	FileNotifyChangeDirName     uint32 = 0x00000002
	FileNotifyChangeAttributes  uint32 = 0x00000004
	FileNotifyChangeSize        uint32 = 0x00000008
	FileNotifyChangeLastWrite   uint32 = 0x00000010
	FileNotifyChangeLastAccess  uint32 = 0x00000020
	FileNotifyChangeCreation    uint32 = 0x00000040
	FileNotifyChangeEA          uint32 = 0x00000080
	FileNotifyChangeSecurity    uint32 = 0x00000100
	FileNotifyChangeStreamName  uint32 = 0x00000200
	FileNotifyChangeStreamSize  uint32 = 0x00000400
	FileNotifyChangeStreamWrite uint32 = 0x00000800
)

Change-notify CompletionFilter bits (MS-SMB2 §2.2.35).

View Source
const (
	FileActionAdded           uint32 = 0x00000001
	FileActionRemoved         uint32 = 0x00000002
	FileActionModified        uint32 = 0x00000003
	FileActionRenamedOldName  uint32 = 0x00000004
	FileActionRenamedNewName  uint32 = 0x00000005
	FileActionAddedStream     uint32 = 0x00000006
	FileActionRemovedStream   uint32 = 0x00000007
	FileActionModifiedStream  uint32 = 0x00000008
	FileActionRemovedByDelete uint32 = 0x00000009
)

FileAction values for FILE_NOTIFY_INFORMATION (MS-FSCC §2.7.1).

View Source
const (
	// StatusNotifyCleanup terminates a watch because the handle was closed.
	StatusNotifyCleanup uint32 = 0x0000010B
	// StatusNotifyEnumDir tells the client too many changes occurred to
	// enumerate; it must re-scan the directory itself. Returning it with an
	// empty buffer is always a valid answer to a change-notify.
	StatusNotifyEnumDir uint32 = 0x0000010C
	// StatusInsufficientResources is returned when the async-operation cap is
	// reached.
	StatusInsufficientResources uint32 = 0xC000009A
)

NTSTATUS values specific to change-notify.

View Source
const (
	FileAttributeReadonly  uint32 = 0x00000001
	FileAttributeHidden    uint32 = 0x00000002
	FileAttributeSystem    uint32 = 0x00000004
	FileAttributeDirectory uint32 = 0x00000010
	FileAttributeArchive   uint32 = 0x00000020
	FileAttributeNormal    uint32 = 0x00000080
)

File attribute bits used by the Create/QueryInfo handlers. Only those needed for memvfs and round-trip tests are listed; callers may OR in any bit.

View Source
const (
	FsctlValidateNegotiateInfo uint32 = 0x00140204
	FsctlGetReparsePoint       uint32 = 0x000900a8
	FsctlQueryNetworkInterface uint32 = 0x001401fc
)

IOCTL function codes the server recognizes. The named constants in smb.go are sparse — list the additional ones we handle here.

View Source
const (
	// DefaultIdleTimeout is how long a connection may go without completing a
	// request before it is closed. Generous enough for a real client that is
	// simply idle between operations, short enough to reclaim abandoned and
	// slow-loris connections.
	DefaultIdleTimeout = 5 * time.Minute
	// DefaultWriteTimeout bounds a single reply write.
	DefaultWriteTimeout = 30 * time.Second
	// DefaultMaxConnections caps concurrently served connections.
	DefaultMaxConnections = 512
)

Timeout and capacity defaults. They exist so that a server constructed with a zero ServerConfig is still bounded: an unauthenticated peer must not be able to consume a goroutine, a socket, and read buffers indefinitely just by connecting and going quiet.

View Source
const DefaultDurableTimeout = 60 * time.Second

DefaultDurableTimeout is how long a parked handle is retained when the client does not request a specific timeout (or requests 0). Windows uses 60s for durable handles; matching it keeps client-side expectations sane.

View Source
const MaxDurableTimeout = 10 * time.Minute

MaxDurableTimeout caps what a client may ask for. A handle parked forever is a resource leak an unauthenticated-then-disconnected client could trigger at will, so the request is clamped rather than honored blindly.

Variables

View Source
var ErrServerClosed = fmt.Errorf("server closed")

ErrServerClosed is returned by Serve and ListenAndServe after a call to Shutdown or Close.

Functions

This section is empty.

Types

type Account

type Account struct {
	NTHash []byte
}

Account is a single user record consumed by MapAuthenticator. NTHash is the 16-byte MD4 of the unicode-encoded password (a.k.a. "NT hash" or "NTLMv1 hash").

type AlwaysFailAuthenticator

type AlwaysFailAuthenticator struct{}

AlwaysFailAuthenticator is the default Authenticator: it captures the hash via OnCredentialCaptured (raised by the SessionSetup handler) and always returns smb.StatusLogonFailure. Suitable for honeypot deployments.

func (AlwaysFailAuthenticator) Verify

func (AlwaysFailAuthenticator) Verify(_ *Conn, _ *ntlmssp.Authenticate, _ [8]byte) ([]byte, uint32)

type Authenticator

type Authenticator interface {
	Verify(c *Conn, auth *ntlmssp.Authenticate, serverChallenge [8]byte) (sessionKey []byte, status uint32)
}

Authenticator verifies an NTLMSSP AUTHENTICATE message. Returning a non-nil sessionKey and status==smb.StatusOk authenticates the session (sessionKey is the 16-byte exported session key used to derive signing and encryption keys). Returning a non-zero status fails the authentication; status is sent back to the client (typically smb.StatusLogonFailure).

The default Authenticator is AlwaysFailAuthenticator: it captures the hash but always returns logon-failure. Use MapAuthenticator (or your own implementation) to allow real logons against a stored NT-hash table.

type ChangeNotifier added in v0.12.0

type ChangeNotifier interface {
	WatchChanges(ctx context.Context, h Handle, completionFilter uint32, watchTree bool) ([]FileNotifyChange, error)
}

ChangeNotifier is the optional VFS extension that backs SMB2 CHANGE_NOTIFY. A VFS that does not implement it still works: the server answers change notifications with STATUS_NOT_SUPPORTED, which clients (including Explorer) accept as "this server does not do notifications" and stop asking.

WatchChanges must block until at least one change matching completionFilter occurs beneath h, or ctx is cancelled. Returning (nil, nil) is allowed and is treated as "watch ended without changes". Implementations must honor ctx promptly — it is cancelled when the client cancels the request, when the handle is closed, and when the connection goes away.

type Conn

type Conn struct {
	Server     *Server
	RemoteAddr net.Addr

	// Negotiated parameters (filled by the Negotiate handler; per-session
	// signing/cipher state is held on each Session).
	Dialect            uint16
	SigningRequired    bool   // server-side policy from cfg, echoed in NegotiateRes.SecurityMode
	ClientSecurityMode uint16 // raw SecurityMode from the inbound NegotiateReq (drives Session.SigningRequired)
	SupportsEncryption bool
	ClientWantsEncrypt bool // client offered GlobalCapEncryption in NegotiateReq
	CipherID           uint16
	SigningID          uint16
	PreauthHashID      uint16
	ClientGUID         [16]byte

	// Compression carries the negotiated compression state (filled by the
	// Negotiate handler when both the server config and the client offered
	// SMB2_COMPRESSION_CAPABILITIES). It starts inactive, so a client cannot
	// drive the decompressor before compression has actually been negotiated.
	Compression compress.Codec

	// NegotiatedCapabilities and NegotiatedSecurityMode capture the exact
	// values emitted in NegotiateRes (post-OnNegotiate-hook). They are the
	// authoritative source for FSCTL_VALIDATE_NEGOTIATE_INFO replies — the
	// client uses them to detect a downgrade attack and any divergence from
	// what we actually sent will fail the check.
	NegotiatedCapabilities uint32
	NegotiatedSecurityMode uint16
	// contains filtered or unexported fields
}

Conn is the per-connection state. Public so hooks can read or mutate it.

func (*Conn) RemoveSession

func (c *Conn) RemoveSession(id uint64) *Session

RemoveSession is the public counterpart of removeSession — used by relay hooks (smb/server.OnSessionSetup) that own session lifetime after returning a non-nil *Status. Returns the evicted Session, or nil if not found.

type CreateRequest

type CreateRequest struct {
	Path              Path
	DesiredAccess     uint32
	FileAttributes    uint32
	ShareAccess       uint32
	CreateDisposition uint32
	CreateOptions     uint32
}

CreateRequest mirrors the relevant fields of an SMB2 CREATE request. The server fills it from CreateReq before calling VFS.Create.

type CreateResult

type CreateResult struct {
	Handle       Handle
	CreateAction uint32 // FILE_SUPERSEDED / OPENED / CREATED / OVERWRITTEN.
	Info         FileInfo
}

CreateResult is what VFS.Create returns on success.

type Credential

type Credential struct {
	Username        string
	Domain          string
	Workstation     string
	LM              []byte
	NT              []byte
	ServerChallenge [8]byte
	Hashcat         string
	Format          string // "Net-NTLMv2" or "Net-NTLMv1"
	RemoteAddr      net.Addr
}

Credential represents a captured NTLM authentication attempt. The Hashcat field is pre-formatted as "user::domain:serverChallenge:ntProof:temp", suitable for `hashcat -m 5600`.

func BuildCredential

func BuildCredential(c *Conn, auth *ntlmssp.Authenticate, chal [8]byte) *Credential

BuildCredential assembles a Credential from a parsed NTLMSSP Authenticate message and the server-side challenge that was actually used. Exposed to the relay/ package so it can format captured upstream credentials with the same hashcat string the listener emits.

type DirEntry

type DirEntry struct {
	FileInfo
	ShortName string // optional; filled if VFS supplies 8.3 names.
}

DirEntry is a single entry returned by VFS.QueryDirectory.

type FileInfo

type FileInfo struct {
	Name           string
	Size           int64
	AllocationSize int64
	Attributes     uint32 // FILE_ATTRIBUTE_*; FILE_ATTRIBUTE_DIRECTORY for dirs.
	CreationTime   time.Time
	LastAccessTime time.Time
	LastWriteTime  time.Time
	ChangeTime     time.Time
	FileID         uint64 // optional; some clients consume MS-FSCC FileId.
}

FileInfo describes a single VFS entry. Times are FILETIME (100ns since 1601), matching the SMB2 wire format directly to avoid repeated conversion.

type FileNotifyChange added in v0.12.0

type FileNotifyChange struct {
	// Action is one of the FileAction* constants.
	Action uint32
	// Name is the changed item's path relative to the watched directory,
	// "\"-separated.
	Name string
}

FileNotifyChange is a single change event. The server serializes these into the FILE_NOTIFY_INFORMATION list the client expects.

type Handle

type Handle interface {
	Stat() (FileInfo, error)
	Path() Path
	IsDir() bool
}

Handle is an open VFS object. Implementations should be cheap to copy by pointer; the server tracks them in a per-Tree handle table.

type Logger

type Logger interface {
	Errorf(format string, v ...any)
	Errorln(v ...any)
	Noticef(format string, v ...any)
	Noticeln(v ...any)
	Infof(format string, v ...any)
	Infoln(v ...any)
	Debugf(format string, v ...any)
	Debugln(v ...any)
}

Logger is the small subset of golog used by the server. Override ServerConfig.Logger to integrate with custom logging (e.g. SIEM forwarders).

type MapAuthenticator

type MapAuthenticator struct {
	Domain   string
	Accounts map[string]*Account
}

MapAuthenticator implements Authenticator by computing the expected NTLMv2 response from a stored NT hash and comparing against the inbound proof. It does not handle NTLMv1.

The Domain field is matched case-insensitively. Accounts is keyed by lower-cased username.

func (*MapAuthenticator) Verify

func (m *MapAuthenticator) Verify(c *Conn, auth *ntlmssp.Authenticate, serverChallenge [8]byte) ([]byte, uint32)

Verify implements Authenticator.

type MapPipeOpener

type MapPipeOpener struct {
	Pipes map[string]func(*Session) (PipeBackend, error)
}

MapPipeOpener is a trivial PipeOpener — a name->factory map. Names are matched case-insensitively and have any leading backslashes stripped. Concurrent reads are safe; mutate the map only before installing the opener on a Server.

func (*MapPipeOpener) OpenPipe

func (m *MapPipeOpener) OpenPipe(_ context.Context, sess *Session, name string) (PipeBackend, uint32, error)

OpenPipe implements PipeOpener.

type Path

type Path = string

Path is an SMB-style, "\"-separated, share-relative path. The empty string or "\" denotes the share root.

type PipeBackend

type PipeBackend interface {
	Transceive(ctx context.Context, in []byte) (out []byte, status uint32, err error)
	Write(ctx context.Context, data []byte) (n int, status uint32, err error)
	Read(ctx context.Context, max int) (out []byte, status uint32, err error)
	Close(ctx context.Context) error
}

PipeBackend is the per-open state for a named pipe (e.g. "srvsvc"). Implementations service the three SMB operations a client may invoke on a pipe handle:

  • Transceive: an FSCTL_PIPE_TRANSCEIVE round trip — the typical DCERPC request/response. Most clients use this exclusively.
  • Write / Read: separate WRITE then READ — used by some Linux clients and for fragmented DCERPC PDUs. Implementations that only care about Transceive may return STATUS_NOT_SUPPORTED here.
  • Close: free per-open state.

type PipeOpener

type PipeOpener interface {
	OpenPipe(ctx context.Context, sess *Session, name string) (PipeBackend, uint32, error)
}

PipeOpener routes a pipe-name (e.g. "srvsvc") to a fresh PipeBackend per open. Returning (nil, status, nil) rejects the open with that NTSTATUS. Returning (nil, _, err) aborts the connection.

type Server

type Server struct {
	Config *ServerConfig
	// contains filtered or unexported fields
}

Server hosts an SMB2/3 service. The zero value is not usable; create one with a non-nil Config.

func (*Server) Close

func (s *Server) Close() error

Close immediately tears down all listeners and active connections.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(addr string) error

ListenAndServe listens on the given TCP address (":445" if empty) and serves SMB connections until Shutdown or Close is called.

func (*Server) RegisterAliasedShares

func (s *Server) RegisterAliasedShares(names []string, share Share)

RegisterAliasedShares registers the same Share under multiple names so one VFS instance is exposed under each alias. Each registration gets its own Share value (Name is set per-alias), but VFS / Capabilities / MaximalAccess / EncryptData are shared by reference.

func (*Server) RegisterShare

func (s *Server) RegisterShare(name string, share Share)

RegisterShare adds or replaces a share in Config.Shares. Convenience for callers who don't want to allocate the map themselves.

func (*Server) Serve

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

Serve accepts incoming connections on l and spawns one goroutine per connection. Serve always returns a non-nil error; after Shutdown or Close the returned error is ErrServerClosed.

func (*Server) Shutdown

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

Shutdown stops accepting new connections, then waits for active connections to drain (or until ctx is done).

type ServerConfig

type ServerConfig struct {
	// ServerGUID is advertised in NegotiateRes. A random value is generated
	// if zero.
	ServerGUID [16]byte

	// NetBIOS / DNS identity advertised in the NTLMSSP TargetInfo AVPairs.
	// Defaults to "GO-SMB" / empty if unset.
	NetBIOSName     string
	NetBIOSDomain   string
	DnsComputerName string
	DnsDomainName   string
	NtlmTargetName  string // NTLMSSP TargetName; defaults to NetBIOSName

	// MinDialect / MaxDialect bound the SMB2 dialects the server will accept
	// during Negotiate. Default: smb.DialectSmb_2_1 .. smb.DialectSmb_3_1_1.
	MinDialect uint16
	MaxDialect uint16

	// SigningRequired advertises SecurityModeSigningRequired and rejects
	// unsigned requests once a session is established.
	SigningRequired bool

	// EncryptionSupported advertises GlobalCapEncryption and gates SMB 3.x
	// transport encryption.
	EncryptionSupported bool

	// RequireEncryption forces every authenticated session into the SMB 3.x
	// transport-encryption regime: the server sets SessionFlagEncryptData
	// at SessionSetup time, expects every inbound PDU to arrive inside a
	// TransformHeader, and wraps every outbound reply. Implies
	// EncryptionSupported. Clients without GlobalCapEncryption in their
	// NegotiateReq capability set will fail SessionSetup with
	// STATUS_ACCESS_DENIED.
	RequireEncryption bool

	// Compression advertises SMB2_COMPRESSION_CAPABILITIES on SMB 3.1.1 and
	// enables compression-transform (0xFCSMB) frames in both directions. When
	// enabled, an inbound client offer is answered with the intersection of the
	// client's algorithms and CompressionAlgorithms; when there is no common
	// algorithm the context is omitted (MS-SMB2 §2.2.3.1.3).
	Compression bool
	// CompressionAlgorithms, when non-nil, overrides the supported algorithm set
	// (preference order). Leaving it nil advertises the default
	// (LZ77+Huffman, LZ77, Pattern_V1).
	CompressionAlgorithms []uint16

	// DurableHandles enables SMB2 durable handles (MS-SMB2 §3.3.5.9.6): a
	// handle whose CREATE carried a durable request survives the loss of its
	// connection and can be reclaimed by the same principal on a later one,
	// so a transient network blip does not abort an in-progress transfer.
	// Handles are held open (consuming a VFS handle) for at most
	// MaxDurableHandleTimeout after the connection drops.
	DurableHandles bool
	// DurableHandleTimeout is how long a parked handle is retained when the
	// client does not request a specific timeout. Default:
	// DefaultDurableTimeout.
	DurableHandleTimeout time.Duration
	// MaxDurableHandleTimeout caps what a client may request. Default:
	// MaxDurableTimeout.
	MaxDurableHandleTimeout time.Duration

	// Maximum sizes advertised in NegotiateRes. Defaults: 65536 each.
	MaxReadSize     uint32
	MaxWriteSize    uint32
	MaxTransactSize uint32

	// IdleTimeout bounds how long a connection may sit without sending a
	// complete request before the server closes it. Without it a peer that
	// opens a socket and never speaks — or dribbles a PDU one byte at a time —
	// holds a goroutine and its buffers indefinitely. The deadline is armed
	// before each read and cleared while a request is being handled, so a slow
	// handler is never mistaken for an idle client. Default:
	// DefaultIdleTimeout. Set a negative value to disable.
	IdleTimeout time.Duration

	// WriteTimeout bounds a single reply write. A peer that stops reading
	// would otherwise block the writing goroutine forever once the socket
	// buffers fill. Default: DefaultWriteTimeout. Set a negative value to
	// disable.
	WriteTimeout time.Duration

	// MaxConnections caps concurrently served connections. Accepted
	// connections beyond the cap are closed immediately. Default:
	// DefaultMaxConnections. Set a negative value for no limit.
	MaxConnections int

	// Shares maps the wire-visible share name (case-insensitive) to the
	// share definition. Use (*Server).RegisterShare to populate this without
	// allocating the map yourself. IPC$ is auto-provided as a Pipe share if
	// not explicitly registered.
	Shares map[string]Share

	// PipeOpener routes named-pipe opens (Create on a Pipe-typed share, e.g.
	// IPC$) to per-open backends. When nil, all pipe opens fail with
	// STATUS_OBJECT_NAME_NOT_FOUND. The default IPC$ share auto-provided by
	// lookupShare is a Pipe share, so installing a non-nil PipeOpener is the
	// minimum needed to answer share-enumeration RPCs.
	PipeOpener PipeOpener

	// Authenticator verifies parsed NTLMSSP AUTHENTICATE messages. Default:
	// AlwaysFailAuthenticator (capture mode — every login fails after the
	// hash is captured via OnCredentialCaptured).
	Authenticator Authenticator

	// AllowAnonymous lets clients with empty NT/LM responses obtain a null
	// session (sessionFlags |= SessionFlagIsNull). Default false. Useful for
	// hosting payload shares to clients that don't authenticate.
	AllowAnonymous bool

	// AllowGuest grants a guest session (sessionFlags |= SessionFlagIsGuest)
	// when authentication fails but the user/password were non-empty.
	// Default false.
	AllowGuest bool

	// OnConnect fires after a TCP accept, before any framing has been read.
	// Returning a non-nil error closes the connection without a reply.
	OnConnect func(c *Conn) error
	// OnDisconnect fires after the connection has been torn down (best
	// effort; not invoked if the process is killed).
	OnDisconnect func(c *Conn)

	// OnNegotiate fires after the server has parsed a NegotiateReq and
	// populated a default NegotiateRes. The hook can mutate either side.
	// Returning a non-nil error aborts the connection.
	OnNegotiate func(c *Conn, req *smb.NegotiateReq, res *smb.NegotiateRes) error

	// OnSessionSetup fires for each leg of the SessionSetup exchange after
	// the inbound blob has been peeled out of the SMB2 envelope. The Conn,
	// Session (allocated on the first leg), the SecurityBlob bytes, and the
	// stage are passed in. Returning a non-nil *Status replaces the default
	// SMB-level reply status (e.g. force StatusAccessDenied without
	// processing the NTLMSSP). Status.SecurityBlob, when non-nil, is sent
	// verbatim as the outbound SessionSetupRes.SecurityBlob — relay flows
	// inject the upstream's wrapped NTLMSSP CHALLENGE here on leg 1 and the
	// upstream's accept-completed token on leg 2.
	//
	// Hooks that return a non-nil *Status own the session lifetime: the
	// server does NOT auto-evict the session, so the hook must call
	// (*Conn).RemoveSession explicitly when it wants the session destroyed
	// (e.g. capture-and-drop relay leg 2 — the hook discards the inbound
	// session and keeps a separate upstream Connection alive in its pool).
	//
	// Returning (nil, err) aborts the connection.
	OnSessionSetup func(c *Conn, s *Session, securityBlob []byte, stage SessionSetupStage) (*Status, error)

	// OnCredentialCaptured fires once per AUTHENTICATE message, regardless of
	// the verify outcome. Useful for honeypot logging / SIEM forwarding.
	OnCredentialCaptured func(c *Conn, cred *Credential)

	// OnLogoff fires when the client cleanly tears down a session.
	OnLogoff func(c *Conn, s *Session)

	// OnTreeConnect fires after the server has parsed a TreeConnectReq and
	// looked up the requested share. The hook receives the resolved share
	// name (without the "\\host\" prefix) and the default-populated res
	// struct. Returning a non-nil *Status replaces the default reply status.
	OnTreeConnect func(c *Conn, s *Session, share string, req *smb.TreeConnectReq, res *smb.TreeConnectRes) (*Status, error)

	// OnTreeDisconnect fires after a successful TreeDisconnect, before any
	// open handles are forcibly closed.
	OnTreeDisconnect func(c *Conn, s *Session, t *Tree)

	// OnEcho fires for each inbound SMB2 Echo (keepalive) request before the
	// default StatusOk reply is written. Useful for liveness logging or to
	// short-circuit the connection (returning a non-nil error aborts).
	OnEcho func(c *Conn) error

	// OnRawRequest fires for every inbound PDU (post-NetBIOS-framing,
	// pre-dispatch) and provides a hook for relay/instrumentation. Returning
	// (true, nil) means "I wrote a reply myself, do not dispatch."
	OnRawRequest func(c *Conn, raw []byte) (handled bool, err error)
	// OnRawResponse fires after the default handler builds a reply but before
	// signing/encryption/wire-write. Mutation of the returned slice is allowed.
	OnRawResponse func(c *Conn, raw []byte) ([]byte, error)

	// OnUnknownCommand fires for any SMB2 command not handled by the current
	// build. Default: respond with STATUS_NOT_SUPPORTED.
	OnUnknownCommand func(c *Conn, h *smb.Header, body []byte) (*Status, error)

	// Logger overrides the default golog logger.
	Logger Logger
}

ServerConfig holds the tunable knobs for a Server. Function-field hooks allow callers to observe or override every protocol step. Hooks are called synchronously from the per-connection goroutine.

Hook return convention:

  • return (nil, nil): default handler runs.
  • return (&Status{Code: ntstatus, Body: optional}, nil): server replies with the supplied status (and body, if set), default handler is skipped.
  • return (nil, err): the connection is aborted.

type Session

type Session struct {
	ID    uint64
	Conn  *Conn
	Flags uint16 // SessionFlagIsGuest | SessionFlagIsNull | SessionFlagEncryptData

	// Authentication state. Once setup completes successfully Username,
	// Domain, Workstation are populated and SessionKey holds the 16-byte
	// exported session key used to derive signing / encryption keys.
	Username    string
	Domain      string
	Workstation string
	SessionKey  []byte

	// AuthAcceptor holds the in-flight SPNEGO acceptor across the two
	// SessionSetup legs. Set on first leg, consumed on second.
	AuthAcceptor *spnego.NTLMAcceptor
	NTLMServer   *ntlmssp.Server

	// Authenticated reports whether the session has completed setup with a
	// non-failure status.
	Authenticated bool

	SigningActive bool

	// SigningRequired is the negotiated requirement per MS-SMB2 §3.3.5.5.3:
	// TRUE when the server's RequireMessageSigning is set OR the client's
	// NegotiateReq SecurityMode included SMB2_NEGOTIATE_SIGNING_REQUIRED.
	// Only when this is TRUE does the server sign every outbound PDU and
	// reject unsigned post-auth inbound PDUs. Independent of SigningActive
	// (which just reports that signing keys are derived).
	SigningRequired bool
	// contains filtered or unexported fields
}

Session is the per-(SMB)session state held on a Conn. A single TCP connection may carry multiple SMB sessions (each with its own SessionID), though most clients open just one. Hooks may read or mutate fields below; mutation while the server is dispatching is safe as long as the calling goroutine holds Session.mu (the dispatcher does so for the duration of a hook).

type SessionSetupStage

type SessionSetupStage int

SessionSetupStage identifies which leg of the SessionSetup exchange has arrived. The default acceptor parses the security blob and dispatches by leading byte (0x60 = NegTokenInit -> stage Negotiate; 0xa1 = NegTokenResp -> stage Authenticate).

const (
	SessionSetupStageNegotiate    SessionSetupStage = 1
	SessionSetupStageAuthenticate SessionSetupStage = 3
)

type Share

type Share struct {
	// Name is the wire-visible share name (case-insensitive at lookup).
	Name string
	// Type is one of smb.ShareTypeDisk / ShareTypePipe / ShareTypePrint.
	Type byte
	// Remark is a free-form description (not currently advertised; reserved
	// for future use by share-enumeration RPCs).
	Remark string
	// VFS is the pluggable filesystem for Disk shares. Required for Disk;
	// must be nil for Pipe.
	VFS VFS
	// EncryptData advertises ShareFlagEncryptData on this share. After the
	// TreeConnect reply lands, the server rejects every plaintext PDU
	// against the tree with STATUS_ACCESS_DENIED (MS-SMB2 §3.3.5.2.11).
	// The TreeConnect itself is permitted plaintext — the client uses the
	// flag in the reply to switch to encrypted operation. Requires the
	// server config to have EncryptionSupported (or RequireEncryption) and
	// the negotiated dialect ≥ 3.0; otherwise this flag is meaningless.
	EncryptData bool
	// Capabilities is OR'd into TreeConnectRes.Capabilities. The server will
	// add ShareCap defaults (e.g. nothing) on top.
	Capabilities uint32
	// MaximalAccess is the access mask returned in TreeConnectRes. Clients
	// (notably Windows Explorer) inspect this to gate UI affordances. Default
	// to FILE_ALL_ACCESS / 0x001f01ff if zero.
	MaximalAccess uint32

	// WritableUsers, when non-nil, restricts write access on this share to
	// the lower-cased usernames mapped to true. A nil map means every
	// authenticated (non-guest, non-null) user has write access — the
	// historical default. An empty (non-nil) map disables write for every
	// authenticated user, which combined with the anonymous/guest flags
	// below yields a fully read-only share.
	//
	// Guest and null (anonymous) sessions ignore this map and consult
	// GuestWritable / AnonymousWritable instead.
	WritableUsers map[string]bool

	// AnonymousWritable allows null sessions (SessionFlagIsNull, established
	// via ServerConfig.AllowAnonymous) to write. Default false: anonymous
	// users get read-only access on shares that grant them tree-connect.
	AnonymousWritable bool

	// GuestWritable allows guest sessions (SessionFlagIsGuest, established
	// via ServerConfig.AllowGuest) to write. Default false: guests get
	// read-only access.
	GuestWritable bool
}

Share is one offered SMB share. Disk shares require VFS to be non-nil; for Pipe shares VFS is ignored (named-pipe traffic flows over CreateContexts / IoCtl, handled separately). Print shares are not supported.

func (*Share) UserCanWrite

func (sh *Share) UserCanWrite(sess *Session) bool

UserCanWrite reports whether sess has write access on this share. The rules are:

  • null (anonymous) session: AnonymousWritable
  • guest session: GuestWritable
  • authenticated session, WritableUsers == nil: true (default)
  • authenticated session, WritableUsers != nil: WritableUsers[lower(user)]

type Status

type Status struct {
	Code         uint32
	Body         []byte
	SecurityBlob []byte
}

Status is returned by hooks to short-circuit the default handler with a custom NT status code. Body, when non-nil, replaces the default response body bytes (header is built by the server). When the response struct is already populated and only the status code differs, leave Body nil.

SecurityBlob, when non-nil, is used by SessionSetup hooks (and only those) to inject a SPNEGO-wrapped security blob into the outbound SessionSetupRes. Useful for relay flows where the upstream's CHALLENGE (leg 1) or accept-completed token (leg 2) needs to be forwarded to the inbound client.

type Tree

type Tree struct {
	ID    uint32
	Share Share
	// contains filtered or unexported fields
}

Tree is the per-(SMB)session-per-share state established by TreeConnect. A successful TreeConnect adds a Tree to Session.trees; TreeDisconnect (or session/connection teardown) removes it after closing any open handles.

type VFS

type VFS interface {
	Create(ctx context.Context, sess *Session, req CreateRequest) (CreateResult, uint32, error)
	Close(ctx context.Context, h Handle) error
	Read(ctx context.Context, h Handle, offset int64, buf []byte) (n int, status uint32, err error)
	Write(ctx context.Context, h Handle, offset int64, data []byte) (n int, status uint32, err error)
	Flush(ctx context.Context, h Handle) (uint32, error)
	QueryFileInfo(ctx context.Context, h Handle, infoClass byte) (any, uint32, error)
	SetFileInfo(ctx context.Context, h Handle, infoClass byte, raw []byte) (uint32, error)
	QueryDirectory(ctx context.Context, h Handle, pattern string, restart bool) ([]DirEntry, uint32, error)
	QueryFSInfo(ctx context.Context, infoClass byte) (any, uint32, error)
	QuerySecurity(ctx context.Context, h Handle, addInfo uint32) ([]byte, uint32, error)
	Ioctl(ctx context.Context, h Handle, code uint32, in []byte, maxOut uint32) ([]byte, uint32, error)
}

VFS is the pluggable filesystem behind a Disk share. Methods return an NTSTATUS code as their second-to-last return value; non-zero short-circuits the default response builder. err is reserved for fatal/transport-level failures that should abort the connection.

Optional methods (QuerySecurity, Ioctl) may return (nil, STATUS_NOT_SUPPORTED, nil) to delegate to the server's default behavior.

Directories

Path Synopsis
Package filevfs is a file-backed implementation of the smb/server VFS interface.
Package filevfs is a file-backed implementation of the smb/server VFS interface.
Package memvfs provides a reference in-memory implementation of the smb/server VFS interface.
Package memvfs provides a reference in-memory implementation of the smb/server VFS interface.

Jump to

Keyboard shortcuts

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