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