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