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