diff --git a/usr.bin/chat/Makefile b/usr.bin/chat/Makefile new file mode 100644 index 000000000000..be272711406c --- /dev/null +++ b/usr.bin/chat/Makefile @@ -0,0 +1,8 @@ +# $Id: Makefile.bsd,v 1.2 1994/05/26 06:45:03 paulus Exp $ + +PROG= chat +SRCS= chat.c +MAN8= chat.8 +BINDIR= /usr/bin + +.include diff --git a/usr.bin/chat/chat.8 b/usr.bin/chat/chat.8 new file mode 100644 index 000000000000..3d2eee0eb8c4 --- /dev/null +++ b/usr.bin/chat/chat.8 @@ -0,0 +1,251 @@ +.\" -*- nroff -*- +.\" manual page [] for chat 1.8 +.\" $Id: chat.8,v 1.7 1994/03/04 20:19:30 callahan Exp $ +.\" SH section heading +.\" SS subsection heading +.\" LP paragraph +.\" IP indented paragraph +.\" TP hanging label +.TH CHAT 8 "17 April 1994" "Chat Version 1.8" +.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 +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 -l \fI +Perform the UUCP style locking using the indicated lock file. +.IP +If the file could not be created then the \fIchat\fR program will +fail. The lock file will be deleted only if the \fIchat\fR program +fails to perform the script for any reason. If the script is +successful the lock file will be left on the disk. It is expected that +the lock file will be deleted when the \fIpppd\fR process no longer +wishes to use the serial device. +.IP +The use of a lock file with +.I chat +and +\fIpppd\fR\'s +.I lock +option should not be used at the same time. They are mutually +exclusive options and will cause one or the other program to fail to +achieve the required lock if you use both. +.TP +.B -t \fI +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 -v +Request that the \fIchat\fR script be executed in a verbose mode. The +\fIchat\fR program will then log all text received from the modem and +the output strings which it sends to the SYSLOG. +.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 \\r 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 ssowrd: 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 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 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 assowrd: 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\\K\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 \\\\b +represents a backspace character. +.TP +.B \\\\c +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\\c will simply send the characters h, e, l, l, o. +.I (not valid in expect.) +.TP +.B \\\\d +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 \\\\K +Insert a BREAK +.I (not valid in expect.) +.TP +.B \\\\n +Send a newline or linefeed character. +.TP +.B \\\\N +Send a null character. The same sequence may be represented by \\0. +.I (not valid in expect.) +.TP +.B \\\\p +Pause for a fraction of a second. The delay is 1/10th of a second. +.I (not valid in expect.) +.TP +.B \\\\q +Suppress writing the string to the SYSLOG file. The string ?????? is +written to the log in its place. +.I (not valid in expect.) +.TP +.B \\\\r +Send or expect a carriage return. +.TP +.B \\\\s +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\\sTIM are the same. +.TP +.B \\\\t +Send or expect a tab character. +.TP +.B \\\\\\\\ +Send or expect a backslash character. +.TP +.B \\\\ddd +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 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 +uucico(1), uucp(1) +.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. diff --git a/usr.bin/chat/chat.c b/usr.bin/chat/chat.c new file mode 100644 index 000000000000..6c614d49dbb1 --- /dev/null +++ b/usr.bin/chat/chat.c @@ -0,0 +1,1166 @@ +/* + * Chat -- a program for automatic session establishment (i.e. dial + * the phone and log in). + * + * This software is in the public domain. + * + * Please send all bug reports, requests for information, etc. to: + * + * Al Longyear (longyear@netcom.com) + * (I was the last person to change this code.) + * + * The original author is: + * + * Karl Fox + * Morning Star Technologies, Inc. + * 1760 Zollinger Road + * Columbus, OH 43221 + * (614)451-1883 + */ + +static char rcsid[] = "$Id: chat.c,v 1.4 1994/05/30 00:30:37 paulus Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TERMIO +#undef TERMIOS +#define TERMIOS +#endif + +#ifdef sun +# if defined(SUNOS) && SUNOS >= 41 +# ifndef HDB +# define HDB +# endif +# endif +#endif + +#ifdef TERMIO +#include +#endif +#ifdef TERMIOS +#include +#endif + +#define STR_LEN 1024 + +#ifndef SIGTYPE +#define SIGTYPE void +#endif + +#ifdef __STDC__ +#undef __P +#define __P(x) x +#else +#define __P(x) () +#define const +#endif + +/*************** Micro getopt() *********************************************/ +#define OPTION(c,v) (_O&2&&**v?*(*v)++:!c||_O&4?0:(!(_O&1)&& \ + (--c,++v),_O=4,c&&**v=='-'&&v[0][1]?*++*v=='-'\ + &&!v[0][1]?(--c,++v,0):(_O=2,*(*v)++):0)) +#define OPTARG(c,v) (_O&2?**v||(++v,--c)?(_O=1,--c,*v++): \ + (_O=4,(char*)0):(char*)0) +#define OPTONLYARG(c,v) (_O&2&&**v?(_O=1,--c,*v++):(char*)0) +#define ARG(c,v) (c?(--c,*v++):(char*)0) + +static int _O = 0; /* Internal state */ +/*************** Micro getopt() *********************************************/ + +char *program_name; + +#ifndef LOCK_DIR +# ifdef __NetBSD__ +# define PIDSTRING +# define LOCK_DIR "/var/spool/lock" +# else +# ifdef HDB +# define PIDSTRING +# define LOCK_DIR "/usr/spool/locks" +# else /* HDB */ +# define LOCK_DIR "/usr/spool/uucp" +# endif /* HDB */ +# endif +#endif /* LOCK_DIR */ + +#define MAX_ABORTS 50 +#define DEFAULT_CHAT_TIMEOUT 45 + +int verbose = 0; +int quiet = 0; +char *lock_file = (char *)0; +char *chat_file = (char *)0; +int timeout = DEFAULT_CHAT_TIMEOUT; + +int have_tty_parameters = 0; +#ifdef TERMIO +struct termio saved_tty_parameters; +#endif +#ifdef TERMIOS +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; + +void *dup_mem __P((void *b, size_t c)); +void *copy_of __P((char *s)); +void usage __P((void)); +void logf __P((const char *str)); +void logflush __P((void)); +void fatal __P((const char *msg)); +void sysfatal __P((const char *msg)); +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 break_sequence __P((void)); +void terminate __P((int status)); +void do_file __P((char *chat_file)); +void lock __P((void)); +void delay __P((void)); +int get_string __P((register char *string)); +int put_string __P((register char *s)); +int write_char __P((int c)); +int put_char __P((char c)); +int get_char __P((void)); +void chat_send __P((register char *s)); +char *character __P((char c)); +void chat_expect __P((register char *s)); +char *clean __P((register char *s, int sending)); +void unlock __P((void)); +void lock __P((void)); +void break_sequence __P((void)); +void terminate __P((int status)); +void die __P((void)); + +void *dup_mem(b, c) +void *b; +size_t c; + { + void *ans = malloc (c); + if (!ans) + fatal ("memory error!\n"); + memcpy (ans, b, c); + return ans; + } + +void *copy_of (s) +char *s; + { + return dup_mem (s, strlen (s) + 1); + } + +/* + * chat [ -v ] [ -t timeout ] [ -l lock-file ] [ -f chat-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; + char *arg; + + program_name = *argv; + + while (option = OPTION(argc, argv)) + switch (option) + { + case 'v': + ++verbose; + break; + + case 'f': + if (arg = OPTARG(argc, argv)) + chat_file = copy_of(arg); + else + usage(); + + break; + + case 'l': + if (arg = OPTARG(argc, argv)) + lock_file = copy_of(arg); + else + usage(); + + break; + + case 't': + if (arg = OPTARG(argc, argv)) + timeout = atoi(arg); + else + usage(); + + break; + + default: + usage(); + } + +#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) + { + arg = ARG(argc, argv); + if (arg != NULL) + usage(); + else + do_file (chat_file); + } + else + { + while (arg = ARG(argc, argv)) + { + chat_expect(arg); + + if (arg = ARG(argc, argv)) + chat_send(arg); + } + } + + terminate(0); + } + +/* + * Process a chat script when read from a file. + */ + +void do_file (chat_file) +char *chat_file; + { + int linect, len, sendflg; + char *sp, *arg, quote; + char buf [STR_LEN]; + FILE *cfp; + + if ((cfp = fopen (chat_file, "r")) == NULL) + { + syslog (LOG_ERR, "%s -- open failed: %m", chat_file); + terminate (1); + } + + linect = 0; + sendflg = 0; + + while (fgets(buf, STR_LEN, cfp) != NULL) + { + sp = strchr (buf, '\n'); + if (sp) + *sp = '\0'; + + linect++; + sp = buf; + while (*sp != '\0') + { + if (*sp == ' ' || *sp == '\t') + { + ++sp; + continue; + } + + if (*sp == '"' || *sp == '\'') + { + quote = *sp++; + arg = sp; + while (*sp != quote) + { + if (*sp == '\0') + { + syslog (LOG_ERR, "unterminated quote (line %d)", + linect); + terminate (1); + } + + 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 [-v] [-l lock-file] [-t timeout] {-f chat-file || chat-script}\n", + program_name); + exit(1); + } + +char line[256]; +char *p; + +void logf (str) +const char *str; + { + p = line + strlen(line); + strcat (p, str); + + if (str[strlen(str)-1] == '\n') + { + syslog (LOG_INFO, "%s", line); + line[0] = 0; + } + } + +void logflush() + { + if (line[0] != 0) + { + syslog(LOG_INFO, "%s", line); + line[0] = 0; + } + } + +/* + * Unlock and terminate with an error. + */ +void die() + { + unlock(); + terminate(1); + } + +/* + * Print an error message and terminate. + */ + +void fatal (msg) +const char *msg; + { + syslog(LOG_ERR, "%s", msg); + unlock(); + terminate(1); + } + +/* + * Print an error message along with the system error message and + * terminate. + */ + +void sysfatal (msg) +const char *msg; + { + syslog(LOG_ERR, "%s: %m", msg); + unlock(); + terminate(1); + } + +int alarmed = 0; + +SIGTYPE sigalrm(signo) +int signo; + { + int flags; + + alarm(1); + alarmed = 1; /* Reset alarm to avoid race window */ + signal(SIGALRM, sigalrm); /* that can cause hanging in read() */ + + logflush(); + if ((flags = fcntl(0, F_GETFL, 0)) == -1) + sysfatal("Can't get file mode flags on stdin"); + else + if (fcntl(0, F_SETFL, flags | FNDELAY) == -1) + sysfatal("Can't set file mode flags on stdin"); + + if (verbose) + { + syslog(LOG_INFO, "alarm"); + } + } + +void unalarm() + { + int flags; + + if ((flags = fcntl(0, F_GETFL, 0)) == -1) + sysfatal("Can't get file mode flags on stdin"); + else + if (fcntl(0, F_SETFL, flags & ~FNDELAY) == -1) + sysfatal("Can't set file mode flags on stdin"); + } + +SIGTYPE sigint(signo) +int signo; + { + fatal("SIGINT"); + } + +SIGTYPE sigterm(signo) +int signo; + { + fatal("SIGTERM"); + } + +SIGTYPE sighup(signo) +int signo; + { + fatal("SIGHUP"); + } + +void init() + { + signal(SIGINT, sigint); + signal(SIGTERM, sigterm); + signal(SIGHUP, sighup); + + if (lock_file) + lock(); + + set_tty_parameters(); + signal(SIGALRM, sigalrm); + alarm(0); + alarmed = 0; + } + +void set_tty_parameters() + { +#ifdef TERMIO + struct termio t; + + if (ioctl(0, TCGETA, &t) < 0) + sysfatal("Can't get terminal parameters"); +#endif +#ifdef TERMIOS + struct termios t; + + if (tcgetattr(0, &t) < 0) + sysfatal("Can't get terminal parameters"); +#endif + + saved_tty_parameters = t; + have_tty_parameters = 1; + + t.c_iflag |= IGNBRK | ISTRIP | IGNPAR; + t.c_oflag = 0; + t.c_lflag = 0; + t.c_cc[VERASE] = t.c_cc[VKILL] = 0; + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + +#ifdef TERMIO + if (ioctl(0, TCSETA, &t) < 0) + sysfatal("Can't set terminal parameters"); +#endif +#ifdef TERMIOS + if (tcsetattr(0, TCSANOW, &t) < 0) + sysfatal("Can't set terminal parameters"); +#endif + } + +void break_sequence() + { +#ifdef TERMIOS + tcsendbreak (0, 0); +#endif + } + +void terminate(status) +int status; + { + if (have_tty_parameters && +#ifdef TERMIO + ioctl(0, TCSETA, &saved_tty_parameters) < 0 +#endif +#ifdef TERMIOS + tcsetattr(0, TCSANOW, &saved_tty_parameters) < 0 +#endif + ) { + syslog(LOG_ERR, "Can't restore terminal parameters: %m"); + unlock(); + exit(1); + } + exit(status); + } + +/* + * Create a lock file for the named lock device + */ +void lock() + { + int fd, pid; +# ifdef PIDSTRING + char hdb_lock_buffer[12]; +# endif + + lock_file = strcat(strcat(strcpy(malloc(strlen(LOCK_DIR) + + 1 + strlen(lock_file) + 1), + LOCK_DIR), "/"), lock_file); + + if ((fd = open(lock_file, O_EXCL | O_CREAT | O_RDWR, 0644)) < 0) + { + char *s = lock_file; + lock_file = (char *)0; /* Don't remove someone else's lock file! */ + syslog(LOG_ERR, "Can't get lock file '%s': %m", s); + die(); + } + +# ifdef PIDSTRING + sprintf(hdb_lock_buffer, "%10d\n", getpid()); + write(fd, hdb_lock_buffer, 11); +# else + pid = getpid(); + write(fd, &pid, sizeof pid); +# endif + + close(fd); + } + +/* + * Remove our lockfile + */ +void unlock() + { + if (lock_file) + { + unlink(lock_file); + lock_file = (char *)0; + } + } + +/* + * 'Clean up' this string. + */ +char *clean(s, sending) +register char *s; +int sending; + { + char temp[STR_LEN], cur_chr; + register char *s1; + int add_return = sending; +#define isoctal(chr) (((chr) >= '0') && ((chr) <= '7')) + + 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 (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 'q': + quiet = ! quiet; + 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; + + 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 */ + } + +/* + * Process the expect string + */ +void chat_expect(s) +register char *s; + { + if (strcmp(s, "ABORT") == 0) + { + ++abort_next; + return; + } + + if (strcmp(s, "TIMEOUT") == 0) + { + ++timeout_next; + return; + } + + while (*s) + { + register char *hyphen; + + for (hyphen = s; *hyphen; ++hyphen) + if (*hyphen == '-') + if (hyphen == s || hyphen[-1] != '\\') + break; + + if (*hyphen == '-') + { + *hyphen = '\0'; + + if (get_string(s)) + return; + else + { + s = hyphen + 1; + + for (hyphen = s; *hyphen; ++hyphen) + if (*hyphen == '-') + if (hyphen == s || hyphen[-1] != '\\') + break; + + if (*hyphen == '-') + { + *hyphen = '\0'; + + chat_send(s); + s = hyphen + 1; + } + else + { + chat_send(s); + return; + } + } + } + else + if (get_string(s)) + return; + else + { + if (fail_reason) + syslog(LOG_INFO, "Failed (%s)", fail_reason); + else + syslog(LOG_INFO, "Failed"); + + unlock(); + terminate(1); + } + } + } + +char *character(c) +char 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; + { + if (abort_next) + { + char *s1; + + abort_next = 0; + + if (n_aborts >= MAX_ABORTS) + fatal("Too many ABORT strings"); + + s1 = clean(s, 0); + + if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1) + { + syslog(LOG_WARNING, "Illegal or too-long ABORT string ('%s')", s); + die(); + } + + abort_string[n_aborts++] = s1; + + if (verbose) + { + logf("abort on ("); + + for (s1 = s; *s1; ++s1) + logf(character(*s1)); + + logf(")\n"); + } + } + else + if (timeout_next) + { + timeout_next = 0; + timeout = atoi(s); + + if (timeout <= 0) + timeout = DEFAULT_CHAT_TIMEOUT; + + if (verbose) + { + syslog(LOG_INFO, "timeout set to %d seconds", timeout); + } + } + else + { + if (strcmp(s, "EOT") == 0) + s = "^D\\c"; + else + if (strcmp(s, "BREAK") == 0) + s = "\\K\\c"; + if ( ! put_string(s)) + { + syslog(LOG_INFO, "Failed"); + unlock(); + terminate(1); + } + } + } + +int get_char() + { + int status; + char c; + + status = read(0, &c, 1); + + switch (status) + { + case 1: + return ((int)c & 0x7F); + + default: + syslog(LOG_WARNING, "warning: read() on stdin returned %d", + status); + + case -1: + if ((status = fcntl(0, F_GETFL, 0)) == -1) + sysfatal("Can't get file mode flags on stdin"); + else + if (fcntl(0, F_SETFL, status & ~FNDELAY) == -1) + sysfatal("Can't set file mode flags on stdin"); + + return (-1); + } + } + +int put_char(c) +char c; + { + int status; + + delay(); + + status = write(1, &c, 1); + + switch (status) + { + case 1: + return (0); + + default: + syslog(LOG_WARNING, "warning: write() on stdout returned %d", + status); + + case -1: + if ((status = fcntl(0, F_GETFL, 0)) == -1) + sysfatal("Can't get file mode flags on stdin"); + else + if (fcntl(0, F_SETFL, status & ~FNDELAY) == -1) + sysfatal("Can't set file mode flags on stdin"); + + return (-1); + } + } + +int write_char (c) +int c; + { + if (alarmed || put_char(c) < 0) + { + extern int errno; + + alarm(0); alarmed = 0; + + if (verbose) + { + if (errno == EINTR || errno == EWOULDBLOCK) + syslog(LOG_INFO, " -- write timed out"); + else + syslog(LOG_INFO, " -- write failed: %m"); + } + return (0); + } + return (1); + } + +int put_string (s) +register char *s; + { + s = clean(s, 1); + + if (verbose) + { + logf("send ("); + + if (quiet) + logf("??????"); + else + { + register char *s1 = s; + + for (s1 = s; *s1; ++s1) + logf(character(*s1)); + } + + logf(")\n"); + } + + 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. */ + break; + + default: + if (!write_char (c)) + return 0; + break; + } + } + + alarm(0); + alarmed = 0; + return (1); + } + +/* + * '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; + + fail_reason = (char *)0; + string = clean(string, 0); + len = strlen(string); + minlen = (len > sizeof(fail_buffer)? len: sizeof(fail_buffer)) - 1; + + if (verbose) + { + register char *s1; + + logf("expect ("); + + for (s1 = string; *s1; ++s1) + logf(character(*s1)); + + logf(")\n"); + } + + if (len > STR_LEN) + { + syslog(LOG_INFO, "expect string is too long"); + return 0; + } + + if (len == 0) + { + if (verbose) + { + syslog(LOG_INFO, "got it"); + } + + return (1); + } + + alarm(timeout); alarmed = 0; + + while ( ! alarmed && (c = get_char()) >= 0) + { + int n, abort_len; + + if (verbose) + { + if (c == '\n') + logf("\n"); + else + logf(character(c)); + } + + *s++ = c; + + if (s - temp >= len && + c == string[len - 1] && + strncmp(s - len, string, len) == 0) + { + if (verbose) + { + 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) + { + logf(" -- failed\n"); + } + + alarm(0); alarmed = 0; + strcpy(fail_reason = fail_buffer, abort_string[n]); + return (0); + } + + if (s >= end) + { + strncpy(temp, s - minlen, minlen); + s = temp + minlen; + } + + if (alarmed && verbose) + syslog(LOG_WARNING, "warning: alarm synchronization problem"); + } + + alarm(0); + + if (verbose && printed) + { + if (alarmed) + logf(" -- read timed out\n"); + else + { + logflush(); + syslog(LOG_INFO, " -- read failed: %m"); + } + } + + alarmed = 0; + return (0); + } + +#ifdef ultrix +#undef NO_USLEEP +#include +#include + +/* + 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 + +/* + * Delay an amount appropriate for between typed characters. + */ +void delay() + { +# ifdef NO_USLEEP + register int i; + + for (i = 0; i < 30000; ++i) /* ... did we just say appropriate? */ + ; +# else /* NO_USLEEP */ + usleep(100); +# endif /* NO_USLEEP */ + }