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