package plg_backend_samba import ( "fmt" "io" "net" "net/url" "os" "strings" "time" "github.com/hirochachacha/go-smb2" . "github.com/mickael-kerjean/filestash/server/common" ) var SambaCache AppCache func init() { Backend.Register("samba", Samba{}) SambaCache = NewAppCache(30) SambaCache.OnEvict(func(key string, value interface{}) { smb := value.(*Samba) for key, _ := range smb.share { if err := smb.share[key].Umount(); err != nil { Log.Warning("samba: error unmounting share: %v", err) } } if err := smb.session.Logoff(); err != nil { Log.Warning("samba: error logging out: %v", err) } }) } type Samba struct { session *smb2.Session share map[string]*smb2.Share } func (smb Samba) Init(params map[string]string, app *App) (IBackend, error) { if c := SambaCache.Get(params); c != nil { return c.(*Samba), nil } if strings.HasPrefix(params["host"], "smb://") == false { params["host"] = "smb://" + params["host"] } if u, err := url.Parse(params["host"]); err == nil { params["host"] = u.Host if params["port"] == "" { params["port"] = u.Port() } if params["share"] == "" { params["share"] = strings.ReplaceAll(u.Path, "/", "") } if params["username"] == "" && u.User != nil { params["username"] = u.User.Username() } if params["password"] == "" && u.User != nil { params["password"], _ = u.User.Password() } } if params["port"] == "" { params["port"] = "445" } host := fmt.Sprintf("%s:%s", params["host"], params["port"]) conn, err := net.DialTimeout("tcp", host, 10*time.Second) if err != nil { Log.Debug("plg_backend_samba::netdial host[%s] err[%s]", host, err.Error()) return nil, err } smb.share = make(map[string]*smb2.Share, 0) smb.session, err = (&smb2.Dialer{ Initiator: &smb2.NTLMInitiator{ User: func() string { if params["username"] == "" { return "Guest" } return params["username"] }(), Password: params["password"], Domain: params["domain"], }, }).Dial(conn) if err != nil { Log.Debug("plg_backend_samba::smbdial host[%s] err[%s] username[%s] domain[%s]", host, err.Error(), params["username"], params["domain"]) return nil, err } if params["share"] == "" { names, err := smb.session.ListSharenames() if err != nil { Log.Debug("plg_backend_samba::list host[%s] err[%s]", host, err.Error()) return nil, err } for _, name := range names { if strings.HasSuffix(name, "$") { continue } if m, err := smb.session.Mount(name); err == nil { smb.share[name] = m } } } else { if m, err := smb.session.Mount(params["share"]); err == nil { smb.share[params["share"]] = m } } SambaCache.Set(params, &smb) return &smb, nil } func (smb Samba) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "samba", }, { Name: "host", Type: "text", Placeholder: "Hostname", }, { Name: "username", Type: "text", Placeholder: "Username", }, { Name: "password", Type: "password", Placeholder: "Password", }, { Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"samba_port", "samba_path", "samba_domain", "samba_share"}, }, { Id: "samba_path", Name: "path", Type: "text", Placeholder: "Path", }, { Id: "samba_port", Name: "port", Type: "number", Placeholder: "Port - eg: 445", }, { Id: "samba_domain", Name: "domain", Type: "text", Placeholder: "Domain", }, { Id: "samba_share", Name: "share", Type: "text", Placeholder: "Share Name", }, }, } } func (smb Samba) Ls(path string) ([]os.FileInfo, error) { if path == "/" { f := make([]os.FileInfo, 0) for key, _ := range smb.share { f = append(f, File{ FName: key, FType: "directory", }) } return f, nil } share, path, err := smb.toSambaPath(path) if err != nil { return nil, err } dir, err := share.Open(path) if err != nil { return nil, fromSambaErr(err) } defer dir.Close() fs, err := dir.Readdir(-1) return fs, fromSambaErr(err) } func (smb Samba) Cat(path string) (io.ReadCloser, error) { share, path, err := smb.toSambaPath(path) if err != nil { return nil, err } f, err := share.Open(path) return f, fromSambaErr(err) } func (smb Samba) Mkdir(path string) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } return fromSambaErr(share.Mkdir(path, os.ModeDir)) } func (smb Samba) Rm(path string) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } return fromSambaErr(share.RemoveAll(path)) } func (smb Samba) Mv(from, to string) error { fromShare, fromPath, err := smb.toSambaPath(from) if err != nil { return err } toShare, toPath, err := smb.toSambaPath(to) if err != nil { return err } if fromShare != toShare { return ErrNotImplemented } return fromSambaErr(fromShare.Rename(fromPath, toPath)) } func (smb Samba) Save(path string, content io.Reader) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } f, err := share.Create(path) if err != nil { return fromSambaErr(err) } if _, err = io.Copy(f, content); err != nil { f.Close() return fromSambaErr(err) } return f.Close() } func (smb Samba) Touch(path string) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } f, err := share.Create(path) if err != nil { return fromSambaErr(err) } return fromSambaErr(f.Close()) } func (smb Samba) toSambaPath(path string) (*smb2.Share, string, error) { p := strings.Split(strings.Trim(path, "/"), "/") if len(p) == 0 { return nil, "", ErrNotAllowed } sharename := p[0] oPath := strings.TrimLeft(strings.Join(p[1:], "\\"), "\\") if smb.share[sharename] == nil { return nil, "", ErrNotFound } return smb.share[sharename], oPath, nil } func fromSambaErr(err error) error { switch { case os.IsPermission(err): return ErrPermissionDenied case os.IsNotExist(err): return ErrNotFound default: return err } }