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