smb1

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 22 Imported by: 0

README

smb1client

Go Reference CI

⚠️ Security Warning

SMB1/CIFS is deprecated and insecure. Microsoft disabled it by default starting with Windows 10 Fall Creators Update (1709) and Windows Server version 1709.

This library should ONLY be used in controlled, isolated environments where:

  • Legacy systems require SMB1 support
  • Modern protocols (SMB2/3) are not available
  • The network is trusted and isolated from external threats

Known security issues with SMB1:

  • Exploited by ransomware (e.g., WannaCry, NotPetya)
  • No encryption support
  • Susceptible to man-in-the-middle attacks
  • Credential replay attacks possible
  • No message integrity checking (this client does not implement SMB signing)

For modern SMB support, use github.com/hirochachacha/go-smb2 instead.

Overview

Pure Go implementation of the SMB1/CIFS client protocol (root package smb1). The exported API is a superset of go-smb2's: every exported go-smb2 symbol exists here with an identical signature, so migrating code between the two libraries is an import swap. On top of that parity surface, this library adds connection pooling, error-classification predicates, copy helpers, server capability/clock/volume queries, and context-based logging hooks.

Why use this library?
  • Pure Go: no CGO dependencies; cross-platform compilation works out of the box
  • go-smb2 API parity: code written against go-smb2 compiles unchanged after swapping the import
  • No privileges: does not require root/admin or mounting filesystems
  • Modern Go: context support, standard library error semantics (*os.PathError, os.ErrNotExist), io/fs integration via Share.DirFS
  • Legacy support: connects to old NAS devices, Windows XP/2003, embedded systems
When to use SMB1 vs SMB2/3

Use SMB1 (this library) when:

  • Connecting to devices that only support SMB1 (pre-2007 systems)
  • Legacy NAS devices or embedded systems
  • Windows XP, Windows Server 2003, or old Samba versions

Use SMB2/3 (go-smb2) when:

  • Windows Vista or later, Windows Server 2008 or later
  • Modern Linux with Samba 3.6+
  • Security and performance are priorities
  • Encryption or signing is required

Installation

go get github.com/macourteau/smb1client

Quick Start

package main

import (
    "fmt"
    "net"

    "github.com/macourteau/smb1client"
)

func main() {
    // Connect to SMB server
    conn, err := net.Dial("tcp", "192.0.2.10:445")
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Authenticate with NTLM
    d := &smb1.Dialer{
        Initiator: &smb1.NTLMInitiator{
            User:     "username",
            Password: "password",
            Domain:   "WORKGROUP",
        },
    }

    session, err := d.Dial(conn)
    if err != nil {
        panic(err)
    }
    defer session.Logoff()

    // Mount share
    share, err := session.Mount("sharename")
    if err != nil {
        panic(err)
    }
    defer share.Umount()

    // Read file
    data, err := share.ReadFile("file.txt")
    if err != nil {
        panic(err)
    }
    fmt.Printf("File contents: %s\n", data)
}

Features

Supported Operations

Session management:

  • Protocol negotiation with SMB1 servers (dialect NT LM 0.12)
  • NTLM v2 authentication (password or precomputed hash)
  • Share enumeration (ListSharenames, via RAP with a DCE/RPC SRVSVC fallback)
  • Negotiated server capabilities (Session.Capabilities)
  • Server clock reporting (Session.ServerTime, from the negotiate response)
  • Context support for timeouts and cancellation (WithContext, DialContext)
  • Session logoff

Share operations:

  • Mount/unmount shares (share name or full UNC path)
  • Directory listing (ReadDir, Readdir), tree walking (Walk), globbing (Glob, Match)
  • Create directories (Mkdir, MkdirAll)
  • Stat operations (Stat, Lstat, Exists, IsDir)
  • Timestamp and attribute changes (Chtimes, Chmod)
  • Filesystem capacity (Share.Statfs, File.Statfs, with legacy-level fallback)
  • Volume identity — serial number and label (Share.VolumeInfo)
  • Rename, remove (Remove, RemoveAll), truncate
  • Copy helpers (CopyFile within the share, CopyFrom/CopyTo between local disk and the share)
  • Read-only io/fs view of a share subtree (Share.DirFS)

File operations:

  • Open, Create, OpenFile with standard os package semantics
  • Read, Write, ReadAt, WriteAt, Seek, Truncate, Sync
  • ReadFile, WriteFile convenience methods
  • ReadFrom/WriteTo streaming (client-side; see compatibility notes)
  • Pipelined reads and writes for large transfers, sized to the server's advertised MaxMpxCount
  • Standard io.Reader, io.Writer, io.Seeker, io.ReaderFrom, io.WriterTo interfaces

Connection pooling:

Error handling:

  • Standard os package error types (*os.PathError, *os.LinkError, os.ErrNotExist, ...)
  • Classification predicates: IsNotFoundError, IsPermissionError, IsAuthError, IsExistError, IsNetworkError, IsTimeoutError, IsTemporary
  • go-smb2-compatible wrapper types ContextError and TransportError

Path compatibility:

  • Automatic / to \ normalization (controlled by NORMALIZE_PATH)
  • UNC path handling, validation, and traversal protection
  • Helpers: ToWindowsPath, ToUnixPath, NormalizeShareName, ValidateShareName

Logging:

  • Context-based: attach any smb1.Logger implementation (Debug/Info/Warn/Error methods) with smb1.WithLogger
Not Supported
  • Plaintext/legacy authentication (security reasons); NTLM v2 only
  • SMB signing
  • SMB encryption (does not exist in SMB1)
  • NetBIOS over NetBEUI (only direct TCP on port 445)
  • DFS (Distributed File System)
  • General named pipe operations (only \srvsvc for share enumeration)
  • Symbolic links (Symlink/Readlink exist for API parity but return errors.ErrUnsupported)
  • File locking
  • Extended attributes

Documentation

Usage Examples

Basic File Operations
// Write a file
err := share.WriteFile("test.txt", []byte("Hello, SMB1!"), 0644)

// Read a file
data, err := share.ReadFile("test.txt")

// Check if file exists
_, err = share.Stat("test.txt")
if os.IsNotExist(err) {
    fmt.Println("File does not exist")
}

// Rename a file
err = share.Rename("old.txt", "new.txt")
Directory Operations
// Create directory
err := share.Mkdir("mydir", 0755)

// Create nested directories
err = share.MkdirAll("path/to/nested/dir", 0755)

// List directory contents
files, err := share.ReadDir("mydir")
for _, f := range files {
    fmt.Printf("%s (%d bytes)\n", f.Name(), f.Size())
}

// Remove file or empty directory
err = share.Remove("mydir/file.txt")

// Remove a tree
err = share.RemoveAll("mydir")
Advanced File Operations
// Open file with specific flags
f, err := share.OpenFile("data.bin",
    os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
    return err
}
defer f.Close()

// Write at specific offset
n, err := f.WriteAt([]byte("data"), 100)

// Seek to position
offset, err := f.Seek(0, io.SeekStart)

// Read incrementally
buf := make([]byte, 4096)
for {
    n, err := f.Read(buf)
    if err == io.EOF {
        break
    }
    // Process buf[:n]
}
Context Support
// Set timeout for dial
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

session, err := d.DialContext(ctx, conn)

// Use context for all operations
sessionWithTimeout := session.WithContext(ctx)
share, err := sessionWithTimeout.Mount("Share")
Share Enumeration
// List all shares on the server
shares, err := session.ListSharenames()
if err != nil {
    log.Fatalf("Failed to list shares: %v", err)
}

for _, share := range shares {
    fmt.Printf("Share: %s\n", share)
}

The library uses RAP (Remote Administration Protocol) for share enumeration on most servers. If RAP is not supported (common on some embedded devices), it falls back to DCE/RPC with the SRVSVC interface.

Filesystem Information
// Capacity of the filesystem backing the mounted share. The name argument
// exists for go-smb2 parity; "" queries the share root.
info, err := share.Statfs("")
if err != nil {
    log.Fatalf("Failed to query capacity: %v", err)
}
totalBytes := info.TotalBlockCount() * info.FragmentSize() * info.BlockSize()
freeBytes := info.FreeBlockCount() * info.FragmentSize() * info.BlockSize()
fmt.Printf("total=%d free=%d\n", totalBytes, freeBytes)

