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