Install go-smb2
masterTo install the go-smb2 SMB2/3 client implementation, use the following command:
go get github.com/hirochachacha/go-smb2repository·master·Indexed 19 days ago
https://github.com/hirochachacha/go-smb2A 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.
To install the go-smb2 SMB2/3 client implementation, use the following command:
go get github.com/hirochachacha/go-smb2The negotiation process involves creating a NegotiateRequest based on the Negotiator settings, sending it to the server, and processing the NegotiateResponse.
Key behaviors during negotiation:
RequireMessageSigning, the request sets SMB2_NEGOTIATE_SIGNING_REQUIRED or SMB2_NEGOTIATE_SIGNING_ENABLED.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.NegotiateContext for pre-authentication integrity (e.g., SHA512) and encryption capabilities (e.g., AES128GCM).maxTransactSize, maxReadSize, and maxWriteSize.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)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
}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))
}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)
}
}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)
}
}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:
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
}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,
}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",
}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.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