/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* PPPoE Server-mode daemon for use with Solaris PPP 4.0.
*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stropts.h>
#include <wait.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <net/sppptun.h>
#include <net/pppoe.h>
#include "common.h"
#include "pppoed.h"
#include "logging.h"
static int tunfd; /* Global connection to tunnel device */
char *myname; /* Copied from argv[0] for logging */
static int main_argc; /* Saved for reparse on SIGHUP */
static char **main_argv; /* Saved for reparse on SIGHUP */
static time_t time_started; /* Time daemon was started; for debug */
static time_t last_reread; /* Last time configuration was read. */
/* Various operational statistics. */
static unsigned long input_packets, padi_packets, padr_packets;
static unsigned long output_packets;
static unsigned long sessions_started;
static sigset_t sigmask; /* Global signal mask */
/*
* Used for handling errors that occur before we daemonize.
*/
static void
early_error(const char *str)
{
const char *cp;
cp = mystrerror(errno);
if (isatty(2)) {
(void) fprintf(stderr, "%s: %s: %s\n", myname, str, cp);
} else {
reopen_log();
logerr("%s: %s", str, cp);
}
exit(1);
}
/*
* Open the sppptun driver.
*/
static void
open_tunnel_dev(void)
{
struct ppptun_peer ptp;
tunfd = open(tunnam, O_RDWR);
if (tunfd == -1) {
early_error(tunnam);
}
/*
* Tell the device driver that I'm a daemon handling inbound
* connections, not a PPP session.
*/
(void) memset(&ptp, '\0', sizeof (ptp));
ptp.ptp_style = PTS_PPPOE;
ptp.ptp_flags = PTPF_DAEMON;
(void) memcpy(ptp.ptp_address.pta_pppoe.ptma_mac, ether_bcast,
sizeof (ptp.ptp_address.pta_pppoe.ptma_mac));
if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) <
0) {
myperror("PPPTUN_SPEER");
exit(1);
}
}
/*
* Callback function for fdwalk. Closes everything but the tunnel
* file descriptor when becoming daemon. (Log file must be reopened
* manually, since syslog file descriptor, if any, is unknown.)
*/
/*ARGSUSED*/
static int
fdcloser(void *arg, int fd)
{
if (fd != tunfd)
(void) close(fd);
return (0);
}
/*
* Become a daemon.
*/
static void
daemonize(void)
{
pid_t cpid;
/*
* A little bit of magic here. By the first fork+setsid, we
* disconnect from our current controlling terminal and become
* a session group leader. By forking again without setsid,
* we make certain that we're not the session group leader and
* can never reacquire a controlling terminal.
*/
if ((cpid = fork()) == (pid_t)-1) {
early_error("fork 1");
}
if (cpid != 0) {
(void) wait(NULL);
_exit(0);
}
if (setsid() == (pid_t)-1) {
early_error("setsid");
}
if ((cpid = fork()) == (pid_t)-1) {
early_error("fork 2");
}
if (cpid != 0) {
/* Parent just exits */
(void) printf("%d\n", (int)cpid);
(void) fflush(stdout);
_exit(0);
}
(void) chdir("/");
(void) umask(0);
(void) fdwalk(fdcloser, NULL);
reopen_log();
}
/*
* Handle SIGHUP -- close and reopen non-syslog log files and reparse
* options.
*/
/*ARGSUSED*/
static void
handle_hup(int sig)
{
close_log_files();
global_logging();
last_reread = time(NULL);
parse_options(tunfd, main_argc, main_argv);
}
/*
* Handle SIGINT -- write current daemon status to /tmp.
*/
/*ARGSUSED*/
static void
handle_int(int sig)
{
FILE *fp;
char dumpname[MAXPATHLEN];
time_t now;
struct rusage rusage;
(void) snprintf(dumpname, sizeof (dumpname), "/tmp/pppoed.%ld",
getpid());
if ((fp = fopen(dumpname, "w+")) == NULL) {
logerr("%s: %s", dumpname, mystrerror(errno));
return;
}
now = time(NULL);
(void) fprintf(fp, "pppoed running %s", ctime(&now));
(void) fprintf(fp, "Started on %s", ctime(&time_started));
if (last_reread != 0)
(void) fprintf(fp, "Last reconfig %s", ctime(&last_reread));
(void) putc('\n', fp);
if (getrusage(RUSAGE_SELF, &rusage) == 0) {
(void) fprintf(fp,
"CPU usage: user %ld.%06ld, system %ld.%06ld\n",
rusage.ru_utime.tv_sec, rusage.ru_utime.tv_usec,
rusage.ru_stime.tv_sec, rusage.ru_stime.tv_usec);
}
(void) fprintf(fp, "Packets: %lu received (%lu PADI, %lu PADR), ",
input_packets, padi_packets, padr_packets);
(void) fprintf(fp, "%lu transmitted\n", output_packets);
(void) fprintf(fp, "Sessions started: %lu\n\n", sessions_started);
dump_configuration(fp);
(void) fclose(fp);
}
static void
add_signal_handlers(void)
{
struct sigaction sa;
(void) sigemptyset(&sigmask);
(void) sigaddset(&sigmask, SIGHUP);
(void) sigaddset(&sigmask, SIGCHLD);
(void) sigaddset(&sigmask, SIGINT);
(void) sigprocmask(SIG_BLOCK, &sigmask, NULL);
sa.sa_mask = sigmask;
sa.sa_flags = 0;
/* Signals to handle */
sa.sa_handler = handle_hup;
if (sigaction(SIGHUP, &sa, NULL) < 0)
early_error("sigaction HUP");
sa.sa_handler = handle_int;
if (sigaction(SIGINT, &sa, NULL) < 0)
early_error("sigaction INT");
/*
* Signals to ignore. Ignoring SIGCHLD in this way makes the
* children exit without ever creating zombies. (No wait(2)
* call required.)
*/
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) < 0)
early_error("sigaction PIPE");
sa.sa_flags = SA_NOCLDWAIT;
if (sigaction(SIGCHLD, &sa, NULL) < 0)
early_error("sigaction CHLD");
}
/*
* Dispatch a message from the tunnel driver. It could be an actual
* PPPoE message or just an event notification.
*/
static void
handle_input(uint32_t *ctrlbuf, int ctrllen, uint32_t *databuf, int datalen)
{
poep_t *poep = (poep_t *)databuf;
union ppptun_name ptn;
int retv;
struct strbuf ctrl;
struct strbuf data;
void *srvp;
boolean_t launch;
struct ppptun_control *ptc;
if (ctrllen != sizeof (*ptc)) {
logdbg("bogus %d byte control message from driver",
ctrllen);
return;
}
ptc = (struct ppptun_control *)ctrlbuf;
/* Switch out on event notifications. */
switch (ptc->ptc_action) {
case PTCA_TEST:
logdbg("test reply for discriminator %X", ptc->ptc_discrim);
return;
case PTCA_CONTROL:
break;
case PTCA_DISCONNECT:
logdbg("session %d disconnected on %s; send PADT",
ptc->ptc_rsessid, ptc->ptc_name);
poep = poe_mkheader(pkt_output, POECODE_PADT,
ptc->ptc_rsessid);
ptc->ptc_action = PTCA_CONTROL;
ctrl.len = sizeof (*ptc);
ctrl.buf = (caddr_t)ptc;
data.len = poe_length(poep) + sizeof (*poep);
data.buf = (caddr_t)poep;
if (putmsg(tunfd, &ctrl, &data, 0) < 0) {
logerr("putmsg PADT: %s", mystrerror(errno));
} else {
output_packets++;
}
return;
case PTCA_UNPLUMB:
logdbg("%s unplumbed", ptc->ptc_name);
return;
case PTCA_BADCTRL:
logwarn("bad control data on %s for session %u", ptc->ptc_name,
ptc->ptc_rsessid);
return;
default:
logdbg("unexpected code %d from driver", ptc->ptc_action);
return;
}
/* Only PPPoE control messages get here. */
input_packets++;
if (datalen < sizeof (*poep)) {
logdbg("incomplete PPPoE message from %s/%s",
ehost(&ptc->ptc_address), ptc->ptc_name);
return;
}
/* Server handles only PADI and PADR; all others are ignored. */
if (poep->poep_code == POECODE_PADI) {
padi_packets++;
} else if (poep->poep_code == POECODE_PADR) {
padr_packets++;
} else {
loginfo("unexpected %s from %s",
poe_codename(poep->poep_code), ehost(&ptc->ptc_address));
return;
}
logdbg("Recv from %s/%s: %s", ehost(&ptc->ptc_address), ptc->ptc_name,
poe_codename(poep->poep_code));
/* Parse out service and formulate template reply. */
retv = locate_service(poep, datalen, ptc->ptc_name, &ptc->ptc_address,
pkt_output, &srvp);
/* Continue formulating reply */
launch = B_FALSE;
if (retv != 1) {
/* Ignore initiation if we don't offer a service. */
if (retv <= 0 && poep->poep_code == POECODE_PADI) {
logdbg("no services; no reply");
return;
}
if (retv == 0)
(void) poe_add_str((poep_t *)pkt_output, POETT_NAMERR,
"No such service.");
} else {
/* Exactly one service chosen; if it's PADR, then we start. */
if (poep->poep_code == POECODE_PADR) {
launch = B_TRUE;
}
}
poep = (poep_t *)pkt_output;
/* Select control interface for output. */
(void) strncpy(ptn.ptn_name, ptc->ptc_name, sizeof (ptn.ptn_name));
if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) {
logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno));
return;
}
/* Launch the PPP service */
if (launch && launch_service(tunfd, poep, srvp, ptc))
sessions_started++;
/* Send the reply. */
ctrl.len = sizeof (*ptc);
ctrl.buf = (caddr_t)ptc;
data.len = poe_length(poep) + sizeof (*poep);
data.buf = (caddr_t)poep;
if (putmsg(tunfd, &ctrl, &data, 0) < 0) {
logerr("putmsg %s: %s", ptc->ptc_name, mystrerror(errno));
} else {
output_packets++;
logdbg("Send to %s/%s: %s", ehost(&ptc->ptc_address),
ptc->ptc_name, poe_codename(poep->poep_code));
}
}
static void
main_loop(void)
{
struct strbuf ctrl;
struct strbuf data;
int flags;
int rc;
int err;
for (;;) {
ctrl.maxlen = PKT_OCTL_LEN;
ctrl.buf = (caddr_t)pkt_octl;
data.maxlen = PKT_INPUT_LEN;
data.buf = (caddr_t)pkt_input;
/* Allow signals only while idle */
(void) sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
errno = 0;
flags = 0;
rc = mygetmsg(tunfd, &ctrl, &data, &flags);
err = errno;
/*
* Block signals -- data structures must not change
* while we're busy dispatching the client's request
*/
(void) sigprocmask(SIG_BLOCK, &sigmask, NULL);
if (rc == -1) {
if (err == EAGAIN || err == EINTR)
continue;
logerr("%s getmsg: %s", tunnam, mystrerror(err));
exit(1);
}
if (rc > 0)
logwarn("%s returned truncated data", tunnam);
else
handle_input(pkt_octl, ctrl.len, pkt_input, data.len);
}
}
int
main(int argc, char **argv)
{
prog_name = "pppoed";
log_level = 1; /* Default to error messages only at first */
time_started = time(NULL);
if ((myname = argv[0]) == NULL)
myname = "pppoed";
main_argc = argc;
main_argv = argv;
open_tunnel_dev();
add_signal_handlers();
daemonize();
parse_options(tunfd, argc, argv);
main_loop();
return (0);
}