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