Generated from Manticore v1.1.6 • 388 packages. View on pkg.go.dev

server

import "github.com/TheManticoreProject/Manticore/network/smb/smb_v10/server"

Package server implements the server side of the SMB 1.0 (CIFS) protocol.

It is a library, not a tool: it exposes a Server that listens, decodes requests with the message layer in network/smb/smb_v10/message, and answers them, plus a Handler chain a caller can use to observe or intercept requests before the built-in dispatch sees them. The shape mirrors network/llmnr/server, so the two compose: a name-service poisoner can steer a client at this server.

What is implemented

This package is being built up in phases, and it is deliberately explicit about where it currently stops, because a partial SMB server answers enough of the protocol to look functional while refusing everything that matters.

Implemented:

  • Listening on Direct TCP (445) and NetBIOS over TCP (139), via network/smb/common/transport.
  • The per-connection receive loop, request decoding, the handler chain, and response framing with correlated reply headers.
  • Error responses in both encodings: the NTSTATUS form, and the legacy SMBSTATUS class/code form for a client that did not negotiate SMB_FLAGS2_NT_STATUS_ERROR_CODES.
  • SMB_COM_NEGOTIATE, selecting the NT LM 0.12 dialect under extended security.
  • SMB_COM_SESSION_SETUP_ANDX, including verifying the response against a credential, establishing a session, and the guest and anonymous policies.
  • SMB_COM_LOGOFF_ANDX.
  • SMB_COM_ECHO.
  • Message signing in both directions, when the policy calls for it.
  • Tree connect and disconnect against a registered share.
  • File service: open and create, read, write, close, flush, delete, rename, and the directory create, remove and check commands.
  • Directory enumeration and the information levels, over TRANSACTION2: FIND_FIRST2 and FIND_NEXT2 with search handles, the query and set levels for a path and for an open handle, and the volume levels. Requests and responses both fragment across as many messages as they need.
  • Security descriptors and file-system controls, over NT_TRANSACT: QUERY_SECURITY_DESC, SET_SECURITY_DESC and IOCTL. SMB_COM_NT_CANCEL is accepted silently, since nothing here leaves a request outstanding.
  • Named pipes, over TRANSACTION: a pipe is opened on a pipe share like a file, and TRANS_TRANSACT_NMPIPE writes a message to the handle and returns the answer. That write-then-read is the operation MS-RPC travels over, so a PipeHandler is all an RPC service needs to be reachable over SMB1.
  • The volume queries a client actually asks: the TRANSACTION2 volume levels, the pass-through information classes above 0x03E8 that carry the native ones, and the legacy SMB_COM_QUERY_INFORMATION_DISK. A client asks about free space after a listing whether or not anything wanted it, so leaving these unanswered puts an error in every session.

All three transaction families share one reassembly, since they are the same shape at different field widths: totals, a per-message count and a displacement, with the subcommand selected by a setup word, a Function field or a name.

Not yet implemented, and answered with STATUS_NOT_IMPLEMENTED: byte-range locking, seek, the legacy SMB_COM_OPEN_ANDX, and batched AndX chains beyond their first command.

NT_TRANSACT_NOTIFY_CHANGE is deliberately absent rather than pending. It needs two things this package does not have: a FileSystem that can be watched, and a connection whose write path can be used from outside the request that is being served — a notification is answered when the change happens, not when it is asked for. Both are architectural additions, and half of either would be worse than the honest refusal.

NT_TRANSACT_CREATE and NT_TRANSACT_RENAME are also absent by choice: they duplicate SMB_COM_NT_CREATE_ANDX and SMB_COM_RENAME, which are served. The quota subcommands are absent because nothing here tracks a quota, and a number invented for them is a number a client would believe.

Shares

A Share is registered with AddShare and backed by a FileSystem. NewLocalFileSystem serves a directory on the host; NewMemoryFileSystem serves storage that never touches disk, which is what the tests use and what a share meant to look real without being real would use.

A share may be marked ReadOnly, which refuses every modifying command whatever access the client asked for. That is enforced in the handlers rather than left to the backend, so a backend cannot forget it.

Security descriptors

A Share may carry a SecurityProvider, which answers the NT_TRANSACT security subcommands. NewReflectiveSecurityProvider derives a descriptor from the share’s own configuration: a read-only share does not describe write access, because it does not grant any. That is the point of deriving one rather than returning a fixed descriptor — a client uses a descriptor to predict what it will be allowed to do, so one that disagreed with the handlers would make the client wrong. For the same reason it refuses a change instead of accepting one it has nowhere to store.

A share with no provider answers STATUS_NOT_SUPPORTED rather than inventing a descriptor.

Named pipes

A Share of type ShareTypeNamedPipe carries a PipeHandler instead of a FileSystem. A client opens a pipe on it with SMB_COM_NT_CREATE_ANDX and then transacts on the handle: [MS-CIFS] identifies the pipe a transaction acts on by the FID in the request’s setup words, not by the name the request carries, so the handle is what matters and the name is boilerplate.

An answer larger than the client’s buffer is cut to fit and reported with STATUS_BUFFER_OVERFLOW, which is what tells the client to read again. Reporting plain success would leave an RPC client parsing a truncated response as a whole one.

Character encoding

Unicode is a per-message property, not a per-connection one: SMB_FLAGS2_UNICODE is set on each message, and a client may negotiate Unicode and then send a request in OEM. So every name is read and written in the encoding that message declared, never in the connection’s.

The consequences are easy to underestimate. A null-terminated field ends at its first null CHARACTER, so a Unicode name has a two-byte terminator and a single-byte scan ends it after one character. A Unicode field also has to begin on a 2-byte boundary measured from the start of the SMB header, so a padding byte stands before it whenever the fields ahead of it did not leave it aligned — which for the second name of a rename depends on the length of the first. And a name in a response is read by the client as whatever the message declared, so a name written in the other encoding produces a reply of the right shape and the wrong text rather than an error.

Path containment

Every path a client sends passes through resolvePath before any backend sees it, and a backend is entitled to assume the result cannot escape the share. The resolver refuses rather than normalises: a path containing “..” is rejected outright instead of being rewritten, because rewriting turns a traversal attempt into a successful access somewhere unintended.

LocalFileSystem adds a second, independent check, because path validation cannot see a symbolic link inside the share pointing out of it: every resolved host path is compared against the share root again after the host has followed its links.

Authentication

