diff --git a/src/library.c b/src/library.c index ca718a9..e40177d 100644 --- a/src/library.c +++ b/src/library.c @@ -1,3 +1,5 @@ +#include "path.c" + typedef struct { bool is_dir; SV name; @@ -41,34 +43,6 @@ static void set_modified_time(Library_Entry *entry) { } } -#define build_path(url, first, ...) build_path_((url), (first), __VA_ARGS__, NULL) -static Noh_String build_path_(bool url, SV *first, ...) { - va_list args; - SV *elem_ = first; - va_start(args, first); - bool start = true; - Noh_String result = {0}; - if (url) noh_string_append_cstr(&result, "/"); - - while (elem_ != NULL) { - SV elem = *elem_; - while (elem.count > 0) { - SV component = noh_sv_chop_by_delim(&elem, '/'); - if (noh_sv_eq(component, sv(".."))) continue; - - if (component.count > 0) { - if (start) start = false; else noh_string_append_cstr(&result, "/"); - noh_string_append_sv(&result, component); - } - } - - elem_ = va_arg(args, SV *); - } - va_end(args); - - return result; -} - /** * Creates a Library entry from a dirent structure. */ diff --git a/src/path.c b/src/path.c new file mode 100644 index 0000000..5608c40 --- /dev/null +++ b/src/path.c @@ -0,0 +1,27 @@ +#define build_path(url, first, ...) build_path_((url), (first), __VA_ARGS__, NULL) +static Noh_String build_path_(bool url, Noh_String_View *first, ...) { + va_list args; + Noh_String_View *elem_ = first; + va_start(args, first); + bool start = true; + Noh_String result = {0}; + if (url) noh_string_append_cstr(&result, "/"); + + while (elem_ != NULL) { + 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 (component.count > 0) { + if (start) start = false; else noh_string_append_cstr(&result, "/"); + noh_string_append_sv(&result, component); + } + } + + elem_ = va_arg(args, Noh_String_View *); + } + va_end(args); + + return result; +} diff --git a/src/userctl.c b/src/userctl.c index 196f054..f4e1361 100644 --- a/src/userctl.c +++ b/src/userctl.c @@ -3,11 +3,26 @@ #define NOH_IMPLEMENTATION #include "noh.h" +#define SV Noh_String_View +#define sv noh_sv_from_cstr + +#include "path.c" + static void show_usage(char *program_name, char *error) { noh_log(NOH_INFO, "Usage:"); - noh_log(NOH_INFO, "%s username password", program_name); + 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_ERROR, "%s", error); exit(1); @@ -38,21 +53,133 @@ Noh_String hash_password(Noh_Arena *arena, Noh_String_View username, Noh_String_ 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 read_users_file(char *file_path, Users *users) { + Noh_String users_file = {0}; + + 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); + + for (size_t i = 0; users_file_sv.count > 0; i++) { + Noh_String_View line = noh_sv_chop_line(&users_file_sv); + + 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); + return false; + } + + noh_da_append(users, ((User){ .username = un, .hash = line })); + } + + return true; +} + +bool write_users_file(char *file_path, Users *users) { + Noh_String users_file = {0}; + + for (size_t i = 0; i < users->count; i++) { + User *u = &users->elems[i]; + 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"); + } + + return noh_string_write_file(&users_file, file_path); +} + + +bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_String_View password) { + // Read the file. + Noh_String_View file_name = noh_sv_from_cstr("users.txt"); + 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}; + if (!read_users_file(users_file_path.elems, &users)) return false; + bool result = true; + + // Hash the password. + Noh_Arena arena = noh_arena_init(1 KB); + Noh_String hashed = hash_password(&arena, 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. + noh_log(NOH_INFO, "User already exists. Update password? [yn]"); + + char response; + if (!scanf(" %c", &response)) { + noh_log(NOH_ERROR, "Error reading response from stdin."); + noh_return_defer(false); + } + + if (response != 'y') { + 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)) 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)) noh_return_defer(false); + +defer: + noh_da_free(&users); + return result; +} + int main(int argc, char **argv) { // Parse input parameters. char *program_name = noh_shift_args(&argc, &argv); + Noh_Arena arena = noh_arena_init(64 KB); + + 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 (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)); + if (noh_sv_eq_ci(command, noh_sv_from_cstr("register"))) { + if (argc < 1) show_usage(program_name, "Password not provided."); + Noh_String_View password = noh_sv_from_cstr(noh_shift_args(&argc, &argv)); - Noh_Arena arena = noh_arena_init(64 KB); - Noh_String result = hash_password(&arena, username, password); - noh_string_append_null(&result); - printf("%s\n", result.elems); + if (!register_user(library_path, username, password)) return 1; + return 0; - return 0; + } else if (noh_sv_eq_ci(command, sv("delete"))) { + noh_assert(false); + + } else { + show_usage(program_name, noh_arena_sprintf(&arena, "Unknown command: "Nsv_Fmt, Nsv_Arg(command))); + } }