diff --git a/README.md b/README.md index 1f8d9606..87cfd9b0 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,10 @@ The `sftpgo.conf` configuration file contains the following sections: - `connectionstring`, string. Provide a custom database connection string. If not empty this connection string will be used instead of build one using the previous parameters - `users_table`, string. Database table for SFTP users - `manage_users`, integer. Set to 0 to disable users management, 1 to enable - - `track_quota`, integer. Set to 0 to disable quota tracking, 1 to update the used quota each time a user upload or delete a file + - `track_quota`, integer. Set the preferred way to track users quota between the following choices: + - 0, disable quota tracking. REST API to scan user dir and update quota will do nothing + - 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions + - 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions. With this configuration you can still use the "quota scan" REST API to periodically update space usage for users without quota restrictions - **"httpd"**, the configuration for the HTTP server used to serve REST API - `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080 - `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1" @@ -157,7 +160,7 @@ These properties are stored inside the data provider. If you want to use your ex SFTPGo exposes REST API to manage users and quota and to get real time reports for the active connections with possibility of forcibly closing a connection. -If quota tracking is enabled in `sftpgo.conf` configuration file, then the used size and number of files are updated each time a file is added/removed. If files are added/removed not using SFTP, you can rescan the user home dir and update the used quota using the REST API. +If quota tracking is enabled in `sftpgo.conf` configuration file, then the used size and number of files are updated each time a file is added/removed. If files are added/removed not using SFTP or if you change `track_quota` from `2` to `1`, you can rescan the user home dir and update the used quota using the REST API. REST API is designed to run on localhost or on a trusted network, if you need https or authentication you can setup a reverse proxy using an HTTP Server such as Apache or NGNIX. diff --git a/api/quota.go b/api/quota.go index fe8aaaab..c930f643 100644 --- a/api/quota.go +++ b/api/quota.go @@ -33,11 +33,8 @@ func startQuotaScan(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Warn(logSender, "error scanning user home dir %v: %v", user.HomeDir, err) } else { - err := dataprovider.UpdateUserQuota(dataProvider, user.Username, numFiles, size, true) - if err != nil { - logger.Debug(logSender, "error updating user quota for %v: %v", user.Username, err) - } - logger.Debug(logSender, "user dir scanned, user: %v, dir: %v", user.Username, user.HomeDir) + err := dataprovider.UpdateUserQuota(dataProvider, user, numFiles, size, true) + logger.Debug(logSender, "user dir scanned, user: %v, dir: %v, error: %v", user.Username, user.HomeDir, err) } sftpd.RemoveQuotaScan(user.Username) }() diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 6add9807..c27570f8 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -23,7 +23,7 @@ const ( logSender = "dataProvider" argonPwdPrefix = "$argon2id$" manageUsersDisabledError = "please set manage_users to 1 in sftpgo.conf to enable this method" - trackQuotaDisabledError = "please set track_quota to 1 in sftpgo.conf to enable this method" + trackQuotaDisabledError = "please enable track_quota in sftpgo.conf to use this method" ) var ( @@ -117,11 +117,13 @@ func CheckUserAndPubKey(p Provider, username string, pubKey string) (User, error } // UpdateUserQuota update the quota for the given user -func UpdateUserQuota(p Provider, username string, filesAdd int, sizeAdd int64, reset bool) error { +func UpdateUserQuota(p Provider, user User, filesAdd int, sizeAdd int64, reset bool) error { if config.TrackQuota == 0 { return &MethodDisabledError{err: trackQuotaDisabledError} + } else if config.TrackQuota == 2 && !reset && !user.HasQuotaRestrictions() { + return nil } - return p.updateQuota(username, filesAdd, sizeAdd, reset) + return p.updateQuota(user.Username, filesAdd, sizeAdd, reset) } // GetUsedQuota returns the used quota for the given user diff --git a/dataprovider/user.go b/dataprovider/user.go index 6d93056e..c6cb2d58 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -82,3 +82,9 @@ func (u *User) GetGID() int { func (u *User) GetHomeDir() string { return filepath.Clean(u.HomeDir) } + +// HasQuotaRestrictions returns true if there is a quota restriction on number of files +// or size or both +func (u *User) HasQuotaRestrictions() bool { + return u.QuotaFiles > 0 || u.QuotaSize > 0 +} diff --git a/sftpd/handler.go b/sftpd/handler.go index 0cbebb77..803a0ef5 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -167,8 +167,7 @@ func (c Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { if trunc { // the file is truncated so we need to decrease quota size but not quota files - logger.Debug(logSender, "file truncation requested update quota for user %v", c.User.Username) - dataprovider.UpdateUserQuota(dataProvider, c.User.Username, 0, -stat.Size(), false) + dataprovider.UpdateUserQuota(dataProvider, c.User, 0, -stat.Size(), false) } utils.SetPathPermissions(p, c.User.GetUID(), c.User.GetGID()) @@ -339,7 +338,7 @@ func (c Connection) handleSFTPRmdir(path string) error { } logger.CommandLog(sftpdRmdirLogSender, path, "", c.User.Username, c.ID) - dataprovider.UpdateUserQuota(dataProvider, c.User.Username, -numFiles, -size, false) + dataprovider.UpdateUserQuota(dataProvider, c.User, -numFiles, -size, false) for _, p := range fileList { executeAction(operationDelete, c.User.Username, p, "") } @@ -392,7 +391,7 @@ func (c Connection) handleSFTPRemove(path string) error { logger.CommandLog(sftpdRemoveLogSender, path, "", c.User.Username, c.ID) if fi.Mode()&os.ModeSymlink != os.ModeSymlink { - dataprovider.UpdateUserQuota(dataProvider, c.User.Username, -1, -size, false) + dataprovider.UpdateUserQuota(dataProvider, c.User, -1, -size, false) } executeAction(operationDelete, c.User.Username, path, "") @@ -410,7 +409,8 @@ func (c Connection) hasSpace(checkFiles bool) bool { logger.Warn(logSender, "error getting used quota for %v: %v", c.User.Username, err) return false } - if (checkFiles && numFile >= c.User.QuotaFiles) || size >= c.User.QuotaSize { + if (checkFiles && c.User.QuotaFiles > 0 && numFile >= c.User.QuotaFiles) || + (c.User.QuotaSize > 0 && size >= c.User.QuotaSize) { logger.Debug(logSender, "quota exceed for user %v, num files: %v/%v, size: %v/%v check files: %v", c.User.Username, numFile, c.User.QuotaFiles, size, c.User.QuotaSize, checkFiles) return false diff --git a/sftpd/transfer.go b/sftpd/transfer.go index e5853207..558ed728 100644 --- a/sftpd/transfer.go +++ b/sftpd/transfer.go @@ -62,7 +62,7 @@ func (t *Transfer) Close() error { if t.isNewFile { numFiles = 1 } - dataprovider.UpdateUserQuota(dataProvider, t.user.Username, numFiles, t.bytesReceived, false) + dataprovider.UpdateUserQuota(dataProvider, t.user, numFiles, t.bytesReceived, false) } return err }