AmendHub

Download

jcs

/

subtext

/

sysop.c

 

(View History)

jcs   logger: Add RFC3164 (BSD) syslog support Latest amendment: 585 on 2024-02-13

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