mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-20 07:14:06 +00:00
6140ba1177
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.
688 lines
15 KiB
C
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);
|
|
}
|