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