]> git.ozlabs.org Git - ppp.git/blobdiff - chat/chat.c
mods to get it to compile on netbsd
[ppp.git] / chat / chat.c
index 62e18f0154ab70ebe49360f987c944e5aa484d70..006841359ee0b9a1848c9a296ee36c0514485ae2 100644 (file)
@@ -2,12 +2,69 @@
  *     Chat -- a program for automatic session establishment (i.e. dial
  *             the phone and log in).
  *
+ * Standard termination codes:
+ *  0 - successful completion of the script
+ *  1 - invalid argument, expect string too large, etc.
+ *  2 - error on an I/O operation or fatal error condition.
+ *  3 - timeout waiting for a simple string.
+ *  4 - the first string declared as "ABORT"
+ *  5 - the second string declared as "ABORT"
+ *  6 - ... and so on for successive ABORT strings.
+ *
  *     This software is in the public domain.
  *
- *     Please send all bug reports, requests for information, etc. to:
+ * -----------------
+ *     added -T and -U option and \T and \U substitution to pass a phone
+ *     number into chat script. Two are needed for some ISDN TA applications.
+ *     Keith Dart <kdart@cisco.com>
+ *     
+ *
+ *     Added SAY keyword to send output to stderr.
+ *      This allows to turn ECHO OFF and to output specific, user selected,
+ *      text to give progress messages. This best works when stderr
+ *      exists (i.e.: pppd in nodetach mode).
+ *
+ *     Added HANGUP directives to allow for us to be called
+ *      back. When HANGUP is set to NO, chat will not hangup at HUP signal.
+ *      We rely on timeouts in that case.
+ *
+ *      Added CLR_ABORT to clear previously set ABORT string. This has been
+ *      dictated by the HANGUP above as "NO CARRIER" (for example) must be
+ *      an ABORT condition until we know the other host is going to close
+ *      the connection for call back. As soon as we have completed the
+ *      first stage of the call back sequence, "NO CARRIER" is a valid, non
+ *      fatal string. As soon as we got called back (probably get "CONNECT"),
+ *      we should re-arm the ABORT "NO CARRIER". Hence the CLR_ABORT command.
+ *      Note that CLR_ABORT packs the abort_strings[] array so that we do not
+ *      have unused entries not being reclaimed.
+ *
+ *      In the same vein as above, added CLR_REPORT keyword.
+ *
+ *      Allow for comments. Line starting with '#' are comments and are
+ *      ignored. If a '#' is to be expected as the first character, the 
+ *      expect string must be quoted.
+ *
+ *
+ *             Francis Demierre <Francis@SwissMail.Com>
+ *             Thu May 15 17:15:40 MET DST 1997
+ *
+ *
+ *      Added -r "report file" switch & REPORT keyword.
+ *              Robert Geer <bgeer@xmission.com>
+ *
+ *      Added -s "use stderr" and -S "don't use syslog" switches.
+ *              June 18, 1997
+ *              Karl O. Pinc <kop@meme.com>
+ *
+ *
+ *     Added -e "echo" switch & ECHO keyword
+ *             Dick Streefland <dicks@tasking.nl>
+ *
+ *
+ *     Considerable updates and modifications by
+ *             Al Longyear <longyear@pobox.com>
+ *             Paul Mackerras <paulus@cs.anu.edu.au>
  *
- *             Al Longyear (longyear@netcom.com)
- *             (I was the last person to change this code.)
  *
  *     The original author is:
  *
  *             1760 Zollinger Road
  *             Columbus, OH  43221
  *             (614)451-1883
+ *
+ *
  */
 
-static char rcsid[] = "$Id: chat.c,v 1.6 1995/04/28 06:21:50 paulus Exp $";
+#ifndef lint
+static char rcsid[] = "$Id: chat.c,v 1.16 1997/11/27 06:00:06 paulus Exp $";
+#endif
 
 #include <stdio.h>
+#include <ctype.h>
+#include <time.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <errno.h>
 #include <string.h>
 #include <stdlib.h>
+#include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <syslog.h>
@@ -42,23 +106,42 @@ static char rcsid[] = "$Id: chat.c,v 1.6 1995/04/28 06:21:50 paulus Exp $";
 #include <termios.h>
 #endif
 
+#if __STDC__
+#include <stdarg.h>
+#define __V(x) x
+#else
+#include <varargs.h>
+#define __V(x) (va_alist) va_dcl
+#define const
+#endif
+
 #define        STR_LEN 1024
 
 #ifndef SIGTYPE
 #define SIGTYPE void
 #endif
 
-#ifdef __STDC__
 #undef __P
+#undef __V
+
+#ifdef __STDC__
+#include <stdarg.h>
+#define __V(x) x
 #define __P(x) x
 #else
+#include <varargs.h>
+#define __V(x) (va_alist) va_dcl
 #define __P(x) ()
 #define const
 #endif
 
+#ifndef O_NONBLOCK
+#define O_NONBLOCK     O_NDELAY
+#endif
+
 /*************** Micro getopt() *********************************************/
 #define        OPTION(c,v)     (_O&2&&**v?*(*v)++:!c||_O&4?0:(!(_O&1)&& \
-                               (--c,++v),_O=4,c&&**v=='-'&&v[0][1]?*++*v=='-'\
+                               ((--c,++v),_O=4,c)&&**v=='-'&&v[0][1]?*++*v=='-'\
                                &&!v[0][1]?(--c,++v,0):(_O=2,*(*v)++):0))
 #define        OPTARG(c,v)     (_O&2?**v||(++v,--c)?(_O=1,--c,*v++): \
                                (_O=4,(char*)0):(char*)0)
@@ -71,33 +154,57 @@ static int _O = 0;         /* Internal state */
 char *program_name;
 
 #define        MAX_ABORTS              50
+#define        MAX_REPORTS             50
 #define        DEFAULT_CHAT_TIMEOUT    45
 
-int verbose = 0;
-int quiet = 0;
-char *lock_file = (char *)0;
-char *chat_file = (char *)0;
-int timeout = DEFAULT_CHAT_TIMEOUT;
+int echo          = 0;
+int verbose       = 0;
+int to_log        = 1;
+int to_stderr     = 0;
+int Verbose       = 0;
+int quiet         = 0;
+int report        = 0;
+int exit_code     = 0;
+FILE* report_fp   = (FILE *) 0;
+char *report_file = (char *) 0;
+char *chat_file   = (char *) 0;
+char *phone_num   = (char *) 0;
+char *phone_num2  = (char *) 0;
+int timeout       = DEFAULT_CHAT_TIMEOUT;
 
 int have_tty_parameters = 0;
+
 #ifdef TERMIO
+#define term_parms struct termio
+#define get_term_param(param) ioctl(0, TCGETA, param)
+#define set_term_param(param) ioctl(0, TCSETA, param)
 struct termio saved_tty_parameters;
 #endif
+
 #ifdef TERMIOS
+#define term_parms struct termios
+#define get_term_param(param) tcgetattr(0, param)
+#define set_term_param(param) tcsetattr(0, TCSANOW, param)
 struct termios saved_tty_parameters;
 #endif
 
 char *abort_string[MAX_ABORTS], *fail_reason = (char *)0,
        fail_buffer[50];
