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