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