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