Allow (and document) execution of commands from within

our chat script.
You can now even run chat(8) - see ppp.conf.sample.
This commit is contained in:
Brian Somers 1997-12-27 07:22:12 +00:00
parent 6293b3997d
commit 313572f3e3
5 changed files with 286 additions and 103 deletions

View File

@ -4,7 +4,7 @@
#
# Originally written by Toshiharu OHNO
#
# $Id: ppp.conf.sample,v 1.24 1997/11/12 00:52:16 brian Exp $
# $Id: ppp.conf.sample,v 1.25 1997/11/18 19:21:47 brian Exp $
#
#################################################################
@ -125,6 +125,10 @@ examples:
#
set server 6670
#
# If you don't like ppp's builtin chat, use an external one:
#
set login "\"!chat \\\\-f /etc/ppp/ppp.dev.chat\""
#
# If we have a ``strange'' modem that must be re-initialized when we
# hangup:
#

View File

@ -18,7 +18,7 @@
* Columbus, OH 43221
* (614)451-1883
*
* $Id: chat.c,v 1.41 1997/12/23 22:38:51 brian Exp $
* $Id: chat.c,v 1.42 1997/12/24 09:28:54 brian Exp $
*
* TODO:
* o Support more UUCP compatible control sequences.
@ -66,6 +66,7 @@ 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
@ -275,6 +276,92 @@ connect_log(const char *str, int single_p)
flush_log();
}
static void
ExecStr(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);
if (modem == 2) {
int nmodem;
nmodem = dup(modem);
close(modem);
modem = nmodem;
}
close(fids[0]);
dup2(fids[1], 2);
close(fids[1]);
dup2(modem, 0);
dup2(modem, 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(const char *estr)
{
@ -284,16 +371,33 @@ WaitforString(const char *estr)
fd_set rfds;
int i, nfds, nb;
char buff[IBSIZE];
#ifdef SIGALRM
int omask;
omask = sigblock(sigmask(SIGALRM));
#endif
clear_log();
ExpandString(estr, buff, sizeof buff, 0);
LogPrintf(LogCHAT, "Wait for (%d): %s --> %s\n", TimeoutSec, estr, buff);
if (*estr == '!') {
ExpandString(estr + 1, buff, sizeof buff, 0);
ExecStr(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;
@ -412,74 +516,6 @@ WaitforString(const char *estr)
}
}
static void
ExecStr(char *command, char *out)
{
int pid;
int fids[2];
char *vector[MAXARGS];
int stat, nb;
char *cp;
char tmp[300];
cp = inbuff + strlen(inbuff) - 1;
while (cp > inbuff) {
if (*cp < ' ' && *cp != '\t') {
cp++;
break;
}
cp--;
}
if (snprintf(tmp, sizeof tmp, "%s %s", command, cp) >= sizeof tmp) {
LogPrintf(LogCHAT, "Too long string to ExecStr: \"%s\"\n", command);
return;
}
MakeArgs(tmp, vector, VECSIZE(vector));
if (pipe(fids) < 0) {
LogPrintf(LogCHAT, "Unable to create pipe in ExecStr: %s\n",
strerror(errno));
return;
}
pid = fork();
if (pid == 0) {
TermTimerService();
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGALRM, SIG_DFL);
close(fids[0]);
if (dup2(fids[1], 1) < 0) {
LogPrintf(LogCHAT, "dup2(fids[1], 1) in ExecStr: %s\n", strerror(errno));
return;
}
close(fids[1]);
nb = open("/dev/tty", O_RDWR);
if (dup2(nb, 0) < 0) {
LogPrintf(LogCHAT, "dup2(nb, 0) in ExecStr: %s\n", strerror(errno));
return;
}
setuid(geteuid());
LogPrintf(LogCHAT, "exec: %s\n", command);
pid = execvp(command, (char **)vector);
LogPrintf(LogCHAT, "execvp failed for (%d/%d): %s\n", pid, errno, command);
exit(127);
} else {
close(fids[1]);
for (;;) {
nb = read(fids[0], out, 1);
if (nb <= 0)
break;
out++;
}
*out = '\0';
close(fids[0]);
close(fids[1]);
waitpid(pid, &stat, WNOHANG);
}
}
static void
SendString(const char *str)
{
@ -499,14 +535,18 @@ SendString(const char *str)
} else {
if (*str == '!') {
ExpandString(str + 1, buff + 2, sizeof buff - 2, 0);
ExecStr(buff + 2, buff + 2);
ExecStr(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\n", str);
else
LogPrintf(LogCHAT, "sending: %s\n", buff + 2);
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 (DEV_IS_SYNC)
memcpy(buff, "\377\003", 2); /* Prepend HDLC header */
@ -531,9 +571,8 @@ ExpectString(char *str)
++timeout_next;
return (MATCH);
}
LogPrintf(LogCHAT, "Expecting %s\n", str);
LogPrintf(LogCHAT, "Expecting: %s\n", str);
while (*str) {
/*
* Check whether if string contains sub-send-expect.
*/
@ -571,7 +610,6 @@ ExpectString(char *str)
return (MATCH);
}
} else {
/*
* Simple case. Wait for string.
*/
@ -581,7 +619,6 @@ ExpectString(char *str)
return (MATCH);
}
static jmp_buf ChatEnv;
static void (*oint) (int);
static void
@ -596,15 +633,17 @@ DoChat(char *script)
{
char *vector[MAXARGS];
char *const *argv;
int argc, n, state;
int argc, n, state, err;
if (!script || !*script)
return MATCH;
/* While we're chatting, we want an INT to fail us */
if (setjmp(ChatEnv)) {
if ((err = setjmp(ChatEnv))) {
signal(SIGINT, oint);
return (-1);
if (err == 1)
/* Caught a SIGINT during chat */
return (-1);
return (NOMATCH);
}
oint = signal(SIGINT, StopDial);

View File

@ -17,7 +17,7 @@
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id: command.c,v 1.117 1997/12/23 22:38:52 brian Exp $
* $Id: command.c,v 1.118 1997/12/24 09:28:54 brian Exp $
*
*/
#include <sys/param.h>
@ -216,7 +216,6 @@ ShellCommand(struct cmdargs const *arg, int bg)
{
const char *shell;
pid_t shpid;
FILE *oVarTerm;
int argc;
char *argv[MAXARGS];
@ -259,6 +258,13 @@ ShellCommand(struct cmdargs const *arg, int bg)
if ((shpid = fork()) == 0) {
int dtablesize, i, fd;
TermTimerService();
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGALRM, SIG_DFL);
if (VarTerm)
fd = fileno(VarTerm);
else if ((fd = open("/dev/null", O_RDWR)) == -1) {
@ -268,15 +274,6 @@ ShellCommand(struct cmdargs const *arg, int bg)
for (i = 0; i < 3; i++)
dup2(fd, i);
if (fd > 2)
if (VarTerm) {
oVarTerm = VarTerm;
VarTerm = 0;
if (oVarTerm && oVarTerm != stdout)
fclose(oVarTerm);
} else
close(fd);
for (dtablesize = getdtablesize(), i = 3; i < dtablesize; i++)
close(i);
@ -305,15 +302,16 @@ ShellCommand(struct cmdargs const *arg, int bg)
exit(1);
}
} else if (VarTerm)
fprintf(VarTerm, "ppp: Pausing until %s finishes\n", arg->argv[0]);
printf("ppp: Pausing until %s finishes\n", arg->argv[0]);
execvp(argv[0], argv);
} else {
if (VarTerm)
fprintf(VarTerm, "ppp: Pausing until %s finishes\n", shell);
printf("ppp: Pausing until %s finishes\n", shell);
execl(shell, shell, NULL);
}
LogPrintf(LogWARN, "exec() of %s failed\n", arg->argc > 0 ? arg->argv[0] : shell);
LogPrintf(LogWARN, "exec() of %s failed\n",
arg->argc > 0 ? arg->argv[0] : shell);
exit(255);
}
if (shpid == (pid_t) - 1) {
@ -324,7 +322,7 @@ ShellCommand(struct cmdargs const *arg, int bg)
waitpid(shpid, &status, 0);
}
TtyCommandMode(1);
TtyCommandMode(0);
return (0);
}

View File

@ -1,4 +1,4 @@
.\" $Id: ppp.8,v 1.89 1997/12/21 02:34:27 brian Exp $
.\" $Id: ppp.8,v 1.90 1997/12/21 03:16:14 brian Exp $
.Dd 20 September 1995
.Os FreeBSD
.Dt PPP 8
@ -1931,6 +1931,77 @@ This means that in practice you should use two escapes, for example:
set dial "... ATDT\\\\T CONNECT"
.Ed
.Pp
It is also possible to execute external commands from the chat script.
To do this, the first character of the expect or send string is an
exclaimation mark
.Pq Dq \&! .
When the command is executed, standard input and standard output are
directed to the modem device (see the
.Dq set device
command), and standard error is read by
.Nm
and substituted as the expect or send string. If
.Nm
is running in interactive mode, file descriptor 4 is attached to
.Pa /dev/tty .
.Pp
For example (wrapped for readability);
.Bd -literal -offset indent
set login "TIMEOUT 5 \\"\\" \\"\\" login:--login: ppp \e
word: ppp \\"!sh \\\\\\\\-c \\\\\\"echo \\\\\\\\-n label: >&2\\\\\\"\\" \e
\\"!/bin/echo in\\" HELLO"
.Ed
.Pp
would result in the following chat sequence (output using the
.Sq set log local chat
command before dialing):
.Bd -literal -offset indent
Dial attempt 1 of 1
dial OK!
Chat: Expecting:
Chat: Sending:
Chat: Expecting: login:--login:
Chat: Wait for (5): login:
Chat: Sending: ppp
Chat: Expecting: word:
Chat: Wait for (5): word:
Chat: Sending: ppp
Chat: Expecting: !sh \\-c "echo \\-n label: >&2"
Chat: Exec: sh -c "echo -n label: >&2"
Chat: Wait for (5): !sh \\-c "echo \\-n label: >&2" --> label:
Chat: Exec: /bin/echo in
Chat: Sending:
Chat: Expecting: HELLO
Chat: Wait for (5): HELLO
login OK!
.Ed
.Pp
Note (again) the use of the escape character, allowing many levels of
nesting. Here, there are four parsers at work. The first parses the
original line, reading it as three arguments. The second parses the
third argument, reading it as 11 arguments. At this point, it is
important that the
.Dq \&-
signs are escaped, otherwise this parser will see them as constituting
an expect-send-expect sequence. When the
.Dq \&!
character is seen, the execution parser reads the first command as three
arguments, and then
.Xr sh 1
itself expands the argument after the
.Fl c .
As we wish to send the output back to the modem, in the first example
we redirect our output to file descriptor 2 (stderr) so that
.Nm
itself sends and logs it, and in the second example, we just output to stdout,
which is attached directly to the modem.
.Pp
This, of course means that it is possible to execute an entirely external
.Dq chat
command rather than using the internal one. See
.Xr chat 8
for a good alternative.
.Pp
.It set hangup chat-script
This specifies the chat script that will be used to reset the modem
before it is closed. It should not normally be necessary, but can

View File

@ -1,4 +1,4 @@
.\" $Id: ppp.8,v 1.89 1997/12/21 02:34:27 brian Exp $
.\" $Id: ppp.8,v 1.90 1997/12/21 03:16:14 brian Exp $
.Dd 20 September 1995
.Os FreeBSD
.Dt PPP 8
@ -1931,6 +1931,77 @@ This means that in practice you should use two escapes, for example:
set dial "... ATDT\\\\T CONNECT"
.Ed
.Pp
It is also possible to execute external commands from the chat script.
To do this, the first character of the expect or send string is an
exclaimation mark
.Pq Dq \&! .
When the command is executed, standard input and standard output are
directed to the modem device (see the
.Dq set device
command), and standard error is read by
.Nm
and substituted as the expect or send string. If
.Nm
is running in interactive mode, file descriptor 4 is attached to
.Pa /dev/tty .
.Pp
For example (wrapped for readability);
.Bd -literal -offset indent
set login "TIMEOUT 5 \\"\\" \\"\\" login:--login: ppp \e
word: ppp \\"!sh \\\\\\\\-c \\\\\\"echo \\\\\\\\-n label: >&2\\\\\\"\\" \e
\\"!/bin/echo in\\" HELLO"
.Ed
.Pp
would result in the following chat sequence (output using the
.Sq set log local chat
command before dialing):
.Bd -literal -offset indent
Dial attempt 1 of 1
dial OK!
Chat: Expecting:
Chat: Sending:
Chat: Expecting: login:--login:
Chat: Wait for (5): login:
Chat: Sending: ppp
Chat: Expecting: word:
Chat: Wait for (5): word:
Chat: Sending: ppp
Chat: Expecting: !sh \\-c "echo \\-n label: >&2"
Chat: Exec: sh -c "echo -n label: >&2"
Chat: Wait for (5): !sh \\-c "echo \\-n label: >&2" --> label:
Chat: Exec: /bin/echo in
Chat: Sending:
Chat: Expecting: HELLO
Chat: Wait for (5): HELLO
login OK!
.Ed
.Pp
Note (again) the use of the escape character, allowing many levels of
nesting. Here, there are four parsers at work. The first parses the
original line, reading it as three arguments. The second parses the
third argument, reading it as 11 arguments. At this point, it is
important that the
.Dq \&-
signs are escaped, otherwise this parser will see them as constituting
an expect-send-expect sequence. When the
.Dq \&!
character is seen, the execution parser reads the first command as three
arguments, and then
.Xr sh 1
itself expands the argument after the
.Fl c .
As we wish to send the output back to the modem, in the first example
we redirect our output to file descriptor 2 (stderr) so that
.Nm
itself sends and logs it, and in the second example, we just output to stdout,
which is attached directly to the modem.
.Pp
This, of course means that it is possible to execute an entirely external
.Dq chat
command rather than using the internal one. See
.Xr chat 8
for a good alternative.
.Pp
.It set hangup chat-script
This specifies the chat script that will be used to reset the modem
before it is closed. It should not normally be necessary, but can