Home | History | Annotate | Download | only in plugins
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 /*
     28  * pppoe.c - pppd plugin to handle PPPoE operation.
     29  */
     30 
     31 #include <unistd.h>
     32 #include <stddef.h>
     33 #include <stdlib.h>
     34 #include <errno.h>
     35 #include <sys/types.h>
     36 #include <fcntl.h>
     37 #include <strings.h>
     38 #include <sys/stropts.h>
     39 #include <netinet/in.h>
     40 #include <net/pppio.h>
     41 #include <net/sppptun.h>
     42 #include <net/pppoe.h>
     43 
     44 #include "pppd.h"
     45 #include "pathnames.h"
     46 
     47 /* Saved hook pointers */
     48 static int (*old_check_options)(uid_t uid);
     49 static int (*old_updown_script)(const char ***argsp);
     50 static int (*old_sys_read_packet)(int retv, struct strbuf *ctrl,
     51     struct strbuf *data, int flags);
     52 
     53 /* Room for 3 IPv4 addresses and metric */
     54 #define	RTE_MSG_LEN	(3*16 + 10 + 1)
     55 
     56 /* Environment string for routes */
     57 #define	RTE_STR	"ROUTE_%d"
     58 
     59 /*
     60  * strioctl()
     61  *
     62  * wrapper for STREAMS I_STR ioctl.
     63  */
     64 static int
     65 strioctl(int fd, int cmd, void *ptr, int ilen, int olen)
     66 {
     67 	struct strioctl	str;
     68 
     69 	str.ic_cmd = cmd;
     70 	str.ic_timout = 0;	/* Use default timer; 15 seconds */
     71 	str.ic_len = ilen;
     72 	str.ic_dp = ptr;
     73 
     74 	if (ioctl(fd, I_STR, &str) == -1) {
     75 		return (-1);
     76 	}
     77 	if (str.ic_len != olen) {
     78 		return (-1);
     79 	}
     80 	return (0);
     81 }
     82 
     83 /*
     84  * If the user named the tunneling device, check that it is
     85  * reasonable; otherwise check that standard input is the tunnel.
     86  */
     87 static int
     88 pppoe_check_options(uid_t uid)
     89 {
     90 	int tstfd;	/* fd for device being checked */
     91 	int err;	/* saved errno value */
     92 	int retv;	/* return value */
     93 	int intv;	/* integer return value (from ioctl) */
     94 	union ppptun_name ptn;
     95 
     96 	if (devnam[0] != '\0') {
     97 		/*
     98 		 * Open as real user so that modes on device can be
     99 		 * used to limit access.
    100 		 */
    101 		if (!devnam_info.priv)
    102 			(void) seteuid(uid);
    103 		tstfd = open(devnam, O_NONBLOCK | O_RDWR, 0);
    104 		err = errno;
    105 		if (!devnam_info.priv)
    106 			(void) seteuid(0);
    107 		if (tstfd == -1) {
    108 			errno = err;
    109 			option_error("unable to open %s: %m", devnam);
    110 			return (-1);
    111 		}
    112 		retv = strioctl(tstfd, PPPTUN_GDATA, &ptn, 0, sizeof (ptn));
    113 		(void) close(tstfd);
    114 		if (retv == -1) {
    115 			option_error("device %s is not a PPP tunneling device",
    116 			    devnam);
    117 			return (-1);
    118 		}
    119 	} else {
    120 		retv = strioctl(0, PPPIO_GTYPE, &intv, 0, sizeof (intv));
    121 		if (retv == -1) {
    122 			option_error("standard input is not a PPP device");
    123 			return (-1);
    124 		}
    125 		retv = strioctl(0, PPPTUN_GDATA, &ptn, 0, sizeof (ptn));
    126 		if (retv == -1) {
    127 			option_error("standard input is not a PPP tunnel");
    128 			return (-1);
    129 		}
    130 		if (strcmp(ptn.ptn_name + strlen(ptn.ptn_name) - 6,
    131 		    ":pppoe") != 0) {
    132 			option_error("standard input not connected to PPPoE");
    133 			return (-1);
    134 		}
    135 	}
    136 	if (old_check_options != NULL &&
    137 	    old_check_options != pppoe_check_options)
    138 		return ((*old_check_options)(uid));
    139 	return (0);
    140 }
    141 
    142 /*
    143  * When we're about to call one of the up or down scripts, change the
    144  * second argument to contain the interface name and selected PPPoE
    145  * service.
    146  */
    147 static int
    148 pppoe_updown_script(const char ***argsp)
    149 {
    150 	const char *cp;
    151 
    152 	if ((*argsp)[2] == devnam &&
    153 	    (cp = script_getenv("IF_AND_SERVICE")) != NULL)
    154 		(*argsp)[2] = cp;
    155 	if (old_updown_script != NULL &&
    156 	    old_updown_script != pppoe_updown_script)
    157 		return ((*old_updown_script)(argsp));
    158 	return (0);
    159 }
    160 
    161 /*
    162  * Concatenate and save strings from command line into environment
    163  * variable.
    164  */
    165 static void
    166 cat_save_env(char **argv, char idchar, const char *envname)
    167 {
    168 	char **argp;
    169 	int totlen;
    170 	char *str;
    171 	char *cp;
    172 
    173 	totlen = 0;
    174 	for (argp = argv; argp[0] != NULL; argp += 2)
    175 		if (*argp[0] == idchar)
    176 			totlen += strlen(argp[1]) + 1;
    177 	if ((str = malloc(totlen + 1)) == NULL) {
    178 		error("cannot malloc PPPoE environment for %s", envname);
    179 		return;
    180 	}
    181 	cp = str;
    182 	for (argp = argv; argp[0] != NULL; argp += 2)
    183 		if (*argp[0] == idchar) {
    184 			(void) strcpy(cp, argp[1]);
    185 			cp += strlen(cp);
    186 			*cp++ = '\n';
    187 		}
    188 	*cp = '\0';
    189 	script_setenv(envname, str, 0);
    190 }
    191 
    192 /*
    193  * Convert Message Of The Moment (MOTM) and Host Uniform Resource
    194  * Locator (HURL) strings into environment variables and command-line
    195  * arguments for script.
    196  */
    197 static void
    198 handle_motm_hurl(char **argv, int argc, const uint8_t *tagp, int pktlen)
    199 {
    200 	int ttype;
    201 	int tlen;
    202 	char *str;
    203 	char **oargv = argv;
    204 
    205 	/* Must have room for two strings and NULL terminator. */
    206 	while (argc >= 3) {
    207 		str = NULL;
    208 		while (pktlen >= POET_HDRLEN) {
    209 			ttype = POET_GET_TYPE(tagp);
    210 			if (ttype == POETT_END)
    211 				break;
    212 			tlen = POET_GET_LENG(tagp);
    213 			if (tlen > pktlen - POET_HDRLEN)
    214 				break;
    215 			if (ttype == POETT_HURL || ttype == POETT_MOTM) {
    216 				if ((str = malloc(tlen + 1)) == NULL) {
    217 					error("cannot malloc PPPoE message");
    218 					break;
    219 				}
    220 				(void) memcpy(str, POET_DATA(tagp), tlen);
    221 				str[tlen] = '\0';
    222 			}
    223 			pktlen -= POET_HDRLEN + tlen;
    224 			tagp += POET_HDRLEN + tlen;
    225 			if (str != NULL)
    226 				break;
    227 		}
    228 		if (str == NULL)
    229 			break;
    230 		*argv++ = ttype == POETT_HURL ? "hurl" : "motm";
    231 		*argv++ = str;
    232 		argc -= 2;
    233 	}
    234 	*argv = NULL;
    235 	cat_save_env(oargv, 'h', "HURL");
    236 	cat_save_env(oargv, 'm', "MOTM");
    237 }
    238 
    239 /*
    240  * Convert IP Route Add structures into environment variables and
    241  * command-line arguments for script.
    242  */
    243 static void
    244 handle_ip_route_add(char **argv, int argc, const uint8_t *tagp, int pktlen)
    245 {
    246 	int ttype;
    247 	int tlen;
    248 	char *str;
    249 	poer_t poer;
    250 	int idx;
    251 	char envname[sizeof (RTE_STR) + 10];
    252 
    253 	idx = 0;
    254 
    255 	/* Must have room for four strings and NULL terminator. */
    256 	while (argc >= 5) {
    257 		str = NULL;
    258 		while (pktlen >= POET_HDRLEN) {
    259 			ttype = POET_GET_TYPE(tagp);
    260 			if (ttype == POETT_END)
    261 				break;
    262 			tlen = POET_GET_LENG(tagp);
    263 			if (tlen > pktlen - POET_HDRLEN)
    264 				break;
    265 			if (ttype == POETT_RTEADD && tlen >= sizeof (poer) &&
    266 			    (str = malloc(RTE_MSG_LEN)) == NULL) {
    267 				error("cannot malloc PPPoE route");
    268 				break;
    269 			}
    270 			pktlen -= POET_HDRLEN + tlen;
    271 			tagp += POET_HDRLEN + tlen;
    272 			if (str != NULL)
    273 				break;
    274 		}
    275 		if (str == NULL)
    276 			break;
    277 		/* No alignment restrictions on source; copy to local. */
    278 		(void) memcpy(&poer, POET_DATA(tagp), sizeof (poer));
    279 		(void) slprintf(str, RTE_MSG_LEN, "%I %I %I %d",
    280 		    poer.poer_dest_network, poer.poer_subnet_mask,
    281 		    poer.poer_gateway, (int)poer.poer_metric);
    282 		/* Save off the environment variable version of this. */
    283 		(void) slprintf(envname, sizeof (envname), RTE_STR, ++idx);
    284 		script_setenv(envname, str, 0);
    285 		*argv++ = str;	/* Destination */
    286 		str = strchr(str, ' ');
    287 		*str++ = '\0';
    288 		*argv++ = str;	/* Subnet mask */
    289 		str = strchr(str, ' ');
    290 		*str++ = '\0';
    291 		*argv++ = str;	/* Gateway */
    292 		str = strchr(str, ' ');
    293 		*str++ = '\0';
    294 		*argv++ = str;	/* Metric */
    295 		argc -= 4;
    296 	}
    297 	*argv = NULL;
    298 }
    299 
    300 /*
    301  * If we get here, then the driver has already validated the sender,
    302  * the PPPoE version, the message length, and session ID.  The code
    303  * number is known not to be zero.
    304  */
    305 static int
    306 handle_pppoe_input(const ppptun_atype *pma, struct strbuf *ctrl,
    307     struct strbuf *data)
    308 {
    309 	const poep_t *poep;
    310 	struct ppp_ls *plp;
    311 	const char *mname;
    312 	const char *cstr;
    313 	char *str;
    314 	char *cp;
    315 	char *argv[64];
    316 	pid_t rpid;
    317 	char **argp;
    318 	int idx;
    319 	char envname[sizeof (RTE_STR) + 10];
    320 	const uint8_t *tagp;
    321 	int pktlen;
    322 
    323 	/*
    324 	 * Warning: the data->buf pointer here is not necessarily properly
    325 	 * aligned for access to the poep_session_id or poep_length members.
    326 	 */
    327 	/* LINTED: alignment */
    328 	poep = (const poep_t *)data->buf;
    329 	tagp = (const uint8_t *)poep + offsetof(poep_t, poep_length);
    330 	pktlen = (tagp[0] << 8) + tagp[1];
    331 	tagp = (const uint8_t *)(poep + 1);
    332 	switch (poep->poep_code) {
    333 	case POECODE_PADT:
    334 		dbglog("received PPPoE PADT; connection has been closed");
    335 		/* LINTED: alignment */
    336 		plp = (struct ppp_ls *)ctrl->buf;
    337 		plp->magic = PPPLSMAGIC;
    338 		plp->ppp_message = PPP_LINKSTAT_HANGUP;
    339 		ctrl->len = sizeof (*plp);
    340 		return (0);
    341 
    342 		/* Active Discovery Message and Network extensions */
    343 	case POECODE_PADM:
    344 	case POECODE_PADN:
    345 		if (poep->poep_code == POECODE_PADM) {
    346 			argv[0] = _ROOT_PATH "/etc/ppp/pppoe-msg";
    347 			mname = "PADM";
    348 			handle_motm_hurl(argv + 4, Dim(argv) - 4, tagp, pktlen);
    349 		} else {
    350 			argv[0] = _ROOT_PATH "/etc/ppp/pppoe-network";
    351 			mname = "PADN";
    352 			handle_ip_route_add(argv + 4, Dim(argv) - 4, tagp,
    353 			    pktlen);
    354 		}
    355 		argv[1] = ifname;
    356 		/* Note: strdup doesn't handle NULL input. */
    357 		str = NULL;
    358 		if ((cstr = script_getenv("IF_AND_SERVICE")) == NULL ||
    359 		    (str = strdup(cstr)) == NULL) {
    360 			argv[2] = argv[3] = "";
    361 		} else {
    362 			if ((cp = strrchr(str, ':')) == NULL)
    363 				cp = str + strlen(str);
    364 			else
    365 				*cp++ = '\0';
    366 			argv[2] = str;
    367 			argv[3] = cp;
    368 		}
    369 		rpid = run_program(argv[0], argv, 0, NULL, NULL);
    370 		if (rpid == (pid_t)0)
    371 			dbglog("ignored PPPoE %s; no %s script", mname,
    372 			    argv[0]);
    373 		else if (rpid != (pid_t)-1)
    374 			dbglog("PPPoE %s: started PID %d", mname, rpid);
    375 		if (str != NULL)
    376 			free(str);
    377 		/* Free storage allocated by handle_{motm_hurl,ip_route_add} */
    378 		idx = 0;
    379 		for (argp = argv + 4; *argp != NULL; ) {
    380 			if (poep->poep_code == POECODE_PADM) {
    381 				free(argp[1]);
    382 				argp += 2;
    383 			} else {
    384 				free(argp[0]);
    385 				argp += 4;
    386 				(void) slprintf(envname, sizeof (envname),
    387 				    RTE_STR, ++idx);
    388 				script_unsetenv(envname);
    389 			}
    390 		}
    391 		if (poep->poep_code == POECODE_PADM) {
    392 			script_unsetenv("HURL");
    393 			script_unsetenv("MOTM");
    394 		}
    395 		break;
    396 
    397 	default:
    398 		warn("unexpected PPPoE code %d from %s", poep->poep_code,
    399 		    ether_ntoa(&pma->pta_pppoe.ptma_mac_ether_addr));
    400 		break;
    401 	}
    402 	return (-1);
    403 }
    404 
    405 /*
    406  * Handle an action code passed up from the driver.
    407  */
    408 static int
    409 handle_action(struct ppptun_control *ptc, struct strbuf *ctrl,
    410     struct strbuf *data)
    411 {
    412 	switch (ptc->ptc_action) {
    413 	case PTCA_CONTROL:
    414 		return (handle_pppoe_input(&ptc->ptc_address, ctrl, data));
    415 
    416 	case PTCA_BADCTRL:
    417 		warn("bad control message; session %u on %s", ptc->ptc_rsessid,
    418 		    ptc->ptc_name);
    419 		return (0);
    420 	}
    421 
    422 	return (-1);
    423 }
    424 
    425 /*
    426  * sys-solaris has just read in a packet; grovel through it and see if
    427  * it's something we need to handle ourselves.
    428  */
    429 static int
    430 pppoe_sys_read_packet(int retv, struct strbuf *ctrl, struct strbuf *data,
    431     int flags)
    432 {
    433 	struct ppptun_control *ptc;
    434 
    435 	if (retv >= 0 && !(retv & MORECTL) && ctrl->len >= sizeof (uint32_t)) {
    436 		/* LINTED: alignment */
    437 		ptc = (struct ppptun_control *)ctrl->buf;
    438 		/* ptc_discrim is the first uint32_t of the structure. */
    439 		if (ptc->ptc_discrim == PPPOE_DISCRIM) {
    440 			retv = -1;
    441 			if (ctrl->len == sizeof (*ptc))
    442 				retv = handle_action(ptc, ctrl, data);
    443 			if (retv < 0)
    444 				errno = EAGAIN;
    445 			return (retv);
    446 		}
    447 	}
    448 	/* Forward along to other plug-ins */
    449 	if (old_sys_read_packet != NULL &&
    450 	    old_sys_read_packet != pppoe_sys_read_packet)
    451 		return ((*old_sys_read_packet)(retv, ctrl, data, flags));
    452 	return (retv);
    453 }
    454 
    455 /*
    456  * Get an environment variable from the chat script.
    457  */
    458 static int
    459 saveenv(FILE *fd, const char *envname)
    460 {
    461 	char envstr[1024];
    462 	int len;
    463 
    464 	if (fgets(envstr, sizeof (envstr), fd) == NULL)
    465 		return (-1);
    466 	len = strlen(envstr);
    467 	if (len <= 1)
    468 		return (0);
    469 	envstr[len-1] = '\0';
    470 	script_setenv(envname, envstr, 0);
    471 	return (1);
    472 }
    473 
    474 /*
    475  * Read environment variables exported by chat script.
    476  */
    477 static void
    478 pppoe_device_pipe(int pipefd)
    479 {
    480 	FILE *fd;
    481 	int i;
    482 	char envname[32];
    483 
    484 	fd = fdopen(pipefd, "r");
    485 	if (fd == NULL)
    486 		fatal("unable to open environment file: %m");
    487 	(void) saveenv(fd, "IF_AND_SERVICE");
    488 	(void) saveenv(fd, "SERVICE_NAME");
    489 	(void) saveenv(fd, "AC_NAME");
    490 	(void) saveenv(fd, "AC_MAC");
    491 	(void) saveenv(fd, "SESSION_ID");
    492 	for (i = 1; ; i++) {
    493 		(void) slprintf(envname, sizeof (envname),
    494 		    "VENDOR_SPECIFIC_%d", i);
    495 		if (saveenv(fd, envname) <= 0)
    496 			break;
    497 	}
    498 	(void) fclose(fd);
    499 }
    500 
    501 void
    502 plugin_init(void)
    503 {
    504 	if (absmax_mtu > 1492)
    505 		absmax_mtu = 1492;
    506 	if (absmax_mru > 1492)
    507 		absmax_mru = 1492;
    508 	old_check_options = check_options_hook;
    509 	check_options_hook = pppoe_check_options;
    510 	old_updown_script = updown_script_hook;
    511 	updown_script_hook = pppoe_updown_script;
    512 	old_sys_read_packet = sys_read_packet_hook;
    513 	sys_read_packet_hook = pppoe_sys_read_packet;
    514 	device_pipe_hook = pppoe_device_pipe;
    515 	already_ppp = 1;
    516 }
    517