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
strioctl(int fd,int cmd,void * ptr,int ilen,int olen)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
pppoe_check_options(uid_t uid)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
pppoe_updown_script(const char *** argsp)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
cat_save_env(char ** argv,char idchar,const char * envname)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
handle_motm_hurl(char ** argv,int argc,const uint8_t * tagp,int pktlen)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
handle_ip_route_add(char ** argv,int argc,const uint8_t * tagp,int pktlen)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
handle_pppoe_input(const ppptun_atype * pma,struct strbuf * ctrl,struct strbuf * data)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
handle_action(struct ppptun_control * ptc,struct strbuf * ctrl,struct strbuf * data)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
pppoe_sys_read_packet(int retv,struct strbuf * ctrl,struct strbuf * data,int flags)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
saveenv(FILE * fd,const char * envname)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
pppoe_device_pipe(int pipefd)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
plugin_init(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