[Date Prev] [Date Index] [Date Next] [Thread Prev] [Thread Index] [Thread Next]

a patch adding hooks to run 'chat' during console initialisation....

Greg A. Woods woods@weird.com
Wed, 1 May 2002 22:29:52 -0700 (PDT)


Attached is a patch to integrate hooks to call the public domain 'chat'
program (often supplied with pppd, but also included in this patch) with
an optional per-console chat script each time the console is initialised
(i.e. after carrier or DTR returns, or after a telnet connection is
re-established).

A separate conserver.chat file is provided for, both to simplify the
configuration and to allow the chat script to be protected from prying
eyes (since its most common application might well be to login to a
terminal server and thus it will contain at least an access password --
that's what I use it for anyway, and the examples are real! :-).

The chat-script is run after the first read for a network console port
(in order to allow telent negotiation to proceed first even though
nothing is really done yet).  This means a chat to a really raw socket
connection might fail unless something can be read before the chat is
attempted.

I apologise for the rather extensive unrelated changes -- I found it
necessary to hack around quite a bit before I got all the right things
in the right places and I'm too lazy to try to undo the stylistic
changes (I find some aspects of the current coding style almost
unreadable, and some of the utility functions are quite bizzare).

The portability of this code isn't guaranteed, though it shouldn't be
too bad, at least for any half-way modern system (not that the code will
even begin to compile on a pre-ANSI machine anyway!).  It's been tested
on NetBSD/sparc-1.3.2 and lightly on NetBSD/{i386,sparc}-1.5W.

Note the version of the 'chat' program included in this patch is a
slightly modified version of the one from NetBSD-current (1.25).  I've
added a new flag, '-I', which causes it to not try to modify the TTY
settings (which fails on a direct socket); and I've fixed up the manual
page a bit.  The hooks in conserver will use this option if the console
is a network port, so you'll definitely need this customised version!

(Note I've not really tested it against a serial TTY, but it should work
fine given that the chat program works fine when tested by hand on the
command-line via a real serial port.)  I've not done any autoconf-
iscation on chat either.  It may compile by default though on many
modern systems since it seems to expects only TERMIOS and other basic
POSIX functionality.  It certainly works fine on NetBSD from whence it
was taken, and no doubt will work equally well on any recent *BSD and
maybe even MacOS-X.

Note too that some, though perhaps not all, of the suggested
improvements in the last patch I posted are included in this one too
(they were too hard for me to separate :-).

(apologies also for the size of this message -- but it appears it will
just under 128kb on the wire and it seems best to post it as one than to
expect y'all to put together a MIME message/partial multi-message post ;-)

-- 
								Greg A. Woods

+1 416 218-0098;  <gwoods@acm.org>;  <g.a.woods@ieee.org>;  <woods@robohack.ca>
Planix, Inc. <woods@planix.com>; VE3TCP; Secrets of the Weird <woods@weird.com>

Index: conserver/readcfg.h
===================================================================
RCS file: /cvs/misc/conserver/conserver/readcfg.h,v
retrieving revision 1.1.1.2
diff -c -r1.1.1.2 readcfg.h
*** conserver/readcfg.h	16 Mar 2002 22:24:02 -0000	1.1.1.2
--- conserver/readcfg.h	1 May 2002 22:54:27 -0000
***************
*** 48,55 ****
--- 48,59 ----
  extern void ReadCfg(char *, FILE *);
  extern char *pruneSpace(char *);
  extern void ReReadCfg();
+ extern void ReadChat(char *, FILE *);
+ extern CONSENT *FindCE(char *);
  #else
  extern void ReadCfg();
  extern char *pruneSpace();
  extern void ReReadCfg();
+ extern void ReadChat();
+ extern CONSENT *FindCE();
  #endif
Index: conserver/readcfg.c
===================================================================
RCS file: /cvs/misc/conserver/conserver/readcfg.c,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 readcfg.c
*** conserver/readcfg.c	24 Apr 2002 21:42:00 -0000	1.1.1.3
--- conserver/readcfg.c	2 May 2002 02:25:49 -0000
***************
*** 246,252 ****
  #if USE_ANSI_PROTO
  ReadCfg(char *pcFile, FILE * fp)
  #else
! ReadCfg(pcFile, fp, master)
      char *pcFile;
      FILE *fp;
  #endif
--- 246,252 ----
  #if USE_ANSI_PROTO
  ReadCfg(char *pcFile, FILE * fp)
  #else
! ReadCfg(pcFile, fp)
      char *pcFile;
      FILE *fp;
  #endif
***************
*** 595,600 ****
--- 595,602 ----
  	pCE->fup = pCE->autoReUp = 0;
  	pCE->pCLon = pCE->pCLwr = (CONSCLIENT *) 0;
  	pCE->fdlog = (CONSFILE *) 0;
+ 	buildMyString((char *)0, &pCE->consoleChat); /* see ReadChat() */
+ 	pCE->doneChat = 0;
  
  	if (pcLine[0] == '!') {
  	    char acOut[100];
***************
*** 1046,1051 ****
--- 1048,1054 ----
  #endif
  {
      FILE *fpConfig;
+     FILE *fpChat;
  
      if ((FILE *) 0 == (fpConfig = fopen(pcConfig, "r"))) {
  	Error("fopen: %s: %s", pcConfig, strerror(errno));
***************
*** 1056,1061 ****
--- 1059,1069 ----
  
      fclose(fpConfig);
  
+     if ((fpChat = fopen(pcChat, "r"))) {
+ 	ReadChat(pcChat, fpChat);
+ 	fclose(fpChat);
+     }
+ 
      if (pGroups == (GRPENT *) 0 && pRCList == (REMOTE *) 0) {
  	if (isMaster) {
  	    Error("No consoles found in configuration file");
***************
*** 1115,1118 ****
--- 1123,1195 ----
  	    }
  	}
      }
+ }
+ 
+ /*
+  * read in the optional chat file....
+  */
+ CONSENT *
+ FindCE(ConsName)
+ 	char *ConsName;
+ {
+ 	GRPENT *pGE;
+ 	CONSENT *pCE;
+ 
+ 	for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
+ 		if (pGE->imembers == 0) {
+ 			continue;
+ 		}
+ 		for (pCE = pGE->pCElist; pCE; pCE = pCE->pCEnext) {
+ 			if (strcmp(pCE->server.string, ConsName) == 0) {
+ 				return pCE;
+ 			}
+ 		}
+ 	}
+ 
+ 	return NULL;
+ }
+ 
+ /*
+  * read in the optional chat file....
+  */
+ void
+ ReadChat(pcFile, fp)
+ 	char *pcFile;
+ 	FILE *fp;
+ {
+ 	int iLine;
+ 	unsigned char *acIn;
+ 	STRING acInSave = { (char *) 0, 0, 0 };
+ 
+ 	buildMyString((char *) 0, &acInSave);
+ 
+ 	iLine = 0;
+ 	while ((acIn = readLine(fp, &acInSave, &iLine))) {
+ 		char *acStart, *pcChat, *pcMark, *pcCN;
+ 		CONSENT *pCE;
+ 
+ 		acStart = pruneSpace(acIn);
+ 
+ 		if ((pcMark = strchr(acStart, ':'))) {
+ 			*pcMark++ = '\000';
+ 			pcCN = pruneSpace(acStart);
+ 			pcChat = pruneSpace(pcMark);
+ 		} else {
+ 			Error("%s(%d) bad config line `%s'",
+ 			      pcFile, iLine, acIn);
+ 			continue;
+ 		}
+ 		if (strchr(pcChat, '\'')) {
+ 			Error("%s(%d) chat script must not contain any single quotes: %s",
+ 			      pcFile, iLine, acIn);
+ 			continue;
+ 		}
+ 		if ((pCE = FindCE(pcCN))) {
+ 			buildMyString(pcChat, &pCE->consoleChat);
+ 		} else {
+ 			Error("%s(%d) unknown console: %s",
+ 			      pcFile, iLine, pcCN);
+ 			continue;
+ 		}
+ 	}
  }
Index: conserver/master.c
===================================================================
RCS file: /cvs/misc/conserver/conserver/master.c,v
retrieving revision 1.1.1.2
diff -c -r1.1.1.2 master.c
*** conserver/master.c	16 Mar 2002 22:24:01 -0000	1.1.1.2
--- conserver/master.c	2 May 2002 02:32:20 -0000
***************
*** 104,110 ****
  	    continue;
  	}
  
! 	for (pGE = pGroups; pGE != (GRPENT *) 0; pGE = pGE->pGEnext) {
  	    if (0 == pGE->imembers)
  		continue;
  	    if (pid != pGE->pid)
--- 104,110 ----
  	    continue;
  	}
  
! 	for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
  	    if (0 == pGE->imembers)
  		continue;
  	    if (pid != pGE->pid)
***************
*** 223,229 ****
  {
      GRPENT *pGE;
  
!     for (pGE = pGroups; pGE != (GRPENT *) 0; pGE = pGE->pGEnext) {
  	if (0 == pGE->imembers || -1 == pGE->pid)
  	    continue;
  	Debug(1, "Sending pid %d signal %d", pGE->pid, arg);
--- 223,229 ----
  {
      GRPENT *pGE;
  
!     for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
  	if (0 == pGE->imembers || -1 == pGE->pid)
  	    continue;
  	Debug(1, "Sending pid %d signal %d", pGE->pid, arg);
***************
*** 462,468 ****
  		(char *)0
  	    };
  	    char **ppc;
! 	    for (ppc = apcHelp; (char *)0 != *ppc; ++ppc) {
  		(void)fileWrite(csocket, *ppc, -1);
  	    }
  	    fileClose(&csocket);
--- 462,468 ----
  		(char *)0
  	    };
  	    char **ppc;
! 	    for (ppc = apcHelp; *ppc; ++ppc) {
  		(void)fileWrite(csocket, *ppc, -1);
  	    }
  	    fileClose(&csocket);
***************
*** 495,501 ****
  	if (0 == strcmp(acIn, "groups")) {
  	    int iSep = 1;
  
! 	    for (pGE = pGroups; pGE != (GRPENT *) 0; pGE = pGE->pGEnext) {
  		if (0 == pGE->imembers)
  		    continue;
  		filePrint(csocket, ":%u" + iSep, ntohs(pGE->port));
--- 495,501 ----
  	if (0 == strcmp(acIn, "groups")) {
  	    int iSep = 1;
  
! 	    for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
  		if (0 == pGE->imembers)
  		    continue;
  		filePrint(csocket, ":%u" + iSep, ntohs(pGE->port));
***************
*** 523,529 ****
  		filePrint(csocket, "@%s", inet_ntoa(lcl.sin_addr));
  		iSep = 0;
  	    }
! 	    for (pRC = pRCUniq; (REMOTE *) 0 != pRC; pRC = pRC->pRCuniq) {
  		filePrint(csocket, ":@%s" + iSep, pRC->rhost.string);
  		iSep = 0;
  	    }
--- 523,529 ----
  		filePrint(csocket, "@%s", inet_ntoa(lcl.sin_addr));
  		iSep = 0;
  	    }
! 	    for (pRC = pRCUniq; pRC; pRC = pRC->pRCuniq) {
  		filePrint(csocket, ":@%s" + iSep, pRC->rhost.string);
  		iSep = 0;
  	    }
***************
*** 553,563 ****
  	found = 0;
  	pRCFound = (REMOTE *) 0;
  	/* look for a local machine */
! 	for (pGE = pGroups; pGE != (GRPENT *) 0; pGE = pGE->pGEnext) {
  	    if (0 == pGE->imembers)
  		continue;
! 	    for (pCE = pGE->pCElist; pCE != (CONSENT *) 0;
! 		 pCE = pCE->pCEnext) {
  		if (0 != strcmp(pcArgs, pCE->server.string)) {
  		    continue;
  		}
--- 553,562 ----
  	found = 0;
  	pRCFound = (REMOTE *) 0;
  	/* look for a local machine */
! 	for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
  	    if (0 == pGE->imembers)
  		continue;
! 	    for (pCE = pGE->pCElist; pCE; pCE = pCE->pCEnext) {
  		if (0 != strcmp(pcArgs, pCE->server.string)) {
  		    continue;
  		}
***************
*** 571,577 ****
  	 * duplicates - a bad state to be in.
  	 * Does the readcfg.c code even check for dups?
  	 */
! 	for (pRC = pRCList; (REMOTE *) 0 != pRC; pRC = pRC->pRCnext) {
  	    if (0 != strcmp(pcArgs, pRC->rserver.string)) {
  		continue;
  	    }
--- 570,576 ----
  	 * duplicates - a bad state to be in.
  	 * Does the readcfg.c code even check for dups?
  	 */
! 	for (pRC = pRCList; pRC; pRC = pRC->pRCnext) {
  	    if (0 != strcmp(pcArgs, pRC->rserver.string)) {
  		continue;
  	    }
***************
*** 581,591 ****
  	    pRCFound = pRC;
  	}
  	if (found == 0) {	/* Then look for substring matches */
! 	    for (pGE = pGroups; pGE != (GRPENT *) 0; pGE = pGE->pGEnext) {
  		if (0 == pGE->imembers)
  		    continue;
! 		for (pCE = pGE->pCElist; pCE != (CONSENT *) 0;
! 		     pCE = pCE->pCEnext) {
  		    if (0 !=
  			strncmp(pcArgs, pCE->server.string,
  				strlen(pcArgs))) {
--- 580,590 ----
  	    pRCFound = pRC;
  	}
  	if (found == 0) {	/* Then look for substring matches */
! 	    Debug(1, "client wants server '%s', looking for substrings", pcArgs);
! 	    for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
  		if (0 == pGE->imembers)
  		    continue;
! 		for (pCE = pGE->pCElist; pCE; pCE = pCE->pCEnext) {
  		    if (0 !=
  			strncmp(pcArgs, pCE->server.string,
  				strlen(pcArgs))) {
***************
*** 599,605 ****
  	    }
  	    /* look for a remote server */
  	    /* again, looks for dups with local consoles */
! 	    for (pRC = pRCList; (REMOTE *) 0 != pRC; pRC = pRC->pRCnext) {
  		if (0 !=
  		    strncmp(pcArgs, pRC->rserver.string, strlen(pcArgs))) {
  		    continue;
--- 598,604 ----
  	    }
  	    /* look for a remote server */
  	    /* again, looks for dups with local consoles */
! 	    for (pRC = pRCList; pRC; pRC = pRC->pRCnext) {
  		if (0 !=
  		    strncmp(pcArgs, pRC->rserver.string, strlen(pcArgs))) {
  		    continue;
Index: conserver/main.h
===================================================================
RCS file: /cvs/misc/conserver/conserver/main.h,v
retrieving revision 1.1.1.2
diff -c -r1.1.1.2 main.h
*** conserver/main.h	16 Mar 2002 22:24:01 -0000	1.1.1.2
--- conserver/main.h	2 May 2002 02:10:30 -0000
***************
*** 44,49 ****
--- 44,50 ----
  extern unsigned int bindPort, bindBasePort;
  extern char *pcLogfile;
  extern char *pcConfig;
+ extern char *pcChat;
  extern char *pcPasswd;
  extern int cMaxMemb;
  extern struct sockaddr_in in_port;
Index: conserver/main.c
===================================================================
RCS file: /cvs/misc/conserver/conserver/main.c,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 main.c
*** conserver/main.c	24 Apr 2002 21:42:00 -0000	1.1.1.3
--- conserver/main.c	2 May 2002 03:37:39 -0000
***************
*** 54,70 ****
  #include <readcfg.h>
  #include <version.h>
  
! int fAll = 0, fVerbose = 0, fSoftcar = 0, fNoinit = 0, fVersion =
!     0, fStrip = 0, fDaemon = 0, fUseLogfile = 0, fReopen = 0, fReopenall =
!     0;
  
  char chDefAcc = 'r';
  
! #define FULLCFPATH SYSCONFDIR "/" CONFIGFILE
! #define FULLPDPATH SYSCONFDIR "/" PASSWDFILE
  
  char *pcLogfile = LOGFILEPATH;
  char *pcConfig = FULLCFPATH;
  char *pcPasswd = FULLPDPATH;
  char *pcPort = DEFPORT;
  char *pcBasePort = DEFBASEPORT;
--- 54,84 ----
  #include <readcfg.h>
  #include <version.h>
  
! int fAll = 0;
! int fDaemon = 0;
! int fDoNotRun = 0;
! int fNoinit = 0;
! int fReopen = 0;
! int fReopenall = 0;
! int fSoftcar = 0;
! int fStrip = 0;
! int fUseLogfile = 0;
! int fVerbose = 0;
! int fVersion = 0;
  
  char chDefAcc = 'r';
  
! #ifdef __STDC__
! # define FULLCFPATH	SYSCONFDIR "/" CONFIGFILE
! # define FULLCHATPATH	SYSCONFDIR "/" CHATFILE
! # define FULLPDPATH	SYSCONFDIR "/" PASSWDFILE
! #else
! # include "ERROR: this code assumes ISO/STD C ability to concatenate strings."
! #endif
  
  char *pcLogfile = LOGFILEPATH;
  char *pcConfig = FULLCFPATH;
+ char *pcChat = FULLCHATPATH;
  char *pcPasswd = FULLPDPATH;
  char *pcPort = DEFPORT;
  char *pcBasePort = DEFBASEPORT;
***************
*** 109,115 ****
   */
  static void
  #if USE_ANSI_PROTO
! daemonize()
  #else
  daemonize()
  #endif
--- 123,129 ----
   */
  static void
  #if USE_ANSI_PROTO
! daemonize(void)
  #else
  daemonize()
  #endif
***************
*** 173,184 ****
  
  
  static char u_terse[] =
!     " [-7dDhinouvV] [-a type] [-m max] [-M addr] [-p port] [-b port] [-C config] [-P passwd] [-L logfile] [-O min]";
  static char *apcLong[] = {
      "7          strip the high bit of all console data",
      "a type     set the default access type",
      "b port     base port for secondary channel (any by default)",
!     "C config   give a new config file to the server process",
      "d          become a daemon, redirecting stdout/stderr to logfile",
      "D          enable debug output, sent to stderr",
      "h          output this message",
--- 187,199 ----
  
  
  static char u_terse[] =
!     " [-7dDhinouvV] [-a type] [-m max] [-M addr] [-p port] [-b port] [-c config] [-C chat] [-P passwd] [-L logfile] [-O min]";
  static char *apcLong[] = {
      "7          strip the high bit of all console data",
      "a type     set the default access type",
      "b port     base port for secondary channel (any by default)",
!     "c config   specify custom config file",
!     "C chat     specify chat-script file",
      "d          become a daemon, redirecting stdout/stderr to logfile",
      "D          enable debug output, sent to stderr",
      "h          output this message",
***************
*** 187,192 ****
--- 202,208 ----
      "m max      maximum consoles managed per process",
      "M addr     address to listen on (all addresses by default)",
      "n          obsolete - see -u",
+     "N          don't start any master process (use with -v)",
      "o          reopen downed console on client connect",
      "O min      reopen all downed consoles every <min> minutes",
      "p port     port to listen on",
***************
*** 301,322 ****
      if (!fDebug)
  	return;
  
!     for (pGE = pGroups; pGE != (GRPENT *) 0; pGE = pGE->pGEnext) {
  	Debug(1, "Group: id=%u pid=%d, port=%d, imembers=%d", pGE->id,
  	      pGE->port, pGE->pid, pGE->imembers);
  
! 	for (pCE = pGE->pCElist; pCE != (CONSENT *) 0; pCE = pCE->pCEnext) {
! 	    if (pCE->pccmd.string == (char *)0)
  		pCE->pccmd.string = empty;
! 	    if (pCE->server.string == (char *)0)
  		pCE->server.string = empty;
! 	    if (pCE->dfile.string == (char *)0)
  		pCE->dfile.string = empty;
! 	    if (pCE->lfile.string == (char *)0)
  		pCE->lfile.string = empty;
! 	    if (pCE->networkConsoleHost.string == (char *)0)
  		pCE->networkConsoleHost.string = empty;
! 	    if (pCE->acslave.string == (char *)0)
  		pCE->acslave.string = empty;
  
  	    Debug(1, "  server=%s, dfile=%s, lfile=%s", pCE->server.string,
--- 317,340 ----
      if (!fDebug)
  	return;
  
!     for (pGE = pGroups; pGE; pGE = pGE->pGEnext) {
  	Debug(1, "Group: id=%u pid=%d, port=%d, imembers=%d", pGE->id,
  	      pGE->port, pGE->pid, pGE->imembers);
  
! 	for (pCE = pGE->pCElist; pCE; pCE = pCE->pCEnext) {
! 	    if (!pCE->pccmd.string)
  		pCE->pccmd.string = empty;
! 	    if (!pCE->server.string)
  		pCE->server.string = empty;
! 	    if (!pCE->dfile.string)
  		pCE->dfile.string = empty;
! 	    if (!pCE->lfile.string)
  		pCE->lfile.string = empty;
! 	    if (!pCE->networkConsoleHost.string)
  		pCE->networkConsoleHost.string = empty;
! 	    if (!pCE->consoleChat.string)
! 		pCE->consoleChat.string = empty;
! 	    if (!pCE->acslave.string)
  		pCE->acslave.string = empty;
  
  	    Debug(1, "  server=%s, dfile=%s, lfile=%s", pCE->server.string,
***************
*** 326,337 ****
  
  	    Debug(1, "  isNetworkConsole=%d, networkConsoleHost=%s",
  		  pCE->isNetworkConsole, pCE->networkConsoleHost.string);
! 	    Debug(1,
! 		  "  networkConsolePort=%d, telnetState=%d, autoReup=%d",
! 		  pCE->networkConsolePort, pCE->telnetState,
! 		  pCE->autoReUp);
  
! 	    Debug(1, "  baud=%s, parity=%c", pCE->pbaud->acrate,
  		  pCE->pparity->ckey);
  
  	    Debug(1, "  fvirtual=%d, acslave=%s, pccmd=%s, ipid=%d",
--- 344,356 ----
  
  	    Debug(1, "  isNetworkConsole=%d, networkConsoleHost=%s",
  		  pCE->isNetworkConsole, pCE->networkConsoleHost.string);
! 	    Debug(1, "  networkConsolePort=%d, telnetState=%d, autoReup=%d",
! 		  pCE->networkConsolePort, pCE->telnetState, pCE->autoReUp);
  
! 	    Debug(1, "  consoleChat='%s'", pCE->consoleChat.string);
! 	    Debug(1, "  doneChat=%d, baud=%s, parity=%c",
! 		  pCE->doneChat,
! 		  pCE->pbaud->acrate,
  		  pCE->pparity->ckey);
  
  	    Debug(1, "  fvirtual=%d, acslave=%s, pccmd=%s, ipid=%d",
***************
*** 344,355 ****
  	    Debug(1, "  ------");
  	}
      }
!     for (pRC = pRCList; (REMOTE *) 0 != pRC; pRC = pRC->pRCnext) {
! 	if (pRC->rserver.string == (char *)0)
  	    pRC->rserver.string = empty;
! 	if (pRC->rhost.string == (char *)0)
  	    pRC->rhost.string = empty;
! 	Debug(1, "Remote: rserver=%s, rhost =%s", pRC->rserver.string,
  	      pRC->rhost.string);
      }
  }
--- 363,374 ----
  	    Debug(1, "  ------");
  	}
      }
!     for (pRC = pRCList; pRC; pRC = pRC->pRCnext) {
! 	if (!pRC->rserver.string)
  	    pRC->rserver.string = empty;
! 	if (!pRC->rhost.string)
  	    pRC->rhost.string = empty;
! 	Debug(1, "Remote: rserver=%s, rhost=%s", pRC->rserver.string,
  	      pRC->rhost.string);
      }
  }
***************
*** 373,380 ****
  {
      int i;
      FILE *fpConfig;
      struct hostent *hpMe;
!     static char acOpts[] = "7a:b:C:dDhiL:m:M:noO:p:P:suVv";
      extern int optopt;
      extern char *optarg;
      struct passwd *pwd;
--- 392,400 ----
  {
      int i;
      FILE *fpConfig;
+     FILE *fpChat;
      struct hostent *hpMe;
!     static char acOpts[] = "7a:b:C:dDhiL:m:M:nNoO:p:P:suvV";
      extern int optopt;
      extern char *optarg;
      struct passwd *pwd;
***************
*** 444,452 ****
  	    case 'b':
  		pcBasePort = optarg;
  		break;
! 	    case 'C':
  		pcConfig = optarg;
  		break;
  	    case 'd':
  		fDaemon = 1;
  		fUseLogfile = 1;
--- 464,475 ----
  	    case 'b':
  		pcBasePort = optarg;
  		break;
! 	    case 'c':
  		pcConfig = optarg;
  		break;
+ 	    case 'C':
+ 		pcChat = optarg;
+ 		break;
  	    case 'd':
  		fDaemon = 1;
  		fUseLogfile = 1;
***************
*** 473,478 ****
--- 496,504 ----
  	    case 'n':
  		/* noop now */
  		break;
+ 	    case 'N':
+ 		fDoNotRun = 1;
+ 		break;
  	    case 'o':
  		/* try reopening downed consoles on connect */
  		fReopen = 1;
***************
*** 624,646 ****
  	Error("fopen: %s: %s", pcConfig, strerror(errno));
  	exit(EX_UNAVAILABLE);
      }
! #if HAVE_FLOCK
!     /* we lock the configuration file so that two identical
!      * conservers will not be running together  (but two with
!      * different configurations can run on the same host).
!      */
!     if (-1 == flock(fileno(fpConfig), LOCK_NB | LOCK_EX)) {
! 	Error("%s is locked, won\'t run more than one conserver?",
! 	      pcConfig);
! 	exit(EX_UNAVAILABLE);
      }
  #endif
  
      ReadCfg(pcConfig, fpConfig);
  
      if (pGroups == (GRPENT *) 0 && pRCList == (REMOTE *) 0) {
  	Error("No consoles found in configuration file");
!     } else {
  	/* if no one can use us we need to come up with a default
  	 */
  	if (pACList == (ACCESS *) 0) {
--- 650,680 ----
  	Error("fopen: %s: %s", pcConfig, strerror(errno));
  	exit(EX_UNAVAILABLE);
      }
! #if HAVE_FLOCK					/* XXX fcntl() is infinitely more portable! */
!     if (!fDoNotRun) {
! 	/* we lock the configuration file so that two identical
! 	 * conservers will not be running together  (but two with
! 	 * different configurations can run on the same host).
! 	 */
! 	if (-1 == flock(fileno(fpConfig), LOCK_NB | LOCK_EX)) {
! 	    Error("%s is locked -- conserver already running, or is file being edited?",
! 		  pcConfig);
! 	    exit(EX_UNAVAILABLE);
! 	}
      }
  #endif
  
      ReadCfg(pcConfig, fpConfig);
+     /* Note: don't close fpConfig until we exit in order to keep lock... */
+ 
+     if ((fpChat = fopen(pcChat, "r"))) {
+ 	ReadChat(pcChat, fpChat);
+ 	fclose(fpChat);
+     }
  
      if (pGroups == (GRPENT *) 0 && pRCList == (REMOTE *) 0) {
  	Error("No consoles found in configuration file");
!     } else if (!fDoNotRun) {
  	/* if no one can use us we need to come up with a default
  	 */
  	if (pACList == (ACCESS *) 0) {
Index: conserver/group.c
===================================================================
RCS file: /cvs/misc/conserver/conserver/group.c,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 group.c
*** conserver/group.c	24 Apr 2002 21:42:00 -0000	1.1.1.3
--- conserver/group.c	2 May 2002 02:45:09 -0000
***************
*** 65,70 ****
--- 65,72 ----
  #include <stdio.h>
  #include <ctype.h>
  #include <signal.h>
+ #include <sys/types.h>
+ #include <sys/wait.h>
  #include <pwd.h>
  #if USE_ANSI_PROTO
  #include <stdarg.h>
***************
*** 98,103 ****
--- 100,108 ----
  static sig_atomic_t fSawReOpen = 0, fSawReUp = 0, fSawMark =
      0, fSawGoAway = 0, fSawReapVirt = 0;
  
+ static int DoTelnet __P((CONSENT *, int, unsigned char *, unsigned char *));
+ static int ConsChat __P((CONSENT *));
+ 
  void
  #if USE_ANSI_PROTO
  SendClientsMsg(CONSENT * pCE, char *message)
***************
*** 1313,1322 ****
      /* open all the files we need for the consoles in our group
       * if we can't get one (bitch and) flag as down
       */
!     if (!fNoinit)
  	for (pCE = pGE->pCElist; pCE != (CONSENT *) 0; pCE = pCE->pCEnext) {
  	    ConsInit(pCE, &pGE->rinit, 1);
  	}
  
      /* set up the list of free connection slots
       */
--- 1318,1328 ----
      /* open all the files we need for the consoles in our group
       * if we can't get one (bitch and) flag as down
       */
!     if (!fNoinit) {
  	for (pCE = pGE->pCElist; pCE != (CONSENT *) 0; pCE = pCE->pCEnext) {
  	    ConsInit(pCE, &pGE->rinit, 1);
  	}
+     }
  
      /* set up the list of free connection slots
       */
***************
*** 1401,1406 ****
--- 1407,1417 ----
  	    if (!pCEServing->fup || !FD_ISSET(pCEServing->fdtty, &rmask)) {
  		continue;
  	    }
+ 	    if (!pCEServing->isNetworkConsole && !pCEServing->doneChat) {
+ 		if (pCEServing->consoleChat.string && pCEServing->consoleChat.used) {
+ 		   pCEServing->doneChat = ConsChat(pCEServing);
+ 		}
+ 	    }
  	    /* read terminal line */
  	    if ((nr =
  		 read(pCEServing->fdtty, acInOrig,
***************
*** 1433,1484 ****
  	    Debug(1, "Read %d bytes from fd %d", nr, pCEServing->fdtty);
  
  	    if (pCEServing->isNetworkConsole) {
! 		/* Do a little Telnet Protocol interpretation
! 		 * state = 0: normal
! 		 *       = 1: Saw a IAC char
! 		 *       = 2: Saw a DONT/DO/WONT/WILL command
! 		 *       = 5: Saw a \r
! 		 */
! 		int new = 0, state;
! 		state = pCEServing->telnetState;
! 		for (i = 0; i < nr; ++i) {
! 		    if (state == 0 && acInOrig[i] == IAC) {
! 			Debug(1, "%s: Got telnet `IAC'",
! 			      pCEServing->server.string);
! 			state = 1;
! 		    } else if (state == 1 && acInOrig[i] != IAC) {
! 			Debug(1, "%s: Got telnet cmd `%u'",
! 			      pCEServing->server.string, acInOrig[i]);
! 			if (acInOrig[i] == DONT || acInOrig[i] == DO ||
! 			    acInOrig[i] == WILL || acInOrig[i] == WONT)
! 			    state = 2;
! 			else
! 			    state = 0;
! 		    } else if (state == 2) {
! 			Debug(1, "%s: Got telnet option `%u'",
! 			      pCEServing->server.string, acInOrig[i]);
! 			state = 0;
! 		    } else {
! 			if (state == 5) {
! 			    state = 0;
! 			    if (acInOrig[i] == '\000')
! 				continue;
! 			}
! 			if (acInOrig[i] == IAC)
! 			    Debug(1, "%s: Quoted `IAC'",
! 				  pCEServing->server.string);
! 			if (fStrip)
! 			    acIn[new++] = acInOrig[i] & 127;
! 			else
! 			    acIn[new++] = acInOrig[i];
! 			if (acInOrig[i] == '\r')
! 			    state = 5;
! 			else
! 			    state = 0;
! 		    }
! 		}
! 		pCEServing->telnetState = state;
! 		nr = new;
  	    } else {
  		for (i = 0; i < nr; ++i) {
  		    if (fStrip)
--- 1444,1450 ----
  	    Debug(1, "Read %d bytes from fd %d", nr, pCEServing->fdtty);
  
  	    if (pCEServing->isNetworkConsole) {
! 		nr = DoTelnet(pCEServing, nr, acInOrig, acIn);
  	    } else {
  		for (i = 0; i < nr; ++i) {
  		    if (fStrip)
***************
*** 1487,1492 ****
--- 1453,1463 ----
  			acIn[i] = acInOrig[i];
  		}
  	    }
+ 	    if (pCEServing->isNetworkConsole && !pCEServing->doneChat) {
+ 		if (pCEServing->consoleChat.string && pCEServing->consoleChat.used) {
+ 		   pCEServing->doneChat = ConsChat(pCEServing);
+ 		}
+ 	    }
  	    if (nr == 0)
  		continue;
  
***************
*** 1834,1841 ****
  
  			/* try to reopen line if specified at server startup
  			 */
! 			if ((fNoinit || fReopen) && !pCEServing->fup)
  			    ConsInit(pCEServing, &pGE->rinit, 0);
  
  			/* try for attach on new console
  			 */
--- 1805,1813 ----
  
  			/* try to reopen line if specified at server startup
  			 */
! 			if ((fNoinit || fReopen) && !pCEServing->fup) {
  			    ConsInit(pCEServing, &pGE->rinit, 0);
+ 			}
  
  			/* try for attach on new console
  			 */
***************
*** 2939,2942 ****
--- 2911,3100 ----
      fileClose(&ssocket);
      Error("internal flow error");
      exit(EX_UNAVAILABLE);
+ }
+ 
+ 
+ /* Do a little Telnet Protocol interpretation
+  * state = 0: normal
+  *       = 1: Saw a IAC char
+  *       = 2: Saw a DONT/DO/WONT/WILL command
+  *       = 5: Saw a \r
+  */
+ static int
+ DoTelnet(pCEServing, nr, acInOrig, acIn)
+ 	CONSENT *pCEServing;
+ 	int nr;
+ 	unsigned char acInOrig[];
+ 	unsigned char acIn[];
+ {
+ 	int i;
+ 	int new = 0;
+ 	int state;
+ 
+ 	state = pCEServing->telnetState;
+ 	for (i = 0; i < nr; ++i) {
+ 		if (state == 0 && acInOrig[i] == IAC) {
+ 			Debug(1, "%s: Got telnet `IAC'",
+ 			      pCEServing->server.string);
+ 			state = 1;
+ 		} else if (state == 1 && acInOrig[i] != IAC) {
+ 			Debug(1, "%s: Got telnet cmd `%u'",
+ 			      pCEServing->server.string, acInOrig[i]);
+ 			if (acInOrig[i] == DONT || acInOrig[i] == DO ||
+ 			    acInOrig[i] == WILL || acInOrig[i] == WONT) {
+ 				state = 2;
+ 			} else {
+ 				state = 0;
+ 			}
+ 		} else if (state == 2) {
+ 			Debug(1, "%s: Got telnet option `%u'",
+ 			      pCEServing->server.string, acInOrig[i]);
+ 			state = 0;
+ 		} else {
+ 			if (state == 5) {
+ 				state = 0;
+ 				if (acInOrig[i] == '\000') {
+ 					continue;
+ 				}
+ 			}
+ 			if (acInOrig[i] == IAC) {
+ 				Debug(1, "%s: Quoted `IAC'",
+ 				      pCEServing->server.string);
+ 			}
+ 			if (fStrip) {
+ 				acIn[new++] = acInOrig[i] & 127;
+ 			} else {
+ 				acIn[new++] = acInOrig[i];
+ 			}
+ 			if (acInOrig[i] == '\r') {
+ 				state = 5;
+ 			} else {
+ 				state = 0;
+ 			}
+ 		}
+ 	}
+ 	pCEServing->telnetState = state;
+ 
+ 	return new;
+ }
+ 
+ /*
+  * ConsChat() - run the chat program with ChatString
+  *
+  * returns true if the script succeeded (chat exit status of 0)
+  */
+ static int
+ ConsChat(pCE)
+ 	CONSENT *pCE;
+ {
+ 	int ConsTTY = pCE->fdtty;
+ 	int isNetworkConsole = pCE->isNetworkConsole;
+ 	char *ChatString = pCE->consoleChat.string;
+ 	int retries = 5;		/* try hard to fork() */
+ 	int cstatus;
+ 	int pid;
+ 	int fd;
+ 	char *ChatCommand;
+ 
+ 	(void) fflush(stdout);
+ 	(void) fflush(stderr);
+ 
+ 	/*
+ 	 * WARNING:  we cannot use vfork() due to the file descriptor tricks we
+ 	 * have to play in the child....
+ 	 */
+ 	while ((pid = fork()) < 0 && errno == EAGAIN && --retries >= 0) {
+ 		Debug(1, "ConsChat: fork() said EAGAIN!");
+ 		(void) sleep(2);	/* sleep a bit between retries */
+ 	}
+ 	switch (pid) {
+ 	case -1:
+ 		Error("ConsChat(): fork(): %s", strerror(errno));
+ 		break;
+ 	default:				/* PARENT */
+ 	    re_wait:
+ 		while (waitpid(pid, &cstatus, 0) < 0) {
+ 			if (errno == EINTR) {
+ 				continue;
+ 			}
+ 			Error("ConsChat: error waiting for chat process: %s",
+ 			      strerror(errno));
+ 		}
+ 		if (WIFEXITED(cstatus)) {
+ 			Debug((WEXITSTATUS(cstatus) == 0) ? 2 : 1,
+ 			      "ConsChat: chat exited on fd %d with status %d",
+ 			      ConsTTY, WEXITSTATUS(cstatus));
+ 		} else if (WIFSIGNALED(cstatus)) {
+ 			Error("ConsChat: chat died on fd %s with signal %d",
+ 			      ConsTTY, WTERMSIG(cstatus));
+ 		} else if (WIFSTOPPED(cstatus)) {
+ 			Error("ConsChat: chat stopped on fd %s with signal %d",
+ 			      ConsTTY, WSTOPSIG(cstatus));
+ 			kill(pid, SIGCONT);
+ 			goto re_wait;
+ 		} else {
+ 			Error("ConsChat: bad status from chat process: \\0%o", cstatus);
+ 		}
+ 		break;
+ 	case 0:					/* CHILD */
+ 		/*
+ 		 * This is rather sleazy, but is also what pppd does -- we use
+ 		 * the shell to break the chat string down into its separate
+ 		 * arguments....  (or a chat file could be specified)
+ 		 *
+ 		 * Note we do the malloc() in the child so we don't have to
+ 		 * worry about any memory leak....  :-)
+ 		 */
+ 		ChatCommand = malloc(strlen(PATH_CHAT) + sizeof(" -I -sSvV ") + strlen(ChatString));
+ 		if (!ChatCommand) {
+ 			Error("ConsChat: malloc() failed: %s", strerror(errno));
+ 			exit(1);
+ 		}
+ 		strcpy(ChatCommand, PATH_CHAT);
+ 		strcat(ChatCommand, isNetworkConsole ? " -I" : "");
+ 		strcat(ChatCommand, fVerbose ? " -sSvV " : " -sS ");
+ 		strcat(ChatCommand, ChatString);
+ 
+ 		Debug(2, "ConsChat: starting: /bin/sh -c '%s' on fd %d", ChatCommand, ConsTTY);
+ 		/*
+ 		 * if we want this to work properly with a real TTY then we
+ 		 * want the child process to become a session leader and to
+ 		 * establish the console tty as its controlling terminal....
+ 		 *
+ 		 * XXX is dup2() sufficient to trigger allocation of a
+ 		 * controlling terminal?
+ 		 */
+ 		setsid();
+ 		if (dup2(ConsTTY, STDIN_FILENO) != STDIN_FILENO) {
+ 			Error("ConsChat(): dup2(stdin): %s", strerror(errno));
+ 			exit(1);
+ 		}
+ 		if (dup2(ConsTTY, STDOUT_FILENO) != STDOUT_FILENO) {
+ 			Error("ConsChat(): dup2(stdout): %s", strerror(errno));
+ 			exit(1);
+ 		}
+ 		close(ConsTTY);			/* pedantic... */
+ 
+ #if 0 && defined(_POSIX_JOB_CONTROL) /* XXX we may need to call tcsetpgrp()? */
+ 		if (tcsetpgrp(0, getpid()) == -1) {
+ 			Error("ConsChat(): tcsetpgrp(stdin, getpid()): %s", strerror(errno));
+ 			exit(1);
+ 		}
+ #endif
+ 		/* close all fds > STDERR_FILENO */
+ 		for (fd = (STDERR_FILENO + 1); fd < getdtablesize(); fd++) {
+ 			(void) close(fd);
+ 		}
+ 		/* leave stderr alone -- it will be open to the log file */
+ 
+ 		execl("/bin/sh", "sh", "-c", ChatCommand, (char *) NULL);
+ 
+ 		Error("ConsChat(): execl(%s): %s", PATH_CHAT, strerror(errno));
+ 		exit(1);
+ 	}
+ 	if (WIFEXITED(cstatus)) {
+ 		return (WEXITSTATUS(cstatus) == 0);
+ 	}
+ 
+ 	return 0;
  }
Index: conserver/consent.h
===================================================================
RCS file: /cvs/misc/conserver/conserver/consent.h,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 consent.h
*** conserver/consent.h	24 Apr 2002 21:42:00 -0000	1.1.1.3
--- conserver/consent.h	1 May 2002 23:22:55 -0000
***************
*** 62,74 ****
      int mark;			/* Mark (chime) interval                */
      long nextMark;		/* Next mark (chime) time               */
      short int breakType;	/* break type [1-9]                     */
!     int autoReUp;
  
      /* Used if network console */
!     int isNetworkConsole;
!     STRING networkConsoleHost;
!     int networkConsolePort;
!     int telnetState;
  
      /* used if virtual console */
      STRING acslave;		/* pseudo-device slave side             */
--- 62,76 ----
      int mark;			/* Mark (chime) interval                */
      long nextMark;		/* Next mark (chime) time               */
      short int breakType;	/* break type [1-9]                     */
!     int autoReUp;		/* ???? */
!     STRING consoleChat;		/* chat script to run on Init */
!     int doneChat;		/* have we run the chat script? */
  
      /* Used if network console */
!     int isNetworkConsole;	/* flag identifying this as a network connected console */
!     STRING networkConsoleHost;	/* hostname where network connection is made */
!     int networkConsolePort;	/* TCP port number to connect to on host */
!     int telnetState;		/* telnet protocol state machinery state */
  
      /* used if virtual console */
      STRING acslave;		/* pseudo-device slave side             */
***************
*** 103,109 ****
  extern void ConsDown(CONSENT *, fd_set *);
  extern int CheckHostCache(const char *);
  extern void AddHostCache(const char *);
! extern void ClearHostCache();
  #else
  extern PARITY *FindParity();
  extern BAUD *FindBaud();
--- 105,111 ----
  extern void ConsDown(CONSENT *, fd_set *);
  extern int CheckHostCache(const char *);
  extern void AddHostCache(const char *);
! extern void ClearHostCache(void);
  #else
  extern PARITY *FindParity();
  extern BAUD *FindBaud();
Index: conserver/consent.c
===================================================================
RCS file: /cvs/misc/conserver/conserver/consent.c,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 consent.c
*** conserver/consent.c	24 Apr 2002 21:42:00 -0000	1.1.1.3
--- conserver/consent.c	1 May 2002 23:30:13 -0000
***************
*** 48,53 ****
--- 48,54 ----
  #include <netinet/in.h>
  #include <netdb.h>
  #include <stdio.h>
+ #include <unistd.h>
  #include <ctype.h>
  #include <signal.h>
  #include <pwd.h>
***************
*** 124,133 ****
      ,				/* even                 */
      {'m', PARENB | CS7 | PARODD | PAREXT, 0}
      ,				/* mark                 */
      {'o', PARENB | CS7 | PARODD, 0}
      ,				/* odd                  */
      {'p', CS8, 0}
!     ,				/* pass 8 bits, no parity */
      {'s', PARENB | CS7 | PAREXT, 0}
      ,				/* space                */
  #else				/* ! HAVE_TERMIOS_H */
--- 125,136 ----
      ,				/* even                 */
      {'m', PARENB | CS7 | PARODD | PAREXT, 0}
      ,				/* mark                 */
+     {'n', CS8, 0}
+     ,				/* pass 8 bits, no parity */
      {'o', PARENB | CS7 | PARODD, 0}
      ,				/* odd                  */
      {'p', CS8, 0}
!     ,				/* pass 8 bits, no parity, old form */
      {'s', PARENB | CS7 | PAREXT, 0}
      ,				/* space                */
  #else				/* ! HAVE_TERMIOS_H */
***************
*** 139,144 ****
--- 142,149 ----
      ,				/* odd                                  */
  # if defined(PASS8)
      {'p', PASS8, EVENP | ODDP}
+     ,				/* pass 8 bits, no parity, old form     */
+     {'n', PASS8, EVENP | ODDP}
      ,				/* pass 8 bits, no parity               */
  # endif
      {'s', 0, EVENP | ODDP}	/* space                                */
***************
*** 649,655 ****
  
  void
  #if USE_ANSI_PROTO
! ClearHostCache()
  #else
  ClearHostCache()
  #endif
--- 654,660 ----
  
  void
  #if USE_ANSI_PROTO
! ClearHostCache(void)
  #else
  ClearHostCache()
  #endif
***************
*** 832,845 ****
  		return;
  	    }
  	}
- 
- # if POKE_ANNEX
- 	/*
- 	 * Poke the connection to get the annex to wake up and
- 	 * register this connection.
- 	 */
- 	write(pCE->fdtty, "\r\n", 2);
- # endif
      } else if (-1 ==
  	       (pCE->fdtty =
  		open(pCE->dfile.string, O_RDWR | O_NDELAY, 0600))) {
--- 837,842 ----
***************
*** 848,853 ****
--- 845,853 ----
  	return;
      }
      FD_SET(pCE->fdtty, pfdSet);
+ 
+     /* we'll need to re-run the chat script.... */
+     pCE->doneChat = 0;
  
      /* ok, now setup the device
       */
Index: configure.in
===================================================================
RCS file: /cvs/misc/conserver/configure.in,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 configure.in
*** configure.in	24 Apr 2002 21:41:59 -0000	1.1.1.3
--- configure.in	2 May 2002 01:22:42 -0000
***************
*** 75,80 ****
--- 75,93 ----
  	[AC_DEFINE_UNQUOTED(CONFIGFILE, "conserver.cf")
  	AC_MSG_RESULT('$sysconfdir/conserver.cf')])
  
+ AC_MSG_CHECKING(for chat-script filename)
+ AC_ARG_WITH(chatfile,
+ 	AC_HELP_STRING([--with-chatfile=CHATFILE],[Specify chat-script filename @<:@conserver.chat@:>@]),
+ 	[if test "$withval" != yes; then
+ 		AC_DEFINE_UNQUOTED(CHATFILE, "$withval")
+ 		AC_MSG_RESULT('$sysconfdir/$withval')
+ 	else
+ 		AC_DEFINE_UNQUOTED(CHATFILE, "conserver.chat")
+ 		AC_MSG_RESULT('$sysconfdir/conserver.chat')
+ 	fi],
+ 	[AC_DEFINE_UNQUOTED(CHATFILE, "conserver.chat")
+ 	AC_MSG_RESULT('$sysconfdir/conserver.chat')])
+ 
  AC_MSG_CHECKING(for password filename)
  AC_ARG_WITH(pwdfile,
  	AC_HELP_STRING([--with-pwdfile=PWDFILE],[Specify password filename @<:@conserver.passwd@:>@]),
***************
*** 140,145 ****
--- 153,171 ----
  	[AC_DEFINE_UNQUOTED(CONNECTTIMEOUT, 10)
  	AC_MSG_RESULT(10)])
  
+ AC_MSG_CHECKING(for chat program)
+ AC_ARG_WITH(chat,
+ 	AC_HELP_STRING([--with-chat=/path/to/chat],[Specify pathname to chat program @<:@chat@:>@]),
+ 	[if test "$withval" != yes; then
+ 		AC_DEFINE_UNQUOTED(PATH_CHAT, "$withval")
+ 		AC_MSG_RESULT('$withval')
+ 	else
+ 		AC_DEFINE_UNQUOTED(PATH_CHAT, "chat")
+ 		AC_MSG_RESULT('chat')
+ 	fi],
+ 	[AC_DEFINE_UNQUOTED(PATH_CHAT, "chat")
+ 	AC_MSG_RESULT('chat')])
+ 
  dnl ### Check for compiler et al. ###################################
  AC_PROG_CC
  AC_PROG_INSTALL
***************
*** 294,298 ****
  
  dnl ### Create output files. #######################################
  AC_SUBST(LIBOBJS)
! AC_CONFIG_FILES([Makefile conserver/Makefile conserver.cf/Makefile console/Makefile autologin/Makefile])
  AC_OUTPUT
--- 320,324 ----
  
  dnl ### Create output files. #######################################
  AC_SUBST(LIBOBJS)
! AC_CONFIG_FILES([Makefile chat/Makefile conserver/Makefile conserver.cf/Makefile console/Makefile autologin/Makefile])
  AC_OUTPUT
Index: acconfig.h
===================================================================
RCS file: /cvs/misc/conserver/acconfig.h,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 acconfig.h
*** acconfig.h	24 Apr 2002 21:41:58 -0000	1.1.1.3
--- acconfig.h	1 May 2002 19:38:59 -0000
***************
*** 24,29 ****
--- 24,34 ----
  #undef CONFIGFILE
  
  /*
+  * Config file path
+  */
+ #undef CHATFILE
+ 
+ /*
   * Password file path
   */
  #undef PASSWDFILE
***************
*** 32,37 ****
--- 37,47 ----
   * Logfile path
   */
  #undef LOGFILEPATH
+ 
+ /*
+  * path to chat program
+  */
+ #undef PATH_CHAT
  
  /*
   * Number of consoles per child process
Index: Makefile.in
===================================================================
RCS file: /cvs/misc/conserver/Makefile.in,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 Makefile.in
*** Makefile.in	5 Dec 2001 20:36:45 -0000	1.1.1.1
--- Makefile.in	2 May 2002 01:22:25 -0000
***************
*** 12,18 ****
  
  ### Makefile rules - no user-servicable parts below
  
! SUBDIRS = conserver console conserver.cf
  
  all:
  	for n in $(SUBDIRS); do \
--- 12,18 ----
  
  ### Makefile rules - no user-servicable parts below
  
! SUBDIRS = chat conserver console conserver.cf
  
  all:
  	for n in $(SUBDIRS); do \
cvs diff: Diffing chat
Index: chat/Makefile.in
===================================================================
RCS file: chat/Makefile.in
diff -N chat/Makefile.in
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- chat/Makefile.in	2 May 2002 01:21:07 -0000
***************
*** 0 ****
--- 1,52 ----
+ ### Path settings
+ srcdir = @srcdir@
+ top_srcdir = @top_srcdir@
+ prefix = @prefix@
+ exec_prefix = @exec_prefix@
+ bindir = @bindir@
+ sysconfdir = @sysconfdir@
+ mandir = @mandir@
+ 
+ ### Installation programs and flags
+ INSTALL = @INSTALL@
+ INSTALL_PROGRAM = @INSTALL_PROGRAM@ -s
+ LN_S = @LN_S@
+ MKDIR = @MKDIR@
+ 
+ ### Compiler and link options
+ CC	= @CC@
+ CFLAGS	= @CFLAGS@
+ DEFS	= @DEFS@
+ CPPFLAGS = -I$(top_srcdir) -I$(srcdir) $(DEFS) @CPPFLAGS@
+ LDFLAGS	= @LDFLAGS@
+ LIBS	= @LIBS@
+ @SET_MAKE@
+ 
+ 
+ ### Makefile rules - no user-servicable parts below
+ 
+ CHAT_OBJS = chat.o
+ CHAT_HDRS = ../config.h
+ ALL = chat
+ 
+ all: $(ALL)
+ 
+ chat: $(CHAT_OBJS)
+ 	$(CC) $(CFLAGS) $(LDFLAGS) -o chat $(CHAT_OBJS) $(LIBS)
+ 
+ .c.o:
+ 	$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
+ 
+ clean:
+ 	rm -f *~ *.o $(ALL) core
+ 
+ distclean: clean
+ 	rm -f Makefile
+ 
+ install: chat
+ 	$(MKDIR) $(DESTDIR)$(bindir)
+ 	$(INSTALL_PROGRAM) chat $(DESTDIR)$(bindir)
+ 	$(MKDIR) $(DESTDIR)$(mandir)/man1
+ 	$(INSTALL) chat.man $(DESTDIR)$(mandir)/man1/chat.1
+ 
+ .PHONY: clean distclean install
Index: chat/chat.c
===================================================================
RCS file: chat/chat.c
diff -N chat/chat.c
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- chat/chat.c	2 May 2002 01:16:33 -0000
***************
*** 0 ****
--- 1,1754 ----
+ /*
+  *	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.
+  *
+  * -----------------
+  *	22-May-99 added environment substitutuion, enabled with -E switch.
+  *	Andreas Arens <andras@cityweb.de>.
+  *
+  *	12-May-99 added a feature to read data to be sent from a file,
+  *	if the send string starts with @.  Idea from gpk <gpk@onramp.net>.
+  *
+  *	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>
+  *
+  *
+  *	The original author is:
+  *
+  *		Karl Fox <karl@MorningStar.Com>
+  *		Morning Star Technologies, Inc.
+  *		1760 Zollinger Road
+  *		Columbus, OH  43221
+  *		(614)451-1883
+  *
+  */
+ 
+ #ifndef __STDC__
+ #define const
+ #endif
+ 
+ #include <sys/cdefs.h>
+ #ifndef lint
+ static const char rcsid[] = "Id: chat.c,v 1.26 1999/12/23 01:39:54 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>
+ 
+ #ifndef TERMIO
+ #undef	TERMIOS
+ #define TERMIOS
+ #endif
+ 
+ #ifdef TERMIO
+ #include <termio.h>
+ #endif
+ #ifdef TERMIOS
+ #include <termios.h>
+ #endif
+ 
+ #define	STR_LEN	1024
+ 
+ #ifndef SIGTYPE
+ #define SIGTYPE void
+ #endif
+ 
+ #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
+ 
+ #ifdef SUNOS
+ extern int sys_nerr;
+ extern char *sys_errlist[];
+ #define memmove(to, from, n)	bcopy(from, to, n)
+ #define strerror(n)		((unsigned)(n) < sys_nerr? sys_errlist[(n)] :\
+ 				 "unknown error")
+ #endif
+ 
+ char *program_name;
+ 
+ #define	MAX_ABORTS		50
+ #define	MAX_REPORTS		50
+ #define	DEFAULT_CHAT_TIMEOUT	45
+ 
+ int echo          = 0;
+ int verbose       = 0;
+ int to_log        = 1;
+ int to_stderr     = 0;
+ int Verbose       = 0;
+ int quiet         = 0;
+ int report        = 0;
+ int use_env       = 0;
+ int exit_code     = 0;
+ int do_set_tty    = 1;
+ 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, 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 *fmt, ...));
+ void fatal __P((int code, const char *fmt, ...));
+ SIGTYPE sigalrm __P((int signo));
+ SIGTYPE sigint __P((int signo));
+ SIGTYPE sigterm __P((int signo));
+ 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));
+ int  get_string __P((register char *string));
+ int  put_string __P((register char *s));
+ int  write_char __P((int c));
+ int  put_char __P((int c));
+ int  get_char __P((void));
+ void chat_send __P((register char *s));
+ char *character __P((int c));
+ void chat_expect __P((register char *s));
+ char *clean __P((register char *s, int sending));
+ void break_sequence __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(2, "memory error!");
+ 
+     memcpy (ans, b, c);
+     return ans;
+ }
+ 
+ void *copy_of (s)
+ char *s;
+ {
+     return dup_mem (s, strlen (s) + 1);
+ }
+ 
+ /*
+  * chat [ -eEIsSvV ] [ -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 option;
+     int i;
+ 
+     program_name = *argv;
+     tzset();
+ 
+     while ((option = getopt(argc, argv, ":eEf:Ir:sSt:T:U:vV")) != -1) {
+ 	switch (option) {
+ 	case 'e':
+ 	    ++echo;
+ 	    break;
+ 
+ 	case 'E':
+ 	    ++use_env;
+ 	    break;
+ 
+ 	case 'v':
+ 	    ++verbose;
+ 	    break;
+ 
+ 	case 'V':
+ 	    ++Verbose;
+ 	    break;
+ 
+ 	case 's':
+ 	    ++to_stderr;
+ 	    break;
+ 
+ 	case 'S':
+ 	    to_log = 0;
+ 	    break;
+ 
+ 	case 'f':
+ 	    if (optarg != NULL)
+ 		    chat_file = copy_of(optarg);
+ 	    else
+ 		usage();
+ 	    break;
+ 
+ 	case 't':
+ 	    if (optarg != NULL)
+ 		timeout = atoi(optarg);
+ 	    else
+ 		usage();
+ 	    break;
+ 
+ 	case 'I':
+ 	    do_set_tty = 0;
+ 	    break;
+ 
+ 	case 'r':
+ 	    if (optarg) {
+ 		if (report_fp != NULL)
+ 		    fclose (report_fp);
+ 		report_file = copy_of (optarg);
+ 		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 (optarg != NULL)
+ 		phone_num = copy_of(optarg);
+ 	    else
+ 		usage();
+ 	    break;
+ 
+ 	case 'U':
+ 	    if (optarg != NULL)
+ 		phone_num2 = copy_of(optarg);
+ 	    else
+ 		usage();
+ 	    break;
+ 
+ 	default:
+ 	    usage();
+ 	    break;
+ 	}
+     }
+     argc -= optind;
+     argv += optind;
+ /*
+  * Default the report file to the stderr location
+  */
+     if (report_fp == NULL)
+ 	report_fp = stderr;
+ 
+     if (to_log) {
+ #ifdef ultrix
+ 	openlog("chat", LOG_PID);
+ #else
+ 	openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
+ 
+ 	if (verbose)
+ 	    setlogmask(LOG_UPTO(LOG_INFO));
+ 	else
+ 	    setlogmask(LOG_UPTO(LOG_WARNING));
+ #endif
+     }
+ 
+     init();
+     
+     if (chat_file != NULL) {
+ 	if (argc)
+ 	    usage();
+ 	else
+ 	    do_file (chat_file);
+     } else {
+ 	for (i = 0; i < argc; i++) {
+ 	    chat_expect(argv[i]);
+ 	    if (++i < argc)
+ 		chat_send(argv[i]);
+ 	}
+     }
+ 
+     terminate(0);
+     return 0;
+ }
+ 
+ /*
+  *  Process a chat script when read from a file.
+  */
+ 
+ void do_file (chat_file)
+ char *chat_file;
+ {
+     int linect, sendflg;
+     char *sp, *arg, quote;
+     char buf [STR_LEN];
+     FILE *cfp;
+ 
+     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) {
+ 	sp = strchr (buf, '\n');
+ 	if (sp)
+ 	    *sp = '\0';
+ 
+ 	linect++;
+ 	sp = buf;
+ 
+         /* 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 == '\'') {
+ 		quote = *sp++;
+ 		arg = sp;
+ 		while (*sp != quote) {
+ 		    if (*sp == '\0')
+ 			fatal(1, "unterminated quote (line %d)", linect);
+ 
+ 		    if (*sp++ == '\\') {
+ 			if (*sp != '\0')
+ 			    ++sp;
+ 		    }
+ 		}
+ 	    }
+ 	    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);
+ }
+ 
+ /*
+  *	We got an error parsing the command line.
+  */
+ void usage()
+ {
+     fprintf(stderr, "\
+ Usage: %s [-eEIsSvV] [-t timeout] [-r report-file]\n\
+        [-T phone-number] [-U phone-number2]\n\
+        {-f chat-file | chat-script}\n\
+ Chat-Script: [...[[expect[-say[-expect...]] say expect[-say[-expect]] ...]]]\n",
+ 	    program_name);
+     exit(1);
+ }
+ 
+ char line[1024];
+ 
+ /*
+  * Send a message to syslog and/or stderr.
+  */
+ void logf __V((const char *fmt, ...))
+ {
+     va_list args;
+ 
+ #ifdef __STDC__
+     va_start(args, fmt);
+ #else
+     char *fmt;
+     va_start(args);
+     fmt = va_arg(args, char *);
+ #endif
+ 
+     vfmtmsg(line, sizeof(line), fmt, args);
+     if (to_log)
+ 	syslog(LOG_INFO, "%s", line);
+     if (to_stderr)
+ 	fprintf(stderr, "%s\n", line);
+ }
+ 
+ /*
+  *	Print an error message and terminate.
+  */
+ 
+ void fatal __V((int code, const char *fmt, ...))
+ {
+     va_list args;
+ 
+ #ifdef __STDC__
+     va_start(args, fmt);
+ #else
+     int code;
+     char *fmt;
+     va_start(args);
+     code = va_arg(args, int);
+     fmt = va_arg(args, char *);
+ #endif
+ 
+     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() */
+ 
+     if ((flags = fcntl(0, F_GETFL, 0)) == -1)
+ 	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)
+ 	logf("alarm");
+ }
+ 
+ void unalarm()
+ {
+     int flags;
+ 
+     if ((flags = fcntl(0, F_GETFL, 0)) == -1)
+ 	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(2, "SIGINT");
+ }
+ 
+ SIGTYPE sigterm(signo)
+ int signo;
+ {
+     fatal(2, "SIGTERM");
+ }
+ 
+ SIGTYPE sighup(signo)
+ int signo;
+ {
+     fatal(2, "SIGHUP");
+ }
+ 
+ void init()
+ {
+     signal(SIGINT, sigint);
+     signal(SIGTERM, sigterm);
+     signal(SIGHUP, sighup);
+ 
+     set_tty_parameters();
+     signal(SIGALRM, sigalrm);
+     alarm(0);
+     alarmed = 0;
+ }
+ 
+ void set_tty_parameters()
+ {
+     term_parms t;
+ 
+     if (!do_set_tty)
+ 	return;
+ 
+ #if defined(get_term_param)
+     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     |= OPOST | ONLCR;
+     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;
+ {
+     static int terminating = 0;
+ 
+     if (terminating)
+ 	exit(status);
+     terminating = 1;
+     echo_stderr(-1);
+ /*
+  * 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 (report_file != (char *) 0 && report_fp != (FILE *) NULL) {
+ 	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
+ 
+     exit(status);
+ }
+ 
+ /*
+  *	'Clean up' this string.
+  */
+ char *clean(s, sending)
+ register char *s;
+ int sending;  /* set to 1 when sending (putting) this string. */
+ {
+     char temp[STR_LEN], env_str[STR_LEN], cur_chr;
+     register char *s1, *phchar;
+     int add_return = sending;
+ #define isoctal(chr)	(((chr) >= '0') && ((chr) <= '7'))
+ #define isalnumx(chr)	((((chr) >= '0') && ((chr) <= '9')) \
+ 			 || (((chr) >= 'a') && ((chr) <= 'z')) \
+ 			 || (((chr) >= 'A') && ((chr) <= 'Z')) \
+ 			 || (chr) == '_')
+ 
+     s1 = temp;
+     while (*s) {
+ 	cur_chr = *s++;
+ 	if (cur_chr == '^') {
+ 	    cur_chr = *s++;
+ 	    if (cur_chr == '\0') {
+ 		*s1++ = '^';
+ 		break;
+ 	    }
+ 	    cur_chr &= 0x1F;
+ 	    if (cur_chr != 0) {
+ 		*s1++ = cur_chr;
+ 	    }
+ 	    continue;
+ 	}
+ 	
+ 	if (use_env && cur_chr == '$') {		/* ARI */
+ 	    phchar = env_str;
+ 	    while (isalnumx(*s))
+ 		*phchar++ = *s++;
+ 	    *phchar = '\0';
+ 	    phchar = getenv(env_str);
+ 	    if (phchar)
+ 		while (*phchar)
+ 		    *s1++ = *phchar++;
+ 	    continue;
+ 	}
+ 
+ 	if (cur_chr != '\\') {
+ 	    *s1++ = cur_chr;
+ 	    continue;
+ 	}
+ 
+ 	cur_chr = *s++;
+ 	if (cur_chr == '\0') {
+ 	    if (sending) {
+ 		*s1++ = '\\';
+ 		*s1++ = '\\';
+ 	    }
+ 	    break;
+ 	}
+ 
+ 	switch (cur_chr) {
+ 	case 'b':
+ 	    *s1++ = '\b';
+ 	    break;
+ 
+ 	case 'c':
+ 	    if (sending && *s == '\0')
+ 		add_return = 0;
+ 	    else
+ 		*s1++ = cur_chr;
+ 	    break;
+ 
+ 	case '\\':
+ 	case 'K':
+ 	case 'p':
+ 	case 'd':
+ 	    if (sending)
+ 		*s1++ = '\\';
+ 	    *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 = 1;
+ 	    break;
+ 
+ 	case 'r':
+ 	    *s1++ = '\r';
+ 	    break;
+ 
+ 	case 'n':
+ 	    *s1++ = '\n';
+ 	    break;
+ 
+ 	case 's':
+ 	    *s1++ = ' ';
+ 	    break;
+ 
+ 	case 't':
+ 	    *s1++ = '\t';
+ 	    break;
+ 
+ 	case 'N':
+ 	    if (sending) {
+ 		*s1++ = '\\';
+ 		*s1++ = '\0';
+ 	    }
+ 	    else
+ 		*s1++ = 'N';
+ 	    break;
+ 	    
+ 	case '$':			/* ARI */
+ 	    if (use_env) {
+ 		*s1++ = cur_chr;
+ 		break;
+ 	    }
+ 	    /* FALL THROUGH */
+ 
+ 	default:
+ 	    if (isoctal (cur_chr)) {
+ 		cur_chr &= 0x07;
+ 		if (isoctal (*s)) {
+ 		    cur_chr <<= 3;
+ 		    cur_chr |= *s++ - '0';
+ 		    if (isoctal (*s)) {
+ 			cur_chr <<= 3;
+ 			cur_chr |= *s++ - '0';
+ 		    }
+ 		}
+ 
+ 		if (cur_chr != 0 || sending) {
+ 		    if (sending && (cur_chr == '\\' || cur_chr == 0))
+ 			*s1++ = '\\';
+ 		    *s1++ = cur_chr;
+ 		}
+ 		break;
+ 	    }
+ 
+ 	    if (sending)
+ 		*s1++ = '\\';
+ 	    *s1++ = cur_chr;
+ 	    break;
+ 	}
+     }
+ 
+     if (add_return)
+ 	*s1++ = '\r';
+ 
+     *s1++ = '\0'; /* guarantee closure */
+     *s1++ = '\0'; /* terminate the string */
+     return dup_mem (temp, (size_t) (s1 - temp)); /* may have embedded nuls */
+ }
+ 
+ /*
+  * A modified version of 'strtok'. This version skips \ sequences.
+  */
+ 
+ char *expect_strtok (s, term)
+      char *s, *term;
+ {
+     static  char *str   = "";
+     int	    escape_flag = 0;
+     char   *result;
+ 
+ /*
+  * If a string was specified then do initial processing.
+  */
+     if (s)
+ 	str = s;
+ 
+ /*
+  * If this is the escape flag then reset it and ignore the character.
+  */
+     if (*str)
+ 	result = str;
+     else
+ 	result = (char *) 0;
+ 
+     while (*str) {
+ 	if (escape_flag) {
+ 	    escape_flag = 0;
+ 	    ++str;
+ 	    continue;
+ 	}
+ 
+ 	if (*str == '\\') {
+ 	    ++str;
+ 	    escape_flag = 1;
+ 	    continue;
+ 	}
+ 
+ /*
+  * If this is not in the termination string, continue.
+  */
+ 	if (strchr (term, *str) == (char *) 0) {
+ 	    ++str;
+ 	    continue;
+ 	}
+ 
+ /*
+  * This is the terminator. Mark the end of the string and stop.
+  */
+ 	*str++ = '\0';
+ 	break;
+     }
+     return (result);
+ }
+ 
+ /*
+  * 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;
+ 
+     meta = (c & 0x80) ? "M-" : "";
+     c &= 0x7F;
+ 
+     if (c < 32)
+ 	sprintf(string, "%s^%c", meta, (int)c + '@');
+     else if (c == 127)
+ 	sprintf(string, "%s^?", meta);
+     else
+ 	sprintf(string, "%s%c", meta, c);
+ 
+     return (string);
+ }
+ 
+ /*
+  *  process the reply string
+  */
+ void chat_send (s)
+ register char *s;
+ {
+     char file_data[STR_LEN];
+ 
+     if (say_next) {
+ 	say_next = 0;
+ 	s = clean(s, 1);
+ 	write(2, s, strlen(s));
+         free(s);
+ 	return;
+     }
+ 
+     if (hup_next) {
+         hup_next = 0;
+ 	if (strcmp(s, "OFF") == 0)
+            signal(SIGHUP, SIG_IGN);
+         else
+            signal(SIGHUP, sighup);
+         return;
+     }
+ 
+     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) + 1 > sizeof(fail_buffer))
+ 	    fatal(1, "Illegal or too-long ABORT string ('%v')", s);
+ 
+ 	abort_string[n_aborts++] = s1;
+ 
+ 	if (verbose)
+ 	    logf("abort on (%v)", s);
+ 	return;
+     }
+ 
+     if (clear_abort_next) {
+ 	char *s1;
+ 	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);
+ 	    }
+ 	}
+         free(s1);
+ 	if (pack)
+ 	    pack_array(abort_string,old_max);
+ 	return;
+     }
+ 
+     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 (clear_report_next) {
+ 	char *s1;
+ 	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;
+     }
+ 
+     /*
+      * The syntax @filename means read the string to send from the
+      * file `filename'.
+      */
+     if (s[0] == '@') {
+ 	/* skip the @ and any following white-space */
+ 	char *fn = s;
+ 	while (*++fn == ' ' || *fn == '\t')
+ 	    ;
+ 
+ 	if (*fn != 0) {
+ 	    FILE *f;
+ 	    int n = 0;
+ 
+ 	    /* open the file and read until STR_LEN-1 bytes or end-of-file */
+ 	    f = fopen(fn, "r");
+ 	    if (f == NULL)
+ 		fatal(1, "%s -- open failed: %m", fn);
+ 	    while (n < STR_LEN - 1) {
+ 		int nr = fread(&file_data[n], 1, STR_LEN - 1 - n, f);
+ 		if (nr < 0)
+ 		    fatal(1, "%s -- read error", fn);
+ 		if (nr == 0)
+ 		    break;
+ 		n += nr;
+ 	    }
+ 	    fclose(f);
+ 
+ 	    /* use the string we got as the string to send,
+ 	       but trim off the final newline if any. */
+ 	    if (n > 0 && file_data[n-1] == '\n')
+ 		--n;
+ 	    file_data[n] = 0;
+ 	    s = file_data;
+ 	}
+     }
+ 
+     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);
+ 
+     default:
+ 	logf("warning: read() on stdin returned %d", status);
+ 
+     case -1:
+ 	if ((status = fcntl(0, F_GETFL, 0)) == -1)
+ 	    fatal(2, "Can't get file mode flags on stdin: %m");
+ 
+ 	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;
+ 
+     usleep(10000);		/* inter-character typing delay (?) */
+ 
+     status = write(1, &ch, 1);
+ 
+     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");
+ 
+ 	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) {
+ 	alarm(0);
+ 	alarmed = 0;
+ 
+ 	if (verbose) {
+ 	    if (errno == EINTR || errno == EWOULDBLOCK)
+ 		logf(" -- write timed out");
+ 	    else
+ 		logf(" -- write failed: %m");
+ 	}
+ 	return (0);
+     }
+     return (1);
+ }
+ 
+ int put_string (s)
+ register char *s;
+ {
+     quiet = 0;
+     s = clean(s, 1);
+ 
+     if (verbose) {
+ 	if (quiet)
+ 	    logf("send (?????\?)");	/* backslash to avoid trigraph ??) */
+ 	else
+ 	    logf("send (%v)", s);
+     }
+ 
+     alarm(timeout); alarmed = 0;
+ 
+     while (*s) {
+ 	register char c = *s++;
+ 
+ 	if (c != '\\') {
+ 	    if (!write_char (c))
+ 		return 0;
+ 	    continue;
+ 	}
+ 
+ 	c = *s++;
+ 	switch (c) {
+ 	case 'd':
+ 	    sleep(1);
+ 	    break;
+ 
+ 	case 'K':
+ 	    break_sequence();
+ 	    break;
+ 
+ 	case 'p':
+ 	    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);
+     len = strlen(string);
+     minlen = (len > sizeof(fail_buffer)? len: sizeof(fail_buffer)) - 1;
+ 
+     if (verbose)
+ 	logf("expect (%v)", string);
+ 
+     if (len > STR_LEN) {
+ 	logf("expect string is too long");
+ 	exit_code = 1;
+ 	return 0;
+     }
+ 
+     if (len == 0) {
+ 	if (verbose)
+ 	    logf("got it");
+ 	return (1);
+     }
+ 
+     alarm(timeout);
+     alarmed = 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;
+ 	}
+ 
+ 	*s++ = c;
+ 
+ 	if (verbose && s >= logged + 80) {
+ 	    logf("%0.*v", s - logged, logged);
+ 	    logged = s;
+ 	}
+ 
+ 	if (Verbose) {
+ 	   if (c == '\n')
+ 	       fputc( '\n', stderr );
+ 	   else if (c != '\r')
+ 	       fprintf( stderr, "%s", character(c) );
+ 	}
+ 
+ 	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);
+ 
+ 		    strftime (report_buffer, 20, "%b %d %H:%M:%S ", tm_now);
+ 		    strcat (report_buffer, report_string[n]);
+ 
+ 		    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) {
+ 		if (s > logged)
+ 		    logf("%0.*v", s - logged, logged);
+ 		logf(" -- got it\n");
+ 	    }
+ 
+ 	    alarm(0);
+ 	    alarmed = 0;
+ 	    return (1);
+ 	}
+ 
+ 	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) {
+ 		    if (s > logged)
+ 			logf("%0.*v", s - logged, logged);
+ 		    logf(" -- failed");
+ 		}
+ 
+ 		alarm(0);
+ 		alarmed = 0;
+ 		exit_code = n + 4;
+ 		strcpy(fail_reason = fail_buffer, abort_string[n]);
+ 		return (0);
+ 	    }
+ 	}
+ 
+ 	if (s >= end) {
+ 	    if (logged < s - minlen) {
+ 		if (verbose)
+ 		    logf("%0.*v", s - logged, logged);
+ 		logged = s;
+ 	    }
+ 	    s -= minlen;
+ 	    memmove(temp, s, minlen);
+ 	    logged = temp + (logged - s);
+ 	    s = temp + minlen;
+ 	}
+ 
+ 	if (alarmed && verbose)
+ 	    logf("warning: alarm synchronization problem");
+     }
+ 
+     alarm(0);
+     
+     if (verbose && printed) {
+ 	if (alarmed)
+ 	    logf(" -- read timed out");
+ 	else
+ 	    logf(" -- read failed: %m");
+     }
+ 
+     exit_code = 3;
+     alarmed   = 0;
+     return (0);
+ }
+ 
+ /*
+  * Gross kludge to handle Solaris versions >= 2.6 having usleep.
+  */
+ #ifdef SOL2
+ #include <sys/param.h>
+ #if MAXUID > 65536		/* then this is Solaris 2.6 or later */
+ #undef NO_USLEEP
+ #endif
+ #endif /* SOL2 */
+ 
+ #ifdef NO_USLEEP
+ #include <sys/types.h>
+ #include <sys/time.h>
+ 
+ /*
+   usleep -- support routine for 4.2BSD system call emulations
+   last edit:  29-Oct-1984     D A Gwyn
+   */
+ 
+ extern int	  select();
+ 
+ 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 */
+ 
+     delay.tv_sec  = usec / 1000000L;
+     delay.tv_usec = usec % 1000000L;
+ 
+     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;
+ 	}
+     }
+ }
+ 
+ /*
+  * 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.
+  */
+ #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];
+     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;
+ }
Index: chat/chat.man
===================================================================
RCS file: chat/chat.man
diff -N chat/chat.man
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- chat/chat.man	2 May 2002 05:13:37 -0000
***************
*** 0 ****
--- 1,511 ----
+ .\" -*- nroff -*-
+ .\" manual page [] for chat 1.8
+ .\" Id: chat.8,v 1.9 1999/09/06 05:10:23 paulus Exp
+ .\" SH section heading
+ .\" SS subsection heading
+ .\" LP paragraph
+ .\" IP indented paragraph
+ .\" TP hanging label
+ .TH CHAT 8 "22 May 1999" "Chat Version 1.22"
+ .SH "NAME"
+ chat \- Automated conversational script with a modem
+ .SH "SYNOPSIS"
+ .B chat
+ [
+ .I options
+ ]
+ .I script
+ .SH "DESCRIPTION"
+ .LP
+ The \fIchat\fR program defines a conversational exchange between the
+ computer and the modem. Its primary purpose is to establish the
+ connection between the Point-to-Point Protocol Daemon (\fIpppd\fR) and
+ the remote's \fIpppd\fR process.
+ .SH "OPTIONS"
+ .TP
+ .B -f \fI<chat file>
+ Read the chat script from the chat \fIfile\fR. The use of this option
+ is mutually exclusive with the chat script parameters. The user must
+ have read access to the file. Multiple lines are permitted in the
+ file. Space or horizontal tab characters should be used to separate
+ the strings.
+ .TP
+ .B -t \fI<timeout>
+ Set the timeout for the expected string to be received. If the string
+ is not received within the time limit then the reply string is not
+ sent. An alternate reply may be sent or the script will fail if there
+ is no alternate reply string. A failed script will cause the
+ \fIchat\fR program to terminate with a non-zero error code.
+ .TP
+ .B -r \fI<report file>
+ Set the file for output of the report strings. If you use the keyword
+ \fIREPORT\fR, the resulting strings are written to this file. If this
+ option is not used and you still use \fIREPORT\fR keywords, the
+ \fIstderr\fR file is used for the report strings.
+ .TP
+ .B -e
+ Start with the echo option turned on. Echoing may also be turned on
+ or off at specific points in the chat script by using the \fIECHO\fR
+ keyword. When echoing is enabled, all output from the modem is echoed
+ to \fIstderr\fR.
+ .TP
+ .B -E
+ Enables environment variable substituion within chat scripts using the
+ standard \fI$xxx\fR syntax.
+ .TP
+ .B -v
+ Request that the \fIchat\fR script be executed in a verbose mode. The
+ \fIchat\fR program will then log the execution state of the chat
+ script as well as all text received from the modem and the output
+ strings sent to the modem.  The default is to log through the SYSLOG;
+ the logging method may be altered with the -S and -s flags. SYSLOGs
+ are logged to facility LOG_LOCAL2.
+ .TP
+ .B -V
+ Request that the \fIchat\fR script be executed in a stderr verbose
+ mode. The \fIchat\fR program will then log all text received from the
+ modem and the output strings sent to the modem to the stderr device. This
+ device is usually the local console at the station running the chat or
+ pppd program.
+ .TP
+ .B -s
+ Use stderr.  All log messages from '-v' and all error messages will be
+ sent to stderr.
+ .TP
+ .B -S
+ Do not use the SYSLOG.  By default, error messages are sent to the
+ SYSLOG.  The use of -S will prevent both log messages from '-v' and
+ error messages from being sent to the SYSLOG (to facility LOG_LOCAL2).
+ .TP
+ .B -T \fI<phone number>
+ Pass in an arbitary string, usually a phone number, that will be
+ substituted for the \eT substitution metacharacter in a send string.
+ .TP
+ .B -U \fI<phone number 2>
+ Pass in a second string, usually a phone number, that will be
+ substituted for the \eU substitution metacharacter in a send string.
+ This is useful when dialing an ISDN terminal adapter that requires two
+ numbers.
+ .TP
+ .B script
+ If the script is not specified in a file with the \fI-f\fR option then
+ the script is included as parameters to the \fIchat\fR program.
+ .SH "CHAT SCRIPT"
+ .LP
+ The \fIchat\fR script defines the communications.
+ .LP
+ A script consists of one or more "expect-send" pairs of strings,
+ separated by spaces, with an optional "subexpect-subsend" string pair,
+ separated by a dash as in the following example:
+ .IP
+ ogin:-BREAK-ogin: ppp ssword: hello2u2
+ .LP
+ This line indicates that the \fIchat\fR program should expect the string
+ "ogin:". If it fails to receive a login prompt within the time interval
+ allotted, it is to send a break sequence to the remote and then expect the
+ string "ogin:". If the first "ogin:" is received then the break sequence is
+ not generated.
+ .LP
+ Once it received the login prompt the \fIchat\fR program will send the
+ string ppp and then expect the prompt "ssword:". When it receives the
+ prompt for the password, it will send the password hello2u2.
+ .LP
+ A carriage return is normally sent following the reply string. It is not
+ expected in the "expect" string unless it is specifically requested by using
+ the \er character sequence.
+ .LP
+ The expect sequence should contain only what is needed to identify the
+ string. Since it is normally stored on a disk file, it should not contain
+ variable information. It is generally not acceptable to look for time
+ strings, network identification strings, or other variable pieces of data as
+ an expect string.
+ .LP
+ To help correct for characters which may be corrupted during the initial
+ sequence, look for the string "ogin:" rather than "login:". It is possible
+ that the leading "l" character may be received in error and you may never
+ find the string even though it was sent by the system. For this reason,
+ scripts look for "ogin:" rather than "login:" and "ssword:" rather than
+ "password:".
+ .LP
+ A very simple script might look like this:
+ .IP
+ ogin: ppp ssword: hello2u2
+ .LP
+ In other words, expect ....ogin:, send ppp, expect ...ssword:, send hello2u2.
+ .LP
+ In actual practice, simple scripts are rare. At the vary least, you
+ should include sub-expect sequences should the original string not be
+ received. For example, consider the following script:
+ .IP
+ ogin:--ogin: ppp ssword: hello2u2
+ .LP
+ This would be a better script than the simple one used earlier. This would look
+ for the same login: prompt, however, if one was not received, a single
+ return sequence is sent and then it will look for login: again. Should line
+ noise obscure the first login prompt then sending the empty line will
+ usually generate a login prompt again.
+ .SH "COMMENTS"
+ Comments can be embedded in the chat script. A comment is a line which
+ starts with the \fB#\fR (hash) character in column 1. Such comment
+ lines are just ignored by the chat program. If a '#' character is to
+ be expected as the first character of the expect sequence, you should
+ quote the expect string, or give its octal value, `\e043'.
+ In a script file if you want to wait for a prompt that starts with a '#'
+ character, you would have to write something like this:
+ .IP
+ # Now wait for the prompt and send logout string
+ .br
+ \'# ' logout
+ .SH "SENDING DATA FROM A FILE"
+ If the string to send starts with an at sign (@), the rest of the
+ string is taken to be the name of a file to read to get the string to
+ send.  If the last character of the data read is a newline, it is
+ removed.  The file can be a named pipe (or fifo) instead of a regular
+ file.  This provides a way for \fBchat\fR to communicate with another
+ program, for example, a program to prompt the user and receive a
+ password typed in.
+ .SH "ABORT STRINGS"
+ Many modems will report the status of the call as a string. These
+ strings may be \fBCONNECTED\fR or \fBNO CARRIER\fR or \fBBUSY\fR. It
+ is often desirable to terminate the script should the modem fail to
+ connect to the remote. The difficulty is that a script would not know
+ exactly which modem string it may receive. On one attempt, it may
+ receive \fBBUSY\fR while the next time it may receive \fBNO CARRIER\fR.
+ .LP
+ These "abort" strings may be specified in the script using the \fIABORT\fR
+ sequence. It is written in the script as in the following example:
+ .IP
+ ABORT BUSY ABORT 'NO CARRIER' '' ATZ OK ATDT5551212 CONNECT
+ .LP
+ This sequence will expect nothing; and then send the string ATZ. The
+ expected response to this is the string \fIOK\fR. When it receives \fIOK\fR,
+ the string ATDT5551212 to dial the telephone. The expected string is
+ \fICONNECT\fR. If the string \fICONNECT\fR is received the remainder of the
+ script is executed. However, should the modem find a busy telephone, it will
+ send the string \fIBUSY\fR. This will cause the string to match the abort
+ character sequence. The script will then fail because it found a match to
+ the abort string. If it received the string \fINO CARRIER\fR, it will abort
+ for the same reason. Either string may be received. Either string will
+ terminate the \fIchat\fR script.
+ .SH "CLR_ABORT STRINGS"
+ This sequence allows for clearing previously set \fBABORT\fR strings.
+ \fBABORT\fR strings are kept in an array of a pre-determined size (at
+ compilation time); \fBCLR_ABORT\fR will reclaim the space for cleared
+ entries so that new strings can use that space.
+ .SH "SAY STRINGS"
+ The \fBSAY\fR directive allows the script to send strings to the user
+ at the terminal via standard error.  If \fBchat\fR is being run by
+ pppd, and pppd is running as a daemon (detached from its controlling
+ terminal), standard error will normally be redirected to the file
+ /etc/ppp/connect-errors.
+ .LP
+ \fBSAY\fR strings must be enclosed in single or double quotes. If
+ carriage return and line feed are needed in the string to be output,
+ you must explicitely add them to your string.
+ .LP
+ The SAY strings could be used to give progress messages in sections of
+ the script where you want to have 'ECHO OFF' but still let the user
+ know what is happening.  An example is:
+ .IP
+ ABORT BUSY
+ .br
+ ECHO OFF
+ .br
+ SAY "Dialling your ISP...\en"
+ .br
+ \'' ATDT5551212
+ .br
+ TIMEOUT 120
+ .br
+ SAY "Waiting up to 2 minutes for connection ... "
+ .br
+ CONNECT ''
+ .br
+ SAY "Connected, now logging in ...\n"
+ .br
+ ogin: account
+ .br
+ ssword: pass
+ .br
+ $ \c
+ SAY "Logged in OK ...\n"
+ \fIetc ...\fR
+ .LP
+ This sequence will only present the SAY strings to the user and all
+ the details of the script will remain hidden. For example, if the
+ above script works, the user will see:
+ .IP
+ Dialling your ISP...
+ .br
+ Waiting up to 2 minutes for connection ... Connected, now logging in ...
+ .br
+ Logged in OK ...
+ .LP
+ .SH "REPORT STRINGS"
+ A \fBreport\fR string is similar to the ABORT string. The difference
+ is that the strings, and all characters to the next control character
+ such as a carriage return, are written to the report file.
+ .LP
+ The report strings may be used to isolate the transmission rate of the
+ modem's connect string and return the value to the chat user. The
+ analysis of the report string logic occurs in conjunction with the
+ other string processing such as looking for the expect string. The use
+ of the same string for a report and abort sequence is probably not
+ very useful, however, it is possible.
+ .LP
+ The report strings to no change the completion code of the program.
+ .LP
+ These "report" strings may be specified in the script using the \fIREPORT\fR
+ sequence. It is written in the script as in the following example:
+ .IP
+ REPORT CONNECT ABORT BUSY '' ATDT5551212 CONNECT '' ogin: account
+ .LP
+ This sequence will expect nothing; and then send the string
+ ATDT5551212 to dial the telephone. The expected string is
+ \fICONNECT\fR. If the string \fICONNECT\fR is received the remainder
+ of the script is executed. In addition the program will write to the
+ expect-file the string "CONNECT" plus any characters which follow it
+ such as the connection rate.
+ .SH "CLR_REPORT STRINGS"
+ This sequence allows for clearing previously set \fBREPORT\fR strings.
+ \fBREPORT\fR strings are kept in an array of a pre-determined size (at
+ compilation time); \fBCLR_REPORT\fR will reclaim the space for cleared
+ entries so that new strings can use that space.
+ .SH "ECHO"
+ The echo options controls whether the output from the modem is echoed
+ to \fIstderr\fR. This option may be set with the \fI-e\fR option, but
+ it can also be controlled by the \fIECHO\fR keyword. The "expect-send"
+ pair \fIECHO\fR \fION\fR enables echoing, and \fIECHO\fR \fIOFF\fR
+ disables it. With this keyword you can select which parts of the
+ conversation should be visible. For instance, with the following
+ script:
+ .IP
+ ABORT   'BUSY'
+ .br
+ ABORT   'NO CARRIER'
+ .br
+ ''      ATZ
+ .br
+ OK\er\en  ATD1234567
+ .br
+ \er\en    \ec
+ .br
+ ECHO    ON
+ .br
+ CONNECT \ec
+ .br
+ ogin:   account
+ .LP
+ all output resulting from modem configuration and dialing is not visible,
+ but starting with the \fICONNECT\fR (or \fIBUSY\fR) message, everything
+ will be echoed.
+ .SH "HANGUP"
+ The HANGUP options control whether a modem hangup should be considered
+ as an error or not.  This option is useful in scripts for dialling
+ systems which will hang up and call your system back.  The HANGUP
+ options can be \fBON\fR or \fBOFF\fR.
+ .br
+ When HANGUP is set OFF and the modem hangs up (e.g., after the first
+ stage of logging in to a callback system), \fBchat\fR will continue
+ running the script (e.g., waiting for the incoming call and second
+ stage login prompt). As soon as the incoming call is connected, you
+ should use the \fBHANGUP ON\fR directive to reinstall normal hang up
+ signal behavior.  Here is an (simple) example script:
+ .IP
+ ABORT   'BUSY'
+ .br
+ ''      ATZ
+ .br
+ OK\er\en  ATD1234567
+ .br
+ \er\en    \ec
+ .br
+ CONNECT \ec
+ .br
+ \'Callback login:' call_back_ID
+ .br
+ HANGUP OFF
+ .br
+ ABORT "Bad Login"
+ .br
+ \'Callback Password:' Call_back_password
+ .br
+ TIMEOUT 120
+ .br
+ CONNECT \ec
+ .br
+ HANGUP ON
+ .br
+ ABORT "NO CARRIER"
+ .br
+ ogin:--BREAK--ogin: real_account
+ .br
+ \fIetc ...\fR
+ .LP
+ .SH "TIMEOUT"
+ The initial timeout value is 45 seconds. This may be changed using the \fB-t\fR
+ parameter.
+ .LP
+ To change the timeout value for the next expect string, the following
+ example may be used:
+ .IP
+ ATZ OK ATDT5551212 CONNECT TIMEOUT 10 ogin:--ogin: TIMEOUT 5 assword: hello2u2
+ .LP
+ This will change the timeout to 10 seconds when it expects the login:
+ prompt. The timeout is then changed to 5 seconds when it looks for the
+ password prompt.
+ .LP
+ The timeout, once changed, remains in effect until it is changed again.
+ .SH "SENDING EOT"
+ The special reply string of \fIEOT\fR indicates that the chat program
+ should send an EOT character to the remote. This is normally the
+ End-of-file character sequence. A return character is not sent
+ following the EOT.
+ .PR
+ The EOT sequence may be embedded into the send string using the
+ sequence \fI^D\fR.
+ .SH "GENERATING BREAK"
+ The special reply string of \fIBREAK\fR will cause a break condition
+ to be sent. The break is a special signal on the transmitter. The
+ normal processing on the receiver is to change the transmission rate.
+ It may be used to cycle through the available transmission rates on
+ the remote until you are able to receive a valid login prompt.
+ .PR
+ The break sequence may be embedded into the send string using the
+ \fI\eK\fR sequence.
+ .SH "ESCAPE SEQUENCES"
+ The expect and reply strings may contain escape sequences. All of the
+ sequences are legal in the reply string. Many are legal in the expect.
+ Those which are not valid in the expect sequence are so indicated.
+ .TP
+ .B ''
+ Expects or sends a null string. If you send a null string then it will still
+ send the return character. This sequence may either be a pair of apostrophe
+ or quote characters.
+ .TP
+ .B \eb
+ represents a backspace character.
+ .TP
+ .B \ec
+ Suppresses the newline at the end of the reply string. This is the only
+ method to send a string without a trailing return character. It must
+ be at the end of the send string. For example,
+ the sequence hello\ec will simply send the characters h, e, l, l, o.
+ .I (not valid in expect.)
+ .TP
+ .B \ed
+ Delay for one second. The program uses sleep(1) which will delay to a
+ maximum of one second.
+ .I (not valid in expect.)
+ .TP
+ .B \eK
+ Insert a BREAK
+ .I (not valid in expect.)
+ .TP
+ .B \en
+ Send a newline or linefeed character.
+ .TP
+ .B \eN
+ Send a null character. The same sequence may be represented by \e0.
+ .I (not valid in expect.)
+ .TP
+ .B \ep
+ Pause for a fraction of a second. The delay is 1/10th of a second.
+ .I (not valid in expect.)
+ .TP
+ .B \eq
+ Suppress writing the string to the SYSLOG. The string ?????? is
+ written to the log in its place.
+ .I (not valid in expect.)
+ .TP
+ .B \er
+ Send or expect a carriage return.
+ .TP
+ .B \es
+ Represents a space character in the string. This may be used when it
+ is not desirable to quote the strings which contains spaces. The
+ sequence 'HI\ TIM' and HI\esTIM are the same.
+ .TP
+ .B \et
+ Send or expect a tab character.
+ .TP
+ .B \eT
+ Send the phone number string as specified with the \fI-T\fR option
+ .I (not valid in expect.)
+ .TP
+ .B \eU
+ Send the phone number 2 string as specified with the \fI-U\fR option
+ .I (not valid in expect.)
+ .TP
+ .B \e\e
+ Send or expect a backslash character.
+ .TP
+ .B \eddd
+ Collapse the octal digits (ddd) into a single ASCII character and send that
+ character.
+ .I (some characters are not valid in expect.)
+ .TP
+ .B \^^C
+ Substitute the sequence with the control character represented by C.
+ For example, the character DC1 (17) is shown as \^^Q.
+ .I (some characters are not valid in expect.)
+ .SH "ENVIRONMENT VARIABLES"
+ Environment variables are available within chat scripts, if  the \fI-E\fR
+ option was specified in the command line. The metacharacter \fI$\fR is used
+ to introduce the name of the environment variable to substitute. If the
+ substition fails, because the requested environment variable is not set,
+ \fInothing\fR is replaced for the variable.
+ .SH "TERMINATION CODES"
+ The \fIchat\fR program will terminate with the following completion
+ codes.
+ .TP
+ .B 0
+ The normal termination of the program. This indicates that the script
+ was executed without error to the normal conclusion.
+ .TP
+ .B 1
+ One or more of the parameters are invalid or an expect string was too
+ large for the internal buffers. This indicates that the program as not
+ properly executed.
+ .TP
+ .B 2
+ An error occurred during the execution of the program. This may be due
+ to a read or write operation failing for some reason or chat receiving
+ a signal such as SIGINT.
+ .TP
+ .B 3
+ A timeout event occurred when there was an \fIexpect\fR string without
+ having a "-subsend" string. This may mean that you did not program the
+ script correctly for the condition or that some unexpected event has
+ occurred and the expected string could not be found.
+ .TP
+ .B 4
+ The first string marked as an \fIABORT\fR condition occurred.
+ .TP
+ .B 5
+ The second string marked as an \fIABORT\fR condition occurred.
+ .TP
+ .B 6
+ The third string marked as an \fIABORT\fR condition occurred.
+ .TP
+ .B 7
+ The fourth string marked as an \fIABORT\fR condition occurred.
+ .TP
+ .B ...
+ The other termination codes are also strings marked as an \fIABORT\fR
+ condition.
+ .LP
+ Using the termination code, it is possible to determine which event
+ terminated the script. It is possible to decide if the string "BUSY"
+ was received from the modem as opposed to "NO DIAL TONE". While the
+ first event may be retried, the second will probably have little
+ chance of succeeding during a retry.
+ .SH "SEE ALSO"
+ Additional information about \fIchat\fR scripts may be found with UUCP
+ documentation. The \fIchat\fR script was taken from the ideas proposed
+ by the scripts used by the \fIuucico\fR program.
+ .LP
+ uucp(1), uucico(8)
+ .SH "COPYRIGHT"
+ The \fIchat\fR program is in public domain. This is not the GNU public
+ license. If it breaks then you get to keep both pieces.
Index: conserver.cf/Makefile.in
===================================================================
RCS file: /cvs/misc/conserver/conserver.cf/Makefile.in,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 Makefile.in
*** conserver.cf/Makefile.in	5 Dec 2001 20:36:45 -0000	1.1.1.1
--- conserver.cf/Makefile.in	2 May 2002 04:31:04 -0000
***************
*** 23,28 ****
--- 23,29 ----
  install:
  	$(MKDIR) $(DESTDIR)$(mandir)/man5
  	$(INSTALL) conserver.cf.man $(DESTDIR)$(mandir)/man5/conserver.cf.5
+ 	$(INSTALL) conserver.chat.man $(DESTDIR)$(mandir)/man5/conserver.chat.5
  	$(INSTALL) conserver.passwd.man $(DESTDIR)$(mandir)/man5/conserver.passwd.5
  
  .PHONY: clean distclean install
Index: conserver.cf/conserver.cf.man
===================================================================
RCS file: /cvs/misc/conserver/conserver.cf/conserver.cf.man,v
retrieving revision 1.1.1.3
diff -c -r1.1.1.3 conserver.cf.man
*** conserver.cf/conserver.cf.man	24 Apr 2002 21:41:59 -0000	1.1.1.3
--- conserver.cf/conserver.cf.man	2 May 2002 04:44:01 -0000
***************
*** 4,53 ****
  .SH NAME
  conserver.cf \- console configuration file for conserver(8)
  .SH SYNOPSIS
- .br
  .BI \s-1LOGDIR\s0= logdirectory
! .br
  .BI \s-1TIMESTAMP\s0= timestamp-spec
! .br
  .BI \s-1BREAK\s0\fIn\fP= break-spec
! .br
! \fIname\fP:\fIdevice\fP[@\fIconserver\fP]:\fIbaud\fP:\fIlogfile\fP:\fItimestamp-spec\fP:\fIbreak\fP
! .br
! \fIname\fP:!\fItermserver\fP[@\fIconserver\fP]:\fIport\fP:\fIlogfile\fP:\fItimestamp-spec\fP:\fIbreak\fP
! .br
! \fIname\fP:|\fIcommand\fP[@\fIconserver\fP]::\fIlogfile\fP:\fItimestamp-spec\fP:\fIbreak\fP
! .br
  \fB%%\fP
! .br
  \fIaccess\fP: \fIhosts\fP
  .SH DESCRIPTION
  .B Conserver.cf
  is the configuration file for
  .BR conserver (8).
- It is read once upon startup;
- modifications to the file take effect only upon restarting \fBconserver\fP.
  .PP
  Blank lines and comment lines (those beginning with a ``#'' and
  optional leading whitespace) are ignored.  Non-ignored lines
  beginning with whitespace are considered continuations of the
  previous line.  This allows you to span one logical line over
  many physical lines and insert comments wherever appropriate.
  .PP
! The first section of the file has logical lines that are separated into
! five colon-separated fields.  Leading and trailing white space in each
! field is ignored.
  .TP
  .I name
  the unique name by which this connection is referred to
  when using the \fBconsole\fP program.
  This is typically the name of the host whose console is being monitored.
  .TP
  .I device
! the full path name of the device for this line.
! The \fIbaud\fP rate is the speed and parity for this console.
! Speed may be given as an integer,
! parity only requires the first letter of any of: even, odd, mark, space.
! For no parity, use the character `p'.
  .TP
  .BI ! termserver
  the hostname of the terminal server to connect to.