Config.Authenticator resolves a claimed identity to its NT hash, and StaticAccounts builds one from a fixed list. With no Authenticator no logon can succeed, which is the configuration a server whose purpose is harvesting responses wants.

Config.AllowGuest admits an identity the store does not know, reporting SMB_SETUP_GUEST so the client knows it was not authenticated as itself, and Config.AllowAnonymous admits a null session. Neither derives a key, so neither can sign: under a policy that requires signatures they are refused outright rather than granted a session that could not carry a single request.

Signing

Config.SigningPolicy selects whether signatures are unsupported, offered or demanded, and only what the server will honour is advertised. Signing is bootstrapped by the authentication exchange itself: the client signs its AUTHENTICATE with the key it derived, and the server can only check that once it has derived the same key from the response. From then on every request must carry a valid signature at the number the exchange has reached, and every response is signed at the number above.

Credential capture

A CaptureHandler registered on the server harvests the NTLM response from every attempt and renders it in hashcat form, so material a server cannot verify can be cracked offline instead. It composes with the above: a server with no Authenticator refuses every logon and captures every response, while one with an Authenticator serves the identities it knows and captures the rest.

Security posture

The receive loop is the attack surface of a listening service, so it is written to survive arbitrary input: a frame that is not an SMB message, or whose header is well formed but whose body will not decode, is answered or dropped rather than propagated, and a panic in a handler takes down only that connection. FuzzServerFrame in this package exercises that path.

A handle is not always backed by a file — a pipe handle has a handler instead, and a backend may decline to open a directory — so every command that reads or writes through one checks. The client chooses the handle, so an unguarded dereference there is reachable by anyone who can open a pipe.

Interoperability

The unit suite pairs this server with the SMB1 client in this repository, which is fast to work with but shares this implementation’s assumptions: a wire detail both halves get wrong agrees with itself, and every round-trip passes. live_interop_integration_test.go exists for that reason. Behind the “integration” build tag, it drives a third-party client and a third-party RPC client against a server started in-process, and asserts a clean session: a listing by name, a file in both directions, the name-carrying commands, a mandatory-signing session verified in both directions, and an RPC bind completed over a named pipe.

Not covered there: the NT_TRANSACT security-descriptor path, because the tool that would drive it cannot be pointed at a non-privileged port. It is covered by unit tests that parse the descriptor back with an independent parser.

Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/

Index

Constants

Path limits. SMB carries a path in a 16-bit-counted field, but a server has no reason to accept anything near that: these bounds match what a file system will take and keep a malicious path from turning into work.

const (
    // MaxPathLength is the longest share-relative path accepted, in bytes.
    MaxPathLength = 4096

    // MaxPathComponentLength is the longest single element of a path, in bytes.
    // 255 is what every file system this could sit on enforces.
    MaxPathComponentLength = 255
)

Configuration defaults applied by NewServer when a field is left zero.

const (
    // DefaultNativeOS and DefaultNativeLanMan are informational only, but must
    // be non-empty for strict clients to accept the session setup.
    DefaultNativeOS     = "Unix"
    DefaultNativeLanMan = "Manticore"

    // DefaultMaxBufferSize is what Windows offers. [MS-CIFS] 2.2.4.52.2 requires
    // a multiple of 4 and suggests at least 4356.
    DefaultMaxBufferSize uint32 = 16644

    // DefaultMaxMpxCount is the number of outstanding commands advertised. The
    // server answers one request at a time, so this is what it will accept
    // rather than a promise of concurrency.
    DefaultMaxMpxCount uint16 = 50

    // DefaultMaxSessionsPerConnection, DefaultMaxTreesPerConnection and
    // DefaultMaxOpensPerConnection bound what one connection may hold at once.
    // The open limit is the largest because a client legitimately holds many
    // files, and the smallest of the three would be the one that broke first.
    DefaultMaxSessionsPerConnection = 64
    DefaultMaxTreesPerConnection    = 64
    DefaultMaxOpensPerConnection    = 1024
    DefaultMaxSearchesPerConnection = 128
)

Session-setup Action bits, returned in the final session-setup response ([MS-CIFS] 2.2.4.53.2).

const (
    // SMB_SETUP_GUEST reports that the logon was mapped to the guest account
    // rather than to the identity the client claimed. A client is entitled to
    // treat that as a failure, which is why it must be reported rather than
    // silently granted.
    SMB_SETUP_GUEST = 0x0001

    // SMB_SETUP_USE_LANMAN_KEY reports that the client's LM key is in use for
    // signing rather than a derived session key. This server never does that.
    SMB_SETUP_USE_LANMAN_KEY = 0x0002
)

MaxEchoCount bounds the number of responses one SMB_COM_ECHO request can produce. [MS-CIFS] 2.2.4.39 puts no ceiling on EchoCount, so an unbounded implementation lets a client ask for 65535 responses to a single request. The cap keeps that from being an amplification primitive; a client asking for more receives this many.

const MaxEchoCount = 64

Variables

Sentinel errors a FileSystem returns so the server can answer with the right protocol status. A backend may wrap them.

var (
    // ErrNotFound reports that the path does not exist.
    ErrNotFound = fmt.Errorf("no such file or directory")

    // ErrExists reports that the path already exists where it must not.
    ErrExists = fmt.Errorf("file or directory already exists")

    // ErrNotDirectory reports that a path element that had to be a directory is
    // not one, or that a directory operation was asked of a file.
    ErrNotDirectory = fmt.Errorf("not a directory")

    // ErrIsDirectory reports that a file operation was asked of a directory.
    ErrIsDirectory = fmt.Errorf("is a directory")

    // ErrNotEmpty reports that a directory being removed still has entries.
    ErrNotEmpty = fmt.Errorf("directory not empty")

    // ErrAccessDenied reports that the operation is not permitted.
    ErrAccessDenied = fmt.Errorf("access denied")

    // ErrReadOnly reports that the share or the entry refuses modification.
    ErrReadOnly = fmt.Errorf("read-only")
)

func EncodeStatus

func EncodeStatus(status nt_status.NT_STATUS, ntStatusCodes bool) uint32

EncodeStatus renders an NTSTATUS into the 32-bit Status header field in whichever form the client selected. A client that set SMB_FLAGS2_NT_STATUS_ERROR_CODES in its request receives the NTSTATUS unchanged; any other client receives the legacy SMBSTATUS encoding.

Parameters:

  • status: the NTSTATUS the server wants to report
  • ntStatusCodes: whether the client negotiated NT status codes

