sftpfs.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  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. "golang.org/x/crypto/ssh"
  19. "github.com/drakkan/sftpgo/v2/kms"
  20. "github.com/drakkan/sftpgo/v2/logger"
  21. "github.com/drakkan/sftpgo/v2/sdk"
  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. return fs.sftpClient.Rename(source, target)
  304. }
  305. // Remove removes the named file or (empty) directory.
  306. func (fs *SFTPFs) Remove(name string, isDir bool) error {
  307. if err := fs.checkConnection(); err != nil {
  308. return err
  309. }
  310. return fs.sftpClient.Remove(name)
  311. }
  312. // Mkdir creates a new directory with the specified name and default permissions
  313. func (fs *SFTPFs) Mkdir(name string) error {
  314. if err := fs.checkConnection(); err != nil {
  315. return err
  316. }
  317. return fs.sftpClient.Mkdir(name)
  318. }
  319. // MkdirAll creates a directory named path, along with any necessary parents,
  320. // and returns nil, or else returns an error.
  321. // If path is already a directory, MkdirAll does nothing and returns nil.
  322. func (fs *SFTPFs) MkdirAll(name string, uid int, gid int) error {
  323. if err := fs.checkConnection(); err != nil {
  324. return err
  325. }
  326. return fs.sftpClient.MkdirAll(name)
  327. }
  328. // Symlink creates source as a symbolic link to target.
  329. func (fs *SFTPFs) Symlink(source, target string) error {
  330. if err := fs.checkConnection(); err != nil {
  331. return err
  332. }
  333. return fs.sftpClient.Symlink(source, target)
  334. }
  335. // Readlink returns the destination of the named symbolic link
  336. func (fs *SFTPFs) Readlink(name string) (string, error) {
  337. if err := fs.checkConnection(); err != nil {
  338. return "", err
  339. }
  340. return fs.sftpClient.ReadLink(name)
  341. }
  342. // Chown changes the numeric uid and gid of the named file.
  343. func (fs *SFTPFs) Chown(name string, uid int, gid int) error {
  344. if err := fs.checkConnection(); err != nil {
  345. return err
  346. }
  347. return fs.sftpClient.Chown(name, uid, gid)
  348. }
  349. // Chmod changes the mode of the named file to mode.
  350. func (fs *SFTPFs) Chmod(name string, mode os.FileMode) error {
  351. if err := fs.checkConnection(); err != nil {
  352. return err
  353. }
  354. return fs.sftpClient.Chmod(name, mode)
  355. }
  356. // Chtimes changes the access and modification times of the named file.
  357. func (fs *SFTPFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error {
  358. if err := fs.checkConnection(); err != nil {
  359. return err
  360. }
  361. return fs.sftpClient.Chtimes(name, atime, mtime)
  362. }
  363. // Truncate changes the size of the named file.
  364. func (fs *SFTPFs) Truncate(name string, size int64) error {
  365. if err := fs.checkConnection(); err != nil {
  366. return err
  367. }
  368. return fs.sftpClient.Truncate(name, size)
  369. }
  370. // ReadDir reads the directory named by dirname and returns
  371. // a list of directory entries.
  372. func (fs *SFTPFs) ReadDir(dirname string) ([]os.FileInfo, error) {
  373. if err := fs.checkConnection(); err != nil {
  374. return nil, err
  375. }
  376. return fs.sftpClient.ReadDir(dirname)
  377. }
  378. // IsUploadResumeSupported returns true if resuming uploads is supported.
  379. func (fs *SFTPFs) IsUploadResumeSupported() bool {
  380. return fs.config.BufferSize == 0
  381. }
  382. // IsAtomicUploadSupported returns true if atomic upload is supported.
  383. func (fs *SFTPFs) IsAtomicUploadSupported() bool {
  384. return fs.config.BufferSize == 0
  385. }
  386. // IsNotExist returns a boolean indicating whether the error is known to
  387. // report that a file or directory does not exist
  388. func (*SFTPFs) IsNotExist(err error) bool {
  389. return os.IsNotExist(err)
  390. }
  391. // IsPermission returns a boolean indicating whether the error is known to
  392. // report that permission is denied.
  393. func (*SFTPFs) IsPermission(err error) bool {
  394. if _, ok := err.(*pathResolutionError); ok {
  395. return true
  396. }
  397. return os.IsPermission(err)
  398. }
  399. // IsNotSupported returns true if the error indicate an unsupported operation
  400. func (*SFTPFs) IsNotSupported(err error) bool {
  401. if err == nil {
  402. return false
  403. }
  404. return err == ErrVfsUnsupported
  405. }
  406. // CheckRootPath creates the specified local root directory if it does not exists
  407. func (fs *SFTPFs) CheckRootPath(username string, uid int, gid int) bool {
  408. if fs.config.BufferSize > 0 {
  409. // we need a local directory for temporary files
  410. osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "")
  411. osFs.CheckRootPath(username, uid, gid)
  412. }
  413. if fs.config.Prefix == "/" {
  414. return true
  415. }
  416. if err := fs.MkdirAll(fs.config.Prefix, uid, gid); err != nil {
  417. fsLog(fs, logger.LevelDebug, "error creating root directory %#v for user %#v: %v", fs.config.Prefix, username, err)
  418. return false
  419. }
  420. return true
  421. }
  422. // ScanRootDirContents returns the number of files contained in a directory and
  423. // their size
  424. func (fs *SFTPFs) ScanRootDirContents() (int, int64, error) {
  425. return fs.GetDirSize(fs.config.Prefix)
  426. }
  427. // CheckMetadata checks the metadata consistency
  428. func (*SFTPFs) CheckMetadata() error {
  429. return nil
  430. }
  431. // GetAtomicUploadPath returns the path to use for an atomic upload
  432. func (*SFTPFs) GetAtomicUploadPath(name string) string {
  433. dir := path.Dir(name)
  434. guid := xid.New().String()
  435. return path.Join(dir, ".sftpgo-upload."+guid+"."+path.Base(name))
  436. }
  437. // GetRelativePath returns the path for a file relative to the sftp prefix if any.
  438. // This is the path as seen by SFTPGo users
  439. func (fs *SFTPFs) GetRelativePath(name string) string {
  440. rel := path.Clean(name)
  441. if rel == "." {
  442. rel = ""
  443. }
  444. if !path.IsAbs(rel) {
  445. return "/" + rel
  446. }
  447. if fs.config.Prefix != "/" {
  448. if !strings.HasPrefix(rel, fs.config.Prefix) {
  449. rel = "/"
  450. }
  451. rel = path.Clean("/" + strings.TrimPrefix(rel, fs.config.Prefix))
  452. }
  453. if fs.mountPath != "" {
  454. rel = path.Join(fs.mountPath, rel)
  455. }
  456. return rel
  457. }
  458. // Walk walks the file tree rooted at root, calling walkFn for each file or
  459. // directory in the tree, including root
  460. func (fs *SFTPFs) Walk(root string, walkFn filepath.WalkFunc) error {
  461. if err := fs.checkConnection(); err != nil {
  462. return err
  463. }
  464. walker := fs.sftpClient.Walk(root)
  465. for walker.Step() {
  466. err := walker.Err()
  467. if err != nil {
  468. return err
  469. }
  470. err = walkFn(walker.Path(), walker.Stat(), err)
  471. if err != nil {
  472. return err
  473. }
  474. }
  475. return nil
  476. }
  477. // Join joins any number of path elements into a single path
  478. func (*SFTPFs) Join(elem ...string) string {
  479. return path.Join(elem...)
  480. }
  481. // HasVirtualFolders returns true if folders are emulated
  482. func (*SFTPFs) HasVirtualFolders() bool {
  483. return false
  484. }
  485. // ResolvePath returns the matching filesystem path for the specified virtual path
  486. func (fs *SFTPFs) ResolvePath(virtualPath string) (string, error) {
  487. if fs.mountPath != "" {
  488. virtualPath = strings.TrimPrefix(virtualPath, fs.mountPath)
  489. }
  490. if !path.IsAbs(virtualPath) {
  491. virtualPath = path.Clean("/" + virtualPath)
  492. }
  493. fsPath := fs.Join(fs.config.Prefix, virtualPath)
  494. if fs.config.Prefix != "/" && fsPath != "/" {
  495. // we need to check if this path is a symlink outside the given prefix
  496. // or a file/dir inside a dir symlinked outside the prefix
  497. if err := fs.checkConnection(); err != nil {
  498. return "", err
  499. }
  500. var validatedPath string
  501. var err error
  502. validatedPath, err = fs.getRealPath(fsPath)
  503. if err != nil && !os.IsNotExist(err) {
  504. fsLog(fs, logger.LevelError, "Invalid path resolution, original path %v resolved %#v err: %v",
  505. virtualPath, fsPath, err)
  506. return "", err
  507. } else if os.IsNotExist(err) {
  508. for os.IsNotExist(err) {
  509. validatedPath = path.Dir(validatedPath)
  510. if validatedPath == "/" {
  511. err = nil
  512. break
  513. }
  514. validatedPath, err = fs.getRealPath(validatedPath)
  515. }
  516. if err != nil {
  517. fsLog(fs, logger.LevelError, "Invalid path resolution, dir %#v original path %#v resolved %#v err: %v",
  518. validatedPath, virtualPath, fsPath, err)
  519. return "", err
  520. }
  521. }
  522. if err := fs.isSubDir(validatedPath); err != nil {
  523. fsLog(fs, logger.LevelError, "Invalid path resolution, dir %#v original path %#v resolved %#v err: %v",
  524. validatedPath, virtualPath, fsPath, err)
  525. return "", err
  526. }
  527. }
  528. return fsPath, nil
  529. }
  530. // getRealPath returns the real remote path trying to resolve symbolic links if any
  531. func (fs *SFTPFs) getRealPath(name string) (string, error) {
  532. info, err := fs.sftpClient.Lstat(name)
  533. if err != nil {
  534. return name, err
  535. }
  536. if info.Mode()&os.ModeSymlink != 0 {
  537. return fs.sftpClient.ReadLink(name)
  538. }
  539. return name, err
  540. }
  541. func (fs *SFTPFs) isSubDir(name string) error {
  542. if name == fs.config.Prefix {
  543. return nil
  544. }
  545. if len(name) < len(fs.config.Prefix) {
  546. err := fmt.Errorf("path %#v is not inside: %#v", name, fs.config.Prefix)
  547. return &pathResolutionError{err: err.Error()}
  548. }
  549. if !strings.HasPrefix(name, fs.config.Prefix+"/") {
  550. err := fmt.Errorf("path %#v is not inside: %#v", name, fs.config.Prefix)
  551. return &pathResolutionError{err: err.Error()}
  552. }
  553. return nil
  554. }
  555. // GetDirSize returns the number of files and the size for a folder
  556. // including any subfolders
  557. func (fs *SFTPFs) GetDirSize(dirname string) (int, int64, error) {
  558. numFiles := 0
  559. size := int64(0)
  560. if err := fs.checkConnection(); err != nil {
  561. return numFiles, size, err
  562. }
  563. isDir, err := IsDirectory(fs, dirname)
  564. if err == nil && isDir {
  565. walker := fs.sftpClient.Walk(dirname)
  566. for walker.Step() {
  567. err := walker.Err()
  568. if err != nil {
  569. return numFiles, size, err
  570. }
  571. if walker.Stat().Mode().IsRegular() {
  572. size += walker.Stat().Size()
  573. numFiles++
  574. }
  575. }
  576. }
  577. return numFiles, size, err
  578. }
  579. // GetMimeType returns the content type
  580. func (fs *SFTPFs) GetMimeType(name string) (string, error) {
  581. if err := fs.checkConnection(); err != nil {
  582. return "", err
  583. }
  584. f, err := fs.sftpClient.OpenFile(name, os.O_RDONLY)
  585. if err != nil {
  586. return "", err
  587. }
  588. defer f.Close()
  589. var buf [512]byte
  590. n, err := io.ReadFull(f, buf[:])
  591. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  592. return "", err
  593. }
  594. ctype := http.DetectContentType(buf[:n])
  595. // Rewind file.
  596. _, err = f.Seek(0, io.SeekStart)
  597. return ctype, err
  598. }
  599. // GetAvailableDiskSize return the available size for the specified path
  600. func (fs *SFTPFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
  601. if err := fs.checkConnection(); err != nil {
  602. return nil, err
  603. }
  604. if _, ok := fs.sftpClient.HasExtension("[email protected]"); !ok {
  605. return nil, ErrStorageSizeUnavailable
  606. }
  607. return fs.sftpClient.StatVFS(dirName)
  608. }
  609. // Close the connection
  610. func (fs *SFTPFs) Close() error {
  611. fs.Lock()
  612. defer fs.Unlock()
  613. var sftpErr, sshErr error
  614. if fs.sftpClient != nil {
  615. sftpErr = fs.sftpClient.Close()
  616. }
  617. if fs.sshClient != nil {
  618. sshErr = fs.sshClient.Close()
  619. }
  620. if sftpErr != nil {
  621. return sftpErr
  622. }
  623. return sshErr
  624. }
  625. func (fs *SFTPFs) copy(dst io.Writer, src io.Reader) (written int64, err error) {
  626. buf := make([]byte, 32768)
  627. for {
  628. nr, er := src.Read(buf)
  629. if nr > 0 {
  630. nw, ew := dst.Write(buf[0:nr])
  631. if nw < 0 || nr < nw {
  632. nw = 0
  633. if ew == nil {
  634. ew = errors.New("invalid write")
  635. }
  636. }
  637. written += int64(nw)
  638. if ew != nil {
  639. err = ew
  640. break
  641. }
  642. if nr != nw {
  643. err = io.ErrShortWrite
  644. break
  645. }
  646. }
  647. if er != nil {
  648. if er != io.EOF {
  649. err = er
  650. }
  651. break
  652. }
  653. }
  654. return written, err
  655. }
  656. func (fs *SFTPFs) checkConnection() error {
  657. err := fs.closed()
  658. if err == nil {
  659. return nil
  660. }
  661. return fs.createConnection()
  662. }
  663. func (fs *SFTPFs) createConnection() error {
  664. fs.Lock()
  665. defer fs.Unlock()
  666. var err error
  667. clientConfig := &ssh.ClientConfig{
  668. User: fs.config.Username,
  669. HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
  670. fp := ssh.FingerprintSHA256(key)
  671. if util.IsStringInSlice(fp, sftpFingerprints) {
  672. if util.IsStringInSlice(fs.config.Username, fs.config.forbiddenSelfUsernames) {
  673. fsLog(fs, logger.LevelError, "SFTP loop or nested local SFTP folders detected, mount path %#v, username %#v, forbidden usernames: %+v",
  674. fs.mountPath, fs.config.Username, fs.config.forbiddenSelfUsernames)
  675. return ErrSFTPLoop
  676. }
  677. }
  678. if len(fs.config.Fingerprints) > 0 {
  679. for _, provided := range fs.config.Fingerprints {
  680. if provided == fp {
  681. return nil
  682. }
  683. }
  684. return fmt.Errorf("invalid fingerprint %#v", fp)
  685. }
  686. fsLog(fs, logger.LevelWarn, "login without host key validation, please provide at least a fingerprint!")
  687. return nil
  688. },
  689. ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version),
  690. }
  691. if fs.config.PrivateKey.GetPayload() != "" {
  692. signer, err := ssh.ParsePrivateKey([]byte(fs.config.PrivateKey.GetPayload()))
  693. if err != nil {
  694. fs.err <- err
  695. return err
  696. }
  697. clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
  698. }
  699. if fs.config.Password.GetPayload() != "" {
  700. clientConfig.Auth = append(clientConfig.Auth, ssh.Password(fs.config.Password.GetPayload()))
  701. }
  702. fs.sshClient, err = ssh.Dial("tcp", fs.config.Endpoint, clientConfig)
  703. if err != nil {
  704. fs.err <- err
  705. return err
  706. }
  707. fs.sftpClient, err = sftp.NewClient(fs.sshClient)
  708. if err != nil {
  709. fs.sshClient.Close()
  710. fs.err <- err
  711. return err
  712. }
  713. if fs.config.DisableCouncurrentReads {
  714. fsLog(fs, logger.LevelDebug, "disabling concurrent reads")
  715. opt := sftp.UseConcurrentReads(false)
  716. opt(fs.sftpClient) //nolint:errcheck
  717. }
  718. if fs.config.BufferSize > 0 {
  719. fsLog(fs, logger.LevelDebug, "enabling concurrent writes")
  720. opt := sftp.UseConcurrentWrites(true)
  721. opt(fs.sftpClient) //nolint:errcheck
  722. }
  723. go fs.wait()
  724. return nil
  725. }
  726. func (fs *SFTPFs) wait() {
  727. // we wait on the sftp client otherwise if the channel is closed but not the connection
  728. // we don't detect the event.
  729. fs.err <- fs.sftpClient.Wait()
  730. fsLog(fs, logger.LevelDebug, "sftp channel closed")
  731. fs.Lock()
  732. defer fs.Unlock()
  733. if fs.sshClient != nil {
  734. fs.sshClient.Close()
  735. }
  736. }
  737. func (fs *SFTPFs) closed() error {
  738. select {
  739. case err := <-fs.err:
  740. return err
  741. default:
  742. return nil
  743. }
  744. }