common.go 21 KB

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