Returns:

  • The value to place in the response header’s Status field

func StaticAccounts

func StaticAccounts(accounts ...Account) func(domain, username string) ([16]byte, bool)

StaticAccounts returns an Authenticator backed by a fixed list of accounts.

The username is matched case-insensitively, as Windows does; the domain is matched exactly, because it is folded into the client’s response as sent and a different spelling is a different credential.

Parameters:

  • accounts: the credentials to accept

Returns:

  • An Authenticator over those credentials

type Account

Account is one credential the server will authenticate against.

type Account struct {
    // Domain and Username are matched against what a client claims. The domain
    // is compared exactly, because NTLMv2 folds it into the response as sent, so
    // a client claiming a different spelling produced a different response.
    Domain   string
    Username string

    // NTHash is the account's NT hash, which is all that verification needs.
    NTHash [16]byte
}

type AttrMask

AttrMask selects which fields of a FileAttr a SetAttr call applies, so that setting one timestamp does not overwrite the others with zeroes.

type AttrMask struct {
    Size     bool
    ReadOnly bool
    Created  bool
    Accessed bool
    Modified bool
    Changed  bool
}

type CaptureConfig

CaptureConfig configures a CaptureHandler.

type CaptureConfig struct {
    // OnCredential is called for each harvested attempt, on the goroutine serving
    // the connection, so an implementation that blocks holds that client up. Nil
    // logs the attempt instead.
    OnCredential func(Credential)

    // OutputFile appends each attempt's hashcat line to a file, created if
    // absent. Empty disables file output. Lines from both modes land in the same
    // file, each prefixed with a comment naming its mode, since a mixed file
    // cannot be fed to hashcat unsorted.
    OutputFile string

    // UniquePerUser records only the first attempt seen for an identity. A client
    // refused a logon typically retries, so without this one user produces
    // several identical-looking lines.
    UniquePerUser bool

    // Status is the status returned to the client after an attempt is recorded.
    // The zero value means STATUS_LOGON_FAILURE, which is usually what is wanted:
    // a client that believes it mistyped a password often retries with another
    // credential.
    Status nt_status.NT_STATUS
}

type CaptureHandler

CaptureHandler harvests NTLM authentication attempts.

Registered on a Server, it intercepts the second leg of a session setup, records what the client sent, and refuses the logon. Everything else — negotiation, the challenge leg — falls through to the built-in dispatch, so the server still behaves like a server on the wire; a client has no way to tell that the refusal was the point.

It is safe for concurrent use across connections.

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

func NewCaptureHandler

func NewCaptureHandler(config CaptureConfig) (*CaptureHandler, error)

NewCaptureHandler creates a capture handler.

Parameters:

  • config: how to report and record what is captured

Returns:

  • The handler
  • An error if the configuration cannot be honoured

func (*CaptureHandler) Credentials

func (h *CaptureHandler) Credentials() []Credential

Credentials returns a copy of what has been harvested so far.

func (*CaptureHandler) Run

func (h *CaptureHandler) Run(srv *Server, conn *Connection, w ResponseWriter, req *message.Message) bool

Run intercepts the second leg of a session setup, records the attempt and refuses it.

It returns false for everything else, including the challenge leg, so the built-in dispatch answers those normally.

type Config

Config configures a Server.

It holds only the settings the current phase honours; an exported knob that does nothing is worse than one that does not exist yet. Negotiation and authentication settings arrive with the phases that consume them.

type Config struct {
    // ServerName and DomainName are the NetBIOS names advertised during
    // authentication, and DNSComputerName and DNSDomainName their fully
    // qualified forms. They reach the client in the CHALLENGE TargetInfo, where a
    // client folds them into its response, so they are part of what it commits
    // to. Empty names are omitted rather than advertised as empty.
    ServerName      string
    DomainName      string
    DNSComputerName string
    DNSDomainName   string

    // NativeOS and NativeLanMan are the informational strings returned in the
    // session-setup response. They MUST NOT be empty: strict clients reject a
    // session setup that leaves them blank, so NewServer defaults them.
    NativeOS     string
    NativeLanMan string

    // MaxBufferSize is the largest SMB message the server accepts, and
    // MaxMpxCount the number of commands it will have outstanding. NewServer
    // defaults both.
    MaxBufferSize uint32
    MaxMpxCount   uint16

    // ServerGUID is advertised in the negotiate response under extended
    // security. The zero value means generate a random one at NewServer time; a
    // client uses it only to notice that two names resolve to one host, and it
    // is not a secure identifier.
    ServerGUID guid.GUID

    // Authenticator resolves a claimed identity to the account's NT hash, and
    // reports false for an identity it does not know. StaticAccounts builds one
    // from a fixed list.
    //
    // Nil means no logon can succeed: every attempt is refused, which is what a
    // server whose purpose is to harvest responses wants. Holding NT hashes
    // rather than passwords is deliberate — the hash is all verification needs.
    Authenticator func(domain, username string) (ntHash [16]byte, ok bool)

    // AllowGuest admits a logon whose identity the Authenticator does not know,
    // as a guest, reporting SMB_SETUP_GUEST so the client knows it was not
    // authenticated as itself.
    //
    // A guest session derives no key, so it cannot sign. It is therefore refused
    // outright when the signing policy requires signatures, rather than being
    // granted a session that cannot carry a single subsequent request.
    AllowGuest bool

    // AllowAnonymous admits a null session: a logon claiming no identity and
    // carrying no response. Like a guest session it has no key and cannot sign.
    AllowAnonymous bool

    // SigningPolicy selects whether message signing is unsupported, offered or
    // demanded. The zero value is SigningDisabled.
    SigningPolicy SigningPolicy

    // MaxSessionsPerConnection, MaxTreesPerConnection and MaxOpensPerConnection
    // bound what one connection may hold at once, so a client cannot exhaust an
    // identifier space or the backend's handles. Zero applies the default.
    MaxSessionsPerConnection int
    MaxTreesPerConnection    int
    MaxOpensPerConnection    int
    MaxSearchesPerConnection int

    // Timeout bounds each read on a connection, so a client that opens a socket
    // and says nothing does not hold a goroutine forever. Zero means no bound.
    Timeout time.Duration

    // MaxConnections bounds the number of connections served at once. A
    // connection arriving while the server is at the limit is closed
    // immediately. Zero means unbounded.
    MaxConnections int
}

type Connection

