vroot.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. package sftpserver
  2. import (
  3. "errors"
  4. "io"
  5. "io/fs"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "sort"
  10. "strings"
  11. "syscall"
  12. "time"
  13. "github.com/pkg/sftp"
  14. "imuslab.com/arozos/mod/filesystem"
  15. "imuslab.com/arozos/mod/filesystem/arozfs"
  16. )
  17. //Root of the serving tree
  18. type root struct {
  19. username string
  20. rootFile *rootFolder
  21. startDirectory string
  22. fshs []*filesystem.FileSystemHandler
  23. }
  24. type rootFolder struct {
  25. name string
  26. modtime time.Time
  27. isdir bool
  28. content []byte
  29. }
  30. //Fake folders in root for vroot redirections
  31. type rootEntry struct {
  32. thisFsh *filesystem.FileSystemHandler
  33. }
  34. func NewVrootEmulatedDirEntry(fsh *filesystem.FileSystemHandler) *rootEntry {
  35. return &rootEntry{
  36. thisFsh: fsh,
  37. }
  38. }
  39. func (r *rootEntry) Name() string {
  40. return r.thisFsh.UUID
  41. }
  42. func (r *rootEntry) Size() int64 {
  43. return 0
  44. }
  45. func (r *rootEntry) Mode() os.FileMode {
  46. return fs.ModeDir
  47. }
  48. func (r *rootEntry) ModTime() time.Time {
  49. return time.Now()
  50. }
  51. func (r *rootEntry) IsDir() bool {
  52. return true
  53. }
  54. func (r *rootEntry) Sys() interface{} {
  55. return nil
  56. }
  57. type sftpFileInterface interface {
  58. Name() string
  59. Size() int64
  60. Mode() os.FileMode
  61. ModTime() time.Time
  62. IsDir() bool
  63. Sys() interface{}
  64. ReadAt([]byte, int64) (int, error)
  65. WriteAt([]byte, int64) (int, error)
  66. }
  67. //Wrapper for the arozfs File to provide missing functions
  68. type wrappedArozFile struct {
  69. file arozfs.File
  70. }
  71. func newArozFileWrapper(arozfile arozfs.File) *wrappedArozFile {
  72. return &wrappedArozFile{file: arozfile}
  73. }
  74. func (f *wrappedArozFile) Name() string {
  75. return f.file.Name()
  76. }
  77. func (f *wrappedArozFile) Size() int64 {
  78. stat, err := f.file.Stat()
  79. if err != nil {
  80. return 0
  81. }
  82. return stat.Size()
  83. }
  84. func (f *wrappedArozFile) Mode() os.FileMode {
  85. stat, err := f.file.Stat()
  86. if err != nil {
  87. return 0
  88. }
  89. return stat.Mode()
  90. }
  91. func (f *wrappedArozFile) ModTime() time.Time {
  92. stat, err := f.file.Stat()
  93. if err != nil {
  94. return time.Time{}
  95. }
  96. return stat.ModTime()
  97. }
  98. func (f *wrappedArozFile) IsDir() bool {
  99. stat, err := f.file.Stat()
  100. if err != nil {
  101. return false
  102. }
  103. return stat.IsDir()
  104. }
  105. func (f *wrappedArozFile) Sys() interface{} {
  106. return nil
  107. }
  108. func (f *wrappedArozFile) ReadAt(b []byte, off int64) (int, error) {
  109. return f.file.ReadAt(b, off)
  110. }
  111. func (f *wrappedArozFile) WriteAt(b []byte, off int64) (int, error) {
  112. return f.file.WriteAt(b, off)
  113. }
  114. func GetNewSFTPRoot(username string, accessibleFileSystemHandlers []*filesystem.FileSystemHandler) sftp.Handlers {
  115. root := &root{
  116. username: username,
  117. rootFile: &rootFolder{name: "/", modtime: time.Now(), isdir: true},
  118. startDirectory: "/",
  119. fshs: accessibleFileSystemHandlers,
  120. }
  121. return sftp.Handlers{root, root, root, root}
  122. }
  123. func (fs *root) getFshFromID(fshID string) *filesystem.FileSystemHandler {
  124. for _, thisFsh := range fs.fshs {
  125. if thisFsh.UUID == fshID && !thisFsh.Closed {
  126. return thisFsh
  127. }
  128. }
  129. return nil
  130. }
  131. func (fs *root) Fileread(r *sftp.Request) (io.ReaderAt, error) {
  132. flags := r.Pflags()
  133. if !flags.Read {
  134. // sanity check
  135. return nil, os.ErrInvalid
  136. }
  137. return fs.OpenFile(r)
  138. }
  139. func (fs *root) Filewrite(r *sftp.Request) (io.WriterAt, error) {
  140. if arozfs.ToSlash(filepath.Dir(r.Filepath)) == "/" {
  141. //Uploading to virtual root folder. Return error
  142. return nil, errors.New("ArozOS SFTP root is read only")
  143. }
  144. fsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(r.Filepath)
  145. if err != nil {
  146. return nil, err
  147. }
  148. f, err := fsh.FileSystemAbstraction.OpenFile(rpath, os.O_CREATE|os.O_WRONLY, 0775)
  149. if err != nil {
  150. return nil, err
  151. }
  152. return f, nil
  153. }
  154. func (fs *root) OpenFile(r *sftp.Request) (sftp.WriterAtReaderAt, error) {
  155. //fmt.Println("Open File", r.Filepath)
  156. fsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(r.Filepath)
  157. if err != nil {
  158. return nil, err
  159. }
  160. f, err := fsh.FileSystemAbstraction.OpenFile(rpath, os.O_RDWR, 0775)
  161. if err != nil {
  162. return nil, err
  163. }
  164. return f, nil
  165. }
  166. func (fs *root) Filecmd(r *sftp.Request) error {
  167. switch r.Method {
  168. case "Setstat":
  169. return nil
  170. case "Rename":
  171. // SFTP-v2: "It is an error if there already exists a file with the name specified by newpath."
  172. // This varies from the POSIX specification, which allows limited replacement of target files.
  173. //if fs.exists(r.Target) {
  174. // return os.ErrExist
  175. //}
  176. return fs.rename(r.Filepath, r.Target)
  177. case "Rmdir":
  178. return fs.rmdir(r.Filepath)
  179. case "Remove":
  180. // IEEE 1003.1 remove explicitly can unlink files and remove empty directories.
  181. // We use instead here the semantics of unlink, which is allowed to be restricted against directories.
  182. return fs.unlink(r.Filepath)
  183. case "Mkdir":
  184. return fs.mkdir(r.Filepath)
  185. case "Link":
  186. return fs.link(r.Filepath, r.Target)
  187. case "Symlink":
  188. // NOTE: r.Filepath is the target, and r.Target is the linkpath.
  189. return fs.symlink(r.Filepath, r.Target)
  190. }
  191. return errors.New("unsupported")
  192. }
  193. func (fs *root) rename(oldpath, newpath string) error {
  194. oldFsh, _, realOldPath, err := fs.getFshAndSubpathFromSFTPPathname(oldpath)
  195. if err != nil {
  196. return err
  197. }
  198. newFsh, _, realNewPath, err := fs.getFshAndSubpathFromSFTPPathname(newpath)
  199. if err != nil {
  200. return err
  201. }
  202. if oldFsh.UUID == newFsh.UUID {
  203. //Use rename function
  204. err = oldFsh.FileSystemAbstraction.Rename(realOldPath, realNewPath)
  205. if err != nil {
  206. return err
  207. }
  208. } else {
  209. //Cross root rename (aka move)
  210. src, err := oldFsh.FileSystemAbstraction.ReadStream(realOldPath)
  211. if err != nil {
  212. return err
  213. }
  214. defer src.Close()
  215. err = newFsh.FileSystemAbstraction.WriteStream(realNewPath, src, 0775)
  216. if err != nil {
  217. return err
  218. }
  219. //Remove the src
  220. //oldFsh.FileSystemAbstraction.RemoveAll(realOldPath)
  221. }
  222. return nil
  223. }
  224. func (fs *root) PosixRename(r *sftp.Request) error {
  225. return fs.rename(r.Filepath, r.Target)
  226. }
  227. func (fs *root) StatVFS(r *sftp.Request) (*sftp.StatVFS, error) {
  228. return nil, errors.New("unsupported")
  229. }
  230. func (fs *root) mkdir(pathname string) error {
  231. fsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(pathname)
  232. if err != nil {
  233. return err
  234. }
  235. return fsh.FileSystemAbstraction.MkdirAll(rpath, 0775)
  236. }
  237. func (fs *root) rmdir(pathname string) error {
  238. fsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(pathname)
  239. if err != nil {
  240. return err
  241. }
  242. return fsh.FileSystemAbstraction.RemoveAll(rpath)
  243. }
  244. func (fs *root) link(oldpath, newpath string) error {
  245. return errors.New("unsupported")
  246. }
  247. // symlink() creates a symbolic link named `linkpath` which contains the string `target`.
  248. // NOTE! This would be called with `symlink(req.Filepath, req.Target)` due to different semantics.
  249. func (fs *root) symlink(target, linkpath string) error {
  250. return errors.New("unsupported")
  251. }
  252. func (fs *root) unlink(pathname string) error {
  253. fsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(pathname)
  254. if err != nil {
  255. return err
  256. }
  257. if fsh.FileSystemAbstraction.IsDir(rpath) {
  258. // IEEE 1003.1: implementations may opt out of allowing the unlinking of directories.
  259. // SFTP-v2: SSH_FXP_REMOVE may not remove directories.
  260. return os.ErrInvalid
  261. }
  262. return fsh.FileSystemAbstraction.Remove(rpath)
  263. }
  264. type listerat []os.FileInfo
  265. // Modeled after strings.Reader's ReadAt() implementation
  266. func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
  267. var n int
  268. if offset >= int64(len(f)) {
  269. return 0, io.EOF
  270. }
  271. n = copy(ls, f[offset:])
  272. if n < len(ls) {
  273. return n, io.EOF
  274. }
  275. return n, nil
  276. }
  277. func (fs *root) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
  278. switch r.Method {
  279. case "List":
  280. files, err := fs.readdir(r.Filepath)
  281. if err != nil {
  282. return nil, err
  283. }
  284. return listerat(files), nil
  285. case "Stat":
  286. file, err := fs.fetch(r.Filepath)
  287. if err != nil {
  288. return nil, err
  289. }
  290. return listerat{file}, nil
  291. case "Readlink":
  292. return nil, errors.New("unsupported")
  293. }
  294. return nil, errors.New("unsupported")
  295. }
  296. func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
  297. if cleanPath(pathname) == "/" {
  298. //Handle special root listing
  299. results := []os.FileInfo{}
  300. for _, fsh := range fs.fshs {
  301. results = append(results, NewVrootEmulatedDirEntry(fsh))
  302. }
  303. return results, nil
  304. }
  305. //Get the content of the dir using fsh infrastructure
  306. targetFsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(pathname)
  307. if err != nil {
  308. return nil, err
  309. }
  310. if !targetFsh.FileSystemAbstraction.IsDir(rpath) {
  311. return nil, syscall.ENOTDIR
  312. }
  313. //Read Dir, and convert the results into os.FileInfo
  314. entries, err := targetFsh.FileSystemAbstraction.ReadDir(rpath)
  315. if err != nil {
  316. return nil, err
  317. }
  318. files := []os.FileInfo{}
  319. for _, entry := range entries {
  320. i, err := entry.Info()
  321. if err != nil {
  322. continue
  323. }
  324. files = append(files, i)
  325. }
  326. sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
  327. return files, nil
  328. }
  329. func (fs *root) readlink(pathname string) (string, error) {
  330. return "", errors.New("unsupported")
  331. }
  332. // implements LstatFileLister interface
  333. func (fs *root) Lstat(r *sftp.Request) (sftp.ListerAt, error) {
  334. file, err := fs.lfetch(r.Filepath)
  335. if err != nil {
  336. return nil, err
  337. }
  338. return listerat{file}, nil
  339. }
  340. // implements RealpathFileLister interface
  341. func (fs *root) Realpath(p string) string {
  342. if fs.startDirectory == "" || fs.startDirectory == "/" {
  343. return cleanPath(p)
  344. }
  345. return cleanPathWithBase(fs.startDirectory, p)
  346. }
  347. //Convert sftp raw path into fsh, subpath and realpath. return err if any
  348. func (fs *root) getFshAndSubpathFromSFTPPathname(pathname string) (*filesystem.FileSystemHandler, string, string, error) {
  349. pathname = strings.TrimSpace(pathname)
  350. if pathname[0:1] != "/" {
  351. pathname = "/" + pathname
  352. }
  353. pathChunks := strings.Split(pathname, "/")
  354. vrootID := pathChunks[1]
  355. subpath := ""
  356. if len(pathChunks) >= 2 {
  357. //Something like /user/Music
  358. subpath = strings.Join(pathChunks[2:], "/")
  359. }
  360. //Get target fsh
  361. fsh := fs.getFshFromID(vrootID)
  362. if fsh == nil {
  363. //Target fsh not found
  364. return nil, "", "", os.ErrExist
  365. }
  366. //Combined virtual path
  367. vpath := vrootID + ":/" + subpath
  368. //Translate it realpath and get from fsh
  369. fshAbs := fsh.FileSystemAbstraction
  370. rpath, err := fshAbs.VirtualPathToRealPath(vpath, fs.username)
  371. if err != nil {
  372. return nil, "", "", err
  373. }
  374. return fsh, subpath, rpath, nil
  375. }
  376. func (fs *root) lfetch(path string) (sftpFileInterface, error) {
  377. path = strings.TrimSpace(path)
  378. if path == "/" {
  379. //fmt.Println("Requesting SFTP Root")
  380. return fs.rootFile, nil
  381. }
  382. //Fetching path other than root. Extract the vroot id from the path
  383. fsh, _, rpath, err := fs.getFshAndSubpathFromSFTPPathname(path)
  384. if err != nil {
  385. return nil, err
  386. }
  387. fshAbs := fsh.FileSystemAbstraction
  388. if !fshAbs.FileExists(rpath) {
  389. //Target file not exists
  390. return nil, os.ErrExist
  391. }
  392. //Open the file and return
  393. f, err := fshAbs.Open(rpath)
  394. if err != nil {
  395. return nil, err
  396. }
  397. f2 := newArozFileWrapper(f)
  398. return f2, nil
  399. }
  400. func (fs *root) fetch(path string) (sftpFileInterface, error) {
  401. file, err := fs.lfetch(path)
  402. if err != nil {
  403. return nil, err
  404. }
  405. return file, nil
  406. }
  407. // Have memFile fulfill os.FileInfo interface
  408. func (f *rootFolder) Name() string { return path.Base(f.name) }
  409. func (f *rootFolder) Size() int64 {
  410. return int64(len(f.content))
  411. }
  412. func (f *rootFolder) Mode() os.FileMode {
  413. return os.FileMode(0755) | os.ModeDir
  414. }
  415. func (f *rootFolder) ModTime() time.Time { return f.modtime }
  416. func (f *rootFolder) IsDir() bool { return f.isdir }
  417. func (f *rootFolder) Sys() interface{} {
  418. return nil
  419. }
  420. func (f *rootFolder) ReadAt(b []byte, off int64) (int, error) {
  421. return 0, errors.New("root folder not support writeAt")
  422. }
  423. func (f *rootFolder) WriteAt(b []byte, off int64) (int, error) {
  424. // mimic write delays, should be optional
  425. time.Sleep(time.Microsecond * time.Duration(len(b)))
  426. return 0, errors.New("root folder not support writeAt")
  427. }
  428. /*
  429. Utilities
  430. */
  431. // Makes sure we have a clean POSIX (/) absolute path to work with
  432. func cleanPath(p string) string {
  433. return cleanPathWithBase("/", p)
  434. }
  435. func cleanPathWithBase(base, p string) string {
  436. p = filepath.ToSlash(filepath.Clean(p))
  437. if !path.IsAbs(p) {
  438. return path.Join(base, p)
  439. }
  440. return p
  441. }