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