Connection is the server-side state of one client connection. It is created by the accept loop and owned by the single goroutine that serves the connection, so its fields need no locking.

It holds only what the current phase populates. The session and tree tables and the signing state arrive with the phases that establish them, rather than being declared here in advance.

type Connection struct {
    // Server is the server this connection was accepted by.
    Server *Server

    // Transport carries SMB messages to and from the client, already framed and
    // with any transport-level handshake complete.
    Transport transport.Transport

    // Remote is the client's address, used for logging and for a handler that
    // wants to record who it is talking to.
    Remote net.Addr

    // Dialect is the dialect string selected during negotiation, empty before it.
    Dialect string

    // Negotiated records that a dialect has been agreed. A second NEGOTIATE on
    // one connection is a protocol violation, and every command other than
    // NEGOTIATE and ECHO requires one to have happened.
    Negotiated bool

    // ClientMaxBufferSize and ClientCapabilities are what the client advertised
    // in its session setup, bounding what the server may send back.
    ClientMaxBufferSize uint32
    ClientCapabilities  capabilities.Capabilities

    // UseUnicode and UseNTStatus record what the client negotiated, so a handler
    // does not have to re-derive them from each request header.
    UseUnicode  bool
    UseNTStatus bool

    // ExtendedSecurity records that the client negotiated extended security, and
    // so expects a GSS security blob rather than a challenge in the clear.
    ExtendedSecurity bool

    // SigningActive reports that every request must carry a valid signature and
    // every response must be signed. SigningKey is the MAC key, and
    // ExpectedRequestSequenceNumber the number the next request must be signed
    // at.
    SigningActive                 bool
    SigningKey                    []byte
    ExpectedRequestSequenceNumber uint32
    // contains filtered or unexported fields
}

func (*Connection) Close

func (c *Connection) Close() error

Close closes the connection’s transport. It is safe to call more than once.

func (*Connection) Open

func (c *Connection) Open(fid uint16) *Open

Open returns the handle a FID names on this connection, or nil when it names none.

func (*Connection) PendingAuth

func (c *Connection) PendingAuth(uid uint16) *spnego.AcceptContext

PendingAuth returns the authentication exchange a UID names while it is still in progress, or nil when the UID names no such exchange. A handler uses it to tell the second leg of a session setup from the first.

func (*Connection) Search

func (c *Connection) Search(sid uint16) *Search

Search returns the enumeration a SID names, or nil when it names none.

func (*Connection) Session

func (c *Connection) Session(uid uint16) *Session

Session returns the session a UID names, or nil when it names none.

func (*Connection) Sessions

func (c *Connection) Sessions() []*Session

Sessions returns the sessions established on the connection.

func (*Connection) Tree

func (c *Connection) Tree(tid uint16) *Tree

Tree returns the tree a TID names on this connection, or nil when it names none.

type Credential

Credential is one authentication attempt harvested from a client.

Everything in it is what the client asserted, verified against nothing. That is the point: the response is worth keeping precisely when the server cannot confirm it, because it can be cracked offline instead.

type Credential struct {
    // RemoteAddr is the client the attempt came from, and Time when it arrived.
    RemoteAddr net.Addr
    Time       time.Time

    // Domain, Username and Workstation are the identity the client claimed.
    Domain      string
    Username    string
    Workstation string

    // ServerChallenge is the challenge this server issued, which the response
    // answers. Cracking needs it, so a response recorded without it is useless.
    ServerChallenge [8]byte

    // LmResponse and NtResponse are the responses as received. The length of
    // NtResponse is what distinguishes NetNTLMv1 from NetNTLMv2: 24 bytes is v1,
    // longer is v2.
    LmResponse []byte
    NtResponse []byte
}

func (Credential) Account

func (c Credential) Account() string

Account renders the claimed identity as DOMAIN\user, or just the username when no domain was claimed.

func (Credential) Hashcat

func (c Credential) Hashcat() (string, int, error)

Hashcat renders the attempt in the form hashcat expects, together with the mode number that form belongs to: mode 5600 for NetNTLMv2, mode 5500 for NetNTLMv1.

The two modes use different field layouts, so the mode is returned alongside the line rather than left for a caller to guess.

Returns:

  • The hashcat line
  • The hashcat mode the line is in
  • An error if the response cannot be rendered

func (Credential) IsNTLMv2

func (c Credential) IsNTLMv2() bool

IsNTLMv2 reports whether the attempt carried a NetNTLMv2 response.

type DirEntry

DirEntry is one entry of a directory listing.

type DirEntry struct {
    Attr FileAttr
}

type ErrorClass

ErrorClass is the SMB error class carried in the low byte of the Status field when the client has not negotiated SMB_FLAGS2_NT_STATUS_ERROR_CODES.

Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/8f11e0f3-d545-46cc-97e6-f00569e3e1bc

type ErrorClass uint8
const (
    // ERRSUCCESS indicates the command completed with no error.
    ERRSUCCESS ErrorClass = 0x00
    // ERRDOS is the OS/2 (MS-DOS) error class.
    ERRDOS ErrorClass = 0x01
    // ERRSRV is the server error class, used for errors in the SMB protocol
    // itself rather than in the underlying file system operation.
    ERRSRV ErrorClass = 0x02
    // ERRHRD is the hardware error class.
    ERRHRD ErrorClass = 0x03
    // ERRCMD indicates the server received a message that was not in SMB
    // format. No error codes are defined for use with this class.
    ERRCMD ErrorClass = 0xFF
)

func (ErrorClass) String

func (c ErrorClass) String() string

String renders the error class by its [MS-CIFS] name.

type File

File is an open file on a FileSystem.

Offsets are explicit on every call: SMB carries the offset in the request, so a backend needs no cursor of its own and two opens of the same file cannot interfere through a shared position.

type File interface {
    // ReadAt reads into p starting at off. It returns io.EOF only when nothing
    // could be read; a short read at the end of the file is not an error, because
    // SMB reports a short read rather than a failure.
    ReadAt(p []byte, off int64) (int, error)

    // WriteAt writes p at off, extending the file if needed.
    WriteAt(p []byte, off int64) (int, error)

    // Truncate sets the file's length.
    Truncate(size int64) error

    // Sync commits any buffered contents.
    Sync() error

    // Stat describes the open file.
    Stat() (FileAttr, error)

    // Close releases the handle.
    Close() error
}

type FileAttr

FileAttr describes a file or directory.