-int n_aborts = 0, abort_next = 0, timeout_next = 0;
+int n_aborts = 0, abort_next = 0, timeout_next = 0, echo_next = 0;
+int clear_abort_next = 0;
+
+char *report_string[MAX_REPORTS] ;
+char  report_buffer[50] ;
+int n_reports = 0, report_next = 0, report_gathering = 0 ; 
+int clear_report_next = 0;
+
+int say_next = 0, hup_next = 0;
 
 void *dup_mem __P((void *b, size_t c));
 void *copy_of __P((char *s));
 void usage __P((void));
-void logf __P((const char *str));
-void logflush __P((void));
-void fatal __P((const char *msg));
-void sysfatal __P((const char *msg));
+void logf __P((const char *fmt, ...));
+void fatal __P((int code, const char *fmt, ...));
 SIGTYPE sigalrm __P((int signo));
 SIGTYPE sigint __P((int signo));
 SIGTYPE sigterm __P((int signo));
@@ -105,10 +212,10 @@ SIGTYPE sighup __P((int signo));
 void unalarm __P((void));
 void init __P((void));
 void set_tty_parameters __P((void));
+void echo_stderr __P((int));
 void break_sequence __P((void));
 void terminate __P((int status));
 void do_file __P((char *chat_file));
-void delay __P((void));
 int  get_string __P((register char *string));
 int  put_string __P((register char *s));
 int  write_char __P((int c));
@@ -120,111 +227,158 @@ void chat_expect __P((register char *s));
 char *clean __P((register char *s, int sending));
 void break_sequence __P((void));
 void terminate __P((int status));
-void die __P((void));
+void pack_array __P((char **array, int end));
+char *expect_strtok __P((char *, char *));
+int vfmtmsg __P((char *, int, const char *, va_list)); /* vsprintf++ */
+
+int main __P((int, char *[]));
 
 void *dup_mem(b, c)
 void *b;
 size_t c;
-    {
+{
     void *ans = malloc (c);
     if (!ans)
-       fatal ("memory error!\n");
+       fatal(2, "memory error!");
+
     memcpy (ans, b, c);
     return ans;
-    }
+}
 
 void *copy_of (s)
 char *s;
-    {
+{
     return dup_mem (s, strlen (s) + 1);
-    }
+}
 
 /*
- *     chat [ -v ] [ -t timeout ] [ -f chat-file ] \
+ * chat [ -v ] [-T number] [-U number] [ -t timeout ] [ -f chat-file ] \
+ * [ -r report-file ] \
  *             [...[[expect[-say[-expect...]] say expect[-say[-expect]] ...]]]
  *
  *     Perform a UUCP-dialer-like chat script on stdin and stdout.
  */
 int
 main(argc, argv)
-int argc;
-char **argv;
-    {
+     int argc;
+     char **argv;
+{
     int option;
     char *arg;
 
     program_name = *argv;
+    tzset();
 
-    while (option = OPTION(argc, argv))
-       switch (option)
-           {
-           case 'v':
-               ++verbose;
-               break;
-
-           case 'f':
-               if (arg = OPTARG(argc, argv))
-                   chat_file = copy_of(arg);
-               else
-                   usage();
+    while ((option = OPTION(argc, argv)) != 0) {
+       switch (option) {
+       case 'e':
+           ++echo;
+           break;
 
-               break;
+       case 'v':
+           ++verbose;
+           break;
 
-           case 'l':
-               if (arg = OPTARG(argc, argv))
-                   lock_file = copy_of(arg);
-               else
-                   usage();
+       case 'V':
+           ++Verbose;
+           break;
 
-               break;
+       case 's':
+           ++to_stderr;
+           break;
 
-           case 't':
-               if (arg = OPTARG(argc, argv))
-                   timeout = atoi(arg);
-               else
-                   usage();
+       case 'S':
+           to_log = 0;
+           break;
 
-               break;
+       case 'f':
+           if ((arg = OPTARG(argc, argv)) != NULL)
+                   chat_file = copy_of(arg);
+           else
+               usage();
+           break;
 
-           default:
+       case 't':
+           if ((arg = OPTARG(argc, argv)) != NULL)
+               timeout = atoi(arg);
+           else
                usage();
+           break;
+
+       case 'r':
+           arg = OPTARG (argc, argv);
+           if (arg) {
+               if (report_fp != NULL)
+                   fclose (report_fp);
+               report_file = copy_of (arg);
+               report_fp   = fopen (report_file, "a");
+               if (report_fp != NULL) {
+                   if (verbose)
+                       fprintf (report_fp, "Opening \"%s\"...\n",
+                                report_file);
+                   report = 1;
+               }
            }
+           break;
+
+       case 'T':
+           if ((arg = OPTARG(argc, argv)) != NULL)
+               phone_num = copy_of(arg);
+           else
+               usage();
+           break;
+
+       case 'U':
+           if ((arg = OPTARG(argc, argv)) != NULL)
+               phone_num2 = copy_of(arg);
+           else
+               usage();
+           break;
+
+       default:
+           usage();
+           break;
+       }
+    }
+/*
+ * Default the report file to the stderr location
+ */
+    if (report_fp == NULL)
+       report_fp = stderr;
 
+    if (to_log) {
 #ifdef ultrix
-    openlog("chat", LOG_PID);
+       openlog("chat", LOG_PID);
 #else
-    openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
+       openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
 
-    if (verbose) {
-       setlogmask(LOG_UPTO(LOG_INFO));
-    } else {
-       setlogmask(LOG_UPTO(LOG_WARNING));
-    }
+       if (verbose)
+           setlogmask(LOG_UPTO(LOG_INFO));
+       else
+           setlogmask(LOG_UPTO(LOG_WARNING));
 #endif
+    }
 
     init();
     
-    if (chat_file != NULL)
-       {
+    if (chat_file != NULL) {
        arg = ARG(argc, argv);
        if (arg != NULL)
            usage();
        else
            do_file (chat_file);
-       }
-    else
-       {
-       while (arg = ARG(argc, argv))
-           {
+    } else {
+       while ((arg = ARG(argc, argv)) != NULL) {
            chat_expect(arg);
 
-           if (arg = ARG(argc, argv))
+           if ((arg = ARG(argc, argv)) != NULL)
                chat_send(arg);
-           }
        }
+    }
 
     terminate(0);
-    }
+    return 0;
+}
 
 /*
  *  Process a chat script when read from a file.
@@ -232,201 +386,183 @@ char **argv;
 
 void do_file (chat_file)
 char *chat_file;
-    {
-    int linect, len, sendflg;
+{
+    int linect, sendflg;
     char *sp, *arg, quote;
     char buf [STR_LEN];
     FILE *cfp;
 
-    if ((cfp = fopen (chat_file, "r")) == NULL)
-       {
-       syslog (LOG_ERR, "%s -- open failed: %m", chat_file);
-       terminate (1);
-       }
+    cfp = fopen (chat_file, "r");
+    if (cfp == NULL)
+       fatal(1, "%s -- open failed: %m", chat_file);
 
     linect = 0;
     sendflg = 0;
 
-    while (fgets(buf, STR_LEN, cfp) != NULL)
-       {
+    while (fgets(buf, STR_LEN, cfp) != NULL) {
        sp = strchr (buf, '\n');
        if (sp)
            *sp = '\0';
 
        linect++;
        sp = buf;
-       while (*sp != '\0')
-           {
-           if (*sp == ' ' || *sp == '\t')
-               {
+
+        /* lines starting with '#' are comments. If a real '#'
+           is to be expected, it should be quoted .... */
+        if ( *sp == '#' )
+           continue;
+
+       while (*sp != '\0') {
+           if (*sp == ' ' || *sp == '\t') {
                ++sp;
                continue;
-               }
+           }
 
-           if (*sp == '"' || *sp == '\'')
-               {
+           if (*sp == '"' || *sp == '\'') {
                quote = *sp++;
                arg = sp;
-               while (*sp != quote)
-                   {
+               while (*sp != quote) {
                    if (*sp == '\0')
-                       {
-                       syslog (LOG_ERR, "unterminated quote (line %d)",
-                               linect);
-                       terminate (1);
-                       }
-                   
-                   if (*sp++ == '\\')
+                       fatal(1, "unterminated quote (line %d)", linect);
+
+                   if (*sp++ == '\\') {
                        if (*sp != '\0')
                            ++sp;
                    }
                }
-           else
-               {
+           }
+           else {
                arg = sp;
                while (*sp != '\0' && *sp != ' ' && *sp != '\t')
                    ++sp;
-               }
+           }
 
            if (*sp != '\0')
                *sp++ = '\0';
 
            if (sendflg)
-               {
                chat_send (arg);
-               }
            else
-               {
                chat_expect (arg);
-               }
            sendflg = !sendflg;
-           }
        }
-    fclose (cfp);
     }
+    fclose (cfp);
+}
 
 /*
  *     We got an error parsing the command line.
  */
 void usage()
