diff options
Diffstat (limited to 'ep_login.c')
| -rw-r--r-- | ep_login.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/ep_login.c b/ep_login.c new file mode 100644 index 0000000..0247ce4 --- /dev/null +++ b/ep_login.c @@ -0,0 +1,306 @@ +/* + * 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/>. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "endpoints.h" +#include "db.h" +#include "defs.h" +#include "form.h" +#include "op.h" +#include "utils.h" +#include <cjson/cJSON.h> +#include <dynstr.h> +#include <libweb/form.h> +#include <sodium.h> +#include <sqlite3.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct login +{ + bool valid; + char *username, *password, *key; + sqlite3 *db; +}; + +static void free_login(void *const p) +{ + int error; + struct login *const l = p; + + if (!l) + return; + else if ((error = sqlite3_close(l->db)) != SQLITE_OK) + fprintf(stderr, "%s: sqlite3_close: %s\n", __func__, + sqlite3_errstr(error)); + + free(l->username); + free(l->password); + free(l->key); + free(l); +} + +static int row(sqlite3_stmt *const stmt, + const struct http_payload *const p, struct http_response *const r, + void *const user, void *const args) +{ + int ret = -1; + struct login *const l = args; + sqlite3 *const db = l->db; + const size_t len = strlen(l->password); + char *const hashpwd = db_str(db, stmt, "password"), + *key = db_str(db, stmt, "signkey"); + + if (!hashpwd) + { + fprintf(stderr, "%s: missing password\n", __func__); + goto end; + } + else if (!key) + { + fprintf(stderr, "%s: missing signkey\n", __func__); + goto end; + } + else if (!crypto_pwhash_str_verify(hashpwd, l->password, len)) + { + l->valid = true; + l->key = key; + key = NULL; + } + + ret = 0; + +end: + free(key); + free(hashpwd); + return ret; +} + +static int authorize(const struct login *const l, struct http_response *const r) +{ + int ret = -1; + char *const cookie = gencookie(l->username, l->key); + + if (!cookie) + { + fprintf(stderr, "%s: gencookie failed\n", __func__); + goto end; + } + + *r = (const struct http_response){.status = HTTP_STATUS_SEE_OTHER}; + + if (http_response_add_header(r, "Location", "/") + || http_response_add_header(r, "Set-Cookie", cookie)) + { + fprintf(stderr, "%s: http_response_add_header failed\n", __func__); + goto end; + } + + ret = 0; + +end: + free(cookie); + return ret; +} + +static int end(const struct http_payload *const pl, + struct http_response *const r, void *const user, void *const args) +{ + struct login *const l = args; + const int error = sqlite3_close(l->db); + + l->db = NULL; + + if (error != SQLITE_OK) + { + fprintf(stderr, "%s: sqlite3_close: %s\n", __func__, + sqlite3_errstr(error)); + return -1; + } + else if (l->valid) + { + if (authorize(l, r)) + { + fprintf(stderr, "%s: authorize failed\n", __func__); + return -1; + } + } + else if (form_unauthorized("Invalid username or password", r)) + { + fprintf(stderr, "%s: form_unauthorized failed\n", __func__); + return -1; + } + + free_login(l); + return 0; +} + +static int query_creds(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + struct login *const l = args; + struct dynstr d; + + dynstr_init(&d); + + if (dynstr_append(&d, + "SELECT signkey, password FROM users WHERE name = '%s'", l->username)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + const struct op_cfg op = + { + .error = free_login, + .end = end, + .row = row, + .args = l + }; + + if (!op_run(l->db, d.str, &op, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto end; + } + + ret = 0; + +end: + + if (ret) + free_login(l); + + dynstr_free(&d); + return ret; +} + +static int open_db(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + const struct cfg *const cfg = user; + struct login *const l = args; + const int error = db_open(cfg->dir, &l->db); + + if (error != SQLITE_OK) + { + if (error != SQLITE_BUSY) + { + fprintf(stderr, "%s: db_open: %s\n", __func__, + sqlite3_errstr(error)); + goto failure; + } + } + else + *r = (const struct http_response) + { + .step.payload = query_creds, + .args = l + }; + + return 0; + +failure: + free(l); + return -1; +} + +static int check_user(const char *const username) +{ + return strspn(username, USER_SYMS) != strlen(username); +} + +int ep_login(const struct http_payload *const p, struct http_response *const r, + void *const user) +{ + int error, ret = -1; + char *userdup = NULL, *passdup = NULL; + struct form *f = NULL; + struct login *l = NULL; + const char *username, *password; + + if ((error = form_alloc(p->u.post.data, &f))) + { + if ((ret = error) < 0) + fprintf(stderr, "%s: form_alloc failed\n", __func__); + else + ret = form_badreq("Invalid request", r); + + goto end; + } + else if (!(username = form_value(f, "username"))) + { + fprintf(stderr, "%s: missing username\n", __func__); + ret = form_badreq("Missing username", r); + goto end; + } + else if (!(password = form_value(f, "password"))) + { + fprintf(stderr, "%s: missing password\n", __func__); + ret = form_badreq("Missing password", r); + goto end; + } + else if (check_user(username)) + { + fprintf(stderr, "%s: check_user failed\n", __func__); + ret = form_unauthorized("Invalid username", r); + goto end; + } + else if (!(userdup = strdup(username)) + || !(passdup = strdup(password))) + { + fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); + goto end; + } + else if (!(l = malloc(sizeof *l))) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + goto end; + } + + *l = (const struct login) + { + .username = userdup, + .password = passdup, + }; + + *r = (const struct http_response) + { + .step.payload = open_db, + .args = l + }; + + ret = 0; + +end: + + if (ret) + { + free(userdup); + free(passdup); + free(l); + } + + form_free(f); + return 0; +} |
