039443edfc27099d68f5640e34613612ad5b78d7
[dragonfly.git] / usr.bin / chat / chat.c
1 /*
2  *      Chat -- a program for automatic session establishment (i.e. dial
3  *              the phone and log in).
4  *
5  * Standard termination codes:
6  *  0 - successful completion of the script
7  *  1 - invalid argument, expect string too large, etc.
8  *  2 - error on an I/O operation or fatal error condition.
9  *  3 - timeout waiting for a simple string.
10  *  4 - the first string declared as "ABORT"
11  *  5 - the second string declared as "ABORT"
12  *  6 - ... and so on for successive ABORT strings.
13  *
14  *      This software is in the public domain.
15  *
16  * -----------------
17  *      added -T and -U option and \T and \U substitution to pass a phone
18  *      number into chat script. Two are needed for some ISDN TA applications.
19  *      Keith Dart <kdart@cisco.com>
20  *      
21  *
22  *      Added SAY keyword to send output to stderr.
23  *      This allows to turn ECHO OFF and to output specific, user selected,
24  *      text to give progress messages. This best works when stderr
25  *      exists (i.e.: pppd in nodetach mode).
26  *
27  *      Added HANGUP directives to allow for us to be called
28  *      back. When HANGUP is set to NO, chat will not hangup at HUP signal.
29  *      We rely on timeouts in that case.
30  *
31  *      Added CLR_ABORT to clear previously set ABORT string. This has been
32  *      dictated by the HANGUP above as "NO CARRIER" (for example) must be
33  *      an ABORT condition until we know the other host is going to close
34  *      the connection for call back. As soon as we have completed the
35  *      first stage of the call back sequence, "NO CARRIER" is a valid, non
36  *      fatal string. As soon as we got called back (probably get "CONNECT"),
37  *      we should re-arm the ABORT "NO CARRIER". Hence the CLR_ABORT command.
38  *      Note that CLR_ABORT packs the abort_strings[] array so that we do not
39  *      have unused entries not being reclaimed.
40  *
41  *      In the same vein as above, added CLR_REPORT keyword.
42  *
43  *      Allow for comments. Line starting with '#' are comments and are
44  *      ignored. If a '#' is to be expected as the first character, the 
45  *      expect string must be quoted.
46  *
47  *
48  *              Francis Demierre <Francis@SwissMail.Com>
49  *              Thu May 15 17:15:40 MET DST 1997
50  *
51  *
52  *      Added -r "report file" switch & REPORT keyword.
53  *              Robert Geer <bgeer@xmission.com>
54  *
55  *      Added -s "use stderr" and -S "don't use syslog" switches.
56  *              June 18, 1997
57  *              Karl O. Pinc <kop@meme.com>
58  *
59  *
60  *      Added -e "echo" switch & ECHO keyword
61  *              Dick Streefland <dicks@tasking.nl>
62  *
63  *
64  *      Considerable updates and modifications by
65  *              Al Longyear <longyear@pobox.com>
66  *              Paul Mackerras <paulus@cs.anu.edu.au>
67  *
68  *
69  *      The original author is:
70  *
71  *              Karl Fox <karl@MorningStar.Com>
72  *              Morning Star Technologies, Inc.
73  *              1760 Zollinger Road
74  *              Columbus, OH  43221
75  *              (614)451-1883
76  *
77  *
78  * $FreeBSD: src/usr.bin/chat/chat.c,v 1.20 2003/08/22 17:47:40 markm Exp $
79  * $DragonFly: src/usr.bin/chat/chat.c,v 1.8 2004/12/31 21:16:09 cpressey Exp $
80  */
81
82 #include <sys/cdefs.h>
83 #include <sys/types.h>
84 #include <sys/stat.h>
85 #include <ctype.h>
86 #include <errno.h>
87 #include <fcntl.h>
88 #include <signal.h>
89 #include <stdarg.h>
90 #include <stdio.h>
91 #include <stdlib.h>
92 #include <string.h>
93 #include <syslog.h>
94 #include <termios.h>
95 #include <time.h>
96 #include <unistd.h>
97
98 #define STR_LEN 1024
99
100 #ifndef SIGTYPE
101 #define SIGTYPE void
102 #endif
103
104 #ifndef O_NONBLOCK
105 #define O_NONBLOCK      O_NDELAY
106 #endif
107
108 /*************** Micro getopt() *********************************************/
109 #define OPTION(c,v)     (_O&2&&**v?*(*v)++:!c||_O&4?0:(!(_O&1)&& \
110                                 (--c,++v),_O=4,c&&**v=='-'&&v[0][1]?*++*v=='-'\
111                                 &&!v[0][1]?(--c,++v,0):(_O=2,*(*v)++):0))
112 #define OPTARG(c,v)     (_O&2?**v||(++v,--c)?(_O=1,--c,*v++): \
113                                 (_O=4,(char*)0):(char*)0)
114 #define ARG(c,v)        (c?(--c,*v++):(char*)0)
115
116 static int _O = 0;              /* Internal state */
117 /*************** Micro getopt() *********************************************/
118
119 #define MAX_ABORTS              50
120 #define MAX_REPORTS             50
121 #define DEFAULT_CHAT_TIMEOUT    45
122
123 int echo          = 0;
124 int verbose       = 0;
125 int to_log        = 1;
126 int to_stderr     = 0;
127 int Verbose       = 0;
128 int quiet         = 0;
129 int exit_code     = 0;
130 FILE* report_fp   = (FILE *) 0;
131 char *report_file = (char *) 0;
132 char *chat_file   = (char *) 0;
133 char *phone_num   = (char *) 0;
134 char *phone_num2  = (char *) 0;
135 int timeout       = DEFAULT_CHAT_TIMEOUT;
136
137 static char blank[] = "";
138
139 int have_tty_parameters = 0;
140
141 #define term_parms struct termios
142 #define get_term_param(param) tcgetattr(0, param)
143 #define set_term_param(param) tcsetattr(0, TCSANOW, param)
144 struct termios saved_tty_parameters;
145
146 char *abort_string[MAX_ABORTS], *fail_reason = (char *)0,
147         fail_buffer[50];
148 int n_aborts = 0, abort_next = 0, timeout_next = 0, echo_next = 0;
149 int clear_abort_next = 0;
150
151 char *report_string[MAX_REPORTS] ;
152 char  report_buffer[50] ;
153 int n_reports = 0, report_next = 0, report_gathering = 0 ; 
154 int clear_report_next = 0;
155
156 int say_next = 0, hup_next = 0;
157
158 void *dup_mem(void *b, size_t c);
159 void *copy_of(char *s);
160 static void usage(void);
161 void logf(const char *fmt, ...);
162 void fatal(int code, const char *fmt, ...);
163 SIGTYPE sigalrm(int signo);
164 SIGTYPE sigint(int signo);
165 SIGTYPE sigterm(int signo);
166 SIGTYPE sighup(int signo);
167 void init(void);
168 void set_tty_parameters(void);
169 void echo_stderr(int);
170 void break_sequence(void);
171 void terminate(int status);
172 void do_file(char *chatfile);
173 int  get_string(char *string);
174 int  put_string(char *s);
175 int  write_char(int c);
176 int  put_char(int c);
177 int  get_char(void);
178 void chat_send(char *s);
179 char *character(int c);
180 void chat_expect(char *s);
181 char *clean(char *s, int sending);
182 void pack_array(char **array, int end);
183 char *expect_strtok(char *, const char *);
184 int vfmtmsg(char *, int, const char *, va_list);        /* vsprintf++ */
185
186 void *
187 dup_mem(void *b, size_t c)
188 {
189     void *ans = malloc (c);
190     if (!ans)
191         fatal(2, "memory error!");
192
193     memcpy (ans, b, c);
194     return ans;
195 }
196
197 void *
198 copy_of(char *s)
199 {
200     return dup_mem (s, strlen (s) + 1);
201 }
202
203 /*
204  * chat [ -v ] [-T number] [-U number] [ -t timeout ] [ -f chat-file ] \
205  * [ -r report-file ] \
206  *              [...[[expect[-say[-expect...]] say expect[-say[-expect]] ...]]]
207  *
208  *      Perform a UUCP-dialer-like chat script on stdin and stdout.
209  */
210 int
211 main(int argc, char *argv[])
212 {
213     int option;
214     char *arg;
215
216     tzset();
217
218     while ((option = OPTION(argc, argv)) != 0) {
219         switch (option) {
220         case 'e':
221             ++echo;
222             break;
223
224         case 'v':
225             ++verbose;
226             break;
227
228         case 'V':
229             ++Verbose;
230             break;
231
232         case 's':
233             ++to_stderr;
234             break;
235
236         case 'S':
237             to_log = 0;
238             break;
239
240         case 'f':
241             if ((arg = OPTARG(argc, argv)) != NULL)
242                     chat_file = copy_of(arg);
243             else
244                 usage();
245             break;
246
247         case 't':
248             if ((arg = OPTARG(argc, argv)) != NULL)
249                 timeout = atoi(arg);
250             else
251                 usage();
252             break;
253
254         case 'r':
255             arg = OPTARG (argc, argv);
256             if (arg) {
257                 if (report_fp != NULL)
258                     fclose (report_fp);
259                 report_file = copy_of (arg);
260                 report_fp   = fopen (report_file, "a");
261                 if (report_fp != NULL) {
262                     if (verbose)
263                         fprintf (report_fp, "Opening \"%s\"...\n",
264                                  report_file);
265                 }
266             }
267             break;
268
269         case 'T':
270             if ((arg = OPTARG(argc, argv)) != NULL)
271                 phone_num = copy_of(arg);
272             else
273                 usage();
274             break;
275
276         case 'U':
277             if ((arg = OPTARG(argc, argv)) != NULL)
278                 phone_num2 = copy_of(arg);
279             else
280                 usage();
281             break;
282
283         default:
284             usage();
285             break;
286         }
287     }
288 /*
289  * Default the report file to the stderr location
290  */
291     if (report_fp == NULL)
292         report_fp = stderr;
293
294     if (to_log) {
295         openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
296
297         if (verbose)
298             setlogmask(LOG_UPTO(LOG_INFO));
299         else
300             setlogmask(LOG_UPTO(LOG_WARNING));
301     }
302
303     init();
304     
305     if (chat_file != NULL) {
306         arg = ARG(argc, argv);
307         if (arg != NULL)
308             usage();
309         else
310             do_file (chat_file);
311     } else {
312         while ((arg = ARG(argc, argv)) != NULL) {
313             chat_expect(arg);
314
315             if ((arg = ARG(argc, argv)) != NULL)
316                 chat_send(arg);
317         }
318     }
319
320     terminate(0);
321     return 0;
322 }
323
324 /*
325  *  Process a chat script when read from a file.
326  */
327
328 void
329 do_file(char *chatfile)
330 {
331     int linect, sendflg;
332     char *sp, *arg, quote;
333     char buf [STR_LEN];
334     FILE *cfp;
335
336     cfp = fopen (chatfile, "r");
337     if (cfp == NULL)
338         fatal(1, "%s -- open failed: %m", chatfile);
339
340     linect = 0;
341     sendflg = 0;
342
343     while (fgets(buf, STR_LEN, cfp) != NULL) {
344         sp = strchr (buf, '\n');
345         if (sp)
346             *sp = '\0';
347
348         linect++;
349         sp = buf;
350
351         /* lines starting with '#' are comments. If a real '#'
352            is to be expected, it should be quoted .... */
353         if ( *sp == '#' )
354             continue;
355
356         while (*sp != '\0') {
357             if (*sp == ' ' || *sp == '\t') {
358                 ++sp;
359                 continue;
360             }
361
362             if (*sp == '"' || *sp == '\'') {
363                 quote = *sp++;
364                 arg = sp;
365                 while (*sp != quote) {
366                     if (*sp == '\0')
367                         fatal(1, "unterminated quote (line %d)", linect);
368
369                     if (*sp++ == '\\') {
370                         if (*sp != '\0')
371                             ++sp;
372                     }
373                 }
374             }
375             else {
376                 arg = sp;
377                 while (*sp != '\0' && *sp != ' ' && *sp != '\t')
378                     ++sp;
379             }
380
381             if (*sp != '\0')
382                 *sp++ = '\0';
383
384             if (sendflg)
385                 chat_send (arg);
386             else
387                 chat_expect (arg);
388             sendflg = !sendflg;
389         }
390     }
391     fclose (cfp);
392 }
393
394 /*
395  *      We got an error parsing the command line.
396  */
397 static void
398 usage(void)
399 {
400     fprintf(stderr, "\
401 Usage: chat [-e] [-v] [-V] [-t timeout] [-r report-file] [-T phone-number]\n\
402      [-U phone-number2] {-f chat-file | chat-script}\n");
403     exit(1);
404 }
405
406 char line[1024];
407
408 /*
409  * Send a message to syslog and/or stderr.
410  */
411 void
412 logf(const char *fmt, ...)
413 {
414     va_list args;
415
416     va_start(args, fmt);
417     vfmtmsg(line, sizeof(line), fmt, args);
418     if (to_log)
419         syslog(LOG_INFO, "%s", line);
420     if (to_stderr)
421         fprintf(stderr, "%s\n", line);
422 }
423
424 /*
425  *      Print an error message and terminate.
426  */
427
428 void
429 fatal(int code, const char *fmt, ...)
430 {
431     va_list args;
432
433     va_start(args, fmt);
434     vfmtmsg(line, sizeof(line), fmt, args);
435     if (to_log)
436         syslog(LOG_ERR, "%s", line);
437     if (to_stderr)
438         fprintf(stderr, "%s\n", line);
439     terminate(code);
440 }
441
442 int alarmed = 0;
443
444 SIGTYPE sigalrm(int signo __unused)
445 {
446     int flags;
447
448     alarm(1);
449     alarmed = 1;                /* Reset alarm to avoid race window */
450     signal(SIGALRM, sigalrm);   /* that can cause hanging in read() */
451
452     if ((flags = fcntl(0, F_GETFL, 0)) == -1)
453         fatal(2, "Can't get file mode flags on stdin: %m");
454
455     if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1)
456         fatal(2, "Can't set file mode flags on stdin: %m");
457
458     if (verbose)
459         logf("alarm");
460 }
461
462 SIGTYPE sigint(int signo __unused)
463 {
464     fatal(2, "SIGINT");
465 }
466
467 SIGTYPE sigterm(int signo __unused)
468 {
469     fatal(2, "SIGTERM");
470 }
471
472 SIGTYPE sighup(int signo __unused)
473 {
474     fatal(2, "SIGHUP");
475 }
476
477 void init(void)
478 {
479     signal(SIGINT, sigint);
480     signal(SIGTERM, sigterm);
481     signal(SIGHUP, sighup);
482
483     set_tty_parameters();
484     signal(SIGALRM, sigalrm);
485     alarm(0);
486     alarmed = 0;
487 }
488
489 void set_tty_parameters(void)
490 {
491 #if defined(get_term_param)
492     term_parms t;
493
494     if (get_term_param (&t) < 0)
495         fatal(2, "Can't get terminal parameters: %m");
496
497     saved_tty_parameters = t;
498     have_tty_parameters  = 1;
499
500     t.c_iflag     |= IGNBRK | ISTRIP | IGNPAR;
501     t.c_oflag      = 0;
502     t.c_lflag      = 0;
503     t.c_cc[VERASE] =
504     t.c_cc[VKILL]  = 0;
505     t.c_cc[VMIN]   = 1;
506     t.c_cc[VTIME]  = 0;
507
508     if (set_term_param (&t) < 0)
509         fatal(2, "Can't set terminal parameters: %m");
510 #endif
511 }
512
513 void break_sequence(void)
514 {
515     tcsendbreak (0, 0);
516 }
517
518 void terminate(int status)
519 {
520     echo_stderr(-1);
521     if (report_file != (char *) 0 && report_fp != (FILE *) NULL) {
522 /*
523  * Allow the last of the report string to be gathered before we terminate.
524  */
525         if (report_gathering) {
526             int c;
527             size_t rep_len;
528
529             rep_len = strlen(report_buffer);
530             while (rep_len + 1 <= sizeof(report_buffer)) {
531                 alarm(1);
532                 c = get_char();
533                 alarm(0);
534                 if (c < 0 || iscntrl(c))
535                     break;
536                 report_buffer[rep_len] = c;
537                 ++rep_len;
538             }
539             report_buffer[rep_len] = 0;
540             fprintf (report_fp, "chat:  %s\n", report_buffer);
541         }
542         if (verbose)
543             fprintf (report_fp, "Closing \"%s\".\n", report_file);
544         fclose (report_fp);
545         report_fp = (FILE *) NULL;
546     }
547
548 #if defined(get_term_param)
549     if (have_tty_parameters) {
550         if (set_term_param (&saved_tty_parameters) < 0)
551             fatal(2, "Can't restore terminal parameters: %m");
552     }
553 #endif
554
555     exit(status);
556 }
557
558 /*
559  *      'Clean up' this string.
560  */
561 char *
562 clean(char *s, int sending)
563 {
564     char temp[STR_LEN], cur_chr;
565     char *s1, *phchar;
566     int add_return = sending;
567 #define isoctal(chr) (((chr) >= '0') && ((chr) <= '7'))
568
569     s1 = temp;
570     /* Don't overflow buffer, leave room for chars we append later */
571     while (*s && s1 - temp < (off_t)(sizeof(temp) - 2 - add_return)) {
572         cur_chr = *s++;
573         if (cur_chr == '^') {
574             cur_chr = *s++;
575             if (cur_chr == '\0') {
576                 *s1++ = '^';
577                 break;
578             }
579             cur_chr &= 0x1F;
580             if (cur_chr != 0) {
581                 *s1++ = cur_chr;
582             }
583             continue;
584         }
585
586         if (cur_chr != '\\') {
587             *s1++ = cur_chr;
588             continue;
589         }
590
591         cur_chr = *s++;
592         if (cur_chr == '\0') {
593             if (sending) {
594                 *s1++ = '\\';
595                 *s1++ = '\\';
596             }
597             break;
598         }
599
600         switch (cur_chr) {
601         case 'b':
602             *s1++ = '\b';
603             break;
604
605         case 'c':
606             if (sending && *s == '\0')
607                 add_return = 0;
608             else
609                 *s1++ = cur_chr;
610             break;
611
612         case '\\':
613         case 'K':
614         case 'p':
615         case 'd':
616             if (sending)
617                 *s1++ = '\\';
618
619             *s1++ = cur_chr;
620             break;
621
622         case 'T':
623             if (sending && phone_num) {
624                 for ( phchar = phone_num; *phchar != '\0'; phchar++) 
625                     *s1++ = *phchar;
626             }
627             else {
628                 *s1++ = '\\';
629                 *s1++ = 'T';
630             }
631             break;
632
633         case 'U':
634             if (sending && phone_num2) {
635                 for ( phchar = phone_num2; *phchar != '\0'; phchar++) 
636                     *s1++ = *phchar;
637             }
638             else {
639                 *s1++ = '\\';
640                 *s1++ = 'U';
641             }
642             break;
643
644         case 'q':
645             quiet = 1;
646             break;
647
648         case 'r':
649             *s1++ = '\r';
650             break;
651
652         case 'n':
653             *s1++ = '\n';
654             break;
655
656         case 's':
657             *s1++ = ' ';
658             break;
659
660         case 't':
661             *s1++ = '\t';
662             break;
663
664         case 'N':
665             if (sending) {
666                 *s1++ = '\\';
667                 *s1++ = '\0';
668             }
669             else
670                 *s1++ = 'N';
671             break;
672             
673         default:
674             if (isoctal (cur_chr)) {
675                 cur_chr &= 0x07;
676                 if (isoctal (*s)) {
677                     cur_chr <<= 3;
678                     cur_chr |= *s++ - '0';
679                     if (isoctal (*s)) {
680                         cur_chr <<= 3;
681                         cur_chr |= *s++ - '0';
682                     }
683                 }
684
685                 if (cur_chr != 0 || sending) {
686                     if (sending && (cur_chr == '\\' || cur_chr == 0))
687                         *s1++ = '\\';
688                     *s1++ = cur_chr;
689                 }
690                 break;
691             }
692
693             if (sending)
694                 *s1++ = '\\';
695             *s1++ = cur_chr;
696             break;
697         }
698     }
699
700     if (add_return)
701         *s1++ = '\r';
702
703     *s1++ = '\0'; /* guarantee closure */
704     *s1++ = '\0'; /* terminate the string */
705     return dup_mem (temp, (size_t) (s1 - temp)); /* may have embedded nuls */
706 }
707
708 /*
709  * A modified version of 'strtok'. This version skips \ sequences.
710  */
711
712 char *
713 expect_strtok (char *s, const char *term)
714 {
715     static  char *str   = blank;
716     int     escape_flag = 0;
717     char   *result;
718
719 /*
720  * If a string was specified then do initial processing.
721  */
722     if (s)
723         str = s;
724
725 /*
726  * If this is the escape flag then reset it and ignore the character.
727  */
728     if (*str)
729         result = str;
730     else
731         result = (char *) 0;
732
733     while (*str) {
734         if (escape_flag) {
735             escape_flag = 0;
736             ++str;
737             continue;
738         }
739
740         if (*str == '\\') {
741             ++str;
742             escape_flag = 1;
743             continue;
744         }
745
746 /*
747  * If this is not in the termination string, continue.
748  */
749         if (strchr (term, *str) == (char *) 0) {
750             ++str;
751             continue;
752         }
753
754 /*
755  * This is the terminator. Mark the end of the string and stop.
756  */
757         *str++ = '\0';
758         break;
759     }
760     return (result);
761 }
762
763 /*
764  * Process the expect string
765  */
766
767 void
768 chat_expect(char *s)
769 {
770     char *expect;
771     char *reply;
772
773     if (strcmp(s, "HANGUP") == 0) {
774         ++hup_next;
775         return;
776     }
777  
778     if (strcmp(s, "ABORT") == 0) {
779         ++abort_next;
780         return;
781     }
782
783     if (strcmp(s, "CLR_ABORT") == 0) {
784         ++clear_abort_next;
785         return;
786     }
787
788     if (strcmp(s, "REPORT") == 0) {
789         ++report_next;
790         return;
791     }
792
793     if (strcmp(s, "CLR_REPORT") == 0) {
794         ++clear_report_next;
795         return;
796     }
797
798     if (strcmp(s, "TIMEOUT") == 0) {
799         ++timeout_next;
800         return;
801     }
802
803     if (strcmp(s, "ECHO") == 0) {
804         ++echo_next;
805         return;
806     }
807
808     if (strcmp(s, "SAY") == 0) {
809         ++say_next;
810         return;
811     }
812
813 /*
814  * Fetch the expect and reply string.
815  */
816     for (;;) {
817         expect = expect_strtok (s, "-");
818         s      = (char *) 0;
819
820         if (expect == (char *) 0)
821             return;
822
823         reply = expect_strtok (s, "-");
824
825 /*
826  * Handle the expect string. If successful then exit.
827  */
828         if (get_string (expect))
829             return;
830
831 /*
832  * If there is a sub-reply string then send it. Otherwise any condition
833  * is terminal.
834  */
835         if (reply == (char *) 0 || exit_code != 3)
836             break;
837
838         chat_send (reply);
839     }
840
841 /*
842  * The expectation did not occur. This is terminal.
843  */
844     if (fail_reason)
845         logf("Failed (%s)", fail_reason);
846     else
847         logf("Failed");
848     terminate(exit_code);
849 }
850
851 /*
852  * Translate the input character to the appropriate string for printing
853  * the data.
854  */
855
856 char *
857 character(int c)
858 {
859     static char string[10];
860     const char *meta;
861
862     meta = (c & 0x80) ? "M-" : "";
863     c &= 0x7F;
864
865     if (c < 32)
866         sprintf(string, "%s^%c", meta, (int)c + '@');
867     else if (c == 127)
868         sprintf(string, "%s^?", meta);
869     else
870         sprintf(string, "%s%c", meta, c);
871
872     return (string);
873 }
874
875 /*
876  *  process the reply string
877  */
878 void
879 chat_send(char *s)
880 {
881     if (say_next) {
882         say_next = 0;
883         s = clean(s,0);
884         write(STDERR_FILENO, s, strlen(s));
885         free(s);
886         return;
887     }
888
889     if (hup_next) {
890         hup_next = 0;
891         if (strcmp(s, "OFF") == 0)
892            signal(SIGHUP, SIG_IGN);
893         else
894            signal(SIGHUP, sighup);
895         return;
896     }
897
898     if (echo_next) {
899         echo_next = 0;
900         echo = (strcmp(s, "ON") == 0);
901         return;
902     }
903
904     if (abort_next) {
905         char *s1;
906         
907         abort_next = 0;
908         
909         if (n_aborts >= MAX_ABORTS)
910             fatal(2, "Too many ABORT strings");
911         
912         s1 = clean(s, 0);
913         
914         if (strlen(s1) > strlen(s)
915             || strlen(s1) + 1 > sizeof(fail_buffer))
916             fatal(1, "Illegal or too-long ABORT string ('%v')", s);
917
918         abort_string[n_aborts++] = s1;
919
920         if (verbose)
921             logf("abort on (%v)", s);
922         return;
923     }
924
925     if (clear_abort_next) {
926         char *s1;
927         int   i;
928         int   old_max;
929         int   pack = 0;
930         
931         clear_abort_next = 0;
932         
933         s1 = clean(s, 0);
934         
935         if (strlen(s1) > strlen(s)
936             || strlen(s1) + 1 > sizeof(fail_buffer))
937             fatal(1, "Illegal or too-long CLR_ABORT string ('%v')", s);
938
939         old_max = n_aborts;
940         for (i=0; i < n_aborts; i++) {
941             if ( strcmp(s1,abort_string[i]) == 0 ) {
942                 free(abort_string[i]);
943                 abort_string[i] = NULL;
944                 pack++;
945                 n_aborts--;
946                 if (verbose)
947                     logf("clear abort on (%v)", s);
948             }
949         }
950         free(s1);
951         if (pack)
952             pack_array(abort_string,old_max);
953         return;
954     }
955
956     if (report_next) {
957         char *s1;
958         
959         report_next = 0;
960         if (n_reports >= MAX_REPORTS)
961             fatal(2, "Too many REPORT strings");
962         
963         s1 = clean(s, 0);
964         
965         if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
966             fatal(1, "Illegal or too-long REPORT string ('%v')", s);
967         
968         report_string[n_reports++] = s1;
969         
970         if (verbose)
971             logf("report (%v)", s);
972         return;
973     }
974
975     if (clear_report_next) {
976         char *s1;
977         int   i;
978         int   old_max;
979         int   pack = 0;
980         
981         clear_report_next = 0;
982         
983         s1 = clean(s, 0);
984         
985         if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
986             fatal(1, "Illegal or too-long REPORT string ('%v')", s);
987
988         old_max = n_reports;
989         for (i=0; i < n_reports; i++) {
990             if ( strcmp(s1,report_string[i]) == 0 ) {
991                 free(report_string[i]);
992                 report_string[i] = NULL;
993                 pack++;
994                 n_reports--;
995                 if (verbose)
996                     logf("clear report (%v)", s);
997             }
998         }
999         free(s1);
1000         if (pack)
1001             pack_array(report_string,old_max);
1002         
1003         return;
1004     }
1005
1006     if (timeout_next) {
1007         timeout_next = 0;
1008         timeout = atoi(s);
1009         
1010         if (timeout <= 0)
1011             timeout = DEFAULT_CHAT_TIMEOUT;
1012
1013         if (verbose)
1014             logf("timeout set to %d seconds", timeout);
1015
1016         return;
1017     }
1018
1019     if (strcmp(s, "EOT") == 0)
1020         s = strdup("^D\\c");
1021     else if (strcmp(s, "BREAK") == 0)
1022         s = strdup("\\K\\c");
1023
1024     if (!put_string(s))
1025         fatal(1, "Failed");
1026 }
1027
1028 int
1029 get_char(void)
1030 {
1031     int status;
1032     char c;
1033
1034     status = read(STDIN_FILENO, &c, 1);
1035
1036     switch (status) {
1037     case 1:
1038         return ((int)c & 0x7F);
1039
1040     default:
1041         logf("warning: read() on stdin returned %d", status);
1042
1043     case -1:
1044         if ((status = fcntl(0, F_GETFL, 0)) == -1)
1045             fatal(2, "Can't get file mode flags on stdin: %m");
1046
1047         if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1)
1048             fatal(2, "Can't set file mode flags on stdin: %m");
1049         
1050         return (-1);
1051     }
1052 }
1053
1054 int put_char(int c)
1055 {
1056     int status;
1057     char ch = c;
1058
1059     usleep(10000);              /* inter-character typing delay (?) */
1060
1061     status = write(STDOUT_FILENO, &ch, 1);
1062
1063     switch (status) {
1064     case 1:
1065         return (0);
1066         
1067     default:
1068         logf("warning: write() on stdout returned %d", status);
1069         
1070     case -1:
1071         if ((status = fcntl(0, F_GETFL, 0)) == -1)
1072             fatal(2, "Can't get file mode flags on stdin, %m");
1073
1074         if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1)
1075             fatal(2, "Can't set file mode flags on stdin: %m");
1076         
1077         return (-1);
1078     }
1079 }
1080
1081 int
1082 write_char(int c)
1083 {
1084     if (alarmed || put_char(c) < 0) {
1085         alarm(0);
1086         alarmed = 0;
1087
1088         if (verbose) {
1089             if (errno == EINTR || errno == EWOULDBLOCK)
1090                 logf(" -- write timed out");
1091             else
1092                 logf(" -- write failed: %m");
1093         }
1094         return (0);
1095     }
1096     return (1);
1097 }
1098
1099 int
1100 put_string(char *s)
1101 {
1102     quiet = 0;
1103     s = clean(s, 1);
1104
1105     if (verbose)
1106         logf("send (%v)", quiet ? "??????" : s);
1107
1108     alarm(timeout); alarmed = 0;
1109
1110     while (*s) {
1111         char c = *s++;
1112
1113         if (c != '\\') {
1114             if (!write_char (c))
1115                 return 0;
1116             continue;
1117         }
1118
1119         c = *s++;
1120         switch (c) {
1121         case 'd':
1122             sleep(1);
1123             break;
1124
1125         case 'K':
1126             break_sequence();
1127             break;
1128
1129         case 'p':
1130             usleep(10000);      /* 1/100th of a second (arg is microseconds) */
1131             break;
1132
1133         default:
1134             if (!write_char (c))
1135                 return 0;
1136             break;
1137         }
1138     }
1139
1140     alarm(0);
1141     alarmed = 0;
1142     return (1);
1143 }
1144
1145 /*
1146  *      Echo a character to stderr.
1147  *      When called with -1, a '\n' character is generated when
1148  *      the cursor is not at the beginning of a line.
1149  */
1150 void
1151 echo_stderr(int n)
1152 {
1153     static int need_lf;
1154     char *s;
1155
1156     switch (n) {
1157     case '\r':          /* ignore '\r' */
1158         break;
1159     case -1:
1160         if (need_lf == 0)
1161             break;
1162         /* FALLTHROUGH */
1163     case '\n':
1164         write(STDERR_FILENO, "\n", 1);
1165         need_lf = 0;
1166         break;
1167     default:
1168         s = character(n);
1169         write(STDERR_FILENO, s, strlen(s));
1170         need_lf = 1;
1171         break;
1172     }
1173 }
1174
1175 /*
1176  *      'Wait for' this string to appear on this file descriptor.
1177  */
1178 int
1179 get_string(char *string)
1180 {
1181     char temp[STR_LEN];
1182     int c, printed = 0;
1183     size_t len, minlen;
1184     char *s = temp, *end = s + STR_LEN;
1185     char *logged = temp;
1186
1187     fail_reason = (char *)0;
1188
1189     if (strlen(string) > STR_LEN) {
1190         logf("expect string is too long");
1191         exit_code = 1;
1192         return 0;
1193     }
1194
1195     string = clean(string, 0);
1196     len = strlen(string);
1197     minlen = (len > sizeof(fail_buffer)? len: sizeof(fail_buffer)) - 1;
1198
1199     if (verbose)
1200         logf("expect (%v)", string);
1201
1202     if (len == 0) {
1203         if (verbose)
1204             logf("got it");
1205         return (1);
1206     }
1207
1208     alarm(timeout);
1209     alarmed = 0;
1210
1211     while ( ! alarmed && (c = get_char()) >= 0) {
1212         int n, abort_len, report_len;
1213
1214         if (echo)
1215             echo_stderr(c);
1216         if (verbose && c == '\n') {
1217             if (s == logged)
1218                 logf("");       /* blank line */
1219             else
1220                 logf("%0.*v", s - logged, logged);
1221             logged = s + 1;
1222         }
1223
1224         *s++ = c;
1225
1226         if (verbose && s >= logged + 80) {
1227             logf("%0.*v", s - logged, logged);
1228             logged = s;
1229         }
1230
1231         if (Verbose) {
1232            if (c == '\n')
1233                fputc( '\n', stderr );
1234            else if (c != '\r')
1235                fprintf( stderr, "%s", character(c) );
1236         }
1237
1238         if (!report_gathering) {
1239             for (n = 0; n < n_reports; ++n) {
1240                 if ((report_string[n] != (char*) NULL) &&
1241                     s - temp >= (report_len = strlen(report_string[n])) &&
1242                     strncmp(s - report_len, report_string[n], report_len) == 0) {
1243                     time_t time_now   = time ((time_t*) NULL);
1244                     struct tm* tm_now = localtime (&time_now);
1245
1246                     strftime (report_buffer, 20, "%b %d %H:%M:%S ", tm_now);
1247                     strcat (report_buffer, report_string[n]);
1248
1249                     report_string[n] = (char *) NULL;
1250                     report_gathering = 1;
1251                     break;
1252                 }
1253             }
1254         }
1255         else {
1256             if (!iscntrl (c)) {
1257                 int rep_len = strlen (report_buffer);
1258                 report_buffer[rep_len]     = c;
1259                 report_buffer[rep_len + 1] = '\0';
1260             }
1261             else {
1262                 report_gathering = 0;
1263                 fprintf (report_fp, "chat:  %s\n", report_buffer);
1264             }
1265         }
1266
1267         if ((size_t)(s - temp) >= len &&
1268             c == string[len - 1] &&
1269             strncmp(s - len, string, len) == 0) {
1270             if (verbose) {
1271                 if (s > logged)
1272                     logf("%0.*v", s - logged, logged);
1273                 logf(" -- got it\n");
1274             }
1275
1276             alarm(0);
1277             alarmed = 0;
1278             return (1);
1279         }
1280
1281         for (n = 0; n < n_aborts; ++n) {
1282             if (s - temp >= (abort_len = strlen(abort_string[n])) &&
1283                 strncmp(s - abort_len, abort_string[n], abort_len) == 0) {
1284                 if (verbose) {
1285                     if (s > logged)
1286                         logf("%0.*v", s - logged, logged);
1287                     logf(" -- failed");
1288                 }
1289
1290                 alarm(0);
1291                 alarmed = 0;
1292                 exit_code = n + 4;
1293                 strcpy(fail_reason = fail_buffer, abort_string[n]);
1294                 return (0);
1295             }
1296         }
1297
1298         if (s >= end) {
1299             if (logged < s - minlen) {
1300                 logf("%0.*v", s - logged, logged);
1301                 logged = s;
1302             }
1303             s -= minlen;
1304             memmove(temp, s, minlen);
1305             logged = temp + (logged - s);
1306             s = temp + minlen;
1307         }
1308
1309         if (alarmed && verbose)
1310             logf("warning: alarm synchronization problem");
1311     }
1312
1313     alarm(0);
1314     
1315     if (verbose && printed) {
1316         if (alarmed)
1317             logf(" -- read timed out");
1318         else
1319             logf(" -- read failed: %m");
1320     }
1321
1322     exit_code = 3;
1323     alarmed   = 0;
1324     return (0);
1325 }
1326
1327 void
1328 pack_array(char **array, int end)
1329 {
1330     int i, j;
1331
1332     for (i = 0; i < end; i++) {
1333         if (array[i] == NULL) {
1334             for (j = i+1; j < end; ++j)
1335                 if (array[j] != NULL)
1336                     array[i++] = array[j];
1337             for (; i < end; ++i)
1338                 array[i] = NULL;
1339             break;
1340         }
1341     }
1342 }
1343
1344 /*
1345  * vfmtmsg - format a message into a buffer.  Like vsprintf except we
1346  * also specify the length of the output buffer, and we handle the
1347  * %m (error message) format.
1348  * Doesn't do floating-point formats.
1349  * Returns the number of chars put into buf.
1350  */
1351 #define OUTCHAR(c)      (buflen > 0? (--buflen, *buf++ = (c)): 0)
1352
1353 int
1354 vfmtmsg(char *buf, int buflen, const char *fmt, va_list args)
1355 {
1356     int c, i, n;
1357     int width, prec, fillch;
1358     int base, len, neg, quoted;
1359     unsigned long val = 0;
1360     char *str, *buf0;
1361     const char *f;
1362     unsigned char *p;
1363     char num[32];
1364     static char hexchars[] = "0123456789abcdef";
1365
1366     buf0 = buf;
1367     --buflen;
1368     while (buflen > 0) {
1369         for (f = fmt; *f != '%' && *f != 0; ++f)
1370             ;
1371         if (f > fmt) {
1372             len = f - fmt;
1373             if (len > buflen)
1374                 len = buflen;
1375             memcpy(buf, fmt, len);
1376             buf += len;
1377             buflen -= len;
1378             fmt = f;
1379         }
1380         if (*fmt == 0)
1381             break;
1382         c = *++fmt;
1383         width = prec = 0;
1384         fillch = ' ';
1385         if (c == '0') {
1386             fillch = '0';
1387             c = *++fmt;
1388         }
1389         if (c == '*') {
1390             width = va_arg(args, int);
1391             c = *++fmt;
1392         } else {
1393             while (isdigit(c)) {
1394                 width = width * 10 + c - '0';
1395                 c = *++fmt;
1396             }
1397         }
1398         if (c == '.') {
1399             c = *++fmt;
1400             if (c == '*') {
1401                 prec = va_arg(args, int);
1402                 c = *++fmt;
1403             } else {
1404                 while (isdigit(c)) {
1405                     prec = prec * 10 + c - '0';
1406                     c = *++fmt;
1407                 }
1408             }
1409         }
1410         str = 0;
1411         base = 0;
1412         neg = 0;
1413         ++fmt;
1414         switch (c) {
1415         case 'd':
1416             i = va_arg(args, int);
1417             if (i < 0) {
1418                 neg = 1;
1419                 val = -i;
1420             } else
1421                 val = i;
1422             base = 10;
1423             break;
1424         case 'o':
1425             val = va_arg(args, unsigned int);
1426             base = 8;
1427             break;
1428         case 'x':
1429             val = va_arg(args, unsigned int);
1430             base = 16;
1431             break;
1432         case 'p':
1433             val = (unsigned long) va_arg(args, void *);
1434             base = 16;
1435             neg = 2;
1436             break;
1437         case 's':
1438             str = va_arg(args, char *);
1439             break;
1440         case 'c':
1441             num[0] = va_arg(args, int);
1442             num[1] = 0;
1443             str = num;
1444             break;
1445         case 'm':
1446             str = strerror(errno);
1447             break;
1448         case 'v':               /* "visible" string */
1449         case 'q':               /* quoted string */
1450             quoted = c == 'q';
1451             p = va_arg(args, unsigned char *);
1452             if (fillch == '0' && prec > 0) {
1453                 n = prec;
1454             } else {
1455                 n = strlen((char *)p);
1456                 if (prec > 0 && prec < n)
1457                     n = prec;
1458             }
1459             while (n > 0 && buflen > 0) {
1460                 c = *p++;
1461                 --n;
1462                 if (!quoted && c >= 0x80) {
1463                     OUTCHAR('M');
1464                     OUTCHAR('-');
1465                     c -= 0x80;
1466                 }
1467                 if (quoted && (c == '"' || c == '\\'))
1468                     OUTCHAR('\\');
1469                 if (c < 0x20 || (0x7f <= c && c < 0xa0)) {
1470                     if (quoted) {
1471                         OUTCHAR('\\');
1472                         switch (c) {
1473                         case '\t':      OUTCHAR('t');   break;
1474                         case '\n':      OUTCHAR('n');   break;
1475                         case '\b':      OUTCHAR('b');   break;
1476                         case '\f':      OUTCHAR('f');   break;
1477                         default:
1478                             OUTCHAR('x');
1479                             OUTCHAR(hexchars[c >> 4]);
1480                             OUTCHAR(hexchars[c & 0xf]);
1481                         }
1482                     } else {
1483                         if (c == '\t')
1484                             OUTCHAR(c);
1485                         else {
1486                             OUTCHAR('^');
1487                             OUTCHAR(c ^ 0x40);
1488                         }
1489                     }
1490                 } else
1491                     OUTCHAR(c);
1492             }
1493             continue;
1494         default:
1495             *buf++ = '%';
1496             if (c != '%')
1497                 --fmt;          /* so %z outputs %z etc. */
1498             --buflen;
1499             continue;
1500         }
1501         if (base != 0) {
1502             str = num + sizeof(num);
1503             *--str = 0;
1504             while (str > num + neg) {
1505                 *--str = hexchars[val % base];
1506                 val = val / base;
1507                 if (--prec <= 0 && val == 0)
1508                     break;
1509             }
1510             switch (neg) {
1511             case 1:
1512                 *--str = '-';
1513                 break;
1514             case 2:
1515                 *--str = 'x';
1516                 *--str = '0';
1517                 break;
1518             }
1519             len = num + sizeof(num) - 1 - str;
1520         } else {
1521             len = strlen(str);
1522             if (prec > 0 && len > prec)
1523                 len = prec;
1524         }
1525         if (width > 0) {
1526             if (width > buflen)
1527                 width = buflen;
1528             if ((n = width - len) > 0) {
1529                 buflen -= n;
1530                 for (; n > 0; --n)
1531                     *buf++ = fillch;
1532             }
1533         }
1534         if (len > buflen)
1535             len = buflen;
1536         memcpy(buf, str, len);
1537         buf += len;
1538         buflen -= len;
1539     }
1540     *buf = 0;
1541     return buf - buf0;
1542 }