diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-03-06 05:09:56 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-03-06 05:51:49 +0100 |
| commit | ff8da797a143cb1dfbeb4ce2d2c3e4a1d0c2e56a (patch) | |
| tree | c51bbbd084f531d11ba765186cc5d39709e40ba3 /main.c | |
| parent | 804b8841f3fe59f7058c91fa25c1694f4433642a (diff) | |
| download | slcl-ff8da797a143cb1dfbeb4ce2d2c3e4a1d0c2e56a.tar.gz | |
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
Diffstat (limited to 'main.c')
| -rw-r--r-- | main.c | 114 |
1 files changed, 111 insertions, 3 deletions
@@ -1,4 +1,5 @@ #include "auth.h" +#include "cftw.h" #include "handler.h" #include "http.h" #include "page.h" @@ -351,6 +352,84 @@ static int search(const struct http_payload *const p, return -1; } + +static int add_length(const char *const fpath, const struct stat *const sb, + void *const user) +{ + unsigned long long *const l = user; + + *l += sb->st_size; + return 0; +} + +static int quota_current(const struct auth *const a, + const char *const username, unsigned long long *const cur) +{ + int ret = -1; + const char *const adir = auth_dir(a); + struct dynstr d; + + dynstr_init(&d); + + if (!adir) + { + fprintf(stderr, "%s: auth_dir failed\n", __func__); + goto end; + } + else if (dynstr_append(&d, "%s/user/%s", adir, username)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + *cur = 0; + + if (cftw(d.str, add_length, cur)) + { + fprintf(stderr, "%s: cftw: %s\n", __func__, strerror(errno)); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + return ret; +} + +static int check_quota(const struct auth *const a, const char *const username, + const unsigned long long len, const unsigned long long quota) +{ + unsigned long long total; + + if (quota_current(a, username, &total)) + { + fprintf(stderr, "%s: quota_current failed\n", __func__); + return -1; + } + + return total + len > quota ? 1 : 0; +} + +static int check_length(const unsigned long long len, + const struct http_cookie *const c, void *const user) +{ + struct auth *const a = user; + const char *const username = c->field; + bool has_quota; + unsigned long long quota; + + if (auth_quota(a, username, &has_quota, "a)) + { + fprintf(stderr, "%s: auth_quota failed\n", __func__); + return -1; + } + else if (has_quota) + return check_quota(a, username, len, quota); + + return 0; +} + static bool path_isrel(const char *const path) { if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../")) @@ -458,7 +537,27 @@ static int getnode(const struct http_payload *const p, goto end; } - ret = page_resource(r, dir, root.str, d.str); + bool available; + unsigned long long cur, max; + + if (auth_quota(a, username, &available, &max)) + { + fprintf(stderr, "%s: quota_available failed\n", __func__); + goto end; + } + else if (available && quota_current(a, username, &cur)) + { + fprintf(stderr, "%s: quota_current failed\n", __func__); + goto end; + } + + const struct page_quota pq = + { + .cur = cur, + .max = max + }, *const ppq = available ? &pq : NULL; + + ret = page_resource(r, dir, root.str, d.str, ppq); end: dynstr_free(&d); @@ -899,8 +998,17 @@ int main(const int argc, char *const argv[]) unsigned short port; if (parse_args(argc, argv, &dir, &port, &tmpdir) - || !(a = auth_alloc(dir)) - || !(h = handler_alloc(tmpdir)) + || !(a = auth_alloc(dir))) + goto end; + + const struct handler_cfg cfg = + { + .length = check_length, + .tmpdir = tmpdir, + .user = a + }; + + if (!(h = handler_alloc(&cfg)) || handler_add(h, "/", HTTP_OP_GET, serve_index, a) || handler_add(h, "/index.html", HTTP_OP_GET, serve_index, a) || handler_add(h, "/style.css", HTTP_OP_GET, serve_style, NULL) |
