aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-03-09 01:29:48 +0100
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-03-09 02:02:40 +0100
commit2e1b1313962d979b6e15491c63d316d829638bf0 (patch)
treee2b74911503568d539ff4f377b80759257590635
parent13f96054f6fb68aae464363e283e32f01d2da1a3 (diff)
downloadslcl-2e1b1313962d979b6e15491c63d316d829638bf0.tar.gz
Implement public file sharing
An HTML form is now added next to each regular file, that generates a POST request. Then, slcl replies with a HTML document with a link to the public resource (which are implemented as symlinks). Limitations: - For now, only regular files can be shared i.e., sharing directories is not possible. While feasible, it still requires a larger refactor to list_dir and resource_layout, so that read-only access to the directory is provided to anonymous users.
-rw-r--r--main.c196
-rw-r--r--page.c186
-rw-r--r--page.h2
3 files changed, 369 insertions, 15 deletions
diff --git a/main.c b/main.c
index a6c5809..d559220 100644
--- a/main.c
+++ b/main.c
@@ -1,8 +1,11 @@
#include "auth.h"
#include "cftw.h"
#include "handler.h"
+#include "hex.h"
#include "http.h"
#include "page.h"
+#include <openssl/err.h>
+#include <openssl/rand.h>
#include <dynstr.h>
#include <libgen.h>
#include <fcntl.h>
@@ -352,6 +355,116 @@ end:
return ret;
}
+static bool path_isrel(const char *const path)
+{
+ if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../"))
+ return true;
+
+ static const char suffix[] = "/..";
+ const size_t n = strlen(path), sn = strlen(suffix);
+
+ if (n >= sn && !strcmp(path + n - sn, suffix))
+ return true;
+
+ return false;
+}
+
+static int getpublic(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ int ret = -1;
+ struct auth *const a = user;
+ 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 (path_isrel(p->resource))
+ {
+ fprintf(stderr, "%s: illegal relative path %s\n",
+ __func__, p->resource);
+ ret = page_forbidden(r);
+ goto end;
+ }
+ else if (dynstr_append(&d, "%s%s", adir, p->resource))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (page_public(r, d.str))
+ {
+ fprintf(stderr, "%s: page_public failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&d);
+ return ret;
+}
+
+static char *create_symlink(const char *const username, const char *const dir,
+ const char *const path)
+{
+ char *ret = NULL;
+ unsigned char buf[16];
+ char dbuf[1 + 2 * sizeof buf];
+ struct dynstr user, abs, rel;
+
+ dynstr_init(&user);
+ dynstr_init(&abs);
+ dynstr_init(&rel);
+
+ if (RAND_bytes(buf, sizeof buf) != 1)
+ {
+ fprintf(stderr, "%s: RAND_bytes failed with %lu\n",
+ __func__, ERR_get_error());
+ goto end;
+ }
+ else if (hex_encode(buf, dbuf, sizeof buf, sizeof dbuf))
+ {
+ fprintf(stderr, "%s: hex_encode failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&user, "%s/user/%s%s", dir, username, path))
+ {
+ fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&rel, "/public/%s", dbuf))
+ {
+ fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&abs, "%s%s", dir, rel.str))
+ {
+ fprintf(stderr, "%s: dynstr_append abs failed\n", __func__);
+ goto end;
+ }
+ else if (symlink(user.str, abs.str))
+ {
+ fprintf(stderr, "%s: symlink(2): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+
+ ret = rel.str;
+
+end:
+ dynstr_free(&user);
+ dynstr_free(&abs);
+
+ if (!ret)
+ dynstr_free(&rel);
+
+ return ret;
+}
+
static int search(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
@@ -367,6 +480,73 @@ static int search(const struct http_payload *const p,
return -1;
}
+static int share(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ struct auth *const a = user;
+
+ if (auth_cookie(a, &p->cookie))
+ {
+ fprintf(stderr, "%s: auth_cookie failed\n", __func__);
+ return page_forbidden(r);
+ }
+
+ const char *const adir = auth_dir(a);
+
+ if (!adir)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ return -1;
+ }
+
+ int ret = -1;
+ struct form *forms = NULL;
+ size_t n;
+ char *sympath = NULL;
+
+ if (!(forms = get_forms(p, &n)))
+ {
+ fprintf(stderr, "%s: get_forms failed\n", __func__);
+ ret = page_bad_request(r);
+ goto end;
+ }
+ else if (n != 1)
+ {
+ fprintf(stderr, "%s: expected 1 form, got %zu\n", __func__, n);
+ ret = page_bad_request(r);
+ goto end;
+ }
+
+ const char *const path = forms->value, *const username = p->cookie.field;
+
+ if (path_isrel(path))
+ {
+ fprintf(stderr, "%s: invalid path %s\n", __func__, path);
+ ret = page_bad_request(r);
+ goto end;
+ }
+ else if (!(sympath = create_symlink(username, adir, path)))
+ {
+ fprintf(stderr, "%s: create_symlink failed\n", __func__);
+ goto end;
+ }
+ else if (page_share(r, sympath))
+ {
+ fprintf(stderr, "%s: page_share failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ if (forms)
+ for (size_t i = 0; i < n; i++)
+ form_free(&forms[i]);
+
+ free(forms);
+ free(sympath);
+ return ret;
+}
static int add_length(const char *const fpath, const struct stat *const sb,
void *const user)
@@ -445,20 +625,6 @@ static int check_length(const unsigned long long len,
return 0;
}
-static bool path_isrel(const char *const path)
-{
- if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../"))
- return true;
-
- static const char suffix[] = "/..";
- const size_t n = strlen(path), sn = strlen(suffix);
-
- if (n >= sn && !strcmp(path + n - sn, suffix))
- return true;
-
- return false;
-}
-
static int getnode(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
@@ -976,7 +1142,9 @@ int main(const int argc, char *const argv[])
|| handler_add(h, "/user/*", HTTP_OP_GET, getnode, a)
|| handler_add(h, "/login", HTTP_OP_POST, login, a)
|| handler_add(h, "/logout", HTTP_OP_POST, logout, a)
+ || handler_add(h, "/public/*", HTTP_OP_GET, getpublic, a)
|| handler_add(h, "/search", HTTP_OP_POST, search, a)
+ || handler_add(h, "/share", HTTP_OP_POST, share, a)
|| handler_add(h, "/upload", HTTP_OP_POST, upload, a)
|| handler_add(h, "/mkdir", HTTP_OP_POST, createdir, a)
|| handler_listen(h, port))
diff --git a/page.c b/page.c
index 9130147..b87f38d 100644
--- a/page.c
+++ b/page.c
@@ -161,11 +161,89 @@ static int prepare_date(struct html_node *const n, const struct stat *const sb)
return 0;
}
+static int prepare_share(struct html_node *const n,
+ const struct stat *const sb, const char *const dir, const char *const name)
+{
+ int ret = -1;
+ const char *const fdir = dir + strlen("/user");
+ struct html_node *form, *file, *submit;
+ struct dynstr d;
+
+ dynstr_init(&d);
+
+ if (!S_ISREG(sb->st_mode))
+ return 0;
+
+ if (!(form = html_node_add_child(n, "form")))
+ {
+ fprintf(stderr, "%s: html_node_add_child form failed\n", __func__);
+ goto end;
+ }
+ else if (!(file = html_node_add_child(form, "input")))
+ {
+ fprintf(stderr, "%s: html_node_add_child file failed\n", __func__);
+ goto end;
+ }
+ else if (!(submit = html_node_add_child(form, "input")))
+ {
+ fprintf(stderr, "%s: html_node_add_child file failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(form, "method", "post"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr method failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(form, "action", "/share"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr action failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(file, "type", "hidden"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr file type failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(file, "name", "name"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr file name failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "%s%s", fdir, name))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(file, "value", d.str))
+ {
+ fprintf(stderr, "%s: html_node_add_attr file value failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(submit, "type", "submit"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr submit type failed\n",
+ __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(submit, "value", "Share"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr submit value failed\n",
+ __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&d);
+ return ret;
+}
+
static int add_element(struct html_node *const n, const char *const dir,
const char *const res, const char *const name)
{
int ret = -1;
- enum {NAME, SIZE, DATE, COLUMNS};
+ enum {NAME, SIZE, DATE, SHARE, COLUMNS};
struct html_node *tr, *td[COLUMNS];
struct dynstr path;
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
@@ -214,6 +292,11 @@ static int add_element(struct html_node *const n, const char *const dir,
fprintf(stderr, "%s: prepare_date failed\n", __func__);
goto end;
}
+ else if (prepare_share(td[SHARE], &sb, dir, name))
+ {
+ fprintf(stderr, "%s: prepare_date failed\n", __func__);
+ goto end;
+ }
ret = 0;
@@ -892,6 +975,32 @@ int page_resource(struct http_response *const r, const char *const dir,
return -1;
}
+int page_public(struct http_response *const r, const char *const res)
+{
+ struct stat sb;
+
+ if (stat(res, &sb))
+ {
+ fprintf(stderr, "%s: stat(2) %s: %s\n",
+ __func__, res, strerror(errno));
+
+ if (errno == ENOENT)
+ return page_not_found(r);
+ else
+ return -1;
+ }
+
+ const mode_t m = sb.st_mode;
+
+ if (!S_ISREG(m))
+ {
+ fprintf(stderr, "%s: only regular files are supported\n", __func__);
+ return -1;
+ }
+
+ return serve_file(r, &sb, res);
+}
+
int page_failed_login(struct http_response *const r)
{
static const char index[] =
@@ -1036,3 +1145,78 @@ int page_style(struct http_response *const r)
return 0;
}
+
+int page_share(struct http_response *const r, const char *const path)
+{
+ int ret = -1;
+ struct dynstr out;
+ struct html_node *const html = html_node_alloc("html"),
+ *head, *a;
+
+ dynstr_init(&out);
+
+ if (!html)
+ {
+ fprintf(stderr, "%s: html_node_alloc failed\n", __func__);
+ goto end;
+ }
+ else if (!(head = html_node_add_child(html, "head")))
+ {
+ fprintf(stderr, "%s: html_node_add_child head failed\n", __func__);
+ goto end;
+ }
+ else if (common_head(head, NULL))
+ {
+ fprintf(stderr, "%s: common_head failed\n", __func__);
+ goto end;
+ }
+ else if (!(a = html_node_add_child(html, "a")))
+ {
+ fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(a, "href", path))
+ {
+ fprintf(stderr, "%s: html_node_add_attr href failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_set_value(a, "Copy this link"))
+ {
+ fprintf(stderr, "%s: html_node_set_value href failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&out, DOCTYPE_TAG))
+ {
+ fprintf(stderr, "%s: dynstr_prepend failed\n", __func__);
+ goto end;
+ }
+ else if (html_serialize(html, &out))
+ {
+ fprintf(stderr, "%s: html_serialize failed\n", __func__);
+ goto end;
+ }
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_OK,
+ .buf.rw = out.str,
+ .n = out.len,
+ .free = free
+ };
+
+ if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ html_node_free(html);
+
+ if (ret)
+ dynstr_free(&out);
+
+ return ret;
+}
diff --git a/page.h b/page.h
index 7ddce40..9df1eea 100644
--- a/page.h
+++ b/page.h
@@ -15,5 +15,7 @@ int page_forbidden(struct http_response *r);
int page_bad_request(struct http_response *r);
int page_resource(struct http_response *r, const char *dir, const char *root,
const char *res, const struct page_quota *q);
+int page_public(struct http_response *r, const char *res);
+int page_share(struct http_response *r, const char *path);
#endif /* PAGE_H */