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