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 2004 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 * sys-solaris has just read in a packet; grovel through it and see if 407 * it's something we need to handle ourselves. 408 */ 409 static int 410 pppoe_sys_read_packet(int retv, struct strbuf *ctrl, struct strbuf *data, 411 int flags) 412 { 413 struct ppptun_control *ptc; 414 415 if (retv >= 0 && !(retv & MORECTL) && ctrl->len >= sizeof (uint32_t)) { 416 /* LINTED: alignment */ 417 ptc = (struct ppptun_control *)ctrl->buf; 418 /* ptc_discrim is the first uint32_t of the structure. */ 419 if (ptc->ptc_discrim == PPPOE_DISCRIM) { 420 retv = -1; 421 if (ctrl->len == sizeof (*ptc) && 422 ptc->ptc_action == PTCA_CONTROL) 423 retv = handle_pppoe_input(&ptc->ptc_address, 424 ctrl, data); 425 if (retv < 0) 426 errno = EAGAIN; 427 return (retv); 428 } 429 } 430 /* Forward along to other plug-ins */ 431 if (old_sys_read_packet != NULL && 432 old_sys_read_packet != pppoe_sys_read_packet) 433 return ((*old_sys_read_packet)(retv, ctrl, data, flags)); 434 return (retv); 435 } 436 437 /* 438 * Get an environment variable from the chat script. 439 */ 440 static int 441 saveenv(FILE *fd, const char *envname) 442 { 443 char envstr[1024]; 444 int len; 445 446 if (fgets(envstr, sizeof (envstr), fd) == NULL) 447 return (-1); 448 len = strlen(envstr); 449 if (len <= 1) 450 return (0); 451 envstr[len-1] = '\0'; 452 script_setenv(envname, envstr, 0); 453 return (1); 454 } 455 456 /* 457 * Read environment variables exported by chat script. 458 */ 459 static void 460 pppoe_device_pipe(int pipefd) 461 { 462 FILE *fd; 463 int i; 464 char envname[32]; 465 466 fd = fdopen(pipefd, "r"); 467 if (fd == NULL) 468 fatal("unable to open environment file: %m"); 469 (void) saveenv(fd, "IF_AND_SERVICE"); 470 (void) saveenv(fd, "SERVICE_NAME"); 471 (void) saveenv(fd, "AC_NAME"); 472 (void) saveenv(fd, "AC_MAC"); 473 (void) saveenv(fd, "SESSION_ID"); 474 for (i = 1; ; i++) { 475 slprintf(envname, sizeof (envname), "VENDOR_SPECIFIC_%d", i); 476 if (saveenv(fd, envname) <= 0) 477 break; 478 } 479 (void) fclose(fd); 480 } 481 482 void 483 plugin_init(void) 484 { 485 if (absmax_mtu > 1492) 486 absmax_mtu = 1492; 487 if (absmax_mru > 1492) 488 absmax_mru = 1492; 489 old_check_options = check_options_hook; 490 check_options_hook = pppoe_check_options; 491 old_updown_script = updown_script_hook; 492 updown_script_hook = pppoe_updown_script; 493 old_sys_read_packet = sys_read_packet_hook; 494 sys_read_packet_hook = pppoe_sys_read_packet; 495 device_pipe_hook = pppoe_device_pipe; 496 already_ppp = 1; 497 } 498