From 9b75e8714988c48d1d9ff10ac4bf7f5e22cf89ac Mon Sep 17 00:00:00 2001 From: ThoNohT Date: Thu, 11 Jun 2026 16:26:55 +0200 Subject: [PATCH] Implement authorization Read username from user config, check existence of user and then check password. --- src/library.c | 2 - src/path.c | 2 +- src/server.c | 94 ++++++++++++++++++++++++++---------- src/userctl.c | 129 ++++++++++++-------------------------------------- src/users.c | 87 ++++++++++++++++++++++++++++++++++ 5 files changed, 189 insertions(+), 125 deletions(-) create mode 100644 src/users.c diff --git a/src/library.c b/src/library.c index e40177d..23b3d6b 100644 --- a/src/library.c +++ b/src/library.c @@ -1,5 +1,3 @@ -#include "path.c" - typedef struct { bool is_dir; SV name; diff --git a/src/path.c b/src/path.c index 9b06186..8075f50 100644 --- a/src/path.c +++ b/src/path.c @@ -11,7 +11,7 @@ static Noh_String build_path_(bool url, Noh_String_View *first, ...) { Noh_String_View elem = *elem_; while (elem.count > 0) { Noh_String_View component = noh_sv_chop_by_delim(&elem, '/'); - if (noh_sv_eq(component, sv(".."))) continue; + if (noh_sv_eq(component, noh_sv_from_cstr(".."))) continue; if (component.count > 0) { if (start) start = false; else noh_string_append_cstr(&result, "/"); diff --git a/src/server.c b/src/server.c index 6b3f336..c0d952d 100644 --- a/src/server.c +++ b/src/server.c @@ -5,25 +5,82 @@ #define NOH_IMPLEMENTATION #include "noh.h" -#define NO_AUTH +Noh_Arena temp; + +#include "path.c" +#include "users.c" + +#define SV Noh_String_View +#define sv noh_sv_from_cstr + /* To disable Basic auth for testing: #define NO_AUTH This will ignore any auth headers and always log in with username demo. */ -#define SV Noh_String_View -#define sv noh_sv_from_cstr - -Noh_Arena temp; - -#include "id_store.c" -#include "library.c" - typedef struct { SV base_path; SV library_name; } Settings; +// Authorizes the username from the Basic authorization info in the provided connection. +// The username data is guaranteed to live as long as the connection lives. +static bool authorize_user(struct MHD_Connection *connection, SV *username, Settings *settings) { +#ifdef NO_AUTH + (void)connection; + + *username = sv("demo"); + return false; +#else + bool result = true; + + struct MHD_BasicAuthInfo *auth = MHD_basic_auth_get_username_password3(connection); + if (!auth) return false; + + SV provided_username = { .elems = auth->username, .count = auth->username_len }; + SV provided_password = { .elems = auth->password, .count = auth->password_len }; + + // Read the users file. + noh_arena_save(&temp); + Noh_String_View file_name = noh_sv_from_cstr("users.conf"); + Noh_String users_file_path = build_path(false, &settings->base_path, &file_name); + noh_string_append_null(&users_file_path); + + Noh_String hashed = {0}; + Users users = {0}; + Noh_String users_file = {0}; + if (!read_users_file(users_file_path.elems, &users_file, &users)) noh_return_defer(false); + + // Find the user in the users list. + bool found_user = false; + User *user = NULL; + for (size_t i = 0; i < users.count && !found_user; i++) { + user = &users.elems[i]; + + if (noh_sv_eq(provided_username, user->username)) found_user = true; + } + + if (!found_user) noh_return_defer(false); + + // Hash the provided password. + hashed = hash_password(provided_username, provided_password); + + // Check the hash. + if (!noh_sv_eq(noh_sv_from_string(hashed), user->hash)) noh_return_defer(false); + + *username = provided_username; + +defer: + noh_string_free(&hashed); + noh_da_free(&users); + noh_string_free(&users_file_path); + noh_string_free(&users_file); + noh_arena_reset(&temp); + + return result; +#endif +} + static enum MHD_Result send_response(struct MHD_Connection *connection, int status, SV message, bool xml) { struct MHD_Response *response = MHD_create_response_from_buffer( message.count, (void*)message.elems, MHD_RESPMEM_MUST_COPY); @@ -47,6 +104,8 @@ static enum MHD_Result send_epub_file(struct MHD_Connection *connection, int fil return ret; } +#include "id_store.c" +#include "library.c" #include "opds_handler.c" #include "get_handler.c" @@ -65,20 +124,7 @@ static enum MHD_Result request_handler( noh_log(NOH_INFO, "Request: %s %s %s", version, method, _url); SV username; -#ifdef NO_AUTH - username = sv("demo"); -#else - struct MHD_BasicAuthInfo *auth = MHD_basic_auth_get_username_password3(connection); - if (!auth) return send_response(connection, 401, sv(""), false); - - username = (SV){ .elems = auth->username, .count = auth->username_len }; - SV password = { .elems = auth->password, .count = auth->password_len }; - - // TODO: Read username from user config. - if (!noh_sv_eq(password, sv("jaja"))) return send_response(connection, 401, sv(""), false); -#endif - - noh_log(NOH_INFO, "Authenticated as "Nsv_Fmt, Nsv_Arg(username)); + if (!authorize_user(connection, &username, &settings)) return send_response(connection, 401, sv(""), false); if (strcmp(method, "GET") != 0) return send_response(connection, 405, sv(""), false); @@ -87,7 +133,7 @@ static enum MHD_Result request_handler( SV controller = noh_sv_chop_by_delim(&url, '/'); if (noh_sv_eq_ci(controller, sv("opds"))) return handle_opds(connection, url, settings, username); - if (noh_sv_eq_ci(controller, sv("get"))) return handle_get(connection, url, settings, username); + if ( noh_sv_eq_ci(controller, sv("get"))) return handle_get(connection, url, settings, username); return send_response(connection, 404, sv(""), false); } diff --git a/src/userctl.c b/src/userctl.c index 3933c6f..73ba25c 100644 --- a/src/userctl.c +++ b/src/userctl.c @@ -1,15 +1,15 @@ #include -#include #include #define NOH_IMPLEMENTATION #include "noh.h" -#define SV Noh_String_View -#define sv noh_sv_from_cstr +Noh_Arena temp; #include "path.c" +#include "users.c" -Noh_Arena temp; +#define SV Noh_String_View +#define sv noh_sv_from_cstr static void show_usage(char *program_name, char *error) { noh_log(NOH_INFO, "Usage:"); @@ -35,42 +35,6 @@ static void show_usage(char *program_name, char *error) { exit(1); } -Noh_String hash_password(Noh_String_View username, Noh_String_View password) { - // Build the sha input string. - Noh_String input = {0}; - noh_string_append_sv(&input, username); - noh_string_append_cstr(&input, "<><>"); - noh_string_append_sv(&input, password); - - // Calculate the hash. - SHA256_CTX ctx = {0}; - BYTE hash[32]; - sha256_init(&ctx); - sha256_update(&ctx, (BYTE*)input.elems, input.count); - sha256_final(&ctx, hash); - - // Build the result string. - noh_arena_save(&temp); - Noh_String result = {0}; - for (int i = 0; i < 32; i++) { - noh_string_append_cstr(&result, noh_arena_sprintf(&temp, "%02x", hash[i])); - } - noh_arena_reset(&temp); - - return result; -} - -typedef struct { - Noh_String_View username; - Noh_String_View hash; -} User; - -typedef struct { - User *elems; - size_t count; - size_t capacity; -} Users; - bool remove_directory(char *path) { noh_arena_save(&temp); bool result = true; @@ -103,55 +67,6 @@ bool remove_directory(char *path) { return result; } -bool read_users_file(char *file_path, Users *users) { - Noh_String users_file = {0}; - - noh_log(NOH_INFO, "Reading users file: %s", file_path); - if (!noh_file_exists(file_path)) return true; - if (!noh_string_read_file(&users_file, file_path)) return false; - Noh_String_View users_file_sv = noh_sv_from_string(users_file); - - Noh_String_View line = {0}; - for (size_t i = 0; users_file_sv.count > 0; i++) { - line = noh_sv_chop_line(&users_file_sv); - - // Ignore empty lines. - noh_sv_trim_space(&line); - if (line.count == 0) continue; - - Noh_String_View un = noh_sv_chop_by_delim(&line, ':'); - - if (line.count == 0) { - noh_log(NOH_ERROR, "%s %zu: Line not in username:hash format.", file_path, i); - noh_string_free(&users_file); - return false; - } - - noh_da_append(users, ((User){ .username = un, .hash = line })); - } - - return true; -} - -bool write_users_file(char *file_path, Users *users, User *user_to_skip) { - Noh_String users_file = {0}; - - for (size_t i = 0; i < users->count; i++) { - User *u = &users->elems[i]; - if (u == user_to_skip) continue; - - noh_string_append_sv(&users_file, u->username); - noh_string_append_cstr(&users_file, ":"); - noh_string_append_sv(&users_file, u->hash); - noh_string_append_cstr(&users_file, "\n"); - } - - bool result = noh_string_write_file(&users_file, file_path); - noh_string_free(&users_file); - return result; -} - - /// Prompts a user with a question and returns their response. bool prompt_user(char *question) { noh_arena_save(&temp); @@ -168,18 +83,22 @@ bool prompt_user(char *question) { } bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_String_View password) { + bool result = true; + // Read the file. Noh_String_View file_name = noh_sv_from_cstr("users.conf"); Noh_String users_file_path = build_path(false, &library_path, &file_name); noh_string_append_null(&users_file_path); // Build users map. + Noh_String hashed = {0}; Users users = {0}; - if (!read_users_file(users_file_path.elems, &users)) return false; - bool result = true; + Noh_String users_file = {0}; + Noh_String user_folder_path = {0}; + if (!read_users_file(users_file_path.elems, &users_file, &users)) noh_return_defer(false); // Hash the password. - Noh_String hashed = hash_password(username, password); + hashed = hash_password(username, password); // Find the user in the map of users. for (size_t i = 0; i < users.count; i++) { @@ -197,7 +116,7 @@ bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_S if (!write_users_file(users_file_path.elems, &users, NULL)) noh_return_defer(false); // Create user folder if needed. - Noh_String user_folder_path = build_path(false, &library_path, &username); + user_folder_path = build_path(false, &library_path, &username); noh_string_append_null(&user_folder_path); if (!noh_mkdir_if_needed(user_folder_path.elems)) noh_return_defer(false); @@ -210,25 +129,32 @@ bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_S if (!write_users_file(users_file_path.elems, &users, NULL)) noh_return_defer(false); // Create user folder if needed. - Noh_String user_folder_path = build_path(false, &library_path, &username); + user_folder_path = build_path(false, &library_path, &username); noh_string_append_null(&user_folder_path); if (!noh_mkdir_if_needed(user_folder_path.elems)) noh_return_defer(false); defer: + noh_string_free(&users_file_path); + noh_string_free(&user_folder_path); noh_da_free(&users); + noh_string_free(&users_file); + noh_string_free(&hashed); return result; } bool delete_user(Noh_String_View library_path, Noh_String_View username) { + bool result = true; + // Read the file. Noh_String_View file_name = noh_sv_from_cstr("users.conf"); Noh_String users_file_path = build_path(false, &library_path, &file_name); noh_string_append_null(&users_file_path); // Build users map. + Noh_String user_folder_path = {0}; Users users = {0}; - if (!read_users_file(users_file_path.elems, &users)) return false; - bool result = true; + Noh_String users_file = {0}; + if (!read_users_file(users_file_path.elems, &users_file, &users)) noh_return_defer(false); // Check if the user exists. User *user_to_delete = NULL; @@ -256,7 +182,7 @@ bool delete_user(Noh_String_View library_path, Noh_String_View username) { if (!write_users_file(users_file_path.elems, &users, user_to_delete)) noh_return_defer(false); // Prompt to delete the user's library folder. - Noh_String user_folder_path = build_path(false, &library_path, &username); + user_folder_path = build_path(false, &library_path, &username); noh_string_append_null(&user_folder_path); if (!noh_file_exists(user_folder_path.elems)) noh_return_defer(true); @@ -266,7 +192,10 @@ bool delete_user(Noh_String_View library_path, Noh_String_View username) { } defer: + noh_string_free(&user_folder_path); + noh_string_free(&users_file_path); noh_da_free(&users); + noh_string_free(&users_file); return result; } @@ -278,11 +207,13 @@ bool list_users(Noh_String_View library_path) { // Build users map. Users users = {0}; - if (!read_users_file(users_file_path.elems, &users)) return false; + Noh_String users_file = {0}; + if (!read_users_file(users_file_path.elems, &users_file, &users)) return false; if (users.count == 0) { noh_log(NOH_INFO, "No users registered."); noh_da_free(&users); + noh_string_free(&users_file); return true; } @@ -294,7 +225,9 @@ bool list_users(Noh_String_View library_path) { } noh_arena_reset(&temp); + noh_string_free(&users_file_path); noh_da_free(&users); + noh_string_free(&users_file); return true; } diff --git a/src/users.c b/src/users.c new file mode 100644 index 0000000..5a05e92 --- /dev/null +++ b/src/users.c @@ -0,0 +1,87 @@ +#include + +typedef struct { + Noh_String_View username; + Noh_String_View hash; +} User; + +typedef struct { + User *elems; + size_t count; + size_t capacity; +} Users; + +Noh_String hash_password(Noh_String_View username, Noh_String_View password) { + // Build the sha input string. + Noh_String input = {0}; + noh_string_append_sv(&input, username); + noh_string_append_cstr(&input, "<><>"); + noh_string_append_sv(&input, password); + + // Calculate the hash. + SHA256_CTX ctx = {0}; + BYTE hash[32]; + sha256_init(&ctx); + sha256_update(&ctx, (BYTE*)input.elems, input.count); + sha256_final(&ctx, hash); + + // Build the result string. + noh_arena_save(&temp); + Noh_String result = {0}; + for (int i = 0; i < 32; i++) { + noh_string_append_cstr(&result, noh_arena_sprintf(&temp, "%02x", hash[i])); + } + noh_arena_reset(&temp); + + return result; +} + +// Reads the users file into users_file. Free this string when the data is no longer needed. +// Note that the string needs to be initialized and empty before calling this function. +// The users struct will contain string views referencing the data in this string. +bool read_users_file(char *file_path, Noh_String *users_file, Users *users) { + noh_log(NOH_INFO, "Reading users file: %s", file_path); + if (!noh_file_exists(file_path)) return true; + if (!noh_string_read_file(users_file, file_path)) return false; + Noh_String_View users_file_sv = noh_sv_from_string(*users_file); + + Noh_String_View line = {0}; + for (size_t i = 0; users_file_sv.count > 0; i++) { + line = noh_sv_chop_line(&users_file_sv); + + // Ignore empty lines. + noh_sv_trim_space(&line); + if (line.count == 0) continue; + + Noh_String_View un = noh_sv_chop_by_delim(&line, ':'); + + if (line.count == 0) { + noh_log(NOH_ERROR, "%s %zu: Line not in username:hash format.", file_path, i); + noh_string_free(users_file); + return false; + } + + noh_da_append(users, ((User){ .username = un, .hash = line })); + } + + return true; +} + +bool write_users_file(char *file_path, Users *users, User *user_to_skip) { + Noh_String users_file = {0}; + + for (size_t i = 0; i < users->count; i++) { + User *u = &users->elems[i]; + if (u == user_to_skip) continue; + + noh_string_append_sv(&users_file, u->username); + noh_string_append_cstr(&users_file, ":"); + noh_string_append_sv(&users_file, u->hash); + noh_string_append_cstr(&users_file, "\n"); + } + + bool result = noh_string_write_file(&users_file, file_path); + noh_string_free(&users_file); + return result; +} +