├── .github └── workflows │ └── tests.yaml ├── COPYING ├── README.md ├── cmd └── smb │ └── main.go ├── go.mod ├── go.sum ├── libsmbclient.c ├── libsmbclient.go ├── libsmbclient_examples_test.go ├── libsmbclient_lfs.go ├── libsmbclient_test.go └── test ├── smb.conf └── test.sh /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Install Go 8 | uses: actions/setup-go@v2 9 | - name: Checkout code 10 | uses: actions/checkout@v2 11 | - name: Install apt test dependencies 12 | run: | 13 | sudo apt update 14 | sudo apt install -y libsmbclient-dev samba 15 | - name: Install go dependencies 16 | run: | 17 | go get -t -v ./... 18 | - name: Test 19 | run: | 20 | go test ./... 21 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2019 Michael Vogt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | . 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | . 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Bindings for libsmbclient 2 | ============================ 3 | 4 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/mvo5/libsmbclient-go)](https://pkg.go.dev/github.com/mvo5/libsmbclient-go) 5 | [![Tests report](https://github.com/mvo5/libsmbclient-go/workflows/Test/badge.svg)](https://github.com//mvo5/libsmbclient-go/actions?query=workflow%3ATest) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/mvo5/libsmbclient-go)](https://goreportcard.com/report/github.com/mvo5/libsmbclient-go) 7 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/mvo5/libsmbclient-go/blob/master/COPYING) 8 | 9 | 10 | Bindings (read-only for now) for the libsmbclient library from 11 | samba. To compile on debian/ubuntu install the "libsmbclient-dev" 12 | package. 13 | 14 | Build it with: 15 | ``` 16 | $ go build ./cmd/smb 17 | $ ./smb -show-dir smb://localhost 18 | ``` 19 | 20 | Check main.go for a code example. 21 | 22 | Limitation: 23 | ----------- 24 | 25 | The C libsmbclient from samba is not thread safe, so all go 26 | smbclient.Client/smbclient.File operations are serialized (i.e. there 27 | can only be one operation at a time for each Client/File). As a 28 | workaround you should create one smbclient.Client per goroutine. 29 | 30 | 31 | Example usage: 32 | -------------- 33 | 34 | ``` 35 | import ( 36 | "fmt" 37 | 38 | "github.com/mvo5/libsmbclient-go" 39 | ) 40 | 41 | client := smbclient.New() 42 | dh, err := client.Opendir("smb://localhost") 43 | if err != nil { 44 | return err 45 | } 46 | defer dh.Closedir() 47 | for { 48 | dirent, err := dh.Readdir() 49 | if err != nil { 50 | break 51 | } 52 | fmt.Println(dirent) 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /cmd/smb/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "strings" 12 | "time" 13 | 14 | "github.com/mvo5/libsmbclient-go" 15 | ) 16 | 17 | func openSmbdir(client *libsmbclient.Client, duri string) { 18 | dh, err := client.Opendir(duri) 19 | if err != nil { 20 | log.Print(err) 21 | return 22 | } 23 | defer dh.Closedir() 24 | for { 25 | dirent, err := dh.Readdir() 26 | if err != nil { 27 | break 28 | } 29 | fmt.Println(dirent) 30 | } 31 | } 32 | 33 | func openSmbfile(client *libsmbclient.Client, furi string) { 34 | f, err := client.Open(furi, 0, 0) 35 | if err != nil { 36 | log.Print(err) 37 | return 38 | } 39 | buf := make([]byte, 64*1024) 40 | for { 41 | _, err := f.Read(buf) 42 | if err == io.EOF { 43 | break 44 | } 45 | if err != nil { 46 | log.Print(err) 47 | return 48 | } 49 | fmt.Print(string(buf)) 50 | } 51 | f.Close() 52 | } 53 | 54 | func askAuth(serverName, shareName string) (outDomain, outUsername, outPassword string) { 55 | bio := bufio.NewReader(os.Stdin) 56 | fmt.Printf("auth for %s %s\n", serverName, shareName) 57 | // domain 58 | fmt.Print("Domain: ") 59 | domain, _ := bio.ReadString('\n') 60 | // read username 61 | fmt.Print("Username: ") 62 | username, _ := bio.ReadString('\n') 63 | // read pw from stdin 64 | fmt.Print("Password: ") 65 | setEcho(false) 66 | password, _ := bio.ReadString('\n') 67 | setEcho(true) 68 | return strings.TrimSpace(domain), strings.TrimSpace(username), strings.TrimSpace(password) 69 | } 70 | 71 | func setEcho(terminalEchoEnabled bool) { 72 | var cmd *exec.Cmd 73 | if terminalEchoEnabled { 74 | cmd = exec.Command("stty", "-F", "/dev/tty", "echo") 75 | } else { 76 | cmd = exec.Command("stty", "-F", "/dev/tty", "-echo") 77 | } 78 | cmd.Run() 79 | } 80 | 81 | func multiThreadStressTest(client *libsmbclient.Client, uri string) { 82 | fmt.Println("m: " + uri) 83 | dh, err := client.Opendir(uri) 84 | if err != nil { 85 | log.Print(err) 86 | return 87 | } 88 | defer dh.Closedir() 89 | for { 90 | dirent, err := dh.Readdir() 91 | if err != nil { 92 | break 93 | } 94 | newUri := uri + "/" + dirent.Name 95 | switch dirent.Type { 96 | case libsmbclient.SmbcDir, libsmbclient.SmbcFileShare: 97 | fmt.Println("d: " + newUri) 98 | go multiThreadStressTest(client, newUri) 99 | case libsmbclient.SmbcFile: 100 | fmt.Println("f: " + newUri) 101 | go openSmbfile(client, newUri) 102 | } 103 | } 104 | 105 | // FIXME: instead of sleep, wait for all threads to exit 106 | time.Sleep(10 * time.Second) 107 | } 108 | 109 | func main() { 110 | var duri, furi, suri string 111 | var withAuth, withKrb5 bool 112 | flag.StringVar(&duri, "show-dir", "", "smb://path/to/dir style directory") 113 | flag.StringVar(&furi, "show-file", "", "smb://path/to/file style file") 114 | flag.BoolVar(&withAuth, "with-auth", false, "ask for auth") 115 | flag.BoolVar(&withKrb5, "with-krb5", false, "use Kerberos for auth") 116 | flag.StringVar(&suri, "stress-test", "", "run threaded stress test") 117 | flag.Parse() 118 | 119 | client := libsmbclient.New() 120 | //client.SetDebug(99) 121 | 122 | if withKrb5 { 123 | client.SetUseKerberos() 124 | } else if withAuth { 125 | client.SetAuthCallback(askAuth) 126 | } 127 | 128 | var fn func(*libsmbclient.Client, string) 129 | var uri string 130 | if duri != "" { 131 | fn = openSmbdir 132 | uri = duri 133 | } else if furi != "" { 134 | fn = openSmbfile 135 | uri = furi 136 | } else if suri != "" { 137 | fn = multiThreadStressTest 138 | uri = suri 139 | } else { 140 | flag.Usage() 141 | return 142 | } 143 | fn(client, uri) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mvo5/libsmbclient-go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 7 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 2 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 3 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 4 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 5 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 6 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= 7 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 8 | -------------------------------------------------------------------------------- /libsmbclient.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "_cgo_export.h" 3 | 4 | 5 | SMBCFILE* my_smbc_opendir(SMBCCTX *c, const char *fname) { 6 | smbc_opendir_fn fn = smbc_getFunctionOpendir(c); 7 | return fn(c, fname); 8 | } 9 | 10 | int my_smbc_closedir(SMBCCTX *c, SMBCFILE *dir) { 11 | smbc_closedir_fn fn = smbc_getFunctionClosedir(c); 12 | return fn(c, dir); 13 | } 14 | 15 | struct smbc_dirent* my_smbc_readdir(SMBCCTX *c, SMBCFILE *dir) { 16 | smbc_readdir_fn fn = smbc_getFunctionReaddir(c); 17 | return fn(c, dir); 18 | } 19 | 20 | SMBCFILE* my_smbc_open(SMBCCTX *c, const char *fname, int flags, mode_t mode) { 21 | smbc_open_fn fn = smbc_getFunctionOpen(c); 22 | return fn(c, fname, flags, mode); 23 | } 24 | 25 | ssize_t my_smbc_read(SMBCCTX *c, SMBCFILE *file, void *buf, size_t count) { 26 | smbc_read_fn fn = smbc_getFunctionRead(c); 27 | return fn(c, file, buf, count); 28 | } 29 | 30 | off_t my_smbc_lseek(SMBCCTX *c, SMBCFILE * file, off_t offset, int whence) { 31 | smbc_lseek_fn fn = smbc_getFunctionLseek(c); 32 | return fn(c, file, offset, whence); 33 | } 34 | 35 | void my_smbc_close(SMBCCTX *c, SMBCFILE *f) { 36 | smbc_close_fn fn = smbc_getFunctionClose(c); 37 | fn(c, f); 38 | } 39 | 40 | void my_smbc_auth_callback(SMBCCTX *ctx, 41 | const char *server_name, const char *share_name, 42 | char *domain_out, int domainmaxlen, 43 | char *username_out, int unmaxlen, 44 | char *password_out, int pwmaxlen) { 45 | void *go_fn = smbc_getOptionUserData(ctx); 46 | authCallbackHelper(go_fn, 47 | (char*)server_name, (char*)share_name, 48 | domain_out, domainmaxlen, 49 | username_out, unmaxlen, 50 | password_out, pwmaxlen); 51 | } 52 | 53 | void my_smbc_init_auth_callback(SMBCCTX *ctx, void *go_fn) 54 | { 55 | smbc_setOptionUserData(ctx, go_fn); 56 | smbc_setFunctionAuthDataWithContext(ctx, my_smbc_auth_callback); 57 | } 58 | -------------------------------------------------------------------------------- /libsmbclient.go: -------------------------------------------------------------------------------- 1 | package libsmbclient 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | "unsafe" 8 | ) 9 | 10 | /* 11 | #cgo pkg-config: smbclient 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | SMBCFILE* my_smbc_opendir(SMBCCTX *c, const char *fname); 19 | int my_smbc_closedir(SMBCCTX *c, SMBCFILE *dir); 20 | struct smbc_dirent* my_smbc_readdir(SMBCCTX *c, SMBCFILE *dir); 21 | SMBCFILE* my_smbc_open(SMBCCTX *c, const char *fname, int flags, mode_t mode); 22 | ssize_t my_smbc_read(SMBCCTX *c, SMBCFILE *file, void *buf, size_t count); 23 | void my_smbc_close(SMBCCTX *c, SMBCFILE *f); 24 | void my_smbc_auth_callback(SMBCCTX *context, 25 | const char *server_name, const char *share_name, 26 | char *domain_out, int domainmaxlen, 27 | char *username_out, int unmaxlen, 28 | char *password_out, int pwmaxlen); 29 | void my_smbc_init_auth_callback(SMBCCTX *context, void *go_fn); 30 | off_t my_smbc_lseek(SMBCCTX *c, SMBCFILE * file, off_t offset, int whence); 31 | */ 32 | import "C" 33 | 34 | // SmbcType is the different type of entity returned by samba. 35 | type SmbcType int 36 | 37 | const ( 38 | // SmbcWorkgroup is a workgroup entity. 39 | SmbcWorkgroup SmbcType = C.SMBC_WORKGROUP 40 | // SmbcFileShare is a file share. 41 | SmbcFileShare = C.SMBC_FILE_SHARE 42 | // SmbcPrinterShare is a printer share. 43 | SmbcPrinterShare = C.SMBC_PRINTER_SHARE 44 | // SmbcCommsShare is a communication share. 45 | SmbcCommsShare = C.SMBC_COMMS_SHARE 46 | // SmbcIPCShare is an ipc share entity. 47 | SmbcIPCShare = C.SMBC_IPC_SHARE 48 | // SmbcDir is a directory. 49 | SmbcDir = C.SMBC_DIR 50 | // SmbcFile is a file. 51 | SmbcFile = C.SMBC_FILE 52 | // SmbcLink is a symlink. 53 | SmbcLink = C.SMBC_LINK 54 | ) 55 | 56 | // *sigh* even with libsmbclient-4.0 the library is not MT safe, 57 | // e.g. smbc_init_context from multiple threads crashes 58 | var smbMu = sync.Mutex{} 59 | 60 | // Client is a samba client instance, handling its own context and lock. 61 | type Client struct { 62 | ctx *C.SMBCCTX 63 | authCallback *AuthCallback 64 | // libsmbclient is not thread safe 65 | smbMu *sync.Mutex 66 | } 67 | 68 | // Dirent represents a samba directory entry. 69 | type Dirent struct { 70 | Type SmbcType 71 | Comment string 72 | Name string 73 | } 74 | 75 | // File reprends a samba file. 76 | type File struct { 77 | client *Client 78 | smbcfile *C.SMBCFILE 79 | } 80 | 81 | // New creates a new samba client. 82 | func New() *Client { 83 | smbMu.Lock() 84 | defer smbMu.Unlock() 85 | 86 | c := &Client{ 87 | ctx: C.smbc_new_context(), 88 | smbMu: &smbMu, 89 | } 90 | C.smbc_init_context(c.ctx) 91 | // this does not work reliable, see TestLibsmbclientThreaded test 92 | /* 93 | runtime.SetFinalizer(c, func(c2 *Client) { 94 | fmt.Println(fmt.Sprintf("d: %v", c2)) 95 | c2.Close() 96 | }) 97 | */ 98 | 99 | return c 100 | } 101 | 102 | // Destroy closes the current samba client. 103 | func (c *Client) Destroy() error { 104 | return c.Close() 105 | } 106 | 107 | // Close closes the current samba client and release context. 108 | func (c *Client) Close() error { 109 | c.smbMu.Lock() 110 | defer c.smbMu.Unlock() 111 | 112 | var err error 113 | if c.ctx != nil { 114 | // 1 would mean we force the destroy 115 | _, err = C.smbc_free_context(c.ctx, C.int(1)) 116 | c.ctx = nil 117 | } 118 | return err 119 | } 120 | 121 | // AuthCallback is the authentication function that will be called during connection with samba. 122 | type AuthCallback = func(serverName, shareName string) (domain, username, password string) 123 | 124 | // SetAuthCallback assigns the authentication function that will be called during connection 125 | // with samba. 126 | func (c *Client) SetAuthCallback(f AuthCallback) { 127 | c.smbMu.Lock() 128 | defer c.smbMu.Unlock() 129 | 130 | C.my_smbc_init_auth_callback(c.ctx, unsafe.Pointer(&f)) 131 | // we need to store it in the Client struct to ensure its not garbage 132 | // collected later (I think) 133 | c.authCallback = &f 134 | } 135 | 136 | // SetUseKerberos enable krb5 integration for authentication. 137 | func (c *Client) SetUseKerberos() { 138 | c.smbMu.Lock() 139 | defer c.smbMu.Unlock() 140 | 141 | C.smbc_setOptionUseKerberos(c.ctx, C.int(1)) 142 | } 143 | 144 | // GetDebug returns the debug level. 145 | func (c *Client) GetDebug() int { 146 | c.smbMu.Lock() 147 | defer c.smbMu.Unlock() 148 | 149 | return int(C.smbc_getDebug(c.ctx)) 150 | } 151 | 152 | // SetDebug sets the degug level. 153 | func (c *Client) SetDebug(level int) { 154 | c.smbMu.Lock() 155 | defer c.smbMu.Unlock() 156 | 157 | C.smbc_setDebug(c.ctx, C.int(level)) 158 | } 159 | 160 | // GetUser returns the authenticated user. 161 | func (c *Client) GetUser() string { 162 | c.smbMu.Lock() 163 | defer c.smbMu.Unlock() 164 | 165 | return C.GoString(C.smbc_getUser(c.ctx)) 166 | } 167 | 168 | // SetUser sets the user to use for the session. 169 | func (c *Client) SetUser(user string) { 170 | c.smbMu.Lock() 171 | defer c.smbMu.Unlock() 172 | 173 | C.smbc_setUser(c.ctx, C.CString(user)) 174 | } 175 | 176 | // GetWorkgroup returns the name of the current workgroup. 177 | func (c *Client) GetWorkgroup() string { 178 | c.smbMu.Lock() 179 | defer c.smbMu.Unlock() 180 | 181 | return C.GoString(C.smbc_getWorkgroup(c.ctx)) 182 | } 183 | 184 | // SetWorkgroup sets the work group to use for the session. 185 | func (c *Client) SetWorkgroup(wg string) { 186 | c.smbMu.Lock() 187 | defer c.smbMu.Unlock() 188 | 189 | C.smbc_setWorkgroup(c.ctx, C.CString(wg)) 190 | } 191 | 192 | // Opendir opens a directory and returns a handle on success. 193 | func (c *Client) Opendir(durl string) (File, error) { 194 | c.smbMu.Lock() 195 | defer c.smbMu.Unlock() 196 | 197 | d, err := C.my_smbc_opendir(c.ctx, C.CString(durl)) 198 | if d == nil { 199 | return File{}, fmt.Errorf("cannot open %v: %v", durl, err) 200 | } 201 | return File{client: c, smbcfile: d}, nil 202 | } 203 | 204 | // Closedir closes current directory. 205 | func (e *File) Closedir() error { 206 | e.client.smbMu.Lock() 207 | defer e.client.smbMu.Unlock() 208 | 209 | if e.smbcfile != nil { 210 | _, err := C.my_smbc_closedir(e.client.ctx, e.smbcfile) 211 | e.smbcfile = nil 212 | return err 213 | } 214 | return nil 215 | } 216 | 217 | // Readdir reads the directory named pointed by File and returned its Dirent. 218 | func (e *File) Readdir() (*Dirent, error) { 219 | e.client.smbMu.Lock() 220 | defer e.client.smbMu.Unlock() 221 | 222 | cDirent, err := C.my_smbc_readdir(e.client.ctx, e.smbcfile) 223 | if cDirent == nil && err != nil { 224 | return nil, fmt.Errorf("cannot readdir: %v", err) 225 | } 226 | if cDirent == nil { 227 | return nil, io.EOF 228 | } 229 | dirent := Dirent{Type: SmbcType(cDirent.smbc_type), 230 | Comment: C.GoStringN(cDirent.comment, C.int(cDirent.commentlen)), 231 | Name: C.GoStringN(&cDirent.name[0], C.int(cDirent.namelen))} 232 | return &dirent, nil 233 | } 234 | 235 | // Open opens a file and returns a handle on success. 236 | // FIXME: mode is actually "mode_t mode" 237 | func (c *Client) Open(furl string, flags, mode int) (File, error) { 238 | c.smbMu.Lock() 239 | defer c.smbMu.Unlock() 240 | 241 | cs := C.CString(furl) 242 | sf, err := C.my_smbc_open(c.ctx, cs, C.int(flags), C.mode_t(mode)) 243 | if sf == nil && err != nil { 244 | return File{}, fmt.Errorf("cannot open %v: %v", furl, err) 245 | } 246 | 247 | return File{client: c, smbcfile: sf}, nil 248 | } 249 | 250 | // Read reads up to len(b) bytes from the File. It returns the number of bytes read and any error encountered. 251 | // At end of file, Read returns 0, io.EOF. 252 | func (e *File) Read(buf []byte) (int, error) { 253 | e.client.smbMu.Lock() 254 | defer e.client.smbMu.Unlock() 255 | 256 | cCount, err := C.my_smbc_read(e.client.ctx, e.smbcfile, unsafe.Pointer(&buf[0]), C.size_t(len(buf))) 257 | c := int(cCount) 258 | if c == 0 { 259 | return 0, io.EOF 260 | } else if c < 0 && err != nil { 261 | return c, fmt.Errorf("cannot read: %v", err) 262 | } 263 | return c, nil 264 | } 265 | 266 | // Lseek repositions the file offset of the open file to the argument offset according to the directive whence. 267 | func (e *File) Lseek(offset, whence int) (int, error) { 268 | e.client.smbMu.Lock() 269 | defer e.client.smbMu.Unlock() 270 | 271 | newOffset, err := C.my_smbc_lseek(e.client.ctx, e.smbcfile, C.off_t(offset), C.int(whence)) 272 | if int(newOffset) < 0 && err != nil { 273 | return int(newOffset), fmt.Errorf("cannot seek: %v", err) 274 | } 275 | return int(newOffset), nil 276 | } 277 | 278 | // Close closes current file and and releases its ressources. 279 | func (e *File) Close() { 280 | e.client.smbMu.Lock() 281 | defer e.client.smbMu.Unlock() 282 | 283 | if e.smbcfile != nil { 284 | C.my_smbc_close(e.client.ctx, e.smbcfile) 285 | e.smbcfile = nil 286 | } 287 | } 288 | 289 | //export authCallbackHelper 290 | func authCallbackHelper(fn unsafe.Pointer, serverName, shareName, domainOut *C.char, domainLen C.int, usernameOut *C.char, ulen C.int, passwordOut *C.char, pwlen C.int) { 291 | callback := *(*AuthCallback)(fn) 292 | domain, user, pw := callback(C.GoString(serverName), C.GoString(shareName)) 293 | C.strncpy(domainOut, C.CString(domain), C.size_t(domainLen)) 294 | C.strncpy(usernameOut, C.CString(user), C.size_t(ulen)) 295 | C.strncpy(passwordOut, C.CString(pw), C.size_t(pwlen)) 296 | } 297 | -------------------------------------------------------------------------------- /libsmbclient_examples_test.go: -------------------------------------------------------------------------------- 1 | package libsmbclient_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/mvo5/libsmbclient-go" 8 | ) 9 | 10 | func ExampleNew() { 11 | client := libsmbclient.New() 12 | dh, err := client.Opendir("smb://localhost") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | defer dh.Closedir() 17 | for { 18 | dirent, err := dh.Readdir() 19 | if err != nil { 20 | break 21 | } 22 | fmt.Println(dirent) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libsmbclient_lfs.go: -------------------------------------------------------------------------------- 1 | //go:build 386 || amd64p32 || arm || armbe || mips || mips64p32 || mips64p32le || mipsle || ppc || riscv || s390 || sparc 2 | // +build 386 amd64p32 arm armbe mips mips64p32 mips64p32le mipsle ppc riscv s390 sparc 3 | 4 | package libsmbclient 5 | 6 | /* 7 | #cgo CFLAGS: -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /libsmbclient_test.go: -------------------------------------------------------------------------------- 1 | package libsmbclient_test 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "testing" 14 | "text/template" 15 | "time" 16 | 17 | . "gopkg.in/check.v1" 18 | 19 | "github.com/mvo5/libsmbclient-go" 20 | ) 21 | 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | var SmbConfTemplate = `[global] 25 | workgroup = TESTGROUP 26 | interfaces = lo 127.0.0.0/8 27 | smb ports = 1445 28 | log level = 2 29 | map to guest = Bad User 30 | passdb backend = smbpasswd 31 | smb passwd file = {{.Tempdir}}/smbpasswd 32 | lock directory = {{.Tempdir}}/intern 33 | state directory = {{.Tempdir}}/intern 34 | cache directory = {{.Tempdir}}/intern 35 | pid directory = {{.Tempdir}}/intern 36 | private dir = {{.Tempdir}}/intern 37 | ncalrpc dir = {{.Tempdir}}/intern 38 | 39 | [public] 40 | path = {{.Tempdir}}/public 41 | guest ok = yes 42 | 43 | [private] 44 | path = {{.Tempdir}}/private 45 | read only = no 46 | ` 47 | 48 | type smbclientSuite struct { 49 | smbdCmd *exec.Cmd 50 | 51 | tempdir string 52 | } 53 | 54 | var _ = Suite(&smbclientSuite{}) 55 | 56 | func waitPortReadyOrPanic(port int) { 57 | for i := 0; i < 100; i++ { 58 | conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port)) 59 | if err != nil { 60 | time.Sleep(100 * time.Millisecond) 61 | continue 62 | } 63 | conn.Close() 64 | return 65 | } 66 | panic(fmt.Sprintf("cannot connect to port %v", port)) 67 | } 68 | 69 | func (s *smbclientSuite) SetUpSuite(c *C) { 70 | s.tempdir = c.MkDir() 71 | s.startSmbd(c) 72 | } 73 | 74 | func (s *smbclientSuite) TearDownSuite(c *C) { 75 | s.smbdCmd.Process.Kill() 76 | // XXX: wait will segfault because libsmbclient overrides sigchld 77 | //s.smbdCmd.Wait() 78 | s.smbdCmd = nil 79 | } 80 | 81 | func (s *smbclientSuite) generateSmbdConf(c *C) string { 82 | paths := [...]string{ 83 | s.tempdir, 84 | filepath.Join(s.tempdir, "samaba", "private"), 85 | filepath.Join(s.tempdir, "samba", "public"), 86 | } 87 | for _, d := range paths { 88 | err := os.MkdirAll(d, 0755) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | } 93 | err := os.Mkdir(filepath.Join(s.tempdir, "private"), 0755) 94 | c.Assert(err, IsNil) 95 | err = os.Mkdir(filepath.Join(s.tempdir, "public"), 0755) 96 | c.Assert(err, IsNil) 97 | f, err := os.Create(filepath.Join(s.tempdir, "smbd.conf")) 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | defer f.Close() 102 | templateText := SmbConfTemplate 103 | type Dir struct { 104 | Tempdir string 105 | } 106 | t, err := template.New("smb-conf").Parse(templateText) 107 | if err != nil { 108 | log.Fatal(err) 109 | } 110 | t.Execute(f, Dir{s.tempdir}) 111 | return f.Name() 112 | } 113 | 114 | func (s *smbclientSuite) startSmbd(c *C) { 115 | // tells smbd to use a port different from "445" 116 | os.Setenv("LIBSMB_PROG", "nc localhost 1445") 117 | smbConf := s.generateSmbdConf(c) 118 | cmd := exec.Command("smbd", "-F", "-l", c.MkDir(), "-s", smbConf) 119 | //cmd.Stdout = os.Stdout 120 | cmd.Stderr = os.Stderr 121 | err := cmd.Start() 122 | c.Assert(err, IsNil) 123 | s.smbdCmd = cmd 124 | waitPortReadyOrPanic(1445) 125 | } 126 | 127 | func (s *smbclientSuite) TestLibsmbclientBindings(c *C) { 128 | // open client 129 | client := libsmbclient.New() 130 | defer client.Close() 131 | 132 | d, err := client.Opendir("smb://localhost:1445") 133 | c.Assert(err, IsNil) 134 | defer d.Closedir() 135 | 136 | // collect dirs 137 | foundSmbDirs := map[string]bool{} 138 | for { 139 | dirent, err := d.Readdir() 140 | if err != nil { 141 | break 142 | } 143 | foundSmbDirs[dirent.Name] = true 144 | } 145 | // check for expected data 146 | if !foundSmbDirs["private"] || !foundSmbDirs["public"] { 147 | c.Errorf("missing excpected folder in (%v)", foundSmbDirs) 148 | } 149 | } 150 | 151 | func getRandomFileName() string { 152 | return fmt.Sprintf("%d", rand.Int()) 153 | } 154 | 155 | func openFile(client *libsmbclient.Client, path string, ch chan int) { 156 | f, err := client.Open(path, 0, 0) 157 | if err != nil { 158 | log.Fatal(fmt.Sprintf("%s: %s", path, err)) 159 | } 160 | defer func() { 161 | f.Close() 162 | ch <- 1 163 | }() 164 | 165 | // FIXME: move this into the lib as ioutil.ReadFile() 166 | buf := make([]byte, 64*1024) 167 | for { 168 | _, err := f.Read(buf) 169 | if err == io.EOF { 170 | break 171 | } 172 | if err != nil { 173 | log.Print(err) 174 | return 175 | } 176 | } 177 | } 178 | 179 | func readAllFilesInDir(client *libsmbclient.Client, baseDir string, ch chan int) { 180 | d, err := client.Opendir(baseDir) 181 | if err != nil { 182 | log.Fatal(fmt.Sprintf("%s: %s", baseDir, err)) 183 | } 184 | defer d.Closedir() 185 | for { 186 | dirent, err := d.Readdir() 187 | if err == io.EOF { 188 | break 189 | } 190 | if err != nil { 191 | log.Fatalf("readdir failed %s", err) 192 | } 193 | if dirent.Name == "." || dirent.Name == ".." { 194 | continue 195 | } 196 | if dirent.Type == libsmbclient.SmbcDir { 197 | go readAllFilesInDir(client, baseDir+dirent.Name+"/", ch) 198 | } 199 | if dirent.Type == libsmbclient.SmbcFile { 200 | go openFile(client, baseDir+dirent.Name, ch) 201 | } 202 | } 203 | } 204 | 205 | func (s *smbclientSuite) TestLibsmbclientThreaded(c *C) { 206 | CLIENTS := 4 207 | DIRS := 4 208 | THREADS := 8 209 | FILE_SIZE := 4 * 1024 210 | 211 | for i := 0; i < DIRS; i++ { 212 | dirname := fmt.Sprintf("%s/public/%d/", s.tempdir, i) 213 | err := os.MkdirAll(dirname, 0755) 214 | c.Assert(err, IsNil) 215 | 216 | // create a bunch of test files 217 | buf := make([]byte, FILE_SIZE) 218 | for j := 0; j < THREADS; j++ { 219 | tmpf := dirname + getRandomFileName() 220 | err = ioutil.WriteFile(tmpf, buf, 0644) 221 | c.Assert(err, IsNil) 222 | } 223 | } 224 | 225 | // make N clients 226 | ch := make(chan int) 227 | for i := 0; i < CLIENTS; i++ { 228 | baseDir := "smb://localhost:1445/public/" 229 | client := libsmbclient.New() 230 | defer client.Close() 231 | go readAllFilesInDir(client, baseDir, ch) 232 | } 233 | 234 | count := 0 235 | for count < THREADS*DIRS*CLIENTS { 236 | count += <-ch 237 | } 238 | 239 | fmt.Println(fmt.Sprintf("done: %d", count)) 240 | } 241 | -------------------------------------------------------------------------------- /test/smb.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | workgroup = TESTGROUP 3 | interfaces = lo 127.0.0.0/8 4 | smb ports = 1445 5 | log level = 2 6 | map to guest = Bad User 7 | passdb backend = smbpasswd 8 | smb passwd file = /tmp/samba/smbpasswd 9 | lock directory = /tmp/samba/intern 10 | state directory = /tmp/samba/intern 11 | cache directory = /tmp/samba/intern 12 | pid directory = /tmp/samba/intern 13 | private dir = /tmp/samba/intern 14 | ncalrpc dir = /tmp/samba/intern 15 | 16 | [public] 17 | path = /tmp/samba/public 18 | guest ok = yes 19 | 20 | [private] 21 | path = /tmp/samba/private 22 | read only = no -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HERE=$(dirname $0) 4 | 5 | smbd -iFS -s $HERE/smb.conf 6 | --------------------------------------------------------------------------------