freebsd-dev/usr.sbin/ppp/chat.c
Brian Somers 6140ba1177 Create struct async' and make it part of struct physical'.
This structure contains the asynchronous state of the physical
link.
Unfortunately, just about every .h file is included in every .c
file now.  Fixing this can be one of the last jobs.
1998-02-02 19:33:40 +00:00

688 lines
15 KiB
C

/*
* Written by Toshiharu OHNO (tony-o@iij.ad.jp)
*
* Copyright (C) 1993, Internet Initiative Japan, Inc. All rights reserverd.
*
* Most of codes are derived from chat.c by Karl Fox (karl@MorningStar.Com).
*
* 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:
*
* Karl Fox <karl@MorningStar.Com>
* Morning Star Technologies, Inc.
* 1760 Zollinger Road
* Columbus, OH 43221
* (614)451-1883
*
* $Id: chat.c,v 1.44.2.1 1998/01/29 00:49:14 brian Exp $
*
* TODO:
* o Support more UUCP compatible control sequences.
* o Dialing shoud not block monitor process.
* o Reading modem by select should be unified into main.c
*/
#include <sys/param.h>
#include <netinet/in.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include "command.h"
#include "mbuf.h"
#include "log.h"
#include "defs.h"
#include "timer.h"
#include "loadalias.h"
#include "vars.h"
#include "chat.h"
#include "modem.h"
#include "hdlc.h"
#include "throughput.h"
#include "fsm.h"
#include "lcp.h"
#include "link.h"
#include "async.h"
#include "physical.h"
#ifndef isblank
#define isblank(c) ((c) == '\t' || (c) == ' ')
#endif
#define IBSIZE LINE_LEN
static int TimeoutSec;
static int abort_next, timeout_next;
static int numaborts;
static char *AbortStrings[50];
static char inbuff[IBSIZE * 2 + 1];
static jmp_buf ChatEnv;
#define MATCH 1
#define NOMATCH 0
#define ABORT -1
static char *
findblank(char *p, int instring)
{
if (instring) {
while (*p) {
if (*p == '\\') {
strcpy(p, p + 1);
if (!*p)
break;
} else if (*p == '"')
return (p);
p++;
}
} else {
while (*p) {
if (isblank(*p))
return (p);
p++;
}
}
return p;
}
int
MakeArgs(char *script, char **pvect, int maxargs)
{
int nargs, nb;
int instring;
nargs = 0;
while (*script) {
nb = strspn(script, " \t");
script += nb;
if (*script) {
if (*script == '"') {
instring = 1;
script++;
if (*script == '\0')
break; /* Shouldn't return here. Need to null
* terminate below */
} else
instring = 0;
if (nargs >= maxargs - 1)
break;
*pvect++ = script;
nargs++;
script = findblank(script, instring);
if (*script)
*script++ = '\0';
}
}
*pvect = NULL;
return nargs;
}
/*
* \c don't add a cr
* \d Sleep a little (delay 2 seconds
* \n Line feed character
* \P Auth Key password
* \p pause 0.25 sec
* \r Carrige return character
* \s Space character
* \T Telephone number(s) (defined via `set phone')
* \t Tab character
* \U Auth User
*/
char *
ExpandString(const char *str, char *result, int reslen, int sendmode)
{
int addcr = 0;
char *phone;
result[--reslen] = '\0';
if (sendmode)
addcr = 1;
while (*str && reslen > 0) {
switch (*str) {
case '\\':
str++;
switch (*str) {
case 'c':
if (sendmode)
addcr = 0;
break;
case 'd': /* Delay 2 seconds */
nointr_sleep(2);
break;
case 'p':
nointr_usleep(250000);
break; /* Pause 0.25 sec */
case 'n':
*result++ = '\n';
reslen--;
break;
case 'r':
*result++ = '\r';
reslen--;
break;
case 's':
*result++ = ' ';
reslen--;
break;
case 't':
*result++ = '\t';
reslen--;
break;
case 'P':
strncpy(result, VarAuthKey, reslen);
reslen -= strlen(result);
result += strlen(result);
break;
case 'T':
if (VarAltPhone == NULL) {
if (VarNextPhone == NULL) {
strncpy(VarPhoneCopy, VarPhoneList, sizeof VarPhoneCopy - 1);
VarPhoneCopy[sizeof VarPhoneCopy - 1] = '\0';
VarNextPhone = VarPhoneCopy;
}
VarAltPhone = strsep(&VarNextPhone, ":");
}
phone = strsep(&VarAltPhone, "|");
strncpy(result, phone, reslen);
reslen -= strlen(result);
result += strlen(result);
if (VarTerm)
fprintf(VarTerm, "Phone: %s\n", phone);
LogPrintf(LogPHASE, "Phone: %s\n", phone);
break;
case 'U':
strncpy(result, VarAuthName, reslen);
reslen -= strlen(result);
result += strlen(result);
break;
default:
reslen--;
*result++ = *str;
break;
}
if (*str)
str++;
break;
case '^':
str++;
if (*str) {
*result++ = *str++ & 0x1f;
reslen--;
}
break;
default:
*result++ = *str++;
reslen--;
break;
}
}
if (--reslen > 0) {
if (addcr)
*result++ = '\r';
}
if (--reslen > 0)
*result++ = '\0';
return (result);
}
#define MAXLOGBUFF LINE_LEN
static char logbuff[MAXLOGBUFF];
static int loglen = 0;
static void
clear_log(void)
{
memset(logbuff, 0, MAXLOGBUFF);
loglen = 0;
}
static void
flush_log(void)
{
if (LogIsKept(LogCONNECT))
LogPrintf(LogCONNECT, "%s\n", logbuff);
else if (LogIsKept(LogCARRIER) && strstr(logbuff, "CARRIER"))
LogPrintf(LogCARRIER, "%s\n", logbuff);
clear_log();
}
static void
connect_log(const char *str, int single_p)
{
int space = MAXLOGBUFF - loglen - 1;
while (space--) {
if (*str == '\n') {
flush_log();
} else {
logbuff[loglen++] = *str;
}
if (single_p || !*++str)
break;
}
if (!space)
flush_log();
}
static void
ExecStr(struct physical *physical, char *command, char *out, int olen)
{
pid_t pid;
int fids[2];
char *vector[MAXARGS], *startout, *endout;
int stat, nb;
LogPrintf(LogCHAT, "Exec: %s\n", command);
MakeArgs(command, vector, VECSIZE(vector));
if (pipe(fids) < 0) {
LogPrintf(LogCHAT, "Unable to create pipe in ExecStr: %s\n",
strerror(errno));
longjmp(ChatEnv, 2);
}
if ((pid = fork()) == 0) {
TermTimerService();
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGALRM, SIG_DFL);
/* XXX-ML This looks like it might need more encapsulation. */
if (Physical_GetFD(physical) == 2) {
Physical_DupAndClose(physical);
}
close(fids[0]);
dup2(fids[1], 2);
close(fids[1]);
dup2(Physical_GetFD(physical), 0);
dup2(Physical_GetFD(physical), 1);
if ((nb = open("/dev/tty", O_RDWR)) > 3) {
dup2(nb, 3);
close(nb);
}
setuid(geteuid());
execvp(vector[0], vector);
fprintf(stderr, "execvp failed: %s: %s\n", vector[0], strerror(errno));
exit(127);
} else {
char *name = strdup(vector[0]);
close(fids[1]);
endout = out + olen - 1;
startout = out;
while (out < endout) {
nb = read(fids[0], out, 1);
if (nb <= 0)
break;
out++;
}
*out = '\0';
close(fids[0]);
close(fids[1]);
waitpid(pid, &stat, WNOHANG);
if (WIFSIGNALED(stat)) {
LogPrintf(LogWARN, "%s: signal %d\n", name, WTERMSIG(stat));
free(name);
longjmp(ChatEnv, 3);
} else if (WIFEXITED(stat)) {
switch (WEXITSTATUS(stat)) {
case 0:
free(name);
break;
case 127:
LogPrintf(LogWARN, "%s: %s\n", name, startout);
free(name);
longjmp(ChatEnv, 4);
break;
default:
LogPrintf(LogWARN, "%s: exit %d\n", name, WEXITSTATUS(stat));
free(name);
longjmp(ChatEnv, 5);
break;
}
} else {
LogPrintf(LogWARN, "%s: Unexpected exit result\n", name);
free(name);
longjmp(ChatEnv, 6);
}
}
}
static int
WaitforString(struct physical *physical, const char *estr)
{
struct timeval timeout;
char *s, *str, ch;
char *inp;
fd_set rfds;
int i, nfds, nb;
char buff[IBSIZE];
#ifdef SIGALRM
int omask;
omask = sigblock(sigmask(SIGALRM));
#endif
clear_log();
if (*estr == '!') {
ExpandString(estr + 1, buff, sizeof buff, 0);
ExecStr(physical, buff, buff, sizeof buff);
} else {
ExpandString(estr, buff, sizeof buff, 0);
}
if (LogIsKept(LogCHAT)) {
s = buff + strlen(buff) - 1;
while (s >= buff && *s == '\n')
s--;
if (!strcmp(estr, buff))
LogPrintf(LogCHAT, "Wait for (%d): %.*s\n",
TimeoutSec, s - buff + 1, buff);
else
LogPrintf(LogCHAT, "Wait for (%d): %s --> %.*s\n",
TimeoutSec, estr, s - buff + 1, buff);
}
if (buff[0] == '\0')
return (MATCH);
str = buff;
inp = inbuff;
if (strlen(str) >= IBSIZE) {
str[IBSIZE - 1] = 0;
LogPrintf(LogCHAT, "Truncating String to %d character: %s\n", IBSIZE, str);
}
/* XXX-ML - this look REALLY fishy. */
nfds = Physical_GetFD(physical) + 1;
s = str;
for (;;) {
FD_ZERO(&rfds);
FD_SET(Physical_GetFD(physical), &rfds);
/*
* Because it is not clear whether select() modifies timeout value, it is
* better to initialize timeout values everytime.
*/
timeout.tv_sec = TimeoutSec;
timeout.tv_usec = 0;
i = select(nfds, &rfds, NULL, NULL, &timeout);
#ifdef notdef
TimerService();
#endif
if (i < 0) {
#ifdef SIGALRM
if (errno == EINTR)
continue;
sigsetmask(omask);
#endif
LogPrintf(LogERROR, "WaitForString: select(): %s\n", strerror(errno));
*inp = 0;
return (NOMATCH);
} else if (i == 0) { /* Timeout reached! */
*inp = 0;
if (inp != inbuff)
LogPrintf(LogCHAT, "Got: %s\n", inbuff);
LogPrintf(LogCHAT, "Can't get (%d).\n", timeout.tv_sec);
#ifdef SIGALRM
sigsetmask(omask);
#endif
return (NOMATCH);
}
if (Physical_FD_ISSET(physical, &rfds)) { /* got something */
if (Physical_IsSync(physical)) {
int length;
if ((length = strlen(inbuff)) > IBSIZE) {
/* shuffle down next part */
memcpy(inbuff, &(inbuff[IBSIZE]), IBSIZE + 1);
length = strlen(inbuff);
}
if (length + IBSIZE > sizeof(inbuff))
abort(); /* Bug & security problem */
nb = Physical_Read(physical, &(inbuff[length]), IBSIZE);
inbuff[nb + length] = 0;
connect_log(inbuff, 0);
if (strstr(inbuff, str)) {
#ifdef SIGALRM
sigsetmask(omask);
#endif
flush_log();
return (MATCH);
}
for (i = 0; i < numaborts; i++) {
if (strstr(inbuff, AbortStrings[i])) {
LogPrintf(LogCHAT, "Abort: %s\n", AbortStrings[i]);
#ifdef SIGALRM
sigsetmask(omask);
#endif
flush_log();
return (ABORT);
}
}
} else {
if (Physical_Read(physical, &ch, 1) < 0) {
LogPrintf(LogERROR, "read error: %s\n", strerror(errno));
*inp = '\0';
return (NOMATCH);
}
connect_log(&ch, 1);
*inp++ = ch;
if (ch == *s) {
s++;
if (*s == '\0') {
#ifdef SIGALRM
sigsetmask(omask);
#endif
*inp = 0;
flush_log();
return (MATCH);
}
} else
s = str;
if (inp == inbuff + IBSIZE) {
memcpy(inbuff, inp - 100, 100);
inp = inbuff + 100;
}
if (s == str) {
for (i = 0; i < numaborts; i++) { /* Look for Abort strings */
int len;
char *s1;
s1 = AbortStrings[i];
len = strlen(s1);
if ((len <= inp - inbuff) && (strncmp(inp - len, s1, len) == 0)) {
LogPrintf(LogCHAT, "Abort: %s\n", s1);
*inp = 0;
#ifdef SIGALRM
sigsetmask(omask);
#endif
flush_log();
return (ABORT);
}
}
}
}
}
}
}
static void
SendString(struct physical *physical, const char *str)
{
char *cp;
int on;
char buff[LINE_LEN];
if (abort_next) {
abort_next = 0;
ExpandString(str, buff, sizeof buff, 0);
AbortStrings[numaborts++] = strdup(buff);
} else if (timeout_next) {
timeout_next = 0;
TimeoutSec = atoi(str);
if (TimeoutSec <= 0)
TimeoutSec = 30;
} else {
if (*str == '!') {
ExpandString(str + 1, buff + 2, sizeof buff - 2, 0);
ExecStr(physical, buff + 2, buff + 2, sizeof buff - 2);
} else {
ExpandString(str, buff + 2, sizeof buff - 2, 1);
}
if (strstr(str, "\\P")) /* Do not log the password itself. */
LogPrintf(LogCHAT, "Sending: %s", str);
else {
cp = buff + strlen(buff + 2) + 1;
while (cp >= buff + 2 && *cp == '\n')
cp--;
LogPrintf(LogCHAT, "Sending: %.*s\n", cp - buff - 1, buff + 2);
}
cp = buff;
if (Physical_IsSync(physical))
memcpy(buff, "\377\003", 2); /* Prepend HDLC header */
else
cp += 2;
on = strlen(cp);
/* XXX - missing return value check */
Physical_Write(physical, cp, on);
}
}
static int
ExpectString(struct physical *physical, char *str)
{
char *minus;
int state;
if (strcmp(str, "ABORT") == 0) {
++abort_next;
return (MATCH);
}
if (strcmp(str, "TIMEOUT") == 0) {
++timeout_next;
return (MATCH);
}
LogPrintf(LogCHAT, "Expecting: %s\n", str);
while (*str) {
/*
* Check whether if string contains sub-send-expect.
*/
for (minus = str; *minus; minus++) {
if (*minus == '-') {
if (minus == str || minus[-1] != '\\')
break;
}
}
if (*minus == '-') { /* We have sub-send-expect. */
*minus = '\0'; /* XXX: Cheat with the const string */
state = WaitforString(physical, str);
*minus = '-'; /* XXX: Cheat with the const string */
minus++;
if (state != NOMATCH)
return (state);
/*
* Can't get expect string. Sendout send part.
*/
str = minus;
for (minus = str; *minus; minus++) {
if (*minus == '-') {
if (minus == str || minus[-1] != '\\')
break;
}
}
if (*minus == '-') {
*minus = '\0'; /* XXX: Cheat with the const string */
SendString(physical, str);
*minus = '-'; /* XXX: Cheat with the const string */
str = ++minus;
} else {
SendString(physical, str);
return (MATCH);
}
} else {
/*
* Simple case. Wait for string.
*/
return (WaitforString(physical, str));
}
}
return (MATCH);
}
static void (*oint) (int);
static void
StopDial(int sig)
{
LogPrintf(LogPHASE, "DoChat: Caught signal %d, abort connect\n", sig);
longjmp(ChatEnv, 1);
}
int
DoChat(struct physical *physical, char *script)
{
char *vector[MAXARGS];
char *const *argv;
int argc, n, state, err;
if (!script || !*script)
return MATCH;
if ((err = setjmp(ChatEnv))) {
signal(SIGINT, oint);
if (err == 1)
/* Caught a SIGINT during chat */
return (-1);
return (NOMATCH);
}
oint = signal(SIGINT, StopDial);
timeout_next = abort_next = 0;
for (n = 0; AbortStrings[n]; n++) {
free(AbortStrings[n]);
AbortStrings[n] = NULL;
}
numaborts = 0;
memset(vector, '\0', sizeof vector);
argc = MakeArgs(script, vector, VECSIZE(vector));
argv = vector;
TimeoutSec = 30;
while (*argv) {
if (strcmp(*argv, "P_ZERO") == 0 ||
strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) {
ChangeParity(physical, *argv++);
continue;
}
state = ExpectString(physical, *argv++);
switch (state) {
case MATCH:
if (*argv)
SendString(physical, *argv++);
break;
case ABORT:
case NOMATCH:
signal(SIGINT, oint);
return (NOMATCH);
}
}
signal(SIGINT, oint);
return (MATCH);
}