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