Files
FolderOpdsServer/src/userctl.c
ThoNohT 9b75e87149 Implement authorization
Read username from user config, check existence of user and then check password.
2026-06-12 08:44:20 +02:00

272 lines
9.3 KiB
C

#include <stdio.h>
#include <dirent.h>
#define NOH_IMPLEMENTATION
#include "noh.h"
Noh_Arena temp;
#include "path.c"
#include "users.c"
#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:");
noh_log(NOH_INFO, "%s command [args]", program_name);
noh_log(NOH_INFO, "");
noh_log(NOH_INFO, "Commands:");
noh_log(NOH_INFO, "%s register library_path username password", program_name);
noh_log(NOH_INFO, " library_path: The path to the library where the users file and user subfolders are located.");
noh_log(NOH_INFO, " username: The username of the user to register.");
noh_log(NOH_INFO, " password: The password of the user to register.");
noh_log(NOH_INFO, "Registers a new user, or changes their password if they already exist.");
noh_log(NOH_INFO, "");
noh_log(NOH_INFO, "%s delete library_path username", program_name);
noh_log(NOH_INFO, " library_path: The path to the library where the users file and user subfolders are located.");
noh_log(NOH_INFO, " username: The username of the user to delete.");
noh_log(NOH_INFO, "Deletes a user, optionally also deleting their library folder.");
noh_log(NOH_INFO, "");
noh_log(NOH_INFO, "%s list library_path", program_name);
noh_log(NOH_INFO, " library_path: The path to the library where the users file and user subfolders are located.");
noh_log(NOH_INFO, "Lists all users defined in the provided library path.");
noh_log(NOH_INFO, "");
noh_log(NOH_ERROR, "%s", error);
exit(1);
}
bool remove_directory(char *path) {
noh_arena_save(&temp);
bool result = true;
DIR *dir = opendir(path);
if (!dir) return false;
struct dirent *p;
while ((p = readdir(dir))) {
// Ignore . and .. file entries.
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue;
char *sub_path = noh_arena_sprintf(&temp, "%s/%s", path, p->d_name);
// Delete files and recurse subfolders.
struct stat statbuf;
if (!stat(sub_path, &statbuf)) {
if (S_ISDIR(statbuf.st_mode))
result &= remove_directory(sub_path);
else
result &= unlink(sub_path) == 0;
}
}
closedir(dir);
if (result) result = rmdir(path);
noh_arena_reset(&temp);
return result;
}
/// Prompts a user with a question and returns their response.
bool prompt_user(char *question) {
noh_arena_save(&temp);
noh_log(NOH_INFO, noh_arena_sprintf(&temp, "%s [yn]", question));
noh_arena_reset(&temp);
char response;
if (!scanf(" %c", &response)) {
noh_log(NOH_ERROR, "Error reading response from stdin.");
return false;
}
return response == 'y' || response == 'Y';
}
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};
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.
hashed = hash_password(username, password);
// Find the user in the map of users.
for (size_t i = 0; i < users.count; i++) {
User *user = &users.elems[i];
if (!noh_sv_eq_ci(user->username, username)) continue;
// The user exists. Update password after confirmation.
if (!prompt_user("User already exists. Update password?")) {
noh_log(NOH_INFO, "Ok, not changing anything.");
noh_return_defer(true);
}
noh_log(NOH_INFO, "Updating user password.");
user->hash = noh_sv_from_string(hashed);
if (!write_users_file(users_file_path.elems, &users, NULL)) noh_return_defer(false);
// Create user folder if needed.
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);
noh_return_defer(true);
}
// User was not found, add it to the list.
noh_log(NOH_INFO, "Adding a new user.");
noh_da_append(&users, ((User){ .username = username, .hash = noh_sv_from_string(hashed) }));
if (!write_users_file(users_file_path.elems, &users, NULL)) noh_return_defer(false);
// Create user folder if needed.
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};
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;
for (size_t i = 0; i < users.count; i++) {
User *user = &users.elems[i];
if (!noh_sv_eq_ci(user->username, username)) continue;
user_to_delete = user;
break;
}
// User does not exist, don't change anything.
if (!user_to_delete) {
noh_log(NOH_INFO, "User does not exist.");
noh_return_defer(true);
}
// The user exists. Prompt to confirm deletion.
if (!prompt_user("User found, delete the user?")) {
noh_log(NOH_INFO, "Ok, not changing anything.");
noh_return_defer(true);
}
noh_log(NOH_INFO, "Deleting user.");
if (!write_users_file(users_file_path.elems, &users, user_to_delete)) noh_return_defer(false);
// Prompt to delete the user's library folder.
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);
if (prompt_user("Library folder exists. Delete it?")) {
noh_log(NOH_INFO, "Deleting library folder.");
return remove_directory(user_folder_path.elems);
}
defer:
noh_string_free(&user_folder_path);
noh_string_free(&users_file_path);
noh_da_free(&users);
noh_string_free(&users_file);
return result;
}
bool list_users(Noh_String_View library_path) {
// 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.
Users users = {0};
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;
}
noh_arena_save(&temp);
noh_log(NOH_INFO, "Users:");
for (size_t i = 0; i < users.count; i++) {
User *user = &users.elems[i];
noh_log(NOH_INFO, noh_arena_sprintf(&temp, "- "Nsv_Fmt, Nsv_Arg(user->username)));
}
noh_arena_reset(&temp);
noh_string_free(&users_file_path);
noh_da_free(&users);
noh_string_free(&users_file);
return true;
}
int main(int argc, char **argv) {
temp = noh_arena_init(64 KB);
// Parse input parameters.
char *program_name = noh_shift_args(&argc, &argv);
if (argc < 1) show_usage(program_name, "Command not provided");
Noh_String_View command = noh_sv_from_cstr(noh_shift_args(&argc, &argv));
if (argc < 1) show_usage(program_name, "Library path not provided.");
Noh_String_View library_path = noh_sv_from_cstr(noh_shift_args(&argc, &argv));
if (noh_sv_eq_ci(command, noh_sv_from_cstr("register"))) {
if (argc < 1) show_usage(program_name, "Username not provided.");
Noh_String_View username = noh_sv_from_cstr(noh_shift_args(&argc, &argv));
if (argc < 1) show_usage(program_name, "Password not provided.");
Noh_String_View password = noh_sv_from_cstr(noh_shift_args(&argc, &argv));
return register_user(library_path, username, password) ? 0 : 1;
} else if (noh_sv_eq_ci(command, sv("delete"))) {
if (argc < 1) show_usage(program_name, "Username not provided.");
Noh_String_View username = noh_sv_from_cstr(noh_shift_args(&argc, &argv));
return delete_user(library_path, username) ? 0 : 1;
} else if (noh_sv_eq_ci(command, sv("list"))) {
return list_users(library_path) ? 0 : 1;
} else {
noh_arena_save(&temp);
show_usage(program_name, noh_arena_sprintf(&temp, "Unknown command: "Nsv_Fmt, Nsv_Arg(command)));
noh_arena_reset(&temp);
return 1;
}
}