* Message reading and other functions which use the fmout() screen
[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 #include "citadel_ipc.h"
56 #ifndef HAVE_SNPRINTF
57 #include "snprintf.h"
58 #endif
59 #include "screen.h"
60
61 struct citcmd {
62         struct citcmd *next;
63         int c_cmdnum;
64         int c_axlevel;
65         char c_keys[5][64];
66 };
67
68 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
69
70
71 int rc_exp_beep;
72 char rc_exp_cmd[1024];
73 int rc_allow_attachments;
74 int rc_display_message_numbers;
75 int rc_force_mail_prompts;
76 int rc_remember_passwords;
77 int rc_ansi_color;
78 int num_urls = 0;
79 int rc_prompt_control = 0;
80 time_t rc_idle_threshold = (time_t)900;
81 char urls[MAXURLS][SIZ];
82 char rc_url_cmd[SIZ];
83
84 char *gl_string;
85 int next_lazy_cmd = 5;
86
87 int lines_printed = 0;          /* line count for paginator */
88 extern int screenwidth, screenheight;
89 extern int termn8;
90
91 struct citcmd *cmdlist = NULL;
92
93
94 /* these variables are local to this module */
95 char keepalives_enabled = KA_YES;       /* send NOOPs to server when idle */
96 int ok_to_interrupt = 0;                /* print express msgs asynchronously */
97 time_t AnsiDetect;                      /* when did we send the detect code? */
98 int enable_color = 0;                   /* nonzero for ANSI color */
99
100
101
102
103 /*
104  * If an interesting key has been pressed, return its value, otherwise 0
105  */
106 char was_a_key_pressed(void) {
107         fd_set rfds;
108         struct timeval tv;
109         int the_character;
110         int retval;
111
112         FD_ZERO(&rfds);
113         FD_SET(0, &rfds);
114         tv.tv_sec = 0;
115         tv.tv_usec = 0;
116         retval = select(1, &rfds, NULL, NULL, &tv); 
117
118         /* Careful!  Disable keepalives during keyboard polling; we're probably
119          * in the middle of a data transfer from the server, in which case
120          * sending a NOOP would throw the client protocol out of sync.
121          */
122         if (FD_ISSET(0, &rfds)) {
123                 set_keepalives(KA_NO);
124                 the_character = inkey();
125                 set_keepalives(KA_YES);
126         }
127         else {
128                 the_character = 0;
129         }
130         return(the_character);
131 }
132
133
134
135
136
137 /*
138  * Check to see if we need to pause at the end of a screen.
139  * If we do, we have to switch to half keepalives during the pause because
140  * we are probably in the middle of a server operation and the NOOP command
141  * would confuse everything.
142  */
143 int checkpagin(int lp, int pagin, int height)
144 {
145         int thekey;
146
147         if (sigcaught) return(lp);
148         thekey = was_a_key_pressed();
149         if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
150                 thekey = STOP_KEY;
151         if (thekey == 'n' || thekey == 'N')
152                 thekey = NEXT_KEY;
153         if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
154         if (sigcaught) return(lp);
155
156         if (!pagin) return(0);
157         if (lp>=(height-1)) {
158                 set_keepalives(KA_HALF);
159                 hit_any_key();
160                 set_keepalives(KA_YES);
161                 return(0);
162         }
163         return(lp);
164 }
165
166
167
168
169 /*
170  * pprintf()  ...   paginated version of printf()
171  */
172 void pprintf(const char *format, ...) {   
173         va_list arg_ptr;
174         static char buf[4096];  /* static for performance, change if needed */
175         int i;
176
177         /* If sigcaught is nonzero, a keypress has interrupted this and we
178          * should just drain output.
179          */
180         if (sigcaught) return;
181  
182         /* Otherwise, start spewing... */ 
183         va_start(arg_ptr, format);   
184         vsnprintf(buf, sizeof(buf), format, arg_ptr);   
185         va_end(arg_ptr);   
186
187         for (i=0; i<strlen(buf); ++i) {
188                 scr_putc(buf[i]);
189                 if (buf[i]==10) {
190                         ++lines_printed;
191                         lines_printed = checkpagin(lines_printed,
192                                 (userflags & US_PAGINATOR),
193                                 screenheight);
194                 }
195         }
196 }   
197
198
199
200 /*
201  * print_express()  -  print express messages if there are any
202  */
203 void print_express(void)
204 {
205         char buf[1024];
206         FILE *outpipe;
207         time_t timestamp;
208         struct tm *stamp;
209         int flags = 0;
210         char sender[64];
211         char node[64];
212         char *listing = NULL;
213         int r;                  /* IPC result code */
214
215         if (express_msgs == 0)
216                 return;
217
218         if (rc_exp_beep) {
219                 scr_putc(7);
220         }
221         if (strlen(rc_exp_cmd) == 0) {
222                 color(BRIGHT_RED);
223                 scr_printf("\r---");
224         }
225         
226         while (express_msgs != 0) {
227                 r = CtdlIPCGetInstantMessage(&listing, buf);
228                 if (r / 100 != 1)
229                         return;
230         
231                 express_msgs = extract_int(buf, 0);
232                 timestamp = extract_long(buf, 1);
233                 flags = extract_int(buf, 2);
234                 extract(sender, buf, 3);
235                 extract(node, buf, 4);
236                 strcpy(last_paged, sender);
237         
238                 stamp = localtime(&timestamp);
239
240                 /* If the page is a Logoff Request, honor it. */
241                 if (flags & 2) {
242                         termn8 = 1;
243                         return;
244                 }
245         
246                 if (strlen(rc_exp_cmd) > 0) {
247                         outpipe = popen(rc_exp_cmd, "w");
248                         if (outpipe != NULL) {
249                                 /* Header derived from flags */
250                                 if (flags & 2)
251                                         fprintf(outpipe,
252                                                "Please log off now, as requested ");
253                                 else if (flags & 1)
254                                         fprintf(outpipe, "Broadcast message ");
255                                 else if (flags & 4)
256                                         fprintf(outpipe, "Chat request ");
257                                 else
258                                         fprintf(outpipe, "Message ");
259                                 /* Timestamp.  Can this be improved? */
260                                 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)
261                                         fprintf(outpipe, "at 12:%02d%cm",
262                                                 stamp->tm_min, 
263                                                 stamp->tm_hour ? 'p' : 'a');
264                                 else if (stamp->tm_hour > 12)           /* pm */
265                                         fprintf(outpipe, "at %d:%02dpm",
266                                                 stamp->tm_hour - 12,
267                                                 stamp->tm_min);
268                                 else                                    /* am */
269                                         fprintf(outpipe, "at %d:%02dam",
270                                                 stamp->tm_hour, stamp->tm_min);
271                                 fprintf(outpipe, " from %s", sender);
272                                 if (strncmp(serv_info.serv_nodename, node, 32))
273                                         fprintf(outpipe, " @%s", node);
274                                 fprintf(outpipe, ":\n%s\n", listing);
275                                 pclose(outpipe);
276                                 if (express_msgs == 0)
277                                         return;
278                                 continue;
279                         }
280                 }
281                 /* fall back to built-in express message display */
282                 scr_printf("\n");
283                 lines_printed++;
284
285                 /* Header derived from flags */
286                 if (flags & 2)
287                         scr_printf("Please log off now, as requested ");
288                 else if (flags & 1)
289                         scr_printf("Broadcast message ");
290                 else if (flags & 4)
291                         scr_printf("Chat request ");
292                 else
293                         scr_printf("Message ");
294         
295                 /* Timestamp.  Can this be improved? */
296                 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)/* 12am/12pm */
297                         scr_printf("at 12:%02d%cm", stamp->tm_min, 
298                                 stamp->tm_hour ? 'p' : 'a');
299                 else if (stamp->tm_hour > 12)                   /* pm */
300                         scr_printf("at %d:%02dpm",
301                                 stamp->tm_hour - 12, stamp->tm_min);
302                 else                                            /* am */
303                         scr_printf("at %d:%02dam", stamp->tm_hour, stamp->tm_min);
304                 
305                 /* Sender */
306                 scr_printf(" from %s", sender);
307         
308                 /* Remote node, if any */
309                 if (strncmp(serv_info.serv_nodename, node, 32))
310                         scr_printf(" @%s", node);
311         
312                 scr_printf(":\n");
313                 lines_printed++;
314                 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
315                 free(listing);
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 format given text */
1263         char *text,     /* Text to be formatted (when fpin is NULL) */
1264         FILE *fpout,    /* File to write to, or NULL to write to screen */
1265         char pagin,     /* nonzero if we should use the paginator */
1266         int height,     /* screen height to use */
1267         int starting_lp,/* starting value for lines_printed, -1 for global */
1268         char subst)     /* nonzero if we should use hypertext mode */
1269 {
1270         int a, b, c, old;
1271         int real = (-1);
1272         char aaa[140];
1273         char buffer[512];
1274         char *e;
1275         int eof_flag = 0;
1276
1277         num_urls = 0;   /* Start with a clean slate of embedded URL's */
1278
1279         if (starting_lp >= 0) {
1280                 lines_printed = starting_lp;
1281         }
1282         strcpy(aaa, "");
1283         old = 255;
1284         strcpy(buffer, "");
1285         c = 1;                  /* c is the current pos */
1286         e = text;               /* e is pointer to current pos */
1287
1288 FMTA:   while ((eof_flag == 0) && (strlen(buffer) < 126)) {
1289                 if (fpin != NULL) {     /* read from file */
1290                         if (feof(fpin))
1291                                 eof_flag = 1;
1292                         if (eof_flag == 0) {
1293                                 a = getc(fpin);
1294                                 buffer[strlen(buffer) + 1] = 0;
1295                                 buffer[strlen(buffer)] = a;
1296                         }
1297                 } else {        /* read from text */
1298                         if (!*e) {
1299                                 eof_flag = 1;
1300                                 while (isspace(buffer[strlen(buffer) - 1]))
1301                                         buffer[strlen(buffer) - 1] = 0;
1302                                 buffer[strlen(buffer) + 1] = 0;
1303                                 buffer[strlen(buffer)] = 10;
1304                         }
1305                         if (eof_flag == 0) {
1306                                 a = *e++;
1307                                 buffer[strlen(buffer) + 1] = 0;
1308                                 buffer[strlen(buffer)] = a;
1309                         }
1310                 }
1311         }
1312
1313         if ( (!strncasecmp(buffer, "http://", 7))
1314            || (!strncasecmp(buffer, "ftp://", 6)) ) {
1315                 safestrncpy(urls[num_urls], buffer, (SIZ-1));
1316                 for (a=0; a<strlen(urls[num_urls]); ++a) {
1317                         b = urls[num_urls][a];
1318                         if ( (b==' ') || (b==')') || (b=='>') || (b==10)
1319                            || (b==13) || (b==9) || (b=='\"') )
1320                                 urls[num_urls][a] = 0;
1321                 }
1322                 ++num_urls;
1323         }
1324
1325         buffer[strlen(buffer) + 1] = 0;
1326         a = buffer[0];
1327         strcpy(buffer, &buffer[1]);
1328
1329         old = real;
1330         real = a;
1331         if (a <= 0)
1332                 goto FMTEND;
1333
1334         if (((a == 13) || (a == 10)) && (old != 13) && (old != 10))
1335                 a = 32;
1336         if (((old == 13) || (old == 10)) && (isspace(real))) {
1337                 if (fpout) {
1338                         fprintf(fpout, "\n");
1339                 } else {
1340                         scr_printf("\n");
1341                         ++lines_printed;
1342                         lines_printed = checkpagin(lines_printed, pagin, height);
1343                 }
1344                 c = 1;
1345         }
1346         if (a > 126)
1347                 goto FMTA;
1348
1349         if (a > 32) {
1350                 if (((strlen(aaa) + c) > (width - 1)) && (strlen(aaa) > (width - 1))) {
1351                         if (fpout) {
1352                                 fprintf(fpout, "\n%s", aaa);
1353                         } else {
1354                                 scr_printf("\n%s", aaa);
1355                                 ++lines_printed;
1356                                 lines_printed = checkpagin(lines_printed, pagin, height);
1357                         }
1358                         c = strlen(aaa);
1359                         aaa[0] = 0;
1360                 }
1361                 b = strlen(aaa);
1362                 aaa[b] = a;
1363                 aaa[b + 1] = 0;
1364         }
1365         if (a == 32) {
1366                 if ((strlen(aaa) + c) > (width - 1)) {
1367                         c = 1;
1368                         if (fpout) {
1369                                 fprintf(fpout, "\n");
1370                         } else {
1371                                 scr_printf("\n");
1372                                 ++lines_printed;
1373                                 lines_printed = checkpagin(lines_printed, pagin, height);
1374                         }
1375                 }
1376                 if (fpout) {
1377                         fprintf(fpout, "%s ", aaa);
1378                 } else {
1379                         scr_printf("%s ", aaa);
1380                 }
1381                 ++c;
1382                 c = c + strlen(aaa);
1383                 strcpy(aaa, "");
1384                 goto FMTA;
1385         }
1386         if ((a == 13) || (a == 10)) {
1387                 if (fpout) {
1388                         fprintf(fpout, "%s\n", aaa);
1389                 } else {
1390                         scr_printf("%s\n", aaa);
1391                         ++lines_printed;
1392                         lines_printed = checkpagin(lines_printed, pagin, height);
1393                 }
1394                 c = 1;
1395                 if (sigcaught) goto OOPS;
1396                 strcpy(aaa, "");
1397                 goto FMTA;
1398         }
1399         goto FMTA;
1400
1401         /* keypress caught; drain the server */
1402 OOPS:   /* do {
1403                 serv_gets(aaa);
1404         } while (strcmp(aaa, "000")); */
1405
1406 FMTEND:
1407         if (fpout) {
1408                 fprintf(fpout, "\n");
1409         } else {
1410                 scr_printf("\n");
1411                 ++lines_printed;
1412                 lines_printed = checkpagin(lines_printed, pagin, height);
1413         }
1414         return (sigcaught);
1415 }
1416
1417
1418 /*
1419  * support ANSI color if defined
1420  */
1421 void color(int colornum)
1422 {
1423         static int is_bold = 0;
1424         static int hold_color, current_color;
1425
1426         if (colornum == COLOR_PUSH) {
1427                 hold_color = current_color;
1428                 return;
1429         }
1430
1431         if (colornum == COLOR_POP) {
1432                 color(hold_color);
1433                 return;
1434         }
1435
1436         current_color = colornum;
1437         if (enable_color) {
1438 #ifdef HAVE_CURSES_H
1439                 if (scr_color(colornum))
1440                         return;
1441 #endif
1442                 /* When switching to dim white, actually output an 'original
1443                  * pair' sequence -- this looks better on black-on-white
1444                  * terminals.
1445                  */
1446                 if (colornum == DIM_WHITE)
1447                         printf("\033[39;49m");
1448                 else
1449                         printf("\033[3%d;40m", (colornum & 7));
1450
1451                 if ((colornum >= 8) && (is_bold == 0)) {
1452                         printf("\033[1m");
1453                         is_bold = 1;
1454                 } else if ((colornum < 8) && (is_bold == 1)) {
1455                         printf("\033[0m");
1456                         is_bold = 0;
1457                 }
1458                 scr_flush();
1459         }
1460 }
1461
1462 void cls(int colornum)
1463 {
1464         if (enable_color) {
1465                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum);
1466                 scr_flush();
1467         }
1468 }
1469
1470
1471 /*
1472  * Detect whether ANSI color is available (answerback)
1473  */
1474 void send_ansi_detect(void)
1475 {
1476         if (rc_ansi_color == 2) {
1477                 printf("\033[c");
1478                 scr_flush();
1479                 time(&AnsiDetect);
1480         }
1481 }
1482
1483 void look_for_ansi(void)
1484 {
1485         fd_set rfds;
1486         struct timeval tv;
1487         char abuf[512];
1488         time_t now;
1489         int a;
1490
1491         if (rc_ansi_color == 0) {
1492                 enable_color = 0;
1493         } else if (rc_ansi_color == 1) {
1494                 enable_color = 1;
1495         } else if (rc_ansi_color == 2) {
1496
1497                 /* otherwise, do the auto-detect */
1498
1499                 strcpy(abuf, "");
1500
1501                 time(&now);
1502                 if ((now - AnsiDetect) < 2)
1503                         sleep(1);
1504
1505                 do {
1506                         FD_ZERO(&rfds);
1507                         FD_SET(0, &rfds);
1508                         tv.tv_sec = 0;
1509                         tv.tv_usec = 1;
1510
1511                         select(1, &rfds, NULL, NULL, &tv);
1512                         if (FD_ISSET(0, &rfds)) {
1513                                 abuf[strlen(abuf) + 1] = 0;
1514                                 read(0, &abuf[strlen(abuf)], 1);
1515                         }
1516                 } while (FD_ISSET(0, &rfds));
1517
1518                 for (a = 0; a < strlen(abuf); ++a) {
1519                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1520                             && (abuf[a + 2] == '?')) {
1521                                 enable_color = 1;
1522                         }
1523                 }
1524         }
1525 }
1526
1527
1528 /*
1529  * Display key options (highlight hotkeys inside angle brackets)
1530  */
1531 void keyopt(char *buf) {
1532         int i;
1533
1534         color(DIM_WHITE);
1535         for (i=0; i<strlen(buf); ++i) {
1536                 if (buf[i]=='<') {
1537                         scr_putc(buf[i]);
1538                         color(BRIGHT_MAGENTA);
1539                 } else {
1540                         if (buf[i]=='>') {
1541                                 color(DIM_WHITE);
1542                         }
1543                         scr_putc(buf[i]);
1544                 }
1545         }
1546         color(DIM_WHITE);
1547 }
1548
1549
1550
1551 /*
1552  * Present a key-menu line choice type of thing
1553  */
1554 char keymenu(char *menuprompt, char *menustring) {
1555         int i, c, a;
1556         int choices;
1557         int do_prompt = 0;
1558         char buf[1024];
1559         int ch;
1560         int display_prompt = 1;
1561
1562         choices = num_tokens(menustring, '|');
1563
1564         if (menuprompt != NULL) do_prompt = 1;
1565         if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1566
1567         while (1) {
1568                 if (display_prompt) {
1569                         if (do_prompt) {
1570                                 scr_printf("%s ", menuprompt);
1571                         } 
1572                         else {
1573                                 for (i=0; i<choices; ++i) {
1574                                         extract(buf, menustring, i);
1575                                         keyopt(buf);
1576                                         scr_printf(" ");
1577                                 }
1578                         }
1579                         scr_printf(" -> ");
1580                         display_prompt = 0;
1581                 }
1582                 ch = lkey();
1583         
1584                 if ( (do_prompt) && (ch=='?') ) {
1585                         scr_printf("\rOne of...                               ");
1586                         scr_printf("                                      \n");
1587                         for (i=0; i<choices; ++i) {
1588                                 extract(buf, menustring, i);
1589                                 scr_printf("   ");
1590                                 keyopt(buf);
1591                                 scr_printf("\n");
1592                         }
1593                         scr_printf("\n");
1594                         display_prompt = 1;
1595                 }
1596
1597                 for (i=0; i<choices; ++i) {
1598                         extract(buf, menustring, i);
1599                         for (c=1; c<strlen(buf); ++c) {
1600                                 if ( (ch == tolower(buf[c]))
1601                                    && (buf[c-1]=='<')
1602                                    && (buf[c+1]=='>') ) {
1603                                         for (a=0; a<strlen(buf); ++a) {
1604                                                 if ( (a!=(c-1)) && (a!=(c+1))) {
1605                                                         scr_putc(buf[a]);
1606                                                 }
1607                                         }
1608                                         scr_printf("\n\n");
1609                                         return ch;
1610                                 }
1611                         }
1612                 }
1613         }
1614 }