1N/A/*
1N/A * CDDL HEADER START
1N/A *
1N/A * The contents of this file are subject to the terms of the
1N/A * Common Development and Distribution License (the "License").
1N/A * You may not use this file except in compliance with the License.
1N/A *
1N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
1N/A * or http://www.opensolaris.org/os/licensing.
1N/A * See the License for the specific language governing permissions
1N/A * and limitations under the License.
1N/A *
1N/A * When distributing Covered Code, include this CDDL HEADER in each
1N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1N/A * If applicable, add the following below this CDDL HEADER, with the
1N/A * fields enclosed by brackets "[]" replaced with your own identifying
1N/A * information: Portions Copyright [yyyy] [name of copyright owner]
1N/A *
1N/A * CDDL HEADER END
1N/A */
1N/A
1N/A/*
1N/A * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
1N/A * Use is subject to license terms.
1N/A */
1N/A
1N/A/*
1N/A * pppoe.c - pppd plugin to handle PPPoE operation.
1N/A */
1N/A
1N/A#include <unistd.h>
1N/A#include <stddef.h>
1N/A#include <stdlib.h>
1N/A#include <errno.h>
1N/A#include <sys/types.h>
1N/A#include <fcntl.h>
1N/A#include <strings.h>
1N/A#include <sys/stropts.h>
1N/A#include <netinet/in.h>
1N/A#include <net/pppio.h>
1N/A#include <net/sppptun.h>
1N/A#include <net/pppoe.h>
1N/A
1N/A#include "pppd.h"
1N/A#include "pathnames.h"
1N/A
1N/A/* Saved hook pointers */
1N/Astatic int (*old_check_options)(uid_t uid);
1N/Astatic int (*old_updown_script)(const char ***argsp);
1N/Astatic int (*old_sys_read_packet)(int retv, struct strbuf *ctrl,
1N/A struct strbuf *data, int flags);
1N/A
1N/A/* Room for 3 IPv4 addresses and metric */
1N/A#define RTE_MSG_LEN (3*16 + 10 + 1)
1N/A
1N/A/* Environment string for routes */
1N/A#define RTE_STR "ROUTE_%d"
1N/A
1N/A/*
1N/A * strioctl()
1N/A *
1N/A * wrapper for STREAMS I_STR ioctl.
1N/A */
1N/Astatic int
1N/Astrioctl(int fd, int cmd, void *ptr, int ilen, int olen)
1N/A{
1N/A struct strioctl str;
1N/A
1N/A str.ic_cmd = cmd;
1N/A str.ic_timout = 0; /* Use default timer; 15 seconds */
1N/A str.ic_len = ilen;
1N/A str.ic_dp = ptr;
1N/A
1N/A if (ioctl(fd, I_STR, &str) == -1) {
1N/A return (-1);
1N/A }
1N/A if (str.ic_len != olen) {
1N/A return (-1);
1N/A }
1N/A return (0);
1N/A}
1N/A
1N/A/*
1N/A * If the user named the tunneling device, check that it is
1N/A * reasonable; otherwise check that standard input is the tunnel.
1N/A */
1N/Astatic int
1N/Apppoe_check_options(uid_t uid)
1N/A{
1N/A int tstfd; /* fd for device being checked */
1N/A int err; /* saved errno value */
1N/A int retv; /* return value */
1N/A int intv; /* integer return value (from ioctl) */
1N/A union ppptun_name ptn;
1N/A
1N/A if (devnam[0] != '\0') {
1N/A /*
1N/A * Open as real user so that modes on device can be
1N/A * used to limit access.
1N/A */
1N/A if (!devnam_info.priv)
1N/A (void) seteuid(uid);
1N/A tstfd = open(devnam, O_NONBLOCK | O_RDWR, 0);
1N/A err = errno;
1N/A if (!devnam_info.priv)
1N/A (void) seteuid(0);
1N/A if (tstfd == -1) {
1N/A errno = err;
1N/A option_error("unable to open %s: %m", devnam);
1N/A return (-1);
1N/A }
1N/A retv = strioctl(tstfd, PPPTUN_GDATA, &ptn, 0, sizeof (ptn));
1N/A (void) close(tstfd);
1N/A if (retv == -1) {
1N/A option_error("device %s is not a PPP tunneling device",
1N/A devnam);
1N/A return (-1);
1N/A }
1N/A } else {
1N/A retv = strioctl(0, PPPIO_GTYPE, &intv, 0, sizeof (intv));
1N/A if (retv == -1) {
1N/A option_error("standard input is not a PPP device");
1N/A return (-1);
1N/A }
1N/A retv = strioctl(0, PPPTUN_GDATA, &ptn, 0, sizeof (ptn));
1N/A if (retv == -1) {
1N/A option_error("standard input is not a PPP tunnel");
1N/A return (-1);
1N/A }
1N/A if (strcmp(ptn.ptn_name + strlen(ptn.ptn_name) - 6,
1N/A ":pppoe") != 0) {
1N/A option_error("standard input not connected to PPPoE");
1N/A return (-1);
1N/A }
1N/A }
1N/A if (old_check_options != NULL &&
1N/A old_check_options != pppoe_check_options)
1N/A return ((*old_check_options)(uid));
1N/A return (0);
1N/A}
1N/A
1N/A/*
1N/A * When we're about to call one of the up or down scripts, change the
1N/A * second argument to contain the interface name and selected PPPoE
1N/A * service.
1N/A */
1N/Astatic int
1N/Apppoe_updown_script(const char ***argsp)
1N/A{
1N/A const char *cp;
1N/A
1N/A if ((*argsp)[2] == devnam &&
1N/A (cp = script_getenv("IF_AND_SERVICE")) != NULL)
1N/A (*argsp)[2] = cp;
1N/A if (old_updown_script != NULL &&
1N/A old_updown_script != pppoe_updown_script)
1N/A return ((*old_updown_script)(argsp));
1N/A return (0);
1N/A}
1N/A
1N/A/*
1N/A * Concatenate and save strings from command line into environment
1N/A * variable.
1N/A */
1N/Astatic void
1N/Acat_save_env(char **argv, char idchar, const char *envname)
1N/A{
1N/A char **argp;
1N/A int totlen;
1N/A char *str;
1N/A char *cp;
1N/A
1N/A totlen = 0;
1N/A for (argp = argv; argp[0] != NULL; argp += 2)
1N/A if (*argp[0] == idchar)
1N/A totlen += strlen(argp[1]) + 1;
1N/A if ((str = malloc(totlen + 1)) == NULL) {
1N/A error("cannot malloc PPPoE environment for %s", envname);
1N/A return;
1N/A }
1N/A cp = str;
1N/A for (argp = argv; argp[0] != NULL; argp += 2)
1N/A if (*argp[0] == idchar) {
1N/A (void) strcpy(cp, argp[1]);
1N/A cp += strlen(cp);
1N/A *cp++ = '\n';
1N/A }
1N/A *cp = '\0';
1N/A script_setenv(envname, str, 0);
1N/A}
1N/A
1N/A/*
1N/A * Convert Message Of The Moment (MOTM) and Host Uniform Resource
1N/A * Locator (HURL) strings into environment variables and command-line
1N/A * arguments for script.
1N/A */
1N/Astatic void
1N/Ahandle_motm_hurl(char **argv, int argc, const uint8_t *tagp, int pktlen)
1N/A{
1N/A int ttype;
1N/A int tlen;
1N/A char *str;
1N/A char **oargv = argv;
1N/A
1N/A /* Must have room for two strings and NULL terminator. */
1N/A while (argc >= 3) {
1N/A str = NULL;
1N/A while (pktlen >= POET_HDRLEN) {
1N/A ttype = POET_GET_TYPE(tagp);
1N/A if (ttype == POETT_END)
1N/A break;
1N/A tlen = POET_GET_LENG(tagp);
1N/A if (tlen > pktlen - POET_HDRLEN)
1N/A break;
1N/A if (ttype == POETT_HURL || ttype == POETT_MOTM) {
1N/A if ((str = malloc(tlen + 1)) == NULL) {
1N/A error("cannot malloc PPPoE message");
1N/A break;
1N/A }
1N/A (void) memcpy(str, POET_DATA(tagp), tlen);
1N/A str[tlen] = '\0';
1N/A }
1N/A pktlen -= POET_HDRLEN + tlen;
1N/A tagp += POET_HDRLEN + tlen;
1N/A if (str != NULL)
1N/A break;
1N/A }
1N/A if (str == NULL)
1N/A break;
1N/A *argv++ = ttype == POETT_HURL ? "hurl" : "motm";
1N/A *argv++ = str;
1N/A argc -= 2;
1N/A }
1N/A *argv = NULL;
1N/A cat_save_env(oargv, 'h', "HURL");
1N/A cat_save_env(oargv, 'm', "MOTM");
1N/A}
1N/A
1N/A/*
1N/A * Convert IP Route Add structures into environment variables and
1N/A * command-line arguments for script.
1N/A */
1N/Astatic void
1N/Ahandle_ip_route_add(char **argv, int argc, const uint8_t *tagp, int pktlen)
1N/A{
1N/A int ttype;
1N/A int tlen;
1N/A char *str;
1N/A poer_t poer;
1N/A int idx;
1N/A char envname[sizeof (RTE_STR) + 10];
1N/A
1N/A idx = 0;
1N/A
1N/A /* Must have room for four strings and NULL terminator. */
1N/A while (argc >= 5) {
1N/A str = NULL;
1N/A while (pktlen >= POET_HDRLEN) {
1N/A ttype = POET_GET_TYPE(tagp);
1N/A if (ttype == POETT_END)
1N/A break;
1N/A tlen = POET_GET_LENG(tagp);
1N/A if (tlen > pktlen - POET_HDRLEN)
1N/A break;
1N/A if (ttype == POETT_RTEADD && tlen >= sizeof (poer) &&
1N/A (str = malloc(RTE_MSG_LEN)) == NULL) {
1N/A error("cannot malloc PPPoE route");
1N/A break;
1N/A }
1N/A pktlen -= POET_HDRLEN + tlen;
1N/A tagp += POET_HDRLEN + tlen;
1N/A if (str != NULL)
1N/A break;
1N/A }
1N/A if (str == NULL)
1N/A break;
1N/A /* No alignment restrictions on source; copy to local. */
1N/A (void) memcpy(&poer, POET_DATA(tagp), sizeof (poer));
1N/A (void) slprintf(str, RTE_MSG_LEN, "%I %I %I %d",
1N/A poer.poer_dest_network, poer.poer_subnet_mask,
1N/A poer.poer_gateway, (int)poer.poer_metric);
1N/A /* Save off the environment variable version of this. */
1N/A (void) slprintf(envname, sizeof (envname), RTE_STR, ++idx);
1N/A script_setenv(envname, str, 0);
1N/A *argv++ = str; /* Destination */
1N/A str = strchr(str, ' ');
1N/A *str++ = '\0';
1N/A *argv++ = str; /* Subnet mask */
1N/A str = strchr(str, ' ');
1N/A *str++ = '\0';
1N/A *argv++ = str; /* Gateway */
1N/A str = strchr(str, ' ');
1N/A *str++ = '\0';
1N/A *argv++ = str; /* Metric */
1N/A argc -= 4;
1N/A }
1N/A *argv = NULL;
1N/A}
1N/A
1N/A/*
1N/A * If we get here, then the driver has already validated the sender,
1N/A * the PPPoE version, the message length, and session ID. The code
1N/A * number is known not to be zero.
1N/A */
1N/Astatic int
1N/Ahandle_pppoe_input(const ppptun_atype *pma, struct strbuf *ctrl,
1N/A struct strbuf *data)
1N/A{
1N/A const poep_t *poep;
1N/A struct ppp_ls *plp;
1N/A const char *mname;
1N/A const char *cstr;
1N/A char *str;
1N/A char *cp;
1N/A char *argv[64];
1N/A pid_t rpid;
1N/A char **argp;
1N/A int idx;
1N/A char envname[sizeof (RTE_STR) + 10];
1N/A const uint8_t *tagp;
1N/A int pktlen;
1N/A
1N/A /*
1N/A * Warning: the data->buf pointer here is not necessarily properly
1N/A * aligned for access to the poep_session_id or poep_length members.
1N/A */
1N/A /* LINTED: alignment */
1N/A poep = (const poep_t *)data->buf;
1N/A tagp = (const uint8_t *)poep + offsetof(poep_t, poep_length);
1N/A pktlen = (tagp[0] << 8) + tagp[1];
1N/A tagp = (const uint8_t *)(poep + 1);
1N/A switch (poep->poep_code) {
1N/A case POECODE_PADT:
1N/A dbglog("received PPPoE PADT; connection has been closed");
1N/A /* LINTED: alignment */
1N/A plp = (struct ppp_ls *)ctrl->buf;
1N/A plp->magic = PPPLSMAGIC;
1N/A plp->ppp_message = PPP_LINKSTAT_HANGUP;
1N/A ctrl->len = sizeof (*plp);
1N/A return (0);
1N/A
1N/A /* Active Discovery Message and Network extensions */
1N/A case POECODE_PADM:
1N/A case POECODE_PADN:
1N/A if (poep->poep_code == POECODE_PADM) {
1N/A argv[0] = _ROOT_PATH "/etc/ppp/pppoe-msg";
1N/A mname = "PADM";
1N/A handle_motm_hurl(argv + 4, Dim(argv) - 4, tagp, pktlen);
1N/A } else {
1N/A argv[0] = _ROOT_PATH "/etc/ppp/pppoe-network";
1N/A mname = "PADN";
1N/A handle_ip_route_add(argv + 4, Dim(argv) - 4, tagp,
1N/A pktlen);
1N/A }
1N/A argv[1] = ifname;
1N/A /* Note: strdup doesn't handle NULL input. */
1N/A str = NULL;
1N/A if ((cstr = script_getenv("IF_AND_SERVICE")) == NULL ||
1N/A (str = strdup(cstr)) == NULL) {
1N/A argv[2] = argv[3] = "";
1N/A } else {
1N/A if ((cp = strrchr(str, ':')) == NULL)
1N/A cp = str + strlen(str);
1N/A else
1N/A *cp++ = '\0';
1N/A argv[2] = str;
1N/A argv[3] = cp;
1N/A }
1N/A rpid = run_program(argv[0], argv, 0, NULL, NULL);
1N/A if (rpid == (pid_t)0)
1N/A dbglog("ignored PPPoE %s; no %s script", mname,
1N/A argv[0]);
1N/A else if (rpid != (pid_t)-1)
1N/A dbglog("PPPoE %s: started PID %d", mname, rpid);
1N/A if (str != NULL)
1N/A free(str);
1N/A /* Free storage allocated by handle_{motm_hurl,ip_route_add} */
1N/A idx = 0;
1N/A for (argp = argv + 4; *argp != NULL; ) {
1N/A if (poep->poep_code == POECODE_PADM) {
1N/A free(argp[1]);
1N/A argp += 2;
1N/A } else {
1N/A free(argp[0]);
1N/A argp += 4;
1N/A (void) slprintf(envname, sizeof (envname),
1N/A RTE_STR, ++idx);
1N/A script_unsetenv(envname);
1N/A }
1N/A }
1N/A if (poep->poep_code == POECODE_PADM) {
1N/A script_unsetenv("HURL");
1N/A script_unsetenv("MOTM");
1N/A }
1N/A break;
1N/A
1N/A default:
1N/A warn("unexpected PPPoE code %d from %s", poep->poep_code,
1N/A ether_ntoa(&pma->pta_pppoe.ptma_mac_ether_addr));
1N/A break;
1N/A }
1N/A return (-1);
1N/A}
1N/A
1N/A/*
1N/A * Handle an action code passed up from the driver.
1N/A */
1N/Astatic int
1N/Ahandle_action(struct ppptun_control *ptc, struct strbuf *ctrl,
1N/A struct strbuf *data)
1N/A{
1N/A switch (ptc->ptc_action) {
1N/A case PTCA_CONTROL:
1N/A return (handle_pppoe_input(&ptc->ptc_address, ctrl, data));
1N/A
1N/A case PTCA_BADCTRL:
1N/A warn("bad control message; session %u on %s", ptc->ptc_rsessid,
1N/A ptc->ptc_name);
1N/A return (0);
1N/A }
1N/A
1N/A return (-1);
1N/A}
1N/A
1N/A/*
1N/A * sys-solaris has just read in a packet; grovel through it and see if
1N/A * it's something we need to handle ourselves.
1N/A */
1N/Astatic int
1N/Apppoe_sys_read_packet(int retv, struct strbuf *ctrl, struct strbuf *data,
1N/A int flags)
1N/A{
1N/A struct ppptun_control *ptc;
1N/A
1N/A if (retv >= 0 && !(retv & MORECTL) && ctrl->len >= sizeof (uint32_t)) {
1N/A /* LINTED: alignment */
1N/A ptc = (struct ppptun_control *)ctrl->buf;
1N/A /* ptc_discrim is the first uint32_t of the structure. */
1N/A if (ptc->ptc_discrim == PPPOE_DISCRIM) {
1N/A retv = -1;
1N/A if (ctrl->len == sizeof (*ptc))
1N/A retv = handle_action(ptc, ctrl, data);
1N/A if (retv < 0)
1N/A errno = EAGAIN;
1N/A return (retv);
1N/A }
1N/A }
1N/A /* Forward along to other plug-ins */
1N/A if (old_sys_read_packet != NULL &&
1N/A old_sys_read_packet != pppoe_sys_read_packet)
1N/A return ((*old_sys_read_packet)(retv, ctrl, data, flags));
1N/A return (retv);
1N/A}
1N/A
1N/A/*
1N/A * Get an environment variable from the chat script.
1N/A */
1N/Astatic int
1N/Asaveenv(FILE *fd, const char *envname)
1N/A{
1N/A char envstr[1024];
1N/A int len;
1N/A
1N/A if (fgets(envstr, sizeof (envstr), fd) == NULL)
1N/A return (-1);
1N/A len = strlen(envstr);
1N/A if (len <= 1)
1N/A return (0);
1N/A envstr[len-1] = '\0';
1N/A script_setenv(envname, envstr, 0);
1N/A return (1);
1N/A}
1N/A
1N/A/*
1N/A * Read environment variables exported by chat script.
1N/A */
1N/Astatic void
1N/Apppoe_device_pipe(int pipefd)
1N/A{
1N/A FILE *fd;
1N/A int i;
1N/A char envname[32];
1N/A
1N/A fd = fdopen(pipefd, "r");
1N/A if (fd == NULL)
1N/A fatal("unable to open environment file: %m");
1N/A (void) saveenv(fd, "IF_AND_SERVICE");
1N/A (void) saveenv(fd, "SERVICE_NAME");
1N/A (void) saveenv(fd, "AC_NAME");
1N/A (void) saveenv(fd, "AC_MAC");
1N/A (void) saveenv(fd, "SESSION_ID");
1N/A for (i = 1; ; i++) {
1N/A (void) slprintf(envname, sizeof (envname),
1N/A "VENDOR_SPECIFIC_%d", i);
1N/A if (saveenv(fd, envname) <= 0)
1N/A break;
1N/A }
1N/A (void) fclose(fd);
1N/A}
1N/A
1N/Avoid
1N/Aplugin_init(void)
1N/A{
1N/A if (absmax_mtu > 1492)
1N/A absmax_mtu = 1492;
1N/A if (absmax_mru > 1492)
1N/A absmax_mru = 1492;
1N/A old_check_options = check_options_hook;
1N/A check_options_hook = pppoe_check_options;
1N/A old_updown_script = updown_script_hook;
1N/A updown_script_hook = pppoe_updown_script;
1N/A old_sys_read_packet = sys_read_packet_hook;
1N/A sys_read_packet_hook = pppoe_sys_read_packet;
1N/A device_pipe_hook = pppoe_device_pipe;
1N/A already_ppp = 1;
1N/A}