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