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