aboutsummaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-01-09 01:22:54 +0100
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-02-28 01:43:56 +0100
commitd26f046fc9149693a6ebc28301ccc3581c0f144e (patch)
tree9da22edd4840304d6cd19a27efab58ce245985aa /main.c
downloadslcl-d26f046fc9149693a6ebc28301ccc3581c0f144e.tar.gz
Initial commit
Diffstat (limited to 'main.c')
-rw-r--r--main.c935
1 files changed, 935 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..2ebf655
--- /dev/null
+++ b/main.c
@@ -0,0 +1,935 @@
+#include "auth.h"
+#include "handler.h"
+#include "http.h"
+#include "page.h"
+#include <dynstr.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct form
+{
+ char *key, *value;
+};
+
+static int redirect(struct http_response *const r)
+{
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_SEE_OTHER
+ };
+
+ if (http_response_add_header(r, "Location", "/user/"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int serve_index(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))
+ return page_login(r);
+
+ return redirect(r);
+}
+
+static int serve_style(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ return page_style(r);
+}
+
+static char *alloc_form_data(const char *const s, const char **const end)
+{
+ const char *const next = strchr(s, '&');
+ const size_t len = next ? next - s : strlen(s);
+ char *const data = malloc(len + 1);
+
+ if (!data)
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return NULL;
+ }
+
+ memcpy(data, s, len);
+ data[len] = '\0';
+ *end = s + len;
+
+ if (next)
+ *end += 1;
+
+ return data;
+}
+
+static struct form *append_form(struct form *forms, const char **const s,
+ size_t *const n)
+{
+ struct form *ret = NULL;
+ const char *end;
+ char *const data = alloc_form_data(*s, &end), *key = NULL, *value = NULL;
+ struct form *f = NULL;
+
+ if (!data)
+ {
+ fprintf(stderr, "%s: alloc_form_data failed\n", __func__);
+ goto end;
+ }
+ else if (!(forms = realloc(forms, (*n + 1) * sizeof *forms)))
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+
+ const char *const sep = strchr(data, '=');
+
+ if (!sep)
+ {
+ fprintf(stderr, "%s: strchr(3) returned NULL\n", __func__);
+ goto end;
+ }
+ else if (!data || !*(sep + 1))
+ {
+ fprintf(stderr, "%s: expected key=value (%s)\n", __func__, data);
+ goto end;
+ }
+
+ f = &forms[(*n)++];
+
+ const size_t keylen = sep - data;
+
+ if (!(key = strndup(data, keylen)))
+ {
+ fprintf(stderr, "%s: strndup(3) key: %s\n", __func__, strerror(errno));
+ goto end;
+ }
+ else if (!(value = strdup(sep + 1)))
+ {
+ fprintf(stderr, "%s: strdup(3) value: %s\n", __func__, strerror(errno));
+ goto end;
+ }
+
+ *f = (const struct form)
+ {
+ .key = http_decode_url(key),
+ .value = http_decode_url(value)
+ };
+
+ if (!f->key || !f->value)
+ {
+ fprintf(stderr, "%s: http_decode_url key/value failed\n", __func__);
+ goto end;
+ }
+
+ *s = end;
+ ret = forms;
+
+end:
+ free(key);
+ free(value);
+ free(data);
+
+ if (!ret)
+ {
+ if (f)
+ {
+ free(f->key);
+ free(f->value);
+ }
+
+ return NULL;
+ }
+
+ return ret;
+}
+
+static struct form *get_forms(const struct http_payload *const pl,
+ size_t *const outn)
+{
+ const struct http_post *const p = &pl->u.post;
+ const char *const ref = p->data;
+ char *dup = NULL;
+ struct form *forms = NULL;
+
+ if (!ref)
+ {
+ fprintf(stderr, "%s: expected non-NULL buffer\n", __func__);
+ goto failure;
+ }
+ else if (!(dup = strndup(ref, p->n)))
+ {
+ fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+
+ const char *s = dup;
+
+ *outn = 0;
+
+ while (*s)
+ if (!(forms = append_form(forms, &s, outn)))
+ {
+ fprintf(stderr, "%s: append_form failed\n", __func__);
+ goto failure;
+ }
+
+ free(dup);
+ return forms;
+
+failure:
+ free(dup);
+ free(forms);
+ return NULL;
+}
+
+static int check_credentials(struct auth *const a,
+ const struct form *const forms, const size_t n, char **const cookie)
+{
+ const char *username = NULL, *pwd = NULL;
+
+ *cookie = NULL;
+
+ for (size_t i = 0; i < n; i++)
+ {
+ const struct form *const f = &forms[i];
+
+ if (!strcmp(f->key, "username"))
+ username = f->value;
+ else if (!strcmp(f->key, "password"))
+ pwd = f->value;
+ }
+
+ if (!username || !pwd)
+ {
+ fprintf(stderr, "%s: missing credentials\n", __func__);
+ return 1;
+ }
+
+ const int ret = auth_login(a, username, pwd, cookie);
+
+ if (ret < 0)
+ fprintf(stderr, "%s: auth_login failed\n", __func__);
+
+ return ret;
+}
+
+static int login(const struct http_payload *const pl,
+ struct http_response *const r, void *const user)
+{
+ int res = -1;
+ size_t n;
+ struct form *const forms = get_forms(pl, &n);
+ struct auth *const a = user;
+ char *cookie = NULL;
+
+ if (!forms)
+ {
+ fprintf(stderr, "%s: get_forms failed\n", __func__);
+ goto end;
+ }
+ else if ((res = check_credentials(a, forms, n, &cookie)))
+ {
+ if (res < 0)
+ fprintf(stderr, "%s: check_credentials failed\n", __func__);
+
+ goto end;
+ }
+ else if (redirect(r))
+ {
+ fprintf(stderr, "%s: redirect failed\n", __func__);
+ goto end;
+ }
+ else if (cookie && http_response_add_header(r, "Set-Cookie", cookie))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+
+ res = 0;
+
+end:
+ if (forms)
+ for (size_t i = 0; i < n; i++)
+ {
+ free(forms[i].key);
+ free(forms[i].value);
+ }
+
+ free(cookie);
+ free(forms);
+
+ if (res)
+ {
+ if (page_failed_login(r))
+ {
+ fprintf(stderr, "%s: page_failed_login failed\n", __func__);
+ return -1;
+ }
+ else if (http_response_add_header(r, "Location", "/"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+ else if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int logout(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ struct auth *const a = user;
+ const struct http_cookie *const c = &p->cookie;
+ const int res = auth_cookie(a, c);
+
+ if (res < 0)
+ {
+ fprintf(stderr, "%s: auth_cookie failed\n", __func__);
+ return -1;
+ }
+ else if (res)
+ return page_forbidden(r);
+
+ int ret = -1;
+ struct dynstr d;
+ static const char date[] = "Thu, 1 Jan 1970 00:00:00 GMT";
+
+ dynstr_init(&d);
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_SEE_OTHER
+ };
+
+ if (http_response_add_header(r, "Location", "/"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+ else if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+ /* Force expired cookie so they are removed by the browser, too. */
+ else if (dynstr_append(&d, "%s=%s; Expires=%s", c->field, c->value, date))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (http_response_add_header(r, "Set-Cookie", d.str))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&d);
+ return ret;
+}
+
+static int search(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);
+ }
+
+ fprintf(stderr, "%s: TODO\n", __func__);
+ return -1;
+}
+
+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 char *cust_dirname(char *const d)
+{
+ /*
+ path dirname cust_dirname
+ /usr/lib /usr /usr/
+ /usr/ / /usr/
+ usr . .
+ / / /
+ . . .
+ .. . .
+ */
+
+ if (!strcmp(d, "."))
+ return d;
+
+ char *const s = strrchr(d, '/');
+
+ if (!s)
+ return ".";
+
+ *(s + 1) = '\0';
+ return d;
+}
+
+static int getnode(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__);
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_SEE_OTHER
+ };
+
+ if (http_response_add_header(r, "Location", "/"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+ else if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ const char *const username = p->cookie.field,
+ *const resource = p->resource + strlen("/user/");
+
+ if (path_isrel(resource))
+ {
+ fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
+ return page_forbidden(r);
+ }
+
+ int ret = -1;
+ struct dynstr root, d;
+ char *const dird = strdup(p->resource), *dir = NULL;
+ const char *const adir = auth_dir(a);
+
+ dynstr_init(&d);
+ dynstr_init(&root);
+
+ if (!adir)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ goto end;
+ }
+ else if (!dird)
+ {
+ fprintf(stderr, "%s: strdup(3) failed: %s\n",
+ __func__, strerror(errno));
+ goto end;
+ }
+ else if (!(dir = cust_dirname(dird)))
+ {
+ fprintf(stderr, "%s: dirname(3) failed: %s\n",
+ __func__, strerror(errno));
+ goto end;
+ }
+ else if (dynstr_append(&root, "%s/user/%s/", adir, username)
+ || dynstr_append(&d, "%s%s", root.str, resource))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+
+ ret = page_resource(r, dir, root.str, d.str);
+
+end:
+ dynstr_free(&d);
+ dynstr_free(&root);
+ free(dird);
+ return ret;
+}
+
+static int move_file(const char *const old, const char *const new)
+{
+ int ret = -1;
+ FILE *const f = fopen(old, "rb");
+ const int fd = open(new, O_WRONLY | O_CREAT, 0600);
+ struct stat sb;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+ else if (fd < 0)
+ {
+ fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+ else if (stat(old, &sb))
+ {
+ fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+
+ for (off_t i = 0; i < sb.st_size;)
+ {
+ char buf[1024];
+ const off_t left = sb.st_size - i;
+ const size_t rem = left > sizeof buf ? sizeof buf : left;
+ ssize_t w;
+
+ if (!fread(buf, rem, 1, f))
+ {
+ fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n",
+ __func__, feof(f), ferror(f));
+ goto end;
+ }
+ else if ((w = write(fd, buf, rem)) < 0)
+ {
+ fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+ else if (w != rem)
+ {
+ fprintf(stderr, "%s: write(2): expected to write %zu bytes, "
+ "only %ju written\n", __func__, rem, (intmax_t)w);
+ goto end;
+ }
+
+ i += rem;
+ }
+
+ ret = 0;
+
+end:
+ if (fd >= 0 && close(fd))
+ {
+ fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
+ ret = -1;
+ }
+
+ if (f && fclose(f))
+ {
+ fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
+ ret = -1;
+ }
+ else if (remove(old))
+ {
+ fprintf(stderr, "%s: remove(3): %s\n", __func__, strerror(errno));
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int rename_or_move(const char *const old, const char *const new)
+{
+ const int res = rename(old, new);
+
+ if (res && errno == EXDEV)
+ return move_file(old, new);
+ else if (res)
+ fprintf(stderr, "%s: rename(3): %s\n", __func__, strerror(errno));
+
+ return res;
+}
+
+static int upload_file(const struct http_post_file *const f,
+ const char *const user, const char *const root, const char *const dir)
+{
+ int ret = -1;
+ struct dynstr d;
+
+ dynstr_init(&d);
+
+ if (!root)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "%s/user/%s/%s%s", root, user, dir, f->filename))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (rename_or_move(f->tmpname, d.str))
+ {
+ fprintf(stderr, "%s: rename_or_move failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&d);
+ return ret;
+}
+
+static int redirect_to_dir(const char *const dir,
+ struct http_response *const r)
+{
+ int ret = -1;
+ struct dynstr d;
+ char *const encdir = http_encode_url(dir);
+
+ dynstr_init(&d);
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_SEE_OTHER
+ };
+
+ if (!encdir)
+ {
+ fprintf(stderr, "%s: http_encode_url failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "/user%s", encdir))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (http_response_add_header(r, "Location", d.str))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ free(encdir);
+ dynstr_free(&d);
+ return ret;
+}
+
+static int upload_files(const struct http_payload *const p,
+ struct http_response *const r, const struct auth *const a)
+{
+ const struct http_post *const po = &p->u.post;
+ const char *const root = auth_dir(a), *const user = p->cookie.field,
+ *const dir = po->dir;
+
+ if (!po->files)
+ {
+ fprintf(stderr, "%s: expected file list\n", __func__);
+ return 1;
+ }
+ else if (!root)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ return -1;
+ }
+ else if (!dir)
+ {
+ static const char body[] = "<html>No target directory set</html>";
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_BAD_REQUEST,
+ .buf.ro = body,
+ .n = strlen(body)
+ };
+
+ if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ for (size_t i = 0; i < po->n; i++)
+ {
+ if (upload_file(&po->files[i], user, root, po->dir))
+ {
+ fprintf(stderr, "%s: upload_file failed\n", __func__);
+ return -1;
+ }
+ }
+
+ return redirect_to_dir(dir, r);
+}
+
+static int upload(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ const struct auth *const a = user;
+
+ if (auth_cookie(a, &p->cookie))
+ {
+ fprintf(stderr, "%s: auth_cookie failed\n", __func__);
+ return page_forbidden(r);
+ }
+ else if (p->u.post.expect_continue)
+ {
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_CONTINUE
+ };
+
+ return 0;
+ }
+
+ return upload_files(p, r, a);
+}
+
+static int createdir(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);
+ }
+
+ size_t n;
+ struct form *const forms = get_forms(p, &n);
+
+ if (!forms)
+ {
+ fprintf(stderr, "%s: get_forms failed\n", __func__);
+ return page_bad_request(r);
+ }
+ else if (n != 2)
+ {
+ fprintf(stderr, "%s: expected 2 forms, got %zu\n", __func__, n);
+ return page_bad_request(r);
+ }
+
+ char *name = NULL;
+ const char *dir = NULL;
+
+ for (size_t i = 0; i < n; i++)
+ {
+ const struct form *const f = &forms[i];
+
+ if (!strcmp(f->key, "name"))
+ name = f->value;
+ else if (!strcmp(f->key, "dir"))
+ dir = f->value;
+ else
+ {
+ fprintf(stderr, "%s: unexpected key %s\n", __func__, f->key);
+ return page_bad_request(r);
+ }
+ }
+
+ if (!name || !dir)
+ {
+ fprintf(stderr, "%s: missing name or directory\n", __func__);
+ return page_bad_request(r);
+ }
+ else if (path_isrel(name) || strpbrk(name, "/*"))
+ {
+ fprintf(stderr, "%s: invalid directory name %s\n", __func__, dir);
+ return page_bad_request(r);
+ }
+ else if (path_isrel(dir) || strchr(dir, '*'))
+ {
+ fprintf(stderr, "%s: invalid name %s\n", __func__, name);
+ return page_bad_request(r);
+ }
+
+ /* HTML input forms use '+' for whitespace, rather than %20. */
+ {
+ char *c = name;
+
+ while ((c = strchr(c, '+')))
+ *c = ' ';
+ }
+
+ const char *const root = auth_dir(a);
+
+ if (!root)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ return -1;
+ }
+
+ int ret = -1;
+ struct dynstr d, userd;
+
+ dynstr_init(&d);
+ dynstr_init(&userd);
+
+ if (dynstr_append(&d, "%s/user/%s/%s%s", root, p->cookie.field, dir, name))
+ {
+ fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&userd, "/user%s%s/", dir, name))
+ {
+ fprintf(stderr, "%s: dynstr_append userd failed\n", __func__);
+ goto end;
+ }
+ else if (mkdir(d.str, 0700))
+ {
+ fprintf(stderr, "%s: mkdir(2): %s\n", __func__, strerror(errno));
+
+ if (errno != EEXIST)
+ goto end;
+ else
+ {
+ static const char body[] = "<html>Directory already exists</html>";
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_BAD_REQUEST,
+ .buf.ro = body,
+ .n = strlen(body)
+ };
+
+ if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_SEE_OTHER
+ };
+
+ if (http_response_add_header(r, "Location", userd.str))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+ else if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&userd);
+ dynstr_free(&d);
+ return ret;
+}
+
+static void usage(char *const argv[])
+{
+ fprintf(stderr, "%s [-t tmpdir] [-p port] dir\n", *argv);
+}
+
+static int parse_args(const int argc, char *const argv[],
+ const char **const dir, unsigned short *const port,
+ const char **const tmpdir)
+{
+ const char *const envtmp = getenv("TMPDIR");
+ int opt;
+
+ /* Default values. */
+ *port = 0;
+ *tmpdir = envtmp ? envtmp : "/tmp";
+
+ while ((opt = getopt(argc, argv, "t:p:")) != -1)
+ {
+ switch (opt)
+ {
+ case 't':
+ *tmpdir = optarg;
+ break;
+
+ case 'p':
+ {
+ const unsigned long portul = strtoul(optarg, NULL, 10);
+
+ if (portul > UINT16_MAX)
+ {
+ fprintf(stderr, "%s: invalid port %lu\n", __func__, portul);
+ return -1;
+ }
+
+ *port = portul;
+ }
+ break;
+
+ default:
+ usage(argv);
+ return -1;
+ }
+ }
+
+ if (optind >= argc)
+ {
+ usage(argv);
+ return -1;
+ }
+
+ *dir = argv[optind];
+ return 0;
+}
+
+int main(const int argc, char *const argv[])
+{
+ int ret = EXIT_FAILURE;
+ struct handler *h = NULL;
+ struct auth *a = NULL;
+ const char *dir, *tmpdir;
+ unsigned short port;
+
+ if (parse_args(argc, argv, &dir, &port, &tmpdir)
+ || !(a = auth_alloc(dir))
+ || !(h = handler_alloc(tmpdir))
+ || 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)
+ || 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, "/search", HTTP_OP_POST, search, a)
+ || handler_add(h, "/upload", HTTP_OP_POST, upload, a)
+ || handler_add(h, "/mkdir", HTTP_OP_POST, createdir, a)
+ || handler_listen(h, port))
+ goto end;
+
+ ret = EXIT_SUCCESS;
+
+end:
+ auth_free(a);
+ handler_free(h);
+ return ret;
+}