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