freebsd-dev/usr.sbin/ppp/main.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

624 lines
16 KiB
C

/*
* User Process PPP
*
* Written by Toshiharu OHNO (tony-o@iij.ad.jp)
*
* Copyright (C) 1993, Internet Initiative Japan, Inc. All rights reserverd.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the Internet Initiative Japan, Inc. The name of the
* IIJ may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id: main.c,v 1.121.2.51 1998/04/25 10:49:26 brian Exp $
*
* TODO:
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/un.h>
#include <net/if_tun.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "mbuf.h"
#include "log.h"
#include "defs.h"
#include "id.h"
#include "timer.h"
#include "fsm.h"
#include "lqr.h"
#include "hdlc.h"
#include "lcp.h"
#include "ccp.h"
#include "iplist.h"
#include "throughput.h"
#include "slcompress.h"
#include "ipcp.h"
#include "filter.h"
#include "descriptor.h"
#include "link.h"
#include "mp.h"
#include "bundle.h"
#include "loadalias.h"
#include "auth.h"
#include "systems.h"
#include "ip.h"
#include "sig.h"
#include "main.h"
#include "pathnames.h"
#include "tun.h"
#include "server.h"
#include "prompt.h"
#include "chat.h"
#include "chap.h"
#include "datalink.h"
#ifndef O_NONBLOCK
#ifdef O_NDELAY
#define O_NONBLOCK O_NDELAY
#endif
#endif
static char pid_filename[MAXPATHLEN];
static void DoLoop(struct bundle *, struct prompt *);
static void TerminalStop(int);
static const char *ex_desc(int);
static struct bundle *SignalBundle;
static struct prompt *SignalPrompt;
void
Cleanup(int excode)
{
SignalBundle->CleaningUp = 1;
if (bundle_Phase(SignalBundle) != PHASE_DEAD)
bundle_Close(SignalBundle, NULL, 1);
}
void
AbortProgram(int excode)
{
ServerClose(SignalBundle);
ID0unlink(pid_filename);
LogPrintf(LogPHASE, "PPP Terminated (%s).\n", ex_desc(excode));
bundle_Close(SignalBundle, NULL, 1);
bundle_Destroy(SignalBundle);
LogClose();
exit(excode);
}
static void
CloseConnection(int signo)
{
/* NOTE, these are manual, we've done a setsid() */
struct datalink *dl;
pending_signal(SIGINT, SIG_IGN);
LogPrintf(LogPHASE, "Caught signal %d, abort connection(s)\n", signo);
for (dl = SignalBundle->links; dl; dl = dl->next)
datalink_Down(dl, 1);
pending_signal(SIGINT, CloseConnection);
}
static void
CloseSession(int signo)
{
LogPrintf(LogPHASE, "Signal %d, terminate.\n", signo);
Cleanup(EX_TERM);
}
static pid_t BGPid = 0;
static void
KillChild(int signo)
{
LogPrintf(LogPHASE, "Parent: Signal %d\n", signo);
kill(BGPid, SIGINT);
}
static void
TerminalCont(int signo)
{
signal(SIGCONT, SIG_DFL);
prompt_Continue(SignalPrompt);
}
static void
TerminalStop(int signo)
{
prompt_Suspend(SignalPrompt);
signal(SIGCONT, TerminalCont);
raise(SIGSTOP);
}
#if 0 /* What's our passwd :-O */
static void
SetUpServer(int signo)
{
int res;
VarHaveLocalAuthKey = 0;
LocalAuthInit();
if ((res = ServerTcpOpen(SERVER_PORT + SignalBundle->unit)) != 0)
LogPrintf(LogERROR, "SIGUSR1: Failed %d to open port %d\n",
res, SERVER_PORT + SignalBundle->unit);
}
#endif
static void
BringDownServer(int signo)
{
/* Drops all child prompts too ! */
ServerClose(SignalBundle);
}
static const char *
ex_desc(int ex)
{
static char num[12];
static const char *desc[] = {
"normal", "start", "sock", "modem", "dial", "dead", "done",
"reboot", "errdead", "hangup", "term", "nodial", "nologin"
};
if (ex >= 0 && ex < sizeof desc / sizeof *desc)
return desc[ex];
snprintf(num, sizeof num, "%d", ex);
return num;
}
static void
Usage(void)
{
fprintf(stderr,
"Usage: ppp [-auto | -background | -direct | -dedicated | -ddial ]"
#ifndef NOALIAS
" [ -alias ]"
#endif
" [system]\n");
exit(EX_START);
}
static char *
ProcessArgs(int argc, char **argv, int *mode)
{
int optc, labelrequired;
char *cp;
optc = labelrequired = 0;
*mode = PHYS_MANUAL;
while (argc > 0 && **argv == '-') {
cp = *argv + 1;
if (strcmp(cp, "auto") == 0) {
*mode = PHYS_DEMAND;
labelrequired = 1;
} else if (strcmp(cp, "background") == 0) {
*mode = PHYS_1OFF;
labelrequired = 1;
} else if (strcmp(cp, "direct") == 0)
*mode = PHYS_STDIN;
else if (strcmp(cp, "dedicated") == 0)
*mode = PHYS_DEDICATED;
else if (strcmp(cp, "ddial") == 0) {
*mode = PHYS_PERM;
labelrequired = 1;
} else if (strcmp(cp, "alias") == 0) {
#ifndef NOALIAS
if (loadAliasHandlers() != 0)
#endif
LogPrintf(LogWARN, "Cannot load alias library\n");
optc--; /* this option isn't exclusive */
} else
Usage();
optc++;
argv++;
argc--;
}
if (argc > 1) {
fprintf(stderr, "You may specify only one system label.\n");
exit(EX_START);
}
if (optc > 1) {
fprintf(stderr, "You may specify only one mode.\n");
exit(EX_START);
}
if (labelrequired && argc != 1) {
fprintf(stderr, "Destination system must be specified in"
" auto, background or ddial mode.\n");
exit(EX_START);
}
return argc == 1 ? *argv : NULL; /* Don't SetLabel yet ! */
}
int
main(int argc, char **argv)
{
FILE *lockfile;
char *name, *label;
int nfds, mode;
struct bundle *bundle;
struct prompt *prompt;
nfds = getdtablesize();
if (nfds >= FD_SETSIZE)
/*
* If we've got loads of file descriptors, make sure they're all
* closed. If they aren't, we may end up with a seg fault when our
* `fd_set's get too big when select()ing !
*/
while (--nfds > 2)
close(nfds);
name = strrchr(argv[0], '/');
LogOpen(name ? name + 1 : argv[0]);
argc--;
argv++;
label = ProcessArgs(argc, argv, &mode);
#ifdef __FreeBSD__
/*
* A FreeBSD hack to dodge a bug in the tty driver that drops output
* occasionally.... I must find the real reason some time. To display
* the dodgy behaviour, comment out this bit, make yourself a large
* routing table and then run ppp in interactive mode. The `show route'
* command will drop chunks of data !!!
*/
if (mode == PHYS_MANUAL) {
close(STDIN_FILENO);
if (open(_PATH_TTY, O_RDONLY) != STDIN_FILENO) {
fprintf(stderr, "Cannot open %s for input !\n", _PATH_TTY);
return 2;
}
}
#endif
/* Allow output for the moment (except in direct mode) */
if (mode == PHYS_STDIN)
prompt = NULL;
else {
const char *m;
SignalPrompt = prompt = prompt_Create(NULL, NULL, PROMPT_STD);
if (mode == PHYS_PERM)
m = "direct dial";
else if (mode & PHYS_1OFF)
m = "background";
else if (mode & PHYS_DEMAND)
m = "auto";
else if (mode & PHYS_DEDICATED)
m = "dedicated";
else if (mode & PHYS_MANUAL)
m = "interactive";
else
m = NULL;
if (m)
prompt_Printf(prompt, "Working in %s mode\n", m);
}
ID0init();
if (ID0realuid() != 0) {
char conf[200], *ptr;
snprintf(conf, sizeof conf, "%s/%s", _PATH_PPP, CONFFILE);
do {
if (!access(conf, W_OK)) {
LogPrintf(LogALERT, "ppp: Access violation: Please protect %s\n", conf);
return -1;
}
ptr = conf + strlen(conf)-2;
while (ptr > conf && *ptr != '/')
*ptr-- = '\0';
} while (ptr >= conf);
}
if (!ValidSystem(label, prompt, mode)) {
fprintf(stderr, "You may not use ppp in this mode with this label\n");
if (mode == PHYS_STDIN) {
const char *l;
l = label ? label : "default";
LogPrintf(LogWARN, "Label %s rejected -direct connection\n", l);
}
LogClose();
return 1;
}
if ((bundle = bundle_Create(TUN_PREFIX, prompt, mode)) == NULL) {
LogPrintf(LogWARN, "bundle_Create: %s\n", strerror(errno));
return EX_START;
}
SignalBundle = bundle;
if (SelectSystem(bundle, "default", CONFFILE, prompt) < 0)
prompt_Printf(prompt, "Warning: No default entry found in config file.\n");
pending_signal(SIGHUP, CloseSession);
pending_signal(SIGTERM, CloseSession);
pending_signal(SIGINT, CloseConnection);
pending_signal(SIGQUIT, CloseSession);
pending_signal(SIGALRM, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
if (mode == PHYS_MANUAL)
pending_signal(SIGTSTP, TerminalStop);
#if 0 /* What's our passwd :-O */
pending_signal(SIGUSR1, SetUpServer);
#endif
pending_signal(SIGUSR2, BringDownServer);
if (label) {
/*
* Set label both before and after SelectSystem !
* This way, "set enddisc label" works during SelectSystem, and we
* also end up with the correct label if we have embedded load
* commands.
*/
bundle_SetLabel(bundle, label);
if (SelectSystem(bundle, label, CONFFILE, prompt) < 0) {
prompt_Printf(prompt, "Destination system (%s) not found.\n", label);
AbortProgram(EX_START);
}
bundle_SetLabel(bundle, label);
if (mode == PHYS_DEMAND &&
bundle->ncp.ipcp.cfg.peer_range.ipaddr.s_addr == INADDR_ANY) {
prompt_Printf(prompt, "You must \"set ifaddr\" with a peer address "
"in label %s for auto mode.\n", label);
AbortProgram(EX_START);
}
}
if (mode != PHYS_MANUAL) {
if (mode != PHYS_STDIN) {
int bgpipe[2];
pid_t bgpid;
if (mode == PHYS_1OFF && pipe(bgpipe)) {
LogPrintf(LogERROR, "pipe: %s\n", strerror(errno));
AbortProgram(EX_SOCK);
}
bgpid = fork();
if (bgpid == -1) {
LogPrintf(LogERROR, "fork: %s\n", strerror(errno));
AbortProgram(EX_SOCK);
}
if (bgpid) {
char c = EX_NORMAL;
if (mode == PHYS_1OFF) {
close(bgpipe[1]);
BGPid = bgpid;
/* If we get a signal, kill the child */
signal(SIGHUP, KillChild);
signal(SIGTERM, KillChild);
signal(SIGINT, KillChild);
signal(SIGQUIT, KillChild);
/* Wait for our child to close its pipe before we exit */
if (read(bgpipe[0], &c, 1) != 1) {
prompt_Printf(prompt, "Child exit, no status.\n");
LogPrintf(LogPHASE, "Parent: Child exit, no status.\n");
} else if (c == EX_NORMAL) {
prompt_Printf(prompt, "PPP enabled.\n");
LogPrintf(LogPHASE, "Parent: PPP enabled.\n");
} else {
prompt_Printf(prompt, "Child failed (%s).\n", ex_desc((int) c));
LogPrintf(LogPHASE, "Parent: Child failed (%s).\n",
ex_desc((int) c));
}
close(bgpipe[0]);
}
return c;
} else if (mode == PHYS_1OFF) {
close(bgpipe[0]);
bundle->notify.fd = bgpipe[1];
}
/* -auto, -dedicated, -ddial & -background */
prompt_Destroy(prompt, 0);
close(STDOUT_FILENO);
close(STDERR_FILENO);
close(STDIN_FILENO);
setsid();
} else {
/* -direct: STDIN_FILENO gets used by modem_Open */
prompt_TtyInit(NULL);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
} else {
/* Interactive mode */
close(STDERR_FILENO);
prompt_TtyInit(prompt);
prompt_TtyCommandMode(prompt);
prompt_Required(prompt);
}
snprintf(pid_filename, sizeof pid_filename, "%stun%d.pid",
_PATH_VARRUN, bundle->unit);
lockfile = ID0fopen(pid_filename, "w");
if (lockfile != NULL) {
fprintf(lockfile, "%d\n", (int) getpid());
fclose(lockfile);
}
#ifndef RELEASE_CRUNCH
else
LogPrintf(LogALERT, "Warning: Can't create %s: %s\n",
pid_filename, strerror(errno));
#endif
LogPrintf(LogPHASE, "PPP Started (%s mode).\n", mode2Nam(mode));
DoLoop(bundle, prompt);
AbortProgram(EX_NORMAL);
return EX_NORMAL;
}
static void
DoLoop(struct bundle *bundle, struct prompt *prompt)
{
fd_set rfds, wfds, efds;
int pri, i, n, nfds;
int qlen;
struct tun_data tun;
do {
nfds = 0;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
qlen = bundle_FillQueues(bundle);
handle_signals();
descriptor_UpdateSet(&bundle->desc, &rfds, &wfds, &efds, &nfds);
descriptor_UpdateSet(&server.desc, &rfds, &wfds, &efds, &nfds);
descriptor_UpdateSet(&bundle->ncp.mp.server.desc, &rfds, &wfds,
&efds, &nfds);
/* If there are aren't many packets queued, look for some more. */
if (qlen < 20 && bundle->tun_fd >= 0) {
if (bundle->tun_fd + 1 > nfds)
nfds = bundle->tun_fd + 1;
FD_SET(bundle->tun_fd, &rfds);
}
if (bundle_IsDead(bundle))
/* Don't select - we'll be here forever */
break;
i = select(nfds, &rfds, &wfds, &efds, NULL);
if (i == 0)
continue;
if (i < 0) {
if (errno == EINTR) {
handle_signals();
continue;
}
LogPrintf(LogERROR, "DoLoop: select(): %s\n", strerror(errno));
break;
}
for (i = 0; i <= nfds; i++)
if (FD_ISSET(i, &efds)) {
LogPrintf(LogALERT, "Exception detected on descriptor %d\n", i);
break;
}
if (descriptor_IsSet(&bundle->ncp.mp.server.desc, &rfds))
descriptor_Read(&bundle->ncp.mp.server.desc, bundle, &rfds);
if (descriptor_IsSet(&server.desc, &rfds))
descriptor_Read(&server.desc, bundle, &rfds);
if (descriptor_IsSet(&bundle->desc, &wfds))
descriptor_Write(&bundle->desc, bundle, &wfds);
if (descriptor_IsSet(&bundle->desc, &rfds))
descriptor_Read(&bundle->desc, bundle, &rfds);
if (bundle->tun_fd >= 0 && FD_ISSET(bundle->tun_fd, &rfds)) {
/* something to read from tun */
n = read(bundle->tun_fd, &tun, sizeof tun);
if (n < 0) {
LogPrintf(LogERROR, "read from tun: %s\n", strerror(errno));
continue;
}
n -= sizeof tun - sizeof tun.data;
if (n <= 0) {
LogPrintf(LogERROR, "read from tun: Only %d bytes read\n", n);
continue;
}
if (!tun_check_header(tun, AF_INET))
continue;
if (((struct ip *)tun.data)->ip_dst.s_addr ==
bundle->ncp.ipcp.my_ip.s_addr) {
/* we've been asked to send something addressed *to* us :( */
if (Enabled(bundle, OPT_LOOPBACK)) {
pri = PacketCheck(bundle, tun.data, n, &bundle->filter.in);
if (pri >= 0) {
struct mbuf *bp;
#ifndef NOALIAS
if (AliasEnabled()) {
(*PacketAlias.In)(tun.data, sizeof tun.data);
n = ntohs(((struct ip *)tun.data)->ip_len);
}
#endif
bp = mballoc(n, MB_IPIN);
memcpy(MBUF_CTOP(bp), tun.data, n);
IpInput(bundle, bp);
LogPrintf(LogDEBUG, "Looped back packet addressed to myself\n");
}
continue;
} else
LogPrintf(LogDEBUG, "Oops - forwarding packet addressed to myself\n");
}
/*
* Process on-demand dialup. Output packets are queued within tunnel
* device until IPCP is opened.
*/
if (bundle_Phase(bundle) == PHASE_DEAD) {
/*
* Note, we must be in AUTO mode :-/ otherwise our interface should
* *not* be UP and we can't receive data
*/
if ((pri = PacketCheck(bundle, tun.data, n, &bundle->filter.dial)) >= 0)
bundle_Open(bundle, NULL, PHYS_DEMAND);
else
/*
* Drop the packet. If we were to queue it, we'd just end up with
* a pile of timed-out data in our output queue by the time we get
* around to actually dialing. We'd also prematurely reach the
* threshold at which we stop select()ing to read() the tun
* device - breaking auto-dial.
*/
continue;
}
pri = PacketCheck(bundle, tun.data, n, &bundle->filter.out);
if (pri >= 0) {
#ifndef NOALIAS
if (AliasEnabled()) {
(*PacketAlias.Out)(tun.data, sizeof tun.data);
n = ntohs(((struct ip *)tun.data)->ip_len);
}
#endif
IpEnqueue(pri, tun.data, n);
}
}
} while (bundle_CleanDatalinks(bundle), !bundle_IsDead(bundle));
LogPrintf(LogDEBUG, "DoLoop done.\n");
}