* Completed 'fsck'-like reference count verifier (server and client)
[citadel.git] / citadel / commands.c
1 /*
2  * $Id$
3  *
4  * This file contains functions which implement parts of the
5  * text-mode user interface.
6  *
7  */
8
9 #include "sysdep.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #include <sys/time.h>
18
19 #ifdef HAVE_TERMIOS_H
20 #include <termios.h>
21 #else
22 #include <sgtty.h>
23 #endif
24
25 #ifdef HAVE_SYS_SELECT_H
26 #include <sys/select.h>
27 #endif
28
29
30 #include <signal.h>
31 #include <errno.h>
32 #include <stdarg.h>
33 #include "citadel.h"
34 #include "commands.h"
35 #include "messages.h"
36 #include "citadel_decls.h"
37 #include "routines.h"
38 #include "routines2.h"
39 #include "tools.h"
40 #ifndef HAVE_SNPRINTF
41 #include "snprintf.h"
42 #endif
43
44 struct citcmd {
45         struct citcmd *next;
46         int c_cmdnum;
47         int c_axlevel;
48         char c_keys[5][64];
49 };
50
51 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
52
53
54 int rc_exp_beep;
55 char rc_exp_cmd[256];
56 int rc_allow_attachments;
57 int rc_display_message_numbers;
58 int rc_force_mail_prompts;
59 int rc_ansi_color;
60 int num_urls = 0;
61 char urls[MAXURLS][256];
62 char rc_url_cmd[256];
63
64 char *gl_string;
65 int next_lazy_cmd = 5;
66
67 struct citcmd *cmdlist = NULL;
68
69
70 /* these variables are local to this module */
71 char keepalives_enabled = KA_YES;       /* send NOOPs to server when idle */
72 int ok_to_interrupt = 0;                /* print express msgs asynchronously */
73 time_t AnsiDetect;                      /* when did we send the detect code? */
74 int enable_color = 0;                   /* nonzero for ANSI color */
75
76
77 /*
78  * print_express()  -  print express messages if there are any
79  */
80 void print_express(void)
81 {
82         char buf[256];
83         FILE *outpipe;
84
85         if (express_msgs == 0)
86                 return;
87         express_msgs = 0;
88         serv_puts("PEXP");
89         serv_gets(buf);
90         if (buf[0] != '1')
91                 return;
92
93         if (strlen(rc_exp_cmd) > 0) {
94                 outpipe = popen(rc_exp_cmd, "w");
95                 if (outpipe != NULL) {
96                         while (serv_gets(buf), strcmp(buf, "000")) {
97                                 fprintf(outpipe, "%s\n", buf);
98                         }
99                         pclose(outpipe);
100                         return;
101                 }
102         }
103         /* fall back to built-in express message display */
104         if (rc_exp_beep) {
105                 putc(7, stdout);
106         }
107         color(BRIGHT_RED);
108         printf("\r---\n");
109         while (serv_gets(buf), strcmp(buf, "000")) {
110                 printf("%s\n", buf);
111         }
112         printf("---\n");
113         color(BRIGHT_WHITE);
114 }
115
116
117 void set_keepalives(int s)
118 {
119         keepalives_enabled = (char) s;
120 }
121
122 /* 
123  * This loop handles the "keepalive" messages sent to the server when idling.
124  */
125 void do_keepalive(void)
126 {
127         char buf[256];
128         static time_t idlet = 0;
129         time_t now;
130
131         time(&now);
132         if ((now - idlet) < ((long) S_KEEPALIVE))
133                 return;
134         time(&idlet);
135
136         /* Do a space-backspace to keep telnet sessions from idling out */
137         printf(" %c", 8);
138         fflush(stdout);
139
140         if (keepalives_enabled != KA_NO) {
141                 serv_puts("NOOP");
142                 if (keepalives_enabled == KA_YES) {
143                         serv_gets(buf);
144                         if (buf[3] == '*') {
145                                 express_msgs = 1;
146                                 if (ok_to_interrupt == 1) {
147                                         printf("\r%64s\r", "");
148                                         print_express();
149                                         printf("%s%c ", room_name,
150                                                room_prompt(room_flags));
151                                         fflush(stdout);
152                                 }
153                         }
154                 }
155         }
156 }
157
158
159 int inkey(void)
160 {                               /* get a character from the keyboard, with   */
161         int a;                  /* the watchdog timer in effect if necessary */
162         fd_set rfds;
163         struct timeval tv;
164         time_t start_time, now;
165         char inbuf[2];
166
167         fflush(stdout);
168         time(&start_time);
169
170         do {
171
172                 /* This loop waits for keyboard input.  If the keepalive
173                  * timer expires, it sends a keepalive to the server if
174                  * necessary and then waits again.
175                  */
176                 do {
177                         do_keepalive();
178                         FD_ZERO(&rfds);
179                         FD_SET(0, &rfds);
180                         tv.tv_sec = S_KEEPALIVE;
181                         tv.tv_usec = 0;
182
183                         time(&now);
184                         if (((now - start_time) > SLEEPING)
185                             && (SLEEPING != 0) && (getppid() == 1)) {
186                                 printf("Sleeping? Call again.\n");
187                                 logoff(SIGALRM);
188                         }
189                         select(1, &rfds, NULL, NULL, &tv);
190                 } while (!FD_ISSET(0, &rfds));
191
192
193
194
195                 /* At this point, there's input, so fetch it.
196                  * (There's a hole in the bucket...)
197                  */
198                 read(0, inbuf, 1);
199                 a = inbuf[0];
200                 if (a == 127)
201                         a = 8;
202                 if (a > 126)
203                         a = 0;
204                 if (a == 10)
205                         a = 13;
206                 if (((a != 4) && (a != 13) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
207                     && ((a < 32) || (a > 126)))
208                         a = 0;
209         } while (a == 0);
210         return (a);
211 }
212
213
214 int yesno(void)
215 {                               /* Returns 1 for yes, 0 for no */
216         int a;
217         while (1) {
218                 a = inkey();
219                 a = tolower(a);
220                 if (a == 'y') {
221                         printf("Yes\n");
222                         return (1);
223                 }
224                 if (a == 'n') {
225                         printf("No\n");
226                         return (0);
227                 }
228         }
229 }
230
231 /* Returns 1 for yes, 0 for no, arg is default value */
232 int yesno_d(int d)
233 {
234         int a;
235         while (1) {
236                 a = inkey();
237                 a = tolower(a);
238                 if (a == 13)
239                         a = (d ? 'y' : 'n');
240                 if (a == 'y') {
241                         printf("Yes\n");
242                         return (1);
243                 }
244                 if (a == 'n') {
245                         printf("No\n");
246                         return (0);
247                 }
248         }
249 }
250
251
252
253
254 /* Gets a line from the terminal */
255 /* string == Pointer to string buffer */
256 /* lim == Maximum length - if negative, no-show */
257 void getline(char *string, int lim) 
258 {
259         int a, b;
260         char flag = 0;
261
262         if (lim < 0) {
263                 lim = (0 - lim);
264                 flag = 1;
265         }
266         strcpy(string, "");
267         gl_string = string;
268       GLA:a = inkey();
269         a = (a & 127);
270         if ((a == 8) && (strlen(string) == 0))
271                 goto GLA;
272         if ((a != 13) && (a != 8) && (strlen(string) == lim))
273                 goto GLA;
274         if ((a == 8) && (string[0] != 0)) {
275                 string[strlen(string) - 1] = 0;
276                 putc(8, stdout);
277                 putc(32, stdout);
278                 putc(8, stdout);
279                 goto GLA;
280         }
281         if ((a == 13) || (a == 10)) {
282                 putc(13, stdout);
283                 putc(10, stdout);
284                 return;
285         }
286         if (a < 32)
287                 a = '.';
288         b = strlen(string);
289         string[b] = a;
290         string[b + 1] = 0;
291         if (flag == 0)
292                 putc(a, stdout);
293         if (flag == 1)
294                 putc('*', stdout);
295         goto GLA;
296 }
297
298
299 /*
300  * strprompt()  -  prompt for a string, print the existing value and
301  *                 allow the user to press return to keep it...
302  */
303 void strprompt(char *prompt, char *str, int len)
304 {
305         char buf[128];
306         print_express();
307         color(DIM_WHITE);
308         printf("%s ", prompt);
309         color(DIM_MAGENTA);
310         printf("[");
311         color(BRIGHT_MAGENTA);
312         printf("%s", str);
313         color(DIM_MAGENTA);
314         printf("]");
315         color(DIM_WHITE);
316         printf(": ");
317         color(BRIGHT_CYAN);
318         getline(buf, len);
319         if (buf[0] != 0)
320                 strcpy(str, buf);
321         color(DIM_WHITE);
322 }
323
324 /*
325  * boolprompt()  -  prompt for a yes/no, print the existing value and
326  *                  allow the user to press return to keep it...
327  */
328 int boolprompt(char *prompt, int prev_val)
329 {
330         int r;
331
332         color(DIM_WHITE);
333         printf("%s ", prompt);
334         color(DIM_MAGENTA);
335         printf(" [");
336         color(BRIGHT_MAGENTA);
337         printf("%s", (prev_val ? "Yes" : "No"));
338         color(DIM_MAGENTA);
339         printf("]: ");
340         color(BRIGHT_CYAN);
341         r = (yesno_d(prev_val));
342         color(DIM_WHITE);
343         return r;
344 }
345
346 /* 
347  * intprompt()  -  like strprompt(), except for an integer
348  *                 (note that it RETURNS the new value!)
349  */
350 int intprompt(char *prompt, int ival, int imin, int imax)
351 {
352         char buf[16];
353         int i;
354         i = ival;
355         do {
356                 snprintf(buf, sizeof buf, "%d", i);
357                 strprompt(prompt, buf, 15);
358                 i = atoi(buf);
359                 if (i < imin)
360                         printf("*** Must be no less than %d.\n", imin);
361                 if (i > imax)
362                         printf("*** Must be no more than %d.\n", imax);
363         } while ((i < imin) || (i > imax));
364         return (i);
365 }
366
367 /* 
368  * newprompt()  -  prompt for a string with no existing value
369  *                 (clears out string buffer first)
370  */
371 void newprompt(char *prompt, char *str, int len)
372 {
373         color(BRIGHT_MAGENTA);
374         printf("%s", prompt);
375         color(DIM_MAGENTA);
376         getline(str, len);
377         color(DIM_WHITE);
378 }
379
380
381 int lkey(void)
382 {                               /* returns a lower case value */
383         int a;
384         a = inkey();
385         if (isupper(a))
386                 a = tolower(a);
387         return (a);
388 }
389
390 /*
391  * parse the citadel.rc file
392  */
393 void load_command_set(void)
394 {
395         FILE *ccfile;
396         char buf[256];
397         struct citcmd *cptr;
398         struct citcmd *lastcmd = NULL;
399         int a, d;
400         int b = 0;
401
402
403         /* first, set up some defaults for non-required variables */
404
405         strcpy(editor_path, "");
406         strcpy(printcmd, "");
407         strcpy(rc_username, "");
408         strcpy(rc_password, "");
409         rc_floor_mode = 0;
410         rc_exp_beep = 1;
411         rc_allow_attachments = 0;
412         strcpy(rc_exp_cmd, "");
413         rc_display_message_numbers = 0;
414         rc_force_mail_prompts = 0;
415         rc_ansi_color = 0;
416         strcpy(rc_url_cmd, "");
417
418         /* now try to open the citadel.rc file */
419
420         ccfile = NULL;
421         if (getenv("HOME") != NULL) {
422                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
423                 ccfile = fopen(buf, "r");
424         }
425         if (ccfile == NULL) {
426                 ccfile = fopen("/usr/local/lib/citadel.rc", "r");
427         }
428         if (ccfile == NULL) {
429                 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
430                 ccfile = fopen(buf, "r");
431         }
432         if (ccfile == NULL) {
433                 ccfile = fopen("./citadel.rc", "r");
434         }
435         if (ccfile == NULL) {
436                 perror("commands: cannot open citadel.rc");
437                 logoff(errno);
438         }
439         while (fgets(buf, 256, ccfile) != NULL) {
440                 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
441                         buf[strlen(buf) - 1] = 0;
442
443                 if (!struncmp(buf, "editor=", 7))
444                         strcpy(editor_path, &buf[7]);
445
446                 if (!struncmp(buf, "printcmd=", 9))
447                         strcpy(printcmd, &buf[9]);
448
449                 if (!struncmp(buf, "expcmd=", 7))
450                         strcpy(rc_exp_cmd, &buf[7]);
451
452                 if (!struncmp(buf, "local_screen_dimensions=", 24))
453                         have_xterm = (char) atoi(&buf[24]);
454
455                 if (!struncmp(buf, "use_floors=", 11)) {
456                         if (!strucmp(&buf[11], "yes"))
457                                 rc_floor_mode = RC_YES;
458                         if (!strucmp(&buf[11], "no"))
459                                 rc_floor_mode = RC_NO;
460                         if (!strucmp(&buf[11], "default"))
461                                 rc_floor_mode = RC_DEFAULT;
462                 }
463                 if (!struncmp(buf, "beep=", 5)) {
464                         rc_exp_beep = atoi(&buf[5]);
465                 }
466                 if (!struncmp(buf, "allow_attachments=", 18)) {
467                         rc_allow_attachments = atoi(&buf[18]);
468                 }
469                 if (!struncmp(buf, "display_message_numbers=", 24)) {
470                         rc_display_message_numbers = atoi(&buf[24]);
471                 }
472                 if (!struncmp(buf, "force_mail_prompts=", 19)) {
473                         rc_force_mail_prompts = atoi(&buf[19]);
474                 }
475                 if (!struncmp(buf, "ansi_color=", 11)) {
476                         if (!struncmp(&buf[11], "on", 2))
477                                 rc_ansi_color = 1;
478                         if (!struncmp(&buf[11], "auto", 4))
479                                 rc_ansi_color = 2;      /* autodetect */
480                         if (!struncmp(&buf[11], "user", 4))
481                                 rc_ansi_color = 3;      /* user config */
482                 }
483                 if (!struncmp(buf, "username=", 9))
484                         strcpy(rc_username, &buf[9]);
485
486                 if (!struncmp(buf, "password=", 9))
487                         strcpy(rc_password, &buf[9]);
488
489                 if (!struncmp(buf, "urlcmd=", 7))
490                         strcpy(rc_url_cmd, &buf[7]);
491
492                 if (!struncmp(buf, "cmd=", 4)) {
493                         strcpy(buf, &buf[4]);
494
495                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
496
497                         cptr->c_cmdnum = atoi(buf);
498                         for (d = strlen(buf); d >= 0; --d)
499                                 if (buf[d] == ',')
500                                         b = d;
501                         strcpy(buf, &buf[b + 1]);
502
503                         cptr->c_axlevel = atoi(buf);
504                         for (d = strlen(buf); d >= 0; --d)
505                                 if (buf[d] == ',')
506                                         b = d;
507                         strcpy(buf, &buf[b + 1]);
508
509                         for (a = 0; a < 5; ++a)
510                                 cptr->c_keys[a][0] = 0;
511
512                         a = 0;
513                         b = 0;
514                         buf[strlen(buf) + 1] = 0;
515                         while (strlen(buf) > 0) {
516                                 b = strlen(buf);
517                                 for (d = strlen(buf); d >= 0; --d)
518                                         if (buf[d] == ',')
519                                                 b = d;
520                                 strncpy(cptr->c_keys[a], buf, b);
521                                 cptr->c_keys[a][b] = 0;
522                                 if (buf[b] == ',')
523                                         strcpy(buf, &buf[b + 1]);
524                                 else
525                                         strcpy(buf, "");
526                                 ++a;
527                         }
528
529                         cptr->next = NULL;
530                         if (cmdlist == NULL)
531                                 cmdlist = cptr;
532                         else
533                                 lastcmd->next = cptr;
534                         lastcmd = cptr;
535                 }
536         }
537         fclose(ccfile);
538 }
539
540
541
542 /*
543  * return the key associated with a command
544  */
545 char keycmd(char *cmdstr)
546 {
547         int a;
548
549         for (a = 0; a < strlen(cmdstr); ++a)
550                 if (cmdstr[a] == '&')
551                         return (tolower(cmdstr[a + 1]));
552         return (0);
553 }
554
555
556 /*
557  * Output the string from a key command without the ampersand
558  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
559  */
560 char *cmd_expand(char *strbuf, int mode)
561 {
562         int a;
563         static char exp[64];
564         char buf[256];
565
566         strcpy(exp, strbuf);
567
568         for (a = 0; a < strlen(exp); ++a) {
569                 if (strbuf[a] == '&') {
570
571                         if (mode == 0) {
572                                 strcpy(&exp[a], &exp[a + 1]);
573                         }
574                         if (mode == 1) {
575                                 exp[a] = '<';
576                                 strcpy(buf, &exp[a + 2]);
577                                 exp[a + 2] = '>';
578                                 exp[a + 3] = 0;
579                                 strcat(exp, buf);
580                         }
581                 }
582                 if (!strncmp(&exp[a], "^r", 2)) {
583                         strcpy(buf, exp);
584                         strcpy(&exp[a], room_name);
585                         strcat(exp, &buf[a + 2]);
586                 }
587                 if (!strncmp(&exp[a], "^c", 2)) {
588                         exp[a] = ',';
589                         strcpy(&exp[a + 1], &exp[a + 2]);
590                 }
591         }
592
593         return (exp);
594 }
595
596
597
598 /*
599  * Comparison function to determine if entered commands match a
600  * command loaded from the config file.
601  */
602 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
603 {
604         int a;
605         int cmdax;
606
607         cmdax = 0;
608         if (is_room_aide)
609                 cmdax = 1;
610         if (axlevel >= 6)
611                 cmdax = 2;
612
613         for (a = 0; a < ncomp; ++a) {
614                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
615                     || (cptr->c_axlevel > cmdax))
616                         return (0);
617         }
618         return (1);
619 }
620
621
622 /*
623  * This function returns 1 if a given command requires a string input
624  */
625 int requires_string(struct citcmd *cptr, int ncomp)
626 {
627         int a;
628         char buf[64];
629
630         strcpy(buf, cptr->c_keys[ncomp - 1]);
631         for (a = 0; a < strlen(buf); ++a) {
632                 if (buf[a] == ':')
633                         return (1);
634         }
635         return (0);
636 }
637
638
639 /*
640  * Input a command at the main prompt.
641  * This function returns an integer command number.  If the command prompts
642  * for a string then it is placed in the supplied buffer.
643  */
644 int getcmd(char *argbuf)
645 {
646         char cmdbuf[5];
647         int cmdspaces[5];
648         int cmdpos;
649         int ch;
650         int a;
651         int got;
652         int this_lazy_cmd;
653         struct citcmd *cptr;
654
655         /* Switch color support on or off if we're in user mode */
656         if (rc_ansi_color == 3) {
657                 if (userflags & US_COLOR)
658                         enable_color = 1;
659                 else
660                         enable_color = 0;
661         }
662         /* if we're running in idiot mode, display a cute little menu */
663         IFNEXPERT formout("mainmenu");
664
665         print_express();        /* print express messages if there are any */
666         strcpy(argbuf, "");
667         cmdpos = 0;
668         for (a = 0; a < 5; ++a)
669                 cmdbuf[a] = 0;
670         /* now the room prompt... */
671         ok_to_interrupt = 1;
672         color(BRIGHT_WHITE);
673         printf("\n%s", room_name);
674         color(DIM_WHITE);
675         printf("%c ", room_prompt(room_flags));
676         fflush(stdout);
677
678         while (1) {
679                 ch = inkey();
680                 ok_to_interrupt = 0;
681
682                 /* Handle the backspace key, but only if there's something
683                  * to backspace over...
684                  */
685                 if ((ch == 8) && (cmdpos > 0)) {
686                         back(cmdspaces[cmdpos - 1] + 1);
687                         cmdbuf[cmdpos] = 0;
688                         --cmdpos;
689                 }
690                 /* Spacebar invokes "lazy traversal" commands */
691                 if ((ch == 32) && (cmdpos == 0)) {
692                         this_lazy_cmd = next_lazy_cmd;
693                         if (this_lazy_cmd == 13)
694                                 next_lazy_cmd = 5;
695                         if (this_lazy_cmd == 5)
696                                 next_lazy_cmd = 13;
697                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
698                                 if (cptr->c_cmdnum == this_lazy_cmd) {
699                                         for (a = 0; a < 5; ++a)
700                                                 if (cptr->c_keys[a][0] != 0)
701                                                         printf("%s ", cmd_expand(
702                                                                                         cptr->c_keys[a], 0));
703                                         printf("\n");
704                                         return (this_lazy_cmd);
705                                 }
706                         }
707                         printf("\n");
708                         return (this_lazy_cmd);
709                 }
710                 /* Otherwise, process the command */
711                 cmdbuf[cmdpos] = tolower(ch);
712
713                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
714                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
715
716                                 printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
717                                 cmdspaces[cmdpos] = strlen(
718                                     cmd_expand(cptr->c_keys[cmdpos], 0));
719                                 if (cmdpos < 4)
720                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
721                                                 putc(' ', stdout);
722                                 ++cmdpos;
723                         }
724                 }
725
726                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
727                         if (cmdmatch(cmdbuf, cptr, 5)) {
728                                 /* We've found our command. */
729                                 if (requires_string(cptr, cmdpos)) {
730                                         getline(argbuf, 32);
731                                 } else {
732                                         printf("\n");
733                                 }
734
735                                 /* If this command is one that changes rooms,
736                                  * then the next lazy-command (space bar)
737                                  * should be "read new" instead of "goto"
738                                  */
739                                 if ((cptr->c_cmdnum == 5)
740                                     || (cptr->c_cmdnum == 6)
741                                     || (cptr->c_cmdnum == 47)
742                                     || (cptr->c_cmdnum == 52)
743                                     || (cptr->c_cmdnum == 16)
744                                     || (cptr->c_cmdnum == 20))
745                                         next_lazy_cmd = 13;
746
747                                 return (cptr->c_cmdnum);
748
749                         }
750                 }
751
752                 if (ch == '?') {
753                         printf("\rOne of ...                         \n");
754                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
755                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
756                                         for (a = 0; a < 5; ++a) {
757                                                 printf("%s ", cmd_expand(cptr->c_keys[a], 1));
758                                         }
759                                         printf("\n");
760                                 }
761                         }
762
763                         printf("\n%s%c ", room_name, room_prompt(room_flags));
764                         got = 0;
765                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
766                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
767                                         for (a = 0; a < cmdpos; ++a) {
768                                                 printf("%s ",
769                                                        cmd_expand(cptr->c_keys[a], 0));
770                                         }
771                                         got = 1;
772                                 }
773                         }
774                 }
775         }
776
777 }
778
779
780
781
782
783 /*
784  * set tty modes.  commands are:
785  * 
786  * 0 - set to bbs mode, intr/quit disabled
787  * 1 - set to bbs mode, intr/quit enabled
788  * 2 - save current settings for later restoral
789  * 3 - restore saved settings
790  */
791 #ifdef HAVE_TERMIOS_H
792 void sttybbs(int cmd)
793 {                               /* SysV version of sttybbs() */
794         struct termios live;
795         static struct termios saved_settings;
796         static int last_cmd = 0;
797
798         if (cmd == SB_LAST)
799                 cmd = last_cmd;
800         else
801                 last_cmd = cmd;
802
803         if ((cmd == 0) || (cmd == 1)) {
804                 tcgetattr(0, &live);
805                 live.c_iflag = ISTRIP | IXON | IXANY;
806                 live.c_oflag = OPOST | ONLCR;
807                 live.c_lflag = ISIG | NOFLSH;
808
809                 if (cmd == SB_YES_INTR) {
810                         live.c_cc[VINTR] = NEXT_KEY;
811                         live.c_cc[VQUIT] = STOP_KEY;
812                         signal(SIGINT, *sighandler);
813                         signal(SIGQUIT, *sighandler);
814                 } else {
815                         signal(SIGINT, SIG_IGN);
816                         signal(SIGQUIT, SIG_IGN);
817                         live.c_cc[VINTR] = (-1);
818                         live.c_cc[VQUIT] = (-1);
819                 }
820
821                 /* do we even need this stuff anymore? */
822                 /* live.c_line=0; */
823                 live.c_cc[VERASE] = 8;
824                 live.c_cc[VKILL] = 24;
825                 live.c_cc[VEOF] = 1;
826                 live.c_cc[VEOL] = 255;
827                 live.c_cc[VEOL2] = 0;
828                 live.c_cc[VSTART] = 0;
829                 tcsetattr(0, TCSADRAIN, &live);
830         }
831         if (cmd == 2) {
832                 tcgetattr(0, &saved_settings);
833         }
834         if (cmd == 3) {
835                 tcsetattr(0, TCSADRAIN, &saved_settings);
836         }
837 }
838 #else
839 void sttybbs(int cmd)
840 {                               /* BSD version of sttybbs() */
841         struct sgttyb live;
842         static struct sgttyb saved_settings;
843
844         if ((cmd == 0) || (cmd == 1)) {
845                 gtty(0, &live);
846                 live.sg_flags |= CBREAK;
847                 live.sg_flags |= CRMOD;
848                 live.sg_flags |= NL1;
849                 live.sg_flags &= ~ECHO;
850                 if (cmd == 1)
851                         live.sg_flags |= NOFLSH;
852                 stty(0, &live);
853         }
854         if (cmd == 2) {
855                 gtty(0, &saved_settings);
856         }
857         if (cmd == 3) {
858                 stty(0, &saved_settings);
859         }
860 }
861 #endif
862
863
864 /*
865  * display_help()  -  help file viewer
866  */
867 void display_help(char *name)
868 {
869         formout(name);
870 }
871
872
873 /*
874  * fmout()  -  Citadel text formatter and paginator
875  */
876 int fmout(int width, FILE * fp, char pagin, int height, int starting_lp, char subst)
877                         /* screen width to use */
878                         /* file to read from, or NULL to read from server */
879                         /* nonzero if we should use the paginator */
880                         /* screen height to use */
881                         /* starting value for lines_printed, -1 for global */
882                         /* nonzero if we should use hypertext mode */
883 {
884         int a, b, c, d, old;
885         int real = (-1);
886         char aaa[140];
887         char buffer[512];
888         int eof_flag = 0;
889
890         num_urls = 0;   /* Start with a clean slate of embedded URL's */
891
892         if (starting_lp >= 0) {
893                 lines_printed = starting_lp;
894         }
895         strcpy(aaa, "");
896         old = 255;
897         strcpy(buffer, "");
898         c = 1;                  /* c is the current pos */
899
900         sigcaught = 0;
901         sttybbs(1);
902
903 FMTA:   while ((eof_flag == 0) && (strlen(buffer) < 126)) {
904         
905                 if (sigcaught)
906                         goto OOPS;
907                 if (fp != NULL) {       /* read from file */
908                         if (feof(fp))
909                                 eof_flag = 1;
910                         if (eof_flag == 0) {
911                                 a = getc(fp);
912                                 buffer[strlen(buffer) + 1] = 0;
913                                 buffer[strlen(buffer)] = a;
914                         }
915                 } else {        /* read from server */
916                         d = strlen(buffer);
917                         serv_gets(&buffer[d]);
918                         while ((!isspace(buffer[d])) && (isspace(buffer[strlen(buffer) - 1])))
919                                 buffer[strlen(buffer) - 1] = 0;
920                         if (!strcmp(&buffer[d], "000")) {
921                                 buffer[d] = 0;
922                                 eof_flag = 1;
923                                 while (isspace(buffer[strlen(buffer) - 1]))
924                                         buffer[strlen(buffer) - 1] = 0;
925                         }
926                         d = strlen(buffer);
927                         buffer[d] = 10;
928                         buffer[d + 1] = 0;
929                 }
930         }
931
932         if ( (!struncmp(buffer, "http://", 7))
933            || (!struncmp(buffer, "ftp://", 6)) ) {
934                 safestrncpy(urls[num_urls], buffer, 255);
935                 for (a=0; a<strlen(urls[num_urls]); ++a) {
936                         b = urls[num_urls][a];
937                         if ( (b==' ') || (b==')') || (b=='>') || (b==10)
938                            || (b==13) || (b==9) || (b=='\"') )
939                                 urls[num_urls][a] = 0;
940                 }
941                 ++num_urls;
942         }
943
944         buffer[strlen(buffer) + 1] = 0;
945         a = buffer[0];
946         strcpy(buffer, &buffer[1]);
947
948         old = real;
949         real = a;
950         if (a <= 0)
951                 goto FMTEND;
952
953         if (((a == 13) || (a == 10)) && (old != 13) && (old != 10))
954                 a = 32;
955         if (((old == 13) || (old == 10)) && (isspace(real))) {
956                 printf("\n");
957                 ++lines_printed;
958                 lines_printed = checkpagin(lines_printed, pagin, height);
959                 c = 1;
960         }
961         if (a > 126)
962                 goto FMTA;
963
964         if (a > 32) {
965                 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
966                         printf("\n%s", aaa);
967                         c = strlen(aaa);
968                         aaa[0] = 0;
969                         ++lines_printed;
970                         lines_printed = checkpagin(lines_printed, pagin, height);
971                 }
972                 b = strlen(aaa);
973                 aaa[b] = a;
974                 aaa[b + 1] = 0;
975         }
976         if (a == 32) {
977                 if ((strlen(aaa) + c) > (width - 5)) {
978                         c = 1;
979                         printf("\n");
980                         ++lines_printed;
981                         lines_printed = checkpagin(lines_printed, pagin, height);
982                 }
983                 printf("%s ", aaa);
984                 ++c;
985                 c = c + strlen(aaa);
986                 strcpy(aaa, "");
987                 goto FMTA;
988         }
989         if ((a == 13) || (a == 10)) {
990                 printf("%s\n", aaa);
991                 c = 1;
992                 ++lines_printed;
993                 lines_printed = checkpagin(lines_printed, pagin, height);
994                 strcpy(aaa, "");
995                 goto FMTA;
996         }
997         goto FMTA;
998
999         /* signal caught; drain the server */
1000       OOPS:do {
1001                 serv_gets(aaa);
1002         } while (strcmp(aaa, "000"));
1003
1004       FMTEND:printf("\n");
1005         ++lines_printed;
1006         lines_printed = checkpagin(lines_printed, pagin, height);
1007         return (sigcaught);
1008 }
1009
1010
1011 /*
1012  * support ANSI color if defined
1013  */
1014 void color(int colornum)
1015 {
1016         static int is_bold = 0;
1017         static int hold_color, current_color;
1018
1019         if (colornum == COLOR_PUSH) {
1020                 hold_color = current_color;
1021                 return;
1022         }
1023
1024         if (colornum == COLOR_POP) {
1025                 color(hold_color);
1026                 return;
1027         }
1028
1029         current_color = colornum;
1030         if (enable_color) {
1031                 printf("\033[3%dm", (colornum % 8));
1032                 if ((colornum >= 8) && (is_bold == 0)) {
1033                         printf("\033[1m");
1034                         is_bold = 1;
1035                 } else if ((colornum < 8) && (is_bold == 1)) {
1036                         printf("\033[0m");
1037                         is_bold = 0;
1038                 }
1039                 fflush(stdout);
1040         }
1041 }
1042
1043 void cls(int colornum)
1044 {
1045         if (enable_color) {
1046                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum);
1047                 fflush(stdout);
1048         }
1049 }
1050
1051
1052 /*
1053  * Detect whether ANSI color is available (answerback)
1054  */
1055 void send_ansi_detect(void)
1056 {
1057         if (rc_ansi_color == 2) {
1058                 printf("\033[c");
1059                 fflush(stdout);
1060                 time(&AnsiDetect);
1061         }
1062 }
1063
1064 void look_for_ansi(void)
1065 {
1066         fd_set rfds;
1067         struct timeval tv;
1068         char abuf[512];
1069         time_t now;
1070         int a;
1071
1072         if (rc_ansi_color == 0) {
1073                 enable_color = 0;
1074         } else if (rc_ansi_color == 1) {
1075                 enable_color = 1;
1076         } else if (rc_ansi_color == 2) {
1077
1078                 /* otherwise, do the auto-detect */
1079
1080                 strcpy(abuf, "");
1081
1082                 time(&now);
1083                 if ((now - AnsiDetect) < 2)
1084                         sleep(1);
1085
1086                 do {
1087                         FD_ZERO(&rfds);
1088                         FD_SET(0, &rfds);
1089                         tv.tv_sec = 0;
1090                         tv.tv_usec = 1;
1091
1092                         select(1, &rfds, NULL, NULL, &tv);
1093                         if (FD_ISSET(0, &rfds)) {
1094                                 abuf[strlen(abuf) + 1] = 0;
1095                                 read(0, &abuf[strlen(abuf)], 1);
1096                         }
1097                 } while (FD_ISSET(0, &rfds));
1098
1099                 for (a = 0; a < strlen(abuf); ++a) {
1100                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1101                             && (abuf[a + 2] == '?')) {
1102                                 enable_color = 1;
1103                         }
1104                 }
1105         }
1106 }
1107
1108
1109 /*
1110  * Display key options (highlight hotkeys inside angle brackets)
1111  */
1112 void keyopt(char *buf) {
1113         int i;
1114
1115         color(DIM_WHITE);
1116         for (i=0; i<strlen(buf); ++i) {
1117                 if (buf[i]=='<') {
1118                         putc(buf[i], stdout);
1119                         color(BRIGHT_MAGENTA);
1120                 } else {
1121                         if (buf[i]=='>') {
1122                                 color(DIM_WHITE);
1123                         }
1124                         putc(buf[i], stdout);
1125                 }
1126         }
1127         color(DIM_WHITE);
1128 }
1129
1130
1131
1132 /*
1133  * Present a key-menu line choice type of thing
1134  */
1135 char keymenu(char *menuprompt, char *menustring) {
1136         int i, c, a;
1137         int choices;
1138         int do_prompt = 0;
1139         char buf[256];
1140         int ch;
1141         int display_prompt = 1;
1142
1143         choices = num_tokens(menustring, '|');
1144
1145         if (menuprompt != NULL) do_prompt = 1;
1146         if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1147
1148         while (1) {
1149                 if (display_prompt) {
1150                         if (do_prompt) {
1151                                 printf("%s ", menuprompt);
1152                         } 
1153                         else {
1154                                 for (i=0; i<choices; ++i) {
1155                                         extract(buf, menustring, i);
1156                                         keyopt(buf);
1157                                         printf(" ");
1158                                 }
1159                         }
1160                         printf(" -> ");
1161                         display_prompt = 0;
1162                 }
1163                 ch = lkey();
1164         
1165                 if ( (do_prompt) && (ch=='?') ) {
1166                         printf("\rOne of...                               ");
1167                         printf("                                      \n");
1168                         for (i=0; i<choices; ++i) {
1169                                 extract(buf, menustring, i);
1170                                 printf("   ");
1171                                 keyopt(buf);
1172                                 printf("\n");
1173                         }
1174                         printf("\n");
1175                         display_prompt = 1;
1176                 }
1177
1178                 for (i=0; i<choices; ++i) {
1179                         extract(buf, menustring, i);
1180                         for (c=1; c<strlen(buf); ++c) {
1181                                 if ( (ch == tolower(buf[c]))
1182                                    && (buf[c-1]=='<')
1183                                    && (buf[c+1]=='>') ) {
1184                                         for (a=0; a<strlen(buf); ++a) {
1185                                                 if ( (a!=(c-1)) && (a!=(c+1))) {
1186                                                         putc(buf[a], stdout);
1187                                                 }
1188                                         }
1189                                         printf("\n\n");
1190                                         return ch;
1191                                 }
1192                         }
1193                 }
1194         }
1195 }