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