handler.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. package sftpd
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "net"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "sync"
  12. "time"
  13. "github.com/drakkan/sftpgo/utils"
  14. "github.com/rs/xid"
  15. "golang.org/x/crypto/ssh"
  16. "github.com/drakkan/sftpgo/dataprovider"
  17. "github.com/drakkan/sftpgo/logger"
  18. "github.com/pkg/sftp"
  19. )
  20. // Connection details for an authenticated user
  21. type Connection struct {
  22. // Unique identifier for the connection
  23. ID string
  24. // logged in user's details
  25. User dataprovider.User
  26. // client's version string
  27. ClientVersion string
  28. // Remote address for this connection
  29. RemoteAddr net.Addr
  30. // start time for this connection
  31. StartTime time.Time
  32. // last activity for this connection
  33. lastActivity time.Time
  34. protocol string
  35. lock *sync.Mutex
  36. netConn net.Conn
  37. channel ssh.Channel
  38. command string
  39. }
  40. // Log outputs a log entry to the configured logger
  41. func (c Connection) Log(level logger.LogLevel, sender string, format string, v ...interface{}) {
  42. logger.Log(level, sender, c.ID, format, v...)
  43. }
  44. // Fileread creates a reader for a file on the system and returns the reader back.
  45. func (c Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) {
  46. updateConnectionActivity(c.ID)
  47. if !c.User.HasPerm(dataprovider.PermDownload, path.Dir(request.Filepath)) {
  48. return nil, sftp.ErrSSHFxPermissionDenied
  49. }
  50. p, err := c.buildPath(request.Filepath)
  51. if err != nil {
  52. return nil, getSFTPErrorFromOSError(err)
  53. }
  54. c.lock.Lock()
  55. defer c.lock.Unlock()
  56. if _, err := os.Stat(p); err != nil {
  57. return nil, getSFTPErrorFromOSError(err)
  58. }
  59. file, err := os.Open(p)
  60. if err != nil {
  61. c.Log(logger.LevelWarn, logSender, "could not open file %#v for reading: %v", p, err)
  62. return nil, getSFTPErrorFromOSError(err)
  63. }
  64. c.Log(logger.LevelDebug, logSender, "fileread requested for path: %#v", p)
  65. transfer := Transfer{
  66. file: file,
  67. path: p,
  68. start: time.Now(),
  69. bytesSent: 0,
  70. bytesReceived: 0,
  71. user: c.User,
  72. connectionID: c.ID,
  73. transferType: transferDownload,
  74. lastActivity: time.Now(),
  75. isNewFile: false,
  76. protocol: c.protocol,
  77. transferError: nil,
  78. isFinished: false,
  79. minWriteOffset: 0,
  80. }
  81. addTransfer(&transfer)
  82. return &transfer, nil
  83. }
  84. // Filewrite handles the write actions for a file on the system.
  85. func (c Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) {
  86. updateConnectionActivity(c.ID)
  87. p, err := c.buildPath(request.Filepath)
  88. if err != nil {
  89. return nil, getSFTPErrorFromOSError(err)
  90. }
  91. filePath := p
  92. if isAtomicUploadEnabled() {
  93. filePath = getUploadTempFilePath(p)
  94. }
  95. c.lock.Lock()
  96. defer c.lock.Unlock()
  97. stat, statErr := os.Stat(p)
  98. if os.IsNotExist(statErr) {
  99. if !c.User.HasPerm(dataprovider.PermUpload, path.Dir(request.Filepath)) {
  100. return nil, sftp.ErrSSHFxPermissionDenied
  101. }
  102. return c.handleSFTPUploadToNewFile(p, filePath)
  103. }
  104. if statErr != nil {
  105. c.Log(logger.LevelError, logSender, "error performing file stat %#v: %v", p, statErr)
  106. return nil, getSFTPErrorFromOSError(err)
  107. }
  108. // This happen if we upload a file that has the same name of an existing directory
  109. if stat.IsDir() {
  110. c.Log(logger.LevelWarn, logSender, "attempted to open a directory for writing to: %#v", p)
  111. return nil, sftp.ErrSSHFxOpUnsupported
  112. }
  113. if !c.User.HasPerm(dataprovider.PermOverwrite, path.Dir(request.Filepath)) {
  114. return nil, sftp.ErrSSHFxPermissionDenied
  115. }
  116. return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size())
  117. }
  118. // Filecmd hander for basic SFTP system calls related to files, but not anything to do with reading
  119. // or writing to those files.
  120. func (c Connection) Filecmd(request *sftp.Request) error {
  121. updateConnectionActivity(c.ID)
  122. p, err := c.buildPath(request.Filepath)
  123. if err != nil {
  124. return getSFTPErrorFromOSError(err)
  125. }
  126. target, err := c.getSFTPCmdTargetPath(request.Target)
  127. if err != nil {
  128. return err
  129. }
  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 err = c.handleSFTPRename(p, target, request); err != nil {
  137. return err
  138. }
  139. break
  140. case "Rmdir":
  141. return c.handleSFTPRmdir(p, request)
  142. case "Mkdir":
  143. err = c.handleSFTPMkdir(p, request)
  144. if err != nil {
  145. return err
  146. }
  147. break
  148. case "Symlink":
  149. if err = c.handleSFTPSymlink(p, target, request); err != nil {
  150. return err
  151. }
  152. break
  153. case "Remove":
  154. return c.handleSFTPRemove(p, request)
  155. default:
  156. return sftp.ErrSSHFxOpUnsupported
  157. }
  158. var fileLocation = p
  159. if target != "" {
  160. fileLocation = target
  161. }
  162. // we return if we remove a file or a dir so source path or target path always exists here
  163. utils.SetPathPermissions(fileLocation, c.User.GetUID(), c.User.GetGID())
  164. return sftp.ErrSSHFxOk
  165. }
  166. // Filelist is the handler for SFTP filesystem list calls. This will handle calls to list the contents of
  167. // a directory as well as perform file/folder stat calls.
  168. func (c Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
  169. updateConnectionActivity(c.ID)
  170. p, err := c.buildPath(request.Filepath)
  171. if err != nil {
  172. return nil, getSFTPErrorFromOSError(err)
  173. }
  174. switch request.Method {
  175. case "List":
  176. if !c.User.HasPerm(dataprovider.PermListItems, request.Filepath) {
  177. return nil, sftp.ErrSSHFxPermissionDenied
  178. }
  179. c.Log(logger.LevelDebug, logSender, "requested list file for dir: %#v", p)
  180. files, err := ioutil.ReadDir(p)
  181. if err != nil {
  182. c.Log(logger.LevelWarn, logSender, "error listing directory: %#v", err)
  183. return nil, getSFTPErrorFromOSError(err)
  184. }
  185. return listerAt(files), nil
  186. case "Stat":
  187. if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(request.Filepath)) {
  188. return nil, sftp.ErrSSHFxPermissionDenied
  189. }
  190. c.Log(logger.LevelDebug, logSender, "requested stat for path: %#v", p)
  191. s, err := os.Stat(p)
  192. if err != nil {
  193. c.Log(logger.LevelWarn, logSender, "error running stat on path: %#v", err)
  194. return nil, getSFTPErrorFromOSError(err)
  195. }
  196. return listerAt([]os.FileInfo{s}), nil
  197. default:
  198. return nil, sftp.ErrSSHFxOpUnsupported
  199. }
  200. }
  201. func (c Connection) getSFTPCmdTargetPath(requestTarget string) (string, error) {
  202. var target string
  203. // If a target is provided in this request validate that it is going to the correct
  204. // location for the server. If it is not, return an error
  205. if len(requestTarget) > 0 {
  206. var err error
  207. target, err = c.buildPath(requestTarget)
  208. if err != nil {
  209. return target, getSFTPErrorFromOSError(err)
  210. }
  211. }
  212. return target, nil
  213. }
  214. func (c Connection) handleSFTPSetstat(filePath string, request *sftp.Request) error {
  215. if setstatMode == 1 {
  216. return nil
  217. }
  218. if len(request.Attrs) < 1 {
  219. c.Log(logger.LevelInfo, logSender, "cannot handle Setstat request with no attrs, this is probably a buggy client: %v",
  220. c.ClientVersion)
  221. return sftp.ErrSSHFxBadMessage
  222. }
  223. pathForPerms := request.Filepath
  224. if fi, err := os.Lstat(filePath); err == nil {
  225. if fi.IsDir() {
  226. pathForPerms = path.Dir(request.Filepath)
  227. }
  228. }
  229. attrFlags := request.AttrFlags()
  230. if attrFlags.Permissions {
  231. if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
  232. return sftp.ErrSSHFxPermissionDenied
  233. }
  234. fileMode := request.Attributes().FileMode()
  235. if err := os.Chmod(filePath, fileMode); err != nil {
  236. c.Log(logger.LevelWarn, logSender, "failed to chmod path %#v, mode: %v, err: %v", filePath, fileMode.String(), err)
  237. return getSFTPErrorFromOSError(err)
  238. }
  239. logger.CommandLog(chmodLogSender, filePath, "", c.User.Username, fileMode.String(), c.ID, c.protocol, -1, -1, "", "", "")
  240. return nil
  241. } else if attrFlags.UidGid {
  242. if !c.User.HasPerm(dataprovider.PermChown, pathForPerms) {
  243. return sftp.ErrSSHFxPermissionDenied
  244. }
  245. uid := int(request.Attributes().UID)
  246. gid := int(request.Attributes().GID)
  247. if err := os.Chown(filePath, uid, gid); err != nil {
  248. c.Log(logger.LevelWarn, logSender, "failed to chown path %#v, uid: %v, gid: %v, err: %v", filePath, uid, gid, err)
  249. return getSFTPErrorFromOSError(err)
  250. }
  251. logger.CommandLog(chownLogSender, filePath, "", c.User.Username, "", c.ID, c.protocol, uid, gid, "", "", "")
  252. return nil
  253. } else if attrFlags.Acmodtime {
  254. if !c.User.HasPerm(dataprovider.PermChtimes, pathForPerms) {
  255. return sftp.ErrSSHFxPermissionDenied
  256. }
  257. dateFormat := "2006-01-02T15:04:05" // YYYY-MM-DDTHH:MM:SS
  258. accessTime := time.Unix(int64(request.Attributes().Atime), 0)
  259. modificationTime := time.Unix(int64(request.Attributes().Mtime), 0)
  260. accessTimeString := accessTime.Format(dateFormat)
  261. modificationTimeString := modificationTime.Format(dateFormat)
  262. if err := os.Chtimes(filePath, accessTime, modificationTime); err != nil {
  263. c.Log(logger.LevelWarn, logSender, "failed to chtimes for path %#v, access time: %v, modification time: %v, err: %v",
  264. filePath, accessTime, modificationTime, err)
  265. return getSFTPErrorFromOSError(err)
  266. }
  267. logger.CommandLog(chtimesLogSender, filePath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, accessTimeString,
  268. modificationTimeString, "")
  269. return nil
  270. }
  271. return nil
  272. }
  273. func (c Connection) handleSFTPRename(sourcePath string, targetPath string, request *sftp.Request) error {
  274. if c.User.GetRelativePath(sourcePath) == "/" {
  275. c.Log(logger.LevelWarn, logSender, "renaming root dir is not allowed")
  276. return sftp.ErrSSHFxPermissionDenied
  277. }
  278. if !c.User.HasPerm(dataprovider.PermRename, path.Dir(request.Target)) {
  279. return sftp.ErrSSHFxPermissionDenied
  280. }
  281. if err := os.Rename(sourcePath, targetPath); err != nil {
  282. c.Log(logger.LevelWarn, logSender, "failed to rename file, source: %#v target: %#v: %v", sourcePath, targetPath, err)
  283. return getSFTPErrorFromOSError(err)
  284. }
  285. logger.CommandLog(renameLogSender, sourcePath, targetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  286. go executeAction(operationRename, c.User.Username, sourcePath, targetPath, "", 0)
  287. return nil
  288. }
  289. func (c Connection) handleSFTPRmdir(dirPath string, request *sftp.Request) error {
  290. if c.User.GetRelativePath(dirPath) == "/" {
  291. c.Log(logger.LevelWarn, logSender, "removing root dir is not allowed")
  292. return sftp.ErrSSHFxPermissionDenied
  293. }
  294. if !c.User.HasPerm(dataprovider.PermDelete, path.Dir(request.Filepath)) {
  295. return sftp.ErrSSHFxPermissionDenied
  296. }
  297. var fi os.FileInfo
  298. var err error
  299. if fi, err = os.Lstat(dirPath); err != nil {
  300. c.Log(logger.LevelWarn, logSender, "failed to remove a dir %#v: stat error: %v", dirPath, err)
  301. return getSFTPErrorFromOSError(err)
  302. }
  303. if !fi.IsDir() || fi.Mode()&os.ModeSymlink == os.ModeSymlink {
  304. c.Log(logger.LevelDebug, logSender, "cannot remove %#v is not a directory", dirPath)
  305. return sftp.ErrSSHFxFailure
  306. }
  307. if err = os.Remove(dirPath); err != nil {
  308. c.Log(logger.LevelWarn, logSender, "failed to remove directory %#v: %v", dirPath, err)
  309. return getSFTPErrorFromOSError(err)
  310. }
  311. logger.CommandLog(rmdirLogSender, dirPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  312. return sftp.ErrSSHFxOk
  313. }
  314. func (c Connection) handleSFTPSymlink(sourcePath string, targetPath string, request *sftp.Request) error {
  315. if c.User.GetRelativePath(sourcePath) == "/" {
  316. c.Log(logger.LevelWarn, logSender, "symlinking root dir is not allowed")
  317. return sftp.ErrSSHFxPermissionDenied
  318. }
  319. if !c.User.HasPerm(dataprovider.PermCreateSymlinks, path.Dir(request.Target)) {
  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(dirPath string, request *sftp.Request) error {
  330. if !c.User.HasPerm(dataprovider.PermCreateDirs, path.Dir(request.Filepath)) {
  331. return sftp.ErrSSHFxPermissionDenied
  332. }
  333. if err := os.Mkdir(dirPath, 0777); err != nil {
  334. c.Log(logger.LevelWarn, logSender, "error creating missing dir: %#v error: %v", dirPath, err)
  335. return getSFTPErrorFromOSError(err)
  336. }
  337. utils.SetPathPermissions(dirPath, c.User.GetUID(), c.User.GetGID())
  338. logger.CommandLog(mkdirLogSender, dirPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
  339. return nil
  340. }
  341. func (c Connection) handleSFTPRemove(filePath string, request *sftp.Request) error {
  342. if !c.User.HasPerm(dataprovider.PermDelete, path.Dir(request.Filepath)) {
  343. return sftp.ErrSSHFxPermissionDenied
  344. }
  345. var size int64
  346. var fi os.FileInfo
  347. var err error
  348. if fi, err = os.Lstat(filePath); err != nil {
  349. c.Log(logger.LevelWarn, logSender, "failed to remove a file %#v: stat error: %v", filePath, 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", filePath)
  354. return sftp.ErrSSHFxFailure
  355. }
  356. size = fi.Size()
  357. if err := os.Remove(filePath); err != nil {
  358. c.Log(logger.LevelWarn, logSender, "failed to remove a file/symlink %#v: %v", filePath, err)
  359. return getSFTPErrorFromOSError(err)
  360. }
  361. logger.CommandLog(removeLogSender, filePath, "", 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, filePath, "", "", fi.Size())
  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. }