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