go-smb2

repository·master·Indexed 19 days ago

https://github.com/hirochachacha/go-smb2

A Go implementation of the SMB2 and SMB3 protocols that allows developers to interact with SMB shares as a client. It provides a VFS-like interface for file operations, including support for NTLM authentication, recursive directory creation via MkdirAll, and integration with the standard io/fs package through Share.DirFS. The library supports standard Go I/O interfaces (io.Reader, io.Writer, io.Seeker) and integrates with the os package for error checking.

Tokens
7K
Snippets
28
Records
34
Agent score
64%

What's inside go-smb2

  1. How SMB2 negotiation and connection establishment works

    master

    The negotiation process involves creating a NegotiateRequest based on the Negotiator settings, sending it to the server, and processing the NegotiateResponse.

    Key behaviors during negotiation:

    1. Security Mode: Based on RequireMessageSigning, the request sets SMB2_NEGOTIATE_SIGNING_REQUIRED or SMB2_NEGOTIATE_SIGNING_ENABLED.
    2. Dialect Selection: If a SpecifiedDialect is provided, the client attempts to connect using only that dialect. If the server returns a different dialect (specifically if it returns SMB2), the client may retry with SMB210.
    3. Contexts: For modern dialects (like SMB 3.1.1), the negotiator handles NegotiateContext for pre-authentication integrity (e.g., SHA512) and encryption capabilities (e.g., AES128GCM).
    4. Capabilities: The resulting connection inherits capabilities from the server response, such as maxTransactSize, maxReadSize, and maxWriteSize.
  2. Perform efficient server-side file copies with ReadFrom and WriteTo

    master

    The *File type implements io.ReaderFrom and io.WriterTo. If you are copying between two *File objects that belong to the same SMB2 Share, the library attempts to use server-side copy operations (FSCTL_SRV_REQUEST_RESUME_KEY and FSCTL_SRV_COPYCHUNK) instead of pulling data through the client. This significantly improves performance for large files.

    // If both are *File on the same share, this uses server-side copy
    n, err := fileSrc.WriteTo(fileDest)
  3. Check SMB error types using standard library functions

    master

    The go-smb2 library integrates with Go's standard os error checking. You can use os.IsNotExist(err), os.IsExist(err), and os.IsPermission(err) to inspect errors returned by filesystem operations. Additionally, you can use fs.WithContext(ctx) to perform operations with context support, allowing you to detect timeouts via os.IsTimeout(err).

    package main
    
    import (
    	"context"
    	"fmt"
    	"net"
    	"os"
    
    	"github.com/hirochachacha/go-smb2"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "SERVERNAME:445")
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    
    	d := &smb2.Dialer{
    		Initiator: &smb2.NTLMInitiator{
    			User:     "USERNAME",
    			Password: "PASSWORD",
    		},
    	}
    
    	s, err := d.Dial(conn)
    	if err != nil {
    		panic(err)
    	}
    	defer s.Logoff()
    
    	fs, err := s.Mount("SHARENAME")
    	if err != nil {
    		panic(err)
    	}
    	defer fs.Umount()
    
    	_, err = fs.Open("notExist.txt")
    
    	fmt.Println(os.IsNotExist(err)) // true
    	fmt.Println(os.IsExist(err))    // false
    
    	fs.WriteFile("hello2.txt", []byte("test"), 0444)
    	err = fs.WriteFile("hello2.txt", []byte("test2"), 0444)
    	fmt.Println(os.IsPermission(err)) // true
    
    	ctx, cancel := context.WithTimeout(context.Background(), 0)
    	defer cancel()
    
    	_, err = fs.WithContext(ctx).Open("hello.txt")
    
    	fmt.Println(os.IsTimeout(err)) // true
    }
  4. Perform file manipulation on an SMB share

    master

    To manipulate files, mount a specific share using s.Mount("SHARENAME") to obtain an fs object. You can then use standard file operations such as fs.Create(), f.Write(), f.Seek(), and fs.Remove(). Always remember to Umount() the filesystem and Logoff() the session.

    package main
    
    import (
    	"io"
    	"io/ioutil"
    	"net"
    
    	"github.com/hirochachacha/go-smb2"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "SERVERNAME:445")
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    
    	d := &smb2.Dialer{
    		Initiator: &smb2.NTLMInitiator{
    			User:     "USERNAME",
    			Password: "PASSWORD",
    		},
    	}
    
    	s, err := d.Dial(conn)
    	if err != nil {
    		panic(err)
    	}
    	defer s.Logoff()
    
    	fs, err := s.Mount("SHARENAME")
    	if err != nil {
    		panic(err)
    	}
    	defer fs.Umount()
    
    	f, err := fs.Create("hello.txt")
    	if err != nil {
    		panic(err)
    	}
    	defer fs.Remove("hello.txt")
    	defer f.Close()
    
    	_, err = f.Write([]byte("Hello world!"))
    	if err != nil {
    		panic(err)
    	}
    
    	_, err = f.Seek(0, io.SeekStart)
    	if err != nil {
    		panic(err)
    	}
    
    	bs, err := ioutil.ReadAll(f)
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(string(bs))
    }
  5. List share names on an SMB server

    master

    To list the available share names on an SMB server, establish a TCP connection to the server (typically port 445), use smb2.Dialer with an NTLMInitiator to authenticate, and call s.ListSharenames() on the resulting session.

    package main
    
    import (
    	"fmt"
    	"net"
    
    	"github.com/hirochachacha/go-smb2"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "SERVERNAME:445")
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    
    	d := &smb2.Dialer{
    		Initiator: &smb2.NTLMInitiator{
    			User:     "USERNAME",
    			Password: "PASSWORD",
    		},
    	}
    
    	s, err := d.Dial(conn)
    	if err != nil {
    		panic(err)
    	}
    	defer s.Logoff()
    
    	names, err := s.ListSharenames()
    	if err != nil {
    		panic(err)
    	}
    
    	for _, name := range names {
    		fmt.Println(name)
    	}
    }
  6. Use Glob and WalkDir with the FS interface

    master

    The go-smb2 filesystem object can be used with the standard io/fs package. By calling fs.DirFS("."), you can obtain an io/fs.FS compatible interface, which allows you to use iofs.Glob for pattern matching and iofs.WalkDir for recursive directory traversal.

    package main
    
    import (
    	"fmt"
    	"net"
    	iofs "io/fs"
    
    	"github.com/hirochachacha/go-smb2"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "SERVERNAME:445")
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    
    	d := &smb2.Dialer{
    		Initiator: &smb2.NTLMInitiator{
    			User:     "USERNAME",
    			Password: "PASSWORD",
    		},
    	}
    
    	s, err := d.Dial(conn)
    	if err != nil {
    		panic(err)
    	}
    	defer s.Logoff()
    
    	fs, err := s.Mount("SHARENAME")
    	if err != nil {
    		panic(err)
    	}
    	defer fs.Umount()
    
    	matches, err := iofs.Glob(fs.DirFS("."), "*")
    	if err != nil {
    		panic(err)
    	}
    	for _, match := range matches {
    		fmt.Println(match)
    	}
    
    	err = iofs.WalkDir(fs.DirFS("."), ".", func(path string, d iofs.DirEntry, err error) error {
    		fmt.Println(path, d, err)
    
    		return nil
    	})
    	if err != nil {
    		panic(err)
    	}
    }
  7. Dial an SMB connection using Dialer

    master

    To establish an SMB session, use the Dialer struct. You must provide a net.Conn (TCP connection) and a configured Initiator (e.g., NTLMInitiator).

    Note:

    • This implementation does not support NetBIOS transport.
    • It does not support multi-session on a single TCP connection; you must create a new TCP connection for each session.
    • DialContext allows for cancellation via context.Context, but the returned Session does not inherit this context automatically. Use Session.WithContext to associate a specific context with the session.
    dialer := &smb2.Dialer{
    	Initiator: &smb2.NTLMInitiator{
    		User: "username",
    		Password: "password",
    	},
    }
    
    // Dial using a net.Conn
    session, err := dialer.Dial(tcpConn)
    if err != nil {
    	// handle error
    }
  8. Configure SMB2 negotiation with Negotiator

    master

    The Negotiator struct allows you to customize the SMB2 connection negotiation process. You can use it to enforce message signing, specify a custom Client GUID, or restrict the connection to a specific SMB dialect.

    Fields:

    • RequireMessageSigning (bool): If true, enforces signing during negotiation.
    • ClientGuid ([16]byte): A custom GUID for the client. If left as zero, a random GUID is generated using crypto/rand.
    • SpecifiedDialect (uint16): The desired SMB dialect (e.g., SMB202, SMB210, SMB300, SMB302, SMB311). If set to 0 (or UnknownSMB), the client will negotiate using the default supported dialects.
    negotiator := &smb2.Negotiator{
        RequireMessageSigning: true,
        SpecifiedDialect:      smb2.SMB311,
    }
  9. Use NTLMInitiator for SMB2 authentication

    master

    The NTLMInitiator struct is used to implement session setup through NTLMv2. Note that NTLMv1 is not supported. You can authenticate using either a Password or a pre-computed Hash.

    To use it, populate the fields in NTLMInitiator and interact with it via the Initiator interface methods to handle the security context negotiation.

    import "github.com/hirochachacha/go-smb2"
    
    initiator := &smb2.NTLMInitiator{
    	User:        "username",
    	Password:    "password",
    	Domain:      "domain",
    	Workstation: "workstation",
    	TargetSPN:   "SPN",
    }
  10. Access files via the io/fs.FS interface

    master

    Once you have obtained an fs.FS via Share.DirFS, you can use the following standard methods to interact with the SMB share:

    • Open(name string) (fs.File, error): Opens the named file. The returned fs.File implements ReadDir to allow directory traversal.
    • Stat(name string) (fs.FileInfo, error): Returns file information for the named file.
    • ReadFile(name string) ([]byte, error): Reads the entire file into memory.
    • Glob(pattern string) (matches []string, err error): Returns the names of files matching the pattern.
  11. Change file permissions with Chmod

    master

    The Chmod(mode os.FileMode) method allows you to update file attributes. In this implementation, it specifically maps the os.FileMode to the SMB2 FILE_ATTRIBUTE_READONLY attribute. If the bit 0200 is set in the mode, the read-only attribute is cleared; otherwise, it is set.

    err := file.Chmod(0666) // Sets file to non-read-only