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