sftpfs.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. package vfs
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net"
  8. "net/http"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "strings"
  13. "sync"
  14. "time"
  15. "github.com/eikenb/pipeat"
  16. "github.com/pkg/sftp"
  17. "github.com/rs/xid"
  18. "github.com/sftpgo/sdk"
  19. "golang.org/x/crypto/ssh"
  20. "github.com/drakkan/sftpgo/v2/kms"
  21. "github.com/drakkan/sftpgo/v2/logger"
  22. "github.com/drakkan/sftpgo/v2/util"
  23. "github.com/drakkan/sftpgo/v2/version"
  24. )
  25. const (
  26. // sftpFsName is the name for the SFTP Fs implementation
  27. sftpFsName = "sftpfs"
  28. )
  29. // ErrSFTPLoop defines the error to return if an SFTP loop is detected
  30. var ErrSFTPLoop = errors.New("SFTP loop or nested local SFTP folders detected")
  31. // SFTPFsConfig defines the configuration for SFTP based filesystem
  32. type SFTPFsConfig struct {
  33. sdk.BaseSFTPFsConfig
  34. Password *kms.Secret `json:"password,omitempty"`
  35. PrivateKey *kms.Secret `json:"private_key,omitempty"`
  36. forbiddenSelfUsernames []string `json:"-"`
  37. }
  38. // HideConfidentialData hides confidential data
  39. func (c *SFTPFsConfig) HideConfidentialData() {
  40. if c.Password != nil {
  41. c.Password.Hide()
  42. }
  43. if c.PrivateKey != nil {
  44. c.PrivateKey.Hide()
  45. }
  46. }
  47. func (c *SFTPFsConfig) isEqual(other *SFTPFsConfig) bool {
  48. if c.Endpoint != other.Endpoint {
  49. return false
  50. }
  51. if c.Username != other.Username {
  52. return false
  53. }
  54. if c.Prefix != other.Prefix {
  55. return false
  56. }
  57. if c.DisableCouncurrentReads != other.DisableCouncurrentReads {
  58. return false
  59. }
  60. if c.BufferSize != other.BufferSize {
  61. return false
  62. }
  63. if len(c.Fingerprints) != len(other.Fingerprints) {
  64. return false
  65. }
  66. for _, fp := range c.Fingerprints {
  67. if !util.IsStringInSlice(fp, other.Fingerprints) {
  68. return false
  69. }
  70. }
  71. c.setEmptyCredentialsIfNil()
  72. other.setEmptyCredentialsIfNil()
  73. if !c.Password.IsEqual(other.Password) {
  74. return false
  75. }
  76. return c.PrivateKey.IsEqual(other.PrivateKey)
  77. }
  78. func (c *SFTPFsConfig) setEmptyCredentialsIfNil() {
  79. if c.Password == nil {
  80. c.Password = kms.NewEmptySecret()
  81. }
  82. if c.PrivateKey == nil {
  83. c.PrivateKey = kms.NewEmptySecret()
  84. }
  85. }
  86. // Validate returns an error if the configuration is not valid
  87. func (c *SFTPFsConfig) Validate() error {
  88. c.setEmptyCredentialsIfNil()
  89. if c.Endpoint == "" {
  90. return errors.New("endpoint cannot be empty")
  91. }
  92. _, _, err := net.SplitHostPort(c.Endpoint)
  93. if err != nil {
  94. return fmt.Errorf("invalid endpoint: %v", err)
  95. }
  96. if c.Username == "" {
  97. return errors.New("username cannot be empty")
  98. }
  99. if c.BufferSize < 0 || c.BufferSize > 16 {
  100. return errors.New("invalid buffer_size, valid range is 0-16")
  101. }
  102. if err := c.validateCredentials(); err != nil {
  103. return err
  104. }
  105. if c.Prefix != "" {
  106. c.Prefix = util.CleanPath(c.Prefix)
  107. } else {
  108. c.Prefix = "/"
  109. }
  110. return nil
  111. }
  112. func (c *SFTPFsConfig) validateCredentials() error {
  113. if c.Password.IsEmpty() && c.PrivateKey.IsEmpty() {
  114. return errors.New("credentials cannot be empty")
  115. }
  116. if c.Password.IsEncrypted() && !c.Password.IsValid() {
  117. return errors.New("invalid encrypted password")
  118. }
  119. if !c.Password.IsEmpty() && !c.Password.IsValidInput() {
  120. return errors.New("invalid password")
  121. }
  122. if c.PrivateKey.IsEncrypted() && !c.PrivateKey.IsValid() {
  123. return errors.New("invalid encrypted private key")
  124. }
  125. if !c.PrivateKey.IsEmpty() && !c.PrivateKey.IsValidInput() {
  126. return errors.New("invalid private key")
  127. }
  128. return nil
  129. }
  130. // EncryptCredentials encrypts password and/or private key if they are in plain text
  131. func (c *SFTPFsConfig) EncryptCredentials(additionalData string) error {
  132. if c.Password.IsPlain() {
  133. c.Password.SetAdditionalData(additionalData)
  134. if err := c.Password.Encrypt(); err != nil {
  135. return err
  136. }
  137. }
  138. if c.PrivateKey.IsPlain() {
  139. c.PrivateKey.SetAdditionalData(additionalData)
  140. if err := c.PrivateKey.Encrypt(); err != nil {
  141. return err
  142. }
  143. }
  144. return nil
  145. }
  146. // SFTPFs is a Fs implementation for SFTP backends
  147. type SFTPFs struct {
  148. sync.Mutex
  149. connectionID string
  150. // if not empty this fs is mouted as virtual folder in the specified path
  151. mountPath string
  152. localTempDir string
  153. config *SFTPFsConfig
  154. sshClient *ssh.Client
  155. sftpClient *sftp.Client
  156. err chan error
  157. }
  158. // NewSFTPFs returns an SFTPFs object that allows to interact with an SFTP server
  159. func NewSFTPFs(connectionID, mountPath, localTempDir string, forbiddenSelfUsernames []string, config SFTPFsConfig) (Fs, error) {
  160. if localTempDir == "" {
  161. if tempPath != "" {
  162. localTempDir = tempPath
  163. } else {
  164. localTempDir = filepath.Clean(os.TempDir())
  165. }
  166. }
  167. if err := config.Validate(); err != nil {
  168. return nil, err
  169. }
  170. if !config.Password.IsEmpty() {
  171. if err := config.Password.TryDecrypt(); err != nil {
  172. return nil, err
  173. }
  174. }
  175. if !config.PrivateKey.IsEmpty() {
  176. if err := config.PrivateKey.TryDecrypt(); err != nil {
  177. return nil, err
  178. }
  179. }
  180. config.forbiddenSelfUsernames = forbiddenSelfUsernames
  181. sftpFs := &SFTPFs{
  182. connectionID: connectionID,
  183. mountPath: mountPath,
  184. localTempDir: localTempDir,
  185. config: &config,
  186. err: make(chan error, 1),
  187. }
  188. err := sftpFs.createConnection()
  189. return sftpFs, err
  190. }
  191. // Name returns the name for the Fs implementation
  192. func (fs *SFTPFs) Name() string {
  193. return fmt.Sprintf("%v %#v", sftpFsName, fs.config.Endpoint)
  194. }
  195. // ConnectionID returns the connection ID associated to this Fs implementation
  196. func (fs *SFTPFs) ConnectionID() string {
  197. return fs.connectionID
  198. }
  199. // Stat returns a FileInfo describing the named file
  200. func (fs *SFTPFs) Stat(name string) (os.FileInfo, error) {
  201. if err := fs.checkConnection(); err != nil {
  202. return nil, err
  203. }
  204. return fs.sftpClient.Stat(name)
  205. }
  206. // Lstat returns a FileInfo describing the named file
  207. func (fs *SFTPFs) Lstat(name string) (os.FileInfo, error) {
  208. if err := fs.checkConnection(); err != nil {
  209. return nil, err
  210. }
  211. return fs.sftpClient.Lstat(name)
  212. }
  213. // Open opens the named file for reading
  214. func (fs *SFTPFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error) {
  215. if err := fs.checkConnection(); err != nil {
  216. return nil, nil, nil, err
  217. }
  218. f, err := fs.sftpClient.Open(name)
  219. if err != nil {
  220. return nil, nil, nil, err
  221. }
  222. if fs.config.BufferSize == 0 {
  223. return f, nil, nil, err
  224. }
  225. if offset > 0 {
  226. _, err = f.Seek(offset, io.SeekStart)
  227. if err != nil {
  228. f.Close()
  229. return nil, nil, nil, err
  230. }
  231. }
  232. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  233. if err != nil {
  234. f.Close()
  235. return nil, nil, nil, err
  236. }
  237. go func() {
  238. // if we enable buffering the client stalls
  239. //br := bufio.NewReaderSize(f, int(fs.config.BufferSize)*1024*1024)
  240. //n, err := fs.copy(w, br)
  241. n, err := io.Copy(w, f)
  242. w.CloseWithError(err) //nolint:errcheck
  243. f.Close()
  244. fsLog(fs, logger.LevelDebug, "download completed, path: %#v size: %v, err: %v", name, n, err)
  245. }()
  246. return nil, r, nil, nil
  247. }
  248. // Create creates or opens the named file for writing
  249. func (fs *SFTPFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
  250. err := fs.checkConnection()
  251. if err != nil {
  252. return nil, nil, nil, err
  253. }
  254. if fs.config.BufferSize == 0 {
  255. var f File
  256. if flag == 0 {
  257. f, err = fs.sftpClient.Create(name)
  258. } else {
  259. f, err = fs.sftpClient.OpenFile(name, flag)
  260. }
  261. return f, nil, nil, err
  262. }
  263. // buffering is enabled
  264. f, err := fs.sftpClient.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
  265. if err != nil {
  266. return nil, nil, nil, err
  267. }
  268. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  269. if err != nil {
  270. f.Close()
  271. return nil, nil, nil, err
  272. }
  273. p := NewPipeWriter(w)
  274. go func() {
  275. bw := bufio.NewWriterSize(f, int(fs.config.BufferSize)*1024*1024)
  276. // we don't use io.Copy since bufio.Writer implements io.WriterTo and
  277. // so it calls the sftp.File WriteTo method without buffering
  278. n, err := fs.copy(bw, r)
  279. errFlush := bw.Flush()
  280. if err == nil && errFlush != nil {
  281. err = errFlush
  282. }
  283. var errTruncate error
  284. if err != nil {
  285. errTruncate = f.Truncate(n)
  286. }
  287. errClose := f.Close()
  288. if err == nil && errClose != nil {
  289. err = errClose
  290. }
  291. r.CloseWithError(err) //nolint:errcheck
  292. p.Done(err)
  293. fsLog(fs, logger.LevelDebug, "upload completed, path: %#v, readed bytes: %v, err: %v err truncate: %v",
  294. name, n, err, errTruncate)
  295. }()
  296. return nil, p, nil, nil
  297. }
  298. // Rename renames (moves) source to target.
  299. func (fs *SFTPFs) Rename(source, target string) error {
  300. if err := fs.checkConnection(); err != nil {
  301. return err
  302. }
  303. if _, ok := fs.sftpClient.HasExtension("[email protected]"); ok {
  304. return fs.sftpClient.PosixRename(source, target)
  305. }
  306. return fs.sftpClient.Rename(source, target)
  307. }
  308. // Remove removes the named file or (empty) directory.
  309. func (fs *SFTPFs) Remove(name string, isDir bool) error {
  310. if err := fs.checkConnection(); err != nil {
  311. return err
  312. }
  313. return fs.sftpClient.Remove(name)
  314. }
  315. // Mkdir creates a new directory with the specified name and default permissions
  316. func (fs *SFTPFs) Mkdir(name string) error {
  317. if err := fs.checkConnection(); err != nil {
  318. return err
  319. }
  320. return fs.sftpClient.Mkdir(name)
  321. }
  322. // MkdirAll creates a directory named path, along with any necessary parents,
  323. // and returns nil, or else returns an error.
  324. // If path is already a directory, MkdirAll does nothing and returns nil.
  325. func (fs *SFTPFs) MkdirAll(name string, uid int, gid int) error {
  326. if err := fs.checkConnection(); err != nil {
  327. return err
  328. }
  329. return fs.sftpClient.MkdirAll(name)
  330. }
  331. // Symlink creates source as a symbolic link to target.
  332. func (fs *SFTPFs) Symlink(source, target string) error {
  333. if err := fs.checkConnection(); err != nil {
  334. return err
  335. }
  336. return fs.sftpClient.Symlink(source, target)
  337. }
  338. // Readlink returns the destination of the named symbolic link
  339. func (fs *SFTPFs) Readlink(name string) (string, error) {
  340. if err := fs.checkConnection(); err != nil {
  341. return "", err
  342. }
  343. return fs.sftpClient.ReadLink(name)
  344. }
  345. // Chown changes the numeric uid and gid of the named file.
  346. func (fs *SFTPFs) Chown(name string, uid int, gid int) error {
  347. if err := fs.checkConnection(); err != nil {
  348. return err
  349. }
  350. return fs.sftpClient.Chown(name, uid, gid)
  351. }
  352. // Chmod changes the mode of the named file to mode.
  353. func (fs *SFTPFs) Chmod(name string, mode os.FileMode) error {
  354. if err := fs.checkConnection(); err != nil {
  355. return err
  356. }
  357. return fs.sftpClient.Chmod(name, mode)
  358. }
  359. // Chtimes changes the access and modification times of the named file.
  360. func (fs *SFTPFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error {
  361. if err := fs.checkConnection(); err != nil {
  362. return err
  363. }
  364. return fs.sftpClient.Chtimes(name, atime, mtime)
  365. }
  366. // Truncate changes the size of the named file.
  367. func (fs *SFTPFs) Truncate(name string, size int64) error {
  368. if err := fs.checkConnection(); err != nil {
  369. return err
  370. }
  371. return fs.sftpClient.Truncate(name, size)
  372. }
  373. // ReadDir reads the directory named by dirname and returns
  374. // a list of directory entries.
  375. func (fs *SFTPFs) ReadDir(dirname string) ([]os.FileInfo, error) {
  376. if err := fs.checkConnection(); err != nil {
  377. return nil, err
  378. }
  379. return fs.sftpClient.ReadDir(dirname)
  380. }
  381. // IsUploadResumeSupported returns true if resuming uploads is supported.
  382. func (fs *SFTPFs) IsUploadResumeSupported() bool {
  383. return fs.config.BufferSize == 0
  384. }
  385. // IsAtomicUploadSupported returns true if atomic upload is supported.
  386. func (fs *SFTPFs) IsAtomicUploadSupported() bool {
  387. return fs.config.BufferSize == 0
  388. }
  389. // IsNotExist returns a boolean indicating whether the error is known to
  390. // report that a file or directory does not exist
  391. func (*SFTPFs) IsNotExist(err error) bool {
  392. return os.IsNotExist(err)
  393. }
  394. // IsPermission returns a boolean indicating whether the error is known to
  395. // report that permission is denied.
  396. func (*SFTPFs) IsPermission(err error) bool {
  397. if _, ok := err.(*pathResolutionError); ok {
  398. return true
  399. }
  400. return os.IsPermission(err)
  401. }
  402. // IsNotSupported returns true if the error indicate an unsupported operation
  403. func (*SFTPFs) IsNotSupported(err error) bool {
  404. if err == nil {
  405. return false
  406. }
  407. return err == ErrVfsUnsupported
  408. }
  409. // CheckRootPath creates the specified local root directory if it does not exists
  410. func (fs *SFTPFs) CheckRootPath(username string, uid int, gid int) bool {
  411. if fs.config.BufferSize > 0 {
  412. // we need a local directory for temporary files
  413. osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "")
  414. osFs.CheckRootPath(username, uid, gid)
  415. }
  416. if fs.config.Prefix == "/" {
  417. return true
  418. }
  419. if err := fs.MkdirAll(fs.config.Prefix, uid, gid); err != nil {
  420. fsLog(fs, logger.LevelDebug, "error creating root directory %#v for user %#v: %v", fs.config.Prefix, username, err)
  421. return false
  422. }
  423. return true
  424. }
  425. // ScanRootDirContents returns the number of files contained in a directory and
  426. // their size
  427. func (fs *SFTPFs) ScanRootDirContents() (int, int64, error) {
  428. return fs.GetDirSize(fs.config.Prefix)
  429. }
  430. // CheckMetadata checks the metadata consistency
  431. func (*SFTPFs) CheckMetadata() error {
  432. return nil
  433. }
  434. // GetAtomicUploadPath returns the path to use for an atomic upload
  435. func (*SFTPFs) GetAtomicUploadPath(name string) string {
  436. dir := path.Dir(name)
  437. guid := xid.New().String()
  438. return path.Join(dir, ".sftpgo-upload."+guid+"."+path.Base(name))
  439. }
  440. // GetRelativePath returns the path for a file relative to the sftp prefix if any.
  441. // This is the path as seen by SFTPGo users
  442. func (fs *SFTPFs) GetRelativePath(name string) string {
  443. rel := path.Clean(name)
  444. if rel == "." {
  445. rel = ""
  446. }
  447. if !path.IsAbs(rel) {
  448. return "/" + rel
  449. }
  450. if fs.config.Prefix != "/" {
  451. if !strings.HasPrefix(rel, fs.config.Prefix) {
  452. rel = "/"
  453. }
  454. rel = path.Clean("/" + strings.TrimPrefix(rel, fs.config.Prefix))
  455. }
  456. if fs.mountPath != "" {
  457. rel = path.Join(fs.mountPath, rel)
  458. }
  459. return rel
  460. }
  461. // Walk walks the file tree rooted at root, calling walkFn for each file or
  462. // directory in the tree, including root
  463. func (fs *SFTPFs) Walk(root string, walkFn filepath.WalkFunc) error {
  464. if err := fs.checkConnection(); err != nil {
  465. return err
  466. }
  467. walker := fs.sftpClient.Walk(root)
  468. for walker.Step() {
  469. err := walker.Err()
  470. if err != nil {
  471. return err
  472. }
  473. err = walkFn(walker.Path(), walker.Stat(), err)
  474. if err != nil {
  475. return err
  476. }
  477. }
  478. return nil
  479. }
  480. // Join joins any number of path elements into a single path
  481. func (*SFTPFs) Join(elem ...string) string {
  482. return path.Join(elem...)
  483. }
  484. // HasVirtualFolders returns true if folders are emulated
  485. func (*SFTPFs) HasVirtualFolders() bool {
  486. return false
  487. }
  488. // ResolvePath returns the matching filesystem path for the specified virtual path
  489. func (fs *SFTPFs) ResolvePath(virtualPath string) (string, error) {
  490. if fs.mountPath != "" {
  491. virtualPath = strings.TrimPrefix(virtualPath, fs.mountPath)
  492. }
  493. if !path.IsAbs(virtualPath) {
  494. virtualPath = path.Clean("/" + virtualPath)
  495. }
  496. fsPath := fs.Join(fs.config.Prefix, virtualPath)
  497. if fs.config.Prefix != "/" && fsPath != "/" {
  498. // we need to check if this path is a symlink outside the given prefix
  499. // or a file/dir inside a dir symlinked outside the prefix
  500. if err := fs.checkConnection(); err != nil {
  501. return "", err
  502. }
  503. var validatedPath string
  504. var err error
  505. validatedPath, err = fs.getRealPath(fsPath)
  506. if err != nil && !os.IsNotExist(err) {
  507. fsLog(fs, logger.LevelError, "Invalid path resolution, original path %v resolved %#v err: %v",
  508. virtualPath, fsPath, err)
  509. return "", err
  510. } else if os.IsNotExist(err) {
  511. for os.IsNotExist(err) {
  512. validatedPath = path.Dir(validatedPath)
  513. if validatedPath == "/" {
  514. err = nil
  515. break
  516. }
  517. validatedPath, err = fs.getRealPath(validatedPath)
  518. }
  519. if err != nil {
  520. fsLog(fs, logger.LevelError, "Invalid path resolution, dir %#v original path %#v resolved %#v err: %v",
  521. validatedPath, virtualPath, fsPath, err)
  522. return "", err
  523. }
  524. }
  525. if err := fs.isSubDir(validatedPath); err != nil {
  526. fsLog(fs, logger.LevelError, "Invalid path resolution, dir %#v original path %#v resolved %#v err: %v",
  527. validatedPath, virtualPath, fsPath, err)
  528. return "", err
  529. }
  530. }
  531. return fsPath, nil
  532. }
  533. // getRealPath returns the real remote path trying to resolve symbolic links if any
  534. func (fs *SFTPFs) getRealPath(name string) (string, error) {
  535. info, err := fs.sftpClient.Lstat(name)
  536. if err != nil {
  537. return name, err
  538. }
  539. if info.Mode()&os.ModeSymlink != 0 {
  540. return fs.sftpClient.ReadLink(name)
  541. }
  542. return name, err
  543. }
  544. func (fs *SFTPFs) isSubDir(name string) error {
  545. if name == fs.config.Prefix {
  546. return nil
  547. }
  548. if len(name) < len(fs.config.Prefix) {
  549. err := fmt.Errorf("path %#v is not inside: %#v", name, fs.config.Prefix)
  550. return &pathResolutionError{err: err.Error()}
  551. }
  552. if !strings.HasPrefix(name, fs.config.Prefix+"/") {
  553. err := fmt.Errorf("path %#v is not inside: %#v", name, fs.config.Prefix)
  554. return &pathResolutionError{err: err.Error()}
  555. }
  556. return nil
  557. }
  558. // GetDirSize returns the number of files and the size for a folder
  559. // including any subfolders
  560. func (fs *SFTPFs) GetDirSize(dirname string) (int, int64, error) {
  561. numFiles := 0
  562. size := int64(0)
  563. if err := fs.checkConnection(); err != nil {
  564. return numFiles, size, err
  565. }
  566. isDir, err := IsDirectory(fs, dirname)
  567. if err == nil && isDir {
  568. walker := fs.sftpClient.Walk(dirname)
  569. for walker.Step() {
  570. err := walker.Err()
  571. if err != nil {
  572. return numFiles, size, err
  573. }
  574. if walker.Stat().Mode().IsRegular() {
  575. size += walker.Stat().Size()
  576. numFiles++
  577. }
  578. }
  579. }
  580. return numFiles, size, err
  581. }
  582. // GetMimeType returns the content type
  583. func (fs *SFTPFs) GetMimeType(name string) (string, error) {
  584. if err := fs.checkConnection(); err != nil {
  585. return "", err
  586. }
  587. f, err := fs.sftpClient.OpenFile(name, os.O_RDONLY)
  588. if err != nil {
  589. return "", err
  590. }
  591. defer f.Close()
  592. var buf [512]byte
  593. n, err := io.ReadFull(f, buf[:])
  594. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  595. return "", err
  596. }
  597. ctype := http.DetectContentType(buf[:n])
  598. // Rewind file.
  599. _, err = f.Seek(0, io.SeekStart)
  600. return ctype, err
  601. }
  602. // GetAvailableDiskSize return the available size for the specified path
  603. func (fs *SFTPFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
  604. if err := fs.checkConnection(); err != nil {
  605. return nil, err
  606. }
  607. if _, ok := fs.sftpClient.HasExtension("[email protected]"); !ok {
  608. return nil, ErrStorageSizeUnavailable
  609. }
  610. return fs.sftpClient.StatVFS(dirName)
  611. }
  612. // Close the connection
  613. func (fs *SFTPFs) Close() error {
  614. fs.Lock()
  615. defer fs.Unlock()
  616. var sftpErr, sshErr error
  617. if fs.sftpClient != nil {
  618. sftpErr = fs.sftpClient.Close()
  619. }
  620. if fs.sshClient != nil {
  621. sshErr = fs.sshClient.Close()
  622. }
  623. if sftpErr != nil {
  624. return sftpErr
  625. }
  626. return sshErr
  627. }
  628. func (fs *SFTPFs) copy(dst io.Writer, src io.Reader) (written int64, err error) {
  629. buf := make([]byte, 32768)
  630. for {
  631. nr, er := src.Read(buf)
  632. if nr > 0 {
  633. nw, ew := dst.Write(buf[0:nr])
  634. if nw < 0 || nr < nw {
  635. nw = 0
  636. if ew == nil {
  637. ew = errors.New("invalid write")
  638. }
  639. }
  640. written += int64(nw)
  641. if ew != nil {
  642. err = ew
  643. break
  644. }
  645. if nr != nw {
  646. err = io.ErrShortWrite
  647. break
  648. }
  649. }
  650. if er != nil {
  651. if er != io.EOF {
  652. err = er
  653. }
  654. break
  655. }
  656. }
  657. return written, err
  658. }
  659. func (fs *SFTPFs) checkConnection() error {
  660. err := fs.closed()
  661. if err == nil {
  662. return nil
  663. }
  664. return fs.createConnection()
  665. }
  666. func (fs *SFTPFs) createConnection() error {
  667. fs.Lock()
  668. defer fs.Unlock()
  669. var err error
  670. clientConfig := &ssh.ClientConfig{
  671. User: fs.config.Username,
  672. HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
  673. fp := ssh.FingerprintSHA256(key)
  674. if util.IsStringInSlice(fp, sftpFingerprints) {
  675. if util.IsStringInSlice(fs.config.Username, fs.config.forbiddenSelfUsernames) {
  676. fsLog(fs, logger.LevelError, "SFTP loop or nested local SFTP folders detected, mount path %#v, username %#v, forbidden usernames: %+v",
  677. fs.mountPath, fs.config.Username, fs.config.forbiddenSelfUsernames)
  678. return ErrSFTPLoop
  679. }
  680. }
  681. if len(fs.config.Fingerprints) > 0 {
  682. for _, provided := range fs.config.Fingerprints {
  683. if provided == fp {
  684. return nil
  685. }
  686. }
  687. return fmt.Errorf("invalid fingerprint %#v", fp)
  688. }
  689. fsLog(fs, logger.LevelWarn, "login without host key validation, please provide at least a fingerprint!")
  690. return nil
  691. },
  692. ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version),
  693. }
  694. if fs.config.PrivateKey.GetPayload() != "" {
  695. signer, err := ssh.ParsePrivateKey([]byte(fs.config.PrivateKey.GetPayload()))
  696. if err != nil {
  697. fs.err <- err
  698. return err
  699. }
  700. clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
  701. }
  702. if fs.config.Password.GetPayload() != "" {
  703. clientConfig.Auth = append(clientConfig.Auth, ssh.Password(fs.config.Password.GetPayload()))
  704. }
  705. fs.sshClient, err = ssh.Dial("tcp", fs.config.Endpoint, clientConfig)
  706. if err != nil {
  707. fs.err <- err
  708. return err
  709. }
  710. fs.sftpClient, err = sftp.NewClient(fs.sshClient)
  711. if err != nil {
  712. fs.sshClient.Close()
  713. fs.err <- err
  714. return err
  715. }
  716. if fs.config.DisableCouncurrentReads {
  717. fsLog(fs, logger.LevelDebug, "disabling concurrent reads")
  718. opt := sftp.UseConcurrentReads(false)
  719. opt(fs.sftpClient) //nolint:errcheck
  720. }
  721. if fs.config.BufferSize > 0 {
  722. fsLog(fs, logger.LevelDebug, "enabling concurrent writes")
  723. opt := sftp.UseConcurrentWrites(true)
  724. opt(fs.sftpClient) //nolint:errcheck
  725. }
  726. go fs.wait()
  727. return nil
  728. }
  729. func (fs *SFTPFs) wait() {
  730. // we wait on the sftp client otherwise if the channel is closed but not the connection
  731. // we don't detect the event.
  732. fs.err <- fs.sftpClient.Wait()
  733. fsLog(fs, logger.LevelDebug, "sftp channel closed")
  734. fs.Lock()
  735. defer fs.Unlock()
  736. if fs.sshClient != nil {
  737. fs.sshClient.Close()
  738. }
  739. }
  740. func (fs *SFTPFs) closed() error {
  741. select {
  742. case err := <-fs.err:
  743. return err
  744. default:
  745. return nil
  746. }
  747. }