#include #include #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; } }