-    {
+{
     fprintf(stderr, "\
-Usage: %s [-v] [-t timeout] {-f chat-file || chat-script}\n",
-           program_name);
+Usage: %s [-e] [-v] [-t timeout] [-r report-file] [-T phone-number]\n\
+     [-U phone-number2] {-f chat-file | chat-script}\n", program_name);
     exit(1);
-    }
+}
 
-char line[256];
-char *p;
+char line[1024];
 
-void logf (str)
-const char *str;
-    {
-    p = line + strlen(line);
-    strcat (p, str);
+/*
+ * Send a message to syslog and/or stderr.
+ */
+void logf __V((const char *fmt, ...))
+{
+    va_list args;
 
-    if (str[strlen(str)-1] == '\n')
-       {
-       syslog (LOG_INFO, "%s", line);
-       line[0] = 0;
-       }
-    }
+#if __STDC__
+    va_start(args, fmt);
+#else
+    char *fmt;
+    va_start(args);
+    fmt = va_arg(args, char *);
+#endif
 
-void logflush()
-    {
-    if (line[0] != 0)
-       {
+    vfmtmsg(line, sizeof(line), fmt, args);
+    if (to_log)
        syslog(LOG_INFO, "%s", line);
-       line[0] = 0;
-        }
-    }
-
-/*
- *     Terminate with an error.
- */
-void die()
-    {
-    terminate(1);
-    }
+    if (to_stderr)
+       fprintf(stderr, "%s\n", line);
+}
 
 /*
  *     Print an error message and terminate.
  */
 
-void fatal (msg)
-const char *msg;
-    {
-    syslog(LOG_ERR, "%s", msg);
-    terminate(1);
-    }
+void fatal __V((int code, const char *fmt, ...))
+{
+    va_list args;
 
-/*
- *     Print an error message along with the system error message and
- *     terminate.
- */
+#if __STDC__
+    va_start(args, fmt);
+#else
+    int code;
+    char *fmt;
+    va_start(args);
+    code = va_arg(args, int);
+    fmt = va_arg(args, char *);
+#endif
 
-void sysfatal (msg)
-const char *msg;
-    {
-    syslog(LOG_ERR, "%s: %m", msg);
-    terminate(1);
-    }
+    vfmtmsg(line, sizeof(line), fmt, args);
+    if (to_log)
+       syslog(LOG_ERR, "%s", line);
+    if (to_stderr)
+       fprintf(stderr, "%s\n", line);
+    terminate(code);
+}
 
 int alarmed = 0;
 
 SIGTYPE sigalrm(signo)
 int signo;
-    {
+{
     int flags;
 
     alarm(1);
     alarmed = 1;               /* Reset alarm to avoid race window */
     signal(SIGALRM, sigalrm);  /* that can cause hanging in read() */
 
-    logflush();
     if ((flags = fcntl(0, F_GETFL, 0)) == -1)
-       sysfatal("Can't get file mode flags on stdin");
-    else
-       if (fcntl(0, F_SETFL, flags | FNDELAY) == -1)
-           sysfatal("Can't set file mode flags on stdin");
+       fatal(2, "Can't get file mode flags on stdin: %m");
+
+    if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1)
+       fatal(2, "Can't set file mode flags on stdin: %m");
 
     if (verbose)
-       {
-       syslog(LOG_INFO, "alarm");
-       }
-    }
+       logf("alarm");
+}
 
 void unalarm()
-    {
+{
     int flags;
 
     if ((flags = fcntl(0, F_GETFL, 0)) == -1)
-       sysfatal("Can't get file mode flags on stdin");
-    else
-       if (fcntl(0, F_SETFL, flags & ~FNDELAY) == -1)
-           sysfatal("Can't set file mode flags on stdin");
-    }
+       fatal(2, "Can't get file mode flags on stdin: %m");
+
+    if (fcntl(0, F_SETFL, flags & ~O_NONBLOCK) == -1)
+       fatal(2, "Can't set file mode flags on stdin: %m");
+}
 
 SIGTYPE sigint(signo)
 int signo;
-    {
-    fatal("SIGINT");
-    }
+{
+    fatal(2, "SIGINT");
+}
 
 SIGTYPE sigterm(signo)
 int signo;
-    {
-    fatal("SIGTERM");
-    }
+{
+    fatal(2, "SIGTERM");
+}
 
 SIGTYPE sighup(signo)
 int signo;
-    {
-    fatal("SIGHUP");
-    }
+{
+    fatal(2, "SIGHUP");
+}
 
 void init()
-    {
+{
     signal(SIGINT, sigint);
     signal(SIGTERM, sigterm);
     signal(SIGHUP, sighup);
@@ -435,116 +571,122 @@ void init()
     signal(SIGALRM, sigalrm);
     alarm(0);
     alarmed = 0;
-    }
+}
 
 void set_tty_parameters()