type FileAttr struct {
    // Name is the entry's own name, without any path.
    Name string

    // IsDir reports whether the entry is a directory.
    IsDir bool

    // Size is the length in bytes, and AllocationSize the space reserved for it.
    // A backend that does not distinguish the two reports the same value twice.
    Size           int64
    AllocationSize int64

    // ReadOnly reports that the entry cannot be modified.
    ReadOnly bool

    // Created, Accessed, Modified and Changed are the four timestamps SMB
    // carries. A backend without a distinct value for one repeats another.
    Created  time.Time
    Accessed time.Time
    Modified time.Time
    Changed  time.Time
}

type FileSystem

FileSystem is the storage behind a disk share.

Every path reaching a FileSystem has already been resolved by the server: forward-slash separated, relative to the share root, with no empty, “.” or “..” element, and never absolute. A backend does not have to defend against traversal, and MUST NOT try to interpret a path as anything other than that form — the containment guarantee lives in one place so it can be reviewed in one place.

A backend reports failure with the sentinel errors below where they apply, so the server can map an outcome to the right protocol status.

type FileSystem interface {
    // Open opens or creates a file according to flags.
    Open(path string, flags OpenFlags) (File, error)

    // Stat describes a path without opening it.
    Stat(path string) (FileAttr, error)

    // SetAttr applies the fields of attr that mask selects.
    SetAttr(path string, attr FileAttr, mask AttrMask) error

    // Remove deletes a file. It refuses a directory, which Rmdir handles.
    Remove(path string) error

    // Rename moves a file or directory. When replace is false it fails if the
    // destination exists.
    Rename(oldPath, newPath string, replace bool) error

    // Mkdir creates a directory. Its parent must exist.
    Mkdir(path string) error

    // Rmdir removes an empty directory.
    Rmdir(path string) error

    // ReadDir lists the entries of a directory whose names match pattern, which
    // may contain the SMB wildcards "*" and "?". An empty pattern matches
    // everything.
    ReadDir(path, pattern string) ([]DirEntry, error)

    // VolumeInfo describes the storage.
    VolumeInfo() (VolumeInfo, error)
}

type Handler

Handler observes or answers an inbound request before the built-in command dispatch sees it.

Run reports whether it handled the request: true stops the chain and the request is not dispatched, so a handler that returns true is responsible for having written a response. false passes the request to the next handler and ultimately to the dispatch table, which makes a handler that only observes (logging, capture, packet description) a one-liner.

Handlers are shared across connections and run on the goroutine that owns the connection, so an implementation that keeps state must guard it.

type Handler interface {
    Run(srv *Server, conn *Connection, w ResponseWriter, req *message.Message) bool
}

type HandlerFunc

HandlerFunc adapts a function to the Handler interface.

type HandlerFunc func(srv *Server, conn *Connection, w ResponseWriter, req *message.Message) bool

func (HandlerFunc) Run

func (f HandlerFunc) Run(srv *Server, conn *Connection, w ResponseWriter, req *message.Message) bool

Run calls f.

type LocalFileSystem

LocalFileSystem is a FileSystem backed by a directory on the host.

The share is rooted at that directory and nothing above it is reachable. Two separate mechanisms keep that true, because either alone is insufficient: resolvePath refuses a path that could climb out, and every resolved path is checked against the root again after the host has followed any symbolic links in it. The first stops a traversal spelled in the request; the second stops one planted in the file system itself, which no amount of path checking can see.

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

func NewLocalFileSystem

func NewLocalFileSystem(root, label string) (*LocalFileSystem, error)

NewLocalFileSystem roots a file system at a directory on the host.

The directory is resolved through any symbolic links at construction, so the containment checks compare like with like afterwards.

Parameters:

  • root: the directory to serve
  • label: the volume label reported to a client

Returns:

  • The file system
  • An error if the directory is unusable

func (*LocalFileSystem) Mkdir

func (fs *LocalFileSystem) Mkdir(path string) error

Mkdir creates a directory.

func (*LocalFileSystem) Open

func (fs *LocalFileSystem) Open(path string, flags OpenFlags) (File, error)

Open opens or creates a file.

func (*LocalFileSystem) ReadDir

func (fs *LocalFileSystem) ReadDir(path, pattern string) ([]DirEntry, error)

ReadDir lists the entries of a directory that match pattern.

func (*LocalFileSystem) Remove

func (fs *LocalFileSystem) Remove(path string) error

Remove deletes a file.

The name is resolved without following a link in its final element, so deleting a symbolic link removes the link rather than what it points at.

func (*LocalFileSystem) Rename

func (fs *LocalFileSystem) Rename(oldPath, newPath string, replace bool) error

Rename moves a file or directory.

Neither name has a link in its final element followed: renaming a symbolic link moves the link, and renaming onto one replaces the link rather than writing through it.

func (*LocalFileSystem) Rmdir

func (fs *LocalFileSystem) Rmdir(path string) error

Rmdir removes an empty directory.

func (*LocalFileSystem) Root

func (fs *LocalFileSystem) Root() string

Root returns the directory the share is rooted at.

func (*LocalFileSystem) SetAttr

func (fs *LocalFileSystem) SetAttr(path string, attr FileAttr, mask AttrMask) error

SetAttr applies the selected fields.

func (*LocalFileSystem) SetReadOnly

func (fs *LocalFileSystem) SetReadOnly(readOnly bool)

SetReadOnly refuses every modifying operation at the backend.

func (*LocalFileSystem) Stat

func (fs *LocalFileSystem) Stat(path string) (FileAttr, error)

Stat describes a path.

func (*LocalFileSystem) VolumeInfo

func (fs *LocalFileSystem) VolumeInfo() (VolumeInfo, error)

VolumeInfo describes the storage.

type MemoryFileSystem

MemoryFileSystem is a FileSystem held entirely in memory.

It exists for two reasons. Tests get a backend with no disk and no cleanup, so a file-service test is about the protocol rather than about a temporary directory. And a caller can serve a share that touches no storage at all — useful when the point is to look like a file server rather than to be one.

It is safe for concurrent use.

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

func NewMemoryFileSystem

func NewMemoryFileSystem(label string) *MemoryFileSystem

NewMemoryFileSystem creates an empty in-memory file system.

Parameters:

  • label: the volume label reported to a client

Returns:

  • The file system, containing only its root

func (*MemoryFileSystem) AddDirectory

func (fs *MemoryFileSystem) AddDirectory(path string) error

