272 lines
9.3 KiB
C
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;
|
|
}
|
|
}
|
|
|