diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..7587b32 --- /dev/null +++ b/src/main.c @@ -0,0 +1,678 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org> + * based on work by Toshiharu OHNO <tony-o@iij.ad.jp> + * Internet Initiative Japan, Inc (IIJ) + * 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. + * + * $FreeBSD: src/usr.sbin/ppp/main.c,v 1.193.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include <sys/param.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <net/route.h> + +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <termios.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifndef NONAT +#ifdef LOCALNAT +#include "alias.h" +#else +#include <alias.h> +#endif +#endif + +#include "layer.h" +#include "probe.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 "ncpaddr.h" +#include "ipcp.h" +#include "filter.h" +#include "descriptor.h" +#include "link.h" +#include "mp.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" +#include "auth.h" +#include "systems.h" +#include "sig.h" +#include "main.h" +#include "server.h" +#include "prompt.h" +#include "chat.h" +#include "chap.h" +#include "cbcp.h" +#include "datalink.h" +#include "iface.h" + +#ifndef O_NONBLOCK +#ifdef O_NDELAY +#define O_NONBLOCK O_NDELAY +#endif +#endif + +static void DoLoop(struct bundle *); +static void TerminalStop(int); + +static struct bundle *SignalBundle; +static struct prompt *SignalPrompt; + +void +Cleanup() +{ + SignalBundle->CleaningUp = 1; + bundle_Close(SignalBundle, NULL, CLOSE_STAYDOWN); +} + +void +AbortProgram(int excode) +{ + if (SignalBundle) + server_Close(SignalBundle); + log_Printf(LogPHASE, "PPP Terminated (%s).\n", ex_desc(excode)); + if (SignalBundle) { + bundle_Close(SignalBundle, NULL, CLOSE_STAYDOWN); + bundle_Destroy(SignalBundle); + } + log_Close(); + exit(excode); +} + +static void +CloseConnection(int signo) +{ + /* NOTE, these are manual, we've done a setsid() */ + sig_signal(SIGINT, SIG_IGN); + log_Printf(LogPHASE, "Caught signal %d, abort connection(s)\n", signo); + bundle_Down(SignalBundle, CLOSE_STAYDOWN); + sig_signal(SIGINT, CloseConnection); +} + +static void +CloseSession(int signo) +{ + log_Printf(LogPHASE, "Signal %d, terminate.\n", signo); + Cleanup(); +} + +static pid_t BGPid = 0; + +static void +KillChild(int signo) +{ + signal(signo, SIG_IGN); + log_Printf(LogPHASE, "Parent: Signal %d\n", signo); + kill(BGPid, SIGINT); +} + +static void +TerminalCont(int signo __unused) +{ + signal(SIGCONT, SIG_DFL); + prompt_Continue(SignalPrompt); +} + +static void +TerminalStop(int signo __unused) +{ + prompt_Suspend(SignalPrompt); + signal(SIGCONT, TerminalCont); + raise(SIGSTOP); +} + +static void +BringDownServer(int signo __unused) +{ + /* Drops all child prompts too ! */ + if (server_Close(SignalBundle)) + log_Printf(LogPHASE, "Closed server socket\n"); +} + +static void +RestartServer(int signo __unused) +{ + /* Drops all child prompts and re-opens the socket */ + server_Reopen(SignalBundle); +} + +static void +Usage(void) +{ + fprintf(stderr, "usage: ppp [-auto | -foreground | -background | -direct |" + " -dedicated | -ddial | -interactive]" +#ifndef NONAT + " [-nat]" +#endif + " [-quiet] [-unit N] [system ...]\n"); + exit(EX_START); +} + +struct switches { + unsigned nat : 1; + unsigned fg : 1; + unsigned quiet : 1; + int mode; + int unit; +}; + +static int +ProcessArgs(int argc, char **argv, struct switches *sw) +{ + int optc, newmode, arg; + char *cp; + + optc = 0; + memset(sw, '\0', sizeof *sw); + sw->mode = PHYS_INTERACTIVE; + sw->unit = -1; + + for (arg = 1; arg < argc && *argv[arg] == '-'; arg++, optc++) { + cp = argv[arg] + 1; + newmode = Nam2mode(cp); + switch (newmode) { + case PHYS_NONE: + if (strcmp(cp, "nat") == 0) { +#ifdef NONAT + log_Printf(LogWARN, "%s ignored: NAT is compiled out\n", argv[arg]); +#else + sw->nat = 1; +#endif + optc--; /* this option isn't exclusive */ + } else if (strcmp(cp, "alias") == 0) { +#ifdef NONAT + log_Printf(LogWARN, "%s ignored: NAT is compiled out\n", argv[arg]); + fprintf(stderr, "%s ignored: NAT is compiled out\n", argv[arg]); +#else + log_Printf(LogWARN, "%s is deprecated\n", argv[arg]); + fprintf(stderr, "%s is deprecated\n", argv[arg]); + sw->nat = 1; +#endif + optc--; /* this option isn't exclusive */ + } else if (strncmp(cp, "unit", 4) == 0) { + optc--; /* this option isn't exclusive */ + if (cp[4] == '\0') { + optc--; /* nor is the argument */ + if (++arg == argc) { + fprintf(stderr, "-unit: Expected unit number\n"); + Usage(); + } else + sw->unit = atoi(argv[arg]); + } else + sw->unit = atoi(cp + 4); + } else if (strcmp(cp, "quiet") == 0) { + sw->quiet = 1; + optc--; /* this option isn't exclusive */ + } else + Usage(); + break; + + case PHYS_ALL: + Usage(); + break; + + default: + sw->mode = newmode; + if (newmode == PHYS_FOREGROUND) + sw->fg = 1; + } + } + + if (optc > 1) { + fprintf(stderr, "You may specify only one mode.\n"); + exit(EX_START); + } + + if (sw->mode == PHYS_AUTO && arg == argc) { + fprintf(stderr, "A system must be specified in auto mode.\n"); + exit(EX_START); + } + + return arg; /* Don't SetLabel yet ! */ +} + +static void +CheckLabel(const char *label, struct prompt *prompt, int mode) +{ + const char *err; + + if ((err = system_IsValid(label, prompt, mode)) != NULL) { + fprintf(stderr, "%s: %s\n", label, err); + if (mode == PHYS_DIRECT) + log_Printf(LogWARN, "Label %s rejected -direct connection: %s\n", + label, err); + log_Close(); + exit(1); + } +} + + +int +main(int argc, char **argv) +{ + char *name; + const char *lastlabel; + int arg, holdfd[3], label; + unsigned f; + struct bundle *bundle; + struct prompt *prompt; + struct switches sw; + + probe_Init(); + + /* + * We open 3 descriptors to ensure that STDIN_FILENO, STDOUT_FILENO and + * STDERR_FILENO are always open. These are closed before DoLoop(), + * but *after* we've avoided the possibility of erroneously closing + * an important descriptor with close(STD{IN,OUT,ERR}_FILENO). + */ + if ((holdfd[0] = open(_PATH_DEVNULL, O_RDWR)) == -1) { + fprintf(stderr, "Cannot open %s !\n", _PATH_DEVNULL); + return 2; + } + for (f = 1; f < sizeof holdfd / sizeof *holdfd; f++) + holdfd[f] = dup(holdfd[0]); + + name = strrchr(argv[0], '/'); + log_Open(name ? name + 1 : argv[0]); + +#ifndef NONAT + PacketAliasInit(); +#endif + label = ProcessArgs(argc, argv, &sw); + + /* + * A FreeBSD & OpenBSD 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 (sw.mode == PHYS_INTERACTIVE) { + close(STDIN_FILENO); + if (open(_PATH_TTY, O_RDONLY) != STDIN_FILENO) { + fprintf(stderr, "Cannot open %s for input !\n", _PATH_TTY); + return 2; + } + } + + /* Allow output for the moment (except in direct mode) */ + if (sw.mode == PHYS_DIRECT) + prompt = NULL; + else + SignalPrompt = prompt = prompt_Create(NULL, NULL, PROMPT_STD); + + ID0init(); + if (ID0realuid() != 0) { + char conf[200], *ptr; + + snprintf(conf, sizeof conf, "%s/%s", PPP_CONFDIR, CONFFILE); + do { + struct stat sb; + + if (stat(conf, &sb) == 0 && sb.st_mode & S_IWOTH) { + log_Printf(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 (label < argc) + for (arg = label; arg < argc; arg++) + CheckLabel(argv[arg], prompt, sw.mode); + else + CheckLabel("default", prompt, sw.mode); + + if (!sw.quiet) + prompt_Printf(prompt, "Working in %s mode\n", mode2Nam(sw.mode)); + + if ((bundle = bundle_Create(TUN_PREFIX, sw.mode, sw.unit)) == NULL) + return EX_START; + + /* NOTE: We may now have changed argv[1] via a ``set proctitle'' */ + + if (prompt) { + prompt->bundle = bundle; /* couldn't do it earlier */ + if (!sw.quiet) + prompt_Printf(prompt, "Using interface: %s\n", bundle->iface->name); + } + SignalBundle = bundle; + bundle->NatEnabled = sw.nat; + if (sw.nat) + opt_enable(bundle, OPT_IFACEALIAS); + + if (system_Select(bundle, "default", CONFFILE, prompt, NULL) < 0) + prompt_Printf(prompt, "Warning: No default entry found in config file.\n"); + + sig_signal(SIGHUP, CloseSession); + sig_signal(SIGTERM, CloseSession); + sig_signal(SIGINT, CloseConnection); + sig_signal(SIGQUIT, CloseSession); + sig_signal(SIGALRM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + if (sw.mode == PHYS_INTERACTIVE) + sig_signal(SIGTSTP, TerminalStop); + + sig_signal(SIGUSR1, RestartServer); + sig_signal(SIGUSR2, BringDownServer); + + lastlabel = argv[argc - 1]; + for (arg = label; arg < argc; arg++) { + /* In case we use LABEL or ``set enddisc label'' */ + bundle_SetLabel(bundle, lastlabel); + system_Select(bundle, argv[arg], CONFFILE, prompt, NULL); + } + + if (label < argc) + /* In case the last label did a ``load'' */ + bundle_SetLabel(bundle, lastlabel); + + if (sw.mode == PHYS_AUTO && + ncprange_family(&bundle->ncp.ipcp.cfg.peer_range) == AF_UNSPEC) { + prompt_Printf(prompt, "You must ``set ifaddr'' with a peer address " + "in auto mode.\n"); + AbortProgram(EX_START); + } + + if (sw.mode != PHYS_INTERACTIVE) { + if (sw.mode != PHYS_DIRECT) { + if (!sw.fg) { + int bgpipe[2]; + pid_t bgpid; + + if (sw.mode == PHYS_BACKGROUND && pipe(bgpipe)) { + log_Printf(LogERROR, "pipe: %s\n", strerror(errno)); + AbortProgram(EX_SOCK); + } + + bgpid = fork(); + if (bgpid == -1) { + log_Printf(LogERROR, "fork: %s\n", strerror(errno)); + AbortProgram(EX_SOCK); + } + + if (bgpid) { + char c = EX_NORMAL; + int ret; + + if (sw.mode == PHYS_BACKGROUND) { + 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 */ + while ((ret = read(bgpipe[0], &c, 1)) == 1) { + switch (c) { + case EX_NORMAL: + if (!sw.quiet) { + prompt_Printf(prompt, "PPP enabled\n"); + log_Printf(LogPHASE, "Parent: PPP enabled\n"); + } + break; + case EX_REDIAL: + if (!sw.quiet) + prompt_Printf(prompt, "Attempting redial\n"); + continue; + case EX_RECONNECT: + if (!sw.quiet) + prompt_Printf(prompt, "Attempting reconnect\n"); + continue; + default: + prompt_Printf(prompt, "Child failed (%s)\n", + ex_desc((int)c)); + log_Printf(LogPHASE, "Parent: Child failed (%s)\n", + ex_desc((int) c)); + } + break; + } + if (ret != 1) { + prompt_Printf(prompt, "Child exit, no status.\n"); + log_Printf(LogPHASE, "Parent: Child exit, no status.\n"); + } + close(bgpipe[0]); + } + return c; + } else if (sw.mode == PHYS_BACKGROUND) { + close(bgpipe[0]); + bundle->notify.fd = bgpipe[1]; + } + + bundle_ChangedPID(bundle); + bundle_LockTun(bundle); /* we have a new pid */ + } + + /* -auto, -dedicated, -ddial, -foreground & -background */ + prompt_Destroy(prompt, 0); + close(STDOUT_FILENO); + close(STDERR_FILENO); + close(STDIN_FILENO); + if (!sw.fg) + setsid(); + } else { + /* + * -direct - STDIN_FILENO gets used by physical_Open. STDOUT_FILENO + * *may* get used in exec/pipe mode. + */ + prompt_TtyInit(NULL); + close(STDERR_FILENO); + } + } else { + /* -interactive */ + close(STDERR_FILENO); + prompt_TtyInit(prompt); + prompt_TtyCommandMode(prompt); + prompt_Required(prompt); + } + + /* We can get rid of these now */ + for (f = 0; f < sizeof holdfd / sizeof *holdfd; f++) + close(holdfd[f]); + + log_Printf(LogPHASE, "PPP Started (%s mode).\n", mode2Nam(sw.mode)); + DoLoop(bundle); + AbortProgram(EX_NORMAL); + + return EX_NORMAL; +} + +static void +DoLoop(struct bundle *bundle) +{ + fd_set *rfds, *wfds, *efds; + int i, nfds, nothing_done; + + if ((rfds = mkfdset()) == NULL) { + log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n"); + return; + } + + if ((wfds = mkfdset()) == NULL) { + log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n"); + free(rfds); + return; + } + + if ((efds = mkfdset()) == NULL) { + log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n"); + free(rfds); + free(wfds); + return; + } + + for (; !bundle_IsDead(bundle); bundle_CleanDatalinks(bundle)) { + nfds = 0; + zerofdset(rfds); + zerofdset(wfds); + zerofdset(efds); + + /* All our datalinks, the tun device and the MP socket */ + descriptor_UpdateSet(&bundle->desc, rfds, wfds, efds, &nfds); + + /* All our prompts and the diagnostic socket */ + descriptor_UpdateSet(&server.desc, rfds, NULL, NULL, &nfds); + + bundle_CleanDatalinks(bundle); + if (bundle_IsDead(bundle)) + /* Don't select - we'll be here forever */ + break; + + /* + * It's possible that we've had a signal since we last checked. If + * we don't check again before calling select(), we may end up stuck + * after having missed the event.... sig_Handle() tries to be as + * quick as possible if nothing is likely to have happened. + * This is only really likely if we block in open(... O_NONBLOCK) + * which will happen with a misconfigured device. + */ + if (sig_Handle()) + continue; + + i = select(nfds, rfds, wfds, efds, NULL); + + if (i < 0 && errno != EINTR) { + log_Printf(LogERROR, "DoLoop: select(): %s\n", strerror(errno)); + if (log_IsKept(LogTIMER)) { + struct timeval t; + + for (i = 0; i <= nfds; i++) { + if (FD_ISSET(i, rfds)) { + log_Printf(LogTIMER, "Read set contains %d\n", i); + FD_CLR(i, rfds); + t.tv_sec = t.tv_usec = 0; + if (select(nfds, rfds, wfds, efds, &t) != -1) { + log_Printf(LogTIMER, "The culprit !\n"); + break; + } + } + if (FD_ISSET(i, wfds)) { + log_Printf(LogTIMER, "Write set contains %d\n", i); + FD_CLR(i, wfds); + t.tv_sec = t.tv_usec = 0; + if (select(nfds, rfds, wfds, efds, &t) != -1) { + log_Printf(LogTIMER, "The culprit !\n"); + break; + } + } + if (FD_ISSET(i, efds)) { + log_Printf(LogTIMER, "Error set contains %d\n", i); + FD_CLR(i, efds); + t.tv_sec = t.tv_usec = 0; + if (select(nfds, rfds, wfds, efds, &t) != -1) { + log_Printf(LogTIMER, "The culprit !\n"); + break; + } + } + } + } + break; + } + + log_Printf(LogTIMER, "Select returns %d\n", i); + + sig_Handle(); + + if (i <= 0) + continue; + + for (i = 0; i <= nfds; i++) + if (FD_ISSET(i, efds)) { + log_Printf(LogPHASE, "Exception detected on descriptor %d\n", i); + /* We deal gracefully with link descriptor exceptions */ + if (!bundle_Exception(bundle, i)) { + log_Printf(LogERROR, "Exception cannot be handled !\n"); + break; + } + } + + if (i <= nfds) + break; + + nothing_done = 1; + + if (descriptor_IsSet(&server.desc, rfds)) { + descriptor_Read(&server.desc, bundle, rfds); + nothing_done = 0; + } + + if (descriptor_IsSet(&bundle->desc, rfds)) { + descriptor_Read(&bundle->desc, bundle, rfds); + nothing_done = 0; + } + + if (descriptor_IsSet(&bundle->desc, wfds)) + if (descriptor_Write(&bundle->desc, bundle, wfds) <= 0 && nothing_done) { + /* + * This is disastrous. The OS has told us that something is + * writable, and all our write()s have failed. Rather than + * going back immediately to do our UpdateSet()s and select(), + * we sleep for a bit to avoid gobbling up all cpu time. + */ + struct timeval t; + + t.tv_sec = 0; + t.tv_usec = 100000; + select(0, NULL, NULL, NULL, &t); + } + } + + log_Printf(LogDEBUG, "DoLoop done.\n"); +} |