// Identity of the volume behind the share
vol, err := share.VolumeInfo()
if err != nil {
    log.Fatalf("Failed to query volume: %v", err)
}
fmt.Printf("serial=%#08x label=%q\n", vol.SerialNumber, vol.Label)

Statfs prefers SMB_QUERY_FS_SIZE_INFO, whose allocation-unit counts are 64-bit, and falls back to the legacy SMB_INFO_ALLOCATION level when a server declines it — the same accommodation share enumeration makes for servers lacking RAP. The fallback is chosen from the returned NT_STATUS, so a network or permission failure propagates instead of triggering a pointless retry.

Statfs returns go-smb2's FileFsInfo interface with the same field mapping: BlockSize is bytes per sector, FragmentSize is sectors per allocation unit, and the three counts are in allocation units. SMB1's TRANS2_QUERY_FS_INFORMATION is share-wide, so the name argument is validated but does not affect the query, and SMB1 has no per-caller quota concept, so AvailableBlockCount always equals FreeBlockCount.

VolumeInfo reports the serial number assigned when the volume was formatted, which distinguishes one piece of removable media from another without writing anything to the share. It is not a strong identifier: it is not globally unique, and reformatting changes it. Label is often empty, and CreationTime is the zero time on servers whose filesystem does not record it.

Connection Pooling

For workloads with many short-lived operations, ConnectionPool reuses authenticated sessions instead of re-dialing and re-authenticating each time:

d := &smb1.Dialer{
    Initiator: &smb1.NTLMInitiator{User: "username", Password: "password"},
}

pool := smb1.NewConnectionPool("192.0.2.10:445", d, nil) // nil = DefaultPoolConfig()
defer pool.Close()

conn, err := pool.Get(context.Background())
if err != nil {
    return err
}
defer conn.Close() // returns the session to the pool

share, err := conn.Mount("Public")

PoolConfig controls sizing and lifetime: MaxIdle (default 5), MaxActive (default 10), IdleTimeout (default 5 minutes), WaitTimeout (default 30 seconds), and an optional HealthCheck callback run before an idle session is reused. PooledSession.Close returns the session to the pool; ReallyClose tears the connection down instead. Pool.Stats() reports idle and active counts. Getting from a closed or exhausted pool fails with the sentinel errors ErrPoolClosed / ErrPoolExhausted.

Error Handling

The library provides both SMB-specific and standard Go error types:

f, err := share.Open("file.txt")
if err != nil {
    // Check specific error types
    if smb1.IsNotFoundError(err) {
        log.Println("File not found")
    } else if smb1.IsPermissionError(err) {
        log.Println("Permission denied")
    } else if smb1.IsAuthError(err) {
        log.Println("Authentication failed")
    } else if smb1.IsNetworkError(err) {
        log.Println("Network error")
    } else {
        log.Printf("Other error: %v", err)
    }
    return
}
defer f.Close()

// Also works with standard library error checking
if errors.Is(err, os.ErrNotExist) {
    // File not found
}

See ERRORS.md for the complete error taxonomy, including the go-smb2-compatible ContextError and TransportError wrappers.

Logging Configuration

Logging is context-based. Implement the four-method smb1.Logger interface and attach it to the context before calling SMB operations:

// Create a custom logger that implements smb1.Logger interface
type MyLogger struct {
    logger *log.Logger
}

func (l *MyLogger) Debug(format string, v ...interface{}) {
    l.logger.Printf("[DEBUG] "+format, v...)
}

func (l *MyLogger) Info(format string, v ...interface{}) {
    l.logger.Printf("[INFO] "+format, v...)
}

func (l *MyLogger) Warn(format string, v ...interface{}) {
    l.logger.Printf("[WARN] "+format, v...)
}

func (l *MyLogger) Error(format string, v ...interface{}) {
    l.logger.Printf("[ERROR] "+format, v...)
}

// Attach logger to context
logger := &MyLogger{logger: log.New(os.Stderr, "[smb1] ", log.LstdFlags)}
ctx := smb1.WithLogger(context.Background(), logger)

// Use context with SMB operations
session, err := dialer.DialContext(ctx, conn)

Operations invoked without a logger in their context log nothing. Level filtering is up to the Logger implementation — the library calls the method matching the message's severity.

API Compatibility with go-smb2

The exported API is a strict superset of go-smb2 v1.1.0: every exported go-smb2 symbol — types, methods, functions, fields, and constants — exists here with an identical signature. Migrating code from go-smb2 to this library (or back) is an import swap:

// SMB2 code
import "github.com/hirochachacha/go-smb2"
d := &smb2.Dialer{
    Initiator: &smb2.NTLMInitiator{
        User:     "username",
        Password: "password",
    },
}

// SMB1 code (import swap; the package name changes from smb2 to smb1)
import "github.com/macourteau/smb1client"
d := &smb1.Dialer{
    Initiator: &smb1.NTLMInitiator{
        User:     "username",
        Password: "password",
    },
}

Signatures match, but SMB1 cannot express everything SMB2 can, so a few methods behave differently. These are the behavioral notes to review when migrating:

  • Dialer.MaxCreditBalance and Dialer.Negotiator are accepted but ignored: SMB1 has no credit-based flow control, and SMB1 negotiation carries none of the SMB2 negotiate options (signing, client GUID, dialect selection — the dialect is always NT LM 0.12).
  • Statfs(name) matches go-smb2's signature and validates the name like any other path, but SMB1's TRANS2_QUERY_FS_INFORMATION is share-wide, so the name does not affect the query. AvailableBlockCount equals FreeBlockCount because SMB1 has no per-caller quota figure.
  • Lstat is identical to Stat: SMB1 has no symbolic links, so there are no separate lstat semantics.
  • Symlink and Readlink always fail with errors.ErrUnsupported (wrapped in *os.LinkError / *os.PathError): the reparse-point FSCTLs go-smb2 uses are SMB2 constructs with no SMB1 equivalent.
  • Chmod maps only the owner-write bit (0200) to FILE_ATTRIBUTE_READONLY — the only permission SMB attributes can express. All other attributes are preserved via read-modify-write, matching go-smb2's behavior. Servers that reject the TRANS2 set with STATUS_NOT_SUPPORTED get a second attempt via the core-protocol SMB_COM_SET_INFORMATION command; some embedded servers support no attribute mutation at all, in which case Chmod surfaces that status to the caller.
  • Chtimes treats a zero time.Time as "leave that timestamp unchanged" (SMB1 encodes it as FILETIME 0, which the protocol defines that way). This is a deliberate improvement over go-smb2, which encodes zero times literally.
  • Rename does not replace an existing target — SMB_COM_RENAME fails with STATUS_OBJECT_NAME_COLLISION (detect with IsExistError). go-smb2's Rename also does not overwrite an existing target.
  • File.ReadFrom and File.WriteTo are client-side streaming in buffered chunks. Unlike go-smb2, no server-side copy is attempted when the other end is also a remote file; the data always flows through the client.
  • Mount accepts either a bare share name or a full UNC path (\\server\share), same as go-smb2.

Requirements

  • Go version: per the go directive in go.mod (Go 1.26)
  • Network: TCP connectivity to the SMB server on port 445
  • Server: SMB1/CIFS capable server (Samba with NT1 enabled, legacy Windows, NAS devices)

Testing

Unit tests run without any server:

go test ./...        # or ./test.sh
go test -race ./...  # race detection
go test -cover ./... # coverage

Integration tests run against a dockerized Samba server configured to speak SMB1 (NT1), included in integration/:

integration/up.sh                  # build + start the server (idempotent)
go test -tags integration ./...    # run unit + integration tests
integration/down.sh                # stop + remove the server

The integration suite reads these environment variables; the defaults match the dockerized server, so none need to be set when using it:

Variable Default
SMB_SERVER localhost:10445
SMB_USER smbtest
SMB_PASSWORD smbtest
SMB_DOMAIN (empty)
SMB_SHARE testshare

Point the variables at any other SMB1-capable server to test against real hardware.

Contributing

Contributions are welcome. Please:

  1. Open an issue to discuss significant changes
  2. Follow existing code style and conventions
  3. Add tests for new functionality
  4. Update documentation

Note the compatibility contract: the public API must not drift from go-smb2's signatures (compat_gosmb2_test.go guards part of this).

License

MIT License — see LICENSE for details.

Acknowledgments

  • API design based on the excellent go-smb2 library by hirochachacha
  • NTLM implementation adapted from go-smb2 (BSD-2-Clause License)
  • Protocol specifications from Microsoft [MS-CIFS] and [MS-SMB]
  • Tested against Samba and Windows implementations

