vroot.go 10 KB

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