AmendHub

jcs

/

subtext

/

amendments

/

123

sysop: Add user management

Also wire up new/signup login to direct there upon login, adding a
setting to the global config to allow it.

jcs made amendment 123 5 months ago
--- board.c Fri Jun 3 21:52:08 2022 +++ board.c Sat Jun 4 15:52:07 2022 @@ -162,7 +162,7 @@ board_show(struct session *s, short id) if (find_post_ids) { nall_post_ids = board_find_post_ids(board, &npost_ids, &post_ids, page * POSTS_PER_PAGE, POSTS_PER_PAGE); - /* ceil(npost_ids / POSTS_PER_PAGE) */ + /* ceil(nall_post_ids / POSTS_PER_PAGE) */ pages = (nall_post_ids + POSTS_PER_PAGE - 1) / POSTS_PER_PAGE; if (page >= pages) --- db.c Wed Jun 1 13:08:50 2022 +++ db.c Tue Jun 7 17:00:56 2022 @@ -53,6 +53,9 @@ struct struct_field config_fields[] = { { "Timezone (Abbrev)", CONFIG_TYPE_STRING, offsetof(struct config, timezone), 1, member_size(struct config, timezone) }, + { "Allow Open Signup", CONFIG_TYPE_BOOLEAN, + offsetof(struct config, open_signup), + 0, 0 }, }; size_t nconfig_fields = nitems(config_fields); @@ -244,12 +247,14 @@ db_migrate(struct db *tdb, short is_new) user = xmalloczero(sizeof(struct user)); strncpy(user->username, "sysop", sizeof(user->username)); user->created_at = Time; + user->is_enabled = DB_TRUE; user_set_password(user, "p4ssw0rd"); user->is_sysop = DB_TRUE; /* user_save assumes db is already set */ olddb = db; db = tdb; user_save(user); + user_cache_usernames(); db = olddb; } else { if (bile_read(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1) @@ -258,12 +263,49 @@ db_migrate(struct db *tdb, short is_new) if (ver == DB_CUR_VERS) return 0; - if (ask("Migrate this database from version %c to %d to open it?", + if (ask("Migrate this database from version %d to %d to open it?", ver, DB_CUR_VERS) != ASK_YES) return -1; } /* per-version migrations */ + while (ver < DB_CUR_VERS) { + switch (ver) { + case 1: { + /* 1->2, added user is_enabled field */ + struct user user; + struct bile_object *o; + size_t nuser; + + nuser = 0; + while ((o = bile_get_nth_of_type(tdb->bile, nuser, DB_USER_RTYPE))) { + memset(&user, 0, sizeof(user)); + bile_read(tdb->bile, DB_USER_RTYPE, o->id, (char *)&user, + sizeof(user)); + user.is_enabled = DB_TRUE; + bile_write(tdb->bile, DB_USER_RTYPE, o->id, (char *)&user, + sizeof(user)); + free(o); + nuser++; + } + break; + } + case 2: { + /* 2->3, added config open_signup field */ + struct config new_config = { 0 }; + + bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, + sizeof(new_config)); + new_config.open_signup = 0; + + bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, + sizeof(new_config)); + break; + } + } + + ver++; + } /* store new version */ ver = DB_CUR_VERS; --- db.h Mon May 23 14:31:38 2022 +++ db.h Tue Jun 7 16:58:05 2022 @@ -23,7 +23,7 @@ #define DB_TYPE 'STDB' -#define DB_CUR_VERS 1 +#define DB_CUR_VERS 3 #define DB_TRUE 0x100 #define DB_FALSE 0x000 @@ -65,6 +65,7 @@ struct config { char modem_init[96]; short max_idle_minutes; char timezone[8]; + short open_signup; }; extern struct struct_field config_fields[]; --- session.c Sun Jun 5 09:21:28 2022 +++ session.c Tue Jun 7 15:41:17 2022 @@ -82,6 +82,7 @@ session_run(struct uthread *uthread, void *arg) struct tm *date_tm; unsigned short c; bool done = false; + short auth; /* until we negotiate otherwise */ s->terminal_columns = DEFAULT_TERMINAL_COLUMNS; @@ -98,7 +99,8 @@ session_run(struct uthread *uthread, void *arg) } session_flush(s); - if (session_login(s) != AUTH_USER_OK) { + auth = session_login(s); + if (auth == AUTH_USER_FAILED) { session_close(s); return; } @@ -147,7 +149,12 @@ main_menu: session_flush(s); get_another_char: - c = session_input_char(s); + if (auth == AUTH_USER_SIGNUP) { + c = 's'; + auth = AUTH_USER_GUEST; + } else + c = session_input_char(s); + if (s->ending) break; if (c == '\r' || c == 0 || c > 255) @@ -719,12 +726,15 @@ session_login(struct session *s) if (strcmp(username, GUEST_USERNAME) == 0) { session_log(s, "Successful guest login in as %s", username); free(username); - return AUTH_USER_OK; + return AUTH_USER_GUEST; } - if (strcmp(username, "signup") == 0 || - strcmp(username, "new") == 0) { - /* TODO: check for open signups */ + if ((strcmp(username, "signup") == 0 || + strcmp(username, "new") == 0) && db->config.open_signup) { + session_log(s, "Successful guest signup login in as %s", + username); + free(username); + return AUTH_USER_SIGNUP; } else { user = user_find_by_username(username); } @@ -752,9 +762,13 @@ session_login(struct session *s) goto login_bail; if (user) { - if (user_authenticate(user, password) == AUTH_USER_OK) - s->user = user; - else + if (user_authenticate(user, password) == AUTH_USER_OK) { + if (user->is_enabled) + s->user = user; + else + session_log(s, "Successful password login for %s but " + "account is disabled", user->username); + } else session_log(s, "Failed password login for %s", user->username); } else { --- signup.c Fri Jun 3 22:56:12 2022 +++ signup.c Tue Jun 7 14:16:47 2022 @@ -103,9 +103,11 @@ signup(struct session *s) user = xmalloczero(sizeof(struct user)); strncpy(user->username, username, sizeof(user->username)); user->created_at = Time; + user->is_enabled = 1; user_set_password(user, password); user_save(user); - + user_cache_usernames(); + session_log(s, "New user account created for %s", user->username); signup_done: --- sysop.c Wed Jun 1 13:10:33 2022 +++ sysop.c Tue Jun 7 14:23:43 2022 @@ -17,11 +17,19 @@ #include <string.h> #include "subtext.h" +#include "ansi.h" #include "board.h" #include "session.h" #include "sysop.h" +#define USERS_PER_PAGE 10 + void sysop_edit_boards(struct session *s); +void sysop_edit_users(struct session *s); +size_t sysop_find_user_ids(size_t nall_user_ids, + unsigned long *all_user_ids, unsigned long **user_ids, size_t offset, + size_t limit); +void sysop_edit_user(struct session *s, unsigned long id); void sysop_menu(struct session *s) @@ -42,7 +50,7 @@ sysop_menu(struct session *s) session_log(s, "Entered sysop menu"); - while (!done) { + while (!done && !s->ending) { c = session_menu(s, "Sysop Menu", "Sysop", opts, nitems(opts), show_help); show_help = false; @@ -55,6 +63,7 @@ sysop_menu(struct session *s) sysop_edit_settings(s); break; case 'u': + sysop_edit_users(s); break; case '?': show_help = true; @@ -110,7 +119,7 @@ sysop_edit_boards(struct session *s) bool done = false; bool found = false; - while (!done) { + while (!done && !s->ending) { /* * Unfortunately we have to do this every iteration because the * list of boards may change @@ -210,3 +219,220 @@ sysop_edit_boards(struct session *s) } } } + +void +sysop_edit_users(struct session *s) +{ + static struct session_menu_option opts[] = { + { '#', "#0123456789", "Edit user [#]" }, + { '<', "<", "Previous page of users" }, + { 'l', "Ll", "List users" }, + { '>', ">", "Next page of users" }, + { 'q', "QqXx", "Return to sysop menu" }, + { '?', "?", "List menu options" }, + }; + unsigned long *all_user_ids = NULL, *user_ids = NULL; + struct user user; + size_t pages, page, n, size, nall_user_ids, nuser_ids; + char c; + char time[30]; + bool show_help = false; + bool show_list = true; + bool done = false; + bool find_user_ids = true; + + nall_user_ids = bile_sorted_ids_by_type(db->bile, DB_USER_RTYPE, + &all_user_ids); + page = 0; + + while (!done && !s->ending) { + if (find_user_ids) { + nuser_ids = sysop_find_user_ids(nall_user_ids, all_user_ids, + &user_ids, page * USERS_PER_PAGE, USERS_PER_PAGE); + /* ceil(nall_user_ids / USERS_PER_PAGE) */ + pages = (nall_user_ids + USERS_PER_PAGE - 1) / USERS_PER_PAGE; + + if (page >= pages) + page = pages - 1; + + find_user_ids = false; + } + + if (show_list) { + session_printf(s, "{{B}}Users (Page %ld of %ld){{/B}}\r\n", + page + 1, pages); + session_printf(s, "%s# Username ID Active Sysop Last Login%s\r\n", + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); + session_flush(s); + + for (n = 0; n < nuser_ids; n++) { + size = bile_read(db->bile, DB_USER_RTYPE, user_ids[n], + (char *)&user, sizeof(struct user)); + if (size == 0) + continue; + if (user.last_seen_at) + strftime(time, sizeof(time), "%Y-%m-%d %H:%M", + localtime(&user.last_seen_at)); + else + snprintf(time, sizeof(time), "Never"); + session_printf(s, "%ld %-14s %-5ld %c %c %s\r\n", + n, + user.username, + user.id, + user.is_enabled ? 'Y' : ' ', + user.is_sysop ? 'Y' : ' ', + time); + session_flush(s); + } + show_list = false; + } + + c = session_menu(s, "Edit Users", "Sysop:Users", opts, + nitems(opts), show_help); + show_help = false; + +handle_opt: + switch (c) { + case 'l': + show_list = true; + break; + case '>': + case '<': + if (c == '>' && page == pages - 1) { + session_printf(s, "You are at the last page of posts\r\n"); + session_flush(s); + break; + } + if (c == '<' && page == 0) { + session_printf(s, "You are already at the first page\r\n"); + session_flush(s); + break; + } + if (c == '>') + page++; + else + page--; + find_user_ids = true; + show_list = true; + break; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + if (c >= nuser_ids) { + session_printf(s, "Invalid user\r\n"); + session_flush(s); + break; + } + sysop_edit_user(s, user_ids[c]); + break; + case '?': + show_help = true; + break; + default: + done = true; + break; + } + } + + if (all_user_ids != NULL) + free(all_user_ids); +} + +size_t +sysop_find_user_ids(size_t nall_user_ids, unsigned long *all_user_ids, + unsigned long **user_ids, size_t offset, size_t limit) +{ + size_t nuser_ids, n; + + if (nall_user_ids < offset) + return 0; + + nuser_ids = nall_user_ids - offset; + if (nuser_ids > limit) + nuser_ids = limit; + + *user_ids = xcalloc(sizeof(unsigned long), nuser_ids); + + for (n = 0; n < nuser_ids; n++) { + (*user_ids)[n] = all_user_ids[offset + n]; + } + + return nuser_ids; +} + +void +sysop_edit_user(struct session *s, unsigned long id) +{ + static struct session_menu_option opts[] = { + { 'd', "Dd", "Disable account" }, + { 'e', "Ee", "Enable account" }, + { 'p', "Pp", "Change password" }, + { 's', "Ss", "Toggle sysop flag" }, + { 'q', "QqXx", "Return to users menu" }, + { '?', "?", "List menu options" }, + }; + char title[50]; + char prompt[25]; + struct user user; + size_t size; + bool done = false; + bool show_help = true; + char c; + short cc; + + size = bile_read(db->bile, DB_USER_RTYPE, id, (char *)&user, + sizeof(struct user)); + if (!size) { + session_printf(s, "Error: Failed to find user %ld\r\n", id); + session_flush(s); + return; + } + + snprintf(title, sizeof(title), "Edit User %s", user.username); + snprintf(prompt, sizeof(prompt), "Sysop:Users:%ld", user.id); + + while (!done && !s->ending) { + c = session_menu(s, title, prompt, opts, nitems(opts), + show_help); + show_help = false; + +handle_opt: + switch (c) { + case 'd': + user.is_enabled = 0; + user_save(&user); + break; + case 'e': + user.is_enabled = 1; + user_save(&user); + break; + case 'p': + user_change_password(s, &user); + break; + case 's': + if (strcmp(s->user->username, user.username) == 0) { + session_printf(s, "You cannot remove your own sysop " + "flag.\r\n"); + session_flush(s); + break; + } + user.is_sysop = !user.is_sysop; + user_save(&user); + break; + case '?': + show_help = true; + break; + default: + done = true; + break; + } + } +} + --- user.c Fri Jun 3 23:16:35 2022 +++ user.c Tue Jun 7 12:23:37 2022 @@ -66,11 +66,11 @@ user_cache_usernames(void) len = bile_read(db->bile, DB_USER_RTYPE, o->id, (char *)&user, sizeof(user)); if (len != sizeof(user)) - panic("user_update_cache_map: can't read user %lu: %d", o->id, - bile_error(db->bile)); + warn("user_update_cache_map: user %lu read size %lu != %lu (%d)", + o->id, len, sizeof(user), bile_error(db->bile)); strncpy(muser->username, user.username, sizeof(muser->username)); - + free(o); nuser++; } @@ -91,7 +91,7 @@ user_save(struct user *user) if (len != sizeof(struct user)) panic("user_save: failed to write: %d", bile_error(db->bile)); - user_cache_usernames(); + bile_flush(db->bile, true); } struct user * @@ -305,4 +305,75 @@ user_first_sysop_username(void) } return ret; +} + +void +user_change_password(struct session *s, struct user *user) +{ + char *password = NULL, *password_confirm = NULL; + + if (!user) { + session_output_template(s, "{{B}}Error{{/B}}: Guest accounts " + "cannot change passwords\r\n"); + return; + } + + while (!s->ending) { + session_output_template(s, "{{B}}New Password:{{/B}} "); + session_flush(s); + password = session_field_input(s, 64, 64, NULL, false, '*'); + session_output(s, "\r\n", 2); + session_flush(s); + + if (password == NULL || s->ending) + break; + + if (password[0] == '\0') { + session_output_template(s, "{{B}}Error:{{/B}} " + "Password cannot be blank\r\n"); + free(password); + password = NULL; + continue; + } + + session_output_template(s, "{{B}}New Password (again):{{/B}} "); + session_flush(s); + password_confirm = session_field_input(s, 64, 64, NULL, false, '*'); + session_output(s, "\r\n", 2); + session_flush(s); + + if (password_confirm == NULL || s->ending) + break; + + if (strcmp(password_confirm, password) != 0) { + session_output_template(s, "{{B}}Error:{{/B}} " + "Passwords do not match\r\n"); + free(password); + password = NULL; + free(password_confirm); + password_confirm = NULL; + continue; + } + + user_set_password(user, password); + user_save(user); + + if (strcmp(s->user->username, user->username) == 0) { + session_log(s, "User changed password"); + session_output_template(s, "{{B}}Your password has been " + "changed{{/B}}\r\n"); + } else { + session_log(s, "User %s changed password for %s", + s->user->username, user->username); + session_output_template(s, "{{B}}Password has been " + "changed{{/B}}\r\n"); + } + + break; + } + + if (password != NULL) + free(password); + if (password_confirm != NULL) + free(password_confirm); } --- user.h Fri Jun 3 23:16:49 2022 +++ user.h Tue Jun 7 15:36:26 2022 @@ -21,6 +21,8 @@ #define AUTH_USER_OK 1 #define AUTH_USER_FAILED 2 +#define AUTH_USER_GUEST 3 +#define AUTH_USER_SIGNUP 4 #define GUEST_USERNAME "guest" @@ -32,6 +34,7 @@ struct user { unsigned long created_at; unsigned long last_seen_at; short is_sysop; + short is_enabled; }; struct username_cache { @@ -48,5 +51,6 @@ short user_authenticate(struct user *user, const char void user_set_password(struct user *user, const char *password); short user_valid_username(struct user *user, char *username, char **error); char * user_first_sysop_username(void); +void user_change_password(struct session *s, struct user *user); #endif --- user_settings.c Fri Jun 3 23:11:26 2022 +++ user_settings.c Mon Jun 6 14:15:56 2022 @@ -120,68 +120,6 @@ get_terminal_size: } void -user_settings_password(struct session *s) -{ - char *password = NULL, *password_confirm = NULL; - - if (!s->user) { - session_output_template(s, "{{B}}Error{{/B}}: Guest accounts " - "cannot change passwords\r\n"); - return; - } - - while (!s->ending) { - session_output_template(s, "{{B}}New Password:{{/B}} "); - session_flush(s); - password = session_field_input(s, 64, 64, NULL, false, '*'); - session_output(s, "\r\n", 2); - session_flush(s); - - if (password == NULL || s->ending) - break; - - if (password[0] == '\0') { - session_output_template(s, "{{B}}Error:{{/B}} " - "Password cannot be blank\r\n"); - free(password); - password = NULL; - continue; - } - - session_output_template(s, "{{B}}New Password (again):{{/B}} "); - session_flush(s); - password_confirm = session_field_input(s, 64, 64, NULL, false, '*'); - session_output(s, "\r\n", 2); - session_flush(s); - - if (password_confirm == NULL || s->ending) - break; - - if (strcmp(password_confirm, password) != 0) { - session_output_template(s, "{{B}}Error:{{/B}} " - "Passwords do not match\r\n"); - free(password); - password = NULL; - free(password_confirm); - password_confirm = NULL; - continue; - } - - user_set_password(s->user, password); - user_save(s->user); - session_log(s, "User changed password"); - session_output_template(s, "{{B}}Your password has been " - "changed{{/B}}\r\n"); - break; - } - - if (password != NULL) - free(password); - if (password_confirm != NULL) - free(password_confirm); -} - -void user_settings_username(struct session *s) { char *username = NULL, *error = NULL; @@ -226,6 +164,7 @@ user_settings_username(struct session *s) s->user->username, username); strlcpy(s->user->username, username, sizeof(s->user->username)); user_save(s->user); + user_cache_usernames(); session_printf(s, "{{B}}Your username has been " "changed to %s{{/B}}\r\n", s->user->username); break; @@ -261,7 +200,7 @@ user_settings_menu(struct session *s) user_settings_renegotiate(s); break; case 'p': - user_settings_password(s); + user_change_password(s, s->user); break; case 'u': user_settings_username(s);