AmendHub

Download

jcs

/

subtext

/

sysop.c

 

(View History)

jcs   *: Move views out of database to flat files Latest amendment: 566 on 2023-11-28

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 #include <ShutDown.h>
19
20 #include "subtext.h"
21 #include "ansi.h"
22 #include "binkp.h"
23 #include "board.h"
24 #include "serial_local.h"
25 #include "session.h"
26 #include "sysop.h"
27 #include "telnet.h"
28
29 #define USERS_PER_PAGE 20
30
31 void sysop_edit_boards(struct session *s);
32 void sysop_edit_folders(struct session *s);
33 void sysop_edit_motd(struct session *s);
34 void sysop_edit_users(struct session *s);
35 size_t sysop_find_user_ids(size_t nall_user_ids,
36 unsigned long *all_user_ids, unsigned long **user_ids, size_t offset,
37 size_t limit);
38 void sysop_edit_user(struct session *s, unsigned long id);
39
40 void
41 sysop_menu(struct session *s)
42 {
43 static const struct session_menu_option opts[] = {
44 { 'm', "Mm", "Edit Message of the Day" },
45 { 'i', "Ii", "Trigger FTN Binkp poll" },
46 { 'h', "Hh", "Hang up modem" },
47 { 'b', "Bb", "Manage boards" },
48 { 'f', "Ff", "Manage file folders" },
49 { 'u', "Uu", "Manage users" },
50 { 's', "Ss", "Change BBS settings" },
51 { 'v', "Vv", "Reload views" },
52 { 'r', "Rr", "Reboot system" },
53 { 'q', "QqXx", "Return to main menu" },
54 { '?', "?", "List menu options" },
55 };
56 static const char prompt_help[] =
57 "M:MOTD I:Binkp H:Hang Up B:Boards F:Folders U:Users S:Settings V:Views";
58 char c;
59 bool show_help = true;
60 bool done = false;
61
62 if (!s->user || !s->user->is_sysop)
63 return;
64
65 session_logf(s, "Entered sysop menu");
66
67 while (!done && !s->ending) {
68 c = session_menu(s, "Sysop Menu", "Sysop", (char *)prompt_help,
69 opts, nitems(opts), show_help, NULL, NULL);
70 show_help = false;
71
72 switch (c) {
73 case 'b':
74 sysop_edit_boards(s);
75 break;
76 case 'f':
77 sysop_edit_folders(s);
78 break;
79 case 'h':
80 session_printf(s, "Hanging up modem...\r\n");
81 session_flush(s);
82 serial_hangup();
83 serial_init();
84 break;
85 case 'i':
86 session_printf(s, "Requesting binkp poll...\r\n");
87 session_flush(s);
88 binkp_next_poll = 0;
89 binkp_last_poll_error = false;
90 uthread_wakeup(periodic_job_thread);
91 break;
92 case 'm':
93 sysop_edit_motd(s);
94 break;
95 case 'r': {
96 IOParam pb;
97 long m;
98 short sc;
99
100 session_printf(s,
101 "Are you sure you want to reboot the system? [y/N] ");
102 session_flush(s);
103 sc = session_input_char(s);
104 if (sc != 'Y' && sc != 'y') {
105 session_output(s, "\r\n", 2);
106 session_flush(s);
107 break;
108 }
109 session_printf(s, "%c\r\n", sc == 'Y' ? 'Y' : 'y');
110 session_flush(s);
111
112 _exiting(0); /* call atexit list, from atexit.c */
113
114 /* flush the disk and give it a couple seconds */
115 memset(&pb, 0, sizeof(pb));
116 PBFlushVol(&pb, false);
117 Delay(120, &m);
118
119 ShutDwnStart();
120 /* we should never get here */
121 ExitToShell();
122 break;
123 }
124 case 's':
125 sysop_edit_settings(s);
126 break;
127 case 'u':
128 sysop_edit_users(s);
129 break;
130 case 'v':
131 session_printf(s, "Reloading views...\r\n");
132 session_flush(s);
133 db_cache_views(db);
134 break;
135 case '?':
136 show_help = true;
137 break;
138 default:
139 done = true;
140 break;
141 }
142 }
143 }
144
145 void
146 sysop_edit_settings(struct session *s)
147 {
148 struct config *new_config;
149 const struct struct_field *sf;
150 char *new_config_data, *cur_config_data;
151 short ret, n, nsval, osval;
152 unsigned long nlval, olval;
153 unsigned short reinits = 0;
154
155 if (!s->user || !s->user->is_sysop)
156 return;
157
158 ret = struct_editor(s, config_fields, nconfig_fields, NULL, 0,
159 &db->config, sizeof(struct config), (void *)&new_config,
160 "BBS Settings", "Sysop:Settings");
161 if (ret != 0 || new_config == NULL) {
162 session_printf(s, "No changes made\r\n");
163 return;
164 }
165
166 for (n = 0; n < nconfig_fields; n++) {
167 sf = &config_fields[n];
168
169 cur_config_data = (char *)&db->config + sf->off;
170 new_config_data = (char *)new_config + sf->off;
171
172 switch (sf->type) {
173 case CONFIG_TYPE_STRING:
174 case CONFIG_TYPE_PASSWORD:
175 if (memcmp(cur_config_data, new_config_data, sf->max) == 0)
176 goto next_field;
177
178 if (sf->type == CONFIG_TYPE_PASSWORD)
179 session_logf(s, "Changed BBS setting \"%s\" (password)",
180 sf->name);
181 else
182 session_logf(s, "Changed BBS setting \"%s\" from \"%s\" "
183 "to \"%s\"", sf->name, cur_config_data, new_config_data);
184
185 memcpy(cur_config_data, new_config_data, sf->max);
186 break;
187 case CONFIG_TYPE_SHORT:
188 osval = CHARS_TO_SHORT(cur_config_data[0], cur_config_data[1]);
189 nsval = CHARS_TO_SHORT(new_config_data[0], new_config_data[1]);
190
191 if (nsval == osval)
192 goto next_field;
193
194 session_logf(s, "Changed BBS setting \"%s\" from %d to %d",
195 sf->name, osval, nsval);
196
197 memcpy(cur_config_data, new_config_data, sizeof(short));
198 break;
199 case CONFIG_TYPE_LONG:
200 olval = CHARS_TO_LONG(cur_config_data[0], cur_config_data[1],
201 cur_config_data[2], cur_config_data[3]);
202 nlval = CHARS_TO_LONG(new_config_data[0], new_config_data[1],
203 new_config_data[2], new_config_data[3]);
204 if (olval == nlval)
205 goto next_field;
206
207 session_logf(s, "Changed BBS setting \"%s\" from %lu to %lu",
208 sf->name, olval, nlval);
209
210 memcpy(cur_config_data, new_config_data, sizeof(long));
211 break;
212 case CONFIG_TYPE_BOOLEAN:
213 if (cur_config_data[0] == new_config_data[0])
214 goto next_field;
215
216 session_logf(s, "Changed BBS setting \"%s\" from %s to %s",
217 sf->name, cur_config_data[0] ? "true" : "false",
218 new_config_data[0] ? "true" : "false");
219
220 memcpy(cur_config_data, new_config_data, 1);
221 break;
222 case CONFIG_TYPE_IP: {
223 char cur_ip_str[16], new_ip_str[16];
224
225 olval = CHARS_TO_LONG(cur_config_data[0], cur_config_data[1],
226 cur_config_data[2], cur_config_data[3]);
227 nlval = CHARS_TO_LONG(new_config_data[0], new_config_data[1],
228 new_config_data[2], new_config_data[3]);
229 if (olval == nlval)
230 goto next_field;
231
232 if (olval == 0)
233 snprintf(cur_ip_str, sizeof(cur_ip_str), "(None)");
234 else
235 long2ip(olval, cur_ip_str);
236
237 if (nlval == 0)
238 snprintf(new_ip_str, sizeof(new_ip_str), "(None)");
239 else
240 long2ip(nlval, new_ip_str);
241
242 session_logf(s, "Changed BBS setting \"%s\" from %s to %s",
243 sf->name, cur_ip_str, new_ip_str);
244
245 memcpy(cur_config_data, new_config_data, sizeof(long));
246 break;
247 }
248 }
249
250 reinits |= (1 << sf->reinit);
251
252 next_field:
253 continue;
254 }
255
256 db_config_save(db);
257 bile_flush(db->bile, true);
258
259 if (reinits & (1 << CONFIG_REQUIRES_SERIAL_REINIT))
260 serial_reinit();
261 if (reinits & (1 << CONFIG_REQUIRES_TELNET_REINIT))
262 telnet_reinit();
263 if (reinits & (1 << CONFIG_REQUIRES_BINKP_REINIT))
264 binkp_reinit();
265 if (reinits & (1 << CONFIG_REQUIRES_IPDB_REINIT)) {
266 if (db->ipdb)
267 ipdb_close(&db->ipdb);
268 if (db->config.ipdb_path)
269 db->ipdb = ipdb_open(db->config.ipdb_path);
270 }
271
272 xfree(&new_config);
273
274 session_printf(s, "Successfully saved changes to BBS Settings\r\n");
275 }
276
277 void
278 sysop_edit_boards(struct session *s)
279 {
280 static const struct session_menu_option opts[] = {
281 { '#', "", "Enter board to edit" },
282 { 'l', "Ll", "List boards" },
283 { 'n', "Nn", "Create new board" },
284 { 'q', "QqXx", "Return to sysop menu" },
285 { '?', "?", "List menu options" },
286 };
287 static const struct session_menu_option edit_opts[] = {
288 { 'd', "Dd", "Delete board" },
289 { 'i', "Ii", "Re-index posts" },
290 };
291 static const char prompt_help[] =
292 "#:Edit Board L:List N:New Q:Return ?:Help";
293 char prompt[30];
294 struct board *board, *new_board;
295 struct bile *new_board_bile;
296 size_t n, size;
297 short bn, ret, id, sc;
298 char c, *data = NULL, *name;
299 bool done, show_list, show_help;
300
301 show_list = true;
302 show_help = false;
303 done = false;
304
305 while (!done && !s->ending) {
306 if (show_list) {
307 session_printf(s, "{{B}}Boards{{/B}}\r\n");
308 session_printf(s, "%s# Name Description%s\r\n",
309 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
310 session_flush(s);
311
312 for (n = 0; n < db->nboards; n++) {
313 session_printf(s, "%2ld %- 10.10s %s\r\n",
314 n + 1,
315 db->boards[n].name,
316 db->boards[n].description);
317 }
318 session_flush(s);
319
320 show_list = false;
321 }
322
323 c = session_menu(s, "Board Editor", "Sysop:Boards",
324 (char *)prompt_help, opts, nitems(opts), show_help, "Board #",
325 &bn);
326 show_help = false;
327
328 switch (c) {
329 case 'l':
330 show_list = true;
331 break;
332 case 'n':
333 board = xmalloczero(sizeof(struct board));
334 if (board == NULL)
335 continue;
336 board->id = bile_next_id(db->bile, DB_BOARD_RTYPE);
337 board->restricted_posting = false;
338 board->restricted_viewing = false;
339 ret = struct_editor(s, board_fields, nboard_fields, NULL, 0,
340 board, sizeof(struct board), (void *)&new_board,
341 "New Board", "Sysop:Boards:New");
342 if (ret != 0) {
343 xfree(&board);
344 continue;
345 }
346
347 new_board_bile = db_board_create(db, new_board, false);
348 if (new_board_bile == NULL) {
349 xfree(&board);
350 continue;
351 }
352 bile_close(new_board_bile);
353 bile_flush(db->bile, true);
354 db_cache_boards(db);
355
356 session_logf(s, "Created new board %ld: %s", new_board->id,
357 new_board->name);
358 xfree(&board);
359 xfree(&new_board);
360 break;
361 case '#':
362 if (bn < 1 || bn > db->nboards) {
363 session_printf(s, "Invalid board ID\r\n");
364 session_flush(s);
365 break;
366 }
367
368 board = &db->boards[bn - 1];
369 snprintf(prompt, sizeof(prompt), "Sysop:Boards:%ld",
370 board->id);
371 edit_board:
372 ret = struct_editor(s, board_fields, nboard_fields, edit_opts,
373 nitems(edit_opts), board, sizeof(struct board),
374 (void *)&new_board, "Edit Board", prompt);
375 switch (ret) {
376 case 'i':
377 board_index_sorted_post_ids(board, NULL);
378 goto edit_board;
379 case 'd':
380 session_printf(s,
381 "Really delete board %s? [y/N] ", board->name);
382 session_flush(s);
383
384 sc = session_input_char(s);
385 if (s->ending)
386 return;
387 if (sc != 'Y' && sc != 'y') {
388 session_output(s, "\r\n", 2);
389 session_flush(s);
390 break;
391 }
392 session_printf(s, "%c\r\n", sc == 'Y' ? 'Y' : 'y');
393 session_flush(s);
394
395 session_logf(s, "Deleting board %ld (%s)!",
396 board->id, board->name);
397
398 db_board_delete(db, board);
399 bile_flush(db->bile, true);
400 db_cache_boards(db);
401 break;
402 case 0:
403 ret = bile_marshall_object(db->bile, board_object_fields,
404 nboard_object_fields, new_board, &data, &size);
405 if (ret != 0 || size == 0)
406 panic("board: failed to marshall object");
407
408 if (bile_write(db->bile, DB_BOARD_RTYPE, new_board->id,
409 data, size) != size)
410 panic("save of board failed: %d", bile_error(db->bile));
411 xfree(&data);
412 bile_flush(db->bile, true);
413
414 session_logf(s, "Saved changes to board %ld: %s",
415 new_board->id, new_board->name);
416 xfree(&new_board);
417
418 db_cache_boards(db);
419 break;
420 }
421 break;
422 case '?':
423 show_help = true;
424 break;
425 default:
426 done = true;
427 break;
428 }
429 }
430 }
431
432 void
433 sysop_edit_folders(struct session *s)
434 {
435 static const struct session_menu_option opts[] = {
436 { '#', "", "Enter folder to edit" },
437 { 'l', "Ll", "List folders" },
438 { 'n', "Nn", "Create new folder" },
439 { 'q', "QqXx", "Return to sysop menu" },
440 { '?', "?", "List menu options" },
441 };
442 static const struct session_menu_option edit_opts[] = {
443 { 'd', "Dd", "Delete folder" },
444 };
445 static const char prompt_help[] =
446 "#:Edit Folder L:List N:New Q:Return ?:Help";
447 char prompt[30];
448 struct folder *folder, *new_folder;
449 struct bile *new_folder_bile;
450 size_t n, size;
451 short ret, fn, sc;
452 char c, *data = NULL;
453 bool done, show_list, show_help;
454
455 show_list = true;
456 show_help = false;
457 done = false;
458
459 while (!done && !s->ending) {
460 if (show_list) {
461 session_printf(s, "{{B}}Folders{{/B}}\r\n");
462 session_printf(s, "%s# Name Description%s\r\n",
463 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
464 session_flush(s);
465
466 for (n = 0; n < db->nfolders; n++) {
467 session_printf(s, "%2ld %- 15.15s %s\r\n",
468 n + 1,
469 db->folders[n].name,
470 db->folders[n].description);
471 }
472 session_flush(s);
473
474 show_list = false;
475 }
476
477 c = session_menu(s, "Folder Editor", "Sysop:Folders",
478 (char *)prompt_help, opts, nitems(opts), show_help, "Folder #",
479 &fn);
480 show_help = false;
481
482 switch (c) {
483 case 'l':
484 show_list = true;
485 break;
486 case 'n':
487 folder = xmalloczero(sizeof(struct folder));
488 if (folder == NULL)
489 continue;
490 folder->id = bile_next_id(db->bile, DB_FOLDER_RTYPE);
491 ret = struct_editor(s, folder_fields, nfolder_fields, NULL, 0,
492 folder, sizeof(struct folder), (void *)&new_folder,
493 "New Folder", "Sysop:Folders:New");
494 if (ret != 0) {
495 xfree(&folder);
496 continue;
497 }
498
499 new_folder_bile = db_folder_create(db, new_folder, false);
500 if (new_folder_bile == NULL) {
501 xfree(&folder);
502 xfree(&new_folder);
503 continue;
504 }
505 bile_close(new_folder_bile);
506 bile_flush(db->bile, true);
507 db_cache_folders(db);
508
509 session_logf(s, "Created new folder %ld: %s", new_folder->id,
510 new_folder->name);
511 xfree(&folder);
512 xfree(&new_folder);
513 break;
514 case '#':
515 if (fn < 1 || fn > db->nfolders) {
516 session_printf(s, "Invalid folder ID\r\n");
517 session_flush(s);
518 break;
519 }
520
521 folder = &db->folders[fn - 1];
522 snprintf(prompt, sizeof(prompt), "Sysop:Folders:%ld",
523 folder->id);
524 edit_folder:
525 ret = struct_editor(s, folder_fields, nfolder_fields,
526 edit_opts, nitems(edit_opts), folder, sizeof(struct folder),
527 (void *)&new_folder, "Edit Folder", prompt);
528 switch (ret) {
529 case 'd':
530 session_printf(s,
531 "Really delete folder %s? [y/N] ", folder->name);
532 session_flush(s);
533
534 sc = session_input_char(s);
535 if (s->ending)
536 return;
537 if (sc != 'Y' && sc != 'y') {
538 session_output(s, "\r\n", 2);
539 session_flush(s);
540 break;
541 }
542 session_printf(s, "%c\r\n", sc == 'Y' ? 'Y' : 'y');
543 session_flush(s);
544
545 session_logf(s, "Deleting folder %ld (%s)!",
546 folder->id, folder->name);
547
548 db_folder_delete(db, folder);
549 bile_flush(db->bile, true);
550 db_cache_folders(db);
551 break;
552 case 0:
553 ret = bile_marshall_object(db->bile, folder_object_fields,
554 nfolder_object_fields, new_folder, &data, &size);
555 if (ret != 0 || size == 0)
556 panic("folder: failed to marshall object");
557
558 if (bile_write(db->bile, DB_FOLDER_RTYPE, new_folder->id,
559 data, size) != size)
560 panic("save of folder failed: %d", bile_error(db->bile));
561 bile_flush(db->bile, true);
562 xfree(&data);
563
564 session_logf(s, "Saved changes to folder %ld: %s",
565 new_folder->id, new_folder->name);
566 xfree(&new_folder);
567
568 db_cache_folders(db);
569 break;
570 }
571 break;
572 case '?':
573 show_help = true;
574 break;
575 default:
576 done = true;
577 break;
578 }
579 }
580 }
581
582 void
583 sysop_edit_motd(struct session *s)
584 {
585 static const struct session_menu_option opts[] = {
586 { 'n', "Nn", "Create new MOTD" },
587 { 's', "Ss", "Show latest MOTD" },
588 { 'q', "QqXx", "Return to sysop menu" },
589 { '?', "?", "List menu options" },
590 };
591 static const char prompt_help[] =
592 "N:New S:Show Q:Return ?:Help";
593 char c;
594 bool show_help = true;
595 bool done = false;
596
597 while (!done && !s->ending) {
598 c = session_menu(s, "MOTD Editor", "Sysop:MOTD", (char *)prompt_help,
599 opts, nitems(opts), show_help, NULL, NULL);
600 show_help = false;
601
602 switch (c) {
603 case 's': {
604 session_print_motd(s, true);
605 break;
606 }
607 case 'n': {
608 char *motd = NULL, *tmp = NULL;
609 struct bile_object *old_motd;
610 unsigned long new_id;
611
612 motd_write:
613 session_printf(s, "MOTD Text:\r\n");
614 session_flush(s);
615
616 tmp = session_field_input(s, 2048, s->terminal_columns - 1,
617 motd, true, 0);
618 session_output(s, "\r\n", 2);
619 session_flush(s);
620
621 rtrim(tmp, "\r\n\t ");
622
623 if (motd)
624 xfree(&motd);
625 motd = tmp;
626
627 session_printf(s, "\r\n{{B}}(S){{/B}}ave MOTD, "
628 "{{B}}(E){{/B}}dit again, or {{B}}(C){{/B}}ancel? ");
629 session_flush(s);
630
631 c = session_input_char(s);
632 if (c == 0 && s->obuflen > 0) {
633 s->node_funcs->output(s);
634 uthread_yield();
635 xfree(&motd);
636 break;
637 }
638
639 session_printf(s, "%c\r\n", c);
640 session_flush(s);
641
642 switch (c) {
643 case 's':
644 case 'S':
645 case 'y':
646 case '\n':
647 case '\r':
648 /* save */
649 session_printf(s, "Saving MOTD...");
650 session_flush(s);
651
652 new_id = bile_next_id(db->bile, DB_MOTD_RTYPE);
653 old_motd = bile_get_nth_of_type(db->bile, 0,
654 DB_MOTD_RTYPE);
655 if (old_motd) {
656 bile_delete(db->bile, DB_MOTD_RTYPE, old_motd->id,
657 BILE_DELETE_FLAG_ZERO | BILE_DELETE_FLAG_PURGE);
658 xfree(&old_motd);
659 }
660 bile_write(db->bile, DB_MOTD_RTYPE, new_id, motd,
661 strlen(motd) + 1);
662 bile_flush(db->bile, true);
663
664 session_printf(s, " saved!\r\n");
665 session_flush(s);
666 break;
667 case 'e':
668 case 'E':
669 goto motd_write;
670 break;
671 case 'c':
672 case 'C':
673 case CONTROL_C:
674 xfree(&motd);
675 break;
676 }
677 break;
678 }
679 case '?':
680 show_help = true;
681 break;
682 default:
683 done = true;
684 break;
685 }
686 }
687 }
688
689 void
690 sysop_edit_users(struct session *s)
691 {
692 static const struct session_menu_option opts[] = {
693 { '#', "#", "Edit user id [#]" },
694 { 'u', "Uu", "Edit user by username" },
695 { '<', "<", "Previous page of users" },
696 { 'l', "Ll", "List users" },
697 { '>', ">", "Next page of users" },
698 { 'q', "QqXx", "Return to sysop menu" },
699 { '?', "?", "List menu options" },
700 };
701 static const char prompt_help[] =
702 "#:Edit User <:Prev >:Next L:List Q:Return ?:Help";
703 unsigned long *all_user_ids = NULL, *user_ids = NULL;
704 struct user user, *fuser;
705 size_t pages, page, n, size, nall_user_ids, nuser_ids;
706 short suser_id, upp;
707 char c, time[30], *username;
708 bool show_help = false;
709 bool show_list = true;
710 bool done = false;
711 bool find_user_ids = true;
712
713 nall_user_ids = bile_sorted_ids_by_type(db->bile, DB_USER_RTYPE,
714 &all_user_ids);
715 page = 0;
716
717 while (!done && !s->ending) {
718 if (find_user_ids) {
719 upp = USERS_PER_PAGE;
720 if (s->terminal_lines < upp + 3)
721 upp = BOUND(upp, 5, s->terminal_lines - 3);
722 nuser_ids = sysop_find_user_ids(nall_user_ids, all_user_ids,
723 &user_ids, page * upp, upp);
724 /* ceil(nall_user_ids / upp) */
725 pages = (nall_user_ids + upp - 1) / upp;
726
727 if (page >= pages)
728 page = pages - 1;
729
730 find_user_ids = false;
731 }
732
733 if (show_list) {
734 session_printf(s, "{{B}}Users (Page %ld of %ld){{/B}}\r\n",
735 page + 1, pages);
736 session_printf(s, "%s # Username Active Sysop Last Login%s\r\n",
737 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
738 session_flush(s);
739
740 for (n = 0; n < nuser_ids; n++) {
741 size = bile_read(db->bile, DB_USER_RTYPE, user_ids[n],
742 (char *)&user, sizeof(struct user));
743 if (size == 0)
744 continue;
745 if (user.last_seen_at)
746 strftime(time, sizeof(time), "%Y-%m-%d %H:%M",
747 localtime(&user.last_seen_at));
748 else
749 snprintf(time, sizeof(time), "Never");
750 session_printf(s, "%3ld %-14s %c %c %s\r\n",
751 user.id,
752 user.username,
753 user.is_enabled ? 'Y' : '-',
754 user.is_sysop ? 'Y' : '-',
755 time);
756 }
757 session_flush(s);
758 show_list = false;
759 }
760
761 c = session_menu(s, "Edit Users", "Sysop:Users", (char *)prompt_help,
762 opts, nitems(opts), show_help, "User ID", &suser_id);
763 show_help = false;
764
765 handle_opt:
766 switch (c) {
767 case 'l':
768 show_list = true;
769 break;
770 case '>':
771 case '<':
772 if (c == '>' && page == pages - 1) {
773 session_printf(s, "You are at the last page of posts\r\n");
774 session_flush(s);
775 break;
776 }
777 if (c == '<' && page == 0) {
778 session_printf(s, "You are already at the first page\r\n");
779 session_flush(s);
780 break;
781 }
782 if (c == '>')
783 page++;
784 else
785 page--;
786 find_user_ids = true;
787 show_list = true;
788 break;
789 case '#':
790 sysop_edit_user(s, suser_id);
791 break;
792 case 'u':
793 session_printf(s, "Username: ");
794 session_flush(s);
795
796 username = session_field_input(s, 30, 30, NULL, false, 0);
797 session_output(s, "\r\n", 2);
798 session_flush(s);
799 if (username == NULL)
800 break;
801 if (username[0] == '\0') {
802 xfree(&username);
803 break;
804 }
805
806 fuser = user_find_by_username(username);
807 xfree(&username);
808 if (!fuser) {
809 session_printf(s, "Error: No such user.\r\n");
810 session_flush(s);
811 break;
812 }
813
814 sysop_edit_user(s, fuser->id);
815 xfree(&fuser);
816 break;
817 case '?':
818 show_help = true;
819 break;
820 default:
821 done = true;
822 break;
823 }
824 }
825
826 if (all_user_ids != NULL)
827 xfree(&all_user_ids);
828 }
829
830 size_t
831 sysop_find_user_ids(size_t nall_user_ids, unsigned long *all_user_ids,
832 unsigned long **user_ids, size_t offset, size_t limit)
833 {
834 size_t nuser_ids, n;
835
836 if (nall_user_ids < offset)
837 return 0;
838
839 nuser_ids = nall_user_ids - offset;
840 if (nuser_ids > limit)
841 nuser_ids = limit;
842
843 *user_ids = xcalloc(sizeof(unsigned long), nuser_ids);
844 if (*user_ids == NULL)
845 return 0;
846
847 for (n = 0; n < nuser_ids; n++) {
848 (*user_ids)[n] = all_user_ids[offset + n];
849 }
850
851 return nuser_ids;
852 }
853
854 void
855 sysop_edit_user(struct session *s, unsigned long id)
856 {
857 static const struct session_menu_option opts[] = {
858 { 'p', "Pp", "Change password" },
859 { 'e', "Ee", "Enable/disable account" },
860 { 'd', "Dd", "Delete account" },
861 { 's', "Ss", "Toggle sysop flag" },
862 { 'q', "QqXx", "Return to users menu" },
863 { '?', "?", "List menu options" },
864 };
865 static const char prompt_help[] =
866 "P:Password E:Enable/Disable D:Delete S:Sysop Q:Return ?:Help";
867 char title[50];
868 char prompt[25];
869 struct user user;
870 size_t size;
871 bool done = false;
872 bool show_help = true;
873 char c;
874 short cc;
875
876 size = bile_read(db->bile, DB_USER_RTYPE, id, (char *)&user,
877 sizeof(struct user));
878 if (!size) {
879 session_printf(s, "Error: Failed to find user %ld\r\n", id);
880 session_flush(s);
881 return;
882 }
883
884 snprintf(title, sizeof(title), "Edit User %s", user.username);
885 snprintf(prompt, sizeof(prompt), "Sysop:Users:%ld", user.id);
886
887 while (!done && !s->ending) {
888 c = session_menu(s, title, prompt, (char *)prompt_help, opts,
889 nitems(opts), show_help, NULL, NULL);
890 show_help = false;
891
892 handle_opt:
893 switch (c) {
894 case 'd':
895 session_printf(s, "Are you sure you want to permanently "
896 "delete {{B}}%s{{/B}}? [y/N] ", user.username);
897 session_flush(s);
898
899 cc = session_input_char(s);
900 if (cc == 'y' || c == 'Y') {
901 session_printf(s, "%c\r\n", cc);
902 session_flush(s);
903 user_delete(&user);
904 session_printf(s, "\r\n{{B}}User deleted!{{/B}}\r\n");
905 done = true;
906 } else {
907 session_printf(s, "\r\nUser not deleted.\r\n");
908 session_flush(s);
909 }
910 break;
911 case 'e':
912 user.is_enabled = !user.is_enabled;
913 user_save(&user);
914 session_printf(s, "User %s is now %sabled.\r\n", user.username,
915 user.is_enabled ? "en" : "dis");
916 session_flush(s);
917 break;
918 case 'p':
919 user_change_password(s, &user);
920 break;
921 case 's':
922 if (strcmp(s->user->username, user.username) == 0) {
923 session_printf(s, "You cannot remove your own sysop "
924 "flag.\r\n");
925 session_flush(s);
926 break;
927 }
928 user.is_sysop = !user.is_sysop;
929 user_save(&user);
930 session_printf(s, "User %s is now %sa sysop.\r\n",
931 user.username, user.is_sysop ? "" : "no longer ");
932 session_flush(s);
933 break;
934 case '?':
935 show_help = true;
936 break;
937 default:
938 done = true;
939 break;
940 }
941 }
942 }