-    {
-#ifdef TERMIO
-    struct termio t;
-
-    if (ioctl(0, TCGETA, &t) < 0)
-       sysfatal("Can't get terminal parameters");
-#endif
-#ifdef TERMIOS
-    struct termios t;
+{
+#if defined(get_term_param)
+    term_parms t;
 
-    if (tcgetattr(0, &t) < 0)
-       sysfatal("Can't get terminal parameters");
-#endif
+    if (get_term_param (&t) < 0)
+       fatal(2, "Can't get terminal parameters: %m");
 
     saved_tty_parameters = t;
-    have_tty_parameters = 1;
-
-    t.c_iflag |= IGNBRK | ISTRIP | IGNPAR;
-    t.c_oflag  = 0;
-    t.c_lflag  = 0;
-    t.c_cc[VERASE] = t.c_cc[VKILL] = 0;
-    t.c_cc[VMIN] = 1;
-    t.c_cc[VTIME] = 0;
-
-#ifdef TERMIO
-    if (ioctl(0, TCSETA, &t) < 0)
-       sysfatal("Can't set terminal parameters");
-#endif
-#ifdef TERMIOS
-    if (tcsetattr(0, TCSANOW, &t) < 0)
-       sysfatal("Can't set terminal parameters");
+    have_tty_parameters  = 1;
+
+    t.c_iflag     |= IGNBRK | ISTRIP | IGNPAR;
+    t.c_oflag      = 0;
+    t.c_lflag      = 0;
+    t.c_cc[VERASE] =
+    t.c_cc[VKILL]  = 0;
+    t.c_cc[VMIN]   = 1;
+    t.c_cc[VTIME]  = 0;
+
+    if (set_term_param (&t) < 0)
+       fatal(2, "Can't set terminal parameters: %m");
 #endif
-    }
+}
 
 void break_sequence()
-    {
+{
 #ifdef TERMIOS
     tcsendbreak (0, 0);
 #endif
-    }
+}
 
 void terminate(status)
 int status;
-    {
-    if (have_tty_parameters &&
-#ifdef TERMIO
-        ioctl(0, TCSETA, &saved_tty_parameters) < 0
-#endif
-#ifdef TERMIOS
-       tcsetattr(0, TCSANOW, &saved_tty_parameters) < 0
+{
+    echo_stderr(-1);
+    if (report_file != (char *) 0 && report_fp != (FILE *) NULL) {
+/*
+ * Allow the last of the report string to be gathered before we terminate.
+ */
+       if (report_gathering) {
+           int c, rep_len;
+
+           rep_len = strlen(report_buffer);
+           while (rep_len + 1 <= sizeof(report_buffer)) {
+               alarm(1);
+               c = get_char();
+               alarm(0);
+               if (c < 0 || iscntrl(c))
+                   break;
+               report_buffer[rep_len] = c;
+               ++rep_len;
+           }
+           report_buffer[rep_len] = 0;
+           fprintf (report_fp, "chat:  %s\n", report_buffer);
+       }
+       if (verbose)
+           fprintf (report_fp, "Closing \"%s\".\n", report_file);
+       fclose (report_fp);
+       report_fp = (FILE *) NULL;
+    }
+
+#if defined(get_term_param)
+    if (have_tty_parameters) {
+       if (set_term_param (&saved_tty_parameters) < 0)
+           fatal(2, "Can't restore terminal parameters: %m");
+    }
 #endif
-       ) {
-       syslog(LOG_ERR, "Can't restore terminal parameters: %m");
-       exit(1);
-        }
+
     exit(status);
-    }
+}
 
 /*
  *     'Clean up' this string.
  */
 char *clean(s, sending)
 register char *s;
