vroot.go 12 KB

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