AmendHub

Download

jcs

/

subtext

/

session.c

 

(View History)

jcs   session: Add session_get_chars to take multiple chars at once Latest amendment: 494 on 2023-04-27

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