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