Files
FolderOpdsServer/src/server.c
2026-06-17 17:34:07 +02:00

219 lines
6.8 KiB
C

#include <microhttpd.h>
#include <uuid/uuid.h>
#include <dirent.h>
#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;
}