handler.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. package sftpd
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "net"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/drakkan/sftpgo/utils"
  13. "github.com/rs/xid"
  14. "golang.org/x/crypto/ssh"
  15. "github.com/drakkan/sftpgo/dataprovider"
  16. "github.com/drakkan/sftpgo/logger"
  17. "github.com/pkg/sftp"
  18. )
  19. // Connection details for an authenticated user
  20. type Connection struct {
  21. // Unique identifier for the connection
  22. ID string
  23. // logged in user's details
  24. User dataprovider.User
  25. // client's version string
  26. ClientVersion string
  27. // Remote address for this connection
  28. RemoteAddr net.Addr
  29. // start time for this connection
  30. StartTime time.Time
  31. // last activity for this connection
  32. lastActivity time.Time
  33. protocol string
  34. lock *sync.Mutex
  35. netConn net.Conn
  36. channel ssh.Channel
  37. command string
  38. }
  39. // Log outputs a log entry to the configured logger
  40. func (c Connection) Log(level logger.LogLevel, sender string, format string, v ...interface{}) {
  41. logger.Log(level, sender, c.ID, format, v...)
  42. }
  43. // Fileread creates a reader for a file on the system and returns the reader back.
  44. func (c Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) {
  45. updateConnectionActivity(c.ID)
  46. p, err := c.buildPath(request.Filepath)
  47. if err != nil {
  48. return nil, getSFTPErrorFromOSError(err)
  49. }
  50. if !c.User.HasPerm(dataprovider.PermDownload, filepath.Dir(p)) {
  51. return nil, sftp.ErrSSHFxPermissionDenied
  52. }
  53. c.lock.Lock()
  54. defer c.lock.Unlock()
  55. if _, err := os.Stat(p); err != nil {
  56. return nil, getSFTPErrorFromOSError(err)
  57. }
  58. file, err := os.Open(p)
  59. if err != nil {
  60. c.Log(logger.LevelWarn, logSender, "could not open file %#v for reading: %v", p, err)
  61. return nil, getSFTPErrorFromOSError(err)
  62. }
  63. c.Log(logger.LevelDebug, logSender, "fileread requested for path: %#v", p)
  64. transfer := Transfer{
  65. file: file,
  66. path: p,
  67. start: time.Now(),
  68. bytesSent: 0,
  69. bytesReceived: 0,
  70. user: c.User,
  71. connectionID: c.ID,
  72. transferType: transferDownload,
  73. lastActivity: time.Now(),
  74. isNewFile: false,
  75. protocol: c.protocol,
  76. transferError: nil,
  77. isFinished: false,
  78. minWriteOffset: 0,
  79. }
  80. addTransfer(&transfer)
  81. return &transfer, nil
  82. }
  83. // Filewrite handles the write actions for a file on the system.
  84. func (c Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) {
  85. updateConnectionActivity(c.ID)
  86. p, err := c.buildPath(request.Filepath)
  87. if err != nil {
  88. return nil, getSFTPErrorFromOSError(err)
  89. }
  90. filePath := p
  91. if isAtomicUploadEnabled() {
  92. filePath = getUploadTempFilePath(p)
  93. }
  94. c.lock.Lock()
  95. defer c.lock.Unlock()
  96. stat, statErr := os.Stat(p)
  97. if os.IsNotExist(statErr) {
  98. if !c.User.HasPerm(dataprovider.PermUpload, filepath.Dir(p)) {
  99. return nil, sftp.ErrSSHFxPermissionDenied
  100. }
  101. return c.handleSFTPUploadToNewFile(p, filePath)
  102. }
  103. if statErr != nil {
  104. c.Log(logger.LevelError, logSender, "error performing file stat %#v: %v", p, statErr)
  105. return nil, getSFTPErrorFromOSError(err)
  106. }
  107. // This happen if we upload a file that has the same name of an existing directory
  108. if stat.IsDir() {
  109. c.Log(logger.LevelWarn, logSender, "attempted to open a directory for writing to: %#v", p)
  110. return nil, sftp.ErrSSHFxOpUnsupported
  111. }
  112. if !c.User.HasPerm(dataprovider.PermOverwrite, filepath.Dir(filePath)) {
  113. return nil, sftp.ErrSSHFxPermissionDenied
  114. }
  115. return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size())
  116. }
  117. // Filecmd hander for basic SFTP system calls related to files, but not anything to do with reading
  118. // or writing to those files.
  119. func (c Connection) Filecmd(request *sftp.Request) error {
  120. updateConnectionActivity(c.ID)
  121. p, err := c.buildPath(request.Filepath)
  122. if err != nil {
  123. return getSFTPErrorFromOSError(err)
  124. }
  125. target, err := c.getSFTPCmdTargetPath(request.Target)
  126. if err != nil {
  127. return err
  128. }
  129. isHomeDir := c.User.GetRelativePath(p) == "/"
  130. c.Log(logger.LevelDebug, logSender, "new cmd, method: %v, sourcePath: %#v, targetPath: %#v", request.Method,
  131. p, target)
  132. switch request.Method {
  133. case "Setstat":
  134. return c.handleSFTPSetstat(p, request)
  135. case "Rename":
  136. if isHomeDir {
  137. c.Log(logger.LevelWarn, logSender, "renaming root dir is not allowed")
  138. return sftp.ErrSSHFxPermissionDenied
  139. }
  140. if err = c.handleSFTPRename(p, target); err != nil {
  141. return err
  142. }
  143. break
  144. case "Rmdir":
  145. if isHomeDir {
  146. c.Log(logger.LevelWarn, logSender, "removing root dir is not allowed")
  147. return sftp.ErrSSHFxPermissionDenied
  148. }
  149. return c.handleSFTPRmdir(p)
  150. case "Mkdir":
  151. err = c.handleSFTPMkdir(p)
  152. if err != nil {
  153. return err
  154. }
  155. break
  156. case "Symlink":
  157. if isHomeDir {
  158. c.Log(logger.LevelWarn, logSender, "symlinking root dir is not allowed")
  159. return sftp.ErrSSHFxPermissionDenied
  160. }
  161. if err = c.handleSFTPSymlink(p, target); err != nil {
  162. return err
  163. }
  164. break
  165. case "Remove":
  166. return c.handleSFTPRemove(p)
  167. default:
  168. return sftp.ErrSSHFxOpUnsupported
  169. }
  170. var fileLocation = p
  171. if target != "" {
  172. fileLocation = target
  173. }
  174. // we return if we remove a file or a dir so source path or target path always exists here
  175. utils.SetPathPermissions(fileLocation, c.User.GetUID(), c.User.GetGID())
  176. return sftp.ErrSSHFxOk
  177. }
  178. // Filelist is the handler for SFTP filesystem list calls. This will handle calls to list the contents of
  179. // a directory as well as perform file/folder stat calls.
  180. func (c Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
  181. updateConnectionActivity(c.ID)
  182. p, err := c.buildPath(request.Filepath)
  183. if err != nil {
  184. return nil, getSFTPErrorFromOSError(err)
  185. }
  186. switch request.Method {
  187. case "List":
  188. if !c.User.HasPerm(dataprovider.PermListItems, p) {
  189. return nil, sftp.ErrSSHFxPermissionDenied
  190. }
  191. c.Log(logger.LevelDebug, logSender, "requested list file for dir: %#v", p)
  192. files, err := ioutil.ReadDir(p)
  193. if err != nil {
  194. c.Log(logger.LevelWarn, logSender, "error listing directory: %#v", err)
  195. return nil, getSFTPErrorFromOSError(err)
  196. }
  197. return listerAt(files), nil
  198. case "Stat":
  199. if !c.User.HasPerm(dataprovider.PermListItems, filepath.Dir(p)) {
  200. return nil, sftp.ErrSSHFxPermissionDenied
  201. }
  202. c.Log(logger.LevelDebug, logSender, "requested stat for path: %#v", p)
  203. s, err := os.Stat(p)
  204. if err != nil {
  205. c.Log(logger.LevelWarn, logSender, "error running stat on path: %#v", err)
  206. return nil, getSFTPErrorFromOSError(err)
  207. }
  208. return listerAt([]os.FileInfo{s}), nil
  209. default:
  210. return nil, sftp.ErrSSHFxOpUnsupported
  211. }
  212. }
  213. func (c Connection) getSFTPCmdTargetPath(requestTarget string) (string, error) {
  214. var target string
  215. // If a target is provided in this request validate that it is going to the correct
  216. // location for the server. If it is not, return an error
  217. if len(requestTarget) > 0 {
  218. var err error
  219. target, err = c.buildPath(requestTarget)
  220. if err != nil {
  221. return target, getSFTPErrorFromOSError(err)
  222. }
  223. }
  224. return target, nil
  225. }
  226. func (c Connection) handleSFTPSetstat(path string, request *sftp.Request) error {
  227. if setstatMode == 1 {
  228. return nil
  229. }
  230. if len(request.Attrs) < 1 {
  231. c.Log(logger.LevelInfo, logSender, "cannot handle Setstat request with no attrs, this is probably a buggy client: %v",
  232. c.ClientVersion)
  233. return sftp.ErrSSHFxBadMessage
  234. }
  235. pathForPerms := path
  236. if fi, err := os.Lstat(path); err == nil {
  237. if fi.IsDir() {
  238. pathForPerms = filepath.Dir(path)
  239. }
  240. }
  241. attrFlags := request.AttrFlags()
  242. if attrFlags.Permissions {
  243. if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
  244. return sftp.ErrSSHFxPermissionDenied
  245. }
  246. fileMode := request.Attributes().FileMode()
  247. if err := os.Chmod(path, fileMode); err != nil {
  248. c.Log(logger.LevelWarn, logSender, "failed to chmod path %#v, mode: %v, err: %v", path, fileMode.String(), err)
  249. return getSFTPErrorFromOSError(err)
  250. }
  251. logger.CommandLog(chmodLogSender, path, "", c.User.Username, fileMode.String(), c.ID, c.protocol, -1, -1, "", "", "")
  252. return nil
  253. } else if attrFlags.UidGid {
  254. if !c.User.HasPerm(dataprovider.PermChown, pathForPerms) {
  255. return sftp.ErrSSHFxPermissionDenied
  256. }
  257. uid := int(request.Attributes().UID)
  258. gid := int(request.Attributes().GID)
  259. if err := os.Chown(path, uid, gid); err != nil {
  260. c.Log(logger.LevelWarn, logSender, "failed to chown path %#v, uid: %v, gid: %v, err: %v", path, uid, gid, err)
  261. return getSFTPErrorFromOSError(err)
  262. }
  263. logger.CommandLog(chownLogSender, path, "", c.User.Username, "", c.ID, c.protocol, uid, gid, "", "", "")
  264. return nil
  265. } else if attrFlags.Acmodtime {
  266. if !c.User.HasPerm(dataprovider.PermChtimes, pathForPerms) {
  267. return sftp.ErrSSHFxPermissionDenied
  268. }
  269. dateFormat := "2006-01-02T15:04:05" // YYYY-MM-DDTHH:MM:SS
  270. accessTime := time.Unix(int64(request.Attributes().Atime), 0)
  271. modificationTime := time.Unix(int64(request.Attributes().Mtime), 0)
  272. accessTimeString := accessTime.Format(dateFormat)
  273. modificationTimeString := modificationTime.Format(dateFormat)
  274. if err := os.Chtimes(path, accessTime, modificationTime); err != nil {
  275. c.Log(logger.LevelWarn, logSender, "failed to chtimes for path %#v, access time: %v, modification time: %v, err: %v",
  276. path, accessTime, modificationTime, err)
  277. return getSFTPErrorFromOSError(err)
  278. }
  279. logger.CommandLog(chtimesLogSender, path, "", c.User.Username, "", c.ID, c.protocol, -1, -1, accessTimeString,
  280. modificationTimeString, "")
  281. return nil
  282. }
  283. return nil
  284. }
  285. func (c Connection) handleSFTPRename(sourcePath string, targetPath string) error {
  286. if !c.User.HasPerm(dataprovider.PermRename, filepath.Dir(targetPath)) {
  287. return sftp.ErrSSHFxPermissionDenied
  288. }
  289. if err := os.Rename(sourcePath, targetPath); err != nil {
  290. c.Log(logger.LevelWarn, logSender, "failed to rename file, source: %#v target: %#v: %v", sourcePath, targetPath, err)
  291. return getSFTPErrorFromOSError(err)
  292. }
  293. logger.CommandLog(renameLogSender, sourcePath, targetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  294. go executeAction(operationRename, c.User.Username, sourcePath, targetPath, "")
  295. return nil
  296. }
  297. func (c Connection) handleSFTPRmdir(path string) error {
  298. if !c.User.HasPerm(dataprovider.PermDelete, filepath.Dir(path)) {
  299. return sftp.ErrSSHFxPermissionDenied
  300. }
  301. var fi os.FileInfo
  302. var err error
  303. if fi, err = os.Lstat(path); err != nil {
  304. c.Log(logger.LevelWarn, logSender, "failed to remove a dir %#v: stat error: %v", path, err)
  305. return getSFTPErrorFromOSError(err)
  306. }
  307. if !fi.IsDir() || fi.Mode()&os.ModeSymlink == os.ModeSymlink {
  308. c.Log(logger.LevelDebug, logSender, "cannot remove %#v is not a directory", path)
  309. return sftp.ErrSSHFxFailure
  310. }
  311. if err = os.Remove(path); err != nil {
  312. c.Log(logger.LevelWarn, logSender, "failed to remove directory %#v: %v", path, err)
  313. return getSFTPErrorFromOSError(err)
  314. }
  315. logger.CommandLog(rmdirLogSender, path, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  316. return sftp.ErrSSHFxOk
  317. }
  318. func (c Connection) handleSFTPSymlink(sourcePath string, targetPath string) error {
  319. if !c.User.HasPerm(dataprovider.PermCreateSymlinks, filepath.Dir(targetPath)) {
  320. return sftp.ErrSSHFxPermissionDenied
  321. }
  322. if err := os.Symlink(sourcePath, targetPath); err != nil {
  323. c.Log(logger.LevelWarn, logSender, "failed to create symlink %#v -> %#v: %v", sourcePath, targetPath, err)
  324. return getSFTPErrorFromOSError(err)
  325. }
  326. logger.CommandLog(symlinkLogSender, sourcePath, targetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  327. return nil
  328. }
  329. func (c Connection) handleSFTPMkdir(path string) error {
  330. if !c.User.HasPerm(dataprovider.PermCreateDirs, filepath.Dir(path)) {
  331. return sftp.ErrSSHFxPermissionDenied
  332. }
  333. if err := os.Mkdir(path, 0777); err != nil {
  334. c.Log(logger.LevelWarn, logSender, "error creating missing dir: %#v error: %v", path, err)
  335. return getSFTPErrorFromOSError(err)
  336. }
  337. utils.SetPathPermissions(path, c.User.GetUID(), c.User.GetGID())
  338. logger.CommandLog(mkdirLogSender, path, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  339. return nil
  340. }
  341. func (c Connection) handleSFTPRemove(path string) error {
  342. if !c.User.HasPerm(dataprovider.PermDelete, filepath.Dir(path)) {
  343. return sftp.ErrSSHFxPermissionDenied
  344. }
  345. var size int64
  346. var fi os.FileInfo
  347. var err error
  348. if fi, err = os.Lstat(path); err != nil {
  349. c.Log(logger.LevelWarn, logSender, "failed to remove a file %#v: stat error: %v", path, err)
  350. return getSFTPErrorFromOSError(err)
  351. }
  352. if fi.IsDir() && fi.Mode()&os.ModeSymlink != os.ModeSymlink {
  353. c.Log(logger.LevelDebug, logSender, "cannot remove %#v is not a file/symlink", path)
  354. return sftp.ErrSSHFxFailure
  355. }
  356. size = fi.Size()
  357. if err := os.Remove(path); err != nil {
  358. c.Log(logger.LevelWarn, logSender, "failed to remove a file/symlink %#v: %v", path, err)
  359. return getSFTPErrorFromOSError(err)
  360. }
  361. logger.CommandLog(removeLogSender, path, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  362. if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
  363. dataprovider.UpdateUserQuota(dataProvider, c.User, -1, -size, false)
  364. }
  365. go executeAction(operationDelete, c.User.Username, path, "", "")
  366. return sftp.ErrSSHFxOk
  367. }
  368. func (c Connection) handleSFTPUploadToNewFile(requestPath, filePath string) (io.WriterAt, error) {
  369. if !c.hasSpace(true) {
  370. c.Log(logger.LevelInfo, logSender, "denying file write due to space limit")
  371. return nil, sftp.ErrSSHFxFailure
  372. }
  373. file, err := os.Create(filePath)
  374. if err != nil {
  375. c.Log(logger.LevelWarn, logSender, "error creating file %#v: %v", requestPath, err)
  376. return nil, getSFTPErrorFromOSError(err)
  377. }
  378. utils.SetPathPermissions(filePath, c.User.GetUID(), c.User.GetGID())
  379. transfer := Transfer{
  380. file: file,
  381. path: requestPath,
  382. start: time.Now(),
  383. bytesSent: 0,
  384. bytesReceived: 0,
  385. user: c.User,
  386. connectionID: c.ID,
  387. transferType: transferUpload,
  388. lastActivity: time.Now(),
  389. isNewFile: true,
  390. protocol: c.protocol,
  391. transferError: nil,
  392. isFinished: false,
  393. minWriteOffset: 0,
  394. }
  395. addTransfer(&transfer)
  396. return &transfer, nil
  397. }
  398. func (c Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, requestPath, filePath string,
  399. fileSize int64) (io.WriterAt, error) {
  400. var err error
  401. if !c.hasSpace(false) {
  402. c.Log(logger.LevelInfo, logSender, "denying file write due to space limit")
  403. return nil, sftp.ErrSSHFxFailure
  404. }
  405. minWriteOffset := int64(0)
  406. osFlags := getOSOpenFlags(pflags)
  407. if isAtomicUploadEnabled() {
  408. err = os.Rename(requestPath, filePath)
  409. if err != nil {
  410. c.Log(logger.LevelWarn, logSender, "error renaming existing file for atomic upload, source: %#v, dest: %#v, err: %v",
  411. requestPath, filePath, err)
  412. return nil, getSFTPErrorFromOSError(err)
  413. }
  414. }
  415. // we use 0666 so the umask is applied
  416. file, err := os.OpenFile(filePath, osFlags, 0666)
  417. if err != nil {
  418. c.Log(logger.LevelWarn, logSender, "error opening existing file, flags: %v, source: %#v, err: %v", pflags, filePath, err)
  419. return nil, getSFTPErrorFromOSError(err)
  420. }
  421. if pflags.Append && osFlags&os.O_TRUNC == 0 {
  422. c.Log(logger.LevelDebug, logSender, "upload resume requested, file path: %#v initial size: %v", filePath, fileSize)
  423. minWriteOffset = fileSize
  424. } else {
  425. dataprovider.UpdateUserQuota(dataProvider, c.User, 0, -fileSize, false)
  426. }
  427. utils.SetPathPermissions(filePath, c.User.GetUID(), c.User.GetGID())
  428. transfer := Transfer{
  429. file: file,
  430. path: requestPath,
  431. start: time.Now(),
  432. bytesSent: 0,
  433. bytesReceived: 0,
  434. user: c.User,
  435. connectionID: c.ID,
  436. transferType: transferUpload,
  437. lastActivity: time.Now(),
  438. isNewFile: false,
  439. protocol: c.protocol,
  440. transferError: nil,
  441. isFinished: false,
  442. minWriteOffset: minWriteOffset,
  443. }
  444. addTransfer(&transfer)
  445. return &transfer, nil
  446. }
  447. func (c Connection) hasSpace(checkFiles bool) bool {
  448. if (checkFiles && c.User.QuotaFiles > 0) || c.User.QuotaSize > 0 {
  449. numFile, size, err := dataprovider.GetUsedQuota(dataProvider, c.User.Username)
  450. if err != nil {
  451. if _, ok := err.(*dataprovider.MethodDisabledError); ok {
  452. c.Log(logger.LevelWarn, logSender, "quota enforcement not possible for user %v: %v", c.User.Username, err)
  453. return true
  454. }
  455. c.Log(logger.LevelWarn, logSender, "error getting used quota for %v: %v", c.User.Username, err)
  456. return false
  457. }
  458. if (checkFiles && c.User.QuotaFiles > 0 && numFile >= c.User.QuotaFiles) ||
  459. (c.User.QuotaSize > 0 && size >= c.User.QuotaSize) {
  460. c.Log(logger.LevelDebug, logSender, "quota exceed for user %v, num files: %v/%v, size: %v/%v check files: %v",
  461. c.User.Username, numFile, c.User.QuotaFiles, size, c.User.QuotaSize, checkFiles)
  462. return false
  463. }
  464. }
  465. return true
  466. }
  467. // Normalizes a file/directory we get from the SFTP request to ensure the user is not able to escape
  468. // from their data directory. After normalization if the file/directory is still within their home
  469. // path it is returned. If they managed to "escape" an error will be returned.
  470. func (c Connection) buildPath(rawPath string) (string, error) {
  471. r := filepath.Clean(filepath.Join(c.User.HomeDir, rawPath))
  472. p, err := filepath.EvalSymlinks(r)
  473. if err != nil && !os.IsNotExist(err) {
  474. return "", err
  475. } else if os.IsNotExist(err) {
  476. // The requested path doesn't exist, so at this point we need to iterate up the
  477. // path chain until we hit a directory that _does_ exist and can be validated.
  478. _, err = c.findFirstExistingDir(r)
  479. if err != nil {
  480. c.Log(logger.LevelWarn, logSender, "error resolving not existent path: %#v", err)
  481. }
  482. return r, err
  483. }
  484. err = c.isSubDir(p)
  485. if err != nil {
  486. c.Log(logger.LevelWarn, logSender, "Invalid path resolution, dir: %#v outside user home: %#v err: %v", p, c.User.HomeDir, err)
  487. }
  488. return r, err
  489. }
  490. // iterate up the path chain until we hit a directory that does exist and can be validated.
  491. // all nonexistent directories will be returned
  492. func (c Connection) findNonexistentDirs(path string) ([]string, error) {
  493. results := []string{}
  494. cleanPath := filepath.Clean(path)
  495. parent := filepath.Dir(cleanPath)
  496. _, err := os.Stat(parent)
  497. for os.IsNotExist(err) {
  498. results = append(results, parent)
  499. parent = filepath.Dir(parent)
  500. _, err = os.Stat(parent)
  501. }
  502. if err != nil {
  503. return results, err
  504. }
  505. p, err := filepath.EvalSymlinks(parent)
  506. if err != nil {
  507. return results, err
  508. }
  509. err = c.isSubDir(p)
  510. if err != nil {
  511. c.Log(logger.LevelWarn, logSender, "Error finding non existing dir: %v", err)
  512. }
  513. return results, err
  514. }
  515. // iterate up the path chain until we hit a directory that does exist and can be validated.
  516. func (c Connection) findFirstExistingDir(path string) (string, error) {
  517. results, err := c.findNonexistentDirs(path)
  518. if err != nil {
  519. c.Log(logger.LevelWarn, logSender, "unable to find non existent dirs: %v", err)
  520. return "", err
  521. }
  522. var parent string
  523. if len(results) > 0 {
  524. lastMissingDir := results[len(results)-1]
  525. parent = filepath.Dir(lastMissingDir)
  526. } else {
  527. parent = c.User.GetHomeDir()
  528. }
  529. p, err := filepath.EvalSymlinks(parent)
  530. if err != nil {
  531. return "", err
  532. }
  533. fileInfo, err := os.Stat(p)
  534. if err != nil {
  535. return "", err
  536. }
  537. if !fileInfo.IsDir() {
  538. return "", fmt.Errorf("resolved path is not a dir: %#v", p)
  539. }
  540. err = c.isSubDir(p)
  541. return p, err
  542. }
  543. // checks if sub is a subpath of the user home dir.
  544. // EvalSymlink must be used on sub before calling this method
  545. func (c Connection) isSubDir(sub string) error {
  546. // home dir must exist and it is already a validated absolute path
  547. parent, err := filepath.EvalSymlinks(c.User.HomeDir)
  548. if err != nil {
  549. c.Log(logger.LevelWarn, logSender, "invalid home dir %#v: %v", c.User.HomeDir, err)
  550. return err
  551. }
  552. if !strings.HasPrefix(sub, parent) {
  553. c.Log(logger.LevelWarn, logSender, "path %#v is not inside: %#v ", sub, parent)
  554. return fmt.Errorf("path %#v is not inside: %#v", sub, parent)
  555. }
  556. return nil
  557. }
  558. func (c Connection) close() error {
  559. if c.channel != nil {
  560. err := c.channel.Close()
  561. c.Log(logger.LevelInfo, logSender, "channel close, err: %v", err)
  562. }
  563. return c.netConn.Close()
  564. }
  565. func getOSOpenFlags(requestFlags sftp.FileOpenFlags) (flags int) {
  566. var osFlags int
  567. if requestFlags.Read && requestFlags.Write {
  568. osFlags |= os.O_RDWR
  569. } else if requestFlags.Write {
  570. osFlags |= os.O_WRONLY
  571. }
  572. // we ignore Append flag since pkg/sftp use WriteAt that cannot work with os.O_APPEND
  573. /*if requestFlags.Append {
  574. osFlags |= os.O_APPEND
  575. }*/
  576. if requestFlags.Creat {
  577. osFlags |= os.O_CREATE
  578. }
  579. if requestFlags.Trunc {
  580. osFlags |= os.O_TRUNC
  581. }
  582. if requestFlags.Excl {
  583. osFlags |= os.O_EXCL
  584. }
  585. return osFlags
  586. }
  587. func getUploadTempFilePath(path string) string {
  588. dir := filepath.Dir(path)
  589. guid := xid.New().String()
  590. return filepath.Join(dir, ".sftpgo-upload."+guid+"."+filepath.Base(path))
  591. }
  592. func getSFTPErrorFromOSError(err error) error {
  593. if os.IsNotExist(err) {
  594. return sftp.ErrSSHFxNoSuchFile
  595. } else if os.IsPermission(err) {
  596. return sftp.ErrSSHFxPermissionDenied
  597. } else if err != nil {
  598. return sftp.ErrSSHFxFailure
  599. }
  600. return nil
  601. }