smbfs.go 12 KB

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