AddDirectory seeds a directory, creating the directories above it.

func (*MemoryFileSystem) AddFile

func (fs *MemoryFileSystem) AddFile(path string, contents []byte) error

AddFile seeds a file, creating the directories above it. It is for a caller setting up a share before serving it, and for tests.

Parameters:

  • path: the share-relative path, slash separated
  • contents: the file’s contents

Returns:

  • An error if the path is unusable

func (*MemoryFileSystem) Mkdir

func (fs *MemoryFileSystem) Mkdir(path string) error

Mkdir creates a directory.

func (*MemoryFileSystem) Open

func (fs *MemoryFileSystem) Open(path string, flags OpenFlags) (File, error)

Open opens or creates a file.

func (*MemoryFileSystem) ReadDir

func (fs *MemoryFileSystem) ReadDir(path, pattern string) ([]DirEntry, error)

ReadDir lists the immediate children of a directory that match pattern.

func (*MemoryFileSystem) Remove

func (fs *MemoryFileSystem) Remove(path string) error

Remove deletes a file.

func (*MemoryFileSystem) Rename

func (fs *MemoryFileSystem) Rename(oldPath, newPath string, replace bool) error

Rename moves a file or directory, and everything beneath a directory with it.

func (*MemoryFileSystem) Rmdir

func (fs *MemoryFileSystem) Rmdir(path string) error

Rmdir removes an empty directory.

func (*MemoryFileSystem) SetAttr

func (fs *MemoryFileSystem) SetAttr(path string, attr FileAttr, mask AttrMask) error

SetAttr applies the selected fields.

func (*MemoryFileSystem) Stat

func (fs *MemoryFileSystem) Stat(path string) (FileAttr, error)

Stat describes a path.

func (*MemoryFileSystem) VolumeInfo

func (fs *MemoryFileSystem) VolumeInfo() (VolumeInfo, error)

VolumeInfo describes the storage.

type Open

Open is a file handle, established by a create or open and named by a FID.

type Open struct {
    // FID is the identifier the client sends to act on this handle.
    FID uint16

    // Tree is the tree the handle was opened on, and Path the share-relative path
    // it names.
    Tree *Tree
    Path string

    // File is the backend handle. It is nil for a handle onto a directory that
    // the backend declined to open, which is legitimate: a directory handle is
    // only ever used to query.
    File File

    // IsDirectory records what the handle names.
    IsDirectory bool

    // IsPipe records that the handle names a named pipe rather than a file, so
    // Path is a pipe name and there is no backend file behind it. A pipe handle
    // is what a transaction acts on: [MS-CIFS] section 3.3.5.57.7 identifies the
    // pipe by the FID in the request's setup words, not by the name it carries.
    IsPipe bool

    // Readable and Writable are the access the open was granted, enforced on
    // every use so a handle opened for reading cannot later be written through.
    Readable bool
    Writable bool

    // DeleteOnClose removes the file when the handle closes, which is how a
    // client deletes something it holds open.
    DeleteOnClose bool

    // Created is when the handle was opened.
    Created time.Time
}

type OpenFlags

OpenFlags describe what an open is for. They are the subset of the client’s requested access and options that a backend needs in order to open the file: share modes and the finer access bits are enforced above the backend.

type OpenFlags struct {
    // Read and Write are the access the open needs.
    Read  bool
    Write bool

    // Create allows the open to create the file if it does not exist, and
    // CreateNew requires that it did not exist.
    Create    bool
    CreateNew bool

    // Truncate empties an existing file on open.
    Truncate bool

    // Directory requires the target to be a directory, and NonDirectory requires
    // that it is not. Both set is a contradiction and is refused above.
    Directory    bool
    NonDirectory bool
}

type PipeHandler

PipeHandler serves the named pipes on an IPC share.

A pipe is request-response: a client writes a message and reads the answer, which is how MS-RPC travels over SMB. Transact is the operation that does both in one exchange and the one every RPC client uses, so a handler that implements only that is a complete one for the purpose.

A handler is called on the goroutine serving the connection, so an implementation that blocks holds that client up. It may be called concurrently for different connections.

type PipeHandler interface {
    // OpenPipe reports whether a pipe exists under this handler, and prepares
    // whatever per-open state it needs. The name has no leading separator and no
    // "PIPE" prefix: "srvsvc", not "\\PIPE\\srvsvc".
    OpenPipe(name string) error

    // Transact writes a message to a pipe and returns the answer. maxOutput is
    // the largest answer the client can receive; a handler that would exceed it
    // should return what fits and report that more remains.
    Transact(name string, input []byte, maxOutput int) (output []byte, moreRemains bool, err error)

    // ClosePipe releases whatever OpenPipe prepared.
    ClosePipe(name string) error
}

type ReflectiveSecurityProvider

ReflectiveSecurityProvider describes the access the server actually enforces, rather than an access-control model it does not have.

The descriptor it returns says: authenticated users may read, and may also write unless the share is read-only. That is exactly the rule the handlers apply, so the descriptor is truthful — which matters because a client uses a descriptor to predict what it will be allowed to do, and a descriptor describing rights the server does not honour, or withholding ones it does, makes the client wrong in one direction or the other.

It is deliberately not a stand-in for real per-file ACLs. A caller with a real model implements SecurityProvider itself.

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

func NewReflectiveSecurityProvider

func NewReflectiveSecurityProvider(readOnly bool) *ReflectiveSecurityProvider

NewReflectiveSecurityProvider creates a provider describing a share whose read-only state is given.

Parameters:

  • readOnly: whether the share refuses modification

Returns:

  • The provider

func (*ReflectiveSecurityProvider) SecurityDescriptor

func (p *ReflectiveSecurityProvider) SecurityDescriptor(path string, information SecurityInformation) ([]byte, error)

SecurityDescriptor builds the descriptor for a path.

The path does not affect the answer: the server’s access rule is per share, not per file, and pretending otherwise by varying the descriptor would suggest a granularity that does not exist.

func (*ReflectiveSecurityProvider) SetSecurityDescriptor

func (p *ReflectiveSecurityProvider) SetSecurityDescriptor(path string, information SecurityInformation, descriptor []byte) error

SetSecurityDescriptor refuses every change.

The descriptor this provider returns is derived from the share’s configuration, so there is nowhere to store a different one. Accepting a change and then continuing to report the derived descriptor would be worse than refusing: a client would believe it had applied something it had not.

type ResponseWriter