--- 4,53 ----
  .SH NAME
  conserver.cf \- console configuration file for conserver(8)
  .SH SYNOPSIS
  .BI \s-1LOGDIR\s0= logdirectory
! .PP
  .BI \s-1TIMESTAMP\s0= timestamp-spec
! .PP
  .BI \s-1BREAK\s0\fIn\fP= break-spec
! .PP
! \fIname\fP:\fIdevice\fP[@\fIconserver\fP]:\fIbaud\fP:\fIlogfile\fP:[\fItimestamp-spec\fP][:\fIbreak\fP]
! .PP
! \fIname\fP:@\fIconserver\fP:[\fIbaud\fP]:[\fIlogfile\fP]:[\fItimestamp-spec\fP][:\fIbreak\fP]
! .PP
! \fIname\fP:!\fItermserver\fP[@\fIconserver\fP]:\fIport\fP:\fIlogfile\fP:[\fItimestamp-spec\fP][:\fIbreak\fP]
! .PP
! \fIname\fP:|\fIcommand\fP[@\fIconserver\fP]::\fIlogfile\fP:[\fItimestamp-spec\fP][:\fIbreak\fP]
! .PP
  \fB%%\fP
! .PP
  \fIaccess\fP: \fIhosts\fP
  .SH DESCRIPTION
  .B Conserver.cf
  is the configuration file for
  .BR conserver (8).
  .PP
  Blank lines and comment lines (those beginning with a ``#'' and
  optional leading whitespace) are ignored.  Non-ignored lines
  beginning with whitespace are considered continuations of the
  previous line.  This allows you to span one logical line over
  many physical lines and insert comments wherever appropriate.
