AmendHub

Download

jcs

/

subtext

/

sysop.c

 

(View History)

jcs   db: Properly close board and folder biles at shutdown Latest amendment: 297 on 2022-11-30

1 /*
2 * Copyright (c) 2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <string.h>
18
19 #include "subtext.h"
20 #include "ansi.h"
21 #include "board.h"
22 #include "session.h"
23 #include "sysop.h"
24
25 #define USERS_PER_PAGE 10
26
27 void sysop_edit_boards(struct session *s);
28 void sysop_edit_folders(struct session *s);
29 void sysop_edit_motd(struct session *s);
30 void sysop_edit_users(struct session *s);
31 size_t sysop_find_user_ids(size_t nall_user_ids,
32 unsigned long *all_user_ids, unsigned long **user_ids, size_t offset,
33 size_t limit);
34 void sysop_edit_user(struct session *s, unsigned long id);
35
36 void
37 sysop_menu(struct session *s)
38 {
39 static const struct session_menu_option opts[] = {
40 { 'm', "Mm", "Edit Message of the Day" },
41 { 'b', "Bb", "Manage Boards" },
42 { 'f', "Ff", "Manage File Folders" },
43 { 'u', "Uu", "Manage Users" },
44 { 's', "Ss", "Change BBS Settings" },
45 { 'q', "QqXx", "Return to main menu" },
46 { '?', "?", "List menu options" },
47 };
48 char c;
49 bool show_help = true;
50 bool done = false;
51
52 if (!s->user || !s->user->is_sysop)
53 return;
54
55 session_logf(s, "Entered sysop menu");
56
57 while (!done && !s->ending) {
58 c = session_menu(s, "Sysop Menu", "Sysop", opts, nitems(opts),
59 show_help);
60 show_help = false;
61
62 switch (c) {
63 case 'b':
64 sysop_edit_boards(s);
65 break;
66 case 'f':
67 sysop_edit_folders(s);
68 break;
69 case 'm':
70 sysop_edit_motd(s);
71 break;
72 case 's':
73 sysop_edit_settings(s);
74 break;
75 case 'u':
76 sysop_edit_users(s);
77 break;
78 case '?':
79 show_help = true;
80 break;
81 default:
82 done = true;
83 break;
84 }
85 }
86 }
87
88 void
89 sysop_edit_settings(struct session *s)
90 {
91 struct config *new_config, old_config;
92 short ret;
93
94 if (!s->user || !s->user->is_sysop)
95 return;
96
97 ret = struct_editor(s, config_fields, nconfig_fields, &db->config,
98 sizeof(struct config), (void *)&new_config, "BBS Settings",
99 "Sysop:Settings");
100 if (ret != 0) {
101 session_printf(s, "No changes made\r\n");
102 return;
103 }
104
105 memcpy(&old_config, &db->config, sizeof(db->config));
106 memcpy(&db->config, new_config, sizeof(db->config));
107 db_config_save(db);
108 memcpy(&db->config, &old_config, sizeof(db->config));
109
110 session_logf(s, "Changed BBS settings");
111 session_printf(s, "Successfully saved changes to BBS Settings, "
112 "restart to take effect\r\n");
113 xfree(&new_config);
114 }
115
116 void
117 sysop_edit_boards(struct session *s)
118 {
119 static const struct session_menu_option opts[] = {
120 { 'n', "Nn", "Create new board" },
121 { 'q', "QqXx", "Return to sysop menu" },
122 { '?', "?", "List menu options" },
123 };
124 char prompt[30];
125 struct session_menu_option *dopts = NULL, *opt;
126 struct board *board, *new_board;
127 struct bile *new_board_bile;
128 size_t n, size;
129 short ret;
130 char c, *data = NULL;
131 bool show_help = true;
132 bool done = false;
133 bool found = false;
134
135 while (!done && !s->ending) {
136 /*
137 * Unfortunately we have to do this every iteration because the
138 * list of boards may change
139 */
140 if (dopts != NULL)
141 xfree(&dopts);
142 dopts = xmalloc(sizeof(opts) +
143 (db->nboards * sizeof(struct session_menu_option)),
144 "sysop_edit_boards opts");
145 for (n = 0; n < db->nboards; n++) {
146 board = &db->boards[n];
147 opt = &dopts[n];
148 opt->ret = '0' + board->id;
149 opt->key[0] = '0' + board->id;
150 opt->key[1] = '\0';
151 strlcpy(opt->title, board->name, sizeof(opts[0].title));
152 }
153 memcpy(&dopts[db->nboards], opts, sizeof(opts));
154
155 c = session_menu(s, "Board Editor", "Sysop:Boards", dopts,
156 nitems(opts) + db->nboards, show_help);
157 show_help = false;
158
159 switch (c) {
160 case 'n':
161 board = xmalloczero(sizeof(struct board),
162 "sysop_edit_boards board");
163 board->id = bile_next_id(db->bile, DB_BOARD_RTYPE);
164 board->restricted_posting = false;
165 board->restricted_viewing = false;
166 ret = struct_editor(s, board_fields, nboard_fields,
167 board, sizeof(struct board), (void *)&new_board,
168 "New Board", "Sysop:Boards:New");
169 if (ret != 0) {
170 xfree(&board);
171 continue;
172 }
173
174 new_board_bile = db_board_create(db, new_board, false);
175 bile_close(new_board_bile);
176 db_cache_boards(db);
177
178 session_logf(s, "Created new board %ld: %s", new_board->id,
179 new_board->name);
180 xfree(&board);
181 xfree(&new_board);
182 break;
183 case '0':
184 case '1':
185 case '2':
186 case '3':
187 case '4':
188 case '5':
189 case '6':
190 case '7':
191 case '8':
192 case '9':
193 found = false;
194 for (n = 0; n < db->nboards; n++) {
195 board = &db->boards[n];
196 if (board->id != c - '0')
197 continue;
198 found = true;
199 snprintf(prompt, sizeof(prompt), "Sysop:Boards:%ld",
200 board->id);
201 ret = struct_editor(s, board_fields, nboard_fields,
202 board, sizeof(struct board), (void *)&new_board,
203 "Edit Board", prompt);
204 if (ret != 0)
205 continue;
206 memcpy(&db->boards[n], new_board, sizeof(struct board));
207
208 ret = bile_marshall_object(db->bile, board_object_fields,
209 nboard_object_fields, &db->boards[n], &data, &size,
210 "sysop_edit_boards");
211 if (ret != 0 || size == 0)
212 panic("db_board_create: failed to marshall object");
213
214 if (bile_write(db->bile, DB_BOARD_RTYPE, new_board->id,
215 data, size) != size)
216 panic("save of board failed: %d", bile_error(db->bile));
217 xfree(&data);
218
219 session_logf(s, "Saved changes to board %ld: %s",
220 new_board->id, new_board->name);
221 xfree(&new_board);
222 break;
223 }
224 if (!found) {
225 session_printf(s, "Invalid board ID\r\n");
226 session_flush(s);
227 }
228 break;
229 case '?':
230 show_help = true;
231 break;
232 default:
233 done = true;
234 break;
235 }
236 }
237 }
238
239 void
240 sysop_edit_folders(struct session *s)
241 {
242 static const struct session_menu_option opts[] = {
243 { 'n', "Nn", "Create new folder" },
244 { 'q', "QqXx", "Return to sysop menu" },
245 { '?', "?", "List menu options" },
246 };
247 char prompt[30];
248 struct session_menu_option *dopts = NULL, *opt;
249 struct folder *folder, *new_folder;
250 struct bile *new_folder_bile;
251 size_t n, size;
252 short ret;
253 char c, *data = NULL;
254 bool show_help = true;
255 bool done = false;
256 bool found = false;
257
258 while (!done && !s->ending) {
259 /*
260 * Unfortunately we have to do this every iteration because the
261 * list of boards may change
262 */
263 if (dopts != NULL)
264 xfree(&dopts);
265 dopts = xmalloc(sizeof(opts) +
266 (db->nboards * sizeof(struct session_menu_option)),
267 "sysop_edit_folders opts");
268 for (n = 0; n < db->nfolders; n++) {
269 folder = &db->folders[n];
270 opt = &dopts[n];
271 opt->ret = '0' + folder->id;
272 opt->key[0] = '0' + folder->id;
273 opt->key[1] = '\0';
274 strlcpy(opt->title, folder->name, sizeof(opts[0].title));
275 }
276 memcpy(&dopts[db->nfolders], opts, sizeof(opts));
277
278 c = session_menu(s, "Folder Editor", "Sysop:Folders", dopts,
279 nitems(opts) + db->nfolders, show_help);
280 show_help = false;
281
282 switch (c) {
283 case 'n':
284 folder = xmalloczero(sizeof(struct folder),
285 "sysop_edit_folders folder");
286 folder->id = bile_next_id(db->bile, DB_FOLDER_RTYPE);
287 folder->restricted_posting = false;
288 folder->restricted_viewing = false;
289 ret = struct_editor(s, folder_fields, nfolder_fields,
290 folder, sizeof(struct folder), (void *)&new_folder,
291 "New Folder", "Sysop:Folders:New");
292 if (ret != 0) {
293 xfree(&folder);
294 continue;
295 }
296
297 new_folder_bile = db_folder_create(db, new_folder, false);
298 bile_close(new_folder_bile);
299 db_cache_folders(db);
300
301 session_logf(s, "Created new folder %ld: %s", new_folder->id,
302 new_folder->name);
303 xfree(&folder);
304 xfree(&new_folder);
305 break;
306 case '0':
307 case '1':
308 case '2':
309 case '3':
310 case '4':
311 case '5':
312 case '6':
313 case '7':
314 case '8':
315 case '9':
316 found = false;
317 for (n = 0; n < db->nfolders; n++) {
318 folder = &db->folders[n];
319 if (folder->id != c - '0')
320 continue;
321 found = true;
322 snprintf(prompt, sizeof(prompt), "Sysop:Folders:%ld",
323 folder->id);
324 ret = struct_editor(s, folder_fields, nfolder_fields,
325 folder, sizeof(struct folder), (void *)&new_folder,
326 "Edit Folder", prompt);
327 if (ret != 0)
328 continue;
329 memcpy(&db->folders[n], new_folder, sizeof(struct folder));
330
331 ret = bile_marshall_object(db->bile, folder_object_fields,
332 nfolder_object_fields, &db->folders[n], &data, &size,
333 "sysop_edit_folders");
334 if (ret != 0 || size == 0)
335 panic("db_folder_create: failed to marshall object");
336
337 if (bile_write(db->bile, DB_FOLDER_RTYPE, new_folder->id,
338 data, size) != size)
339 panic("save of folder failed: %d",
340 bile_error(db->bile));
341 xfree(&data);
342
343 session_logf(s, "Saved changes to folder %ld: %s",
344 new_folder->id, new_folder->name);
345 xfree(&new_folder);
346 break;
347 }
348 if (!found) {
349 session_printf(s, "Invalid folder ID\r\n");
350 session_flush(s);
351 }
352 break;
353 case '?':
354 show_help = true;
355 break;
356 default:
357 done = true;
358 break;
359 }
360 }
361 }
362
363 void
364 sysop_edit_motd(struct session *s)
365 {
366 static const struct session_menu_option opts[] = {
367 { 'n', "Nn", "Create new MOTD" },
368 { 's', "Ss", "Show latest MOTD" },
369 { 'q', "QqXx", "Return to sysop menu" },
370 { '?', "?", "List menu options" },
371 };
372 char prompt[30];
373 struct session_menu_option *opt;
374 size_t n, size;
375 short ret;
376 char c, *data = NULL;
377 bool show_help = true;
378 bool done = false;
379 bool found = false;
380
381 while (!done && !s->ending) {
382 c = session_menu(s, "MOTD Editor", "Sysop:MOTD", opts,
383 nitems(opts), show_help);
384 show_help = false;
385
386 switch (c) {
387 case 's': {
388 session_print_motd(s, true);
389 break;
390 }
391 case 'n': {
392 char *motd = NULL, *tmp = NULL, c;
393 struct bile_object *old_motd;
394 unsigned long new_id;
395
396 motd_write:
397 session_printf(s, "MOTD Text:\r\n");
398 session_flush(s);
399
400 tmp = session_field_input(s, 2048, s->terminal_columns - 1,
401 motd, true, 0);
402 session_output(s, "\r\n", 2);
403 session_flush(s);
404
405 rtrim(tmp, "\r\n\t ");
406
407 if (motd)
408 xfree(&motd);
409 motd = tmp;
410
411 session_printf(s, "\r\n{{B}}(S){{/B}}ave MOTD, "
412 "{{B}}(E){{/B}}dit again, or {{B}}(C){{/B}}ancel? ");
413 session_flush(s);
414
415 c = session_input_char(s);
416 if (c == 0 && s->obuflen > 0) {
417 s->node_funcs->output(s);
418 uthread_yield();
419 xfree(&motd);
420 break;
421 }
422
423 session_printf(s, "%c\r\n", c);
424 session_flush(s);
425
426 switch (c) {
427 case 's':
428 case 'S':
429 case 'y':
430 case '\n':
431 case '\r':
432 /* save */
433 session_printf(s, "Saving MOTD...");
434 session_flush(s);
435
436 new_id = bile_next_id(db->bile, DB_MOTD_RTYPE);
437 old_motd = bile_get_nth_of_type(db->bile, 0,
438 DB_MOTD_RTYPE);
439 if (old_motd) {
440 bile_delete(db->bile, DB_MOTD_RTYPE, old_motd->id);
441 xfree(&old_motd);
442 }
443 bile_write(db->bile, DB_MOTD_RTYPE, new_id, motd,
444 strlen(motd) + 1);
445
446 session_printf(s, " saved!\r\n");
447 session_flush(s);
448 break;
449 case 'e':
450 case 'E':
451 goto motd_write;
452 break;
453 case 'c':
454 case 'C':
455 case CONTROL_C:
456 xfree(&motd);
457 break;
458 }
459 break;
460 }
461 case '?':
462 show_help = true;
463 break;
464 default:
465 done = true;
466 break;
467 }
468 }
469 }
470
471 void
472 sysop_edit_users(struct session *s)
473 {
474 static const struct session_menu_option opts[] = {
475 { '#', "#0123456789", "Edit user [#]" },
476 { '<', "<", "Previous page of users" },
477 { 'l', "Ll", "List users" },
478 { '>', ">", "Next page of users" },
479 { 'q', "QqXx", "Return to sysop menu" },
480 { '?', "?", "List menu options" },
481 };
482 unsigned long *all_user_ids = NULL, *user_ids = NULL;
483 struct user user;
484 size_t pages, page, n, size, nall_user_ids, nuser_ids;
485 char c;
486 char time[30];
487 bool show_help = false;
488 bool show_list = true;
489 bool done = false;
490 bool find_user_ids = true;
491
492 nall_user_ids = bile_sorted_ids_by_type(db->bile, DB_USER_RTYPE,
493 &all_user_ids);
494 page = 0;
495
496 while (!done && !s->ending) {
497 if (find_user_ids) {
498 nuser_ids = sysop_find_user_ids(nall_user_ids, all_user_ids,
499 &user_ids, page * USERS_PER_PAGE, USERS_PER_PAGE);
500 /* ceil(nall_user_ids / USERS_PER_PAGE) */
501 pages = (nall_user_ids + USERS_PER_PAGE - 1) / USERS_PER_PAGE;
502
503 if (page >= pages)
504 page = pages - 1;
505
506 find_user_ids = false;
507 }
508
509 if (show_list) {
510 session_printf(s, "{{B}}Users (Page %ld of %ld){{/B}}\r\n",
511 page + 1, pages);
512 session_printf(s, "%s# Username ID Active Sysop Last Login%s\r\n",
513 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
514 session_flush(s);
515
516 for (n = 0; n < nuser_ids; n++) {
517 size = bile_read(db->bile, DB_USER_RTYPE, user_ids[n],
518 (char *)&user, sizeof(struct user));
519 if (size == 0)
520 continue;
521 if (user.last_seen_at)
522 strftime(time, sizeof(time), "%Y-%m-%d %H:%M",
523 localtime(&user.last_seen_at));
524 else
525 snprintf(time, sizeof(time), "Never");
526 session_printf(s, "%ld %-14s %-5ld %c %c %s\r\n",
527 n,
528 user.username,
529 user.id,
530 user.is_enabled ? 'Y' : ' ',
531 user.is_sysop ? 'Y' : ' ',
532 time);
533 session_flush(s);
534 }
535 show_list = false;
536 }
537
538 c = session_menu(s, "Edit Users", "Sysop:Users", opts,
539 nitems(opts), show_help);
540 show_help = false;
541
542 handle_opt:
543 switch (c) {
544 case 'l':
545 show_list = true;
546 break;
547 case '>':
548 case '<':
549 if (c == '>' && page == pages - 1) {
550 session_printf(s, "You are at the last page of posts\r\n");
551 session_flush(s);
552 break;
553 }
554 if (c == '<' && page == 0) {
555 session_printf(s, "You are already at the first page\r\n");
556 session_flush(s);
557 break;
558 }
559 if (c == '>')
560 page++;
561 else
562 page--;
563 find_user_ids = true;
564 show_list = true;
565 break;
566 case 0:
567 case 1:
568 case 2:
569 case 3:
570 case 4:
571 case 5:
572 case 6:
573 case 7:
574 case 8:
575 case 9:
576 if (c >= nuser_ids) {
577 session_printf(s, "Invalid user\r\n");
578 session_flush(s);
579 break;
580 }
581 sysop_edit_user(s, user_ids[c]);
582 break;
583 case '?':
584 show_help = true;
585 break;
586 default:
587 done = true;
588 break;
589 }
590 }
591
592 if (all_user_ids != NULL)
593 xfree(&all_user_ids);
594 }
595
596 size_t
597 sysop_find_user_ids(size_t nall_user_ids, unsigned long *all_user_ids,
598 unsigned long **user_ids, size_t offset, size_t limit)
599 {
600 size_t nuser_ids, n;
601
602 if (nall_user_ids < offset)
603 return 0;
604
605 nuser_ids = nall_user_ids - offset;
606 if (nuser_ids > limit)
607 nuser_ids = limit;
608
609 *user_ids = xcalloc(sizeof(unsigned long), nuser_ids,
610 "sysop_find_user_ids");
611
612 for (n = 0; n < nuser_ids; n++) {
613 (*user_ids)[n] = all_user_ids[offset + n];
614 }
615
616 return nuser_ids;
617 }
618
619 void
620 sysop_edit_user(struct session *s, unsigned long id)
621 {
622 static const struct session_menu_option opts[] = {
623 { 'd', "Dd", "Delete account" },
624 { 'e', "Ee", "Enable/disable account" },
625 { 'p', "Pp", "Change password" },
626 { 's', "Ss", "Toggle sysop flag" },
627 { 'q', "QqXx", "Return to users menu" },
628 { '?', "?", "List menu options" },
629 };
630 char title[50];
631 char prompt[25];
632 struct user user;
633 size_t size;
634 bool done = false;
635 bool show_help = true;
636 char c;
637 short cc;
638
639 size = bile_read(db->bile, DB_USER_RTYPE, id, (char *)&user,
640 sizeof(struct user));
641 if (!size) {
642 session_printf(s, "Error: Failed to find user %ld\r\n", id);
643 session_flush(s);
644 return;
645 }
646
647 snprintf(title, sizeof(title), "Edit User %s", user.username);
648 snprintf(prompt, sizeof(prompt), "Sysop:Users:%ld", user.id);
649
650 while (!done && !s->ending) {
651 c = session_menu(s, title, prompt, opts, nitems(opts),
652 show_help);
653 show_help = false;
654
655 handle_opt:
656 switch (c) {
657 case 'd':
658 session_printf(s, "Are you sure you want to permanently "
659 "delete {{B}}%s{{/B}}? [y/N] ", user.username);
660 session_flush(s);
661
662 cc = session_input_char(s);
663 if (cc == 'y' || c == 'Y') {
664 session_printf(s, "%c\r\n", cc);
665 session_flush(s);
666 user_delete(&user);
667 session_printf(s, "\r\n{{B}}User deleted!{{/B}}\r\n");
668 done = true;
669 } else {
670 session_printf(s, "\r\nUser not deleted.\r\n");
671 session_flush(s);
672 }
673 break;
674 case 'e':
675 user.is_enabled = !user.is_enabled;
676 user_save(&user);
677 break;
678 case 'p':
679 user_change_password(s, &user);
680 break;
681 case 's':
682 if (strcmp(s->user->username, user.username) == 0) {
683 session_printf(s, "You cannot remove your own sysop "
684 "flag.\r\n");
685 session_flush(s);
686 break;
687 }
688 user.is_sysop = !user.is_sysop;
689 user_save(&user);
690 break;
691 case '?':
692 show_help = true;
693 break;
694 default:
695 done = true;
696 break;
697 }
698 }
699 }