common.go 35 KB

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