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