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