* Support for Ctrl-W to erase a word when editing or at a prompt
[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 != 23) && (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 || a == 23) && (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); scr_putc(32); scr_putc(8);
576                 goto GLA;
577         }
578         if ((a == 23) && (string[0] != 0)) {
579                 do {
580                         string[strlen(string) - 1] = 0;
581                         scr_putc(8); scr_putc(32); scr_putc(8);
582                 } while (strlen(string) && string[strlen(string) - 1] != ' ');
583                 goto GLA;
584         }
585         if ((a == 10)) {
586                 scr_putc(10);
587                 async_ka_end();
588                 return;
589         }
590         if (a < 32)
591                 a = '.';
592         b = strlen(string);
593         string[b] = a;
594         string[b + 1] = 0;
595         if (flag == 0)
596                 scr_putc(a);
597         if (flag == 1)
598                 scr_putc('*');
599         goto GLA;
600 }
601
602
603 /*
604  * strprompt()  -  prompt for a string, print the existing value and
605  *                 allow the user to press return to keep it...
606  */
607 void strprompt(char *prompt, char *str, int len)
608 {
609         int i;
610         char buf[128];
611
612         print_express();
613         color(DIM_WHITE);
614         scr_printf("%s ", prompt);
615         color(DIM_MAGENTA);
616         scr_printf("[");
617         color(BRIGHT_MAGENTA);
618
619         if (len >= 0) {
620                 scr_printf("%s", str);
621         }
622         else {
623                 for (i=0; i<strlen(str); ++i) {
624                         scr_printf("*");
625                 }
626         }
627
628         color(DIM_MAGENTA);
629         scr_printf("]");
630         color(DIM_WHITE);
631         scr_printf(": ");
632         color(BRIGHT_CYAN);
633         getline(buf, len);
634         if (buf[0] != 0)
635                 strcpy(str, buf);
636         color(DIM_WHITE);
637 }
638
639 /*
640  * boolprompt()  -  prompt for a yes/no, print the existing value and
641  *                  allow the user to press return to keep it...
642  */
643 int boolprompt(char *prompt, int prev_val)
644 {
645         int r;
646
647         color(DIM_WHITE);
648         scr_printf("%s ", prompt);
649         color(DIM_MAGENTA);
650         scr_printf(" [");
651         color(BRIGHT_MAGENTA);
652         scr_printf("%s", (prev_val ? "Yes" : "No"));
653         color(DIM_MAGENTA);
654         scr_printf("]: ");
655         color(BRIGHT_CYAN);
656         r = (yesno_d(prev_val));
657         color(DIM_WHITE);
658         return r;
659 }
660
661 /* 
662  * intprompt()  -  like strprompt(), except for an integer
663  *                 (note that it RETURNS the new value!)
664  */
665 int intprompt(char *prompt, int ival, int imin, int imax)
666 {
667         char buf[16];
668         int i;
669         int p;
670
671         do {
672                 i = ival;
673                 snprintf(buf, sizeof buf, "%d", i);
674                 strprompt(prompt, buf, 15);
675                 i = atoi(buf);
676                 for (p=0; p<strlen(buf); ++p) {
677                         if ( (!isdigit(buf[p]))
678                            && ( (buf[p]!='-') || (p!=0) )  )
679                                 i = imin - 1;
680                 }
681                 if (i < imin)
682                         scr_printf("*** Must be no less than %d.\n", imin);
683                 if (i > imax)
684                         scr_printf("*** Must be no more than %d.\n", imax);
685         } while ((i < imin) || (i > imax));
686         return (i);
687 }
688
689 /* 
690  * newprompt()  -  prompt for a string with no existing value
691  *                 (clears out string buffer first)
692  */
693 void newprompt(char *prompt, char *str, int len)
694 {
695         color(BRIGHT_MAGENTA);
696         scr_printf("%s", prompt);
697         color(DIM_MAGENTA);
698         getline(str, len);
699         color(DIM_WHITE);
700 }
701
702
703 int lkey(void)
704 {                               /* returns a lower case value */
705         int a;
706         a = inkey();
707         if (isupper(a))
708                 a = tolower(a);
709         return (a);
710 }
711
712 /*
713  * parse the citadel.rc file
714  */
715 void load_command_set(void)
716 {
717         FILE *ccfile;
718         char buf[1024];
719         struct citcmd *cptr;
720         struct citcmd *lastcmd = NULL;
721         int a, d;
722         int b = 0;
723
724
725         /* first, set up some defaults for non-required variables */
726
727         strcpy(editor_path, "");
728         strcpy(printcmd, "");
729         strcpy(rc_username, "");
730         strcpy(rc_password, "");
731         rc_floor_mode = 0;
732         rc_exp_beep = 1;
733         rc_allow_attachments = 0;
734         rc_remember_passwords = 0;
735         strcpy(rc_exp_cmd, "");
736         rc_display_message_numbers = 0;
737         rc_force_mail_prompts = 0;
738         rc_ansi_color = 0;
739         strcpy(rc_url_cmd, "");
740 #ifdef HAVE_OPENSSL
741         rc_encrypt = RC_DEFAULT;
742 #endif
743 #ifdef HAVE_CURSES_H
744         rc_screen = RC_DEFAULT;
745 #endif
746         rc_alt_semantics = 0;
747
748         /* now try to open the citadel.rc file */
749
750         ccfile = NULL;
751         if (getenv("HOME") != NULL) {
752                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
753                 ccfile = fopen(buf, "r");
754         }
755         if (ccfile == NULL) {
756                 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
757                 ccfile = fopen(buf, "r");
758         }
759         if (ccfile == NULL) {
760                 ccfile = fopen("/etc/citadel.rc", "r");
761         }
762         if (ccfile == NULL) {
763                 ccfile = fopen("./citadel.rc", "r");
764         }
765         if (ccfile == NULL) {
766                 perror("commands: cannot open citadel.rc");
767                 logoff(errno);
768         }
769         while (fgets(buf, sizeof buf, ccfile) != NULL) {
770                 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
771                         buf[strlen(buf) - 1] = 0;
772
773                 if (!strncasecmp(buf, "encrypt=", 8)) {
774                         if (!strcasecmp(&buf[8], "yes")) {
775 #ifdef HAVE_OPENSSL
776                                 rc_encrypt = RC_YES;
777 #else
778                                 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
779                                 logoff(1);
780 #endif
781                         }
782 #ifdef HAVE_OPENSSL
783                         else if (!strcasecmp(&buf[8], "no")) {
784                                 rc_encrypt = RC_NO;
785                         }
786                         else if (!strcasecmp(&buf[8], "default")) {
787                                 rc_encrypt = RC_DEFAULT;
788                         }
789 #endif
790                 }
791
792 #ifdef HAVE_CURSES_H
793                 if (!strncasecmp(buf, "fullscreen=", 11)) {
794                         if (!strcasecmp(&buf[11], "yes"))
795                                 rc_screen = RC_YES;
796                         else if (!strcasecmp(&buf[11], "no"))
797                                 rc_screen = RC_NO;
798                 }
799 #endif
800
801                 if (!strncasecmp(buf, "editor=", 7))
802                         strcpy(editor_path, &buf[7]);
803
804                 if (!strncasecmp(buf, "printcmd=", 9))
805                         strcpy(printcmd, &buf[9]);
806
807                 if (!strncasecmp(buf, "expcmd=", 7))
808                         strcpy(rc_exp_cmd, &buf[7]);
809
810                 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
811                         have_xterm = (char) atoi(&buf[24]);
812
813                 if (!strncasecmp(buf, "use_floors=", 11)) {
814                         if (!strcasecmp(&buf[11], "yes"))
815                                 rc_floor_mode = RC_YES;
816                         if (!strcasecmp(&buf[11], "no"))
817                                 rc_floor_mode = RC_NO;
818                         if (!strcasecmp(&buf[11], "default"))
819                                 rc_floor_mode = RC_DEFAULT;
820                 }
821                 if (!strncasecmp(buf, "beep=", 5)) {
822                         rc_exp_beep = atoi(&buf[5]);
823                 }
824                 if (!strncasecmp(buf, "allow_attachments=", 18)) {
825                         rc_allow_attachments = atoi(&buf[18]);
826                 }
827                 if (!strncasecmp(buf, "idle_threshold=", 15)) {
828                         rc_idle_threshold = atoi(&buf[15]);
829                 }
830                 if (!strncasecmp(buf, "remember_passwords=", 19)) {
831                         rc_remember_passwords = atoi(&buf[19]);
832                 }
833                 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
834                         rc_display_message_numbers = atoi(&buf[24]);
835                 }
836                 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
837                         rc_force_mail_prompts = atoi(&buf[19]);
838                 }
839                 if (!strncasecmp(buf, "ansi_color=", 11)) {
840                         if (!strncasecmp(&buf[11], "on", 2))
841                                 rc_ansi_color = 1;
842                         if (!strncasecmp(&buf[11], "auto", 4))
843                                 rc_ansi_color = 2;      /* autodetect */
844                         if (!strncasecmp(&buf[11], "user", 4))
845                                 rc_ansi_color = 3;      /* user config */
846                 }
847                 if (!strncasecmp(buf, "prompt_control=", 15)) {
848                         if (!strncasecmp(&buf[15], "on", 2))
849                                 rc_prompt_control = 1;
850                         if (!strncasecmp(&buf[15], "user", 4))
851                                 rc_prompt_control = 3;  /* user config */
852                 }
853                 if (!strncasecmp(buf, "username=", 9))
854                         strcpy(rc_username, &buf[9]);
855
856                 if (!strncasecmp(buf, "password=", 9))
857                         strcpy(rc_password, &buf[9]);
858
859                 if (!strncasecmp(buf, "urlcmd=", 7))
860                         strcpy(rc_url_cmd, &buf[7]);
861
862                 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
863                         if (!strncasecmp(&buf[11], "yes", 3))
864                                 rc_alt_semantics = 1;
865                         if (!strncasecmp(&buf[11], "no", 2))
866                                 rc_alt_semantics = 0;
867                 }
868                 if (!strncasecmp(buf, "cmd=", 4)) {
869                         strcpy(buf, &buf[4]);
870
871                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
872
873                         cptr->c_cmdnum = atoi(buf);
874                         for (d = strlen(buf); d >= 0; --d)
875                                 if (buf[d] == ',')
876                                         b = d;
877                         strcpy(buf, &buf[b + 1]);
878
879                         cptr->c_axlevel = atoi(buf);
880                         for (d = strlen(buf); d >= 0; --d)
881                                 if (buf[d] == ',')
882                                         b = d;
883                         strcpy(buf, &buf[b + 1]);
884
885                         for (a = 0; a < 5; ++a)
886                                 cptr->c_keys[a][0] = 0;
887
888                         a = 0;
889                         b = 0;
890                         buf[strlen(buf) + 1] = 0;
891                         while (strlen(buf) > 0) {
892                                 b = strlen(buf);
893                                 for (d = strlen(buf); d >= 0; --d)
894                                         if (buf[d] == ',')
895                                                 b = d;
896                                 strncpy(cptr->c_keys[a], buf, b);
897                                 cptr->c_keys[a][b] = 0;
898                                 if (buf[b] == ',')
899                                         strcpy(buf, &buf[b + 1]);
900                                 else
901                                         strcpy(buf, "");
902                                 ++a;
903                         }
904
905                         cptr->next = NULL;
906                         if (cmdlist == NULL)
907                                 cmdlist = cptr;
908                         else
909                                 lastcmd->next = cptr;
910                         lastcmd = cptr;
911                 }
912         }
913         fclose(ccfile);
914 }
915
916
917
918 /*
919  * return the key associated with a command
920  */
921 char keycmd(char *cmdstr)
922 {
923         int a;
924
925         for (a = 0; a < strlen(cmdstr); ++a)
926                 if (cmdstr[a] == '&')
927                         return (tolower(cmdstr[a + 1]));
928         return (0);
929 }
930
931
932 /*
933  * Output the string from a key command without the ampersand
934  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
935  */
936 char *cmd_expand(char *strbuf, int mode)
937 {
938         int a;
939         static char exp[64];
940         char buf[1024];
941
942         strcpy(exp, strbuf);
943
944         for (a = 0; a < strlen(exp); ++a) {
945                 if (strbuf[a] == '&') {
946
947                         if (mode == 0) {
948                                 strcpy(&exp[a], &exp[a + 1]);
949                         }
950                         if (mode == 1) {
951                                 exp[a] = '<';
952                                 strcpy(buf, &exp[a + 2]);
953                                 exp[a + 2] = '>';
954                                 exp[a + 3] = 0;
955                                 strcat(exp, buf);
956                         }
957                 }
958                 if (!strncmp(&exp[a], "^r", 2)) {
959                         strcpy(buf, exp);
960                         strcpy(&exp[a], room_name);
961                         strcat(exp, &buf[a + 2]);
962                 }
963                 if (!strncmp(&exp[a], "^c", 2)) {
964                         exp[a] = ',';
965                         strcpy(&exp[a + 1], &exp[a + 2]);
966                 }
967         }
968
969         return (exp);
970 }
971
972
973
974 /*
975  * Comparison function to determine if entered commands match a
976  * command loaded from the config file.
977  */
978 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
979 {
980         int a;
981         int cmdax;
982
983         cmdax = 0;
984         if (is_room_aide)
985                 cmdax = 1;
986         if (axlevel >= 6)
987                 cmdax = 2;
988
989         for (a = 0; a < ncomp; ++a) {
990                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
991                     || (cptr->c_axlevel > cmdax))
992                         return (0);
993         }
994         return (1);
995 }
996
997
998 /*
999  * This function returns 1 if a given command requires a string input
1000  */
1001 int requires_string(struct citcmd *cptr, int ncomp)
1002 {
1003         int a;
1004         char buf[64];
1005
1006         strcpy(buf, cptr->c_keys[ncomp - 1]);
1007         for (a = 0; a < strlen(buf); ++a) {
1008                 if (buf[a] == ':')
1009                         return (1);
1010         }
1011         return (0);
1012 }
1013
1014
1015 /*
1016  * Input a command at the main prompt.
1017  * This function returns an integer command number.  If the command prompts
1018  * for a string then it is placed in the supplied buffer.
1019  */
1020 int getcmd(char *argbuf)
1021 {
1022         char cmdbuf[5];
1023         int cmdspaces[5];
1024         int cmdpos;
1025         int ch;
1026         int a;
1027         int got;
1028         int this_lazy_cmd;
1029         struct citcmd *cptr;
1030
1031         /*
1032          * Starting a new command now, so set sigcaught to 0.  This variable
1033          * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1034          * been interrupted by a keypress.
1035          */
1036         sigcaught = 0;
1037
1038         /* Switch color support on or off if we're in user mode */
1039         if (rc_ansi_color == 3) {
1040                 if (userflags & US_COLOR)
1041                         enable_color = 1;
1042                 else
1043                         enable_color = 0;
1044         }
1045         /* if we're running in idiot mode, display a cute little menu */
1046         IFNEXPERT formout("mainmenu");
1047
1048         print_express();        /* print express messages if there are any */
1049         strcpy(argbuf, "");
1050         cmdpos = 0;
1051         for (a = 0; a < 5; ++a)
1052                 cmdbuf[a] = 0;
1053         /* now the room prompt... */
1054         ok_to_interrupt = 1;
1055         color(BRIGHT_WHITE);
1056         scr_printf("\n%s", room_name);
1057         color(DIM_WHITE);
1058         scr_printf("%c ", room_prompt(room_flags));
1059         scr_flush();
1060
1061         while (1) {
1062                 ch = inkey();
1063                 ok_to_interrupt = 0;
1064
1065                 /* Handle the backspace key, but only if there's something
1066                  * to backspace over...
1067                  */
1068                 if ((ch == 8) && (cmdpos > 0)) {
1069                         back(cmdspaces[cmdpos - 1] + 1);
1070                         cmdbuf[cmdpos] = 0;
1071                         --cmdpos;
1072                 }
1073                 /* Spacebar invokes "lazy traversal" commands */
1074                 if ((ch == 32) && (cmdpos == 0)) {
1075                         this_lazy_cmd = next_lazy_cmd;
1076                         if (this_lazy_cmd == 13)
1077                                 next_lazy_cmd = 5;
1078                         if (this_lazy_cmd == 5)
1079                                 next_lazy_cmd = 13;
1080                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1081                                 if (cptr->c_cmdnum == this_lazy_cmd) {
1082                                         for (a = 0; a < 5; ++a)
1083                                                 if (cptr->c_keys[a][0] != 0)
1084                                                         scr_printf("%s ", cmd_expand(
1085                                                                                         cptr->c_keys[a], 0));
1086                                         scr_printf("\n");
1087                                         return (this_lazy_cmd);
1088                                 }
1089                         }
1090                         scr_printf("\n");
1091                         return (this_lazy_cmd);
1092                 }
1093                 /* Otherwise, process the command */
1094                 cmdbuf[cmdpos] = tolower(ch);
1095
1096                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1097                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1098
1099                                 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1100                                 cmdspaces[cmdpos] = strlen(
1101                                     cmd_expand(cptr->c_keys[cmdpos], 0));
1102                                 if (cmdpos < 4)
1103                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
1104                                                 scr_putc(' ');
1105                                 ++cmdpos;
1106                         }
1107                 }
1108
1109                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1110                         if (cmdmatch(cmdbuf, cptr, 5)) {
1111                                 /* We've found our command. */
1112                                 if (requires_string(cptr, cmdpos)) {
1113                                         getline(argbuf, 32);
1114                                 } else {
1115                                         scr_printf("\n");
1116                                 }
1117
1118                                 /* If this command is one that changes rooms,
1119                                  * then the next lazy-command (space bar)
1120                                  * should be "read new" instead of "goto"
1121                                  */
1122                                 if ((cptr->c_cmdnum == 5)
1123                                     || (cptr->c_cmdnum == 6)
1124                                     || (cptr->c_cmdnum == 47)
1125                                     || (cptr->c_cmdnum == 52)
1126                                     || (cptr->c_cmdnum == 16)
1127                                     || (cptr->c_cmdnum == 20))
1128                                         next_lazy_cmd = 13;
1129
1130                                 return (cptr->c_cmdnum);
1131
1132                         }
1133                 }
1134
1135                 if (ch == '?') {
1136                         pprintf("\rOne of ...                         \n");
1137                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1138                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1139                                         for (a = 0; a < 5; ++a) {
1140                                                 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1141                                         }
1142                                         pprintf("\n");
1143                                 }
1144                         }
1145
1146                         pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1147                         got = 0;
1148                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1149                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1150                                         for (a = 0; a < cmdpos; ++a) {
1151                                                 pprintf("%s ",
1152                                                        cmd_expand(cptr->c_keys[a], 0));
1153                                         }
1154                                         got = 1;
1155                                 }
1156                         }
1157                 }
1158         }
1159
1160 }
1161
1162
1163
1164
1165
1166 /*
1167  * set tty modes.  commands are:
1168  * 
1169  * 01- set to bbs mode
1170  * 2 - save current settings for later restoral
1171  * 3 - restore saved settings
1172  */
1173 #ifdef HAVE_TERMIOS_H
1174 void sttybbs(int cmd)
1175 {                               /* SysV version of sttybbs() */
1176         struct termios live;
1177         static struct termios saved_settings;
1178         static int last_cmd = 0;
1179
1180         if (cmd == SB_LAST)
1181                 cmd = last_cmd;
1182         else
1183                 last_cmd = cmd;
1184
1185         if ((cmd == 0) || (cmd == 1)) {
1186                 tcgetattr(0, &live);
1187                 live.c_iflag = ISTRIP | IXON | IXANY;
1188                 live.c_oflag = OPOST | ONLCR;
1189                 live.c_lflag = ISIG | NOFLSH;
1190
1191                 live.c_cc[VINTR] = (-1);
1192                 live.c_cc[VQUIT] = (-1);
1193
1194 #ifdef hpux
1195                 live.c_cc[VMIN] = 0;
1196                 live.c_cc[VTIME] = 0;
1197 #endif
1198
1199                 /* do we even need this stuff anymore? */
1200                 /* live.c_line=0; */
1201                 live.c_cc[VERASE] = 8;
1202                 live.c_cc[VKILL] = 24;
1203                 live.c_cc[VEOF] = 1;
1204                 live.c_cc[VEOL] = 255;
1205                 live.c_cc[VEOL2] = 0;
1206                 live.c_cc[VSTART] = 0;
1207                 tcsetattr(0, TCSADRAIN, &live);
1208         }
1209         if (cmd == 2) {
1210                 tcgetattr(0, &saved_settings);
1211         }
1212         if (cmd == 3) {
1213                 tcsetattr(0, TCSADRAIN, &saved_settings);
1214         }
1215 }
1216 #else
1217 void sttybbs(int cmd)
1218 {                               /* BSD version of sttybbs() */
1219         struct sgttyb live;
1220         static struct sgttyb saved_settings;
1221         static int last_cmd = 0;
1222
1223         if (cmd == SB_LAST)
1224                 cmd = last_cmd;
1225         else
1226                 last_cmd = cmd;
1227
1228         if ((cmd == 0) || (cmd == 1)) {
1229                 gtty(0, &live);
1230                 live.sg_flags |= CBREAK;
1231                 live.sg_flags |= CRMOD;
1232                 live.sg_flags |= NL1;
1233                 live.sg_flags &= ~ECHO;
1234                 if (cmd == 1)
1235                         live.sg_flags |= NOFLSH;
1236                 stty(0, &live);
1237         }
1238         if (cmd == 2) {
1239                 gtty(0, &saved_settings);
1240         }
1241         if (cmd == 3) {
1242                 stty(0, &saved_settings);
1243         }
1244 }
1245 #endif
1246
1247
1248 /*
1249  * display_help()  -  help file viewer
1250  */
1251 void display_help(char *name)
1252 {
1253         formout(name);
1254 }
1255
1256
1257 /*
1258  * fmout()  -  Citadel text formatter and paginator
1259  */
1260 int fmout(
1261         int width,      /* screen width to use */
1262         FILE *fpin,     /* file to read from, or NULL to read from server */
1263         FILE *fpout,    /* File to write to, or NULL to write to screen */
1264         char pagin,     /* nonzero if we should use the paginator */
1265         int height,     /* screen height to use */
1266         int starting_lp,/* starting value for lines_printed, -1 for global */
1267         char subst)     /* nonzero if we should use hypertext mode */
1268 {
1269         int a, b, c, d, old;
1270         int real = (-1);
1271         char aaa[140];
1272         char buffer[512];
1273         int eof_flag = 0;
1274
1275         num_urls = 0;   /* Start with a clean slate of embedded URL's */
1276
1277         if (starting_lp >= 0) {
1278                 lines_printed = starting_lp;
1279         }
1280         strcpy(aaa, "");
1281         old = 255;
1282         strcpy(buffer, "");
1283         c = 1;                  /* c is the current pos */
1284
1285 FMTA:   while ((eof_flag == 0) && (strlen(buffer) < 126)) {
1286                 if (fpin != NULL) {     /* read from file */
1287                         if (feof(fpin))
1288                                 eof_flag = 1;
1289                         if (eof_flag == 0) {
1290                                 a = getc(fpin);
1291                                 buffer[strlen(buffer) + 1] = 0;
1292                                 buffer[strlen(buffer)] = a;
1293                         }
1294                 } else {        /* read from server */
1295                         d = strlen(buffer);
1296                         serv_gets(&buffer[d]);
1297                         while ((!isspace(buffer[d])) && (isspace(buffer[strlen(buffer) - 1])))
1298                                 buffer[strlen(buffer) - 1] = 0;
1299                         if (!strcmp(&buffer[d], "000")) {
1300                                 buffer[d] = 0;
1301                                 eof_flag = 1;
1302                                 while (isspace(buffer[strlen(buffer) - 1]))
1303                                         buffer[strlen(buffer) - 1] = 0;
1304                         }
1305                         d = strlen(buffer);
1306                         buffer[d] = 10;
1307                         buffer[d + 1] = 0;
1308                 }
1309         }
1310
1311         if ( (!strncasecmp(buffer, "http://", 7))
1312            || (!strncasecmp(buffer, "ftp://", 6)) ) {
1313                 safestrncpy(urls[num_urls], buffer, (SIZ-1));
1314                 for (a=0; a<strlen(urls[num_urls]); ++a) {
1315                         b = urls[num_urls][a];
1316                         if ( (b==' ') || (b==')') || (b=='>') || (b==10)
1317                            || (b==13) || (b==9) || (b=='\"') )
1318                                 urls[num_urls][a] = 0;
1319                 }
1320                 ++num_urls;
1321         }
1322
1323         buffer[strlen(buffer) + 1] = 0;
1324         a = buffer[0];
1325         strcpy(buffer, &buffer[1]);
1326
1327         old = real;
1328         real = a;
1329         if (a <= 0)
1330                 goto FMTEND;
1331
1332         if (((a == 13) || (a == 10)) && (old != 13) && (old != 10))
1333                 a = 32;
1334         if (((old == 13) || (old == 10)) && (isspace(real))) {
1335                 if (fpout) {
1336                         fprintf(fpout, "\n");
1337                 } else {
1338                         scr_printf("\n");
1339                         ++lines_printed;
1340                         lines_printed = checkpagin(lines_printed, pagin, height);
1341                 }
1342                 c = 1;
1343         }
1344         if (a > 126)
1345                 goto FMTA;
1346
1347         if (a > 32) {
1348                 if (((strlen(aaa) + c) > (width - 1)) && (strlen(aaa) > (width - 1))) {
1349                         if (fpout) {
1350                                 fprintf(fpout, "\n%s", aaa);
1351                         } else {
1352                                 scr_printf("\n%s", aaa);
1353                                 ++lines_printed;
1354                                 lines_printed = checkpagin(lines_printed, pagin, height);
1355                         }
1356                         c = strlen(aaa);
1357                         aaa[0] = 0;
1358                 }
1359                 b = strlen(aaa);
1360                 aaa[b] = a;
1361                 aaa[b + 1] = 0;
1362         }
1363         if (a == 32) {
1364                 if ((strlen(aaa) + c) > (width - 1)) {
1365                         c = 1;
1366                         if (fpout) {
1367                                 fprintf(fpout, "\n");
1368                         } else {
1369                                 scr_printf("\n");
1370                                 ++lines_printed;
1371                                 lines_printed = checkpagin(lines_printed, pagin, height);
1372                         }
1373                 }
1374                 if (fpout) {
1375                         fprintf(fpout, "%s ", aaa);
1376                 } else {
1377                         scr_printf("%s ", aaa);
1378                 }
1379                 ++c;
1380                 c = c + strlen(aaa);
1381                 strcpy(aaa, "");
1382                 goto FMTA;
1383         }
1384         if ((a == 13) || (a == 10)) {
1385                 if (fpout) {
1386                         fprintf(fpout, "%s\n", aaa);
1387                 } else {
1388                         scr_printf("%s\n", aaa);
1389                         ++lines_printed;
1390                         lines_printed = checkpagin(lines_printed, pagin, height);
1391                 }
1392                 c = 1;
1393                 if (sigcaught) goto OOPS;
1394                 strcpy(aaa, "");
1395                 goto FMTA;
1396         }
1397         goto FMTA;
1398
1399         /* keypress caught; drain the server */
1400 OOPS:   do {
1401                 serv_gets(aaa);
1402         } while (strcmp(aaa, "000"));
1403
1404 FMTEND:
1405         if (fpout) {
1406                 fprintf(fpout, "\n");
1407         } else {
1408                 scr_printf("\n");
1409                 ++lines_printed;
1410                 lines_printed = checkpagin(lines_printed, pagin, height);
1411         }
1412         return (sigcaught);
1413 }
1414
1415
1416 /*
1417  * support ANSI color if defined
1418  */
1419 void color(int colornum)
1420 {
1421         static int is_bold = 0;
1422         static int hold_color, current_color;
1423
1424         if (colornum == COLOR_PUSH) {
1425                 hold_color = current_color;
1426                 return;
1427         }
1428
1429         if (colornum == COLOR_POP) {
1430                 color(hold_color);
1431                 return;
1432         }
1433
1434         current_color = colornum;
1435         if (enable_color) {
1436 #ifdef HAVE_CURSES_H
1437                 if (scr_color(colornum))
1438                         return;
1439 #endif
1440                 /* When switching to dim white, actually output an 'original
1441                  * pair' sequence -- this looks better on black-on-white
1442                  * terminals.
1443                  */
1444                 if (colornum == DIM_WHITE)
1445                         printf("\033[39;49m");
1446                 else
1447                         printf("\033[3%d;40m", (colornum & 7));
1448
1449                 if ((colornum >= 8) && (is_bold == 0)) {
1450                         printf("\033[1m");
1451                         is_bold = 1;
1452                 } else if ((colornum < 8) && (is_bold == 1)) {
1453                         printf("\033[0m");
1454                         is_bold = 0;
1455                 }
1456                 scr_flush();
1457         }
1458 }
1459
1460 void cls(int colornum)
1461 {
1462         if (enable_color) {
1463                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum);
1464                 scr_flush();
1465         }
1466 }
1467
1468
1469 /*
1470  * Detect whether ANSI color is available (answerback)
1471  */
1472 void send_ansi_detect(void)
1473 {
1474         if (rc_ansi_color == 2) {
1475                 printf("\033[c");
1476                 scr_flush();
1477                 time(&AnsiDetect);
1478         }
1479 }
1480
1481 void look_for_ansi(void)
1482 {
1483         fd_set rfds;
1484         struct timeval tv;
1485         char abuf[512];
1486         time_t now;
1487         int a;
1488
1489         if (rc_ansi_color == 0) {
1490                 enable_color = 0;
1491         } else if (rc_ansi_color == 1) {
1492                 enable_color = 1;
1493         } else if (rc_ansi_color == 2) {
1494
1495                 /* otherwise, do the auto-detect */
1496
1497                 strcpy(abuf, "");
1498
1499                 time(&now);
1500                 if ((now - AnsiDetect) < 2)
1501                         sleep(1);
1502
1503                 do {
1504                         FD_ZERO(&rfds);
1505                         FD_SET(0, &rfds);
1506                         tv.tv_sec = 0;
1507                         tv.tv_usec = 1;
1508
1509                         select(1, &rfds, NULL, NULL, &tv);
1510                         if (FD_ISSET(0, &rfds)) {
1511                                 abuf[strlen(abuf) + 1] = 0;
1512                                 read(0, &abuf[strlen(abuf)], 1);
1513                         }
1514                 } while (FD_ISSET(0, &rfds));
1515
1516                 for (a = 0; a < strlen(abuf); ++a) {
1517                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1518                             && (abuf[a + 2] == '?')) {
1519                                 enable_color = 1;
1520                         }
1521                 }
1522         }
1523 }
1524
1525
1526 /*
1527  * Display key options (highlight hotkeys inside angle brackets)
1528  */
1529 void keyopt(char *buf) {
1530         int i;
1531
1532         color(DIM_WHITE);
1533         for (i=0; i<strlen(buf); ++i) {
1534                 if (buf[i]=='<') {
1535                         scr_putc(buf[i]);
1536                         color(BRIGHT_MAGENTA);
1537                 } else {
1538                         if (buf[i]=='>') {
1539                                 color(DIM_WHITE);
1540                         }
1541                         scr_putc(buf[i]);
1542                 }
1543         }
1544         color(DIM_WHITE);
1545 }
1546
1547
1548
1549 /*
1550  * Present a key-menu line choice type of thing
1551  */
1552 char keymenu(char *menuprompt, char *menustring) {
1553         int i, c, a;
1554         int choices;
1555         int do_prompt = 0;
1556         char buf[1024];
1557         int ch;
1558         int display_prompt = 1;
1559
1560         choices = num_tokens(menustring, '|');
1561
1562         if (menuprompt != NULL) do_prompt = 1;
1563         if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1564
1565         while (1) {
1566                 if (display_prompt) {
1567                         if (do_prompt) {
1568                                 scr_printf("%s ", menuprompt);
1569                         } 
1570                         else {
1571                                 for (i=0; i<choices; ++i) {
1572                                         extract(buf, menustring, i);
1573                                         keyopt(buf);
1574                                         scr_printf(" ");
1575                                 }
1576                         }
1577                         scr_printf(" -> ");
1578                         display_prompt = 0;
1579                 }
1580                 ch = lkey();
1581         
1582                 if ( (do_prompt) && (ch=='?') ) {
1583                         scr_printf("\rOne of...                               ");
1584                         scr_printf("                                      \n");
1585                         for (i=0; i<choices; ++i) {
1586                                 extract(buf, menustring, i);
1587                                 scr_printf("   ");
1588                                 keyopt(buf);
1589                                 scr_printf("\n");
1590                         }
1591                         scr_printf("\n");
1592                         display_prompt = 1;
1593                 }
1594
1595                 for (i=0; i<choices; ++i) {
1596                         extract(buf, menustring, i);
1597                         for (c=1; c<strlen(buf); ++c) {
1598                                 if ( (ch == tolower(buf[c]))
1599                                    && (buf[c-1]=='<')
1600                                    && (buf[c+1]=='>') ) {
1601                                         for (a=0; a<strlen(buf); ++a) {
1602                                                 if ( (a!=(c-1)) && (a!=(c+1))) {
1603                                                         scr_putc(buf[a]);
1604                                                 }
1605                                         }
1606                                         scr_printf("\n\n");
1607                                         return ch;
1608                                 }
1609                         }
1610                 }
1611         }
1612 }