freebsd-dev/usr.sbin/ppp/chat.c
Brian Somers 1fa665f5b3 o Add the link name to modem diagnostics.
o Create struct mpserver as part of struct mp.
  mpserver creates a unix-domain socket based on the
  peers auth name and endpoint discriminator.  If it
  already exists, ppp will ``pass the link'' over to
  the owner of the socket, joining it into the bundle
  of another ppp invocation, otherwise ppp waits for
  other invocations to pass it links through this
  socket.
  The final piece of code will be the code that flattens
  our datalink info and passes it down this channel
  (not yet implemented).
1998-04-28 01:25:46 +00:00

800 lines
20 KiB
C

/*-
* Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: chat.c,v 1.44.2.25 1998/04/19 15:24:37 brian Exp $
*/
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include "mbuf.h"
#include "log.h"
#include "defs.h"
#include "timer.h"
#include "lqr.h"
#include "hdlc.h"
#include "throughput.h"
#include "fsm.h"
#include "lcp.h"
#include "ccp.h"
#include "link.h"
#include "async.h"
#include "descriptor.h"
#include "physical.h"
#include "chat.h"
#include "mp.h"
#include "auth.h"
#include "chap.h"
#include "slcompress.h"
#include "iplist.h"
#include "ipcp.h"
#include "filter.h"
#include "datalink.h"
#include "bundle.h"
#define BUFLEFT(c) (sizeof (c)->buf - ((c)->bufend - (c)->buf))
#define issep(c) ((c) == '\t' || (c) == ' ')
static void ExecStr(struct physical *, char *, char *, int);
static char *ExpandString(struct chat *, const char *, char *, int, int);
static void
chat_PauseTimer(void *v)
{
struct chat *c = (struct chat *)v;
StopTimer(&c->pause);
c->pause.load = 0;
}
static void
chat_Pause(struct chat *c, u_long load)
{
StopTimer(&c->pause);
c->pause.load += load;
c->pause.func = chat_PauseTimer;
c->pause.name = "chat pause";
c->pause.arg = c;
StartTimer(&c->pause);
}
static void
chat_TimeoutTimer(void *v)
{
struct chat *c = (struct chat *)v;
StopTimer(&c->timeout);
c->TimedOut = 1;
}
static void
chat_SetTimeout(struct chat *c)
{
StopTimer(&c->timeout);
if (c->TimeoutSec > 0) {
c->timeout.load = SECTICKS * c->TimeoutSec;
c->timeout.func = chat_TimeoutTimer;
c->timeout.name = "chat timeout";
c->timeout.arg = c;
StartTimer(&c->timeout);
}
}
static char *
chat_NextChar(char *ptr, char ch)
{
for (; *ptr; ptr++)
if (*ptr == ch)
return ptr;
else if (*ptr == '\\')
if (*++ptr == '\0')
return NULL;
return NULL;
}
static int
chat_UpdateSet(struct descriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
{
struct chat *c = descriptor2chat(d);
int special, gotabort, gottimeout, needcr;
int TimedOut = c->TimedOut;
static char arg_term;
if (c->pause.state == TIMER_RUNNING)
return 0;
if (TimedOut) {
LogPrintf(LogCHAT, "Expect timeout\n");
if (c->nargptr == NULL)
c->state = CHAT_FAILED;
else {
/* c->state = CHAT_EXPECT; */
c->argptr = &arg_term;
}
c->TimedOut = 0;
}
if (c->state != CHAT_EXPECT && c->state != CHAT_SEND)
return 0;
gottimeout = gotabort = 0;
if (c->arg < c->argc && (c->arg < 0 || *c->argptr == '\0')) {
/* Go get the next string */
if (c->arg < 0 || c->state == CHAT_SEND)
c->state = CHAT_EXPECT;
else
c->state = CHAT_SEND;
special = 1;
while (special && (c->nargptr || c->arg < c->argc - 1)) {
if (c->arg < 0 || (!TimedOut && c->state == CHAT_SEND))
c->nargptr = NULL;
if (c->nargptr != NULL) {
/* We're doing expect-send-expect.... */
c->argptr = c->nargptr;
/* Put the '-' back in case we ever want to rerun our script */
c->nargptr[-1] = '-';
c->nargptr = chat_NextChar(c->nargptr, '-');
if (c->nargptr != NULL)
*c->nargptr++ = '\0';
} else {
int minus;
c->argptr = c->argv[++c->arg];
if (c->state == CHAT_EXPECT) {
/* Look for expect-send-expect sequence */
c->nargptr = c->argptr;
minus = 0;
while ((c->nargptr = chat_NextChar(c->nargptr, '-'))) {
c->nargptr++;
minus++;
}
if (minus % 2)
LogPrintf(LogWARN, "chat_UpdateSet: \"%s\": Uneven number of"
" '-' chars, all ignored\n", c->argptr);
else if (minus) {
c->nargptr = chat_NextChar(c->argptr, '-');
*c->nargptr++ = '\0';
}
}
}
/*
* c->argptr now temporarily points into c->script (via c->argv)
* If it's an expect-send-expect sequence, we've just got the correct
* portion of that sequence.
*/
needcr = c->state == CHAT_SEND && *c->argptr != '!';
/* We leave room for a potential HDLC header in the target string */
ExpandString(c, c->argptr, c->exp + 2, sizeof c->exp - 2, needcr);
if (gotabort) {
if (c->abort.num < MAXABORTS) {
int len, i;
len = strlen(c->exp+2);
for (i = 0; i < c->abort.num; i++)
if (len > c->abort.string[i].len) {
int last;
for (last = c->abort.num; last > i; last--) {
c->abort.string[last].data = c->abort.string[last-1].data;
c->abort.string[last].len = c->abort.string[last-1].len;
}
break;
}
c->abort.string[i].len = len;
c->abort.string[i].data = (char *)malloc(len+1);
memcpy(c->abort.string[i].data, c->exp+2, len+1);
c->abort.num++;
} else
LogPrintf(LogERROR, "chat_UpdateSet: too many abort strings\n");
gotabort = 0;
} else if (gottimeout) {
c->TimeoutSec = atoi(c->exp + 2);
if (c->TimeoutSec <= 0)
c->TimeoutSec = 30;
gottimeout = 0;
} else if (c->nargptr == NULL && !strcmp(c->exp+2, "ABORT"))
gotabort = 1;
else if (c->nargptr == NULL && !strcmp(c->exp+2, "TIMEOUT"))
gottimeout = 1;
else {
if (c->exp[2] == '!')
ExecStr(c->physical, c->exp + 3, c->exp + 2, sizeof c->exp - 2);
if (c->exp[2] == '\0') {
/* Empty string, reparse (this may be better as a `goto start') */
c->argptr = &arg_term;
return chat_UpdateSet(d, r, w, e, n);
}
special = 0;
}
}
if (special) {
if (gottimeout)
LogPrintf(LogWARN, "chat_UpdateSet: TIMEOUT: Argument expected\n");
else if (gotabort)
LogPrintf(LogWARN, "chat_UpdateSet: ABORT: Argument expected\n");
/* End of script - all ok */
c->state = CHAT_DONE;
return 0;
}
/* set c->argptr to point in the right place */
c->argptr = c->exp + 2;
c->arglen = strlen(c->argptr);
if (c->state == CHAT_EXPECT) {
/* We must check to see if the string's already been found ! */
char *begin, *end;
end = c->bufend - c->arglen + 1;
if (end < c->bufstart)
end = c->bufstart;
for (begin = c->bufstart; begin < end; begin++)
if (!strncmp(begin, c->argptr, c->arglen)) {
c->bufstart = begin + c->arglen;
c->argptr += c->arglen;
c->arglen = 0;
/* Continue - we've already read our expect string */
return chat_UpdateSet(d, r, w, e, n);
}
LogPrintf(LogCHAT, "Expect(%d): %s\n", c->TimeoutSec, c->argptr);
chat_SetTimeout(c);
}
}
/*
* We now have c->argptr pointing at what we want to expect/send and
* c->state saying what we want to do... we now know what to put in
* the fd_set :-)
*/
if (c->state == CHAT_EXPECT)
return Physical_UpdateSet(&c->physical->desc, r, NULL, e, n, 1);
else
return Physical_UpdateSet(&c->physical->desc, NULL, w, e, n, 1);
}
static int
chat_IsSet(struct descriptor *d, const fd_set *fdset)
{
struct chat *c = descriptor2chat(d);
return Physical_IsSet(&c->physical->desc, fdset);
}
static void
chat_UpdateLog(struct chat *c, int in)
{
if (LogIsKept(LogCHAT) || LogIsKept(LogCONNECT)) {
/*
* If a linefeed appears in the last `in' characters of `c's input
* buffer, output from there, all the way back to the last linefeed.
* This is called for every read of `in' bytes.
*/
char *ptr, *end, *stop, ch;
int level;
level = LogIsKept(LogCHAT) ? LogCHAT : LogCONNECT;
if (in == -1)
end = ptr = c->bufend;
else {
ptr = c->bufend - in;
for (end = c->bufend - 1; end >= ptr; end--)
if (*end == '\n')
break;
}
if (end >= ptr) {
for (ptr = c->bufend - (in == -1 ? 1 : in + 1); ptr >= c->bufstart; ptr--)
if (*ptr == '\n')
break;
ptr++;
stop = NULL;
while (stop < end) {
if ((stop = memchr(ptr, '\n', end - ptr)) == NULL)
stop = end;
ch = *stop;
*stop = '\0';
if (level == LogCHAT || strstr(ptr, "CONNECT"))
LogPrintf(level, "Received: %s\n", ptr);
*stop = ch;
ptr = stop + 1;
}
}
}
}
static void
chat_Read(struct descriptor *d, struct bundle *bundle, const fd_set *fdset)
{
struct chat *c = descriptor2chat(d);
if (c->state == CHAT_EXPECT) {
ssize_t in;
char *abegin, *ebegin, *begin, *aend, *eend, *end;
int n;
/*
* XXX - should this read only 1 byte to guarantee that we don't
* swallow any ppp talk from the peer ?
*/
in = BUFLEFT(c);
if (in > sizeof c->buf / 2)
in = sizeof c->buf / 2;
in = Physical_Read(c->physical, c->bufend, in);
if (in <= 0)
return;
/* `begin' and `end' delimit where we're going to strncmp() from */
ebegin = c->bufend - c->arglen + 1;
eend = ebegin + in;
if (ebegin < c->bufstart)
ebegin = c->bufstart;
if (c->abort.num) {
abegin = c->bufend - c->abort.string[0].len + 1;
aend = c->bufend - c->abort.string[c->abort.num-1].len + in + 1;
if (abegin < c->bufstart)
abegin = c->bufstart;
} else {
abegin = ebegin;
aend = eend;
}
begin = abegin < ebegin ? abegin : ebegin;
end = aend < eend ? eend : aend;
c->bufend += in;
chat_UpdateLog(c, in);
if (c->bufend > c->buf + sizeof c->buf / 2) {
/* Shuffle our receive buffer back a bit */
int chop;
for (chop = begin - c->buf; chop; chop--)
if (c->buf[chop] == '\n')
/* found some already-logged garbage to remove :-) */
break;
if (!chop)
chop = begin - c->buf;
if (chop) {
char *from, *to;
to = c->buf;
from = to + chop;
while (from < c->bufend)
*to++ = *from++;
c->bufstart -= chop;
c->bufend -= chop;
begin -= chop;
end -= chop;
abegin -= chop;
aend -= chop;
ebegin -= chop;
eend -= chop;
}
}
for (; begin < end; begin++)
if (begin >= ebegin && begin < eend &&
!strncmp(begin, c->argptr, c->arglen)) {
/* Got it ! */
if (memchr(begin + c->arglen - 1, '\n',
c->bufend - begin - c->arglen + 1) == NULL) {
/* force it into the log */
end = c->bufend;
c->bufend = begin + c->arglen;
chat_UpdateLog(c, -1);
c->bufend = end;
}
c->bufstart = begin + c->arglen;
c->argptr += c->arglen;
c->arglen = 0;
break;
} else if (begin >= abegin && begin < aend) {
for (n = c->abort.num - 1; n >= 0; n--) {
if (begin + c->abort.string[n].len > c->bufend)
break;
if (!strncmp(begin, c->abort.string[n].data,
c->abort.string[n].len)) {
if (memchr(begin + c->abort.string[n].len - 1, '\n',
c->bufend - begin - c->abort.string[n].len + 1) == NULL) {
/* force it into the log */
end = c->bufend;
c->bufend = begin + c->abort.string[n].len;
chat_UpdateLog(c, -1);
c->bufend = end;
}
c->bufstart = begin + c->abort.string[n].len;
c->state = CHAT_FAILED;
return;
}
}
}
}
}
static void
chat_Write(struct descriptor *d, struct bundle *bundle, const fd_set *fdset)
{
struct chat *c = descriptor2chat(d);
if (c->state == CHAT_SEND) {
int wrote;
if (strstr(c->argv[c->arg], "\\P")) /* Don't log the password */
LogPrintf(LogCHAT, "Send: %s\n", c->argv[c->arg]);
else {
int sz;
sz = c->arglen - 1;
while (sz >= 0 && c->argptr[sz] == '\n')
sz--;
LogPrintf(LogCHAT, "Send: %.*s\n", sz + 1, c->argptr);
}
if (Physical_IsSync(c->physical)) {
/* There's always room for the HDLC header */
c->argptr -= 2;
c->arglen += 2;
memcpy(c->argptr, "\377\003", 2); /* Prepend HDLC header */
}
wrote = Physical_Write(c->physical, c->argptr, c->arglen);
if (wrote == -1) {
if (errno != EINTR)
LogPrintf(LogERROR, "chat_Write: %s\n", strerror(errno));
if (Physical_IsSync(c->physical)) {
c->argptr += 2;
c->arglen -= 2;
}
} else if (wrote < 2 && Physical_IsSync(c->physical)) {
/* Oops - didn't even write our HDLC header ! */
c->argptr += 2;
c->arglen -= 2;
} else {
c->argptr += wrote;
c->arglen -= wrote;
}
}
}
void
chat_Init(struct chat *c, struct physical *p, const char *data, int emptybuf,
const char *phone)
{
c->desc.type = CHAT_DESCRIPTOR;
c->desc.next = NULL;
c->desc.UpdateSet = chat_UpdateSet;
c->desc.IsSet = chat_IsSet;
c->desc.Read = chat_Read;
c->desc.Write = chat_Write;
c->physical = p;
c->state = CHAT_EXPECT;
if (data == NULL) {
*c->script = '\0';
c->argc = 0;
} else {
strncpy(c->script, data, sizeof c->script - 1);
c->script[sizeof c->script - 1] = '\0';
c->argc = MakeArgs(c->script, c->argv, VECSIZE(c->argv));
}
c->arg = -1;
c->argptr = NULL;
c->nargptr = NULL;
if (emptybuf)
c->bufstart = c->bufend = c->buf;
c->TimeoutSec = 30;
c->TimedOut = 0;
c->phone = phone;
c->abort.num = 0;
memset(&c->pause, '\0', sizeof c->pause);
memset(&c->timeout, '\0', sizeof c->timeout);
}
void
chat_Destroy(struct chat *c)
{
StopTimer(&c->pause);
StopTimer(&c->timeout);
while (c->abort.num)
free(c->abort.string[--c->abort.num].data);
c->abort.num = 0;
}
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 (issep(*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
*/
static char *
ExpandString(struct chat *c, const char *str, char *result, int reslen,
int sendmode)
{
int addcr = 0;
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 */
chat_Pause(c, 2 * SECTICKS);
break;
case 'p':
chat_Pause(c, SECTICKS / 4);
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, c->physical->dl->bundle->cfg.auth.key, reslen);
reslen -= strlen(result);
result += strlen(result);
break;
case 'T':
strncpy(result, c->phone, reslen);
reslen -= strlen(result);
result += strlen(result);
break;
case 'U':
strncpy(result, c->physical->dl->bundle->cfg.auth.name, 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);
}
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));
*out = '\0';
return;
}
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);
*out = '\0';
return;
} else if (WIFEXITED(stat)) {
switch (WEXITSTATUS(stat)) {
case 0:
free(name);
break;
case 127:
LogPrintf(LogWARN, "%s: %s\n", name, startout);
free(name);
*out = '\0';
return;
break;
default:
LogPrintf(LogWARN, "%s: exit %d\n", name, WEXITSTATUS(stat));
free(name);
*out = '\0';
return;
break;
}
} else {
LogPrintf(LogWARN, "%s: Unexpected exit result\n", name);
free(name);
*out = '\0';
return;
}
}
}