xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/pppd/plugins/pppoe.c (revision 4eaa471005973e11a6110b69fe990530b3b95a38)
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 2009 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 		slprintf(envname, sizeof (envname), "VENDOR_SPECIFIC_%d", i);
494 		if (saveenv(fd, envname) <= 0)
495 			break;
496 	}
497 	(void) fclose(fd);
498 }
499 
500 void
501 plugin_init(void)
502 {
503 	if (absmax_mtu > 1492)
504 		absmax_mtu = 1492;
505 	if (absmax_mru > 1492)
506 		absmax_mru = 1492;
507 	old_check_options = check_options_hook;
508 	check_options_hook = pppoe_check_options;
509 	old_updown_script = updown_script_hook;
510 	updown_script_hook = pppoe_updown_script;
511 	old_sys_read_packet = sys_read_packet_hook;
512 	sys_read_packet_hook = pppoe_sys_read_packet;
513 	device_pipe_hook = pppoe_device_pipe;
514 	already_ppp = 1;
515 }
516