common.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. // Package common defines code shared among file transfer packages and protocols
  2. package common
  3. import (
  4. "errors"
  5. "fmt"
  6. "net"
  7. "os"
  8. "sync"
  9. "time"
  10. "github.com/pires/go-proxyproto"
  11. "github.com/drakkan/sftpgo/logger"
  12. "github.com/drakkan/sftpgo/metrics"
  13. "github.com/drakkan/sftpgo/utils"
  14. )
  15. // constants
  16. const (
  17. uploadLogSender = "Upload"
  18. downloadLogSender = "Download"
  19. renameLogSender = "Rename"
  20. rmdirLogSender = "Rmdir"
  21. mkdirLogSender = "Mkdir"
  22. symlinkLogSender = "Symlink"
  23. removeLogSender = "Remove"
  24. chownLogSender = "Chown"
  25. chmodLogSender = "Chmod"
  26. chtimesLogSender = "Chtimes"
  27. operationDownload = "download"
  28. operationUpload = "upload"
  29. operationDelete = "delete"
  30. operationPreDelete = "pre-delete"
  31. operationRename = "rename"
  32. operationSSHCmd = "ssh_cmd"
  33. chtimesFormat = "2006-01-02T15:04:05" // YYYY-MM-DDTHH:MM:SS
  34. idleTimeoutCheckInterval = 5 * time.Minute
  35. )
  36. // Stat flags
  37. const (
  38. StatAttrUIDGID = 1
  39. StatAttrPerms = 2
  40. StatAttrTimes = 4
  41. )
  42. // Transfer types
  43. const (
  44. TransferUpload = iota
  45. TransferDownload
  46. )
  47. // Supported protocols
  48. const (
  49. ProtocolSFTP = "SFTP"
  50. ProtocolSCP = "SCP"
  51. ProtocolSSH = "SSH"
  52. )
  53. // Upload modes
  54. const (
  55. UploadModeStandard = iota
  56. UploadModeAtomic
  57. UploadModeAtomicWithResume
  58. )
  59. // errors definitions
  60. var (
  61. ErrPermissionDenied = errors.New("permission denied")
  62. ErrNotExist = errors.New("no such file or directory")
  63. ErrOpUnsupported = errors.New("operation unsupported")
  64. ErrGenericFailure = errors.New("failure")
  65. ErrQuotaExceeded = errors.New("denying write due to space limit")
  66. ErrSkipPermissionsCheck = errors.New("permission check skipped")
  67. )
  68. var (
  69. // Config is the configuration for the supported protocols
  70. Config Configuration
  71. // Connections is the list of active connections
  72. Connections ActiveConnections
  73. // QuotaScans is the list of active quota scans
  74. QuotaScans ActiveScans
  75. idleTimeoutTicker *time.Ticker
  76. idleTimeoutTickerDone chan bool
  77. supportedProcols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH}
  78. )
  79. // Initialize sets the common configuration
  80. func Initialize(c Configuration) {
  81. Config = c
  82. Config.idleTimeoutAsDuration = time.Duration(Config.IdleTimeout) * time.Minute
  83. if Config.IdleTimeout > 0 {
  84. startIdleTimeoutTicker(idleTimeoutCheckInterval)
  85. }
  86. }
  87. func startIdleTimeoutTicker(duration time.Duration) {
  88. stopIdleTimeoutTicker()
  89. idleTimeoutTicker = time.NewTicker(duration)
  90. idleTimeoutTickerDone = make(chan bool)
  91. go func() {
  92. for {
  93. select {
  94. case <-idleTimeoutTickerDone:
  95. return
  96. case <-idleTimeoutTicker.C:
  97. Connections.checkIdleConnections()
  98. }
  99. }
  100. }()
  101. }
  102. func stopIdleTimeoutTicker() {
  103. if idleTimeoutTicker != nil {
  104. idleTimeoutTicker.Stop()
  105. idleTimeoutTickerDone <- true
  106. idleTimeoutTicker = nil
  107. }
  108. }
  109. // ActiveTransfer defines the interface for the current active transfers
  110. type ActiveTransfer interface {
  111. GetID() uint64
  112. GetType() int
  113. GetSize() int64
  114. GetVirtualPath() string
  115. GetStartTime() time.Time
  116. }
  117. // ActiveConnection defines the interface for the current active connections
  118. type ActiveConnection interface {
  119. GetID() string
  120. GetUsername() string
  121. GetRemoteAddress() string
  122. GetClientVersion() string
  123. GetProtocol() string
  124. GetConnectionTime() time.Time
  125. GetLastActivity() time.Time
  126. GetCommand() string
  127. Disconnect() error
  128. SetConnDeadline()
  129. AddTransfer(t ActiveTransfer)
  130. RemoveTransfer(t ActiveTransfer)
  131. GetTransfers() []ConnectionTransfer
  132. }
  133. // StatAttributes defines the attributes for set stat commands
  134. type StatAttributes struct {
  135. Mode os.FileMode
  136. Atime time.Time
  137. Mtime time.Time
  138. UID int
  139. GID int
  140. Flags int
  141. }
  142. // ConnectionTransfer defines the trasfer details to expose
  143. type ConnectionTransfer struct {
  144. ID uint64 `json:"-"`
  145. OperationType string `json:"operation_type"`
  146. StartTime int64 `json:"start_time"`
  147. Size int64 `json:"size"`
  148. VirtualPath string `json:"path"`
  149. }
  150. func (t *ConnectionTransfer) getConnectionTransferAsString() string {
  151. result := ""
  152. if t.OperationType == operationUpload {
  153. result += "UL"
  154. } else {
  155. result += "DL"
  156. }
  157. result += fmt.Sprintf(" %#v ", t.VirtualPath)
  158. if t.Size > 0 {
  159. elapsed := time.Since(utils.GetTimeFromMsecSinceEpoch(t.StartTime))
  160. speed := float64(t.Size) / float64(utils.GetTimeAsMsSinceEpoch(time.Now())-t.StartTime)
  161. result += fmt.Sprintf("Size: %#v Elapsed: %#v Speed: \"%.1f KB/s\"", utils.ByteCountSI(t.Size),
  162. utils.GetDurationAsString(elapsed), speed)
  163. }
  164. return result
  165. }
  166. // Configuration defines configuration parameters common to all supported protocols
  167. type Configuration struct {
  168. // Maximum idle timeout as minutes. If a client is idle for a time that exceeds this setting it will be disconnected.
  169. // 0 means disabled
  170. IdleTimeout int `json:"idle_timeout" mapstructure:"idle_timeout"`
  171. // UploadMode 0 means standard, the files are uploaded directly to the requested path.
  172. // 1 means atomic: the files are uploaded to a temporary path and renamed to the requested path
  173. // when the client ends the upload. Atomic mode avoid problems such as a web server that
  174. // serves partial files when the files are being uploaded.
  175. // In atomic mode if there is an upload error the temporary file is deleted and so the requested
  176. // upload path will not contain a partial file.
  177. // 2 means atomic with resume support: as atomic but if there is an upload error the temporary
  178. // file is renamed to the requested path and not deleted, this way a client can reconnect and resume
  179. // the upload.
  180. UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
  181. // Actions to execute for SFTP file operations and SSH commands
  182. Actions ProtocolActions `json:"actions" mapstructure:"actions"`
  183. // SetstatMode 0 means "normal mode": requests for changing permissions and owner/group are executed.
  184. // 1 means "ignore mode": requests for changing permissions and owner/group are silently ignored.
  185. SetstatMode int `json:"setstat_mode" mapstructure:"setstat_mode"`
  186. // Support for HAProxy PROXY protocol.
  187. // If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable
  188. // the proxy protocol. It provides a convenient way to safely transport connection information
  189. // such as a client's address across multiple layers of NAT or TCP proxies to get the real
  190. // client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported.
  191. // - 0 means disabled
  192. // - 1 means proxy protocol enabled. Proxy header will be used and requests without proxy header will be accepted.
  193. // - 2 means proxy protocol required. Proxy header will be used and requests without proxy header will be rejected.
  194. // If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too,
  195. // for example for HAProxy add "send-proxy" or "send-proxy-v2" to each server configuration line.
  196. ProxyProtocol int `json:"proxy_protocol" mapstructure:"proxy_protocol"`
  197. // List of IP addresses and IP ranges allowed to send the proxy header.
  198. // If proxy protocol is set to 1 and we receive a proxy header from an IP that is not in the list then the
  199. // connection will be accepted and the header will be ignored.
  200. // If proxy protocol is set to 2 and we receive a proxy header from an IP that is not in the list then the
  201. // connection will be rejected.
  202. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  203. idleTimeoutAsDuration time.Duration
  204. }
  205. // IsAtomicUploadEnabled returns true if atomic upload is enabled
  206. func (c *Configuration) IsAtomicUploadEnabled() bool {
  207. return c.UploadMode == UploadModeAtomic || c.UploadMode == UploadModeAtomicWithResume
  208. }
  209. // GetProxyListener returns a wrapper for the given listener that supports the
  210. // HAProxy Proxy Protocol or nil if the proxy protocol is not configured
  211. func (c *Configuration) GetProxyListener(listener net.Listener) (*proxyproto.Listener, error) {
  212. var proxyListener *proxyproto.Listener
  213. var err error
  214. if c.ProxyProtocol > 0 {
  215. var policyFunc func(upstream net.Addr) (proxyproto.Policy, error)
  216. if c.ProxyProtocol == 1 && len(c.ProxyAllowed) > 0 {
  217. policyFunc, err = proxyproto.LaxWhiteListPolicy(c.ProxyAllowed)
  218. if err != nil {
  219. return nil, err
  220. }
  221. }
  222. if c.ProxyProtocol == 2 {
  223. if len(c.ProxyAllowed) == 0 {
  224. policyFunc = func(upstream net.Addr) (proxyproto.Policy, error) {
  225. return proxyproto.REQUIRE, nil
  226. }
  227. } else {
  228. policyFunc, err = proxyproto.StrictWhiteListPolicy(c.ProxyAllowed)
  229. if err != nil {
  230. return nil, err
  231. }
  232. }
  233. }
  234. proxyListener = &proxyproto.Listener{
  235. Listener: listener,
  236. Policy: policyFunc,
  237. }
  238. }
  239. return proxyListener, nil
  240. }
  241. // ActiveConnections holds the currect active connections with the associated transfers
  242. type ActiveConnections struct {
  243. sync.RWMutex
  244. connections []ActiveConnection
  245. }
  246. // GetActiveSessions returns the number of active sessions for the given username.
  247. // We return the open sessions for any protocol
  248. func (conns *ActiveConnections) GetActiveSessions(username string) int {
  249. conns.RLock()
  250. defer conns.RUnlock()
  251. numSessions := 0
  252. for _, c := range conns.connections {
  253. if c.GetUsername() == username {
  254. numSessions++
  255. }
  256. }
  257. return numSessions
  258. }
  259. // Add adds a new connection to the active ones
  260. func (conns *ActiveConnections) Add(c ActiveConnection) {
  261. conns.Lock()
  262. defer conns.Unlock()
  263. conns.connections = append(conns.connections, c)
  264. metrics.UpdateActiveConnectionsSize(len(conns.connections))
  265. logger.Debug(c.GetProtocol(), c.GetID(), "connection added, num open connections: %v", len(conns.connections))
  266. }
  267. // Remove removes a connection from the active ones
  268. func (conns *ActiveConnections) Remove(c ActiveConnection) {
  269. conns.Lock()
  270. defer conns.Unlock()
  271. indexToRemove := -1
  272. for i, v := range conns.connections {
  273. if v.GetID() == c.GetID() {
  274. indexToRemove = i
  275. break
  276. }
  277. }
  278. if indexToRemove >= 0 {
  279. conns.connections[indexToRemove] = conns.connections[len(conns.connections)-1]
  280. conns.connections[len(conns.connections)-1] = nil
  281. conns.connections = conns.connections[:len(conns.connections)-1]
  282. logger.Debug(c.GetProtocol(), c.GetID(), "connection removed, num open connections: %v",
  283. len(conns.connections))
  284. } else {
  285. logger.Warn(c.GetProtocol(), c.GetID(), "connection to remove not found!")
  286. }
  287. // we have finished to send data here and most of the time the underlying network connection
  288. // is already closed. Sometime a client can still be reading the last sended data, so we set
  289. // a deadline instead of directly closing the network connection.
  290. // Setting a deadline on an already closed connection has no effect.
  291. // We only need to ensure that a connection will not remain indefinitely open and so the
  292. // underlying file descriptor is not released.
  293. // This should protect us against buggy clients and edge cases.
  294. c.SetConnDeadline()
  295. }
  296. // Close closes an active connection.
  297. // It returns true on success
  298. func (conns *ActiveConnections) Close(connectionID string) bool {
  299. conns.RLock()
  300. result := false
  301. for _, c := range conns.connections {
  302. if c.GetID() == connectionID {
  303. defer func() {
  304. err := c.Disconnect()
  305. logger.Debug(c.GetProtocol(), c.GetID(), "close connection requested, close err: %v", err)
  306. }()
  307. result = true
  308. break
  309. }
  310. }
  311. conns.RUnlock()
  312. return result
  313. }
  314. func (conns *ActiveConnections) checkIdleConnections() {
  315. conns.RLock()
  316. for _, c := range conns.connections {
  317. idleTime := time.Since(c.GetLastActivity())
  318. if idleTime > Config.idleTimeoutAsDuration {
  319. defer func() {
  320. err := c.Disconnect()
  321. logger.Debug(c.GetProtocol(), c.GetID(), "close idle connection, idle time: %v, close err: %v", idleTime, err)
  322. }()
  323. }
  324. }
  325. conns.RUnlock()
  326. }
  327. // GetStats returns stats for active connections
  328. func (conns *ActiveConnections) GetStats() []ConnectionStatus {
  329. conns.RLock()
  330. defer conns.RUnlock()
  331. stats := make([]ConnectionStatus, 0, len(conns.connections))
  332. for _, c := range conns.connections {
  333. stat := ConnectionStatus{
  334. Username: c.GetUsername(),
  335. ConnectionID: c.GetID(),
  336. ClientVersion: c.GetClientVersion(),
  337. RemoteAddress: c.GetRemoteAddress(),
  338. ConnectionTime: utils.GetTimeAsMsSinceEpoch(c.GetConnectionTime()),
  339. LastActivity: utils.GetTimeAsMsSinceEpoch(c.GetLastActivity()),
  340. Protocol: c.GetProtocol(),
  341. SSHCommand: c.GetCommand(),
  342. Transfers: c.GetTransfers(),
  343. }
  344. stats = append(stats, stat)
  345. }
  346. return stats
  347. }
  348. // ConnectionStatus returns the status for an active connection
  349. type ConnectionStatus struct {
  350. // Logged in username
  351. Username string `json:"username"`
  352. // Unique identifier for the connection
  353. ConnectionID string `json:"connection_id"`
  354. // client's version string
  355. ClientVersion string `json:"client_version,omitempty"`
  356. // Remote address for this connection
  357. RemoteAddress string `json:"remote_address"`
  358. // Connection time as unix timestamp in milliseconds
  359. ConnectionTime int64 `json:"connection_time"`
  360. // Last activity as unix timestamp in milliseconds
  361. LastActivity int64 `json:"last_activity"`
  362. // Protocol for this connection: SFTP, SCP, SSH
  363. Protocol string `json:"protocol"`
  364. // active uploads/downloads
  365. Transfers []ConnectionTransfer `json:"active_transfers,omitempty"`
  366. // for the SSH protocol this is the issued command
  367. SSHCommand string `json:"ssh_command,omitempty"`
  368. }
  369. // GetConnectionDuration returns the connection duration as string
  370. func (c ConnectionStatus) GetConnectionDuration() string {
  371. elapsed := time.Since(utils.GetTimeFromMsecSinceEpoch(c.ConnectionTime))
  372. return utils.GetDurationAsString(elapsed)
  373. }
  374. // GetConnectionInfo returns connection info.
  375. // Protocol,Client Version and RemoteAddress are returned.
  376. // For SSH commands the issued command is returned too.
  377. func (c ConnectionStatus) GetConnectionInfo() string {
  378. result := fmt.Sprintf("%v. Client: %#v From: %#v", c.Protocol, c.ClientVersion, c.RemoteAddress)
  379. if c.Protocol == ProtocolSSH && len(c.SSHCommand) > 0 {
  380. result += fmt.Sprintf(". Command: %#v", c.SSHCommand)
  381. }
  382. return result
  383. }
  384. // GetTransfersAsString returns the active transfers as string
  385. func (c ConnectionStatus) GetTransfersAsString() string {
  386. result := ""
  387. for _, t := range c.Transfers {
  388. if len(result) > 0 {
  389. result += ". "
  390. }
  391. result += t.getConnectionTransferAsString()
  392. }
  393. return result
  394. }
  395. // ActiveQuotaScan defines an active quota scan for a user home dir
  396. type ActiveQuotaScan struct {
  397. // Username to which the quota scan refers
  398. Username string `json:"username"`
  399. // quota scan start time as unix timestamp in milliseconds
  400. StartTime int64 `json:"start_time"`
  401. }
  402. // ActiveVirtualFolderQuotaScan defines an active quota scan for a virtual folder
  403. type ActiveVirtualFolderQuotaScan struct {
  404. // folder path to which the quota scan refers
  405. MappedPath string `json:"mapped_path"`
  406. // quota scan start time as unix timestamp in milliseconds
  407. StartTime int64 `json:"start_time"`
  408. }
  409. // ActiveScans holds the active quota scans
  410. type ActiveScans struct {
  411. sync.RWMutex
  412. UserHomeScans []ActiveQuotaScan
  413. FolderScans []ActiveVirtualFolderQuotaScan
  414. }
  415. // GetUsersQuotaScans returns the active quota scans for users home directories
  416. func (s *ActiveScans) GetUsersQuotaScans() []ActiveQuotaScan {
  417. s.RLock()
  418. defer s.RUnlock()
  419. scans := make([]ActiveQuotaScan, len(s.UserHomeScans))
  420. copy(scans, s.UserHomeScans)
  421. return scans
  422. }
  423. // AddUserQuotaScan adds a user to the ones with active quota scans.
  424. // Returns false if the user has a quota scan already running
  425. func (s *ActiveScans) AddUserQuotaScan(username string) bool {
  426. s.Lock()
  427. defer s.Unlock()
  428. for _, scan := range s.UserHomeScans {
  429. if scan.Username == username {
  430. return false
  431. }
  432. }
  433. s.UserHomeScans = append(s.UserHomeScans, ActiveQuotaScan{
  434. Username: username,
  435. StartTime: utils.GetTimeAsMsSinceEpoch(time.Now()),
  436. })
  437. return true
  438. }
  439. // RemoveUserQuotaScan removes a user from the ones with active quota scans.
  440. // Returns false if the user has no active quota scans
  441. func (s *ActiveScans) RemoveUserQuotaScan(username string) bool {
  442. s.Lock()
  443. defer s.Unlock()
  444. indexToRemove := -1
  445. for i, scan := range s.UserHomeScans {
  446. if scan.Username == username {
  447. indexToRemove = i
  448. break
  449. }
  450. }
  451. if indexToRemove >= 0 {
  452. s.UserHomeScans[indexToRemove] = s.UserHomeScans[len(s.UserHomeScans)-1]
  453. s.UserHomeScans = s.UserHomeScans[:len(s.UserHomeScans)-1]
  454. return true
  455. }
  456. return false
  457. }
  458. // GetVFoldersQuotaScans returns the active quota scans for virtual folders
  459. func (s *ActiveScans) GetVFoldersQuotaScans() []ActiveVirtualFolderQuotaScan {
  460. s.RLock()
  461. defer s.RUnlock()
  462. scans := make([]ActiveVirtualFolderQuotaScan, len(s.FolderScans))
  463. copy(scans, s.FolderScans)
  464. return scans
  465. }
  466. // AddVFolderQuotaScan adds a virtual folder to the ones with active quota scans.
  467. // Returns false if the folder has a quota scan already running
  468. func (s *ActiveScans) AddVFolderQuotaScan(folderPath string) bool {
  469. s.Lock()
  470. defer s.Unlock()
  471. for _, scan := range s.FolderScans {
  472. if scan.MappedPath == folderPath {
  473. return false
  474. }
  475. }
  476. s.FolderScans = append(s.FolderScans, ActiveVirtualFolderQuotaScan{
  477. MappedPath: folderPath,
  478. StartTime: utils.GetTimeAsMsSinceEpoch(time.Now()),
  479. })
  480. return true
  481. }
  482. // RemoveVFolderQuotaScan removes a folder from the ones with active quota scans.
  483. // Returns false if the folder has no active quota scans
  484. func (s *ActiveScans) RemoveVFolderQuotaScan(folderPath string) bool {
  485. s.Lock()
  486. defer s.Unlock()
  487. indexToRemove := -1
  488. for i, scan := range s.FolderScans {
  489. if scan.MappedPath == folderPath {
  490. indexToRemove = i
  491. break
  492. }
  493. }
  494. if indexToRemove >= 0 {
  495. s.FolderScans[indexToRemove] = s.FolderScans[len(s.FolderScans)-1]
  496. s.FolderScans = s.FolderScans[:len(s.FolderScans)-1]
  497. return true
  498. }
  499. return false
  500. }