References

  • [MS-CIFS]: Common Internet File System (CIFS) Protocol Specification
  • [MS-SMB]: Server Message Block (SMB) Protocol Specification
  • [MS-SRVS]: Server Service (SRVSVC) Remote Protocol Specification
  • [MS-RPCE]: Remote Procedure Call Protocol Extensions
  • go-smb2: SMB2/3 client library for Go
  • Samba: Open-source SMB/CIFS implementation

Documentation

Overview

Package smb1 implements a pure Go SMB1/CIFS client library.

Security Warning

SMB1/CIFS is an outdated protocol with known security vulnerabilities. It should only be used with legacy systems that do not support SMB2 or SMB3. For modern systems, use the go-smb2 library instead: https://github.com/hirochachacha/go-smb2

This library is provided for compatibility with old NAS devices, embedded systems, and Windows XP/2003 servers that require SMB1.

Usage

Basic usage example:

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"os"

	"github.com/macourteau/smb1client"
)

func main() {
	// Connect to SMB server
	conn, err := net.Dial("tcp", "192.168.1.100:445")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// Setup authentication
	d := &smb1.Dialer{
		Initiator: &smb1.NTLMInitiator{
			User:     "username",
			Password: "password",
			Domain:   "WORKGROUP",
		},
	}

	// Establish SMB session
	session, err := d.Dial(conn)
	if err != nil {
		panic(err)
	}
	defer session.Logoff()

	// Mount share
	share, err := session.Mount("Share")
	if err != nil {
		panic(err)
	}
	defer share.Umount()

	// Open file
	f, err := share.Open("file.txt")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	// Read file
	data, err := io.ReadAll(f)
	if err != nil {
		panic(err)
	}
	fmt.Printf("File contents: %s\n", data)
}

Logging

This library uses context-based logging. To enable logging, attach a logger to the context before calling SMB operations:

// Create a custom logger
type myLogger struct{}

func (l *myLogger) Debug(format string, v ...interface{}) {
	log.Printf("[DEBUG] "+format, v...)
}

func (l *myLogger) Info(format string, v ...interface{}) {
	log.Printf("[INFO] "+format, v...)
}

func (l *myLogger) Warn(format string, v ...interface{}) {
	log.Printf("[WARN] "+format, v...)
}

func (l *myLogger) Error(format string, v ...interface{}) {
	log.Printf("[ERROR] "+format, v...)
}

// Attach logger to context
ctx := smb1.WithLogger(context.Background(), &myLogger{})

// Use context with SMB operations
session, err := d.DialContext(ctx, conn)

API Compatibility

This library provides an API compatible with go-smb2 for easy migration. Code written for go-smb2 can be adapted to SMB1 by changing import paths and adjusting for protocol differences.

Protocol Support

Supported features:

  • NTLM v2 authentication
  • Share mounting/unmounting
  • File read/write operations
  • Directory operations (create, list, remove)
  • File operations (stat, truncate, rename)

Unsupported features:

  • SMB signing (security feature)
  • Encryption (not available in SMB1)
  • DFS (Distributed File System)
  • Extended attributes

For more features and better security, migrate to SMB2/SMB3 using go-smb2: https://github.com/hirochachacha/go-smb2

Example (Compatibility)

Example demonstrates compatibility features

// Path conversion
windowsPath := smb1.ToWindowsPath("dir/subdir/file.txt")
fmt.Println("Windows path:", windowsPath)

unixPath := smb1.ToUnixPath("dir\\subdir\\file.txt")
fmt.Println("Unix path:", unixPath)

// Share name normalization
shareName := smb1.NormalizeShareName("MyShare", "192.168.1.100:445")
fmt.Println("Normalized share:", shareName)

// Validate share name
err := smb1.ValidateShareName("ValidShare")
fmt.Println("Valid share name:", err == nil)
Output:
Windows path: dir\subdir\file.txt
Unix path: dir/subdir/file.txt
Normalized share: \\192.168.1.100\MyShare
Valid share name: true
Example (ErrorHandling)

Example demonstrates basic error handling with the SMB1 client

// Connect to SMB server
conn, err := net.DialTimeout("tcp", "192.168.1.100:445", 5*time.Second)
if err != nil {
	log.Fatal(err)
}
defer conn.Close()

// Setup authentication
d := &smb1.Dialer{
	Initiator: &smb1.NTLMInitiator{
		User:     "username",
		Password: "password",
		Domain:   "WORKGROUP",
	},
}

// Establish SMB session with context timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

session, err := d.DialContext(ctx, conn)
if err != nil {
	// Check for specific error types
	if smb1.IsAuthError(err) {
		log.Fatal("Authentication failed - check username/password")
	}
	if smb1.IsNetworkError(err) {
		log.Fatal("Network error - check connectivity")
	}
	log.Fatal(err)
}
defer session.Logoff()

// Mount share
share, err := session.Mount("Share")
if err != nil {
	log.Fatal(err)
}
defer share.Umount()

// Try to open a file with error handling
file, err := share.Open("nonexistent.txt")
if err != nil {
	if smb1.IsNotFoundError(err) {
		fmt.Println("File not found - this is expected")
		return
	}
	if smb1.IsPermissionError(err) {
		log.Fatal("Permission denied")
	}
	log.Fatal(err)
}
defer file.Close()
Example (ListShares)

Example demonstrates how to list available shares on an SMB1 server

// Connect to SMB server
conn, err := net.DialTimeout("tcp", "192.168.1.100:445", 5*time.Second)
if err != nil {
	log.Fatal(err)
}
defer conn.Close()

// Setup authentication
d := &smb1.Dialer{
	Initiator: &smb1.NTLMInitiator{
		User:     "username",
		Password: "password",
	},
}

// Establish SMB session
session, err := d.Dial(conn)
if err != nil {
	log.Fatal(err)
}
defer session.Logoff()

// List available shares using RAP NetShareEnum
shares, err := session.ListSharenames()
if err != nil {
	log.Fatal(err)
}

// Display the shares
fmt.Println("Available shares:")
for _, share := range shares {
	// Filter out administrative shares if desired
	if share[len(share)-1] == '$' {
		continue // Skip hidden/admin shares
	}
	fmt.Printf("  - %s\n", share)
}

// Mount one of the shares
if len(shares) > 0 {
	// Find first non-admin share
	for _, shareName := range shares {
		if shareName != "IPC$" && shareName[len(shareName)-1] != '$' {
			share, err := session.Mount(shareName)
			if err != nil {
				log.Printf("Failed to mount %s: %v", shareName, err)
				continue
			}
			defer share.Umount()

			fmt.Printf("Successfully mounted: %s\n", shareName)
			break
		}
	}
}
Example (Logging)

Example demonstrates logging configuration with context

package main

import (
	"fmt"
)

func main() {
	// Note: In real code, define a logger type that implements smb1.Logger interface.
	// For this example, we just show the concept.

	// Logging is now context-based. To enable logging:
	// 1. Create a type that implements the smb1.Logger interface
	// 2. Attach it to context using smb1.WithLogger()
	// 3. Pass the context to SMB operations

	fmt.Println("Logging configured via context")
}
Output:
Logging configured via context
Example (RetryLogic)

Example demonstrates retry logic for transient errors

// The library does not retry on your behalf and does not reconnect: once a
// connection fails it stays failed, so a caller that needs durability owns
// the backoff and the redial. IsTemporary classifies which errors are worth
// another attempt.

// Simulated operation that might fail transiently
operation := func() error {
	// Your SMB operation here
	return nil
}

// Retry on temporary errors
err := operation()
if err != nil && smb1.IsTemporary(err) {
	// Back off and try again — or dial a fresh session, if the failure was
	// the connection itself (errors.Is(err, net.ErrClosed)).
	log.Printf("Temporary error, retrying: %v", err)
}

fmt.Println("Operation completed:", err == nil)
Output:
Operation completed: true

Index

Examples

Constants

View Source
const MaxReadSizeLimit = 0x100000 // deprecated constant
View Source
const PathSeparator = '\\'

PathSeparator is the path separator used in SMB paths.

Variables

View Source
var (
	// ErrPoolClosed is returned when attempting to get a connection from a closed pool.
	ErrPoolClosed = errors.New("connection pool is closed")

	// ErrPoolExhausted is returned when the pool has reached its maximum active connections
	// and no connections are available.
	ErrPoolExhausted = errors.New("connection pool exhausted")
)
View Source
var ErrBadPattern = errors.New("syntax error in pattern")

