common.go 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  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. "strconv"
  14. "strings"
  15. "sync"
  16. "sync/atomic"
  17. "time"
  18. "github.com/pires/go-proxyproto"
  19. "github.com/drakkan/sftpgo/v2/command"
  20. "github.com/drakkan/sftpgo/v2/dataprovider"
  21. "github.com/drakkan/sftpgo/v2/httpclient"
  22. "github.com/drakkan/sftpgo/v2/logger"
  23. "github.com/drakkan/sftpgo/v2/metric"
  24. "github.com/drakkan/sftpgo/v2/plugin"
  25. "github.com/drakkan/sftpgo/v2/util"
  26. "github.com/drakkan/sftpgo/v2/vfs"
  27. )
  28. // constants
  29. const (
  30. logSender = "common"
  31. uploadLogSender = "Upload"
  32. downloadLogSender = "Download"
  33. renameLogSender = "Rename"
  34. rmdirLogSender = "Rmdir"
  35. mkdirLogSender = "Mkdir"
  36. symlinkLogSender = "Symlink"
  37. removeLogSender = "Remove"
  38. chownLogSender = "Chown"
  39. chmodLogSender = "Chmod"
  40. chtimesLogSender = "Chtimes"
  41. truncateLogSender = "Truncate"
  42. operationDownload = "download"
  43. operationUpload = "upload"
  44. operationDelete = "delete"
  45. // Pre-download action name
  46. OperationPreDownload = "pre-download"
  47. // Pre-upload action name
  48. OperationPreUpload = "pre-upload"
  49. operationPreDelete = "pre-delete"
  50. operationRename = "rename"
  51. operationMkdir = "mkdir"
  52. operationRmdir = "rmdir"
  53. // SSH command action name
  54. OperationSSHCmd = "ssh_cmd"
  55. chtimesFormat = "2006-01-02T15:04:05" // YYYY-MM-DDTHH:MM:SS
  56. idleTimeoutCheckInterval = 3 * time.Minute
  57. periodicTimeoutCheckInterval = 1 * time.Minute
  58. )
  59. // Stat flags
  60. const (
  61. StatAttrUIDGID = 1
  62. StatAttrPerms = 2
  63. StatAttrTimes = 4
  64. StatAttrSize = 8
  65. )
  66. // Transfer types
  67. const (
  68. TransferUpload = iota
  69. TransferDownload
  70. )
  71. // Supported protocols
  72. const (
  73. ProtocolSFTP = "SFTP"
  74. ProtocolSCP = "SCP"
  75. ProtocolSSH = "SSH"
  76. ProtocolFTP = "FTP"
  77. ProtocolWebDAV = "DAV"
  78. ProtocolHTTP = "HTTP"
  79. ProtocolHTTPShare = "HTTPShare"
  80. ProtocolDataRetention = "DataRetention"
  81. ProtocolOIDC = "OIDC"
  82. )
  83. // Upload modes
  84. const (
  85. UploadModeStandard = iota
  86. UploadModeAtomic
  87. UploadModeAtomicWithResume
  88. )
  89. func init() {
  90. Connections.clients = clientsMap{
  91. clients: make(map[string]int),
  92. }
  93. Connections.perUserConns = make(map[string]int)
  94. }
  95. // errors definitions
  96. var (
  97. ErrPermissionDenied = errors.New("permission denied")
  98. ErrNotExist = errors.New("no such file or directory")
  99. ErrOpUnsupported = errors.New("operation unsupported")
  100. ErrGenericFailure = errors.New("failure")
  101. ErrQuotaExceeded = errors.New("denying write due to space limit")
  102. ErrReadQuotaExceeded = errors.New("denying read due to quota limit")
  103. ErrSkipPermissionsCheck = errors.New("permission check skipped")
  104. ErrConnectionDenied = errors.New("you are not allowed to connect")
  105. ErrNoBinding = errors.New("no binding configured")
  106. ErrCrtRevoked = errors.New("your certificate has been revoked")
  107. ErrNoCredentials = errors.New("no credential provided")
  108. ErrInternalFailure = errors.New("internal failure")
  109. ErrTransferAborted = errors.New("transfer aborted")
  110. errNoTransfer = errors.New("requested transfer not found")
  111. errTransferMismatch = errors.New("transfer mismatch")
  112. )
  113. var (
  114. // Config is the configuration for the supported protocols
  115. Config Configuration
  116. // Connections is the list of active connections
  117. Connections ActiveConnections
  118. // QuotaScans is the list of active quota scans
  119. QuotaScans ActiveScans
  120. transfersChecker TransfersChecker
  121. periodicTimeoutTicker *time.Ticker
  122. periodicTimeoutTickerDone chan bool
  123. supportedProtocols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH, ProtocolFTP, ProtocolWebDAV,
  124. ProtocolHTTP, ProtocolHTTPShare, ProtocolOIDC}
  125. disconnHookProtocols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH, ProtocolFTP}
  126. // the map key is the protocol, for each protocol we can have multiple rate limiters
  127. rateLimiters map[string][]*rateLimiter
  128. )
  129. // Initialize sets the common configuration
  130. func Initialize(c Configuration, isShared int) error {
  131. Config = c
  132. Config.Actions.ExecuteOn = util.RemoveDuplicates(Config.Actions.ExecuteOn, true)
  133. Config.Actions.ExecuteSync = util.RemoveDuplicates(Config.Actions.ExecuteSync, true)
  134. Config.ProxyAllowed = util.RemoveDuplicates(Config.ProxyAllowed, true)
  135. Config.idleLoginTimeout = 2 * time.Minute
  136. Config.idleTimeoutAsDuration = time.Duration(Config.IdleTimeout) * time.Minute
  137. startPeriodicTimeoutTicker(periodicTimeoutCheckInterval)
  138. Config.defender = nil
  139. Config.whitelist = nil
  140. rateLimiters = make(map[string][]*rateLimiter)
  141. for _, rlCfg := range c.RateLimitersConfig {
  142. if rlCfg.isEnabled() {
  143. if err := rlCfg.validate(); err != nil {
  144. return fmt.Errorf("rate limiters initialization error: %w", err)
  145. }
  146. allowList, err := util.ParseAllowedIPAndRanges(rlCfg.AllowList)
  147. if err != nil {
  148. return fmt.Errorf("unable to parse rate limiter allow list %v: %v", rlCfg.AllowList, err)
  149. }
  150. rateLimiter := rlCfg.getLimiter()
  151. rateLimiter.allowList = allowList
  152. for _, protocol := range rlCfg.Protocols {
  153. rateLimiters[protocol] = append(rateLimiters[protocol], rateLimiter)
  154. }
  155. }
  156. }
  157. if c.DefenderConfig.Enabled {
  158. if !util.Contains(supportedDefenderDrivers, c.DefenderConfig.Driver) {
  159. return fmt.Errorf("unsupported defender driver %#v", c.DefenderConfig.Driver)
  160. }
  161. var defender Defender
  162. var err error
  163. switch c.DefenderConfig.Driver {
  164. case DefenderDriverProvider:
  165. defender, err = newDBDefender(&c.DefenderConfig)
  166. default:
  167. defender, err = newInMemoryDefender(&c.DefenderConfig)
  168. }
  169. if err != nil {
  170. return fmt.Errorf("defender initialization error: %v", err)
  171. }
  172. logger.Info(logSender, "", "defender initialized with config %+v", c.DefenderConfig)
  173. Config.defender = defender
  174. }
  175. if c.WhiteListFile != "" {
  176. whitelist := &whitelist{
  177. fileName: c.WhiteListFile,
  178. }
  179. if err := whitelist.reload(); err != nil {
  180. return fmt.Errorf("whitelist initialization error: %w", err)
  181. }
  182. logger.Info(logSender, "", "whitelist initialized from file: %#v", c.WhiteListFile)
  183. Config.whitelist = whitelist
  184. }
  185. vfs.SetTempPath(c.TempPath)
  186. dataprovider.SetTempPath(c.TempPath)
  187. transfersChecker = getTransfersChecker(isShared)
  188. return nil
  189. }
  190. // LimitRate blocks until all the configured rate limiters
  191. // allow one event to happen.
  192. // It returns an error if the time to wait exceeds the max
  193. // allowed delay
  194. func LimitRate(protocol, ip string) (time.Duration, error) {
  195. for _, limiter := range rateLimiters[protocol] {
  196. if delay, err := limiter.Wait(ip); err != nil {
  197. logger.Debug(logSender, "", "protocol %v ip %v: %v", protocol, ip, err)
  198. return delay, err
  199. }
  200. }
  201. return 0, nil
  202. }
  203. // Reload reloads the whitelist, the IP filter plugin and the defender's block and safe lists
  204. func Reload() error {
  205. plugin.Handler.ReloadFilter()
  206. var errWithelist error
  207. if Config.whitelist != nil {
  208. errWithelist = Config.whitelist.reload()
  209. }
  210. if Config.defender == nil {
  211. return errWithelist
  212. }
  213. if err := Config.defender.Reload(); err != nil {
  214. return err
  215. }
  216. return errWithelist
  217. }
  218. // IsBanned returns true if the specified IP address is banned
  219. func IsBanned(ip string) bool {
  220. if plugin.Handler.IsIPBanned(ip) {
  221. return true
  222. }
  223. if Config.defender == nil {
  224. return false
  225. }
  226. return Config.defender.IsBanned(ip)
  227. }
  228. // GetDefenderBanTime returns the ban time for the given IP
  229. // or nil if the IP is not banned or the defender is disabled
  230. func GetDefenderBanTime(ip string) (*time.Time, error) {
  231. if Config.defender == nil {
  232. return nil, nil
  233. }
  234. return Config.defender.GetBanTime(ip)
  235. }
  236. // GetDefenderHosts returns hosts that are banned or for which some violations have been detected
  237. func GetDefenderHosts() ([]dataprovider.DefenderEntry, error) {
  238. if Config.defender == nil {
  239. return nil, nil
  240. }
  241. return Config.defender.GetHosts()
  242. }
  243. // GetDefenderHost returns a defender host by ip, if any
  244. func GetDefenderHost(ip string) (dataprovider.DefenderEntry, error) {
  245. if Config.defender == nil {
  246. return dataprovider.DefenderEntry{}, errors.New("defender is disabled")
  247. }
  248. return Config.defender.GetHost(ip)
  249. }
  250. // DeleteDefenderHost removes the specified IP address from the defender lists
  251. func DeleteDefenderHost(ip string) bool {
  252. if Config.defender == nil {
  253. return false
  254. }
  255. return Config.defender.DeleteHost(ip)
  256. }
  257. // GetDefenderScore returns the score for the given IP
  258. func GetDefenderScore(ip string) (int, error) {
  259. if Config.defender == nil {
  260. return 0, nil
  261. }
  262. return Config.defender.GetScore(ip)
  263. }
  264. // AddDefenderEvent adds the specified defender event for the given IP
  265. func AddDefenderEvent(ip string, event HostEvent) {
  266. if Config.defender == nil {
  267. return
  268. }
  269. Config.defender.AddEvent(ip, event)
  270. }
  271. // the ticker cannot be started/stopped from multiple goroutines
  272. func startPeriodicTimeoutTicker(duration time.Duration) {
  273. stopPeriodicTimeoutTicker()
  274. periodicTimeoutTicker = time.NewTicker(duration)
  275. periodicTimeoutTickerDone = make(chan bool)
  276. go func() {
  277. counter := int64(0)
  278. ratio := idleTimeoutCheckInterval / periodicTimeoutCheckInterval
  279. for {
  280. select {
  281. case <-periodicTimeoutTickerDone:
  282. return
  283. case <-periodicTimeoutTicker.C:
  284. counter++
  285. if Config.IdleTimeout > 0 && counter >= int64(ratio) {
  286. counter = 0
  287. Connections.checkIdles()
  288. }
  289. go Connections.checkTransfers()
  290. }
  291. }
  292. }()
  293. }
  294. func stopPeriodicTimeoutTicker() {
  295. if periodicTimeoutTicker != nil {
  296. periodicTimeoutTicker.Stop()
  297. periodicTimeoutTickerDone <- true
  298. periodicTimeoutTicker = nil
  299. }
  300. }
  301. // ActiveTransfer defines the interface for the current active transfers
  302. type ActiveTransfer interface {
  303. GetID() int64
  304. GetType() int
  305. GetSize() int64
  306. GetDownloadedSize() int64
  307. GetUploadedSize() int64
  308. GetVirtualPath() string
  309. GetStartTime() time.Time
  310. SignalClose(err error)
  311. Truncate(fsPath string, size int64) (int64, error)
  312. GetRealFsPath(fsPath string) string
  313. SetTimes(fsPath string, atime time.Time, mtime time.Time) bool
  314. GetTruncatedSize() int64
  315. HasSizeLimit() bool
  316. }
  317. // ActiveConnection defines the interface for the current active connections
  318. type ActiveConnection interface {
  319. GetID() string
  320. GetUsername() string
  321. GetMaxSessions() int
  322. GetLocalAddress() string
  323. GetRemoteAddress() string
  324. GetClientVersion() string
  325. GetProtocol() string
  326. GetConnectionTime() time.Time
  327. GetLastActivity() time.Time
  328. GetCommand() string
  329. Disconnect() error
  330. AddTransfer(t ActiveTransfer)
  331. RemoveTransfer(t ActiveTransfer)
  332. GetTransfers() []ConnectionTransfer
  333. SignalTransferClose(transferID int64, err error)
  334. CloseFS() error
  335. }
  336. // StatAttributes defines the attributes for set stat commands
  337. type StatAttributes struct {
  338. Mode os.FileMode
  339. Atime time.Time
  340. Mtime time.Time
  341. UID int
  342. GID int
  343. Flags int
  344. Size int64
  345. }
  346. // ConnectionTransfer defines the trasfer details to expose
  347. type ConnectionTransfer struct {
  348. ID int64 `json:"-"`
  349. OperationType string `json:"operation_type"`
  350. StartTime int64 `json:"start_time"`
  351. Size int64 `json:"size"`
  352. VirtualPath string `json:"path"`
  353. HasSizeLimit bool `json:"-"`
  354. ULSize int64 `json:"-"`
  355. DLSize int64 `json:"-"`
  356. }
  357. func (t *ConnectionTransfer) getConnectionTransferAsString() string {
  358. result := ""
  359. switch t.OperationType {
  360. case operationUpload:
  361. result += "UL "
  362. case operationDownload:
  363. result += "DL "
  364. }
  365. result += fmt.Sprintf("%#v ", t.VirtualPath)
  366. if t.Size > 0 {
  367. elapsed := time.Since(util.GetTimeFromMsecSinceEpoch(t.StartTime))
  368. speed := float64(t.Size) / float64(util.GetTimeAsMsSinceEpoch(time.Now())-t.StartTime)
  369. result += fmt.Sprintf("Size: %#v Elapsed: %#v Speed: \"%.1f KB/s\"", util.ByteCountIEC(t.Size),
  370. util.GetDurationAsString(elapsed), speed)
  371. }
  372. return result
  373. }
  374. type whitelist struct {
  375. fileName string
  376. sync.RWMutex
  377. list HostList
  378. }
  379. func (l *whitelist) reload() error {
  380. list, err := loadHostListFromFile(l.fileName)
  381. if err != nil {
  382. return err
  383. }
  384. if list == nil {
  385. return errors.New("cannot accept a nil whitelist")
  386. }
  387. l.Lock()
  388. defer l.Unlock()
  389. l.list = *list
  390. return nil
  391. }
  392. func (l *whitelist) isAllowed(ip string) bool {
  393. l.RLock()
  394. defer l.RUnlock()
  395. return l.list.isListed(ip)
  396. }
  397. // Configuration defines configuration parameters common to all supported protocols
  398. type Configuration struct {
  399. // Maximum idle timeout as minutes. If a client is idle for a time that exceeds this setting it will be disconnected.
  400. // 0 means disabled
  401. IdleTimeout int `json:"idle_timeout" mapstructure:"idle_timeout"`
  402. // UploadMode 0 means standard, the files are uploaded directly to the requested path.
  403. // 1 means atomic: the files are uploaded to a temporary path and renamed to the requested path
  404. // when the client ends the upload. Atomic mode avoid problems such as a web server that
  405. // serves partial files when the files are being uploaded.
  406. // In atomic mode if there is an upload error the temporary file is deleted and so the requested
  407. // upload path will not contain a partial file.
  408. // 2 means atomic with resume support: as atomic but if there is an upload error the temporary
  409. // file is renamed to the requested path and not deleted, this way a client can reconnect and resume
  410. // the upload.
  411. UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
  412. // Actions to execute for SFTP file operations and SSH commands
  413. Actions ProtocolActions `json:"actions" mapstructure:"actions"`
  414. // SetstatMode 0 means "normal mode": requests for changing permissions and owner/group are executed.
  415. // 1 means "ignore mode": requests for changing permissions and owner/group are silently ignored.
  416. // 2 means "ignore mode for cloud fs": requests for changing permissions and owner/group are
  417. // silently ignored for cloud based filesystem such as S3, GCS, Azure Blob. Requests for changing
  418. // modification times are ignored for cloud based filesystem if they are not supported.
  419. SetstatMode int `json:"setstat_mode" mapstructure:"setstat_mode"`
  420. // TempPath defines the path for temporary files such as those used for atomic uploads or file pipes.
  421. // If you set this option you must make sure that the defined path exists, is accessible for writing
  422. // by the user running SFTPGo, and is on the same filesystem as the users home directories otherwise
  423. // the renaming for atomic uploads will become a copy and therefore may take a long time.
  424. // The temporary files are not namespaced. The default is generally fine. Leave empty for the default.
  425. TempPath string `json:"temp_path" mapstructure:"temp_path"`
  426. // Support for HAProxy PROXY protocol.
  427. // If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable
  428. // the proxy protocol. It provides a convenient way to safely transport connection information
  429. // such as a client's address across multiple layers of NAT or TCP proxies to get the real
  430. // client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported.
  431. // - 0 means disabled
  432. // - 1 means proxy protocol enabled. Proxy header will be used and requests without proxy header will be accepted.
  433. // - 2 means proxy protocol required. Proxy header will be used and requests without proxy header will be rejected.
  434. // If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too,
  435. // for example for HAProxy add "send-proxy" or "send-proxy-v2" to each server configuration line.
  436. ProxyProtocol int `json:"proxy_protocol" mapstructure:"proxy_protocol"`
  437. // List of IP addresses and IP ranges allowed to send the proxy header.
  438. // If proxy protocol is set to 1 and we receive a proxy header from an IP that is not in the list then the
  439. // connection will be accepted and the header will be ignored.
  440. // If proxy protocol is set to 2 and we receive a proxy header from an IP that is not in the list then the
  441. // connection will be rejected.
  442. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  443. // Absolute path to an external program or an HTTP URL to invoke as soon as SFTPGo starts.
  444. // If you define an HTTP URL it will be invoked using a `GET` request.
  445. // Please note that SFTPGo services may not yet be available when this hook is run.
  446. // Leave empty do disable.
  447. StartupHook string `json:"startup_hook" mapstructure:"startup_hook"`
  448. // Absolute path to an external program or an HTTP URL to invoke after a user connects
  449. // and before he tries to login. It allows you to reject the connection based on the source
  450. // ip address. Leave empty do disable.
  451. PostConnectHook string `json:"post_connect_hook" mapstructure:"post_connect_hook"`
  452. // Absolute path to an external program or an HTTP URL to invoke after an SSH/FTP connection ends.
  453. // Leave empty do disable.
  454. PostDisconnectHook string `json:"post_disconnect_hook" mapstructure:"post_disconnect_hook"`
  455. // Absolute path to an external program or an HTTP URL to invoke after a data retention check completes.
  456. // Leave empty do disable.
  457. DataRetentionHook string `json:"data_retention_hook" mapstructure:"data_retention_hook"`
  458. // Maximum number of concurrent client connections. 0 means unlimited
  459. MaxTotalConnections int `json:"max_total_connections" mapstructure:"max_total_connections"`
  460. // Maximum number of concurrent client connections from the same host (IP). 0 means unlimited
  461. MaxPerHostConnections int `json:"max_per_host_connections" mapstructure:"max_per_host_connections"`
  462. // Path to a file containing a list of IP addresses and/or networks to allow.
  463. // Only the listed IPs/networks can access the configured services, all other client connections
  464. // will be dropped before they even try to authenticate.
  465. WhiteListFile string `json:"whitelist_file" mapstructure:"whitelist_file"`
  466. // Defender configuration
  467. DefenderConfig DefenderConfig `json:"defender" mapstructure:"defender"`
  468. // Rate limiter configurations
  469. RateLimitersConfig []RateLimiterConfig `json:"rate_limiters" mapstructure:"rate_limiters"`
  470. idleTimeoutAsDuration time.Duration
  471. idleLoginTimeout time.Duration
  472. defender Defender
  473. whitelist *whitelist
  474. }
  475. // IsAtomicUploadEnabled returns true if atomic upload is enabled
  476. func (c *Configuration) IsAtomicUploadEnabled() bool {
  477. return c.UploadMode == UploadModeAtomic || c.UploadMode == UploadModeAtomicWithResume
  478. }
  479. // GetProxyListener returns a wrapper for the given listener that supports the
  480. // HAProxy Proxy Protocol
  481. func (c *Configuration) GetProxyListener(listener net.Listener) (*proxyproto.Listener, error) {
  482. var err error
  483. if c.ProxyProtocol > 0 {
  484. var policyFunc func(upstream net.Addr) (proxyproto.Policy, error)
  485. if c.ProxyProtocol == 1 && len(c.ProxyAllowed) > 0 {
  486. policyFunc, err = proxyproto.LaxWhiteListPolicy(c.ProxyAllowed)
  487. if err != nil {
  488. return nil, err
  489. }
  490. }
  491. if c.ProxyProtocol == 2 {
  492. if len(c.ProxyAllowed) == 0 {
  493. policyFunc = func(upstream net.Addr) (proxyproto.Policy, error) {
  494. return proxyproto.REQUIRE, nil
  495. }
  496. } else {
  497. policyFunc, err = proxyproto.StrictWhiteListPolicy(c.ProxyAllowed)
  498. if err != nil {
  499. return nil, err
  500. }
  501. }
  502. }
  503. return &proxyproto.Listener{
  504. Listener: listener,
  505. Policy: policyFunc,
  506. ReadHeaderTimeout: 10 * time.Second,
  507. }, nil
  508. }
  509. return nil, errors.New("proxy protocol not configured")
  510. }
  511. // ExecuteStartupHook runs the startup hook if defined
  512. func (c *Configuration) ExecuteStartupHook() error {
  513. if c.StartupHook == "" {
  514. return nil
  515. }
  516. if strings.HasPrefix(c.StartupHook, "http") {
  517. var url *url.URL
  518. url, err := url.Parse(c.StartupHook)
  519. if err != nil {
  520. logger.Warn(logSender, "", "Invalid startup hook %#v: %v", c.StartupHook, err)
  521. return err
  522. }
  523. startTime := time.Now()
  524. resp, err := httpclient.RetryableGet(url.String())
  525. if err != nil {
  526. logger.Warn(logSender, "", "Error executing startup hook: %v", err)
  527. return err
  528. }
  529. defer resp.Body.Close()
  530. logger.Debug(logSender, "", "Startup hook executed, elapsed: %v, response code: %v", time.Since(startTime), resp.StatusCode)
  531. return nil
  532. }
  533. if !filepath.IsAbs(c.StartupHook) {
  534. err := fmt.Errorf("invalid startup hook %#v", c.StartupHook)
  535. logger.Warn(logSender, "", "Invalid startup hook %#v", c.StartupHook)
  536. return err
  537. }
  538. startTime := time.Now()
  539. timeout, env := command.GetConfig(c.StartupHook)
  540. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  541. defer cancel()
  542. cmd := exec.CommandContext(ctx, c.StartupHook)
  543. cmd.Env = env
  544. err := cmd.Run()
  545. logger.Debug(logSender, "", "Startup hook executed, elapsed: %v, error: %v", time.Since(startTime), err)
  546. return nil
  547. }
  548. func (c *Configuration) executePostDisconnectHook(remoteAddr, protocol, username, connID string, connectionTime time.Time) {
  549. ipAddr := util.GetIPFromRemoteAddress(remoteAddr)
  550. connDuration := int64(time.Since(connectionTime) / time.Millisecond)
  551. if strings.HasPrefix(c.PostDisconnectHook, "http") {
  552. var url *url.URL
  553. url, err := url.Parse(c.PostDisconnectHook)
  554. if err != nil {
  555. logger.Warn(protocol, connID, "Invalid post disconnect hook %#v: %v", c.PostDisconnectHook, err)
  556. return
  557. }
  558. q := url.Query()
  559. q.Add("ip", ipAddr)
  560. q.Add("protocol", protocol)
  561. q.Add("username", username)
  562. q.Add("connection_duration", strconv.FormatInt(connDuration, 10))
  563. url.RawQuery = q.Encode()
  564. startTime := time.Now()
  565. resp, err := httpclient.RetryableGet(url.String())
  566. respCode := 0
  567. if err == nil {
  568. respCode = resp.StatusCode
  569. resp.Body.Close()
  570. }
  571. logger.Debug(protocol, connID, "Post disconnect hook response code: %v, elapsed: %v, err: %v",
  572. respCode, time.Since(startTime), err)
  573. return
  574. }
  575. if !filepath.IsAbs(c.PostDisconnectHook) {
  576. logger.Debug(protocol, connID, "invalid post disconnect hook %#v", c.PostDisconnectHook)
  577. return
  578. }
  579. timeout, env := command.GetConfig(c.PostDisconnectHook)
  580. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  581. defer cancel()
  582. startTime := time.Now()
  583. cmd := exec.CommandContext(ctx, c.PostDisconnectHook)
  584. cmd.Env = append(env,
  585. fmt.Sprintf("SFTPGO_CONNECTION_IP=%v", ipAddr),
  586. fmt.Sprintf("SFTPGO_CONNECTION_USERNAME=%v", username),
  587. fmt.Sprintf("SFTPGO_CONNECTION_DURATION=%v", connDuration),
  588. fmt.Sprintf("SFTPGO_CONNECTION_PROTOCOL=%v", protocol))
  589. err := cmd.Run()
  590. logger.Debug(protocol, connID, "Post disconnect hook executed, elapsed: %v error: %v", time.Since(startTime), err)
  591. }
  592. func (c *Configuration) checkPostDisconnectHook(remoteAddr, protocol, username, connID string, connectionTime time.Time) {
  593. if c.PostDisconnectHook == "" {
  594. return
  595. }
  596. if !util.Contains(disconnHookProtocols, protocol) {
  597. return
  598. }
  599. go c.executePostDisconnectHook(remoteAddr, protocol, username, connID, connectionTime)
  600. }
  601. // ExecutePostConnectHook executes the post connect hook if defined
  602. func (c *Configuration) ExecutePostConnectHook(ipAddr, protocol string) error {
  603. if c.PostConnectHook == "" {
  604. return nil
  605. }
  606. if strings.HasPrefix(c.PostConnectHook, "http") {
  607. var url *url.URL
  608. url, err := url.Parse(c.PostConnectHook)
  609. if err != nil {
  610. logger.Warn(protocol, "", "Login from ip %#v denied, invalid post connect hook %#v: %v",
  611. ipAddr, c.PostConnectHook, err)
  612. return err
  613. }
  614. q := url.Query()
  615. q.Add("ip", ipAddr)
  616. q.Add("protocol", protocol)
  617. url.RawQuery = q.Encode()
  618. resp, err := httpclient.RetryableGet(url.String())
  619. if err != nil {
  620. logger.Warn(protocol, "", "Login from ip %#v denied, error executing post connect hook: %v", ipAddr, err)
  621. return err
  622. }
  623. defer resp.Body.Close()
  624. if resp.StatusCode != http.StatusOK {
  625. logger.Warn(protocol, "", "Login from ip %#v denied, post connect hook response code: %v", ipAddr, resp.StatusCode)
  626. return errUnexpectedHTTResponse
  627. }
  628. return nil
  629. }
  630. if !filepath.IsAbs(c.PostConnectHook) {
  631. err := fmt.Errorf("invalid post connect hook %#v", c.PostConnectHook)
  632. logger.Warn(protocol, "", "Login from ip %#v denied: %v", ipAddr, err)
  633. return err
  634. }
  635. timeout, env := command.GetConfig(c.PostConnectHook)
  636. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  637. defer cancel()
  638. cmd := exec.CommandContext(ctx, c.PostConnectHook)
  639. cmd.Env = append(env,
  640. fmt.Sprintf("SFTPGO_CONNECTION_IP=%v", ipAddr),
  641. fmt.Sprintf("SFTPGO_CONNECTION_PROTOCOL=%v", protocol))
  642. err := cmd.Run()
  643. if err != nil {
  644. logger.Warn(protocol, "", "Login from ip %#v denied, connect hook error: %v", ipAddr, err)
  645. }
  646. return err
  647. }
  648. // SSHConnection defines an ssh connection.
  649. // Each SSH connection can open several channels for SFTP or SSH commands
  650. type SSHConnection struct {
  651. id string
  652. conn net.Conn
  653. lastActivity int64
  654. }
  655. // NewSSHConnection returns a new SSHConnection
  656. func NewSSHConnection(id string, conn net.Conn) *SSHConnection {
  657. return &SSHConnection{
  658. id: id,
  659. conn: conn,
  660. lastActivity: time.Now().UnixNano(),
  661. }
  662. }
  663. // GetID returns the ID for this SSHConnection
  664. func (c *SSHConnection) GetID() string {
  665. return c.id
  666. }
  667. // UpdateLastActivity updates last activity for this connection
  668. func (c *SSHConnection) UpdateLastActivity() {
  669. atomic.StoreInt64(&c.lastActivity, time.Now().UnixNano())
  670. }
  671. // GetLastActivity returns the last connection activity
  672. func (c *SSHConnection) GetLastActivity() time.Time {
  673. return time.Unix(0, atomic.LoadInt64(&c.lastActivity))
  674. }
  675. // Close closes the underlying network connection
  676. func (c *SSHConnection) Close() error {
  677. return c.conn.Close()
  678. }
  679. // ActiveConnections holds the currect active connections with the associated transfers
  680. type ActiveConnections struct {
  681. // clients contains both authenticated and estabilished connections and the ones waiting
  682. // for authentication
  683. clients clientsMap
  684. transfersCheckStatus int32
  685. sync.RWMutex
  686. connections []ActiveConnection
  687. sshConnections []*SSHConnection
  688. perUserConns map[string]int
  689. }
  690. // internal method, must be called within a locked block
  691. func (conns *ActiveConnections) addUserConnection(username string) {
  692. if username == "" {
  693. return
  694. }
  695. conns.perUserConns[username]++
  696. }
  697. // internal method, must be called within a locked block
  698. func (conns *ActiveConnections) removeUserConnection(username string) {
  699. if username == "" {
  700. return
  701. }
  702. if val, ok := conns.perUserConns[username]; ok {
  703. conns.perUserConns[username]--
  704. if val > 1 {
  705. return
  706. }
  707. delete(conns.perUserConns, username)
  708. }
  709. }
  710. // GetActiveSessions returns the number of active sessions for the given username.
  711. // We return the open sessions for any protocol
  712. func (conns *ActiveConnections) GetActiveSessions(username string) int {
  713. conns.RLock()
  714. defer conns.RUnlock()
  715. return conns.perUserConns[username]
  716. }
  717. // Add adds a new connection to the active ones
  718. func (conns *ActiveConnections) Add(c ActiveConnection) error {
  719. conns.Lock()
  720. defer conns.Unlock()
  721. if username := c.GetUsername(); username != "" {
  722. if maxSessions := c.GetMaxSessions(); maxSessions > 0 {
  723. if val := conns.perUserConns[username]; val >= maxSessions {
  724. return fmt.Errorf("too many open sessions: %d/%d", val, maxSessions)
  725. }
  726. }
  727. conns.addUserConnection(username)
  728. }
  729. conns.connections = append(conns.connections, c)
  730. metric.UpdateActiveConnectionsSize(len(conns.connections))
  731. logger.Debug(c.GetProtocol(), c.GetID(), "connection added, local address %#v, remote address %#v, num open connections: %v",
  732. c.GetLocalAddress(), c.GetRemoteAddress(), len(conns.connections))
  733. return nil
  734. }
  735. // Swap replaces an existing connection with the given one.
  736. // This method is useful if you have to change some connection details
  737. // for example for FTP is used to update the connection once the user
  738. // authenticates
  739. func (conns *ActiveConnections) Swap(c ActiveConnection) error {
  740. conns.Lock()
  741. defer conns.Unlock()
  742. for idx, conn := range conns.connections {
  743. if conn.GetID() == c.GetID() {
  744. conns.removeUserConnection(conn.GetUsername())
  745. if username := c.GetUsername(); username != "" {
  746. if maxSessions := c.GetMaxSessions(); maxSessions > 0 {
  747. if val := conns.perUserConns[username]; val >= maxSessions {
  748. conns.addUserConnection(conn.GetUsername())
  749. return fmt.Errorf("too many open sessions: %d/%d", val, maxSessions)
  750. }
  751. }
  752. conns.addUserConnection(username)
  753. }
  754. err := conn.CloseFS()
  755. conns.connections[idx] = c
  756. logger.Debug(logSender, c.GetID(), "connection swapped, close fs error: %v", err)
  757. conn = nil
  758. return nil
  759. }
  760. }
  761. return errors.New("connection to swap not found")
  762. }
  763. // Remove removes a connection from the active ones
  764. func (conns *ActiveConnections) Remove(connectionID string) {
  765. conns.Lock()
  766. defer conns.Unlock()
  767. for idx, conn := range conns.connections {
  768. if conn.GetID() == connectionID {
  769. err := conn.CloseFS()
  770. lastIdx := len(conns.connections) - 1
  771. conns.connections[idx] = conns.connections[lastIdx]
  772. conns.connections[lastIdx] = nil
  773. conns.connections = conns.connections[:lastIdx]
  774. conns.removeUserConnection(conn.GetUsername())
  775. metric.UpdateActiveConnectionsSize(lastIdx)
  776. logger.Debug(conn.GetProtocol(), conn.GetID(), "connection removed, local address %#v, remote address %#v close fs error: %v, num open connections: %v",
  777. conn.GetLocalAddress(), conn.GetRemoteAddress(), err, lastIdx)
  778. Config.checkPostDisconnectHook(conn.GetRemoteAddress(), conn.GetProtocol(), conn.GetUsername(),
  779. conn.GetID(), conn.GetConnectionTime())
  780. return
  781. }
  782. }
  783. logger.Warn(logSender, "", "connection id %#v to remove not found!", connectionID)
  784. }
  785. // Close closes an active connection.
  786. // It returns true on success
  787. func (conns *ActiveConnections) Close(connectionID string) bool {
  788. conns.RLock()
  789. result := false
  790. for _, c := range conns.connections {
  791. if c.GetID() == connectionID {
  792. defer func(conn ActiveConnection) {
  793. err := conn.Disconnect()
  794. logger.Debug(conn.GetProtocol(), conn.GetID(), "close connection requested, close err: %v", err)
  795. }(c)
  796. result = true
  797. break
  798. }
  799. }
  800. conns.RUnlock()
  801. return result
  802. }
  803. // AddSSHConnection adds a new ssh connection to the active ones
  804. func (conns *ActiveConnections) AddSSHConnection(c *SSHConnection) {
  805. conns.Lock()
  806. defer conns.Unlock()
  807. conns.sshConnections = append(conns.sshConnections, c)
  808. logger.Debug(logSender, c.GetID(), "ssh connection added, num open connections: %v", len(conns.sshConnections))
  809. }
  810. // RemoveSSHConnection removes a connection from the active ones
  811. func (conns *ActiveConnections) RemoveSSHConnection(connectionID string) {
  812. conns.Lock()
  813. defer conns.Unlock()
  814. for idx, conn := range conns.sshConnections {
  815. if conn.GetID() == connectionID {
  816. lastIdx := len(conns.sshConnections) - 1
  817. conns.sshConnections[idx] = conns.sshConnections[lastIdx]
  818. conns.sshConnections[lastIdx] = nil
  819. conns.sshConnections = conns.sshConnections[:lastIdx]
  820. logger.Debug(logSender, conn.GetID(), "ssh connection removed, num open ssh connections: %v", lastIdx)
  821. return
  822. }
  823. }
  824. logger.Warn(logSender, "", "ssh connection to remove with id %#v not found!", connectionID)
  825. }
  826. func (conns *ActiveConnections) checkIdles() {
  827. conns.RLock()
  828. for _, sshConn := range conns.sshConnections {
  829. idleTime := time.Since(sshConn.GetLastActivity())
  830. if idleTime > Config.idleTimeoutAsDuration {
  831. // we close an SSH connection if it has no active connections associated
  832. idToMatch := fmt.Sprintf("_%s_", sshConn.GetID())
  833. toClose := true
  834. for _, conn := range conns.connections {
  835. if strings.Contains(conn.GetID(), idToMatch) {
  836. if time.Since(conn.GetLastActivity()) <= Config.idleTimeoutAsDuration {
  837. toClose = false
  838. break
  839. }
  840. }
  841. }
  842. if toClose {
  843. defer func(c *SSHConnection) {
  844. err := c.Close()
  845. logger.Debug(logSender, c.GetID(), "close idle SSH connection, idle time: %v, close err: %v",
  846. time.Since(c.GetLastActivity()), err)
  847. }(sshConn)
  848. }
  849. }
  850. }
  851. for _, c := range conns.connections {
  852. idleTime := time.Since(c.GetLastActivity())
  853. isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && c.GetUsername() == "")
  854. if idleTime > Config.idleTimeoutAsDuration || (isUnauthenticatedFTPUser && idleTime > Config.idleLoginTimeout) {
  855. defer func(conn ActiveConnection, isFTPNoAuth bool) {
  856. err := conn.Disconnect()
  857. logger.Debug(conn.GetProtocol(), conn.GetID(), "close idle connection, idle time: %v, username: %#v close err: %v",
  858. time.Since(conn.GetLastActivity()), conn.GetUsername(), err)
  859. if isFTPNoAuth {
  860. ip := util.GetIPFromRemoteAddress(c.GetRemoteAddress())
  861. logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, c.GetProtocol(), "client idle")
  862. metric.AddNoAuthTryed()
  863. AddDefenderEvent(ip, HostEventNoLoginTried)
  864. dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip, c.GetProtocol(),
  865. dataprovider.ErrNoAuthTryed)
  866. }
  867. }(c, isUnauthenticatedFTPUser)
  868. }
  869. }
  870. conns.RUnlock()
  871. }
  872. func (conns *ActiveConnections) checkTransfers() {
  873. if atomic.LoadInt32(&conns.transfersCheckStatus) == 1 {
  874. logger.Warn(logSender, "", "the previous transfer check is still running, skipping execution")
  875. return
  876. }
  877. atomic.StoreInt32(&conns.transfersCheckStatus, 1)
  878. defer atomic.StoreInt32(&conns.transfersCheckStatus, 0)
  879. conns.RLock()
  880. if len(conns.connections) < 2 {
  881. conns.RUnlock()
  882. return
  883. }
  884. var wg sync.WaitGroup
  885. logger.Debug(logSender, "", "start concurrent transfers check")
  886. // update the current size for transfers to monitors
  887. for _, c := range conns.connections {
  888. for _, t := range c.GetTransfers() {
  889. if t.HasSizeLimit {
  890. wg.Add(1)
  891. go func(transfer ConnectionTransfer, connID string) {
  892. defer wg.Done()
  893. transfersChecker.UpdateTransferCurrentSizes(transfer.ULSize, transfer.DLSize, transfer.ID, connID)
  894. }(t, c.GetID())
  895. }
  896. }
  897. }
  898. conns.RUnlock()
  899. logger.Debug(logSender, "", "waiting for the update of the transfers current size")
  900. wg.Wait()
  901. logger.Debug(logSender, "", "getting overquota transfers")
  902. overquotaTransfers := transfersChecker.GetOverquotaTransfers()
  903. logger.Debug(logSender, "", "number of overquota transfers: %v", len(overquotaTransfers))
  904. if len(overquotaTransfers) == 0 {
  905. return
  906. }
  907. conns.RLock()
  908. defer conns.RUnlock()
  909. for _, c := range conns.connections {
  910. for _, overquotaTransfer := range overquotaTransfers {
  911. if c.GetID() == overquotaTransfer.ConnID {
  912. logger.Info(logSender, c.GetID(), "user %#v is overquota, try to close transfer id %v",
  913. c.GetUsername(), overquotaTransfer.TransferID)
  914. var err error
  915. if overquotaTransfer.TransferType == TransferDownload {
  916. err = getReadQuotaExceededError(c.GetProtocol())
  917. } else {
  918. err = getQuotaExceededError(c.GetProtocol())
  919. }
  920. c.SignalTransferClose(overquotaTransfer.TransferID, err)
  921. }
  922. }
  923. }
  924. logger.Debug(logSender, "", "transfers check completed")
  925. }
  926. // AddClientConnection stores a new client connection
  927. func (conns *ActiveConnections) AddClientConnection(ipAddr string) {
  928. conns.clients.add(ipAddr)
  929. }
  930. // RemoveClientConnection removes a disconnected client from the tracked ones
  931. func (conns *ActiveConnections) RemoveClientConnection(ipAddr string) {
  932. conns.clients.remove(ipAddr)
  933. }
  934. // GetClientConnections returns the total number of client connections
  935. func (conns *ActiveConnections) GetClientConnections() int32 {
  936. return conns.clients.getTotal()
  937. }
  938. // IsNewConnectionAllowed returns false if the maximum number of concurrent allowed connections is exceeded
  939. // or a whitelist is defined and the specified ipAddr is not listed
  940. func (conns *ActiveConnections) IsNewConnectionAllowed(ipAddr string) bool {
  941. if Config.whitelist != nil {
  942. if !Config.whitelist.isAllowed(ipAddr) {
  943. return false
  944. }
  945. }
  946. if Config.MaxTotalConnections == 0 && Config.MaxPerHostConnections == 0 {
  947. return true
  948. }
  949. if Config.MaxPerHostConnections > 0 {
  950. if total := conns.clients.getTotalFrom(ipAddr); total > Config.MaxPerHostConnections {
  951. logger.Debug(logSender, "", "active connections from %v %v/%v", ipAddr, total, Config.MaxPerHostConnections)
  952. AddDefenderEvent(ipAddr, HostEventLimitExceeded)
  953. return false
  954. }
  955. }
  956. if Config.MaxTotalConnections > 0 {
  957. if total := conns.clients.getTotal(); total > int32(Config.MaxTotalConnections) {
  958. logger.Debug(logSender, "", "active client connections %v/%v", total, Config.MaxTotalConnections)
  959. return false
  960. }
  961. // on a single SFTP connection we could have multiple SFTP channels or commands
  962. // so we check the estabilished connections too
  963. conns.RLock()
  964. defer conns.RUnlock()
  965. return len(conns.connections) < Config.MaxTotalConnections
  966. }
  967. return true
  968. }
  969. // GetStats returns stats for active connections
  970. func (conns *ActiveConnections) GetStats() []ConnectionStatus {
  971. conns.RLock()
  972. defer conns.RUnlock()
  973. stats := make([]ConnectionStatus, 0, len(conns.connections))
  974. for _, c := range conns.connections {
  975. stat := ConnectionStatus{
  976. Username: c.GetUsername(),
  977. ConnectionID: c.GetID(),
  978. ClientVersion: c.GetClientVersion(),
  979. RemoteAddress: c.GetRemoteAddress(),
  980. ConnectionTime: util.GetTimeAsMsSinceEpoch(c.GetConnectionTime()),
  981. LastActivity: util.GetTimeAsMsSinceEpoch(c.GetLastActivity()),
  982. Protocol: c.GetProtocol(),
  983. Command: c.GetCommand(),
  984. Transfers: c.GetTransfers(),
  985. }
  986. stats = append(stats, stat)
  987. }
  988. return stats
  989. }
  990. // ConnectionStatus returns the status for an active connection
  991. type ConnectionStatus struct {
  992. // Logged in username
  993. Username string `json:"username"`
  994. // Unique identifier for the connection
  995. ConnectionID string `json:"connection_id"`
  996. // client's version string
  997. ClientVersion string `json:"client_version,omitempty"`
  998. // Remote address for this connection
  999. RemoteAddress string `json:"remote_address"`
  1000. // Connection time as unix timestamp in milliseconds
  1001. ConnectionTime int64 `json:"connection_time"`
  1002. // Last activity as unix timestamp in milliseconds
  1003. LastActivity int64 `json:"last_activity"`
  1004. // Protocol for this connection
  1005. Protocol string `json:"protocol"`
  1006. // active uploads/downloads
  1007. Transfers []ConnectionTransfer `json:"active_transfers,omitempty"`
  1008. // SSH command or WebDAV method
  1009. Command string `json:"command,omitempty"`
  1010. }
  1011. // GetConnectionDuration returns the connection duration as string
  1012. func (c *ConnectionStatus) GetConnectionDuration() string {
  1013. elapsed := time.Since(util.GetTimeFromMsecSinceEpoch(c.ConnectionTime))
  1014. return util.GetDurationAsString(elapsed)
  1015. }
  1016. // GetConnectionInfo returns connection info.
  1017. // Protocol,Client Version and RemoteAddress are returned.
  1018. func (c *ConnectionStatus) GetConnectionInfo() string {
  1019. var result strings.Builder
  1020. result.WriteString(fmt.Sprintf("%v. Client: %#v From: %#v", c.Protocol, c.ClientVersion, c.RemoteAddress))
  1021. if c.Command == "" {
  1022. return result.String()
  1023. }
  1024. switch c.Protocol {
  1025. case ProtocolSSH, ProtocolFTP:
  1026. result.WriteString(fmt.Sprintf(". Command: %#v", c.Command))
  1027. case ProtocolWebDAV:
  1028. result.WriteString(fmt.Sprintf(". Method: %#v", c.Command))
  1029. }
  1030. return result.String()
  1031. }
  1032. // GetTransfersAsString returns the active transfers as string
  1033. func (c *ConnectionStatus) GetTransfersAsString() string {
  1034. result := ""
  1035. for _, t := range c.Transfers {
  1036. if result != "" {
  1037. result += ". "
  1038. }
  1039. result += t.getConnectionTransferAsString()
  1040. }
  1041. return result
  1042. }
  1043. // ActiveQuotaScan defines an active quota scan for a user home dir
  1044. type ActiveQuotaScan struct {
  1045. // Username to which the quota scan refers
  1046. Username string `json:"username"`
  1047. // quota scan start time as unix timestamp in milliseconds
  1048. StartTime int64 `json:"start_time"`
  1049. }
  1050. // ActiveVirtualFolderQuotaScan defines an active quota scan for a virtual folder
  1051. type ActiveVirtualFolderQuotaScan struct {
  1052. // folder name to which the quota scan refers
  1053. Name string `json:"name"`
  1054. // quota scan start time as unix timestamp in milliseconds
  1055. StartTime int64 `json:"start_time"`
  1056. }
  1057. // ActiveScans holds the active quota scans
  1058. type ActiveScans struct {
  1059. sync.RWMutex
  1060. UserScans []ActiveQuotaScan
  1061. FolderScans []ActiveVirtualFolderQuotaScan
  1062. }
  1063. // GetUsersQuotaScans returns the active quota scans for users home directories
  1064. func (s *ActiveScans) GetUsersQuotaScans() []ActiveQuotaScan {
  1065. s.RLock()
  1066. defer s.RUnlock()
  1067. scans := make([]ActiveQuotaScan, len(s.UserScans))
  1068. copy(scans, s.UserScans)
  1069. return scans
  1070. }
  1071. // AddUserQuotaScan adds a user to the ones with active quota scans.
  1072. // Returns false if the user has a quota scan already running
  1073. func (s *ActiveScans) AddUserQuotaScan(username string) bool {
  1074. s.Lock()
  1075. defer s.Unlock()
  1076. for _, scan := range s.UserScans {
  1077. if scan.Username == username {
  1078. return false
  1079. }
  1080. }
  1081. s.UserScans = append(s.UserScans, ActiveQuotaScan{
  1082. Username: username,
  1083. StartTime: util.GetTimeAsMsSinceEpoch(time.Now()),
  1084. })
  1085. return true
  1086. }
  1087. // RemoveUserQuotaScan removes a user from the ones with active quota scans.
  1088. // Returns false if the user has no active quota scans
  1089. func (s *ActiveScans) RemoveUserQuotaScan(username string) bool {
  1090. s.Lock()
  1091. defer s.Unlock()
  1092. for idx, scan := range s.UserScans {
  1093. if scan.Username == username {
  1094. lastIdx := len(s.UserScans) - 1
  1095. s.UserScans[idx] = s.UserScans[lastIdx]
  1096. s.UserScans = s.UserScans[:lastIdx]
  1097. return true
  1098. }
  1099. }
  1100. return false
  1101. }
  1102. // GetVFoldersQuotaScans returns the active quota scans for virtual folders
  1103. func (s *ActiveScans) GetVFoldersQuotaScans() []ActiveVirtualFolderQuotaScan {
  1104. s.RLock()
  1105. defer s.RUnlock()
  1106. scans := make([]ActiveVirtualFolderQuotaScan, len(s.FolderScans))
  1107. copy(scans, s.FolderScans)
  1108. return scans
  1109. }
  1110. // AddVFolderQuotaScan adds a virtual folder to the ones with active quota scans.
  1111. // Returns false if the folder has a quota scan already running
  1112. func (s *ActiveScans) AddVFolderQuotaScan(folderName string) bool {
  1113. s.Lock()
  1114. defer s.Unlock()
  1115. for _, scan := range s.FolderScans {
  1116. if scan.Name == folderName {
  1117. return false
  1118. }
  1119. }
  1120. s.FolderScans = append(s.FolderScans, ActiveVirtualFolderQuotaScan{
  1121. Name: folderName,
  1122. StartTime: util.GetTimeAsMsSinceEpoch(time.Now()),
  1123. })
  1124. return true
  1125. }
  1126. // RemoveVFolderQuotaScan removes a folder from the ones with active quota scans.
  1127. // Returns false if the folder has no active quota scans
  1128. func (s *ActiveScans) RemoveVFolderQuotaScan(folderName string) bool {
  1129. s.Lock()
  1130. defer s.Unlock()
  1131. for idx, scan := range s.FolderScans {
  1132. if scan.Name == folderName {
  1133. lastIdx := len(s.FolderScans) - 1
  1134. s.FolderScans[idx] = s.FolderScans[lastIdx]
  1135. s.FolderScans = s.FolderScans[:lastIdx]
  1136. return true
  1137. }
  1138. }
  1139. return false
  1140. }