ResponseWriter sends a response correlated to the request being handled. It copies the request’s TID, UID, PID and MID into the reply, sets SMB_FLAGS_REPLY, mirrors the negotiated Flags2 bits, and frames the result on the connection’s transport, so a caller only has to build the command body.

type ResponseWriter interface {
    // RemoteAddr returns the address of the client being answered.
    RemoteAddr() net.Addr

    // WriteResponse sends a successful response carrying cmd. It may be called
    // more than once for a request whose command is defined to produce several
    // responses, such as SMB_COM_ECHO.
    WriteResponse(cmd command_interface.CommandInterface) error

    // WriteResponseWithStatus sends a response carrying cmd together with a
    // status other than success. An interim response needs this: a status such
    // as STATUS_MORE_PROCESSING_REQUIRED travels alongside a payload rather than
    // instead of one, which is how a multi-leg authentication reports that it is
    // unfinished.
    WriteResponseWithStatus(cmd command_interface.CommandInterface, status nt_status.NT_STATUS) error

    // WriteError sends an error response: a header carrying status, with no
    // command payload (WordCount 0, ByteCount 0). The status is encoded in
    // whichever form the request selected.
    WriteError(status nt_status.NT_STATUS) error

    // SetResponseUID overrides the user identifier echoed in responses to this
    // request. A request arrives with the UID the client knows, which is zero
    // until the server assigns one, so the leg that assigns it has to say so.
    SetResponseUID(uid uint16)

    // SetResponseTID overrides the tree identifier echoed in responses to this
    // request, for the tree connect that assigns one.
    SetResponseTID(tid uint16)

    // SignResponse signs responses to this request with the given key and
    // sequence number.
    //
    // The dispatch loop arms this for a connection that is already signing. The
    // exchange that establishes signing has to arm it itself, because the key
    // does not exist until that exchange has been verified.
    SignResponse(macKey []byte, sequenceNumber uint32)
}

type SMBStatus

SMBStatus is the legacy SMBSTATUS form of an error: an error class paired with a class-scoped error code.

SMBSTATUS { UCHAR ErrorClass; UCHAR Reserved; USHORT ErrorCode; }
type SMBStatus struct {
    Class ErrorClass
    Code  uint16
}

func DOSError

func DOSError(status nt_status.NT_STATUS) (SMBStatus, bool)

DOSError returns the legacy SMBSTATUS class/code pair for an NTSTATUS, and whether the mapping was tabulated. An untabulated status returns ERRSRV/ERRsrverror with false, which is still a valid thing to send.

Parameters:

  • status: the NTSTATUS the server wants to report

Returns:

  • The SMBSTATUS pair to send
  • Whether a tabulated mapping was found

func (SMBStatus) Encode

func (s SMBStatus) Encode() uint32

Encode renders the pair into the 32-bit Status header field. The field is little-endian and laid out ErrorClass(1) | Reserved(1) | ErrorCode(2), so the class occupies the low byte and the code the high half-word.

Search is a directory enumeration in progress.

The whole listing is taken once, when the search opens, and then handed out in batches. Re-reading the directory on each continuation would be worse than it sounds: entries appearing or vanishing between batches would make the cursor mean something different each time, so a client could see an entry twice or miss one entirely. A snapshot is stale but coherent, which is the trade a client enumerating a directory expects.

type Search struct {
    // SID is the identifier the client sends to continue the search.
    SID uint16

    // Tree is the tree the search runs on, and Directory the resolved directory
    // being listed.
    Tree      *Tree
    Directory string

    // Pattern is what names are matched against.
    Pattern string

    // InformationLevel is the shape the entries are returned in, fixed when the
    // search opens: a continuation that asked for a different one would be
    // describing a different enumeration.
    InformationLevel uint16

    // Entries is the snapshot, and Position how far through it the client has
    // been taken.
    Entries  []DirEntry
    Position int

    Created time.Time
}

type SecurityInformation

SecurityInformation selects which parts of a security descriptor a query or a set applies to. The values are the SECURITY_INFORMATION bits from [MS-DTYP] 2.4.7.

type SecurityInformation uint32
const (
    // OwnerSecurityInformation selects the owner.
    OwnerSecurityInformation SecurityInformation = 0x00000001
    // GroupSecurityInformation selects the primary group.
    GroupSecurityInformation SecurityInformation = 0x00000002
    // DaclSecurityInformation selects the discretionary ACL.
    DaclSecurityInformation SecurityInformation = 0x00000004
    // SaclSecurityInformation selects the system ACL.
    SaclSecurityInformation SecurityInformation = 0x00000008
)

type SecurityProvider

SecurityProvider supplies the security descriptors for a share.

A share without one refuses the security-descriptor subcommands with STATUS_NOT_SUPPORTED, which is the honest answer: a client that receives a descriptor believes it describes the access the server enforces, so inventing one is worse than admitting there is no model to describe.

type SecurityProvider interface {
    // SecurityDescriptor returns the self-relative descriptor for a path, with
    // only the parts information selects populated.
    SecurityDescriptor(path string, information SecurityInformation) ([]byte, error)

    // SetSecurityDescriptor applies a descriptor to a path.
    SetSecurityDescriptor(path string, information SecurityInformation, descriptor []byte) error
}

type Server

Server is an SMB 1.0 server. It is safe for concurrent use: handlers and listeners may be registered before or during serving, and Close may be called from any goroutine.

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

func NewServer

func NewServer(config Config) (*Server, error)

NewServer creates a server from a configuration.

Parameters:

  • config: the server configuration; the zero value is usable

Returns:

  • The server
  • An error if the configuration is invalid

func (*Server) AddShare

func (s *Server) AddShare(share *Share) error

AddShare registers a share on the server. Share names are matched case-insensitively, so two that differ only in case collide.

Parameters:

  • share: the share to serve

Returns:

  • An error if the share is not usable or its name is already taken

func (*Server) Addr

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

Addr returns the address of the server’s first listener, or nil if it is not listening.

func (*Server) Close

func (s *Server) Close() error

Close stops the server: it closes every listener so the accept loops return, closes every live connection so its receive loop returns, and waits for the connection goroutines to finish. It is safe to call more than once.

func (*Server) Config

func (s *Server) Config() Config

Config returns the server’s configuration.

func (*Server) Connections

func (s *Server) Connections() int

Connections returns the number of connections currently being served.

func (*Server) ListenAndServe

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