+ .SS "Console Specifications"
+ The first section of the file consists of lines either specifying the
+ details for a given console, or giving default setting for the following
+ group of console specifications.
  .PP
! Console specification lines are separated into five colon-separated
! fields.  Leading and trailing white space in each field is ignored.
  .TP
  .I name
  the unique name by which this connection is referred to
  when using the \fBconsole\fP program.
  This is typically the name of the host whose console is being monitored.
+ .PP
+ The next field has one of three alternate forms:
  .TP
  .I device
! the full pathname of the device for this line.
  .TP
  .BI ! termserver
  the hostname of the terminal server to connect to.
***************
*** 56,65 ****
--- 56,83 ----
  .BI | command
  the command to invoke on the console server.
  .PP
+ The interpretation of the third field depends on the form of the second
+ field:
+ .TP
+ .I baud
+ When the second field is a full pathname to a device, the second field
+ is the speed and parity for this device.  The speed may be given as an
+ integer in bits per second.  The desired parity setting for this device
+ is specified by the first letter of any of:  even, odd, mark, space, or
+ none, given immediately after the speed number.
+ .TP
+ .I port
+ When the second field is the hostname of a terminal server (i.e. begins
+ with a `!' character), the second field is the TCP port number to
+ connect to on that host.
+ .PP
  \fIdevice\fP, !\fItermserver\fP, and |\fIcommand\fP may be followed by
  a remote console server name in the form ``\fB@\fP\fIconserver\fP'',
  in which case the conserver daemon will send connections for \fIname\fP
  to the conserver running on the host named \fIconserver\fP.
+ The device, termserver, or command name are ignored and can be omitted,
+ as may other fields only necessary on the master conserver host.
+ .PP
  When the ``\fB@\fP\fIconserver\fP'' notation is used,
  \fBconserver\fP recognizes consoles it should manage locally
  by comparing the IP address of \fIconserver\fP
***************
*** 91,104 ****
  specifies `lines' and will cause timestamps of the form
  `[Mon Jan 25 14:46:56 PST 1999]' to
  be placed every \fImark-interval\fP lines (a newline character signifies
! a new line). So, `5h' specifies every five hours and `2l' specifies every
! two lines.
! An `\fBa\fP' can be specified to add logs of
  `attached', `detached', and `bumped' actions,
  including the user's name and the host from which the
  \fBconsole\fP connection was made,
  to the logfile.
! A `\fBb\fP' can be specified to add logging of break sequences sent
  to the console.
  .IP
  A default \fItimestamp-spec\fP can be specified by using the
--- 109,121 ----
  specifies `lines' and will cause timestamps of the form
  `[Mon Jan 25 14:46:56 PST 1999]' to
  be placed every \fImark-interval\fP lines (a newline character signifies
! a new line).
! An `\fBa\fP' flag can be specified to add logs of
  `attached', `detached', and `bumped' actions,
  including the user's name and the host from which the
  \fBconsole\fP connection was made,
  to the logfile.
! A `\fBb\fP' flag can be specified to add logging of break sequences sent
  to the console.
  .IP
  A default \fItimestamp-spec\fP can be specified by using the
***************
*** 118,155 ****
  The \fIbreak-spec\fP sequences are defined using
  the \fB\s-1BREAK\s0\fIn\fB=\fR
  syntax where \fIn\fP is a number from 1 to 9.
! There are three builtin defaults: ``\s-1BREAK1\s0=\\z'',
! ``\s-1BREAK2\s0=\\r~^b'',
! and ``\s-1BREAK3\s0=#.reset -x\\r''.  The values of
  the \fB\s-1BREAK\s0\fIn\fR
! sequences are simple characters strings with the exception of `\\' and
  `^':
  .sp
  .PD 0
! .IP \\\\a
  alert
! .IP \\\\b
  backspace
! .IP \\\\f
  form-feed
! .IP \\\\n
  newline
! .IP \\\\r
  carriage-return
! .IP \\\\t
  tab
! .IP \\\\v
  vertical-tab
! .IP \\\\z
  serial break
! .IP \\\\\\\\
  backslash
! .IP \\\\^
  circumflex
! .IP \\\\\fIooo\fP
  octal representation of a character (where \fIooo\fP is one to three
  octal digits)
! .IP \\\\\fIc\fP
  character \fIc\fP
  .IP ^?
  delete
--- 135,172 ----
  The \fIbreak-spec\fP sequences are defined using
  the \fB\s-1BREAK\s0\fIn\fB=\fR
  syntax where \fIn\fP is a number from 1 to 9.
! There are three builtin defaults: ``\s-1BREAK1\s0=\ez'',
! ``\s-1BREAK2\s0=\er~^b'',
! and ``\s-1BREAK3\s0=#.reset -x\er''.  The values of
  the \fB\s-1BREAK\s0\fIn\fR
! sequences are simple characters strings with the exception of `\e' and
  `^':
  .sp
  .PD 0
! .IP \ea
  alert
! .IP \eb
  backspace
! .IP \ef
  form-feed
! .IP \en
  newline
! .IP \er
  carriage-return
! .IP \et
  tab
! .IP \ev
  vertical-tab
! .IP \ez
  serial break
! .IP \e\e
  backslash
! .IP \e^
  circumflex
! .IP \e\fIooo\fP
  octal representation of a character (where \fIooo\fP is one to three
  octal digits)
! .IP \e\fIc\fP
  character \fIc\fP
  .IP ^?
  delete
***************
*** 158,164 ****
  .PD
  .PP
  This section is terminated with a `\fB%%\fP' token on a line by itself.
! .PP
  The next section of the file contains a list of hosts and addresses
  which are allowed to connect to the console server.
  .B Conserver
--- 175,181 ----
  .PD
  .PP
  This section is terminated with a `\fB%%\fP' token on a line by itself.
! .SS "Access Specifications"
  The next section of the file contains a list of hosts and addresses
  which are allowed to connect to the console server.
  .B Conserver
Index: conserver.cf/conserver.chat.man
===================================================================
RCS file: conserver.cf/conserver.chat.man
diff -N conserver.cf/conserver.chat.man
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- conserver.cf/conserver.chat.man	2 May 2002 05:19:22 -0000
***************
*** 0 ****
--- 1,94 ----
+ .\" $Id$
+ .\" @(#)constab.5 01/06/91 OSU CIS; Thomas A. Fine
+ .de Vb
+ .ft CW
+ .nf
+ .ne \\$1
+ ..
+ .de Ve
+ .ft R
+ 
+ .fi
+ ..
+ .if n .na
+ .if n .nh
+ .TH CONSERVER.CHAT 5 "Local"
+ .SH NAME
+ conserver.chat \- console chat-script file for conserver(8)
+ .SH SYNOPSIS
+ \fIname\fP:\fIchat-script\fP
+ .\" XXX this optional feature is not yet available -- more parsing is
+ .\" required to handle it properly as currently the second field is
+ .\" simply passed in nearly verbatim form (leading and trailing
+ .\" whitespace is stripped) as part of a single-quoted argument to
+ .\" /bin/sh.
+ .\".PP
+ .\"\fIname\fP:-f \fIchat-script-filename\fP
+ .SH DESCRIPTION
+ .B Conservrer.chat
+ is an optional configuration file for
+ .BR conserver (8)
+ that specifies chat scripts for consoles.  These scripts are handed off
+ to the
+ .BR chat (1)
+ program for execution whenever the console is (re)initialised.
+ .PP
+ Blank lines and comment lines (those beginning with a ``#'' and
+ optional leading whitespace) are ignored.  Non-ignored lines
+ beginning with whitespace are considered continuations of the
+ previous line.  This allows you to span one logical line over
+ many physical lines and insert comments wherever appropriate.
+ .PP
+ Chat-script specification lines are separated into two colon-separated
+ fields.  Leading and trailing white space in each field is ignored.
+ .TP
+ .I name
+ the unique name by which this console is referred to.  This name must
+ match the name of a console connection defined in the
+ .B Conserver.cf
+ file.
+ .TP
+ .I chat-script
+ A chat script specification that will be given on the
+ .BR chat (1)
+ command line.  This field must not contain any single quotes as it will
+ itself be concatentated to the chat command and the whole will be passed
+ as a single-quoted operand to
+ .B "/bin/sh -c"
+ and the shell will then again break this string up into separate
+ parameters which will be given on the
+ .B chat
+ program command-line.  As such any special characters must be quoted
+ with double-quotes as must empty parameters.  The
+ .BR chat (1)
+ manual explains the script syntax and how characters are given.
+ .PP
+ Note that for network connected console ports (i.e. those on a terminal
+ server which are connected to via telnet), it is not currently possible
+ to cause the terminal server to generate a serial BREAK.  Modifications
+ would be necessary to the
+ .B chat
+ program to allow it to send a telnet ``send brk'' command.
+ .SH EXAMPLE
+ Many terminal servers have an access password on their reverse telnet
+ ports.  A chat script can be used to login past this password.  Here we
+ assume access via the
+ .BR console (1)
+ client is controlled to prevent unauthorised connections and that the
+ .B conserver.chat
+ file itself is readable (and writable) only by the
+ .I root
+ user (which is of course the ID used to start
+ .BR conserver ).
+ .PP
+ .Vb 3
+ # These consoles are on a terminal server with an access password
+ best-1.4        : "" "\er\en" "\e043--\e043" access
+ exb210          : "" "\er\en" "\e043--\e043" access
+ hubly           : "" "\er\en" "\e043--\e043" BellSux
+ .Ve
+ .SH "SEE ALSO"
+ .BR chat (1),
+ .BR console (1),
+ .BR conserver.cf (5),
+ .BR conserver (8)