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 over 2 years 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);