ErrBadPattern indicates a pattern was malformed.

View Source
var NORMALIZE_PATH = true

NORMALIZE_PATH controls whether '/' in user-supplied paths and patterns is converted to the SMB separator '\' before use. It is enabled by default; when disabled, paths containing '/' are rejected by validation instead of being rewritten. The variable mirrors go-smb2's of the same name for source compatibility.

Functions

func FileAttributesToUnixMode

func FileAttributesToUnixMode(attrs uint32) os.FileMode

FileAttributesToUnixMode converts Windows file attributes to Unix file mode. This provides a best-effort mapping from Windows attributes to Unix permissions.

func FileTimeToTime

func FileTimeToTime(ft uint64) time.Time

FileTimeToTime converts Windows FILETIME (100-nanosecond intervals since 1601-01-01) to Go time.Time.

func IsArchiveFile

func IsArchiveFile(attrs uint32) bool

IsArchiveFile returns true if the file has the archive bit set. This indicates the file has been modified since last backup.

func IsAuthError

func IsAuthError(err error) bool

IsAuthError returns true if the error is an authentication failure. This includes invalid credentials, access denied, and logon failures.

func IsExistError

func IsExistError(err error) bool

IsExistError returns true if the error indicates a file or object already exists.

func IsHiddenFile

func IsHiddenFile(name string, attrs uint32) bool

IsHiddenFile returns true if the file should be considered hidden. On Windows, this checks the HIDDEN attribute. On Unix systems, files starting with "." are considered hidden.

func IsNetworkError

func IsNetworkError(err error) bool

IsNetworkError returns true if the error is a network-level error. This includes connection failures, timeouts, and I/O errors.

func IsNotFoundError

func IsNotFoundError(err error) bool

IsNotFoundError returns true if the error indicates a file or object was not found. This includes file not found, path not found, and object name not found errors.

func IsPathSeparator

func IsPathSeparator(c uint8) bool

IsPathSeparator reports whether c is the SMB path separator.

func IsPermissionError

func IsPermissionError(err error) bool

IsPermissionError returns true if the error indicates a permission/access denied error.

func IsSystemFile

func IsSystemFile(attrs uint32) bool

IsSystemFile returns true if the file is a system file. On Windows, this checks the SYSTEM attribute.

func IsTemporary

func IsTemporary(err error) bool

IsTemporary returns true if the error is temporary and the operation can be retried. This includes network timeouts, temporary network errors, and transient SMB errors.

func IsTimeoutError

func IsTimeoutError(err error) bool

IsTimeoutError returns true if the error indicates a timeout.

func Match

func Match(pattern, name string) (matched bool, err error)

Match reports whether name matches the shell file name pattern. The pattern syntax is:

pattern:
	{ term }
term:
	'*'         matches any sequence of non-Separator characters
	'?'         matches any single non-Separator character
	'[' [ '^' ] { character-range } ']'
	            character class (must be non-empty)
	c           matches character c (c != '*', '?', '[')

character-range:
	c           matches character c (c != '-', ']')
	lo '-' hi   matches character c for lo <= c <= hi

The separator is the SMB path separator '\'. When NORMALIZE_PATH is enabled, '/' in the pattern reads as '\' and leading ".\" elements are dropped; the name is never rewritten.

Match requires pattern to match all of name, not just a substring. The only possible returned error is ErrBadPattern, when pattern is malformed.

func NormalizeShareName

func NormalizeShareName(shareName, serverAddr string) string

NormalizeShareName normalizes a share name for mounting. It accepts various formats and converts them to the canonical UNC format.

Accepted formats:

  • "ShareName" - just the share name
  • "\\server\ShareName" - full UNC path
  • "//server/ShareName" - Unix-style UNC path
  • "server\ShareName" - UNC without leading slashes
  • "server/ShareName" - Unix-style without leading slashes

Returns a normalized UNC path in the format: \\server\ShareName

func TimeToFileTime

func TimeToFileTime(t time.Time) uint64

TimeToFileTime converts Go time.Time to Windows FILETIME format. Returns a 64-bit value representing 100-nanosecond intervals since January 1, 1601 UTC.

func ToUnixPath

func ToUnixPath(path string) string

ToUnixPath converts a Windows-style path to Unix-style path. It replaces backslashes with forward slashes.

func ToWindowsPath

func ToWindowsPath(path string) string

ToWindowsPath converts a Unix-style path to Windows-style path. It replaces forward slashes with backslashes and normalizes the path.

func UnixModeToFileAttributes

func UnixModeToFileAttributes(mode os.FileMode) uint32

UnixModeToFileAttributes converts Unix file mode to Windows file attributes. This provides a best-effort mapping from Unix permissions to Windows attributes.

func ValidateShareName

func ValidateShareName(name string) error

ValidateShareName validates that a share name is valid for SMB. Valid share names:

  • Must not be empty
  • Must not contain invalid characters: / \ : * ? " < > |
  • Must not be longer than 80 characters (Windows limit)

func WithLogger

func WithLogger(ctx context.Context, logger Logger) context.Context

WithLogger returns a new context with the provided logger attached. The logger will be used by SMB operations that accept this context.

Types

type AuthenticationError

type AuthenticationError struct {
	User   string // Username that failed to authenticate
	Domain string // Domain (if applicable)
	Reason string // Human-readable reason for failure
}

AuthenticationError represents authentication failures. This provides more context about why authentication failed.

func (*AuthenticationError) Error

func (e *AuthenticationError) Error() string

type Client

type Client = Session // deprecated type name

type ConnectionError

type ConnectionError struct {
	Op  string // Operation that failed
	Err error  // Underlying error
}

ConnectionError represents connection-level errors. This includes TCP connection failures, disconnects, and I/O errors.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type ConnectionPool

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

ConnectionPool manages a pool of SMB connections for reuse. It provides connection pooling to improve performance for workloads with many short-lived operations by avoiding the overhead of repeatedly establishing TCP connections and performing authentication.

The pool is thread-safe and can be used concurrently from multiple goroutines.

Example usage:

pool := smb1.NewConnectionPool("192.168.1.100:445", dialer, nil)
defer pool.Close()

// Get a connection from the pool
conn, err := pool.Get(ctx)
if err != nil {
	return err
}
defer conn.Close() // Returns connection to pool

// Use the connection
share, err := conn.Mount("Public")
...

func NewConnectionPool

func NewConnectionPool(serverAddr string, dialer *Dialer, config *PoolConfig) *ConnectionPool

NewConnectionPool creates a new connection pool.

Parameters:

  • serverAddr: The server address (e.g., "192.168.1.100:445")
  • dialer: The Dialer with authentication credentials
  • config: Pool configuration (nil for defaults)

The pool starts a background goroutine to clean up idle connections. Call Close() when done to release resources and close all connections.

func (*ConnectionPool) Close

func (p *ConnectionPool) Close() error

Close closes the pool and all connections in it. After calling Close, the pool cannot be used.

func (*ConnectionPool) Get

Get retrieves a connection from the pool or creates a new one.

The returned PooledSession should be closed when done, which returns it to the pool for reuse. To actually close the connection, call ReallyClose().

If the pool is exhausted (MaxActive reached), Get will wait up to WaitTimeout for a connection to become available. If the context is canceled or the timeout expires, an error is returned.

func (*ConnectionPool) Stats

func (p *ConnectionPool) Stats() PoolStats

Stats returns statistics about the pool.

type ContextError

type ContextError struct {
	Err error
}

ContextError wraps a context cancellation or deadline error surfaced by a public API call, so that os.IsTimeout recognises deadline expiry. The type mirrors go-smb2's ContextError for source compatibility.

func (*ContextError) Error

func (e *ContextError) Error() string

func (*ContextError) Timeout

func (e *ContextError) Timeout() bool

Timeout reports whether the wrapped error is a deadline expiry. It checks the whole chain rather than go-smb2's identity comparison because the wrapped error may itself carry context (e.g. "send failed: context deadline exceeded").

func (*ContextError) Unwrap

func (e *ContextError) Unwrap() error

Unwrap exposes the underlying context error so errors.Is(err, context.Canceled) and IsTimeoutError keep working through the wrapper.

type Dialer

