#include #include #include #include "path.h" #include "users.h" #include "noh.h" Noh_Arena temp; #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. */ 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; noh_log(NOH_INFO, "Authorized as demo."); *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, 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_log(NOH_INFO, "User "Nsv_Fmt" does not exist.", Nsv_Arg(provided_username)); noh_return_defer(false); } // Hash the provided password. hashed = hash_password(&temp, provided_username, provided_password); // Check the hash. if (!noh_sv_eq(noh_sv_from_string(hashed), user->hash)) { noh_log(NOH_INFO, "Incorrect password."); noh_return_defer(false); } *username = provided_username; noh_log(NOH_INFO, "Authorized as "Nsv_Fmt".", Nsv_Arg(*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); if (!response) return MHD_NO; if (xml) MHD_add_response_header(response, "content-type", "application/xml"); enum MHD_Result ret = MHD_queue_response(connection, status, response); MHD_destroy_response(response); return ret; } static enum MHD_Result send_epub_file(struct MHD_Connection *connection, int file, size_t size) { struct MHD_Response *response = MHD_create_response_from_fd(size, file); if (!response) return MHD_NO; MHD_add_response_header(response, "content-type", "application/epub+zip"); enum MHD_Result ret = MHD_queue_response(connection, 200, response); MHD_destroy_response(response); return ret; } #include "id_store.c" #include "library.c" #include "opds_handler.c" #include "get_handler.c" bool is_slash(const char c) { return c == '/'; } static enum MHD_Result request_handler( void *cls, struct MHD_Connection *connection, const char *_url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { (void)con_cls; (void)upload_data; (void)upload_data_size; Settings settings = *(Settings*)cls; noh_log(NOH_INFO, "Request: %s %s %s", version, method, _url); SV 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); SV url = sv(_url); noh_sv_trim_left(&url, is_slash); 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); return send_response(connection, 404, sv(""), false); } void show_usage(char *program_name, char *error) { noh_log(NOH_INFO, "Usage:"); noh_log(NOH_INFO, "%s port library_path library_name", program_name); noh_log(NOH_INFO, " port: The port to listen to, between 1000 and 65535."); noh_log(NOH_INFO, " library_path: The path where the epub libraries are located."); noh_log(NOH_INFO, " The root of this path should have a users.txt file with the usernames and passwords of valid users."); noh_log(NOH_INFO, " For each user, ther must be a subdirectory where this user's library is located."); noh_log(NOH_INFO, " library_name: The name of the library shown as the title of each user's root folder."); noh_log(NOH_INFO, ""); noh_log(NOH_ERROR, "%s", error); exit(1); } // SIGTERM handler. static volatile sig_atomic_t got_term = 0; void on_term(int sig) { (void)sig; got_term = 1; } int main(int argc, char **argv) { // Parse input parameters. char *program_name = noh_shift_args(&argc, &argv); if (argc < 1) show_usage(program_name, "Port not provided."); char *port_str = noh_shift_args(&argc, &argv); int port = atoi(port_str); if (port < 1000 || port > 65535) show_usage(program_name, "Invalid port."); if (argc < 1) show_usage(program_name, "Library path not provided."); char *library_path = noh_shift_args(&argc, &argv); if (argc < 1) show_usage(program_name, "Library name not provided."); char *library_name = noh_shift_args(&argc, &argv); Settings settings = { .base_path = sv(library_path), .library_name = sv(library_name), }; temp = noh_arena_init(1 MB); struct MHD_Daemon *server; // Start the HTTP server server = MHD_start_daemon( MHD_USE_INTERNAL_POLLING_THREAD, port, NULL, NULL, &request_handler, &settings, MHD_OPTION_END); if (!server) { noh_log(NOH_ERROR, "Failed to start server."); return 1; } noh_log(NOH_INFO, "Server is running on http://0.0.0.0:%d", port); // Keep the server running, but handle SIGTERM. struct sigaction sa = {0}; sa.sa_handler = on_term; sigaction(SIGTERM, &sa, NULL); getchar(); noh_log(NOH_INFO, "Shutting down.", port); // Stop the server MHD_stop_daemon(server); return 0; }