aboutsummaryrefslogtreecommitdiff
path: root/ep_ucp.c
diff options
context:
space:
mode:
Diffstat (limited to 'ep_ucp.c')
-rw-r--r--ep_ucp.c241
1 files changed, 241 insertions, 0 deletions
diff --git a/ep_ucp.c b/ep_ucp.c
new file mode 100644
index 0000000..30fb9de
--- /dev/null
+++ b/ep_ucp.c
@@ -0,0 +1,241 @@
+/*
+ * nanobbs, a tiny forums software.
+ * Copyright (C) 2025-2026 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "endpoints.h"
+#include "db.h"
+#include "defs.h"
+#include "auth.h"
+#include "form.h"
+#include <libweb/html.h>
+#include <libweb/http.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static int setup_passwd(struct html_node *const n, const char *const username)
+{
+ int ret = -1;
+ struct dynstr ud;
+ struct html_node *div, *user, *form, *lold, *iold, *lnew,
+ *inew, *lcnew, *icnew, *submit;
+
+ dynstr_init(&ud);
+
+ if (dynstr_append(&ud, "User: %s", username))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (!(div = html_node_add_child(n, "div"))
+ || !(form = html_node_add_child(div, "form"))
+ || !(user = html_node_add_child(form, "label"))
+ || !(lold = html_node_add_child(form, "label"))
+ || !(iold = html_node_add_child(form, "input"))
+ || !(lnew = html_node_add_child(form, "label"))
+ || !(inew = html_node_add_child(form, "input"))
+ || !(lcnew = html_node_add_child(form, "label"))
+ || !(icnew = html_node_add_child(form, "input"))
+ || !(submit = html_node_add_child(form, "input")))
+ {
+ fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(form, "action", "/passwd")
+ || html_node_add_attr(form, "form", "passwdform")
+ || html_node_add_attr(form, "method", "post")
+ || html_node_add_attr(lold, "for", "old")
+ || html_node_add_attr(lnew, "for", "new")
+ || html_node_add_attr(lcnew, "for", "cnew")
+ || html_node_add_attr(iold, "type", "password")
+ || html_node_add_attr(iold, "id", "old")
+ || html_node_add_attr(iold, "name", "old")
+ || html_node_add_attr(inew, "type", "password")
+ || html_node_add_attr(inew, "id", "new")
+ || html_node_add_attr(inew, "name", "new")
+ || html_node_add_attr(icnew, "type", "password")
+ || html_node_add_attr(icnew, "id", "cnew")
+ || html_node_add_attr(icnew, "name", "cnew")
+ || html_node_add_attr(submit, "type", "submit")
+ || html_node_add_attr(submit, "value", "Change password"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_set_value(user, ud.str)
+ || html_node_set_value(lold, "Current password:")
+ || html_node_set_value(lnew, "New password:")
+ || html_node_set_value(lcnew, "Confirm new password:"))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&ud);
+ return ret;
+}
+
+static int setup_useract(struct html_node *const div)
+{
+ struct f
+ {
+ const char *text, *id, *url;
+ struct html_node *luser, *iuser, *form, *submit;
+ } fs[] =
+ {
+ {.text = "Ban user", .id = "banform", .url = "/confirm/ban"},
+ {.text = "Delete user", .id = "delform", .url = "/confirm/deluser"}
+ };
+
+ for (size_t i = 0; i < sizeof fs / sizeof *fs; i++)
+ {
+ struct f *const f = &fs[i];
+
+ if (!(f->form = html_node_add_child(div, "form"))
+ || !(f->luser = html_node_add_child(f->form, "label"))
+ || !(f->iuser = html_node_add_child(f->form, "input"))
+ || !(f->submit = html_node_add_child(f->form, "input")))
+ {
+ fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(f->form, "action", f->url)
+ || html_node_add_attr(f->form, "form", f->id)
+ || html_node_add_attr(f->form, "method", "post")
+ || html_node_add_attr(f->luser, "for", "user")
+ || html_node_add_attr(f->iuser, "type", "text")
+ || html_node_add_attr(f->iuser, "id", "user")
+ || html_node_add_attr(f->iuser, "name", "user")
+ || html_node_add_attr(f->submit, "type", "submit")
+ || html_node_add_attr(f->submit, "value", f->text))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_set_value(f->luser, "Username:"))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int setup(const struct http_payload *const p,
+ struct http_response *const r, void *const user, sqlite3 *const db,
+ const struct auth_user *const u)
+{
+ int ret = -1, error;
+ struct html_node *root = NULL, *body, *div;
+ struct dynstr d;
+
+ dynstr_init(&d);
+
+ if (!u)
+ {
+ ret = form_unauthorized("Login required", r);
+ goto end;
+ }
+ else if (u->role <= AUTH_ROLE_BANNED)
+ {
+ ret = form_unauthorized("Banned account", r);
+ goto end;
+ }
+ else if (!(root = html_node_alloc("html")))
+ {
+ fprintf(stderr, "%s: html_node_alloc failed\n", __func__);
+ goto end;
+ }
+ else if (form_head(root))
+ {
+ fprintf(stderr, "%s: form_head failed\n", __func__);
+ goto end;
+ }
+ else if (!(body = html_node_add_child(root, "body"))
+ || !(div = html_node_add_child(body, "div")))
+ {
+ fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
+ goto end;
+ }
+ else if (setup_passwd(div, u->username))
+ {
+ fprintf(stderr, "%s: setup_passwd failed\n", __func__);
+ goto end;
+ }
+ else if (u->role >= AUTH_ROLE_MOD && setup_useract(div))
+ {
+ fprintf(stderr, "%s: setup_useract failed\n", __func__);
+ goto end;
+ }
+ else if (form_footer(body, p->resource))
+ {
+ fprintf(stderr, "%s: form_footer failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "%s", DOCTYPE_TAG))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (html_serialize(root, &d))
+ {
+ fprintf(stderr, "%s: html_serialize failed\n", __func__);
+ goto end;
+ }
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_OK,
+ .buf.rw = d.str,
+ .n = d.len,
+ .free = free
+ };
+
+ ret = 0;
+
+end:
+
+ if ((error = sqlite3_close(db)) != SQLITE_OK)
+ {
+ fprintf(stderr, "%s: sqlite3_close: %s\n", __func__,
+ sqlite3_errstr(error));
+ ret = -1;
+ }
+
+ if (ret)
+ dynstr_free(&d);
+
+ html_node_free(root);
+ return ret;
+}
+
+int ep_ucp(const struct http_payload *const p, struct http_response *const r,
+ void *const user)
+{
+ const int n = auth_validate(p, r, user, setup);
+
+ if (n < 0)
+ fprintf(stderr, "%s: auth_validate failed\n", __func__);
+ else if (n)
+ return setup(p, r, user, NULL, NULL);
+
+ return n;
+}