Generated from Manticore v1.1.5 • 201 packages. View on pkg.go.dev

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

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
}