smbfs.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. package smbfs
  2. import (
  3. "fmt"
  4. "io"
  5. "io/fs"
  6. "log"
  7. "net"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "strings"
  12. "time"
  13. "github.com/hirochachacha/go-smb2"
  14. "imuslab.com/arozos/mod/filesystem/arozfs"
  15. )
  16. /*
  17. Server Message Block.go
  18. This is a file abstraction that mount SMB folders onto ArozOS as virtual drive
  19. */
  20. type ServerMessageBlockFileSystemAbstraction struct {
  21. UUID string
  22. Hierarchy string
  23. root string
  24. ipaddr string
  25. user string
  26. pass string
  27. conn *net.Conn
  28. session *smb2.Session
  29. share *smb2.Share
  30. tickerChan chan bool
  31. }
  32. func NewServerMessageBlockFileSystemAbstraction(uuid string, hierarchy string, ipaddr string, rootShare string, username string, password string) (ServerMessageBlockFileSystemAbstraction, error) {
  33. log.Println("[SMB-FS] Connecting to " + uuid + ":/ (" + ipaddr + ")")
  34. nd := net.Dialer{Timeout: 10 * time.Second}
  35. conn, err := nd.Dial("tcp", ipaddr)
  36. if err != nil {
  37. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  38. return ServerMessageBlockFileSystemAbstraction{}, err
  39. }
  40. d := &smb2.Dialer{
  41. Initiator: &smb2.NTLMInitiator{
  42. User: username,
  43. Password: password,
  44. },
  45. }
  46. s, err := d.Dial(conn)
  47. if err != nil {
  48. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  49. return ServerMessageBlockFileSystemAbstraction{}, err
  50. }
  51. //Mound remote storage
  52. fs, err := s.Mount(rootShare)
  53. if err != nil {
  54. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  55. return ServerMessageBlockFileSystemAbstraction{}, err
  56. }
  57. done := make(chan bool)
  58. fsAbstraction := ServerMessageBlockFileSystemAbstraction{
  59. UUID: uuid,
  60. Hierarchy: hierarchy,
  61. root: rootShare,
  62. ipaddr: ipaddr,
  63. user: username,
  64. pass: password,
  65. conn: &conn,
  66. session: s,
  67. share: fs,
  68. tickerChan: done,
  69. }
  70. //Create a ticker to check for renewing conncetion once an hour
  71. ticker := time.NewTicker(5 * time.Minute)
  72. go func(s *ServerMessageBlockFileSystemAbstraction) {
  73. for {
  74. select {
  75. case <-done:
  76. return
  77. case <-ticker.C:
  78. //Ping the smb server to make sure it knows we are alive
  79. _, err := s.share.Stat("")
  80. if err != nil {
  81. //Disconnect the connection and re-establish connection
  82. s.reconnect()
  83. }
  84. }
  85. }
  86. }(&fsAbstraction)
  87. return fsAbstraction, nil
  88. }
  89. func (a *ServerMessageBlockFileSystemAbstraction) reconnect() error {
  90. //Close existing connection
  91. a.Close()
  92. //Restart connection
  93. nd := net.Dialer{Timeout: 10 * time.Second}
  94. conn, err := nd.Dial("tcp", a.ipaddr)
  95. if err != nil {
  96. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  97. return err
  98. }
  99. d := &smb2.Dialer{
  100. Initiator: &smb2.NTLMInitiator{
  101. User: a.user,
  102. Password: a.pass,
  103. },
  104. }
  105. s, err := d.Dial(conn)
  106. if err != nil {
  107. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  108. return err
  109. }
  110. //Mound remote storage
  111. fs, err := s.Mount(a.root)
  112. if err != nil {
  113. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  114. return err
  115. }
  116. a.conn = &conn
  117. a.session = s
  118. a.share = fs
  119. fmt.Println("[SMB-FS] Connection restarted")
  120. return nil
  121. }
  122. func (a ServerMessageBlockFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
  123. filename = filterFilepath(filename)
  124. filename = toWinPath(filename)
  125. return a.share.Chmod(filename, mode)
  126. }
  127. func (a ServerMessageBlockFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
  128. return arozfs.ErrOperationNotSupported
  129. }
  130. func (a ServerMessageBlockFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  131. filename = filterFilepath(filename)
  132. filename = toWinPath(filename)
  133. return a.share.Chtimes(filename, atime, mtime)
  134. }
  135. func (a ServerMessageBlockFileSystemAbstraction) Create(filename string) (arozfs.File, error) {
  136. filename = filterFilepath(filename)
  137. f, err := a.share.Create(filename)
  138. if err != nil {
  139. return nil, err
  140. }
  141. af := NewSmbFsFile(f)
  142. return af, nil
  143. }
  144. func (a ServerMessageBlockFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
  145. filename = filterFilepath(filename)
  146. filename = toWinPath(filename)
  147. return a.share.Mkdir(filename, mode)
  148. }
  149. func (a ServerMessageBlockFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
  150. filename = filterFilepath(filename)
  151. filename = toWinPath(filename)
  152. return a.share.MkdirAll(filename, mode)
  153. }
  154. func (a ServerMessageBlockFileSystemAbstraction) Name() string {
  155. return ""
  156. }
  157. func (a ServerMessageBlockFileSystemAbstraction) Open(filename string) (arozfs.File, error) {
  158. filename = toWinPath(filterFilepath(filename))
  159. f, err := a.share.Open(filename)
  160. if err != nil {
  161. return nil, err
  162. }
  163. af := NewSmbFsFile(f)
  164. return af, nil
  165. }
  166. func (a ServerMessageBlockFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
  167. filename = toWinPath(filterFilepath(filename))
  168. f, err := a.share.OpenFile(filename, flag, perm)
  169. if err != nil {
  170. return nil, err
  171. }
  172. af := NewSmbFsFile(f)
  173. return af, nil
  174. }
  175. func (a ServerMessageBlockFileSystemAbstraction) Remove(filename string) error {
  176. filename = filterFilepath(filename)
  177. filename = toWinPath(filename)
  178. return a.share.Remove(filename)
  179. }
  180. func (a ServerMessageBlockFileSystemAbstraction) RemoveAll(filename string) error {
  181. filename = filterFilepath(filename)
  182. filename = toWinPath(filename)
  183. return a.share.RemoveAll(filename)
  184. }
  185. func (a ServerMessageBlockFileSystemAbstraction) Rename(oldname, newname string) error {
  186. oldname = toWinPath(filterFilepath(oldname))
  187. newname = toWinPath(filterFilepath(newname))
  188. return a.share.Rename(oldname, newname)
  189. }
  190. func (a ServerMessageBlockFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
  191. filename = toWinPath(filterFilepath(filename))
  192. return a.share.Stat(filename)
  193. }
  194. func (a ServerMessageBlockFileSystemAbstraction) Close() error {
  195. a.tickerChan <- true
  196. a.share.Umount()
  197. a.session.Logoff()
  198. conn := *(a.conn)
  199. conn.Close()
  200. return nil
  201. }
  202. /*
  203. Abstraction Utilities
  204. */
  205. func (a ServerMessageBlockFileSystemAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
  206. if strings.HasPrefix(subpath, a.UUID+":") {
  207. //This is full virtual path. Trim the uuid and correct the subpath
  208. subpath = strings.TrimPrefix(subpath, a.UUID+":")
  209. }
  210. subpath = filterFilepath(subpath)
  211. if a.Hierarchy == "user" {
  212. return toWinPath(filepath.ToSlash(filepath.Clean(filepath.Join("users", username, subpath)))), nil
  213. } else if a.Hierarchy == "public" {
  214. return toWinPath(filepath.ToSlash(filepath.Clean(subpath))), nil
  215. }
  216. return "", arozfs.ErrVpathResolveFailed
  217. }
  218. func (a ServerMessageBlockFileSystemAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
  219. fullpath = filterFilepath(fullpath)
  220. fullpath = strings.TrimPrefix(fullpath, "\\")
  221. vpath := a.UUID + ":/" + strings.ReplaceAll(fullpath, "\\", "/")
  222. return vpath, nil
  223. }
  224. func (a ServerMessageBlockFileSystemAbstraction) FileExists(realpath string) bool {
  225. realpath = toWinPath(filterFilepath(realpath))
  226. f, err := a.share.Open(realpath)
  227. if err != nil {
  228. return false
  229. }
  230. f.Close()
  231. return true
  232. }
  233. func (a ServerMessageBlockFileSystemAbstraction) IsDir(realpath string) bool {
  234. realpath = filterFilepath(realpath)
  235. realpath = toWinPath(realpath)
  236. stx, err := a.share.Stat(realpath)
  237. if err != nil {
  238. return false
  239. }
  240. return stx.IsDir()
  241. }
  242. func (a ServerMessageBlockFileSystemAbstraction) Glob(realpathWildcard string) ([]string, error) {
  243. realpathWildcard = strings.ReplaceAll(realpathWildcard, "[", "?")
  244. realpathWildcard = strings.ReplaceAll(realpathWildcard, "]", "?")
  245. matches, err := a.share.Glob(realpathWildcard)
  246. if err != nil {
  247. return []string{}, err
  248. }
  249. return matches, nil
  250. }
  251. func (a ServerMessageBlockFileSystemAbstraction) GetFileSize(realpath string) int64 {
  252. realpath = toWinPath(filterFilepath(realpath))
  253. stat, err := a.share.Stat(realpath)
  254. if err != nil {
  255. return 0
  256. }
  257. return stat.Size()
  258. }
  259. func (a ServerMessageBlockFileSystemAbstraction) GetModTime(realpath string) (int64, error) {
  260. realpath = toWinPath(filterFilepath(realpath))
  261. stat, err := a.share.Stat(realpath)
  262. if err != nil {
  263. return 0, nil
  264. }
  265. return stat.ModTime().Unix(), nil
  266. }
  267. func (a ServerMessageBlockFileSystemAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
  268. filename = toWinPath(filterFilepath(filename))
  269. return a.share.WriteFile(filename, content, mode)
  270. }
  271. func (a ServerMessageBlockFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
  272. filename = toWinPath(filterFilepath(filename))
  273. return a.share.ReadFile(filename)
  274. }
  275. func (a ServerMessageBlockFileSystemAbstraction) ReadDir(filename string) ([]fs.DirEntry, error) {
  276. filename = toWinPath(filterFilepath(filename))
  277. fis, err := a.share.ReadDir(filename)
  278. if err != nil {
  279. return []fs.DirEntry{}, err
  280. }
  281. dirEntires := []fs.DirEntry{}
  282. for _, fi := range fis {
  283. if fi.Name() == "System Volume Information" || fi.Name() == "$RECYCLE.BIN" || fi.Name() == "$MFT" {
  284. //System folders. Hide it
  285. continue
  286. }
  287. dirEntires = append(dirEntires, newDirEntryFromFileInfo(fi))
  288. }
  289. return dirEntires, nil
  290. }
  291. func (a ServerMessageBlockFileSystemAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  292. filename = toWinPath(filterFilepath(filename))
  293. f, err := a.share.OpenFile(filename, os.O_CREATE|os.O_WRONLY, mode)
  294. if err != nil {
  295. return err
  296. }
  297. p := make([]byte, 32768)
  298. for {
  299. _, err := stream.Read(p)
  300. if err != nil {
  301. if err == io.EOF {
  302. break
  303. } else {
  304. return err
  305. }
  306. }
  307. _, err = f.Write(p)
  308. if err != nil {
  309. return err
  310. }
  311. }
  312. return nil
  313. }
  314. func (a ServerMessageBlockFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
  315. filename = toWinPath(filterFilepath(filename))
  316. f, err := a.share.OpenFile(filename, os.O_RDONLY, 0755)
  317. if err != nil {
  318. return nil, err
  319. }
  320. return f, nil
  321. }
  322. //Note that walk on SMB is super slow. Avoid using this if possible.
  323. func (a ServerMessageBlockFileSystemAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
  324. root = toWinPath(filterFilepath(root))
  325. err := fs.WalkDir(a.share.DirFS(root), ".", func(path string, d fs.DirEntry, err error) error {
  326. if err != nil {
  327. return err
  328. }
  329. statInfo, err := d.Info()
  330. if err != nil {
  331. return err
  332. }
  333. walkFn(filepath.ToSlash(filepath.Join(root, path)), statInfo, err)
  334. return nil
  335. })
  336. return err
  337. }
  338. /*
  339. Optional Functions
  340. */
  341. func (a *ServerMessageBlockFileSystemAbstraction) CapacityInfo() {
  342. fsinfo, err := a.share.Statfs(".")
  343. if err != nil {
  344. return
  345. }
  346. fmt.Println(fsinfo)
  347. }
  348. /*
  349. Helper Functions
  350. */
  351. func toWinPath(filename string) string {
  352. backslashed := strings.ReplaceAll(filename, "/", "\\")
  353. return strings.TrimPrefix(backslashed, "\\")
  354. }
  355. func filterFilepath(rawpath string) string {
  356. rawpath = filepath.ToSlash(filepath.Clean(rawpath))
  357. rawpath = strings.TrimSpace(rawpath)
  358. if strings.HasPrefix(rawpath, "./") {
  359. return rawpath[1:]
  360. } else if rawpath == "." || rawpath == "" {
  361. return "/"
  362. }
  363. return rawpath
  364. }
  365. func wildCardToRegexp(pattern string) string {
  366. var result strings.Builder
  367. for i, literal := range strings.Split(pattern, "*") {
  368. // Replace * with .*
  369. if i > 0 {
  370. result.WriteString(".*")
  371. }
  372. // Quote any regular expression meta characters in the
  373. // literal text.
  374. result.WriteString(regexp.QuoteMeta(literal))
  375. }
  376. return result.String()
  377. }