-int sending;
-    {
+int sending;  /* set to 1 when sending (putting) this string. */
+{
     char temp[STR_LEN], cur_chr;
-    register char *s1;
+    register char *s1, *phchar;
     int add_return = sending;
 #define isoctal(chr) (((chr) >= '0') && ((chr) <= '7'))
 
     s1 = temp;
-    while (*s)
-       {
+    while (*s) {
        cur_chr = *s++;
-       if (cur_chr == '^')
-           {
+       if (cur_chr == '^') {
            cur_chr = *s++;
-           if (cur_chr == '\0')
-               {
+           if (cur_chr == '\0') {
                *s1++ = '^';
                break;
-               }
+           }
            cur_chr &= 0x1F;
-           if (cur_chr != 0)
+           if (cur_chr != 0) {
                *s1++ = cur_chr;
-           continue;
            }
+           continue;
+       }
 
-       if (cur_chr != '\\')
-           {
+       if (cur_chr != '\\') {
            *s1++ = cur_chr;
            continue;
-           }
+       }
 
        cur_chr = *s++;
-       if (cur_chr == '\0')
-           {
-           if (sending)
-               {
+       if (cur_chr == '\0') {
+           if (sending) {
                *s1++ = '\\';
                *s1++ = '\\';
-               }
-           break;
            }
+           break;
+       }
 
-       switch (cur_chr)
-           {
+       switch (cur_chr) {
        case 'b':
            *s1++ = '\b';
            break;
@@ -566,8 +708,30 @@ int sending;
            *s1++ = cur_chr;
            break;
 
+       case 'T':
+           if (sending && phone_num) {
+               for ( phchar = phone_num; *phchar != '\0'; phchar++) 
+                   *s1++ = *phchar;
+           }
+           else {
+               *s1++ = '\\';
+               *s1++ = 'T';
+           }
+           break;
+
+       case 'U':
+           if (sending && phone_num2) {
+               for ( phchar = phone_num2; *phchar != '\0'; phchar++) 
+                   *s1++ = *phchar;
+           }
+           else {
+               *s1++ = '\\';
+               *s1++ = 'U';
+           }
+           break;
+
        case 'q':
-           quiet = ! quiet;
+           quiet = 1;
            break;
 
        case 'r':
@@ -587,45 +751,40 @@ int sending;
            break;
 
        case 'N':
-           if (sending)
-               {
+           if (sending) {
                *s1++ = '\\';
                *s1++ = '\0';
-               }
+           }
            else
                *s1++ = 'N';
            break;
            
        default:
-           if (isoctal (cur_chr))
-               {
+           if (isoctal (cur_chr)) {
                cur_chr &= 0x07;
-               if (isoctal (*s))
-                   {
+               if (isoctal (*s)) {
                    cur_chr <<= 3;
                    cur_chr |= *s++ - '0';
-                   if (isoctal (*s))
-                       {
+                   if (isoctal (*s)) {
                        cur_chr <<= 3;
                        cur_chr |= *s++ - '0';
-                       }
                    }
+               }
 
-               if (cur_chr != 0 || sending)
-                   {
+               if (cur_chr != 0 || sending) {
                    if (sending && (cur_chr == '\\' || cur_chr == 0))
                        *s1++ = '\\';
                    *s1++ = cur_chr;
-                   }
-               break;
                }
+               break;
+           }
 
            if (sending)
                *s1++ = '\\';
            *s1++ = cur_chr;
            break;
-           }
        }
+    }
 
     if (add_return)
        *s1++ = '\r';
@@ -633,82 +792,159 @@ int sending;
     *s1++ = '\0'; /* guarantee closure */
     *s1++ = '\0'; /* terminate the string */
     return dup_mem (temp, (size_t) (s1 - temp)); /* may have embedded nuls */
-    }
+}
 
 /*
- * Process the expect string
+ * A modified version of 'strtok'. This version skips \ sequences.
  */
-void chat_expect(s)
-register char *s;
-    {
-    if (strcmp(s, "ABORT") == 0)
-       {
-       ++abort_next;
-       return;
-       }
 
-    if (strcmp(s, "TIMEOUT") == 0)
-       {
-       ++timeout_next;
-       return;
-       }
+char *expect_strtok (s, term)
+     char *s, *term;
+{
+    static  char *str   = "";
+    int            escape_flag = 0;
+    char   *result;
 
-    while (*s)
-       {
-       register char *hyphen;
+/*
+ * If a string was specified then do initial processing.
+ */
+    if (s)
+       str = s;
 
-       for (hyphen = s; *hyphen; ++hyphen)
-           if (*hyphen == '-')
-               if (hyphen == s || hyphen[-1] != '\\')
-                   break;
-       
-       if (*hyphen == '-')
-           {
-           *hyphen = '\0';
+/*
+ * If this is the escape flag then reset it and ignore the character.
+ */
+    if (*str)
+       result = str;
+    else
+       result = (char *) 0;
 
-           if (get_string(s))
-               return;
-           else
-               {
-               s = hyphen + 1;
+    while (*str) {
+       if (escape_flag) {
+           escape_flag = 0;
+           ++str;
+           continue;
+       }
 
-               for (hyphen = s; *hyphen; ++hyphen)
-                   if (*hyphen == '-')
-                       if (hyphen == s || hyphen[-1] != '\\')
-                           break;
+       if (*str == '\\') {
+           ++str;
+           escape_flag = 1;
+           continue;
+       }
 
-               if (*hyphen == '-')
-                   {
-                   *hyphen = '\0';
+/*
+ * If this is not in the termination string, continue.
+ */
+       if (strchr (term, *str) == (char *) 0) {
+           ++str;
+           continue;
+       }
 
-                   chat_send(s);
-                   s = hyphen + 1;
-                   }
-               else
-                   {
-                   chat_send(s);
-                   return;
-                   }
-               }
-           }
-       else
-           if (get_string(s))
-               return;
-           else
-               {
-               if (fail_reason)
-                   syslog(LOG_INFO, "Failed (%s)", fail_reason);
-               else
-                   syslog(LOG_INFO, "Failed");
+/*
+ * This is the terminator. Mark the end of the string and stop.
+ */
+       *str++ = '\0';
+       break;
+    }
+    return (result);
+}
 
-               terminate(1);
-               }
-       }
+/*
+ * Process the expect string
+ */
+
+void chat_expect (s)
+char *s;
+{
+    char *expect;
+    char *reply;
+
+    if (strcmp(s, "HANGUP") == 0) {
+       ++hup_next;
+        return;
+    }
+    if (strcmp(s, "ABORT") == 0) {
+       ++abort_next;
+       return;
+    }
+
+    if (strcmp(s, "CLR_ABORT") == 0) {
+       ++clear_abort_next;
+       return;
+    }
+
+    if (strcmp(s, "REPORT") == 0) {
+       ++report_next;
+       return;
+    }
+
+    if (strcmp(s, "CLR_REPORT") == 0) {
+       ++clear_report_next;
+       return;
+    }
+
+    if (strcmp(s, "TIMEOUT") == 0) {
+       ++timeout_next;
+       return;
+    }
+
+    if (strcmp(s, "ECHO") == 0) {
+       ++echo_next;
+       return;
+    }
+
+    if (strcmp(s, "SAY") == 0) {
+       ++say_next;
+       return;
     }
 
+/*
+ * Fetch the expect and reply string.
+ */
+    for (;;) {
+       expect = expect_strtok (s, "-");
+       s      = (char *) 0;
+
+       if (expect == (char *) 0)
+           return;
+
+       reply = expect_strtok (s, "-");
+
+/*
+ * Handle the expect string. If successful then exit.
+ */
+       if (get_string (expect))
+           return;
+
+/*
+ * If there is a sub-reply string then send it. Otherwise any condition
+ * is terminal.
+ */
+       if (reply == (char *) 0 || exit_code != 3)
+           break;
+
+       chat_send (reply);
+    }
+
+/*
+ * The expectation did not occur. This is terminal.
+ */
+    if (fail_reason)
+       logf("Failed (%s)", fail_reason);
+    else
+       logf("Failed");
+    terminate(exit_code);
+}
+
+/*
+ * Translate the input character to the appropriate string for printing
+ * the data.
+ */
+
 char *character(c)
 int c;
-    {
+{
     static char string[10];
     char *meta;
 
@@ -717,194 +953,266 @@ int c;
 
     if (c < 32)
        sprintf(string, "%s^%c", meta, (int)c + '@');
+    else if (c == 127)
+       sprintf(string, "%s^?", meta);
     else
-       if (c == 127)
-           sprintf(string, "%s^?", meta);
-       else
-           sprintf(string, "%s%c", meta, c);
+       sprintf(string, "%s%c", meta, c);
 
     return (string);
-    }
+}
 
 /*
  *  process the reply string
  */
 void chat_send (s)
 register char *s;
-    {
-    if (abort_next)
-       {
-       char *s1;
+{
+    if (say_next) {
+       say_next = 0;
+       s = clean(s,0);
+       write(2, s, strlen(s));
+        free(s);
+       return;
+    }
 
-       abort_next = 0;
+    if (hup_next) {
+        hup_next = 0;
+       if (strcmp(s, "OFF") == 0)
+           signal(SIGHUP, SIG_IGN);
+        else
+           signal(SIGHUP, sighup);
+        return;
+    }
 
-       if (n_aborts >= MAX_ABORTS)
-           fatal("Too many ABORT strings");
+    if (echo_next) {
+       echo_next = 0;
+       echo = (strcmp(s, "ON") == 0);
+       return;
+    }
 
+    if (abort_next) {
+       char *s1;
+       
+       abort_next = 0;
+       
+       if (n_aborts >= MAX_ABORTS)
+           fatal(2, "Too many ABORT strings");
+       
        s1 = clean(s, 0);
-
-       if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
-           {
-           syslog(LOG_WARNING, "Illegal or too-long ABORT string ('%s')", s);
-           die();
-           }
+       
+       if (strlen(s1) > strlen(s)
+           || strlen(s1) + 1 > sizeof(fail_buffer))
+           fatal(1, "Illegal or too-long ABORT string ('%v')", s);
 
        abort_string[n_aborts++] = s1;
 
        if (verbose)
-           {
-           logf("abort on (");
-
-           for (s1 = s; *s1; ++s1)
-               logf(character(*s1));
+           logf("abort on (%v)", s);
+       return;
+    }
 
-           logf(")\n");
+    if (clear_abort_next) {
+       char *s1;
+       char *s2 = s;
+       int   i;
+        int   old_max;
+       int   pack = 0;
+       
+       clear_abort_next = 0;
+       
+       s1 = clean(s, 0);
+       
+       if (strlen(s1) > strlen(s)
+           || strlen(s1) + 1 > sizeof(fail_buffer))
+           fatal(1, "Illegal or too-long CLR_ABORT string ('%v')", s);
+
+        old_max = n_aborts;
+       for (i=0; i < n_aborts; i++) {
+           if ( strcmp(s1,abort_string[i]) == 0 ) {
+               free(abort_string[i]);
+               abort_string[i] = NULL;
+               pack++;
+               n_aborts--;
+               if (verbose)
+                   logf("clear abort on (%v)", s);
            }
        }
-    else
-       if (timeout_next)
-           {
-           timeout_next = 0;
-           timeout = atoi(s);
+        free(s1);
+       if (pack)
+           pack_array(abort_string,old_max);
+       return;
+    }
 
-           if (timeout <= 0)
-               timeout = DEFAULT_CHAT_TIMEOUT;
+    if (report_next) {
+       char *s1;
+       
+       report_next = 0;
+       if (n_reports >= MAX_REPORTS)
+           fatal(2, "Too many REPORT strings");
+       
+       s1 = clean(s, 0);
+       
+       if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
+           fatal(1, "Illegal or too-long REPORT string ('%v')", s);
+       
+       report_string[n_reports++] = s1;
+       
+       if (verbose)
+           logf("report (%v)", s);
+       return;
+    }
 
-           if (verbose)
-               {
-               syslog(LOG_INFO, "timeout set to %d seconds", timeout);
-               }
-           }
-       else
-           {
-           if (strcmp(s, "EOT") == 0)
-               s = "^D\\c";
-           else
-               if (strcmp(s, "BREAK") == 0)
-                   s = "\\K\\c";
-           if ( ! put_string(s))
-               {
-               syslog(LOG_INFO, "Failed");
-               terminate(1);
-               }
+    if (clear_report_next) {
+       char *s1;
+        char *s2 = s;
+       int   i;
+       int   old_max;
+       int   pack = 0;
+       
+       clear_report_next = 0;
+       
+       s1 = clean(s, 0);
+       
+       if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
+           fatal(1, "Illegal or too-long REPORT string ('%v')", s);
+
+       old_max = n_reports;
+       for (i=0; i < n_reports; i++) {
+           if ( strcmp(s1,report_string[i]) == 0 ) {
+               free(report_string[i]);
+               report_string[i] = NULL;
+               pack++;
+               n_reports--;
+               if (verbose)
+                   logf("clear report (%v)", s);
            }
+       }
+        free(s1);
+        if (pack)
+           pack_array(report_string,old_max);
+       
+       return;
     }
 
+    if (timeout_next) {
+       timeout_next = 0;
+       timeout = atoi(s);
+       
+       if (timeout <= 0)
+           timeout = DEFAULT_CHAT_TIMEOUT;
+
+       if (verbose)
+           logf("timeout set to %d seconds", timeout);
+
+       return;
+    }
+
+    if (strcmp(s, "EOT") == 0)
+       s = "^D\\c";
+    else if (strcmp(s, "BREAK") == 0)
+       s = "\\K\\c";
+
+    if (!put_string(s))
+       fatal(1, "Failed");
+}
+
 int get_char()
-    {
+{
     int status;
     char c;
 
     status = read(0, &c, 1);
 
-    switch (status)
-       {
-       case 1:
-           return ((int)c & 0x7F);
+    switch (status) {
+    case 1:
+       return ((int)c & 0x7F);
 
-       default:
-           syslog(LOG_WARNING, "warning: read() on stdin returned %d",
-                  status);
+    default:
+       logf("warning: read() on stdin returned %d", status);
 
-       case -1:
-           if ((status = fcntl(0, F_GETFL, 0)) == -1)
-               sysfatal("Can't get file mode flags on stdin");
-           else
-               if (fcntl(0, F_SETFL, status & ~FNDELAY) == -1)
-                   sysfatal("Can't set file mode flags on stdin");
+    case -1:
+       if ((status = fcntl(0, F_GETFL, 0)) == -1)
+           fatal(2, "Can't get file mode flags on stdin: %m");
 
-           return (-1);
-       }
+       if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1)
+           fatal(2, "Can't set file mode flags on stdin: %m");
+       
+       return (-1);
     }
+}
 
 int put_char(c)
 int c;
-    {
+{
     int status;
+    char ch = c;
 
-    delay();
+    usleep(10000);             /* inter-character typing delay (?) */
 
-    status = write(1, &c, 1);
+    status = write(1, &ch, 1);
 
-    switch (status)
-       {
-       case 1:
-           return (0);
-
-       default:
-           syslog(LOG_WARNING, "warning: write() on stdout returned %d",
-                  status);
-
-       case -1:
-           if ((status = fcntl(0, F_GETFL, 0)) == -1)
-               sysfatal("Can't get file mode flags on stdin");
-           else
-               if (fcntl(0, F_SETFL, status & ~FNDELAY) == -1)
-                   sysfatal("Can't set file mode flags on stdin");
+    switch (status) {
+    case 1:
+       return (0);
+       
+    default:
+       logf("warning: write() on stdout returned %d", status);
+       
+    case -1:
+       if ((status = fcntl(0, F_GETFL, 0)) == -1)
+           fatal(2, "Can't get file mode flags on stdin, %m");
 
-           return (-1);
-       }
+       if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1)
+           fatal(2, "Can't set file mode flags on stdin: %m");
+       
+       return (-1);
     }
+}
 
 int write_char (c)
 int c;
-    {
-    if (alarmed || put_char(c) < 0)
-       {
-       extern int errno;
-
-       alarm(0); alarmed = 0;
+{
+    if (alarmed || put_char(c) < 0) {
+       alarm(0);
+       alarmed = 0;
 
-       if (verbose)
-           {
+       if (verbose) {
            if (errno == EINTR || errno == EWOULDBLOCK)
-               syslog(LOG_INFO, " -- write timed out");
+               logf(" -- write timed out");
            else
-               syslog(LOG_INFO, " -- write failed: %m");
-           }
-       return (0);
+               logf(" -- write failed: %m");
        }
-    return (1);
+       return (0);
     }
+    return (1);
+}
 
 int put_string (s)
 register char *s;
-    {
+{
+    quiet = 0;
     s = clean(s, 1);
 
-    if (verbose)
-       {
-       logf("send (");
-
+    if (verbose) {
        if (quiet)
-           logf("??????");
+           logf("send (??????)");
        else
-           {
-           register char *s1 = s;
-
-           for (s1 = s; *s1; ++s1)
-               logf(character(*s1));
-           }
-
-       logf(")\n");
-       }
+           logf("send (%v)", s);
+    }
 
     alarm(timeout); alarmed = 0;
 
-    while (*s)
-       {
+    while (*s) {
        register char c = *s++;
 
-       if (c != '\\')
-           {
+       if (c != '\\') {
            if (!write_char (c))
                return 0;
            continue;
-           }
+       }
 
        c = *s++;
-       switch (c)
-           {
+       switch (c) {
        case 'd':
            sleep(1);
            break;
@@ -914,30 +1222,61 @@ register char *s;
            break;
 
        case 'p':
-           usleep(10000); /* 1/100th of a second. */
+           usleep(10000);      /* 1/100th of a second (arg is microseconds) */
            break;
 
        default:
            if (!write_char (c))
                return 0;
            break;
-           }
        }
+    }
 
     alarm(0);
     alarmed = 0;
     return (1);
+}
+
+/*
+ *     Echo a character to stderr.
+ *     When called with -1, a '\n' character is generated when
+ *     the cursor is not at the beginning of a line.
+ */
+void echo_stderr(n)
+int n;
+{
+    static int need_lf;
+    char *s;
+
+    switch (n) {
+    case '\r':         /* ignore '\r' */
+       break;
+    case -1:
+       if (need_lf == 0)
+           break;
+       /* fall through */
+    case '\n':
+       write(2, "\n", 1);
+       need_lf = 0;
+       break;
+    default:
+       s = character(n);
+       write(2, s, strlen(s));
+       need_lf = 1;
+       break;
     }
+}
 
 /*
  *     'Wait for' this string to appear on this file descriptor.
  */
 int get_string(string)
 register char *string;
-    {
+{
     char temp[STR_LEN];
     int c, printed = 0, len, minlen;
     register char *s = temp, *end = s + STR_LEN;
+    char *logged = temp;
 
     fail_reason = (char *)0;
     string = clean(string, 0);
@@ -945,105 +1284,140 @@ register char *string;
     minlen = (len > sizeof(fail_buffer)? len: sizeof(fail_buffer)) - 1;
 
     if (verbose)
-       {
-       register char *s1;
+       logf("expect (%v)", string);
 
-       logf("expect (");
+    if (len > STR_LEN) {
+       logf("expect string is too long");
+       exit_code = 1;
+       return 0;
+    }
 
-       for (s1 = string; *s1; ++s1)
-           logf(character(*s1));
+    if (len == 0) {
+       if (verbose)
+           logf("got it");
+       return (1);
+    }
 
-       logf(")\n");
-       }
+    alarm(timeout);
+    alarmed = 0;
 
-    if (len > STR_LEN)
-       {
-       syslog(LOG_INFO, "expect string is too long");
-       return 0;
+    while ( ! alarmed && (c = get_char()) >= 0) {
+       int n, abort_len, report_len;
+
+       if (echo)
+           echo_stderr(c);
+       if (verbose && c == '\n') {
+           if (s == logged)
+               logf("");       /* blank line */
+           else
+               logf("%0.*v", s - logged, logged);
+           logged = s + 1;
        }
 
-    if (len == 0)
-       {
-       if (verbose)
-           {
-           syslog(LOG_INFO, "got it");
-           }
+       *s++ = c;
 
-       return (1);
+       if (verbose && s >= logged + 80) {
+           logf("%0.*v", s - logged, logged);
+           logged = s;
        }
 
-    alarm(timeout); alarmed = 0;
+       if (Verbose) {
+          if (c == '\n')
+              fputc( '\n', stderr );
+          else if (c != '\r')
+              fprintf( stderr, "%s", character(c) );
+       }
 
-    while ( ! alarmed && (c = get_char()) >= 0)
-       {
-       int n, abort_len;
+       if (!report_gathering) {
+           for (n = 0; n < n_reports; ++n) {
+               if ((report_string[n] != (char*) NULL) &&
+                   s - temp >= (report_len = strlen(report_string[n])) &&
+                   strncmp(s - report_len, report_string[n], report_len) == 0) {
+                   time_t time_now   = time ((time_t*) NULL);
+                   struct tm* tm_now = localtime (&time_now);
 
-       if (verbose)
-           {
-           if (c == '\n')
-               logf("\n");
-           else
-               logf(character(c));
-           }
+                   strftime (report_buffer, 20, "%b %d %H:%M:%S ", tm_now);
+                   strcat (report_buffer, report_string[n]);
 
-       *s++ = c;
+                   report_string[n] = (char *) NULL;
+                   report_gathering = 1;
+                   break;
+               }
+           }
+       }
+       else {
+           if (!iscntrl (c)) {
+               int rep_len = strlen (report_buffer);
+               report_buffer[rep_len]     = c;
+               report_buffer[rep_len + 1] = '\0';
+           }
+           else {
+               report_gathering = 0;
+               fprintf (report_fp, "chat:  %s\n", report_buffer);
+           }
+       }
 
        if (s - temp >= len &&
            c == string[len - 1] &&
-           strncmp(s - len, string, len) == 0)
-           {
-           if (verbose)
-               {
+           strncmp(s - len, string, len) == 0) {
+           if (verbose) {
+               if (s > logged)
+                   logf("%0.*v", s - logged, logged);
                logf(" -- got it\n");
-               }
+           }
 
-           alarm(0); alarmed = 0;
+           alarm(0);
+           alarmed = 0;
            return (1);
-           }
+       }
 
-       for (n = 0; n < n_aborts; ++n)
+       for (n = 0; n < n_aborts; ++n) {
            if (s - temp >= (abort_len = strlen(abort_string[n])) &&
-               strncmp(s - abort_len, abort_string[n], abort_len) == 0)
-               {
-               if (verbose)
-                   {
-                   logf(" -- failed\n");
-                   }
+               strncmp(s - abort_len, abort_string[n], abort_len) == 0) {
+               if (verbose) {
+                   if (s > logged)
+                       logf("%0.*v", s - logged, logged);
+                   logf(" -- failed");
+               }
 
-               alarm(0); alarmed = 0;
+               alarm(0);
+               alarmed = 0;
+               exit_code = n + 4;
                strcpy(fail_reason = fail_buffer, abort_string[n]);
                return (0);
-               }
+           }
+       }
 
-       if (s >= end)
-           {
-           strncpy(temp, s - minlen, minlen);
-           s = temp + minlen;
+       if (s >= end) {
+           if (logged < s - minlen) {
+               logf("%0.*v", s - logged, logged);
+               logged = s;
            }
+           s -= minlen;
+           memmove(temp, s, minlen);
+           logged = temp + (logged - s);
+           s = temp + minlen;
+       }
 
        if (alarmed && verbose)
-           syslog(LOG_WARNING, "warning: alarm synchronization problem");
-       }
+           logf("warning: alarm synchronization problem");
+    }
 
     alarm(0);
     
-    if (verbose && printed)
-       {
+    if (verbose && printed) {
        if (alarmed)
-           logf(" -- read timed out\n");
+           logf(" -- read timed out");
        else
-           {
-           logflush();
-           syslog(LOG_INFO, " -- read failed: %m");
-           }
-       }
+           logf(" -- read failed: %m");
+    }
 
-    alarmed = 0;
+    exit_code = 3;
+    alarmed   = 0;
     return (0);
-    }
+}
 
-#ifdef ultrix
-#undef NO_USLEEP
+#ifdef NO_USLEEP
 #include <sys/types.h>
 #include <sys/time.h>
 
@@ -1058,30 +1432,238 @@ int
 usleep( usec )                           /* returns 0 if ok, else -1 */
     long               usec;           /* delay in microseconds */
 {
-    static struct                      /* `timeval' */
-       {
-           long        tv_sec;         /* seconds */
-           long        tv_usec;        /* microsecs */
-       }   delay;          /* _select() timeout */
+    static struct {            /* `timeval' */
+       long    tv_sec;         /* seconds */
+       long    tv_usec;        /* microsecs */
+    } delay;                   /* _select() timeout */
 
-    delay.tv_sec = usec / 1000000L;
+    delay.tv_sec  = usec / 1000000L;
     delay.tv_usec = usec % 1000000L;
 
-    return select( 0, (long *)0, (long *)0, (long *)0, &delay );
+    return select(0, (long *)0, (long *)0, (long *)0, &delay);
 }
 #endif
 
+void
+pack_array (array, end)
+    char **array; /* The address of the array of string pointers */
+    int    end;   /* The index of the next free entry before CLR_ */
+{
+    int i, j;
+
+    for (i = 0; i < end; i++) {
+       if (array[i] == NULL) {
+           for (j = i+1; j < end; ++j)
+               if (array[j] != NULL)
+                   array[i++] = array[j];
+           for (; i < end; ++i)
+               array[i] = NULL;
+           break;
+       }
+    }
+}
+
 /*
- *     Delay an amount appropriate for between typed characters.
+ * vfmtmsg - format a message into a buffer.  Like vsprintf except we
+ * also specify the length of the output buffer, and we handle the
+ * %m (error message) format.
+ * Doesn't do floating-point formats.
+ * Returns the number of chars put into buf.
  */
-void delay()
-    {
-# ifdef NO_USLEEP
-    register int i;
-
-    for (i = 0; i < 30000; ++i)                /* ... did we just say appropriate? */
-       ;
-# else /* NO_USLEEP */
-    usleep(100);
-# endif /* NO_USLEEP */
+#define OUTCHAR(c)     (buflen > 0? (--buflen, *buf++ = (c)): 0)
+
+int
+vfmtmsg(buf, buflen, fmt, args)
+    char *buf;
+    int buflen;
+    const char *fmt;
+    va_list args;
+{
+    int c, i, n;
+    int width, prec, fillch;
+    int base, len, neg, quoted;
+    unsigned long val = 0;
+    char *str, *buf0;
+    const char *f;
+    unsigned char *p;
+    char num[32];
+    time_t t;
+    static char hexchars[] = "0123456789abcdef";
+
+    buf0 = buf;
+    --buflen;
+    while (buflen > 0) {
+       for (f = fmt; *f != '%' && *f != 0; ++f)
+           ;
+       if (f > fmt) {
+           len = f - fmt;
+           if (len > buflen)
+               len = buflen;
+           memcpy(buf, fmt, len);
+           buf += len;
+           buflen -= len;
+           fmt = f;
+       }
+       if (*fmt == 0)
+           break;
+       c = *++fmt;
+       width = prec = 0;
+       fillch = ' ';
+       if (c == '0') {
+           fillch = '0';
+           c = *++fmt;
+       }
+       if (c == '*') {
+           width = va_arg(args, int);
+           c = *++fmt;
+       } else {
+           while (isdigit(c)) {
+               width = width * 10 + c - '0';
+               c = *++fmt;
+           }
+       }
+       if (c == '.') {
+           c = *++fmt;
+           if (c == '*') {
+               prec = va_arg(args, int);
+               c = *++fmt;
+           } else {
+               while (isdigit(c)) {
+                   prec = prec * 10 + c - '0';
+                   c = *++fmt;
+               }
+           }
+       }
+       str = 0;
+       base = 0;
+       neg = 0;
+       ++fmt;
+       switch (c) {
+       case 'd':
+           i = va_arg(args, int);
+           if (i < 0) {
+               neg = 1;
+               val = -i;
+           } else
+               val = i;
+           base = 10;
+           break;
+       case 'o':
+           val = va_arg(args, unsigned int);
+           base = 8;
+           break;
+       case 'x':
+           val = va_arg(args, unsigned int);
+           base = 16;
+           break;
+       case 'p':
+           val = (unsigned long) va_arg(args, void *);
+           base = 16;
+           neg = 2;
+           break;
+       case 's':
+           str = va_arg(args, char *);
+           break;
+       case 'c':
+           num[0] = va_arg(args, int);
+           num[1] = 0;
+           str = num;
+           break;
+       case 'm':
+           str = strerror(errno);
+           break;
+       case 'v':               /* "visible" string */
+       case 'q':               /* quoted string */
+           quoted = c == 'q';
+           p = va_arg(args, unsigned char *);
+           if (fillch == '0' && prec > 0) {
+               n = prec;
+           } else {
+               n = strlen((char *)p);
+               if (prec > 0 && prec < n)
+                   n = prec;
+           }
+           while (n > 0 && buflen > 0) {
+               c = *p++;
+               --n;
+               if (!quoted && c >= 0x80) {
+                   OUTCHAR('M');
+                   OUTCHAR('-');
+                   c -= 0x80;
+               }
+               if (quoted && (c == '"' || c == '\\'))
+                   OUTCHAR('\\');
+               if (c < 0x20 || (0x7f <= c && c < 0xa0)) {
+                   if (quoted) {
+                       OUTCHAR('\\');
+                       switch (c) {
+                       case '\t':      OUTCHAR('t');   break;
+                       case '\n':      OUTCHAR('n');   break;
+                       case '\b':      OUTCHAR('b');   break;
+                       case '\f':      OUTCHAR('f');   break;
+                       default:
+                           OUTCHAR('x');
+                           OUTCHAR(hexchars[c >> 4]);
+                           OUTCHAR(hexchars[c & 0xf]);
+                       }
+                   } else {
+                       if (c == '\t')
+                           OUTCHAR(c);
+                       else {
+                           OUTCHAR('^');
+                           OUTCHAR(c ^ 0x40);
+                       }
+                   }
+               } else
+                   OUTCHAR(c);
+           }
+           continue;
+       default:
+           *buf++ = '%';
+           if (c != '%')
+               --fmt;          /* so %z outputs %z etc. */
+           --buflen;
+           continue;
+       }
+       if (base != 0) {
+           str = num + sizeof(num);
+           *--str = 0;
+           while (str > num + neg) {
+               *--str = hexchars[val % base];
+               val = val / base;
+               if (--prec <= 0 && val == 0)
+                   break;
+           }
+           switch (neg) {
+           case 1:
+               *--str = '-';
+               break;
+           case 2:
+               *--str = 'x';
+               *--str = '0';
+               break;
+           }
+           len = num + sizeof(num) - 1 - str;
+       } else {
+           len = strlen(str);
+           if (prec > 0 && len > prec)
+               len = prec;
+       }
+       if (width > 0) {
+           if (width > buflen)
+               width = buflen;
+           if ((n = width - len) > 0) {
+               buflen -= n;
+               for (; n > 0; --n)
+                   *buf++ = fillch;
+           }
+       }
+       if (len > buflen)
+           len = buflen;
+       memcpy(buf, str, len);
+       buf += len;
+       buflen -= len;
     }
+    *buf = 0;
+    return buf - buf0;
+}