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