AmendHub

Download

jcs

/

subtext

/

session.c

 

(View History)

jcs   session: Fix some memory leaks Latest amendment: 235 on 2022-08-03

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 "mail.h"
27 #include "subtext.h"
28 #include "session.h"
29 #include "settings.h"
30 #include "signup.h"
31 #include "sysop.h"
32 #include "user.h"
33 #include "user_settings.h"
34 #include "uthread.h"
35 #include "util.h"
36
37 #include "console.h"
38
39 struct session *sessions[MAX_SESSIONS] = { 0 };
40 short nsessions = 0;
41
42 struct session_tally session_today_tally = { 0 };
43
44 void session_run(struct uthread *uthread, void *arg);
45 short session_login(struct session *s);
46 size_t session_vprintf(struct session *session, const char *format,
47 va_list ap);
48 size_t session_expand_var(struct session *session, char *ivar, char **ret,
49 bool *end_expansion);
50 void session_page_sysop(struct session *s);
51 void session_answer_page(struct session *s);
52 void sysop_edit_settings(struct session *s);
53 bool session_idled_out(struct session *session);
54
55 void
56 session_load_tally(void)
57 {
58 char date[9];
59 struct tm *date_tm;
60
61 date_tm = localtime((time_t *)&Time);
62 strftime(date, sizeof(date), "%Y%m%d", date_tm);
63 if (bile_read(db->sessions_bile, SL_TALLY_RTYPE, 1,
64 (char *)&session_today_tally,
65 sizeof(session_today_tally)) != sizeof(session_today_tally) ||
66 strcmp(date, session_today_tally.date) != 0) {
67 strlcpy(session_today_tally.date, date,
68 sizeof(session_today_tally.date));
69 session_today_tally.calls = 0;
70 }
71 }
72
73 struct session *
74 session_create(char *node, char *via, struct node_funcs *node_funcs)
75 {
76 struct session *session;
77 size_t n;
78
79 /* reserve 1 session for the console */
80 if (nsessions >= MAX_SESSIONS - 1) {
81 if (strcmp(node, "console") != 0)
82 return NULL;
83
84 /* but only 1 */
85 if (nsessions == MAX_SESSIONS)
86 return NULL;
87 }
88
89 nsessions++;
90
91 session = xmalloczero(sizeof(struct session), "session_create");
92 session->uthread = uthread_add(session_run, session);
93 if (!session->uthread) {
94 xfree(&session);
95 return NULL;
96 }
97
98 session->node_funcs = node_funcs;
99 strlcpy(session->node, node, sizeof(session->node));
100 strlcpy(session->log.node, node, sizeof(session->log.node));
101 strlcpy(session->via, via, sizeof(session->via));
102 session->last_input_at = session->established_at = Time;
103
104 for (n = 0; n < MAX_SESSIONS; n++) {
105 if (sessions[n] == NULL) {
106 sessions[n] = session;
107 return session;
108 }
109 }
110
111 panic("session_create failed to find slot for new session (%d)",
112 nsessions);
113 }
114
115 void
116 session_run(struct uthread *uthread, void *arg)
117 {
118 struct session *s = (struct session *)arg;
119 char date[9];
120 struct tm *date_tm;
121 unsigned short c, last_c = 0;
122 bool done = false;
123 short auth;
124
125 /* until we negotiate otherwise */
126 s->terminal_columns = DEFAULT_TERMINAL_COLUMNS;
127 s->terminal_lines = DEFAULT_TERMINAL_LINES;
128 snprintf(s->terminal_type, sizeof(s->terminal_type), "vt100");
129
130 if (s->node_funcs->setup) {
131 s->node_funcs->setup(s);
132
133 if (s->ending) {
134 session_close(s);
135 return;
136 }
137 }
138
139 session_output_view_or_printf(s, DB_TEXT_ISSUE_ID,
140 "\r\nWelcome to %s (%s)\r\n\r\n", db->config.name, s->node);
141 session_flush(s);
142 uthread_yield();
143
144 auth = session_login(s);
145 if (auth == AUTH_USER_FAILED) {
146 session_close(s);
147 return;
148 }
149
150 s->logged_in = true;
151 logger_update_title();
152
153 /* update session log */
154 if (s->user) {
155 s->user->last_seen_at = Time;
156 user_save(s->user);
157
158 strlcpy(s->log.username, s->user->username,
159 sizeof(s->log.username));
160 } else
161 strlcpy(s->log.username, GUEST_USERNAME, sizeof(s->log.username));
162
163 strlcpy(s->log.via, s->via, sizeof(s->log.via));
164 s->log.tspeed = s->tspeed;
165 s->log.logged_on_at = Time;
166 s->log.id = bile_next_id(db->sessions_bile, SL_LOG_RTYPE);
167
168 if (bile_write(db->sessions_bile, SL_LOG_RTYPE, s->log.id, &s->log,
169 sizeof(s->log)) != sizeof(s->log))
170 panic("bile_write of session log failed: %d",
171 bile_error(db->sessions_bile));
172
173 /* update call tally for today */
174 date_tm = localtime((time_t *)&Time);
175 strftime(date, sizeof(date), "%Y%m%d", date_tm);
176 if (strcmp(date, session_today_tally.date) != 0) {
177 strlcpy(session_today_tally.date, date,
178 sizeof(session_today_tally.date));
179 session_today_tally.calls = 0;
180 }
181 session_today_tally.calls++;
182 bile_write(db->sessions_bile, SL_TALLY_RTYPE, 1,
183 (char *)&session_today_tally, sizeof(session_today_tally));
184
185 session_printf(s, "\r\n"
186 "Welcome, {{B}}%s{{/B}}, you are the %ld%s caller today.\r\n",
187 s->user ? s->user->username : GUEST_USERNAME,
188 session_today_tally.calls, ordinal(session_today_tally.calls));
189 session_flush(s);
190 uthread_yield();
191
192 main_menu:
193 session_output_view_or_printf(s, DB_TEXT_MENU_ID,
194 "\r\n[ Menu missing! ]\r\n\r\n");
195 session_flush(s);
196
197 while (!done && !s->ending) {
198 session_printf(s, "{{B}}Main Menu>{{/B}} ");
199 session_flush(s);
200
201 get_another_char:
202 if (auth == AUTH_USER_SIGNUP) {
203 c = 's';
204 auth = AUTH_USER_GUEST;
205 } else
206 c = session_input_char(s);
207
208 if (s->ending)
209 break;
210 if (c == '\r' || c == 0 || c > 255)
211 goto get_another_char;
212
213 session_printf(s, "%c\r\n", c);
214 session_flush(s);
215
216 /* TODO: make letter->command dynamic from a resource */
217 switch (c) {
218 case 'c':
219 case 'C':
220 /* chat */
221 chat_start(s, NULL);
222 break;
223 case 'f':
224 case 'F':
225 /* files */
226 folder_list(s);
227 break;
228 case 'g':
229 case 'G':
230 /* goodbye */
231 session_output_view_or_printf(s, DB_TEXT_SIGNOFF_ID,
232 "Goodbye!\r\n");
233 session_flush(s);
234 done = true;
235 break;
236 case 'l':
237 case 'L':
238 session_recents(s);
239 break;
240 case 'm':
241 case 'M':
242 mail_menu(s);
243 break;
244 case 'p':
245 case 'P':
246 if (s->user && s->user->is_sysop)
247 session_answer_page(s);
248 else
249 session_page_sysop(s);
250 break;
251 case 's':
252 case 'S':
253 if (s->user)
254 user_settings_menu(s);
255 else if ((s->user = signup(s))) {
256 session_printf(s, "\r\n"
257 "Welcome, {{B}}%s{{/B}}!\r\n", s->user->username);
258 goto main_menu;
259 }
260 break;
261 case 'w':
262 case 'W':
263 /* who's online */
264 session_who(s);
265 break;
266 case '1':
267 case '2':
268 case '3':
269 case '4':
270 case '5':
271 case '6':
272 case '7':
273 case '8':
274 case '9':
275 case '0':
276 board_show(s, c - '0');
277 break;
278 case '!':
279 if (!s->user || !s->user->is_sysop) {
280 session_printf(s, "Invalid option\r\n");
281 session_flush(s);
282 break;
283 }
284 sysop_menu(s);
285 break;
286 case '?':
287 if (last_c == '?') {
288 /* asking twice in a row will print the full menu */
289 session_output_view_or_printf(s, DB_TEXT_MENU_ID,
290 "\r\n[ Menu missing! ]\r\n\r\n");
291 } else {
292 if (session_output_view_or_printf(s, DB_TEXT_SHORTMENU_ID,
293 NULL) == 0)
294 session_output_view_or_printf(s, DB_TEXT_MENU_ID,
295 "\r\n[ Short and long menu missing! ]\r\n\r\n");
296 }
297 session_flush(s);
298 break;
299 default:
300 session_printf(s, "Invalid option\r\n");
301 session_flush(s);
302 break;
303 }
304
305 last_c = c;
306 }
307
308 session_close(s);
309 }
310
311 void
312 session_close(struct session *session)
313 {
314 unsigned long now;
315 short n;
316 bool found;
317
318 if (!session->ending) {
319 if (!session->ban_node_source) {
320 /* give 1 second to flush output */
321 now = Ticks;
322 while (session->obuflen && (Ticks - now) < 60) {
323 session->node_funcs->output(session);
324 uthread_yield();
325 }
326 }
327 session->ending = true;
328 }
329
330 /* finalize session log */
331 session->log.logged_off_at = Time;
332
333 if (bile_write(db->sessions_bile, SL_LOG_RTYPE, session->log.id,
334 &session->log, sizeof(session->log)) != sizeof(session->log))
335 panic("bile_write of session log failed: %d",
336 bile_error(db->sessions_bile));
337
338 /* close the node */
339 session->node_funcs->close(session);
340
341 /* remove session from sessions */
342 found = false;
343 for (n = 0; n < MAX_SESSIONS; n++) {
344 if (sessions[n] == session) {
345 sessions[n] = NULL;
346 found = true;
347 break;
348 }
349 }
350
351 if (!found)
352 panic("session_close failed to find session to remove");
353
354 nsessions--;
355 if (session->user)
356 xfree(&session->user);
357 xfree(&session);
358
359 logger_update_title();
360 }
361
362 void
363 session_flush(struct session *session)
364 {
365 if (session->ending || session->obuflen == 0)
366 return;
367
368 while (session->obuflen != 0) {
369 session->node_funcs->output(session);
370 if (session->obuflen != 0)
371 uthread_yield();
372 if (session->ending)
373 return;
374
375 /*
376 * The console will leave \e in the output buffer if it's the only
377 * thing in there, so don't loop forever.
378 */
379 if (session->obuflen == 1 && session->obuf[0] == '\33')
380 return;
381 }
382 }
383
384 size_t
385 session_log(struct session *session, const char *format, ...)
386 {
387 static char session_log_tbuf[256];
388 va_list ap;
389
390 va_start(ap, format);
391 vsnprintf(session_log_tbuf, sizeof(session_log_tbuf), format, ap);
392 va_end(ap);
393
394 return logger_printf(logger, "[%s] [%s] %s", session->node,
395 session->logged_in ?
396 (session->user ? session->user->username : "guest") : "-",
397 session_log_tbuf);
398 }
399
400 size_t
401 session_output(struct session *session, const char *str, size_t len)
402 {
403 size_t chunk, olen = len, stroff = 0;
404
405 while (len && !session->ending) {
406 chunk = (sizeof(session->obuf) - session->obuflen);
407 if (chunk == 0) {
408 session_flush(session);
409 if (session->ending)
410 return 0;
411 continue;
412 }
413 if (chunk > len)
414 chunk = len;
415
416 memcpy(session->obuf + session->obuflen, str + stroff, chunk);
417 session->obuflen += chunk;
418 stroff += chunk;
419 len -= chunk;
420 }
421
422 return olen;
423 }
424
425 size_t
426 session_printf(struct session *session, const char *format, ...)
427 {
428 va_list ap;
429 size_t len;
430
431 va_start(ap, format);
432 len = session_vprintf(session, format, ap);
433 va_end(ap);
434
435 return len;
436 }
437
438 size_t
439 session_vprintf(struct session *session, const char *format, va_list ap)
440 {
441 static char session_printf_ebuf[160], session_printf_tbuf[160];
442 size_t len, n, en;
443 bool stop = false;
444
445 /* avoid a full session_expand_template for {{B}}, {{/B}}, and {{#}} */
446 session_printf_ebuf[0] = '\0';
447 for (n = 0, en = 0; format[n] != '\0'; n++) {
448 if (!stop) {
449 if (format[n] == '{' && format[n + 1] == '{' &&
450 format[n + 2] == 'B' && format[n + 3] == '}' &&
451 format[n + 4] == '}') {
452 en = strlcat(session_printf_ebuf, ansi_bold(session),
453 sizeof(session_printf_ebuf));
454 n += 4;
455 continue;
456 }
457 if (format[n] == '{' &&
458 format[n + 1] == '{' && format[n + 2] == '/' &&
459 format[n + 3] == 'B' && format[n + 4] == '}' &&
460 format[n + 5] == '}') {
461 en = strlcat(session_printf_ebuf, ansi_reset(session),
462 sizeof(session_printf_ebuf));
463 n += 5;
464 continue;
465 }
466 if (format[n] == '{' &&
467 format[n + 1] == '{' && format[n + 2] == '#' &&
468 format[n + 3] == '}' && format[n + 4] == '}') {
469 stop = true;
470 n += 4;
471 continue;
472 }
473 }
474
475 session_printf_ebuf[en++] = format[n];
476
477 if (en >= sizeof(session_printf_ebuf))
478 panic("session_printf_ebuf overflow!");
479 session_printf_ebuf[en] = '\0';
480 }
481
482 len = vsnprintf(session_printf_tbuf, sizeof(session_printf_tbuf),
483 session_printf_ebuf, ap);
484
485 if (len > sizeof(session_printf_tbuf))
486 panic("session_printf overflow! (%ld > %ld)", len,
487 sizeof(session_printf_tbuf));
488
489 return session_output(session, session_printf_tbuf, len);
490 }
491
492 size_t
493 session_output_view_or_printf(struct session *session, short id,
494 const char *format, ...)
495 {
496 size_t size;
497 struct bile_object *o;
498 char *view, *output;
499 va_list ap;
500
501 /* can't use bile_read_alloc because we need to null terminate */
502 o = bile_find(db->bile, DB_TEXT_TYPE, id);
503 if (o == NULL || o->size == 0) {
504 if (o)
505 xfree(&o);
506
507 if (format == NULL)
508 return 0;
509
510 va_start(ap, format);
511 size = session_vprintf(session, format, ap);
512 va_end(ap);
513
514 return size;
515 }
516
517 view = xmalloc(o->size + 1, "session_output_view_or_printf");
518 size = bile_read_object(db->bile, o, view, o->size);
519 view[size] = '\0';
520 xfree(&o);
521
522 size = session_expand_template(session, view, &output);
523 if (!size) {
524 xfree(&view);
525 return 0;
526 }
527
528 size = session_output(session, output, size);
529 xfree(&output);
530 xfree(&view);
531
532 return size;
533 }
534
535 bool
536 session_idled_out(struct session *session)
537 {
538 if (session->logged_in) {
539 if (Time - session->last_input_at >
540 (db->config.max_idle_minutes * 60))
541 return true;
542 } else {
543 if (Time - session->established_at >
544 db->config.max_login_seconds)
545 return true;
546 }
547
548 return false;
549 }
550
551 short
552 session_get_char(struct session *session, unsigned char *b)
553 {
554 if (session->ibuflen == 0) {
555 if (session_idled_out(session))
556 return 0;
557
558 uthread_yield();
559 session->node_funcs->input(session);
560
561 if (session->ending || session->abort_input)
562 return 0;
563 if (session->ibuflen == 0)
564 return 0;
565 }
566
567 session->last_input_at = Time;
568
569 *b = session->ibuf[session->ibufoff];
570
571 if (session->ibuflen == 1) {
572 session->ibuflen = 0;
573 session->ibufoff = 0;
574 } else {
575 session->ibuflen--;
576 session->ibufoff++;
577 }
578
579 return 1;
580 }
581
582 bool
583 session_wait_for_chars(struct session *session, unsigned short timeout_ms,
584 unsigned short num_chars)
585 {
586 unsigned long expire = 0;
587
588 if (timeout_ms)
589 expire = Ticks + (timeout_ms / ((double)1000 / (double)60));
590
591 while (session->ibuflen < num_chars) {
592 session->node_funcs->input(session);
593 if (session->ending || session->abort_input)
594 return false;
595 if (session->obuflen != 0 && session->chatting)
596 return false;
597 if (expire && Ticks > expire)
598 return false;
599 if (session_idled_out(session))
600 return false;
601 if (session->ibuflen >= num_chars)
602 break;
603 uthread_yield();
604 }
605
606 session->last_input_at = Time;
607 return true;
608 }
609
610 unsigned short
611 session_input_char(struct session *session)
612 {
613 unsigned short ret;
614 short waiting_for = 1;
615 short consumed = 0;
616
617 wait_for_char:
618 if (session->ibuflen < waiting_for &&
619 !session_wait_for_chars(session, 0, waiting_for)) {
620 if (session_idled_out(session))
621 goto idled_out;
622 return 0;
623 }
624
625 if (session->ibuf[0] == ESC) {
626 /* look for a VT100 escape sequence */
627 if (session->ibuflen == 1) {
628 waiting_for = 2;
629 goto wait_for_char;
630 } else if (session->ibuf[1] == '[') {
631 if (session->ibuflen == 2) {
632 waiting_for = 3;
633 goto wait_for_char;
634 } else {
635 consumed = 3;
636 switch (session->ibuf[2]) {
637 case 'A':
638 ret = KEY_UP;
639 break;
640 case 'B':
641 ret = KEY_DOWN;
642 break;
643 case 'C':
644 ret = KEY_RIGHT;
645 break;
646 case 'D':
647 ret = KEY_LEFT;
648 break;
649 default:
650 /* probably not good to pass it through as-is */
651 ret = KEY_OTHER;
652 }
653
654 goto done_consuming;
655 }
656 }
657 }
658
659 ret = session->ibuf[0];
660 consumed = 1;
661
662 done_consuming:
663 if (session->ibuflen == consumed)
664 session->ibuflen = 0;
665 else {
666 memmove(session->ibuf, session->ibuf + consumed,
667 session->ibuflen - consumed);
668 session->ibuflen--;
669 }
670
671 if (session->last_input == '\r' && (ret == '\n' || ret == 0)) {
672 /* we already responded to the \r, just ignore this */
673 session->last_input = ret;
674 goto wait_for_char;
675 }
676
677 session->last_input = ret;
678 return ret;
679
680 idled_out:
681 if (session->logged_in)
682 session_log(session, "Idle too long, logging out");
683 else
684 session_log(session, "Took too long to login, disconnecting");
685 session_printf(session,
686 "\r\n\r\nYou have been idle too long, goodbye.\r\n\r\n");
687 session_flush(session);
688 session->ending = true;
689 return 0;
690 }
691
692 void
693 session_clear_input(struct session *session)
694 {
695 uthread_yield();
696 session->node_funcs->input(session);
697 uthread_yield();
698 session->node_funcs->input(session);
699
700 session->ibuflen = 0;
701 }
702
703 /*
704 * Collect up to size-1 bytes of input with trailing null, in a field
705 * width columns wide, optionally masking echoed output with mask char.
706 * For multiline-capable input,
707 */
708 char *
709 session_field_input(struct session *session, unsigned short size,
710 unsigned short width, char *initial_input, bool multiline, char mask)
711 {
712 short ilen = 0, ipos = 0, over;
713 long n;
714 char *field;
715 unsigned short c;
716 unsigned char chc;
717 bool redraw = false;
718
719 session->abort_input = false;
720
721 field = xmalloczero(size, "session_field_input");
722
723 if (initial_input) {
724 ipos = ilen = strlcpy(field, initial_input, size);
725 /* TODO: handle initial value being longer than width */
726 session_output(session, field, ilen);
727 session_flush(session);
728 }
729
730 for (;;) {
731 c = session_input_char(session);
732 if (session->ending || session->abort_input)
733 goto field_input_bail;
734 switch (c) {
735 case 0: /* session_input_char bailed */
736 if (session->obuflen > 0) {
737 session->node_funcs->output(session);
738 uthread_yield();
739 if (session->ending)
740 goto field_input_bail;
741 }
742 break;
743 case CONTROL_D: /* ^D */
744 return field;
745 case CONTROL_C: /* ^C */
746 if (multiline)
747 return field;
748 goto field_input_bail;
749 case BACKSPACE: /* ^H */
750 case DELETE: /* backspace through telnet */
751 case KEY_LEFT:
752 if (ipos == 0)
753 continue;
754
755 if (ipos < ilen && c != KEY_LEFT) {
756 redraw = true;
757 memmove(field + ipos - 1, field + ipos, ilen - 1);
758 }
759
760 if (field[ipos - 1] == '\n') {
761 /* need to jump up a line and go over */
762 session_printf(session,
763 ansi(session, ANSI_UP_N, 1, ANSI_END));
764 over = 1;
765 for (n = ipos - 2; n >= 0; n--) {
766 if (field[n] == '\n')
767 break;
768 over++;
769 }
770 session_printf(session,
771 ansi(session, ANSI_COL_N, over, ANSI_END));
772 session_flush(session);
773 }
774
775 ipos--;
776
777 if (c != KEY_LEFT) {
778 ilen--;
779 field[ilen] = '\0';
780
781 session_printf(session,
782 ansi(session, ANSI_BACKSPACE, ANSI_END));
783 }
784
785 if (redraw) {
786 session_printf(session,
787 ansi(session, ANSI_SAVE_CURSOR, ANSI_END));
788 session_output(session, field + ipos, ilen - ipos);
789 session_output(session, " ", 1);
790 session_printf(session,
791 ansi(session, ANSI_RESTORE_SAVED_CURSOR, ANSI_END));
792 }
793
794 session_flush(session);
795 break;
796 case '\r':
797 case '\n':
798 if (multiline)
799 goto append_char;
800 return field;
801 case KEY_RIGHT:
802 if (ipos == ilen)
803 continue;
804 ipos++;
805 session_printf(session,
806 ansi(session, ANSI_FORWARD_N, 1, ANSI_END));
807 session_flush(session);
808 break;
809 default:
810 append_char:
811 if ((c < 32 || c > 127) && (c != '\r' && c != '\n'))
812 break;
813 if (ilen >= size - 1)
814 break;
815 chc = c;
816 if (ipos < ilen)
817 memmove(field + ipos + 1, field + ipos, ilen - ipos);
818
819 field[ipos] = chc;
820 ipos++;
821 ilen++;
822 field[ilen] = '\0';
823 session_output(session, mask ? &mask : (char *)&chc, 1);
824
825 if (ipos < ilen) {
826 if (mask) {
827 /* TODO: repeat mask */
828 } else {
829 session_printf(session,
830 ansi(session, ANSI_SAVE_CURSOR, ANSI_END));
831 session_output(session, field + ipos,
832 ilen - ipos);
833 session_printf(session,
834 ansi(session, ANSI_RESTORE_SAVED_CURSOR, ANSI_END));
835 }
836 }
837
838 if (chc == '\r') {
839 c = '\n';
840 goto append_char;
841 }
842
843 session_flush(session);
844 }
845 }
846
847 field_input_bail:
848 xfree(&field);
849 return NULL;
850 }
851
852 short
853 session_login(struct session *s)
854 {
855 struct user *user = NULL;
856 char junk[SHA256_DIGEST_STRING_LENGTH];
857 char *username = NULL, *password = NULL;
858 size_t len;
859 short n;
860
861 for (n = 1; n <= 3; n++) {
862 session_printf(s, "login: ");
863
864 if (s->autologin_username[0]) {
865 session_printf(s, "{{#}}%s\r\n", s->autologin_username);
866 session_flush(s);
867 username = xstrdup(s->autologin_username,
868 "session_login username");
869 } else {
870 session_flush(s);
871 username = session_field_input(s, DB_USERNAME_LENGTH + 1,
872 DB_USERNAME_LENGTH, NULL, false, 0);
873 session_output(s, "\r\n", 2);
874 session_flush(s);
875
876 if (username == NULL || s->ending)
877 goto login_bail;
878
879 if (username[0] == '\0') {
880 n--;
881 xfree(&username);
882 username = NULL;
883 continue;
884 }
885 }
886
887 if (strcmp(username, GUEST_USERNAME) == 0) {
888 session_log(s, "Successful guest login in as %s", username);
889 xfree(&username);
890 return AUTH_USER_GUEST;
891 }
892
893 if ((strcmp(username, "signup") == 0 ||
894 strcmp(username, "new") == 0) && db->config.open_signup) {
895 session_log(s, "Successful guest signup login in as %s",
896 username);
897 xfree(&username);
898 return AUTH_USER_SIGNUP;
899 }
900
901 user = user_find_by_username(username);
902
903 if (!user && user_username_is_banned(username)) {
904 session_log(s, "Attempted login as banned username %s",
905 username);
906 s->ban_node_source = 1;
907 goto login_bail;
908 }
909
910 if (s->autologin_username[0]) {
911 if (user) {
912 xfree(&username);
913 s->user = user;
914 session_log(s, "Automatically logged in as %s",
915 s->autologin_username);
916 return AUTH_USER_OK;
917 }
918
919 memset(s->autologin_username, 0,
920 sizeof(s->autologin_username));
921 }
922
923 session_printf(s, "Password: ");
924 session_flush(s);
925 password = session_field_input(s, 64, 64, NULL, false, '*');
926 session_output(s, "\r\n", 2);
927 session_flush(s);
928
929 if (password == NULL || s->ending)
930 goto login_bail;
931
932 if (user) {
933 if (user_authenticate(user, password) == AUTH_USER_OK) {
934 if (user->is_enabled)
935 s->user = user;
936 else
937 session_log(s, "Successful password login for %s but "
938 "account is disabled", user->username);
939 } else
940 session_log(s, "Failed password login for %s",
941 user->username);
942 } else {
943 /* kill some time */
944 SHA256Data((const u_int8_t *)password, strlen(password), junk);
945 session_log(s, "Failed password login for invalid user %s",
946 username);
947 }
948
949 len = strlen(username);
950 memset(username, 0, len);
951 xfree(&username);
952 username = NULL;
953
954 len = strlen(password);
955 memset(password, 0, len);
956 xfree(&password);
957 password = NULL;
958
959 if (s->user) {
960 session_log(s, "Successful password login for user %s",
961 s->user->username);
962 return AUTH_USER_OK;
963 }
964
965 uthread_msleep(60);
966 session_printf(s, "Login incorrect\r\n");
967 session_flush(s);
968
969 if (user)
970 xfree(&user);
971 }
972
973 login_bail:
974 if (username != NULL)
975 xfree(&username);
976 if (password != NULL)
977 xfree(&password);
978 if (!s->ban_node_source) {
979 if (session_idled_out(s)) {
980 session_log(s, "Login timed out after %d seconds",
981 db->config.max_login_seconds);
982 session_printf(s, "\r\nLogin timed out after %d seconds\r\n",
983 db->config.max_login_seconds);
984 /* session_flush won't do anything since s->ending is set */
985 s->node_funcs->output(s);
986 } else {
987 session_printf(s, "Thanks for playing\r\n");
988 session_flush(s);
989 }
990 }
991
992 return AUTH_USER_FAILED;
993 }
994
995 size_t
996 session_expand_template(struct session *session, const char *tmpl,
997 char **ret)
998 {
999 static char curvar[128], matchvar[128];
1000 size_t tmpllen, retsize, retpos;
1001 size_t vallen;
1002 short n, invar = 0, varlen = 0, doif, sep;
1003 char *varseek, *curvarpos, *val, *data;
1004 bool end_expansion = false;
1005
1006 retsize = 0;
1007 retpos = 0;
1008 data = NULL;
1009 tmpllen = strlen(tmpl);
1010
1011 for (n = 0; n < tmpllen; n++) {
1012 if (invar) {
1013 if (!varlen && (tmpl[n] == ' ' || tmpl[n] == '\t'))
1014 continue;
1015 if (tmpl[n] == '}' && tmpl[n + 1] == '}') {
1016 n++;
1017 goto expand_var;
1018 } else if (varlen < sizeof(curvar) - 2)
1019 curvar[varlen++] = tmpl[n];
1020 } else if (!end_expansion && tmpl[n] == '{' && tmpl[n + 1] == '{') {
1021 invar = 1;
1022 varlen = 0;
1023 n++;
1024 } else if (tmpl[n] == '\r' && tmpl[n + 1] != '\n') {
1025 EXPAND_TO_FIT(data, retsize, retpos, 2, 64);
1026 data[retpos++] = '\r';
1027 data[retpos++] = '\n';
1028 } else {
1029 EXPAND_TO_FIT(data, retsize, retpos, 1, 64);
1030 data[retpos++] = tmpl[n];
1031 }
1032
1033 continue;
1034
1035 expand_var:
1036 invar = 0;
1037 curvar[varlen] = '\0';
1038 varlen = 0;
1039
1040 if (strpos_quoted(curvar, '?') != -1 &&
1041 strpos_quoted(curvar, ':') != -1) {
1042 /* ternary: user ? "a string" : time */
1043 varseek = curvar;
1044 while (varseek[0] == ' ')
1045 varseek++;
1046 sep = strpos_quoted(varseek, '?');
1047 if (sep == -1)
1048 continue;
1049 memcpy(matchvar, varseek, sep);
1050 matchvar[sep] = '\0';
1051 curvarpos = varseek + sep + 1;
1052 while (matchvar[sep - 1] == ' ')
1053 matchvar[--sep] = '\0';
1054
1055 /* matchvar is now the condition */
1056 if (strcmp(matchvar, "user") == 0)
1057 doif = (session->user != NULL);
1058 else if (strcmp(matchvar, "sysop") == 0)
1059 doif = (session->user && session->user->is_sysop);
1060 else
1061 doif = 0;
1062
1063 while (curvarpos[0] == ' ')
1064 curvarpos++;
1065
1066 sep = strpos_quoted(curvarpos, ':');
1067 if (sep == -1)
1068 continue;
1069
1070 while (curvarpos[sep] == ' ') {
1071 curvarpos++;
1072 sep--;
1073 }
1074
1075 if (doif) {
1076 /* copy the "then" */
1077 memcpy(matchvar, curvarpos, sep);
1078 matchvar[sep] = '\0';
1079 } else {
1080 /* copy from after the "then" and : */
1081 strlcpy(matchvar, curvarpos + sep + 1, sizeof(matchvar));
1082 }
1083
1084 sep = strlen(matchvar);
1085 while (matchvar[sep - 1] == ' ')
1086 matchvar[--sep] = '\0';
1087
1088 vallen = session_expand_var(session, matchvar, &val,
1089 &end_expansion);
1090 } else {
1091 vallen = session_expand_var(session, curvar, &val,
1092 &end_expansion);
1093 }
1094
1095 if (vallen) {
1096 EXPAND_TO_FIT(data, retsize, retpos, vallen, 128);
1097 memcpy(data + retpos, val, vallen);
1098 retpos += vallen;
1099 }
1100 }
1101 data[retpos] = '\0';
1102
1103 *ret = data;
1104 return retpos;
1105 }
1106
1107 size_t
1108 session_expand_var(struct session *session, char *ivar, char **ret,
1109 bool *end_expansion)
1110 {
1111 static char var[128], retval[128];
1112 size_t retsize, retlen, varlen, mcount;
1113 struct tm *now;
1114 short count;
1115 bool pad = false;
1116
1117 *ret = (char *)&retval;
1118 retlen = 0;
1119
1120 if (sscanf(ivar, "%[^|]|%ld%n", var, &retsize, &count) == 2 &&
1121 count > 0) {
1122 /* field of fixed length, either truncated or padded */
1123 if (retsize > sizeof(retval))
1124 retsize = sizeof(retval);
1125 pad = true;
1126 varlen = strlen(var);
1127 } else {
1128 while (ivar[0] == ' ')
1129 ivar++;
1130
1131 varlen = strlcpy(var, ivar, sizeof(var));
1132 retsize = sizeof(retval);
1133 }
1134
1135 while (varlen && var[varlen - 1] == ' ') {
1136 var[varlen - 1] = '\0';
1137 varlen--;
1138 }
1139
1140 if (strcmp(var, "B") == 0) {
1141 retlen = strlcpy(retval, ansi_bold(session), retsize);
1142 } else if (strcmp(var, "/B") == 0) {
1143 retlen = strlcpy(retval, ansi_reset(session), retsize);
1144 } else if (strcmp(var, "#") == 0) {
1145 *end_expansion = true;
1146 retlen = 0;
1147 } else if (strcmp(var, "node") == 0) {
1148 retlen = strlcpy(retval, session->node, retsize);
1149 } else if (strcmp(var, "phone_number") == 0) {
1150 retlen = strlcpy(retval, db->config.phone_number, retsize);
1151 } else if (strcmp(var, "time") == 0) {
1152 now = localtime((time_t *)&Time);
1153 retlen = strftime(retval, retsize, "%H:%M", now);
1154 } else if (strcmp(var, "username") == 0) {
1155 retlen = strlcpy(retval, session->user ?
1156 session->user->username : GUEST_USERNAME, retsize);
1157 } else if (strcmp(var, "new_mail") == 0) {
1158 if (session->user) {
1159 mcount = mail_find_ids_for_user(session->user, NULL,
1160 NULL, 0, 0, true);
1161 if (mcount)
1162 retlen = sprintf(retval, "(%ld New)", mcount);
1163 }
1164 } else if (var[0] == '"') {
1165 /* a literal string, remove leading and trailing quotes */
1166 var[varlen - 1] = '\0';
1167 varlen--;
1168 if (varlen > retsize)
1169 varlen = retsize;
1170 strlcpy(retval, var + 1, varlen);
1171 retlen = varlen - 1;
1172 } else {
1173 /* shrug */
1174 strlcpy(retval, var, varlen);
1175 retlen = varlen;
1176 }
1177
1178 if (pad && retlen < retsize) {
1179 while (retlen < retsize)
1180 retval[retlen++] = ' ';
1181 retval[retlen] = '\0';
1182 }
1183
1184 return retlen;
1185 }
1186
1187 void
1188 session_pause_return(struct session *s, char enforce, char *msg)
1189 {
1190 unsigned char c;
1191
1192 session_printf(s, "{{/B}}Press ");
1193 if (enforce == 0 || enforce == '\r')
1194 session_printf(s, "{{B}}<Enter>{{/B}} ");
1195 else if (enforce == CONTROL_C)
1196 session_printf(s, "{{B}}^C{{/B}} ");
1197 else
1198 session_printf(s, "{{B}}%c{{/B}} ", enforce);
1199
1200 if (msg)
1201 session_printf(s, msg);
1202 else
1203 session_printf(s, "to return to the main menu...");
1204 session_flush(s);
1205
1206 for (;;) {
1207 c = session_input_char(s);
1208 if (s->ending)
1209 return;
1210 if (c == 0)
1211 continue;
1212 if (enforce == 0 || enforce == c)
1213 break;
1214 }
1215 session_output(s, "\r\n", 2);
1216 session_flush(s);
1217 }
1218
1219 void
1220 session_page_sysop(struct session *s)
1221 {
1222 char *message = NULL;
1223
1224 session_printf(s, "{{B}}Page Sysop{{/B}} "
1225 "(^C to cancel)\r\n"
1226 "{{B}}-------------------------{{/B}}\r\n");
1227 session_output_view_or_printf(s, DB_TEXT_PAGE_SYSOP_ID,
1228 "(Instructions missing)\r\n");
1229 session_flush(s);
1230
1231 session_printf(s, "{{B}}Message:{{/B}} ");
1232 session_flush(s);
1233 message = session_field_input(s, 64, 64, NULL, false, 0);
1234 session_output(s, "\r\n", 2);
1235 session_flush(s);
1236
1237 if (message == NULL || s->ending)
1238 goto page_done;
1239
1240 /* TODO: show message on the screen */
1241
1242 /* TODO: enter chat with sysop */
1243
1244 session_printf(s, "{{B}}TODO!{{/B}}\r\n");
1245 session_flush(s);
1246
1247 page_done:
1248 if (message != NULL)
1249 xfree(&message);
1250 }
1251
1252 void
1253 session_answer_page(struct session *s)
1254 {
1255 /* TODO */
1256 }
1257
1258 void
1259 session_recents(struct session *s)
1260 {
1261 struct session_log slog;
1262 struct tm *date_tm;
1263 size_t scount, rsize, *ids;
1264 char sdate[12];
1265 short printed;
1266
1267 session_printf(s, "{{B}}Recent Logins{{/B}}\r\n");
1268 session_printf(s,
1269 "{{B}}Date Node User Via Speed{{/B}}\r\n");
1270 session_flush(s);
1271
1272 scount = bile_sorted_ids_by_type(db->sessions_bile, SL_LOG_RTYPE,
1273 &ids);
1274 for (printed = 0; printed < 10 && printed < scount; printed++) {
1275 rsize = bile_read(db->sessions_bile, SL_LOG_RTYPE,
1276 ids[scount - 1 - printed], (char *)&slog, sizeof(slog));
1277 if (rsize != sizeof(slog))
1278 panic("unexpected bile_read response %d (read %ld, "
1279 "expected %ld)", bile_error(db->sessions_bile), rsize,
1280 sizeof(slog));
1281
1282 date_tm = localtime((time_t *)&slog.logged_on_at);
1283 strftime(sdate, sizeof(sdate), "%m/%d", date_tm);
1284
1285 session_printf(s, "%-7s %-7s %-20s %-7s %-6u\r\n",
1286 sdate,
1287 slog.node,
1288 slog.username,
1289 slog.via,
1290 slog.tspeed);
1291 }
1292
1293 session_output(s, "\r\n", 2);
1294 session_flush(s);
1295 session_pause_return(s, 0, NULL);
1296
1297 xfree(&ids);
1298 }
1299
1300 void
1301 session_who(struct session *s)
1302 {
1303 char idle_s[20];
1304 char username[20];
1305 unsigned long idle;
1306 short n;
1307
1308 session_printf(s, "{{B}}Who's Online{{/B}}\r\n");
1309 session_printf(s,
1310 "{{B}}Node User Via Speed Idle{{/B}}\r\n");
1311 session_flush(s);
1312
1313 for (n = 0; n < MAX_SESSIONS; n++) {
1314 if (sessions[n] == NULL || !sessions[n]->logged_in)
1315 continue;
1316
1317 idle = Time - sessions[n]->last_input_at;
1318 if (idle < 60)
1319 sprintf(idle_s, "%lds", idle);
1320 else if (idle < (60 * 60))
1321 sprintf(idle_s, "%ldm", idle / 60);
1322 else if (idle < (60 * 60 * 24))
1323 sprintf(idle_s, "%ldh", idle / (60 * 60));
1324 else
1325 sprintf(idle_s, "%ldd", idle / (60 * 60 * 24));
1326
1327 snprintf(username, sizeof(username), "%s%s",
1328 sessions[n]->user ? sessions[n]->user->username : GUEST_USERNAME,
1329 sessions[n]->user && sessions[n]->user->is_sysop ? " (sysop)" : "");
1330
1331 session_printf(s, "%-7s %-20s %-7s %-6u %-6s\r\n",
1332 sessions[n]->node,
1333 username,
1334 sessions[n]->via,
1335 sessions[n]->tspeed,
1336 idle_s);
1337 }
1338
1339 session_output(s, "\r\n", 2);
1340 session_flush(s);
1341 session_pause_return(s, 0, NULL);
1342 }
1343
1344 char
1345 session_menu(struct session *s, char *title, char *prompt,
1346 const struct session_menu_option *opts, size_t nopts, bool show_opts)
1347 {
1348 size_t n;
1349 short j;
1350 unsigned short c;
1351 bool last_invalid = false;
1352
1353 if (show_opts) {
1354 session_printf(s, "{{B}}%s{{/B}}\r\n", title);
1355 session_flush(s);
1356 }
1357
1358 print_options:
1359 if (show_opts) {
1360 for (n = 0; n < nopts; n++) {
1361 if (opts[n].key[0] == '\0')
1362 continue;
1363 session_printf(s, "{{B}}%c{{/B}}: %s\r\n", opts[n].key[0],
1364 opts[n].title);
1365 }
1366 session_flush(s);
1367 }
1368
1369 for (;;) {
1370 session_printf(s, "{{B}}%s>{{/B}} ", prompt);
1371 session_flush(s);
1372
1373 get_menu_option:
1374 uthread_yield();
1375 c = session_input_char(s);
1376 if (s->ending)
1377 return 0;
1378 if (c == '\r' || c == 0 || c > 255)
1379 goto get_menu_option;
1380
1381 for (n = 0; n < nopts; n++) {
1382 for (j = 0; ; j++) {
1383 if (opts[n].key[j] == '\0')
1384 break;
1385 if (opts[n].ret == '#') {
1386 if (opts[n].key[j] == c && c != '#') {
1387 session_printf(s, "%c\r\n", c);
1388 session_flush(s);
1389 return c - '0'; /* return actual number */
1390 }
1391 } else if (opts[n].key[j] == c) {
1392 session_printf(s, "%c\r\n", c);
1393 session_flush(s);
1394 return opts[n].ret;
1395 }
1396 }
1397 }
1398
1399 /*
1400 * Avoid a constant back-and-forth of the user's session spewing
1401 * bogus characters and us having to print 'invalid option' over
1402 * and over.
1403 */
1404 if (last_invalid)
1405 goto get_menu_option;
1406
1407 session_printf(s, "%c\r\nInvalid option (press ? for help)\r\n", c);
1408 session_flush(s);
1409 last_invalid = true;
1410 }
1411 }