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