AmendHub

Download

jcs

/

subtext

/

session.c

 

(View History)

jcs   session: Avoid having to use strlen, check for null byte while walking Latest amendment: 584 on 2024-02-13

1 /*
2 * Copyright (c) 2021 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 <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include "ansi.h"
23 #include "board.h"
24 #include "chat.h"
25 #include "folder.h"
26 #include "logger.h"
27 #include "mail.h"
28 #include "main_menu.h"
29 #include "subtext.h"
30 #include "session.h"
31 #include "settings.h"
32 #include "signup.h"
33 #include "sysop.h"
34 #include "user.h"
35 #include "user_settings.h"
36 #include "uthread.h"
37 #include "util.h"
38
39 #include "console.h"
40
41 #define OBUF_CANARY 0xaaaaaaaa
42 #define IBUF_CANARY 0xe38e38e3
43 #define OBUFLEN_CANARY 0x1c71c71c
44 #define IBUFLEN_CANARY 0x55555555
45
46 struct session *sessions[MAX_SESSIONS] = { 0 };
47 short nsessions = 0;
48
49 struct session_tally session_today_tally = { 0 };
50 static char session_log_tbuf[256];
51 static const char invalid_option_help[] =
52 "Invalid option (press {{B}}?{{/B}} for help)\r\n";
53
54 /* must match session_area order */
55 static const char area_labels[][12] = {
56 "Main Menu",
57 "MOTD",
58 "Boards",
59 "FTN Boards",
60 "Chat",
61 "Mail",
62 "Files",
63 "Who",
64 "Recents",
65 "Settings",
66 "Signup"
67 };
68
69 void session_run(struct uthread *uthread, void *arg);
70 void session_print_menu(struct session *session, bool short_menu);
71 short session_login(struct session *s);
72 size_t session_log(struct session *session, const char *str);
73 size_t session_vprintf(struct session *session, const char *format,
74 va_list ap);
75 size_t session_expand_var(struct session *session, char *ivar, char **ret,
76 bool *end_expansion);
77 struct session * session_first_waiting_for_sysop(void);
78 void session_page_sysop(struct session *s);
79 void session_answer_page(struct session *s);
80 void sysop_edit_settings(struct session *s);
81 bool session_idled_out(struct session *session);
82
83 struct session *
84 session_create(char *node, char *via, struct node_funcs *node_funcs)
85 {
86 struct session *session;
87 size_t n;
88
89 /* reserve 1 session for the console */
90 if (nsessions >= MAX_SESSIONS - 1) {
91 if (strcmp(node, "console") != 0)
92 return NULL;
93
94 /* but only 1 */
95 if (nsessions == MAX_SESSIONS)
96 return NULL;
97 }
98
99 session = xmalloczero(sizeof(struct session));
100 if (session == NULL)
101 return NULL;
102
103 session->node_funcs = node_funcs;
104 strlcpy(session->node, node, sizeof(session->node));
105 strlcpy(session->log.node, node, sizeof(session->log.node));
106 strlcpy(session->via, via, sizeof(session->via));
107 strlcpy(session->location, "Unknown", sizeof(session->location));
108 strlcpy(session->log.location, session->location,
109 sizeof(session->log.location));
110 session->last_input_at = session->established_at = Time;
111
112 session->obuf_canary = OBUF_CANARY;
113 session->ibuf_canary = IBUF_CANARY;
114 session->obuflen_canary = OBUFLEN_CANARY;
115 session->ibuflen_canary = IBUFLEN_CANARY;
116
117 for (n = 0; n < MAX_SESSIONS; n++) {
118 if (sessions[n] != NULL)
119 continue;
120
121 session->uthread = uthread_add(session_run, session);
122 if (!session->uthread) {
123 xfree(&session);
124 return NULL;
125 }
126
127 sessions[n] = session;
128 nsessions++;
129 return session;
130 }
131
132 /* no room, but how did we get here? */
133 xfree(&session);
134 return NULL;
135 }
136
137 void
138 session_run(struct uthread *uthread, void *arg)
139 {
140 struct session *s = (struct session *)arg;
141 struct session *waiting;
142 char date[9];
143 struct tm *date_tm;
144 unsigned short c, last_c = 0;
145 bool done = false;
146 short auth, i, action, sc;
147
148 /* until we negotiate otherwise */
149 s->terminal_columns = DEFAULT_TERMINAL_COLUMNS;
150 s->terminal_lines = DEFAULT_TERMINAL_LINES;
151 s->area = SESSION_AREA_MAIN_MENU;
152
153 if (main_menu_options == NULL)
154 panic("No main menu!");
155
156 if (s->node_funcs->setup) {
157 s->node_funcs->setup(s);
158
159 if (s->ending) {
160 session_close(s);
161 return;
162 }
163 }
164
165 session_output_view_or_printf(s, DB_VIEW_ISSUE,
166 "\r\nWelcome to %s (%s)\r\n\r\n", db->config.name, s->node);
167 session_flush(s);
168 uthread_yield();
169
170 auth = session_login(s);
171 if (auth == AUTH_USER_FAILED) {
172 session_close(s);
173 return;
174 }
175
176 s->logged_in = true;
177 logger_update_title();
178
179 /* update session log */
180 if (s->user) {
181 s->user->last_seen_at = Time;
182 user_save(s->user);
183
184 strlcpy(s->log.username, s->user->username,
185 sizeof(s->log.username));
186 strlcpy(s->chat_username, s->user->username,
187 sizeof(s->chat_username));
188 } else {
189 strlcpy(s->log.username, GUEST_USERNAME, sizeof(s->log.username));
190 /* guest + ttyt0 -> guest[t0] */
191 snprintf(s->chat_username, sizeof(s->chat_username), "%s[%s]",
192 GUEST_USERNAME, s->node + 3);
193 }
194
195 strlcpy(s->log.via, s->via, sizeof(s->log.via));
196 s->log.tspeed = s->tspeed;
197 s->log.logged_on_at = Time;
198 s->log.id = bile_next_id(db->sessions_bile, SL_LOG_RTYPE);
199
200 if (bile_write(db->sessions_bile, SL_LOG_RTYPE, s->log.id, &s->log,
201 sizeof(s->log)) != sizeof(s->log))
202 panic("bile_write of session log failed: %d",
203 bile_error(db->sessions_bile));
204
205 /* update call tally for today */
206 date_tm = localtime((time_t *)&Time);
207 strftime(date, sizeof(date), "%Y%m%d", date_tm);
208 if (strcmp(date, session_today_tally.date) != 0) {
209 strlcpy(session_today_tally.date, date,
210 sizeof(session_today_tally.date));
211 session_today_tally.calls = 0;
212 }
213 session_today_tally.calls++;
214
215 if (s->terminal_type[0] == '\0') {
216 /* per-node setup didn't fill this in, ask the user */
217 session_output(s, "\r\n", 2);
218 user_settings_renegotiate(s);
219 session_output(s, "\r\n", 2);
220 session_flush(s);
221 }
222
223 session_printf(s, "\r\n"
224 "Welcome, {{B}}%s{{/B}}, you are the %ld%s caller today.\r\n",
225 s->user ? s->user->username : GUEST_USERNAME,
226 session_today_tally.calls, ordinal(session_today_tally.calls));
227 session_flush(s);
228 uthread_yield();
229
230 if (s->user && s->user->is_sysop &&
231 (waiting = session_first_waiting_for_sysop())) {
232 session_printf(s, "\r\n{{B}}%s{{/B}} paged sysop, answer? [Y/n] ",
233 waiting->user ? waiting->user->username : GUEST_USERNAME);
234 session_flush(s);
235
236 sc = session_input_char(s);
237 if (sc == 'N' || sc == 'n') {
238 session_printf(s, "%c\r\n", sc == 'N' ? 'N' : 'n');
239 session_flush(s);
240 } else {
241 session_output(s, "\r\n", 2);
242 session_flush(s);
243 session_answer_page(s);
244 }
245 }
246
247 s->area = SESSION_AREA_MOTD;
248 session_print_motd(s, false);
249
250 if (auth == AUTH_USER_SIGNUP) {
251 auth = AUTH_USER_GUEST;
252 session_output(s, "\r\n", 2);
253
254 s->area = SESSION_AREA_SIGNUP;
255 s->user = signup(s);
256 }
257
258 main_menu:
259 s->area = SESSION_AREA_MAIN_MENU;
260 session_output(s, "\r\n", 2);
261 session_print_menu(s, false);
262 session_flush(s);
263
264 while (!done && !s->ending) {
265 s->area = SESSION_AREA_MAIN_MENU;
266 session_printf(s, "{{B}}Main Menu>{{/B}} ");
267 session_flush(s);
268
269 get_another_char:
270 action = ACTION_NONE;
271
272 c = session_input_char(s);
273
274 if (s->ending)
275 break;
276 if (c == '\r' || c == 0 || c > 255)
277 goto get_another_char;
278
279 session_printf(s, "%c\r\n", c);
280 session_flush(s);
281
282 if (action == ACTION_NONE) {
283 /* check each menu option's all_keys array for matching key */
284 for (i = 0; ; i++) {
285 struct main_menu_option *option = &main_menu_options[i];
286 short j;
287
288 if (option->action == ACTION_NONE)
289 break;
290
291 for (j = 0; option->all_keys[j] != '\0'; j++) {
292 if (option->all_keys[j] == c) {
293 action = option->action;
294 break;
295 }
296 }
297
298 if (action != ACTION_NONE)
299 break;
300 }
301 }
302
303 /* change actions for conditionals */
304 if (action == ACTION_SETTINGS_OR_SIGNUP) {
305 if (s->user)
306 action = ACTION_SETTINGS;
307 else
308 action = ACTION_SIGNUP;
309 } else if (action == ACTION_PAGE_SEND_OR_ANSWER) {
310 if (s->user && s->user->is_sysop)
311 action = ACTION_PAGE_ANSWER;
312 else
313 action = ACTION_PAGE_SEND;
314 }
315
316 switch (action) {
317 case ACTION_BOARD_SHOW_FIRST:
318 s->area = SESSION_AREA_BOARDS;
319 board_show(s, 1, NULL);
320 break;
321 case ACTION_CHAT:
322 s->area = SESSION_AREA_CHAT;
323 chat_start(s, NULL);
324 break;
325 case ACTION_FILES_MENU:
326 s->area = SESSION_AREA_FILES;
327 folder_list(s);
328 break;
329 case ACTION_GOODBYE:
330 session_output_view_or_printf(s, DB_VIEW_SIGNOFF,
331 "Goodbye!\r\n");
332 session_flush(s);
333 done = true;
334 break;
335 case ACTION_RECENT_LOGINS:
336 s->area = SESSION_AREA_RECENT_LOGINS;
337 session_recents(s);
338 break;
339 case ACTION_MAIL_MENU:
340 s->area = SESSION_AREA_MAIL;
341 mail_menu(s);
342 break;
343 case ACTION_MAIL_COMPOSE:
344 s->area = SESSION_AREA_MAIL;
345 mail_compose(s, NULL, NULL, NULL, NULL);
346 break;
347 case ACTION_MOTD:
348 s->area = SESSION_AREA_MOTD;
349 session_print_motd(s, true);
350 break;
351 case ACTION_PAGE_ANSWER:
352 if (!s->user || !s->user->is_sysop) {
353 session_printf(s, invalid_option_help);
354 session_flush(s);
355 break;
356 }
357 s->area = SESSION_AREA_CHAT;
358 session_answer_page(s);
359 break;
360 case ACTION_PAGE_SEND:
361 session_page_sysop(s);
362 break;
363 case ACTION_SETTINGS:
364 if (s->user) {
365 s->area = SESSION_AREA_SETTINGS;
366 user_settings_menu(s);
367 }
368 break;
369 case ACTION_SIGNUP:
370 s->area = SESSION_AREA_SIGNUP;
371 if ((s->user = signup(s)))
372 goto main_menu;
373 break;
374 case ACTION_WHOS_ONLINE:
375 s->area = SESSION_AREA_WHO;
376 session_who(s);
377 break;
378 case ACTION_BOARD_LIST_BOARDS:
379 s->area = SESSION_AREA_BOARDS;
380 board_list_boards(s);
381 break;
382 case ACTION_BOARD_LIST_FTN_AREAS:
383 s->area = SESSION_AREA_FTN;
384 board_list_ftn_areas(s);
385 break;
386 case ACTION_BOARD_SHOW_1:
387 s->area = SESSION_AREA_BOARDS;
388 board_show(s, 1, NULL);
389 break;
390 case ACTION_BOARD_SHOW_2:
391 s->area = SESSION_AREA_BOARDS;
392 board_show(s, 2, NULL);
393 break;
394 case ACTION_BOARD_SHOW_3:
395 s->area = SESSION_AREA_BOARDS;
396 board_show(s, 3, NULL);
397 break;
398 case ACTION_BOARD_SHOW_4:
399 s->area = SESSION_AREA_BOARDS;
400 board_show(s, 4, NULL);
401 break;
402 case ACTION_BOARD_SHOW_5:
403 s->area = SESSION_AREA_BOARDS;
404 board_show(s, 5, NULL);
405 break;
406 case ACTION_BOARD_SHOW_6:
407 s->area = SESSION_AREA_BOARDS;
408 board_show(s, 6, NULL);
409 break;
410 case ACTION_BOARD_SHOW_7:
411 s->area = SESSION_AREA_BOARDS;
412 board_show(s, 7, NULL);
413 break;
414 case ACTION_BOARD_SHOW_8:
415 s->area = SESSION_AREA_BOARDS;
416 board_show(s, 8, NULL);
417 break;
418 case ACTION_BOARD_SHOW_9:
419 s->area = SESSION_AREA_BOARDS;
420 board_show(s, 9, NULL);
421 break;
422 case ACTION_BOARD_SHOW_10:
423 s->area = SESSION_AREA_BOARDS;
424 board_show(s, 10, NULL);
425 break;
426 case ACTION_SYSOP_MENU:
427 if (!s->user || !s->user->is_sysop) {
428 session_printf(s, invalid_option_help);
429 session_flush(s);
430 break;
431 }
432 sysop_menu(s);
433 break;
434 case ACTION_SHOW_MENU:
435 if (last_c == c)
436 /* asking twice in a row will print the full menu */
437 session_print_menu(s, false);
438 else
439 session_print_menu(s, true);
440 session_flush(s);
441 break;
442 default:
443 session_printf(s, invalid_option_help);
444 session_flush(s);
445 break;
446 }
447
448 last_c = c;
449 }
450
451 session_close(s);
452 }
453
454 void
455 session_print_menu(struct session *session, bool short_menu)
456 {
457 struct main_menu_option *option = NULL;
458 size_t size;
459 short i;
460 char *output = NULL;
461
462 if (short_menu && session_output_view_or_printf(session,
463 DB_VIEW_SHORTMENU, NULL) != 0)
464 return;
465
466 if (session_output_view_or_printf(session, DB_VIEW_MENU, NULL) != 0)
467 return;
468
469 for (i = 0; ; i++) {
470 option = &main_menu_options[i];
471
472 if (option->action == ACTION_NONE)
473 break;
474
475 if (option->menu_key == 0 || option->label[0] == '\0')
476 continue;
477
478 size = session_expand_template(session, option->label, &output);
479 if (!size)
480 continue;
481
482 session_printf(session, "{{B}}%c{{/B}}: %s\r\n", option->menu_key,
483 output);
484 xfree(&output);
485 }
486 }
487
488 void
489 session_close(struct session *session)
490 {
491 unsigned long now;
492 short n;
493 bool found;
494
495 if (!session->ending) {
496 if (!session->ban_node_source) {
497 /* give 1 second to flush output */
498 now = Ticks;
499 while (session->obuflen && (Ticks - now) < 60) {
500 session->node_funcs->output(session);
501 uthread_yield();
502 }
503 }
504 session->ending = true;
505 }
506
507 if (session->log.logged_on_at) {
508 /* finalize session log */
509 session->log.logged_off_at = Time;
510
511 if (bile_write(db->sessions_bile, SL_LOG_RTYPE, session->log.id,
512 &session->log, sizeof(session->log)) != sizeof(session->log))
513 panic("bile_write of session log failed: %d",
514 bile_error(db->sessions_bile));
515 }
516
517 /* close the node */
518 session->node_funcs->close(session);
519
520 /* remove session from sessions */
521 found = false;
522 for (n = 0; n < MAX_SESSIONS; n++) {
523 if (sessions[n] == session) {
524 sessions[n] = NULL;
525 found = true;
526 break;
527 }
528 }
529
530 if (!found)
531 panic("session_close failed to find session to remove");
532
533 nsessions--;
534 if (session->user)
535 xfree(&session->user);
536 xfree(&session);
537
538 logger_update_title();
539 }
540
541 void
542 session_check_buf_canaries(struct session *session)
543 {
544 if (session->obuf_canary != OBUF_CANARY)
545 warn("obuf canary dead");
546 if (session->ibuf_canary != IBUF_CANARY)
547 warn("ibuf canary dead");
548 if (session->obuflen_canary != OBUFLEN_CANARY)
549 warn("obuflen canary dead");
550 if (session->ibuflen_canary != IBUFLEN_CANARY)
551 warn("ibuflen canary dead");
552 }
553
554 void
555 session_flush(struct session *session)
556 {
557 if (session->ending || session->obuflen == 0)
558 return;
559
560 while (session->obuflen != 0) {
561 session->node_funcs->output(session);
562 session_check_buf_canaries(session);
563 if (session->obuflen != 0)
564 uthread_yield();
565 if (session->ending)
566 return;
567
568 /*
569 * The console will leave \e in the output buffer if it's the only
570 * thing in there, so don't loop forever.
571 */
572 if (session->obuflen == 1 && session->obuf[0] == '\33')
573 return;
574 }
575 }
576
577 size_t
578 session_log(struct session *session, const char *str)
579 {
580 return logger_printf("[%s] [%s] %s", session->node,
581 session->logged_in ?
582 (session->user ? session->user->username : GUEST_USERNAME) : "-",
583 str);
584 }
585
586 size_t
587 session_logf(struct session *session, const char *format, ...)
588 {
589 va_list ap;
590
591 va_start(ap, format);
592 vsnprintf(session_log_tbuf, sizeof(session_log_tbuf), format, ap);
593 va_end(ap);
594
595 return session_log(session, session_log_tbuf);
596 }
597
598 size_t
599 session_logf_buffered(struct session *session, const char *format, ...)
600 {
601 va_list ap;
602 size_t ret;
603
604 va_start(ap, format);
605 vsnprintf(session_log_tbuf, sizeof(session_log_tbuf), format, ap);
606 va_end(ap);
607
608 logger->autoflush = false;
609 ret = session_log(session, session_log_tbuf);
610 logger->autoflush = true;
611
612 return ret;
613 }
614
615 size_t
616 session_output(struct session *session, const char *str, size_t len)
617 {
618 size_t chunk, olen = len, stroff = 0;
619
620 while (len && !session->ending) {
621 chunk = (sizeof(session->obuf) - session->obuflen);
622 if (chunk == 0) {
623 session_flush(session);
624 if (session->ending)
625 return 0;
626 continue;
627 }
628 if (chunk > len)
629 chunk = len;
630
631 memcpy(session->obuf + session->obuflen, str + stroff, chunk);
632 session->obuflen += chunk;
633 if (session->obuflen > sizeof(session->obuf)) {
634 warn("[%s] Bogus obuflen %d > %ld", session->node,
635 session->obuflen, sizeof(session->obuf));
636 session->ending = true;
637 session_close(session);
638 return 0;
639 }
640 stroff += chunk;
641 len -= chunk;
642 }
643
644 session_check_buf_canaries(session);
645
646 return olen;
647 }
648
649 size_t
650 session_paginate(struct session *session, const char *str, size_t len,
651 size_t first_page_printed)
652 {
653 size_t olen = 0, n, adv;
654 size_t olines = first_page_printed;
655 size_t line_len = session->terminal_columns;
656 ssize_t last_space = -1;
657 short last_input = 0;
658
659 while (len && !session->ending) {
660 for (n = 0, adv = 0; n < line_len && n < len; n++) {
661 if (str[n] == '\r') {
662 last_space = n;
663 adv = n + 1;
664 if (str[n + 1] == '\n')
665 adv++;
666 break;
667 } else if (str[n] == '\n') {
668 last_space = n;
669 adv = n + 1;
670 break;
671 } else if (str[n] == ' ') {
672 last_space = n;
673 adv = n + 1;
674 }
675 }
676 if (last_space == -1) {
677 /* no space, break hard */
678 last_space = MIN(line_len, len);
679 adv = last_space;
680 }
681 if (len == n)
682 adv = last_space = len;
683
684 if (last_space > 0) {
685 session_output(session, str, last_space);
686 olen += last_space;
687 }
688 str += adv;
689 len -= adv;
690
691 session_output(session, "\r\n", 2);
692 olen += 2;
693 olines++;
694
695 if (len && (olines == session->terminal_lines - 1 ||
696 last_input == '\r' || last_input == '\n')) {
697 olines = 0;
698
699 session_printf(session, "-- More --");
700 session_flush(session);
701
702 last_input = session_input_char(session);
703
704 if (session->vt100)
705 session_printf(session, "\r%s",
706 ansi(session, ANSI_ERASE_LINE, ANSI_END));
707 else
708 session_output(session, "\r", 1);
709
710 if (last_input == 'q' || last_input == 'Q')
711 break;
712 }
713 }
714
715 return olen;
716 }
717
718 size_t
719 session_printf(struct session *session, const char *format, ...)
720 {
721 va_list ap;
722 size_t len;
723
724 va_start(ap, format);
725 len = session_vprintf(session, format, ap);
726 va_end(ap);
727
728 return len;
729 }
730
731 size_t
732 session_vprintf(struct session *session, const char *format, va_list ap)
733 {
734 static char session_printf_ebuf[160], session_printf_tbuf[160];
735 size_t len, n, en;
736 bool stop = false;
737
738 /* avoid a full session_expand_template for {{B}}, {{/B}}, and {{#}} */
739 session_printf_ebuf[0] = '\0';
740 for (n = 0, en = 0; format[n] != '\0'; n++) {
741 if (!stop) {
742 if (format[n] == '{' && format[n + 1] == '{' &&
743 format[n + 2] == 'B' && format[n + 3] == '}' &&
744 format[n + 4] == '}') {
745 en = strlcat(session_printf_ebuf, ansi_bold(session),
746 sizeof(session_printf_ebuf));
747 n += 4;
748 continue;
749 }
750 if (format[n] == '{' &&
751 format[n + 1] == '{' && format[n + 2] == '/' &&
752 format[n + 3] == 'B' && format[n + 4] == '}' &&
753 format[n + 5] == '}') {
754 en = strlcat(session_printf_ebuf, ansi_reset(session),
755 sizeof(session_printf_ebuf));
756 n += 5;
757 continue;
758 }
759 if (format[n] == '{' &&
760 format[n + 1] == '{' && format[n + 2] == '#' &&
761 format[n + 3] == '}' && format[n + 4] == '}') {
762 stop = true;
763 n += 4;
764 continue;
765 }
766 }
767
768 session_printf_ebuf[en++] = format[n];
769
770 if (en >= sizeof(session_printf_ebuf) - 1)
771 panic("session_printf_ebuf overflow!");
772 session_printf_ebuf[en] = '\0';
773 }
774
775 len = vsnprintf(session_printf_tbuf, sizeof(session_printf_tbuf),
776 session_printf_ebuf, ap);
777
778 if (len > sizeof(session_printf_tbuf))
779 panic("session_printf overflow! (%ld > %ld)", len,
780 sizeof(session_printf_tbuf));
781
782 return session_output(session, session_printf_tbuf, len);
783 }
784
785 size_t
786 session_output_view_or_printf(struct session *session, short id,
787 const char *format, ...)
788 {
789 size_t size;
790 char *output;
791 va_list ap;
792
793 /* can't use bile_read_alloc because we need to null terminate */
794 if (db->views[id] == NULL || db->views[id][0] == '\0') {
795 if (format == NULL)
796 return 0;
797
798 va_start(ap, format);
799 size = session_vprintf(session, format, ap);
800 va_end(ap);
801
802 return size;
803 }
804
805 size = session_expand_template(session, db->views[id], &output);
806 if (!size)
807 return 0;
808
809 size = session_output(session, output, size);
810 xfree(&output);
811
812 return size;
813 }
814
815 bool
816 session_idled_out(struct session *session)
817 {
818 if (session->logged_in) {
819 if (session->user && session->user->is_sysop) {
820 if (db->config.max_sysop_idle_minutes != 0 &&
821 Time - session->last_input_at >
822 (db->config.max_sysop_idle_minutes * 60))
823 return true;
824 } else {
825 if (db->config.max_idle_minutes != 0 &&
826 Time - session->last_input_at >
827 (db->config.max_idle_minutes * 60))
828 return true;
829 }
830 } else {
831 if (Time - session->established_at >
832 db->config.max_login_seconds)
833 return true;
834 }
835
836 return false;
837 }
838
839 short
840 session_get_char(struct session *session, unsigned char *b)
841 {
842 return session_get_chars(session, b, 1);
843 }
844
845 short
846 session_get_chars(struct session *session, unsigned char *b, size_t count)
847 {
848 short ret = 0;
849
850 while (ret < count) {
851 if (session->ibuflen == 0) {
852 if (session_idled_out(session))
853 return 0;
854
855 uthread_yield();
856 session->node_funcs->input(session);
857
858 if (session->ending || session->abort_input)
859 return 0;
860 if (session->ibuflen == 0)
861 return 0;
862 }
863
864 session->last_input_at = Time;
865
866 *b = session->ibuf[session->ibufoff];
867 b++;
868
869 if (session->ibuflen == 1) {
870 session->ibuflen = 0;
871 session->ibufoff = 0;
872 } else {
873 session->ibuflen--;
874 session->ibufoff++;
875 }
876
877 ret++;
878 }
879
880 session_check_buf_canaries(session);
881
882 return ret;
883 }
884
885 bool
886 session_wait_for_chars(struct session *session, unsigned short timeout_ms,
887 unsigned short num_chars)
888 {
889 unsigned long expire = 0;
890
891 if (timeout_ms)
892 expire = Ticks + (timeout_ms / ((double)1000 / (double)60));
893
894 while (session->ibuflen < num_chars) {
895 session->node_funcs->input(session);
896 if (session->ending || session->abort_input)
897 return false;
898 if (session->obuflen != 0 && session->chatting)
899 return false;
900 if (expire && Ticks > expire)
901 return false;
902 if (session_idled_out(session))
903 return false;
904 if (session->ibuflen >= num_chars)
905 break;
906 uthread_yield();
907 }
908
909 session->last_input_at = Time;
910 session_check_buf_canaries(session);
911 return true;
912 }
913
914 unsigned short
915 session_input_char(struct session *session)
916 {
917 unsigned short ret;
918 short waiting_for = 1;
919 short consumed = 0;
920
921 wait_for_char:
922 if (session->ibuflen < waiting_for &&
923 !session_wait_for_chars(session, 0, waiting_for)) {
924 if (session_idled_out(session))
925 goto idled_out;
926 return 0;
927 }
928
929 if (session->ibuf[0] == ESC) {
930 /* look for a VT100 escape sequence */
931 if (session->ibuflen == 1) {
932 waiting_for = 2;
933 goto wait_for_char;
934 } else if (session->ibuf[1] == '[') {
935 if (session->ibuflen == 2) {
936 waiting_for = 3;
937 goto wait_for_char;
938 } else {
939 consumed = 3;
940 switch (session->ibuf[2]) {
941 case 'A':
942 ret = KEY_UP;
943 break;
944 case 'B':
945 ret = KEY_DOWN;
946 break;
947 case 'C':
948 ret = KEY_RIGHT;
949 break;
950 case 'D':
951 ret = KEY_LEFT;
952 break;
953 default:
954 /* probably not good to pass it through as-is */
955 ret = KEY_OTHER;
956 }
957
958 goto done_consuming;
959 }
960 }
961 }
962
963 ret = session->ibuf[0];
964 consumed = 1;
965
966 done_consuming:
967 if (session->ibuflen == consumed)
968 session->ibuflen = 0;
969 else {
970 memmove(session->ibuf, session->ibuf + consumed,
971 session->ibuflen - consumed);
972 session->ibuflen--;
973 }
974
975 if (session->last_input == '\r' && (ret == '\n' || ret == 0)) {
976 /* we already responded to the \r, just ignore this */
977 session->last_input = ret;
978 goto wait_for_char;
979 }
980
981 session->last_input = ret;
982 return ret;
983
984 idled_out:
985 if (session->logged_in)
986 session_logf(session, "Idle too long, logging out");
987 else
988 session_logf(session, "Took too long to login, disconnecting");
989 session_printf(session,
990 "\r\n\r\nYou have been idle too long, goodbye.\r\n\r\n");
991 session_flush(session);
992 session->ending = true;
993 return 0;
994 }
995
996 void
997 session_clear_input(struct session *session)
998 {
999 uthread_yield();
1000 session->node_funcs->input(session);
1001 uthread_yield();
1002 session->node_funcs->input(session);
1003
1004 session->ibuflen = 0;
1005 }
1006
1007 /*
1008 * Collect up to size-1 bytes of input with trailing null, in a field
1009 * width columns wide, optionally masking echoed output with mask char.
1010 * For multiline-capable input,
1011 */
1012 char *
1013 session_field_input(struct session *session, unsigned short size,
1014 unsigned short width, char *initial_input, bool multiline, char mask)
1015 {
1016 short ilen = 0, ipos = 0, over;
1017 long n;
1018 char *field;
1019 unsigned short c;
1020 unsigned char chc;
1021 bool redraw = false;
1022
1023 session->abort_input = false;
1024
1025 field = xmalloczero(size);
1026 if (field == NULL) {
1027 session->ending = true;
1028 return NULL;
1029 }
1030
1031 if (initial_input) {
1032 ipos = ilen = strlcpy(field, initial_input, size);
1033 /* TODO: handle initial value being longer than width */
1034 if (mask) {
1035 for (n = 0; n < ilen; n++)
1036 session_output(session, &mask, 1);
1037 } else
1038 session_output(session, field, ilen);
1039 session_flush(session);
1040 }
1041
1042 for (;;) {
1043 c = session_input_char(session);
1044 if (session->ending || session->abort_input)
1045 goto field_input_bail;
1046 switch (c) {
1047 case 0: /* session_input_char bailed */
1048 if (session->obuflen > 0) {
1049 session->node_funcs->output(session);
1050 uthread_yield();
1051 if (session->ending)
1052 goto field_input_bail;
1053 }
1054 break;
1055 case CONTROL_D: /* ^D */
1056 return field;
1057 case CONTROL_C: /* ^C */
1058 if (multiline)
1059 return field;
1060 goto field_input_bail;
1061 case BACKSPACE: /* ^H */
1062 case DELETE: /* backspace through telnet */
1063 case KEY_LEFT:
1064 if (ipos == 0)
1065 continue;
1066
1067 if (ipos < ilen && c != KEY_LEFT) {
1068 redraw = true;
1069 memmove(field + ipos - 1, field + ipos, ilen - 1);
1070 }
1071
1072 if (field[ipos - 1] == '\n') {
1073 /* need to jump up a line and go over */
1074 session_printf(session,
1075 ansi(session, ANSI_UP_N, 1, ANSI_END));
1076 over = 1;
1077 for (n = ipos - 2; n >= 0; n--) {
1078 if (field[n] == '\n')
1079 break;
1080 over++;
1081 }
1082 session_printf(session,
1083 ansi(session, ANSI_COL_N, over, ANSI_END));
1084 session_flush(session);
1085 }
1086
1087 ipos--;
1088
1089 if (c != KEY_LEFT) {
1090 ilen--;
1091 field[ilen] = '\0';
1092
1093 session_printf(session,
1094 ansi(session, ANSI_BACKSPACE, ANSI_END));
1095 }
1096
1097 if (redraw) {
1098 session_printf(session,
1099 ansi(session, ANSI_SAVE_CURSOR, ANSI_END));
1100 session_output(session, field + ipos, ilen - ipos);
1101 session_output(session, " ", 1);
1102 session_printf(session,
1103 ansi(session, ANSI_RESTORE_SAVED_CURSOR, ANSI_END));
1104 }
1105
1106 session_flush(session);
1107 break;
1108 case '\r':
1109 case '\n':
1110 if (multiline)
1111 goto append_char;
1112 return field;
1113 case KEY_RIGHT:
1114 if (ipos == ilen)
1115 continue;
1116 ipos++;
1117 session_printf(session,
1118 ansi(session, ANSI_FORWARD_N, 1, ANSI_END));
1119 session_flush(session);
1120 break;
1121 default:
1122 append_char:
1123 if ((c < 32 || c > 127) && (c != '\r' && c != '\n'))
1124 break;
1125 if (ilen >= size - 1)
1126 break;
1127 chc = c;
1128 if (ipos < ilen)
1129 memmove(field + ipos + 1, field + ipos, ilen - ipos);
1130
1131 field[ipos] = chc;
1132 ipos++;
1133 ilen++;
1134 field[ilen] = '\0';
1135 session_output(session, mask ? &mask : (char *)&chc, 1);
1136
1137 if (ipos < ilen) {
1138 if (mask) {
1139 /* TODO: repeat mask */
1140 } else {
1141 session_printf(session,
1142 ansi(session, ANSI_SAVE_CURSOR, ANSI_END));
1143 session_output(session, field + ipos,
1144 ilen - ipos);
1145 session_printf(session,
1146 ansi(session, ANSI_RESTORE_SAVED_CURSOR, ANSI_END));
1147 }
1148 }
1149
1150 if (chc == '\r') {
1151 c = '\n';
1152 goto append_char;
1153 }
1154
1155 session_flush(session);
1156 }
1157 }
1158
1159 field_input_bail:
1160 xfree(&field);
1161 return NULL;
1162 }
1163
1164 short
1165 session_login(struct session *s)
1166 {
1167 struct user *user = NULL;
1168 char junk[SHA256_DIGEST_STRING_LENGTH];
1169 char *username = NULL, *password = NULL;
1170 size_t len;
1171 short n;
1172
1173 for (n = 1; n <= 3; n++) {
1174 session_printf(s, "Username: ");
1175
1176 if (s->autologin_username[0]) {
1177 session_printf(s, "{{#}}%s\r\n", s->autologin_username);
1178 session_flush(s);
1179 username = xstrdup(s->autologin_username);
1180 if (username == NULL)
1181 goto login_bail;
1182 } else {
1183 session_flush(s);
1184 username = session_field_input(s, DB_USERNAME_LENGTH + 1,
1185 DB_USERNAME_LENGTH, NULL, false, 0);
1186 session_output(s, "\r\n", 2);
1187 session_flush(s);
1188
1189 if (username == NULL || s->ending)
1190 goto login_bail;
1191
1192 if (username[0] == '\0') {
1193 n--;
1194 xfree(&username);
1195 username = NULL;
1196 continue;
1197 }
1198 }
1199
1200 if (strcasecmp(username, GUEST_USERNAME) == 0) {
1201 session_logf(s, "Successful guest login in as %s", username);
1202 xfree(&username);
1203 return AUTH_USER_GUEST;
1204 }
1205
1206 if ((strcasecmp(username, "signup") == 0 ||
1207 strcasecmp(username, "new") == 0) && db->config.open_signup) {
1208 session_logf(s, "Successful guest signup login in as %s",
1209 username);
1210 xfree(&username);
1211 return AUTH_USER_SIGNUP;
1212 }
1213
1214 user = user_find_by_username(username);
1215
1216 if (!user && user_username_is_banned(username)) {
1217 session_logf(s, "Attempted login as banned username %s",
1218 username);
1219 s->ban_node_source = 1;
1220 goto login_bail;
1221 }
1222
1223 if (s->autologin_username[0]) {
1224 if (user) {
1225 xfree(&username);
1226 s->user = user;
1227 session_logf(s, "Automatically logged in as %s",
1228 s->autologin_username);
1229 return AUTH_USER_OK;
1230 }
1231
1232 memset(s->autologin_username, 0,
1233 sizeof(s->autologin_username));
1234 }
1235
1236 session_printf(s, "Password: ");
1237 session_flush(s);
1238 password = session_field_input(s, 64, 64, NULL, false, '*');
1239 session_output(s, "\r\n", 2);
1240 session_flush(s);
1241
1242 if (password == NULL || s->ending)
1243 goto login_bail;
1244
1245 if (user) {
1246 if (user_authenticate(user, password) == AUTH_USER_OK) {
1247 if (user->is_enabled)
1248 s->user = user;
1249 else
1250 session_logf(s, "Successful password login for %s but "
1251 "account is disabled", user->username);
1252 } else
1253 session_logf(s, "Failed password login for %s",
1254 user->username);
1255 } else {
1256 /* kill some time */
1257 SHA256Data((const u_int8_t *)password, strlen(password), junk);
1258 session_logf(s, "Failed password login for invalid user %s",
1259 username);
1260 }
1261
1262 len = strlen(username);
1263 memset(username, 0, len);
1264 xfree(&username);
1265 username = NULL;
1266
1267 len = strlen(password);
1268 memset(password, 0, len);
1269 xfree(&password);
1270 password = NULL;
1271
1272 if (s->user) {
1273 session_logf(s, "Successful password login for user %s",
1274 s->user->username);
1275 return AUTH_USER_OK;
1276 }
1277
1278 uthread_msleep(60);
1279 session_printf(s, "Login incorrect\r\n");
1280 session_flush(s);
1281
1282 if (user)
1283 xfree(&user);
1284 }
1285
1286 login_bail:
1287 if (username != NULL)
1288 xfree(&username);
1289 if (password != NULL)
1290 xfree(&password);
1291 if (!s->ban_node_source) {
1292 if (session_idled_out(s)) {
1293 session_logf(s, "Login timed out after %d seconds",
1294 db->config.max_login_seconds);
1295 session_printf(s, "\r\nLogin timed out after %d seconds\r\n",
1296 db->config.max_login_seconds);
1297 /* session_flush won't do anything since s->ending is set */
1298 s->node_funcs->output(s);
1299 } else {
1300 session_printf(s, "Thanks for playing\r\n");
1301 session_flush(s);
1302 }
1303 }
1304
1305 return AUTH_USER_FAILED;
1306 }
1307
1308 size_t
1309 session_expand_template(struct session *session, const char *tmpl,
1310 char **ret)
1311 {
1312 static char curvar[128], matchvar[128];
1313 size_t retsize, retpos;
1314 size_t vallen;
1315 short n, invar = 0, varlen = 0, doif, sep;
1316 char *varseek, *curvarpos, *val, *data;
1317 bool end_expansion = false;
1318
1319 retsize = 0;
1320 retpos = 0;
1321 data = NULL;
1322
1323 for (n = 0; tmpl[n] != '\0'; n++) {
1324 if (invar) {
1325 if (!varlen && (tmpl[n] == ' ' || tmpl[n] == '\t'))
1326 continue;
1327 if (tmpl[n] == '}' && tmpl[n + 1] == '}') {
1328 n++;
1329 goto expand_var;
1330 } else if (varlen < sizeof(curvar) - 2)
1331 curvar[varlen++] = tmpl[n];
1332 } else if (!end_expansion && tmpl[n] == '{' && tmpl[n + 1] == '{') {
1333 invar = 1;
1334 varlen = 0;
1335 n++;
1336 } else if (tmpl[n] == '\r' && tmpl[n + 1] != '\n') {
1337 if (!grow_to_fit(&data, &retsize, retpos, 2, 64)) {
1338 xfree(&data);
1339 break;
1340 }
1341 data[retpos++] = '\r';
1342 data[retpos++] = '\n';
1343 } else {
1344 if (!grow_to_fit(&data, &retsize, retpos, 1, 64)) {
1345 xfree(&data);
1346 break;
1347 }
1348 data[retpos++] = tmpl[n];
1349 }
1350
1351 continue;
1352
1353 expand_var:
1354 invar = 0;
1355 curvar[varlen] = '\0';
1356 varlen = 0;
1357
1358 if (strpos_quoted(curvar, '?') != -1 &&
1359 strpos_quoted(curvar, ':') != -1) {
1360 /* ternary: user ? "a string" : time */
1361 varseek = curvar;
1362 while (varseek[0] == ' ')
1363 varseek++;
1364 sep = strpos_quoted(varseek, '?');
1365 if (sep == -1)
1366 continue;
1367 memcpy(matchvar, varseek, sep);
1368 matchvar[sep] = '\0';
1369 curvarpos = varseek + sep + 1;
1370 while (matchvar[sep - 1] == ' ')
1371 matchvar[--sep] = '\0';
1372
1373 /* matchvar is now the condition */
1374 if (strcmp(matchvar, "user") == 0)
1375 doif = (session->user != NULL);
1376 else if (strcmp(matchvar, "sysop") == 0)
1377 doif = (session->user && session->user->is_sysop);
1378 else
1379 doif = 0;
1380
1381 while (curvarpos[0] == ' ')
1382 curvarpos++;
1383
1384 sep = strpos_quoted(curvarpos, ':');
1385 if (sep == -1)
1386 continue;
1387
1388 while (curvarpos[sep] == ' ') {
1389 curvarpos++;
1390 sep--;
1391 }
1392
1393 if (doif) {
1394 /* copy the "then" */
1395 memcpy(matchvar, curvarpos, sep);
1396 matchvar[sep] = '\0';
1397 } else {
1398 /* copy from after the "then" and : */
1399 strlcpy(matchvar, curvarpos + sep + 1, sizeof(matchvar));
1400 }
1401
1402 sep = strlen(matchvar);
1403 while (matchvar[sep - 1] == ' ')
1404 matchvar[--sep] = '\0';
1405
1406 vallen = session_expand_var(session, matchvar, &val,
1407 &end_expansion);
1408 } else {
1409 vallen = session_expand_var(session, curvar, &val,
1410 &end_expansion);
1411 }
1412
1413 if (vallen) {
1414 if (!grow_to_fit(&data, &retsize, retpos, vallen, 128)) {
1415 xfree(&data);
1416 break;
1417 }
1418 memcpy(data + retpos, val, vallen);
1419 retpos += vallen;
1420 }
1421 }
1422
1423 if (data == NULL) {
1424 *ret = NULL;
1425 return 0;
1426 }
1427
1428 data[retpos] = '\0';
1429 *ret = data;
1430 return retpos;
1431 }
1432
1433 size_t
1434 session_expand_var(struct session *session, char *ivar, char **ret,
1435 bool *end_expansion)
1436 {
1437 static char var[128], retval[128];
1438 size_t retsize, retlen, varlen, unread_count;
1439 long elapsed, telapsed;
1440 struct tm *now;
1441 short count, n;
1442 bool pad = false;
1443
1444 *ret = (char *)&retval;
1445 retlen = 0;
1446
1447 if (sscanf(ivar, "%127[^|]|%lu%n", &var, &retsize, &count) == 2 &&
1448 count > 0) {
1449 /* field of fixed length, either truncated or padded */
1450 if (retsize > sizeof(retval))
1451 retsize = sizeof(retval);
1452 pad = true;
1453 varlen = strlen(var);
1454 } else {
1455 while (ivar[0] == ' ')
1456 ivar++;
1457
1458 varlen = strlcpy(var, ivar, sizeof(var));
1459 retsize = sizeof(retval);
1460 }
1461
1462 while (varlen && var[varlen - 1] == ' ') {
1463 var[varlen - 1] = '\0';
1464 varlen--;
1465 }
1466
1467 if (strcmp(var, "B") == 0) {
1468 retlen = strlcpy(retval, ansi_bold(session), retsize);
1469 } else if (strcmp(var, "/B") == 0) {
1470 retlen = strlcpy(retval, ansi_reset(session), retsize);
1471 } else if (strcmp(var, "#") == 0) {
1472 *end_expansion = true;
1473 retlen = 0;
1474 } else if (strncmp(var, "center_", 7) == 0 &&
1475 sscanf(var, "center_%d", &count) == 1) {
1476 if (session->terminal_columns <= count)
1477 retlen = 0;
1478 else {
1479 retlen = MIN((session->terminal_columns - count) / 2, retsize);
1480 for (n = 0; n < retlen; n++)
1481 retval[n] = ' ';
1482 retval[retlen] = '\0';
1483 }
1484 } else if (strcmp(var, "logged_in_time") == 0) {
1485 memset(retval, 0, retsize);
1486 retlen = 0;
1487 elapsed = Time - session->log.logged_on_at;
1488 while (elapsed > 0) {
1489 if (elapsed < 60) {
1490 telapsed = elapsed;
1491 retlen += snprintf(retval + retlen,
1492 retsize - retlen, "%lus", telapsed);
1493 } else if (elapsed < (60 * 60)) {
1494 telapsed = elapsed / 60;
1495 retlen += snprintf(retval + retlen,
1496 retsize - retlen, "%lum", telapsed);
1497 telapsed *= 60;
1498 } else if (elapsed < (60 * 60 * 24)) {
1499 telapsed = elapsed / (60 * 60);
1500 retlen += snprintf(retval + retlen,
1501 retsize - retlen, "%luh", telapsed);
1502 telapsed *= (60 * 60);
1503 } else {
1504 telapsed = elapsed / (60 * 60 * 24);
1505 retlen += snprintf(retval + retlen,
1506 retsize - retlen, "%luh", telapsed);
1507 telapsed *= (60 * 60 * 24);
1508 }
1509 elapsed -= telapsed;
1510 }
1511 } else if (strcmp(var, "node") == 0) {
1512 retlen = strlcpy(retval, session->node, retsize);
1513 } else if (strcmp(var, "phone_number") == 0) {
1514 retlen = strlcpy(retval, db->config.phone_number, retsize);
1515 } else if (strcmp(var, "time") == 0) {
1516 now = localtime((time_t *)&Time);
1517 retlen = strftime(retval, retsize, "%H:%M", now);
1518 } else if (strcmp(var, "timezone") == 0) {
1519 retlen = strlcpy(retval, db->config.timezone, retsize);
1520 } else if (strcmp(var, "username") == 0) {
1521 retlen = strlcpy(retval, session->user ?
1522 session->user->username : GUEST_USERNAME, retsize);
1523 } else if (strcmp(var, "new_mail") == 0) {
1524 if (session->user) {
1525 mail_find_ids_for_user(session->user, &unread_count, NULL, 0,
1526 0, true);
1527 if (unread_count)
1528 retlen = sprintf(retval, "(%lu New)", unread_count);
1529 }
1530 } else if (var[0] == '"') {
1531 /* a literal string, remove leading and trailing quotes */
1532 var[varlen - 1] = '\0';
1533 varlen--;
1534 if (varlen > retsize)
1535 varlen = retsize;
1536 strlcpy(retval, var + 1, varlen);
1537 retlen = varlen - 1;
1538 } else {
1539 /* shrug */
1540 strlcpy(retval, var, varlen);
1541 retlen = varlen;
1542 }
1543
1544 if (pad && retlen < retsize) {
1545 while (retlen < retsize)
1546 retval[retlen++] = ' ';
1547 retval[retlen] = '\0';
1548 }
1549
1550 return retlen;
1551 }
1552
1553 void
1554 session_pause_return(struct session *s, char enforce, char *msg)
1555 {
1556 unsigned char c;
1557
1558 session_printf(s, "{{/B}}Press ");
1559 if (enforce == 0 || enforce == '\r')
1560 session_printf(s, "{{B}}<Enter>{{/B}} ");
1561 else if (enforce == CONTROL_C)
1562 session_printf(s, "{{B}}^C{{/B}} ");
1563 else
1564 session_printf(s, "{{B}}%c{{/B}} ", enforce);
1565
1566 if (msg)
1567 session_printf(s, msg);
1568 else
1569 session_printf(s, "to return to the main menu...");
1570 session_flush(s);
1571
1572 for (;;) {
1573 c = session_input_char(s);
1574 if (s->ending)
1575 return;
1576 if (c == 0)
1577 continue;
1578 if (enforce == 0 || enforce == c)
1579 break;
1580 }
1581 session_output(s, "\r\n", 2);
1582 session_flush(s);
1583 }
1584
1585 void
1586 session_page_sysop(struct session *s)
1587 {
1588 char *message = NULL;
1589
1590 session_printf(s, "{{B}}Page Sysop{{/B}} "
1591 "(^C to cancel)\r\n"
1592 "{{B}}-------------------------{{/B}}\r\n");
1593 session_output_view_or_printf(s, DB_VIEW_PAGE_SYSOP,
1594 "(Instructions missing)\r\n");
1595 session_flush(s);
1596
1597 session_printf(s, "{{B}}Message:{{/B}} ");
1598 session_flush(s);
1599 message = session_field_input(s, 64, 64, NULL, false, 0);
1600 session_output(s, "\r\n", 2);
1601 session_flush(s);
1602
1603 if (message == NULL || s->ending)
1604 goto page_done;
1605
1606 session_logf(s, "Paging sysop: %s", message);
1607
1608 progress("Page from %s: %s",
1609 s->user ? s->user->username : GUEST_USERNAME, message);
1610 SysBeep(30);
1611 uthread_yield();
1612
1613 chat_start(s, CHAT_WITH_SYSOP);
1614
1615 page_done:
1616 progress(NULL);
1617
1618 if (message != NULL)
1619 xfree(&message);
1620 }
1621
1622 struct session *
1623 session_first_waiting_for_sysop(void)
1624 {
1625 short n;
1626
1627 for (n = 0; n < MAX_SESSIONS; n++) {
1628 if (sessions[n] == NULL || !sessions[n]->logged_in ||
1629 !sessions[n]->chatting)
1630 continue;
1631 if (strcmp(sessions[n]->chatting_with_node, CHAT_WITH_SYSOP) != 0)
1632 continue;
1633
1634 return sessions[n];
1635 }
1636
1637 return NULL;
1638 }
1639
1640 void
1641 session_answer_page(struct session *s)
1642 {
1643 struct session *waiting;
1644
1645 waiting = session_first_waiting_for_sysop();
1646 if (waiting == NULL) {
1647 session_printf(s, "No users waiting to chat.\r\n");
1648 session_flush(s);
1649 return;
1650 }
1651
1652 progress(NULL);
1653 session_logf(s, "Answering page from %s on %s",
1654 waiting->user ? waiting->user->username : GUEST_USERNAME,
1655 waiting->node);
1656
1657 /* point paging user's chatting_with_node to ours */
1658 strlcpy(waiting->chatting_with_node, s->node,
1659 sizeof(waiting->chatting_with_node));
1660
1661 /* and join the party */
1662 chat_start(s, waiting->node);
1663 }
1664
1665 void
1666 session_print_motd(struct session *s, bool force)
1667 {
1668 struct bile_object *o;
1669 unsigned long last_seen_motd = 0, motd_id = 0;
1670 size_t size;
1671 char *view = NULL, *output = NULL;
1672
1673 if (s->user)
1674 last_seen_motd = s->user->last_motd_seen;
1675
1676 o = bile_get_nth_of_type(db->bile, 0, DB_MOTD_RTYPE);
1677 if (o == NULL || (!force && o->id <= last_seen_motd)) {
1678 if (o)
1679 xfree(&o);
1680 return;
1681 }
1682 motd_id = o->id;
1683
1684 view = xmalloc(o->size + 1);
1685 if (view == NULL) {
1686 s->ending = true;
1687 xfree(&o);
1688 return;
1689 }
1690 size = bile_read_object(db->bile, o, view, o->size);
1691 view[size] = '\0';
1692 xfree(&o);
1693
1694 size = session_expand_template(s, view, &output);
1695 xfree(&view);
1696 if (!size)
1697 return;
1698
1699 session_output(s, "\r\n", 2);
1700 session_output(s, output, size);
1701 session_output(s, "\r\n\r\n", 4);
1702 xfree(&output);
1703
1704 session_pause_return(s, 0, "to continue...");
1705
1706 if (s->user && !force) {
1707 s->user->last_motd_seen = motd_id;
1708 user_save(s->user);
1709 }
1710 }
1711
1712 void
1713 session_recents(struct session *s)
1714 {
1715 struct session_log slog;
1716 struct tm *date_tm;
1717 size_t scount, rsize, *ids;
1718 char sdate[12];
1719 short printed;
1720
1721 session_printf(s, "{{B}}Recent Logins{{/B}}\r\n");
1722 session_printf(s,
1723 "{{B}}Date User Via Speed Location{{/B}}\r\n");
1724 session_flush(s);
1725
1726 scount = bile_sorted_ids_by_type(db->sessions_bile, SL_LOG_RTYPE,
1727 &ids);
1728 for (printed = 0; printed < 10 && printed < scount; printed++) {
1729 rsize = bile_read(db->sessions_bile, SL_LOG_RTYPE,
1730 ids[scount - 1 - printed], (char *)&slog, sizeof(slog));
1731 if (rsize != sizeof(slog))
1732 panic("unexpected bile_read response %d (read %ld, "
1733 "expected %ld)", bile_error(db->sessions_bile), rsize,
1734 sizeof(slog));
1735
1736 date_tm = localtime((time_t *)&slog.logged_on_at);
1737 strftime(sdate, sizeof(sdate), "%m/%d", date_tm);
1738
1739 session_printf(s, "%-5s %-20s %-7s %-6u %-32s\r\n",
1740 sdate,
1741 slog.username,
1742 slog.via,
1743 slog.tspeed,
1744 slog.location);
1745 }
1746
1747 session_output(s, "\r\n", 2);
1748 session_flush(s);
1749 session_pause_return(s, 0, NULL);
1750
1751 xfree(&ids);
1752 }
1753
1754 void
1755 session_who(struct session *s)
1756 {
1757 char idle_s[20];
1758 char username[20];
1759 unsigned long idle;
1760 short n;
1761
1762 session_printf(s, "{{B}}Who's Online{{/B}}\r\n");
1763 session_printf(s,
1764 "{{B}}Node User Via Speed Cur Area Idle Location{{/B}}\r\n");
1765 session_flush(s);
1766
1767 for (n = 0; n < MAX_SESSIONS; n++) {
1768 if (sessions[n] == NULL || !sessions[n]->logged_in)
1769 continue;
1770
1771 idle = Time - sessions[n]->last_input_at;
1772 if (idle < 60)
1773 snprintf(idle_s, sizeof(idle_s), "%lus", idle);
1774 else if (idle < (60 * 60))
1775 snprintf(idle_s, sizeof(idle_s), "%lum", idle / 60);
1776 else if (idle < (60 * 60 * 24))
1777 snprintf(idle_s, sizeof(idle_s), "%luh", idle / (60 * 60));
1778 else
1779 snprintf(idle_s, sizeof(idle_s), "%lud", idle / (60 * 60 * 24));
1780
1781 snprintf(username, sizeof(username), "%s%s",
1782 sessions[n]->user ? sessions[n]->user->username : GUEST_USERNAME,
1783 sessions[n]->user && sessions[n]->user->is_sysop ? " (sysop)" : "");
1784
1785 session_printf(s, "%-7s %-17s %-7s %-6u %-10s %-5s %-22s\r\n",
1786 sessions[n]->node,
1787 username,
1788 sessions[n]->via,
1789 sessions[n]->tspeed,
1790 area_labels[sessions[n]->area],
1791 idle_s,
1792 sessions[n]->location);
1793 }
1794
1795 session_output(s, "\r\n", 2);
1796 session_flush(s);
1797 session_pause_return(s, 0, NULL);
1798 }
1799
1800 char
1801 session_menu(struct session *s, char *title, char *prompt,
1802 char *prompt_help, const struct session_menu_option *opts,
1803 size_t nopts, bool show_opts, char *number_prompt, short *ret_number)
1804 {
1805 size_t n;
1806 short j, num;
1807 unsigned short c;
1808 char cn[2], *num_input = NULL;
1809 short invalids = 0;
1810
1811 if (show_opts && title != NULL) {
1812 session_printf(s, "{{B}}%s{{/B}}\r\n", title);
1813 session_flush(s);
1814 }
1815
1816 print_options:
1817 if (show_opts) {
1818 for (n = 0; n < nopts; n++) {
1819 if (opts[n].key[0] == '\0')
1820 continue;
1821 session_printf(s, "{{B}}%c{{/B}}: %s\r\n", opts[n].key[0],
1822 opts[n].title);
1823 }
1824 session_flush(s);
1825 }
1826
1827 #if 0
1828 if (prompt_help) {
1829 session_printf(s, "{{B}}%s{{/B}}\r\n", prompt_help);
1830 session_flush(s);
1831 }
1832 #endif
1833
1834 for (;;) {
1835 show_prompt:
1836 session_printf(s, "{{B}}%s>{{/B}} ", prompt);
1837 session_flush(s);
1838
1839 get_menu_option:
1840 uthread_yield();
1841 c = session_input_char(s);
1842 if (s->ending)
1843 return 0;
1844 if (c == '\r' || c == 0 || c > 255)
1845 goto get_menu_option;
1846
1847 for (n = 0; n < nopts; n++) {
1848 if (opts[n].ret == '#' && c >= '0' && c <= '9') {
1849 session_printf(s, "%s:%s>%s ",
1850 ansi(s, ANSI_BACKSPACE, ANSI_BACKSPACE,
1851 ANSI_BOLD, ANSI_END), number_prompt,
1852 ansi(s, ANSI_RESET, ANSI_END));
1853 session_flush(s);
1854
1855 cn[0] = c;
1856 cn[1] = '\0';
1857 num_input = session_field_input(s, 4, 3, (char *)&cn,
1858 false, 0);
1859 session_printf(s, "\r\n");
1860 session_flush(s);
1861 if (num_input == NULL)
1862 goto show_prompt;
1863 if (num_input[0] == '\0') {
1864 xfree(&num_input);
1865 goto show_prompt;
1866 }
1867 num = atoi(num_input);
1868 xfree(&num_input);
1869 *ret_number = num;
1870 return opts[n].ret;
1871 }
1872
1873 for (j = 0; ; j++) {
1874 if (opts[n].key[j] == '\0')
1875 break;
1876 if (opts[n].key[j] == c) {
1877 session_printf(s, "%c\r\n", c);
1878 session_flush(s);
1879 return opts[n].ret;
1880 }
1881 }
1882 }
1883
1884 /*
1885 * Avoid a constant back-and-forth of the user's session spewing
1886 * bogus characters and us having to print 'invalid option' over
1887 * and over.
1888 */
1889 if (invalids > 3)
1890 goto get_menu_option;
1891
1892 session_printf(s, "%c\r\n", c);
1893 session_printf(s, invalid_option_help);
1894 session_flush(s);
1895 invalids++;
1896 }
1897 }
1898
1899 void
1900 session_prune_logs(short days)
1901 {
1902 struct bile_object *obj;
1903 struct session_log log;
1904 size_t n, size = 0, count = 0, delta;
1905 unsigned long secs, *ids = NULL;
1906
1907 if (days < 1)
1908 return;
1909
1910 secs = (60UL * 60UL * 24UL * (unsigned long)days);
1911
1912 logger_printf("[db] Pruning session logs older than %d days", days);
1913
1914 for (n = 0;
1915 (obj = bile_get_nth_of_type(db->sessions_bile, n, SL_LOG_RTYPE));
1916 n++) {
1917 bile_read_object(db->sessions_bile, obj, &log, sizeof(log));
1918
1919 delta = Time - log.logged_on_at;
1920 if (delta > secs) {
1921 if (!grow_to_fit(&ids, &size, count * sizeof(unsigned long),
1922 sizeof(unsigned long), 10 * sizeof(unsigned long)))
1923 break;
1924 ids[count++] = obj->id;
1925 }
1926
1927 xfree(&obj);
1928 }
1929
1930 uthread_yield();
1931
1932 if (count) {
1933 logger_printf("[db] Deleting %ld of %ld log entries", count, n);
1934
1935 for (n = 0; n < count; n++)
1936 bile_delete(db->sessions_bile, SL_LOG_RTYPE, ids[n], 0);
1937 bile_write_map(db->sessions_bile);
1938 }
1939
1940 if (ids)
1941 xfree(&ids);
1942 }