type Dialer struct {
	// MaxCreditBalance is unused for SMB1 (kept for API compatibility).
	// SMB1 does not use the credit-based flow control that SMB2+ uses.
	MaxCreditBalance uint16

	// Negotiator is unused for SMB1 (kept for API compatibility).
	// SMB1 negotiation is always performed automatically.
	Negotiator Negotiator

	// Initiator is required for authentication.
	// Use NTLMInitiator for NTLM v2 authentication.
	Initiator Initiator
}

Dialer contains options for establishing an SMB1 session.

API Compatibility Note: MaxCreditBalance and Negotiator are kept for go-smb2 API compatibility, but are not used in SMB1 (SMB1 doesn't have credit-based flow control).

func (*Dialer) Dial

func (d *Dialer) Dial(tcpConn net.Conn) (*Session, error)

Dial performs protocol negotiation and authentication on the provided TCP connection.

The tcpConn should already be connected to the SMB server (typically port 445 or 139). This method wraps the connection with NetBIOS framing, negotiates the SMB1 protocol, and performs NTLM authentication.

Important: This implementation doesn't support NetBIOS transport on port 139 yet. Use direct SMB over TCP on port 445 instead.

The returned Session uses context.Background() as its default context (with no logger attached). To attach a logger or custom context, use DialContext instead.

Example:

conn, err := net.Dial("tcp", "192.168.1.100:445")
if err != nil {
	return err
}
defer conn.Close()

d := &smb1.Dialer{
	Initiator: &smb1.NTLMInitiator{
		User:     "username",
		Password: "password",
		Domain:   "WORKGROUP",
	},
}

session, err := d.Dial(conn)
if err != nil {
	return err
}
defer session.Logoff()

func (*Dialer) DialContext

func (d *Dialer) DialContext(ctx context.Context, tcpConn net.Conn) (*Session, error)

DialContext performs negotiation and authentication using the provided context.

The context is used for the negotiation and authentication phases only. Any logger attached to the context will be extracted and used by the returned Session, but timeouts and cancellations are not inherited.

If you want to change the session's context (e.g., to add timeouts), call Session.WithContext() to create a new session with a different context.

The context can be used to set timeouts or cancel the dial operation:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

session, err := d.DialContext(ctx, conn)

type File

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

File represents an open file on an SMB share. It provides methods for reading, writing, and seeking within the file. File implements io.Reader, io.Writer, io.Seeker, io.ReaderAt, and io.WriterAt.

func (*File) Chmod

func (f *File) Chmod(mode os.FileMode) error

Chmod changes the mode of the open file, with the same semantics as Share.Chmod. If there is an error, it will be of type *os.PathError.

The attribute set normally goes out as TRANS2_SET_FILE_INFORMATION on the open handle; legacy servers that reject that with STATUS_NOT_SUPPORTED get the core-protocol SMB_COM_SET_INFORMATION command instead, which is path-based — it addresses the file by the path it was opened with rather than by handle — with the same attribute semantics.

func (*File) Close

func (f *File) Close() error

Close closes the File, rendering it unusable for I/O. It returns an error, if any.

func (*File) Name

func (f *File) Name() string

Name returns the name of the file.

func (*File) Read

func (f *File) Read(b []byte) (n int, err error)

Read reads up to len(b) bytes from the File. It returns the number of bytes read and any error encountered. At end of file, Read returns 0, io.EOF. For large reads (>= 128KB), this method uses request pipelining for improved performance.

func (*File) ReadAt

func (f *File) ReadAt(b []byte, off int64) (n int, err error)

ReadAt reads len(b) bytes from the File starting at byte offset off. It returns the number of bytes read and the error, if any. ReadAt always returns a non-nil error when n < len(b). At end of file, that error is io.EOF.

func (*File) ReadFrom

func (f *File) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom implements io.ReaderFrom: it reads from r until EOF and writes the data to f through the ordinary Write path in buffered chunks. Unlike go-smb2, no server-side copy is attempted when r is another File; the data always streams through the client.

func (*File) Readdir

func (f *File) Readdir(n int) ([]os.FileInfo, error)

Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by Lstat, in directory order. Subsequent calls on the same file will yield further FileInfos.

If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF.

If n <= 0, Readdir returns all the FileInfo from the directory in a single slice. In this case, if Readdir succeeds (reads all the way to the end of the directory), it returns the slice and a nil error. If it encounters an error before the end of the directory, Readdir returns the FileInfo read until that point and a non-nil error.

Note: This implementation uses the file's offset to track pagination state. Calling Seek() on the file will reset the directory reading position. Also note that this only works if the file was opened as a directory.

func (*File) Readdirnames

func (f *File) Readdirnames(n int) (names []string, err error)

Readdirnames reads the contents of the directory associated with file and returns a slice of up to n names of files in the directory, in directory order. Paging behaves exactly as Readdir: subsequent calls on the same file yield further names, and with n > 0 the error at the end of the directory is io.EOF (possibly alongside the final page of names).

func (*File) Seek

func (f *File) Seek(offset int64, whence int) (int64, error)

Seek sets the offset for the next Read or Write on file to offset, interpreted according to whence: 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end. It returns the new offset and an error, if any.

func (*File) Stat

func (f *File) Stat() (os.FileInfo, error)

Stat returns the FileInfo structure describing file. If there is an error, it will be of type *os.PathError.

func (*File) Statfs

func (f *File) Statfs() (FileFsInfo, error)

Statfs returns the capacity of the filesystem backing the file's share, queried over the tree the file was opened on. If there is an error, it will be of type *os.PathError.

func (*File) Sync

func (f *File) Sync() error

Sync commits the current contents of the file to stable storage. Typically, this means flushing the file system's in-memory copy of recently written data to disk.

func (*File) Truncate

func (f *File) Truncate(size int64) error

Truncate changes the size of the file. It does not change the I/O offset. If there is an error, it will be of type *os.PathError.

func (*File) Write

func (f *File) Write(b []byte) (n int, err error)

Write writes len(b) bytes to the File. It returns the number of bytes written and an error, if any. Write returns a non-nil error when n != len(b).

func (*File) WriteAt

func (f *File) WriteAt(b []byte, off int64) (n int, err error)

WriteAt writes len(b) bytes to the File starting at byte offset off. It returns the number of bytes written and an error, if any. WriteAt returns a non-nil error when n != len(b).

func (*File) WriteString

func (f *File) WriteString(s string) (n int, err error)

WriteString is like Write, but writes the contents of string s rather than a slice of bytes.

func (*File) WriteTo

func (f *File) WriteTo(w io.Writer) (n int64, err error)

WriteTo implements io.WriterTo: it reads f to EOF through the ordinary Read path and writes the data to w in buffered chunks. Like ReadFrom this is plain client-side streaming, not a server-side copy.

type FileFsInfo

type FileFsInfo interface {
	BlockSize() uint64
	FragmentSize() uint64
	TotalBlockCount() uint64
	FreeBlockCount() uint64
	AvailableBlockCount() uint64
}

FileFsInfo describes the capacity of the filesystem backing a mounted share. The interface and its field mapping match go-smb2's FileFsInfo: BlockSize is the bytes per sector, FragmentSize is the sectors per allocation unit, and the three counts are in allocation units — so the volume's total capacity in bytes is TotalBlockCount() * FragmentSize() * BlockSize().

type FileStat

type FileStat struct {
	CreationTime   time.Time
	LastAccessTime time.Time
	LastWriteTime  time.Time
	ChangeTime     time.Time
	EndOfFile      int64  // File size
	AllocationSize int64  // Allocated size on disk
	FileAttributes uint32 // SMB file attributes
	FileName       string // File name
}

FileStat implements os.FileInfo for SMB1 files. It contains file metadata returned by SMB1 file operations.

func (*FileStat) IsDir

func (fs *FileStat) IsDir() bool

IsDir reports whether the file is a directory.

func (*FileStat) ModTime

func (fs *FileStat) ModTime() time.Time

ModTime returns the last write time.

func (*FileStat) Mode

func (fs *FileStat) Mode() os.FileMode

Mode returns the file mode and permission bits. SMB file attributes are converted to Unix-style permissions.

func (*FileStat) Name

func (fs *FileStat) Name() string

Name returns the base name of the file.

func (*FileStat) Size

func (fs *FileStat) Size() int64

Size returns the file size in bytes.

func (*FileStat) Sys

func (fs *FileStat) Sys() interface{}

Sys returns the underlying data source (returns self).

type Initiator

type Initiator interface {
	// contains filtered or unexported methods
}

Initiator is the interface for session authentication. It follows the GSS-API pattern used by go-smb2 for API compatibility.

type InternalError

type InternalError struct {
	Message string
}

InternalError indicates a client-side error (not a server error). This usually indicates a programming error or configuration issue.

func (*InternalError) Error

func (e *InternalError) Error() string

type InvalidResponseError

type InvalidResponseError struct {
	Message string
}

InvalidResponseError indicates the server sent a malformed response. This usually indicates a protocol error or incompatible server.

func (*InvalidResponseError) Error

func (e *InvalidResponseError) Error() string

type Logger

type Logger = logging.Logger

Logger is the interface for logging. Applications can provide their own logger implementation for custom logging behavior.

func LoggerFromContext

func LoggerFromContext(ctx context.Context) Logger

LoggerFromContext retrieves the logger from the context. If no logger is attached to the context, it returns a no-op logger.

type NTLMInitiator

type NTLMInitiator struct {
	// User is the username for authentication.
	// Required field.
	User string

	// Password is the plain text password.
	// Use either Password or Hash, not both.
	Password string

	// Hash is the pre-computed NTLM password hash (16 bytes).
	// Use either Password or Hash, not both.
	// Useful for pass-the-hash attacks or when password is not available.
	Hash []byte

	// Domain is the Windows domain or workgroup name.
	// Defaults to "WORKGROUP" if empty.
	Domain string

	// Workstation is the client workstation name.
	// Defaults to "localhost" if empty.
	Workstation string

	// TargetSPN is the Service Principal Name for the target server.
	// Format: "service/hostname[:port]" (e.g., "cifs/server:445")
	// Optional field.
	TargetSPN string
	// contains filtered or unexported fields
}

NTLMInitiator implements NTLM v2 authentication for SMB1 sessions. It provides a public wrapper around the internal NTLM client.

You can use either Password or Hash for authentication:

  • Password: plain text password (will be hashed internally)
  • Hash: pre-computed NTLM hash (16 bytes, NTOWFv2)

Example:

initiator := &smb1.NTLMInitiator{
	User:     "username",
	Password: "password",
	Domain:   "WORKGROUP",
}

type Negotiator

type Negotiator struct {
	RequireMessageSigning bool     // ignored for SMB1
	ClientGuid            [16]byte // ignored for SMB1
	SpecifiedDialect      uint16   // ignored for SMB1
}

Negotiator contains options for protocol negotiation, mirroring go-smb2's type of the same name for source compatibility. SMB1 ignores all fields: negotiation is always performed automatically, this client does not negotiate signing, SMB1 negotiation carries no client GUID, and the dialect is always NT LM 0.12.

type PoolConfig

type PoolConfig struct {
	// MaxIdle is the maximum number of idle connections to keep in the pool.
	// Zero means no idle connections are kept (every Get creates a new connection).
	// Default is 5.
	MaxIdle int

	// MaxActive is the maximum number of active connections (idle + in-use).
	// Zero means no limit on active connections.
	// Default is 10.
	MaxActive int

	// IdleTimeout is the maximum time an idle connection can remain in the pool
	// before being closed. Zero means no timeout (idle connections never expire).
	// Default is 5 minutes.
	IdleTimeout time.Duration

	// WaitTimeout is the maximum time to wait for a connection when the pool is exhausted.
	// Zero means fail immediately if no connection is available.
	// Default is 30 seconds.
	WaitTimeout time.Duration

	// HealthCheck is an optional callback function to verify connection health before reuse.
	// If provided, it will be called before returning a connection from the idle pool.
	// If the health check returns false or an error, the connection is closed and a new one is created.
	// If nil, connections are assumed to be alive (default behavior for backward compatibility).
	//
	// Example health check that sends an Echo command:
	//
	//	HealthCheck: func(s *Session) (bool, error) {
	//		// Try to send an Echo command with a timeout
	//		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	//		defer cancel()
	//		err := s.WithContext(ctx).sendEcho()
	//		return err == nil, err
	//	}
	//
	// Performance note: Health checks add latency to connection acquisition from the pool.
	// Use with caution for latency-sensitive workloads.
	HealthCheck func(*Session) (bool, error)
}

PoolConfig contains configuration options for a ConnectionPool.

func DefaultPoolConfig

func DefaultPoolConfig() *PoolConfig

DefaultPoolConfig returns a PoolConfig with recommended default values.

type PoolStats

type PoolStats struct {
	// Idle is the number of idle connections in the pool.
	Idle int

	// Active is the total number of active connections (idle + in-use).
	Active int

	// Closed indicates whether the pool has been closed.
	Closed bool
}

PoolStats contains statistics about a ConnectionPool.

type PooledSession

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

PooledSession wraps a Session obtained from a ConnectionPool. When Close() is called, the session is returned to the pool for reuse. To actually close the connection, use ReallyClose().

func (*PooledSession) Close

func (ps *PooledSession) Close() error

Close returns the connection to the pool instead of closing it. The session can be reused by another caller.

func (*PooledSession) ListSharenames

func (ps *PooledSession) ListSharenames() ([]string, error)

ListSharenames is a convenience method that calls ListSharenames on the underlying Session.

func (*PooledSession) Logoff

func (ps *PooledSession) Logoff() error

Logoff is a convenience method that calls ReallyClose(). It's provided for API compatibility but actually closes the connection rather than returning it to the pool.

func (*PooledSession) Mount

func (ps *PooledSession) Mount(sharename string) (*Share, error)

Mount is a convenience method that calls Mount on the underlying Session.

func (*PooledSession) ReallyClose

func (ps *PooledSession) ReallyClose() error

ReallyClose closes the connection and removes it from the pool. Use this when you want to permanently close a connection (e.g., on error).

func (*PooledSession) Session

func (ps *PooledSession) Session() *Session

Session returns the underlying Session for use. The returned Session should not be closed directly - use PooledSession.Close() instead.

func (*PooledSession) WithContext

func (ps *PooledSession) WithContext(ctx context.Context) *Session

WithContext is a convenience method that calls WithContext on the underlying Session.

type RemoteFile

type RemoteFile = File // deprecated type name

type RemoteFileStat

type RemoteFileStat = FileStat // deprecated type name

type RemoteFileSystem

type RemoteFileSystem = Share // deprecated type name

type ResponseError

type ResponseError struct {
	Code uint32 // NT_STATUS code
}

ResponseError wraps an NT_STATUS error code from the server. The Code field contains the raw NT_STATUS value. The underlying error string describes the error condition.

func (*ResponseError) Error

func (e *ResponseError) Error() string

type SMBError

type SMBError struct {
	Status  uint32 // NT_STATUS code
	Command uint8  // SMB command that failed
	Message string // Human-readable message
}

SMBError represents an SMB protocol error with detailed context. It provides more information than ResponseError including the command that failed and a human-readable message.

func (*SMBError) Error

func (e *SMBError) Error() string

func (*SMBError) Unwrap

func (e *SMBError) Unwrap() error

Unwrap returns the underlying ResponseError for errors.Is/As support.

type ServerCapabilities

type ServerCapabilities struct {
	// MaxMpxCount is the maximum number of outstanding (pipelined) requests
	// the server can handle concurrently. A value of 0 or 1 means the server
	// does not support request pipelining.
	MaxMpxCount uint16

	// MaxBufferSize is the maximum size of an SMB message (in bytes) that the
	// server can receive. This limits the size of individual read/write requests.
	MaxBufferSize uint32

	// ServerName is the NetBIOS name of the server.
	ServerName string

	// DomainName is the domain or workgroup name the server belongs to.
	DomainName string

	// SupportsPipelining indicates whether the server supports concurrent
	// (pipelined) requests. This is true when MaxMpxCount > 1.
	SupportsPipelining bool

	// EffectivePipelineDepth is the actual pipeline depth that will be used
	// by this client implementation, capped at 50 for safety.
	EffectivePipelineDepth int
}

ServerCapabilities contains information about the SMB server's capabilities as negotiated during the protocol handshake.

func (ServerCapabilities) String

func (c ServerCapabilities) String() string

String returns a human-readable representation of the server capabilities.

type ServerTime

type ServerTime struct {
	// Time is the server's system time in UTC at the moment the server built
	// the negotiate response. It is the zero time if the server did not
	// report a valid clock.
	Time time.Time

	// TimeZoneOffsetMinutes is the server's time zone offset in minutes from
	// UTC, taken verbatim from the ServerTimeZone field of the negotiate
	// response. Time is already in UTC; this value only describes the
	// server's local time zone.
	TimeZoneOffsetMinutes int16

	// ReceivedAt is the local time at which the negotiate response was
	// received. Comparing it against Time gives the offset between the
	// server clock and the local clock as of the protocol handshake.
	ReceivedAt time.Time
}

ServerTime describes the server's clock as reported in the SMB negotiate response. It allows callers to compare the server clock against the local clock, e.g. to compute a clock offset as Time.Sub(ReceivedAt).

type Session

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

Session represents an authenticated SMB1 session. A session is created after successful protocol negotiation and authentication.

The session is used to mount shares (Mount) and can be closed with Logoff. A single session can have multiple shares mounted simultaneously.

func (*Session) Capabilities

func (s *Session) Capabilities() ServerCapabilities

Capabilities returns information about the negotiated server capabilities. This includes details about request pipelining support, buffer sizes, and server identification.

func (*Session) ListSharenames

func (c *Session) ListSharenames() ([]string, error)

ListSharenames enumerates available shares on the server.

This method first attempts to use the RAP (Remote Administration Protocol) NetShareEnum command for maximum compatibility with SMB1 servers. If RAP is not supported (STATUS_NOT_SUPPORTED), it falls back to the more modern RPC/SRVSVC NetShareEnumAll method.

The returned list includes share names but not administrative metadata. Hidden shares (ending with $) and administrative shares (IPC$, ADMIN$, etc.) are included in the results.

Example:

shares, err := session.ListSharenames()
if err != nil {
	log.Fatal(err)
}
for _, share := range shares {
	fmt.Println(share)
}

Limitations:

  • Returns only share names, not types or comments
  • Requires IPC$ access (typically granted to all authenticated users)

func (*Session) Logoff

func (c *Session) Logoff() error

Logoff terminates the SMB session and closes the connection.

After calling Logoff, the session and any mounted shares should not be used. All open files are closed automatically by the server when the session ends.

It's recommended to call Logoff in a defer statement:

session, err := dialer.Dial(conn)
if err != nil {
	return err
}
defer session.Logoff()

func (*Session) Mount

func (c *Session) Mount(sharename string) (*Share, error)

Mount connects to a share on the server.

The sharename can be specified in multiple formats:

  • "ShareName" - just the share name (server name is added automatically)
  • "\\server\ShareName" - full UNC path
  • "server\ShareName" - UNC path without leading backslashes

The server's NetBIOS name is automatically added if not present in the sharename. Note: SMB1 requires the NetBIOS name, not the IP address, in UNC paths.

Examples:

// These are equivalent (assuming server name is "FILESERVER"):
share, err := session.Mount("Public")
share, err := session.Mount("\\FILESERVER\Public")
share, err := session.Mount("FILESERVER\Public")

The returned Share inherits the Session's context (including any logger). Call Share.WithContext() if you need to use a different context for the share.

func (*Session) ServerTime

func (s *Session) ServerTime() ServerTime

ServerTime returns the server clock information captured during protocol negotiation. The values are recorded once at dial time and do not change for the lifetime of the session.

Example (computing the server/local clock offset right after dialing):

st := session.ServerTime()
offset := st.Time.Sub(st.ReceivedAt)

func (*Session) WithContext

func (c *Session) WithContext(ctx context.Context) *Session

WithContext returns a new Session that uses the provided context as its default. If ctx is nil, it returns nil and the original Session remains unmodified.

The original Session is not modified - a new Session struct is returned that shares the same underlying connection and authentication but uses a different default context.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

sessionWithTimeout := session.WithContext(ctx)
share, err := sessionWithTimeout.Mount("Share")

type Share

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

Share represents a mounted SMB share (tree connection). The share provides file system operations like opening files, creating directories, and listing directory contents.

func (*Share) Chmod

func (fs *Share) Chmod(name string, mode os.FileMode) error

Chmod changes the mode of the named file, mimicking os.Chmod within what SMB attributes can express: when the owner-write bit (0200) is absent the file becomes read-only, otherwise read-only is cleared. All other attributes are preserved via a read-modify-write of the current attribute set, matching go-smb2's behavior. If there is an error, it will be of type *os.PathError.

The attribute set normally goes out as TRANS2_SET_PATH_INFORMATION at the SMB_SET_FILE_BASIC_INFO level; legacy servers that reject that with STATUS_NOT_SUPPORTED get the core-protocol SMB_COM_SET_INFORMATION command instead, with the same attribute semantics.

func (*Share) Chtimes

func (fs *Share) Chtimes(name string, atime time.Time, mtime time.Time) error

Chtimes changes the access and modification times of the named file, mimicking os.Chtimes. If there is an error, it will be of type *os.PathError.

It is implemented with TRANS2_SET_PATH_INFORMATION at the SMB_SET_FILE_BASIC_INFO level, so the file is not opened. A zero time value leaves the corresponding timestamp unchanged: SMB1 encodes it as FILETIME 0, which the protocol defines as "do not change".

Servers are free to round the stored times to their filesystem's resolution, and some (Samba among them) do not persist access times at all; the modification time is the one callers can rely on reading back.

func (*Share) CopyFile

func (fs *Share) CopyFile(src, dst string) error

CopyFile copies a file within the share from src to dst. If dst already exists, it will be overwritten. The file contents are copied by reading from src and writing to dst. Both paths must be relative paths within the share.

func (*Share) CopyFrom

func (fs *Share) CopyFrom(localPath, remotePath string) error

CopyFrom uploads a file from the local filesystem to the share. The localPath is a path on the local filesystem, and remotePath is a relative path within the share. If remotePath already exists, it will be overwritten.

func (*Share) CopyTo

func (fs *Share) CopyTo(remotePath, localPath string) error

CopyTo downloads a file from the share to the local filesystem. The remotePath is a relative path within the share, and localPath is a path on the local filesystem. If localPath already exists, it will be overwritten.

func (*Share) Create

func (fs *Share) Create(name string) (*File, error)

Create creates or truncates the named file. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0666. If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR. If there is an error, it will be of type *os.PathError.

func (*Share) DirFS

func (s *Share) DirFS(dirname string) fs.FS

DirFS returns an fs.FS presenting the share subtree rooted at dirname as a read-only file system. dirname is a share path in the same form the other Share methods accept ('\' or, with NORMALIZE_PATH, '/' separators); "" and "." both denote the share root.

Names passed to the returned file system follow the io/fs rules instead: '/'-separated, rooted at dirname, validated with fs.ValidPath, with "." naming the root. Errors are *fs.PathError values carrying those io/fs names, and errors.Is against fs.ErrNotExist, fs.ErrInvalid, etc. works as io/fs documents.

Matching go-smb2, the result also implements fs.StatFS, fs.ReadFileFS and fs.GlobFS (but not fs.ReadDirFS or fs.SubFS; fs.ReadDir and fs.Sub fall back to Open), and files opened on a directory implement fs.ReadDirFile.

func (*Share) Exists

func (fs *Share) Exists(name string) (bool, error)

Exists checks if a file or directory exists at the given path. It returns true if the file/directory exists, false if it does not exist. If there is an error other than "not found", it returns false and the error.

func (*Share) Glob

func (fs *Share) Glob(pattern string) (matches []string, err error)

Glob returns the names of all files matching pattern, or nil if there is no matching file. The pattern syntax is that of Match, and matches are returned in lexical order. Glob ignores file system errors such as directories that cannot be read; the only possible returned error is ErrBadPattern, when pattern is malformed.

Unlike filepath.Glob, results use '\' as the separator, and patterns are relative to the share root (a leading '\' matches nothing, since this library rejects absolute paths).

func (*Share) IsDir

func (fs *Share) IsDir(name string) (bool, error)

IsDir checks if the given path exists and is a directory. It returns true if the path exists and is a directory. If the path does not exist or is not a directory, it returns false. If there is an error checking the path, it returns false and the error.

func (*Share) Lstat

func (fs *Share) Lstat(name string) (os.FileInfo, error)

Lstat returns a FileInfo describing the named file. If the file is a symbolic link, the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link. If there is an error, it will be of type *os.PathError.

Note: SMB1 doesn't have separate lstat semantics, so this is the same as Stat.

func (*Share) Mkdir

func (fs *Share) Mkdir(name string, perm os.FileMode) error

Mkdir creates a new directory with the specified name and permission bits (before umask). If there is an error, it will be of type *os.PathError.

func (*Share) MkdirAll

func (fs *Share) MkdirAll(path string, perm os.FileMode) error

MkdirAll creates a directory named path, along with any necessary parents, and returns nil, or else returns an error. The permission bits perm (before umask) are used for all directories that MkdirAll creates. If path is already a directory, MkdirAll does nothing and returns nil.

func (*Share) Open

func (fs *Share) Open(name string) (*File, error)

Open opens the named file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY. If there is an error, it will be of type *os.PathError.

func (*Share) OpenFile

func (fs *Share) OpenFile(name string, flag int, perm os.FileMode) (*File, error)

OpenFile is the generalized open call; most users will use Open or Create instead. It opens the named file with specified flag (O_RDONLY etc.) and perm (before umask), if applicable. If successful, methods on the returned File can be used for I/O. If there is an error, it will be of type *os.PathError.

func (*Share) ReadDir

func (fs *Share) ReadDir(dirname string) ([]os.FileInfo, error)

ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.

func (*Share) ReadFile

func (fs *Share) ReadFile(filename string) ([]byte, error)

ReadFile reads the file named by filename and returns the contents. A successful call returns err == nil, not err == EOF. Because ReadFile reads the whole file, it does not treat an EOF from Read as an error to be reported.

Stat only sizes the initial buffer. Read is an io.Reader and may legally return fewer bytes than requested without an error, so the loop below runs to EOF and grows the buffer rather than trusting either the stat size or a single Read to have delivered the whole file.

Prefer Open plus io.CopyBuffer (or CopyTo) for large files: ReadFile holds the entire contents in memory.

func (*Share) Readdir

func (fs *Share) Readdir(dirname string, n int) ([]os.FileInfo, error)

Readdir reads the directory named by dirname and returns a list of up to n directory entries.

If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF.

If n <= 0, Readdir returns all the FileInfo from the directory in a single slice. In this case, if Readdir succeeds (reads all the way to the end of the directory), it returns the slice and a nil error. If it encounters an error before the end of the directory, Readdir returns the FileInfo read until that point and a non-nil error.

Note: This implementation does not maintain state between calls. When n > 0, it reads all entries and returns the first n, then returns io.EOF on subsequent calls. For proper pagination with state tracking, use the standard library's fs.ReadDir or open the directory as a File and call File.Readdir.

func (fs *Share) Readlink(name string) (string, error)

Readlink mimics os.Readlink's signature for go-smb2 parity, but SMB1 has no symbolic link operation — see Symlink — so it always returns an *os.PathError wrapping errors.ErrUnsupported.

func (*Share) Remove

func (fs *Share) Remove(name string) error

Remove removes the named file or (empty) directory. If there is an error, it will be of type *os.PathError.

func (*Share) RemoveAll

func (fs *Share) RemoveAll(path string) error

RemoveAll removes path and any children it contains. It removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil (no error).

func (*Share) Rename

func (fs *Share) Rename(oldpath, newpath string) error

Rename renames (moves) oldpath to newpath. Files and directories can be renamed within a directory or moved between directories on the same share.

Unlike os.Rename, Rename does not replace an existing newpath: the underlying SMB_COM_RENAME command fails with STATUS_OBJECT_NAME_COLLISION if newpath already exists (use IsExistError to detect this case). This matches go-smb2, whose Rename also does not overwrite an existing target. If there is an error, it will be of type *os.LinkError.

func (*Share) Stat

func (fs *Share) Stat(name string) (os.FileInfo, error)

Stat returns a FileInfo describing the named file. If there is an error, it will be of type *os.PathError.

func (*Share) Statfs

func (fs *Share) Statfs(name string) (FileFsInfo, error)

Statfs returns the capacity of the filesystem backing the share. If there is an error, it will be of type *os.PathError.

The name argument exists for go-smb2 signature parity and is validated and normalized like every other path argument (the empty string addresses the share root, as go-smb2 allows), but SMB1's TRANS2_QUERY_FS_INFORMATION is share-wide — the query is the same whichever path is named, so the name does not reach the wire.

It prefers SMB_QUERY_FS_SIZE_INFO, whose unit counts are 64-bit, and falls back to the legacy SMB_INFO_ALLOCATION level when the server does not support it — the same accommodation ListSharenames makes for servers that lack RAP. The legacy level's counts are 32-bit and so cannot describe a volume beyond roughly 4 billion allocation units.

func (fs *Share) Symlink(target, linkpath string) error

Symlink mimics os.Symlink's signature for go-smb2 parity, but SMB1 has no symbolic link operation — the reparse-point FSCTLs go-smb2 uses are SMB2 constructs — so it always returns an *os.LinkError wrapping errors.ErrUnsupported.

func (*Share) Truncate

func (fs *Share) Truncate(name string, size int64) error

Truncate changes the size of the named file. It opens the file write-only, resizes it, and closes it; the file must already exist. A negative size returns os.ErrInvalid; other errors are of type *os.PathError.

func (*Share) Umount

func (fs *Share) Umount() error

Umount disconnects from the share (sends TREE_DISCONNECT).

After calling Umount, the share should not be used. All open files on this share are closed automatically by the server.

It's recommended to call Umount in a defer statement:

share, err := session.Mount("Public")
if err != nil {
	return err
}
defer share.Umount()

func (*Share) VolumeInfo

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

VolumeInfo returns the identity of the volume backing the share.

The serial number answers "is this the same physical media I saw last time" without writing a marker file to the share — useful where the share is removable and deleting from the wrong one would be destructive.

func (*Share) Walk

func (fs *Share) Walk(root string, walkFn filepath.WalkFunc) error

Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root. All errors that arise visiting files and directories are filtered by walkFn.

Walk follows the same semantics as filepath.Walk:

  • The files are walked in lexical order
  • Walk does not follow symbolic links
  • If walkFn returns filepath.SkipDir when invoked on a directory, Walk skips the directory's contents
  • If walkFn returns any other non-nil error, Walk stops immediately

Note: Unlike filepath.Walk, paths passed to walkFn use backslashes (\) as the separator, consistent with SMB conventions.

func (*Share) WithContext

func (fs *Share) WithContext(ctx context.Context) *Share

WithContext returns a new Share that uses the provided context as its default. If ctx is nil, it returns nil and the original Share remains unmodified.

The original Share is not modified - a new Share struct is returned that uses the same underlying tree connection but with a different default context.

func (*Share) WriteFile

func (fs *Share) WriteFile(filename string, data []byte, perm os.FileMode) error

WriteFile writes data to a file named by filename. If the file does not exist, WriteFile creates it with permissions perm (before umask); otherwise WriteFile truncates it before writing.

type TransportError

type TransportError struct {
	Err error
}

TransportError represents an error coming from the net.Conn layer, such as a dropped TCP connection or a socket read/write failure. The type and its message format mirror go-smb2's TransportError for source compatibility.

func (*TransportError) Error

func (e *TransportError) Error() string

func (*TransportError) Unwrap

func (e *TransportError) Unwrap() error

Unwrap exposes the underlying failure so errors.Is/As and predicates like IsNetworkError keep classifying wrapped errors. (go-smb2's TransportError has no Unwrap; ours does so the existing error chains stay intact.)

type VolumeInfo

type VolumeInfo struct {
	// SerialNumber is the volume serial number assigned at format time.
	SerialNumber uint32

	// Label is the volume label. It is frequently empty, and a server may
	// decline to report it while still reporting a serial number.
	Label string

	// CreationTime is when the volume was created. Servers whose filesystem
	// does not record this report a zero time.
	CreationTime time.Time
}

VolumeInfo identifies the volume backing a mounted share.

SerialNumber is assigned when the volume is formatted, so it distinguishes one piece of removable media from another without writing anything to it. It is not a strong identifier: it is not unique across the world, and reformatting changes it.

Directories

Path Synopsis
examples
advanced command
basic command
dirops command
fileops command
listshares command
pool command
uploaddownload command
internal
netbios
Package netbios implements the NetBIOS session service layer for SMB1 protocol.
Package netbios implements the NetBIOS session service layer for SMB1 protocol.
smb1
Package smb1 implements the SMB1/CIFS protocol layer.
Package smb1 implements the SMB1/CIFS protocol layer.

Jump to

Keyboard shortcuts

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