-
Notifications
You must be signed in to change notification settings - Fork 246
/
Copy pathget_smbclient.go
350 lines (301 loc) · 8.54 KB
/
get_smbclient.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
package getter
import (
"bytes"
"context"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"
)
// SmbClientGetter is a Getter implementation that will download a module from
// a shared folder using smbclient cli.
type SmbClientGetter struct {
// Timeout in seconds sets a deadline which all smb client CLI operations should
// complete within. Defaults to zero which means to use the default client timeout of 20 seconds.
Timeout int
}
func (g *SmbClientGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) {
if u.Host == "" || u.Path == "" {
return 0, new(smbPathError)
}
// Use smbclient cli to verify mode
mode, err := g.smbClientMode(u)
if err == nil {
return mode, nil
}
return 0, &smbGeneralError{err}
}
func (g *SmbClientGetter) smbClientMode(u *url.URL) (Mode, error) {
hostPath, filePath, err := g.findHostAndFilePath(u)
if err != nil {
return 0, err
}
file := ""
// Get file and subdirectory name when existent
if strings.Contains(filePath, "/") {
i := strings.LastIndex(filePath, "/")
file = filePath[i+1:]
filePath = filePath[:i]
} else {
file = filePath
filePath = "."
}
cmdArgs := g.smbclientCmdArgs(u.User, hostPath, filePath)
// check if file exists in the smb shared folder and check the mode
isDir, err := g.isDirectory(cmdArgs, file)
if err != nil {
return 0, err
}
if isDir {
return ModeDir, nil
}
return ModeFile, nil
}
func (g *SmbClientGetter) Get(ctx context.Context, req *Request) error {
if req.u.Host == "" || req.u.Path == "" {
return new(smbPathError)
}
// If dst folder doesn't exists, we need to remove the created on later in case of failures
dstExisted := false
if req.Dst != "" {
if _, err := os.Lstat(req.Dst); err == nil {
dstExisted = true
}
}
// Download the directory content using smbclient cli
err := g.smbclientGet(req)
if err == nil {
return nil
}
if !dstExisted {
// Remove the destination created for smbclient
os.Remove(req.Dst)
}
return &smbGeneralError{err}
}
func (g *SmbClientGetter) smbclientGet(req *Request) error {
hostPath, directory, err := g.findHostAndFilePath(req.u)
if err != nil {
return err
}
cmdArgs := g.smbclientCmdArgs(req.u.User, hostPath, ".")
// check directory exists in the smb shared folder and is a directory
isDir, err := g.isDirectory(cmdArgs, directory)
if err != nil {
return err
}
if !isDir {
return fmt.Errorf("%s source path must be a directory", directory)
}
// download everything that's inside the directory (files and subdirectories)
cmdArgs = append(cmdArgs, "-c")
cmdArgs = append(cmdArgs, "prompt OFF;recurse ON; mget *")
if req.Dst != "" {
_, err := os.Lstat(req.Dst)
if err != nil {
if os.IsNotExist(err) {
// Create destination folder if it doesn't exist
if err := os.MkdirAll(req.Dst, 0755); err != nil {
return fmt.Errorf("failed to create destination path: %s", err.Error())
}
} else {
return err
}
}
}
_, err = g.runSmbClientCommand(req.Dst, cmdArgs)
return err
}
func (g *SmbClientGetter) GetFile(ctx context.Context, req *Request) error {
if req.u.Host == "" || req.u.Path == "" {
return new(smbPathError)
}
// If dst folder doesn't exist, we need to remove the created one later in case of failures
dstExisted := false
if req.Dst != "" {
if _, err := os.Lstat(req.Dst); err == nil {
dstExisted = true
}
}
// If not mounted, try downloading the file using smbclient cli
err := g.smbclientGetFile(req)
if err == nil {
return nil
}
if !dstExisted {
// Remove the destination created for smbclient
os.Remove(req.Dst)
}
return &smbGeneralError{err}
}
func (g *SmbClientGetter) smbclientGetFile(req *Request) error {
hostPath, filePath, err := g.findHostAndFilePath(req.u)
if err != nil {
return err
}
// Get file and subdirectory name when existent
file := ""
if strings.Contains(filePath, "/") {
i := strings.LastIndex(filePath, "/")
file = filePath[i+1:]
filePath = filePath[:i]
} else {
file = filePath
filePath = "."
}
cmdArgs := g.smbclientCmdArgs(req.u.User, hostPath, filePath)
// check file exists in the smb shared folder and is not a directory
isDir, err := g.isDirectory(cmdArgs, file)
if err != nil {
return err
}
if isDir {
return fmt.Errorf("%s source path must be a file", file)
}
// download file
cmdArgs = append(cmdArgs, "-c")
if req.Dst != "" {
_, err := os.Lstat(req.Dst)
if err != nil {
if os.IsNotExist(err) {
// Create destination folder if it doesn't exist
if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil {
return fmt.Errorf("failed to creat destination path: %s", err.Error())
}
} else {
return err
}
}
cmdArgs = append(cmdArgs, fmt.Sprintf("get %s %s", file, req.Dst))
} else {
cmdArgs = append(cmdArgs, fmt.Sprintf("get %s", file))
}
_, err = g.runSmbClientCommand("", cmdArgs)
return err
}
func (g *SmbClientGetter) smbclientCmdArgs(used *url.Userinfo, hostPath string, fileDir string) (baseCmd []string) {
baseCmd = append(baseCmd, "-N")
// Append auth user and password to baseCmd
auth := used.Username()
if auth != "" {
if password, ok := used.Password(); ok {
auth = auth + "%" + password
}
baseCmd = append(baseCmd, "-U")
baseCmd = append(baseCmd, auth)
}
baseCmd = append(baseCmd, hostPath)
baseCmd = append(baseCmd, "--directory")
baseCmd = append(baseCmd, fileDir)
if g.Timeout > 0 {
baseCmd = append(baseCmd, "-t")
baseCmd = append(baseCmd, strconv.Itoa(g.Timeout))
}
return baseCmd
}
func (g *SmbClientGetter) findHostAndFilePath(u *url.URL) (string, string, error) {
// Host path
hostPath := "//" + u.Host
// Get shared directory
path := strings.TrimPrefix(u.Path, "/")
splt := regexp.MustCompile(`/`)
directories := splt.Split(path, 2)
if len(directories) > 0 {
hostPath = hostPath + "/" + directories[0]
}
// Check file path
if len(directories) <= 1 || directories[1] == "" {
return "", "", fmt.Errorf("can not find file path and/or name in the smb url")
}
return hostPath, directories[1], nil
}
func (g *SmbClientGetter) isDirectory(args []string, object string) (bool, error) {
args = append(args, "-c")
args = append(args, fmt.Sprintf("allinfo %s", object))
output, err := g.runSmbClientCommand("", args)
if err != nil {
return false, err
}
if strings.Contains(output, "OBJECT_NAME_NOT_FOUND") {
return false, fmt.Errorf("source path not found: %s", output)
}
return strings.Contains(output, "attributes: D"), nil
}
func (g *SmbClientGetter) runSmbClientCommand(dst string, args []string) (string, error) {
ctx := context.Background()
cmd := exec.CommandContext(ctx, "smbclient", args...)
if dst != "" {
cmd.Dir = dst
}
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := cmd.Run()
if err == nil {
return buf.String(), nil
}
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return buf.String(), fmt.Errorf(
"%s exited with %d: %s",
cmd.Path,
status.ExitStatus(),
buf.String())
}
}
return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String())
}
func (g *SmbClientGetter) Detect(req *Request) (bool, error) {
if len(req.Src) == 0 {
return false, nil
}
if req.Forced != "" {
// There's a getter being Forced
if !g.validScheme(req.Forced) {
// Current getter is not the Forced one
// Don't use it to try to download the artifact
return false, nil
}
}
isForcedGetter := req.Forced != "" && g.validScheme(req.Forced)
u, err := url.Parse(req.Src)
if err == nil && u.Scheme != "" {
if isForcedGetter {
// Is the Forced getter and source is a valid url
return true, nil
}
if g.validScheme(u.Scheme) {
return true, nil
}
// Valid url with a scheme that is not valid for current getter
return false, nil
}
return false, nil
}
func (g *SmbClientGetter) validScheme(scheme string) bool {
return scheme == "smb"
}
type smbPathError struct {
Path string
}
func (e *smbPathError) Error() string {
if e.Path == "" {
return "samba path should contain valid host, filepath, and authentication if necessary (smb://<user>:<password>@<host>/<file_path>)"
}
return fmt.Sprintf("samba path should contain valid host, filepath, and authentication if necessary (%s)", e.Path)
}
type smbGeneralError struct {
err error
}
func (e *smbGeneralError) Error() string {
if e != nil {
return fmt.Sprintf("smbclient cli needs to be installed and credentials provided when necessary. \n err: %s", e.err.Error())
}
return "smbclient cli needs to be installed and credentials provided when necessary."
}