ListenAndServe listens for Direct TCP connections on addr and serves them. An addr with no port uses port 445. It blocks until the server is closed or the listener fails.

func (*Server) ListenAndServeNBT

func (s *Server) ListenAndServeNBT(addr string, acceptedNames []string) error

ListenAndServeNBT listens for NetBIOS over TCP connections on addr and serves them, answering to the given CALLED NetBIOS names (nil answers to any). An addr with no port uses port 139. It blocks until the server is closed or the listener fails.

func (*Server) Listening

func (s *Server) Listening() bool

Listening reports whether the server currently has a listener.

func (*Server) RegisterHandler

func (s *Server) RegisterHandler(handler Handler)

RegisterHandler appends a handler to the chain. Handlers run in registration order, before the built-in command dispatch, and the first one to report that it handled a request stops the chain.

func (*Server) Serve

func (s *Server) Serve(listener transport.Listener) error

Serve accepts connections from a listener until the server is closed or the listener fails, serving each on its own goroutine. The listener is closed when Serve returns.

Parameters:

  • listener: the listener to accept from

Returns:

  • nil if the server was closed, or the listener’s error otherwise

func (*Server) ServeConn

func (s *Server) ServeConn(conn transport.Transport, remote net.Addr) error

ServeConn serves one already-established connection and returns when it ends.

It is the entry point for a caller that does its own accepting, or that carries SMB over something other than a socket — a pipe, a tunnel, a relayed connection. Serve is this in a loop behind a listener.

The connection is closed when serving finishes. The server’s connection limit and idle timeout apply, and Close tears this connection down like any other.

Parameters:

  • conn: the transport to serve, already connected and handshaken
  • remote: the peer’s address, for logging; may be nil

Returns:

  • An error if the server is closed or at its connection limit

func (*Server) Share

func (s *Server) Share(name string) *Share

Share returns the share a name refers to, or nil when none does.

func (*Server) Shares

func (s *Server) Shares() []*Share

Shares returns the registered shares.

type Session

Session is an authenticated session on a connection.

A session in the table has been authenticated: either its response verified against a credential, or it was admitted under an explicit guest or anonymous policy. Which of the three it was is recorded, because it decides what the session may go on to do — an anonymous session has no key, so it cannot sign.

type Session struct {
    // UID is the identifier the client sends on every request in this session.
    UID uint16

    // Domain, Username and Workstation are the identity that was authenticated,
    // or the identity that was claimed when the session is a guest one.
    Domain      string
    Username    string
    Workstation string

    // SessionKey is the exported session key derived from the authentication. It
    // is the MAC key for signing, and is nil for a guest or anonymous session,
    // where no key was derived.
    SessionKey []byte

    // IsGuest reports that the claimed identity was not verified and the session
    // was admitted as a guest.
    IsGuest bool

    // IsAnonymous reports a null session: no identity was claimed at all.
    IsAnonymous bool

    // Created is when the session was established.
    Created time.Time
}

func (*Session) Account

func (s *Session) Account() string

Account renders the session’s identity as DOMAIN\user, or just the username when no domain was claimed.

func (*Session) CanSign

func (s *Session) CanSign() bool

CanSign reports whether the session has key material to sign with. A guest or anonymous session does not, which is why signing cannot be required of one.

type Share

Share is a named resource a client can connect a tree to.

type Share struct {
    // Name is the share name, matched case-insensitively as Windows does.
    Name string

    // Type is what the share is. A disk share requires FS to be set.
    Type ShareType

    // Comment is the description a share enumeration would report.
    Comment string

    // ReadOnly refuses every modifying operation on the share, whatever access
    // the client asked for. It is enforced in the handlers rather than left to
    // the backend, so a backend cannot forget it.
    ReadOnly bool

    // FS is the storage behind a disk share.
    FS  FileSystem

    // Security supplies the share's security descriptors. Nil refuses the
    // security-descriptor subcommands, which is the honest answer for a share
    // with no access-control model to describe.
    Security SecurityProvider

    // Pipes serves the named pipes on an IPC share. Nil refuses every pipe
    // operation.
    Pipes PipeHandler
}

type ShareType

ShareType is the Service string a tree connect reports, describing what kind of resource the tree names ([MS-CIFS] 2.2.4.55.2).

type ShareType string
const (
    // ShareTypeDisk is a file-system share.
    ShareTypeDisk ShareType = "A:"
    // ShareTypeNamedPipe is the IPC$ share, over which named pipes are reached.
    ShareTypeNamedPipe ShareType = "IPC"
    // ShareTypePrinter is a print queue.
    ShareTypePrinter ShareType = "LPT1:"
    // ShareTypeAny matches any type, which a client may send to mean "whatever
    // this name is".
    ShareTypeAny ShareType = "?????"
)

type SigningPolicy

SigningPolicy selects a server’s stance on SMB message signing.

type SigningPolicy int
const (
    // SigningDisabled advertises no signing support and never signs. A client
    // that requires signatures will refuse to talk to the server.
    SigningDisabled SigningPolicy = iota

    // SigningEnabled advertises signing and uses it when the client asks for it,
    // leaving an unsigned session available to a client that does not.
    SigningEnabled

    // SigningRequired advertises signing as mandatory and refuses a session that
    // cannot sign, which includes every guest and anonymous session.
    SigningRequired
)

func (SigningPolicy) String

func (p SigningPolicy) String() string

String renders the policy for a log line.

type Tree

Tree is a connection to a share, established by a tree connect and named by a TID on every subsequent request.

type Tree struct {
    // TID is the identifier the client sends to act on this tree.
    TID uint16

    // Share is what the tree is connected to.
    Share *Share

    // Session is the session the tree belongs to. A tree is scoped to the session
    // that opened it, so another session's TID cannot be borrowed.
    SessionUID uint16

    // Created is when the tree was connected.
    Created time.Time
}

type VolumeInfo

VolumeInfo describes the storage behind a share.

type VolumeInfo struct {
    // Label and FileSystemName are reported to a client that asks about the
    // volume.
    Label          string
    FileSystemName string

    // SerialNumber identifies the volume.
    SerialNumber uint32

    // TotalBytes and FreeBytes describe capacity. A backend that does not know
    // reports zero for both.
    TotalBytes int64
    FreeBytes  int64

    // SectorsPerAllocationUnit and BytesPerSector describe the allocation
    // geometry a client uses to turn sizes into cluster counts.
    SectorsPerAllocationUnit uint32
    BytesPerSector           uint32
}