client
import "github.com/TheManticoreProject/Manticore/network/smb/client"
Package client provides a single, version-agnostic SMB client.
The caller constructs one Client, supplies an ordered preference list of protocol versions, and dials. The client negotiates the dialect once and binds a version-specific backend (SMB1, SMB2, or — in future — SMB3) behind a common interface. Every subsequent call (TreeConnect, OpenFile, ReadFile, …) is routed to that backend, so the caller never has to know which dialect was selected.
Backends
Each SMB engine (network/smb/smb_v10, network/smb/smb_v20, …) is wrapped by a thin adapter that satisfies the Backend interface. The dependency direction is one-way: this package imports the engines; the engines never import this package. Adapters live here.
Negotiation and preference
Dialect selection is driven by a client-side preference list (Options.Preferred, highest priority first) and a policy:
- PolicyStrictOrder (default) tries the preferred versions in order and selects the first one the server accepts. This honors the caller’s order exactly — it can force SMB1 on a server that also supports SMB3.
- PolicyHighestInSet performs a single multi-protocol negotiate over the whole set and uses the server’s highest-supported dialect within it.
PolicyHighestInSet uses the SMB1 multi-protocol negotiate: one request offers the SMB1 and SMB2 dialect markers, and the reply is dispatched on its protocol marker (\xFFSMB for SMB1, \xFESMB for SMB2). The SMB2 engine’s ceiling is SMB 2.0.2, so the “SMB 2.002” marker is offered and the server returns a concrete SMB 2.0.2 response. (When SMB 2.1+/3.x engines exist, the “SMB 2.???” wildcard will be offered, with a follow-up native SMB2 negotiate to pin the exact revision.)
PolicyStrictOrder instead tries the preferred versions in order with a native per-engine negotiate, reconnecting between attempts and binding the first the server accepts — so a lower dialect listed first is selected even when the server also supports a higher one.
Backends supported today: SMB 1.0 and SMB 2.0.2. SMB 3.x awaits its engine.
Index
- Constants
- type Backend
- type Client
- func Dial(host string, port int, opts Options) (*Client, error)
- func (c *Client) CheckDirectory(path string) error
- func (c *Client) CloseFile(h FileHandle) error
- func (c *Client) ConnectionInfo() ConnectionInfo
- func (c *Client) CreateDirectory(path string) error
- func (c *Client) DeleteDirectory(path string) error
- func (c *Client) DeleteFile(path string) error
- func (c *Client) Dialect() smb.SMBProtocolVersion
- func (c *Client) Disconnect() error
- func (c *Client) ListDirectory(path, pattern string) ([]FileInfo, error)
- func (c *Client) Login(creds *credentials.Credentials) error
- func (c *Client) Logoff() error
- func (c *Client) OpenFile(path string, opts OpenOptions) (FileHandle, error)
- func (c *Client) QuerySecurityDescriptor(path string, info SecurityInformation) ([]byte, error)
- func (c *Client) RPCTransport(pipeName string) (dcerpctransport.Transport, error)
- func (c *Client) ReadFile(h FileHandle, off uint64, n uint32) ([]byte, error)
- func (c *Client) RenameFile(oldPath, newPath string) error
- func (c *Client) ServerIdentity() ServerIdentity
- func (c *Client) TreeConnect(share string) error
- func (c *Client) TreeDisconnect() error
- func (c *Client) WriteFile(h FileHandle, off uint64, data []byte) (uint32, error)
- type ConnectionInfo
- type FileHandle
- type FileInfo
- type NegotiationPolicy
- type OpenOptions
- type Options
- type SecurityInformation
- type ServerIdentity
Constants
DefaultDialTimeout is the per-attempt connect/negotiate bound applied when Options.DialTimeout is zero.
const DefaultDialTimeout = 10 * time.Second
type Backend
Backend is the version-agnostic contract a concrete SMB engine must satisfy to be driven by the generic Client. Each engine (smb_v10, smb_v20, …) is wrapped by an adapter in this package; the engines themselves do not depend on it.
The surface is flat and stateful, mirroring the underlying engines: a backend holds the current session and tree, and the methods act against them. A FileHandle returned by OpenFile is opaque to the caller and must be passed back to the same backend that produced it.
type Backend interface {
// Dialect reports the negotiated protocol version this backend speaks.
Dialect() smb.SMBProtocolVersion
// ConnectionInfo reports the connection capabilities negotiated with the server
// (signing requirement, maximum read/write sizes), normalized across dialects.
ConnectionInfo() ConnectionInfo
// Login authenticates a session with the server.
Login(creds *credentials.Credentials) error
// ServerIdentity reports the server identity (NetBIOS/DNS names, OS version)
// learned during authentication. It is meaningful only after a successful Login;
// fields are empty/zero otherwise or when the server did not advertise them.
ServerIdentity() ServerIdentity
// TreeConnect connects to a share by name (e.g. "C$"), making it the current
// tree for subsequent file operations. The backend forms the full UNC path.
TreeConnect(share string) error
// OpenFile opens or creates a file on the current tree and returns a handle.
OpenFile(path string, opts OpenOptions) (FileHandle, error)
// ReadFile reads up to n bytes from the open file starting at off.
ReadFile(h FileHandle, off uint64, n uint32) ([]byte, error)
// WriteFile writes data to the open file starting at off and returns the
// number of bytes written.
WriteFile(h FileHandle, off uint64, data []byte) (uint32, error)
// CloseFile closes a handle returned by OpenFile.
CloseFile(h FileHandle) error
// ListDirectory enumerates entries of a directory on the current tree that
// match pattern (e.g. "*").
ListDirectory(path, pattern string) ([]FileInfo, error)
// QuerySecurityDescriptor returns the raw self-relative SECURITY_DESCRIPTOR
// bytes for path on the current tree. info selects which parts (owner, group,
// DACL, SACL) to retrieve. The bytes can be parsed with
// github.com/TheManticoreProject/winacl.
QuerySecurityDescriptor(path string, info SecurityInformation) ([]byte, error)
// DeleteFile deletes a file on the current tree.
DeleteFile(path string) error
// CreateDirectory creates a directory on the current tree.
CreateDirectory(path string) error
// DeleteDirectory removes an empty directory on the current tree.
DeleteDirectory(path string) error
// RenameFile renames (or moves) a file or directory on the current tree. It
// fails if newPath already exists.
RenameFile(oldPath, newPath string) error
// CheckDirectory returns nil if path names an existing directory on the
// current tree, and an error otherwise.
CheckDirectory(path string) error
// RPCTransport opens a named pipe on the current tree (IPC$) and returns it as
// a DCE/RPC PDU transport, framed for the negotiated dialect. It is the bridge
// from the version-agnostic SMB client to the DCE/RPC stack, so callers can run
// MS-RPC interfaces (srvsvc, lsarpc, …) without knowing whether SMB1 or SMB2 was
// negotiated. The current tree must be IPC$.
RPCTransport(pipeName string) (dcerpctransport.Transport, error)
// TreeDisconnect disconnects the current tree.
TreeDisconnect() error
// Logoff tears down the authenticated session.
Logoff() error
// Disconnect closes the underlying transport connection.
Disconnect() error
}
type Client
Client is the generic, version-agnostic SMB client. It negotiates a dialect once and routes every operation to a version-specific backend (SMB1/SMB2/…). The surface is flat: the client holds the current session and tree, mirroring the underlying engines.
type Client struct {
// contains filtered or unexported fields
}
func Dial
func Dial(host string, port int, opts Options) (*Client, error)
Dial connects to host:port, negotiates a dialect according to opts, and returns a ready-to-authenticate Client.
Selection is driven by opts.Preferred (highest priority first; defaults to all supported versions, best first) and opts.Policy:
- PolicyStrictOrder (default) tries the preferred versions in order and returns the first the server accepts, reconnecting between attempts. It can select a lower dialect even when the server supports a higher one.
- PolicyHighestInSet performs a single SMB1 multi-protocol negotiate and uses the server’s highest-supported dialect within the set.
Example
ExampleDial shows a full session: negotiate the best mutually-supported dialect, authenticate, connect to a share, and read a file — without the caller having to know whether SMB1 or SMB2 was selected.
package main
import (
"fmt"
"log"
smbclient "github.com/TheManticoreProject/Manticore/network/smb/client"
"github.com/TheManticoreProject/Manticore/windows/credentials"
"github.com/TheManticoreProject/Manticore/windows/fileflags"
)
func main() {
creds, err := credentials.NewCredentials("", "Administrator", "secret", "")
if err != nil {
log.Fatal(err)
}
// Empty Options: offer all supported versions, best first.
c, err := smbclient.Dial("fileserver", 445, smbclient.Options{})
if err != nil {
log.Fatal(err)
}
defer c.Disconnect()
if err := c.Login(creds); err != nil {
log.Fatal(err)
}
fmt.Println("negotiated", c.Dialect())
if err := c.TreeConnect("C$"); err != nil {
log.Fatal(err)
}
defer c.TreeDisconnect()
h, err := c.OpenFile(`Windows\win.ini`, smbclient.OpenOptions{
DesiredAccess: fileflags.GENERIC_READ | fileflags.FILE_READ_ATTRIBUTES,
ShareAccess: fileflags.FILE_SHARE_READ | fileflags.FILE_SHARE_WRITE,
CreateDisposition: fileflags.FILE_OPEN,
CreateOptions: fileflags.FILE_NON_DIRECTORY_FILE,
})
if err != nil {
log.Fatal(err)
}
defer c.CloseFile(h)
data, err := c.ReadFile(h, 0, 4096)
if err != nil {
log.Fatal(err)
}
fmt.Printf("read %d bytes\n", len(data))
}
Example (Preference Order)
ExampleDial_preferenceOrder forces SMB1, falling back to SMB 2.0.2. With the default PolicyStrictOrder the client selects the first version the server accepts, honoring the listed order even when the server supports both.
package main
import (
"fmt"
"log"
"github.com/TheManticoreProject/Manticore/network/smb"
smbclient "github.com/TheManticoreProject/Manticore/network/smb/client"
)
func main() {
c, err := smbclient.Dial("fileserver", 445, smbclient.Options{
Preferred: []smb.SMBProtocolVersion{
smb.SMB_VERSION_1_0,
smb.SMB_VERSION_2_0_2,
},
})
if err != nil {
log.Fatal(err)
}
defer c.Disconnect()
fmt.Println("negotiated", c.Dialect())
}
func (*Client) CheckDirectory
func (c *Client) CheckDirectory(path string) error
CheckDirectory returns nil if path names an existing directory on the current tree, and an error otherwise.
func (*Client) CloseFile
func (c *Client) CloseFile(h FileHandle) error
CloseFile closes a handle returned by OpenFile.
func (*Client) ConnectionInfo
func (c *Client) ConnectionInfo() ConnectionInfo
ConnectionInfo reports the connection capabilities negotiated with the server: whether signing is required and the maximum read/write sizes.
func (*Client) CreateDirectory
func (c *Client) CreateDirectory(path string) error
CreateDirectory creates a directory on the current tree.
func (*Client) DeleteDirectory
func (c *Client) DeleteDirectory(path string) error
DeleteDirectory removes an empty directory on the current tree.
func (*Client) DeleteFile
func (c *Client) DeleteFile(path string) error
DeleteFile deletes a file on the current tree.
func (*Client) Dialect
func (c *Client) Dialect() smb.SMBProtocolVersion
Dialect reports the negotiated protocol version.
func (*Client) Disconnect
func (c *Client) Disconnect() error
Disconnect closes the underlying transport connection.
func (*Client) ListDirectory
func (c *Client) ListDirectory(path, pattern string) ([]FileInfo, error)
ListDirectory enumerates entries of a directory on the current tree.
Example
ExampleClient_ListDirectory enumerates a directory; the returned FileInfo values are the same regardless of the negotiated dialect.
package main
import (
"fmt"
"log"
smbclient "github.com/TheManticoreProject/Manticore/network/smb/client"
)
func main() {
var c *smbclient.Client // obtained from Dial + Login + TreeConnect
entries, err := c.ListDirectory("Windows", "*")
if err != nil {
log.Fatal(err)
}
for _, e := range entries {
fmt.Printf("%s\tdir=%v\tsize=%d\n", e.Name, e.IsDir(), e.Size)
}
}
func (*Client) Login
func (c *Client) Login(creds *credentials.Credentials) error
Login authenticates a session with the server.
func (*Client) Logoff
func (c *Client) Logoff() error
Logoff tears down the authenticated session.
func (*Client) OpenFile
func (c *Client) OpenFile(path string, opts OpenOptions) (FileHandle, error)
OpenFile opens or creates a file on the current tree and returns a handle.
func (*Client) QuerySecurityDescriptor
func (c *Client) QuerySecurityDescriptor(path string, info SecurityInformation) ([]byte, error)
QuerySecurityDescriptor returns the raw self-relative SECURITY_DESCRIPTOR bytes for path on the current tree. info selects which parts (owner, group, DACL, SACL) to retrieve; parse the result with github.com/TheManticoreProject/winacl.
func (*Client) RPCTransport
func (c *Client) RPCTransport(pipeName string) (dcerpctransport.Transport, error)
RPCTransport opens pipeName on the current tree and returns it as a DCE/RPC PDU transport for the negotiated dialect (SMB1 or SMB2). It is the bridge from the version-agnostic SMB client to the DCE/RPC stack: a caller binds it into a network/dcerpc/v5/client.Client (which the returned value satisfies as a transport.Transport) to drive MS-RPC interfaces such as srvsvc over the IPC$ tree, without caring which SMB dialect was negotiated.
The current tree must be IPC$ (TreeConnect(“IPC$”)) before calling.
func (*Client) ReadFile
func (c *Client) ReadFile(h FileHandle, off uint64, n uint32) ([]byte, error)
ReadFile reads up to n bytes from the open file starting at off.
func (*Client) RenameFile
func (c *Client) RenameFile(oldPath, newPath string) error
RenameFile renames (or moves) a file or directory on the current tree. It fails if newPath already exists.
func (*Client) ServerIdentity
func (c *Client) ServerIdentity() ServerIdentity
ServerIdentity reports the server identity (NetBIOS/DNS computer and domain names, OS version) advertised during NTLM authentication. Call it after Login.
func (*Client) TreeConnect
func (c *Client) TreeConnect(share string) error
TreeConnect connects to a share by name (e.g. “C$”), making it the current tree.
func (*Client) TreeDisconnect
func (c *Client) TreeDisconnect() error
TreeDisconnect disconnects the current tree.
func (*Client) WriteFile
func (c *Client) WriteFile(h FileHandle, off uint64, data []byte) (uint32, error)
WriteFile writes data to the open file at off and returns the bytes written.
type ConnectionInfo
ConnectionInfo holds the connection capabilities negotiated with the server, normalized across dialects. SMB1 negotiates a single MaxBufferSize rather than separate read/write maxima, so on SMB1 both MaxReadSize and MaxWriteSize report that value.
type ConnectionInfo struct {
// SigningRequired reports whether the server requires SMB message signing.
SigningRequired bool
// MaxReadSize is the largest read the server will accept, in bytes.
MaxReadSize uint32
// MaxWriteSize is the largest write the server will accept, in bytes.
MaxWriteSize uint32
// SupportsNTLMv2 reports whether the server advertised NTLM2 / extended session
// security (NTLMv2) in its NTLM CHALLENGE. Meaningful only after Login.
SupportsNTLMv2 bool
}
type FileHandle
FileHandle is an opaque, version-agnostic handle to an open file. The backend that produced it knows how to interpret the underlying value (an SMB1 FID or an SMB2 file id); callers must pass it back to that same backend.
type FileHandle struct {
// contains filtered or unexported fields
}
func (FileHandle) Dialect
func (h FileHandle) Dialect() smb.SMBProtocolVersion
Dialect reports which protocol version’s backend owns this handle.
type FileInfo
FileInfo is a version-agnostic directory entry, normalized from the SMB1 TRANS2 FIND information levels and the SMB2 QUERY_DIRECTORY information classes. It is populated by ListDirectory (Phase 6).
type FileInfo struct {
Name string
FileAttributes uint32
Size uint64
AllocationSize uint64
CreationTime time.Time
LastAccessTime time.Time
LastWriteTime time.Time
ChangeTime time.Time
}
func (FileInfo) IsDir
func (fi FileInfo) IsDir() bool
IsDir reports whether the entry has the FILE_ATTRIBUTE_DIRECTORY flag set.
type NegotiationPolicy
NegotiationPolicy controls how the client applies its preference list when selecting a dialect during Dial.
type NegotiationPolicy int
const (
// PolicyStrictOrder tries the preferred versions in order and selects the
// first one the server accepts. It honors the caller's order exactly — it
// can select a lower dialect (e.g. SMB1) even when the server also supports
// a higher one. Out-of-order lists may cost a reconnect per attempt.
PolicyStrictOrder NegotiationPolicy = iota
// PolicyHighestInSet performs a single multi-protocol negotiate over the
// whole preference set and uses the server's highest-supported dialect
// within it. One round-trip; the list acts as an allow-list rather than a
// strict order.
PolicyHighestInSet
)
type OpenOptions
OpenOptions carries the file-open parameters shared by every dialect. Combine the bit values from windows/fileflags with bitwise OR, e.g. DesiredAccess: fileflags.GENERIC_READ | fileflags.FILE_READ_ATTRIBUTES.
type OpenOptions struct {
DesiredAccess uint32 // fileflags GENERIC_* / FILE_* access rights
ShareAccess uint32 // fileflags FILE_SHARE_*
CreateDisposition uint32 // fileflags FILE_OPEN / FILE_CREATE / ...
CreateOptions uint32 // fileflags FILE_DIRECTORY_FILE / FILE_NON_DIRECTORY_FILE / ...
FileAttributes uint32 // fileflags FILE_ATTRIBUTE_*
}
type Options
Options configures a Dial.
type Options struct {
// Preferred is the ordered list of protocol versions, highest priority
// first. When empty, the client offers all supported versions, best first.
Preferred []smb.SMBProtocolVersion
// Policy selects how Preferred is applied. The zero value is
// PolicyStrictOrder.
Policy NegotiationPolicy
// Workstation is the NetBIOS workstation name sent during authentication.
// When empty, a default is used.
Workstation string
// DialTimeout bounds each connect/negotiate attempt during Dial, so that a
// server which silently drops a probe (e.g. an SMB1-only host receiving an
// SMB2 negotiate) fails the attempt instead of blocking forever, letting
// PolicyStrictOrder move on to the next preferred version. The bound is
// lifted once negotiation completes, so it does not constrain long-blocking
// operations such as CHANGE_NOTIFY. Zero applies DefaultDialTimeout; a
// negative value disables the bound.
DialTimeout time.Duration
}
type SecurityInformation
SecurityInformation selects which parts of a SECURITY_DESCRIPTOR a QuerySecurityDescriptor call retrieves. The values are the SECURITY_INFORMATION bits from [MS-DTYP] 2.4.7; combine them with bitwise OR.
type SecurityInformation uint32
const (
// OwnerSecurityInformation requests the owner SID.
OwnerSecurityInformation SecurityInformation = 0x00000001
// GroupSecurityInformation requests the primary-group SID.
GroupSecurityInformation SecurityInformation = 0x00000002
// DaclSecurityInformation requests the discretionary ACL.
DaclSecurityInformation SecurityInformation = 0x00000004
// SaclSecurityInformation requests the system ACL (requires extra privilege).
SaclSecurityInformation SecurityInformation = 0x00000008
)
type ServerIdentity
ServerIdentity is the server identity learned during NTLM authentication: the NetBIOS and DNS computer/domain names advertised in the CHALLENGE TargetInfo, and the OS version from the CHALLENGE Version field. Fields are empty/zero when the server did not advertise them or when authentication has not occurred.
type ServerIdentity struct {
NetBIOSComputerName string
NetBIOSDomainName string
DNSComputerName string
DNSDomainName string
OSVersionMajor uint8
OSVersionMinor uint8
OSVersionBuild uint16
// OSName is the server's native OS string from the SMB1 SESSION_SETUP response.
// It is populated for SMB1 only; SMB2/SMB3 do not transmit an OS name on the wire
// (use the OSVersion* fields there).
OSName string
}