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