pppoed.c revision 53537
1/*- 2 * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: head/libexec/pppoed/pppoed.c 53537 1999-11-21 23:39:14Z brian $ 27 */ 28 29#include <sys/param.h> 30#include <sys/socket.h> 31#include <sys/un.h> 32#include <netinet/in.h> 33#include <arpa/inet.h> 34#include <netdb.h> 35#include <netgraph.h> 36#include <net/ethernet.h> 37#include <netinet/in_systm.h> 38#include <netinet/ip.h> 39#include <netgraph/ng_ether.h> 40#include <netgraph/ng_message.h> 41#include <netgraph/ng_pppoe.h> 42#include <netgraph/ng_socket.h> 43 44#include <errno.h> 45#include <paths.h> 46#include <signal.h> 47#include <stdio.h> 48#include <stdlib.h> 49#include <string.h> 50#include <sysexits.h> 51#include <sys/fcntl.h> 52#ifndef NOKLDLOAD 53#include <sys/linker.h> 54#include <sys/module.h> 55#endif 56#include <sys/uio.h> 57#include <sys/wait.h> 58#include <syslog.h> 59#include <termios.h> 60#include <unistd.h> 61 62 63#define DEFAULT_EXEC_PREFIX "exec /usr/sbin/ppp -direct " 64 65static int 66usage(const char *prog) 67{ 68 fprintf(stderr, "Usage: %s [-Fd] [-a name] [-e exec] [-p provider]" 69 " interface\n", prog); 70 return EX_USAGE; 71} 72 73static void 74Fairwell(int sig) 75{ 76 char buf[] = "Received signal XX, exiting"; 77 78 /* No stdio in a signal handler */ 79 buf[16] = '0' + ((sig / 10) % 10); 80 buf[17] = '0' + (sig % 10); 81 82 syslog(LOG_INFO, buf); 83 84 signal(sig, SIG_DFL); 85 raise(sig); 86} 87 88static int 89ConfigureNode(const char *prog, const char *iface, const char *provider, 90 int cs, int ds, int debug, struct ngm_connect *ngc) 91{ 92 /* 93 * We're going to do this with the passed `ds' & `cs' descriptors: 94 * 95 * .---------. 96 * | ether | 97 * | <iface> | 98 * `---------' 99 * (orphan) ds cs 100 * | | | 101 * | | | 102 * (ethernet) | | 103 * .---------. .-----------. 104 * | pppoe | | socket | 105 * | <iface> |(pppoe-<pid>)<---->(pppoe-<pid>)| <unnamed> | 106 * `--------- `-----------' 107 * (exec-<pid>) 108 * ^ .-----------. .-------------. 109 * | | socket | | ppp -direct | 110 * `--->(exec-<pid>)| <unnamed> |--fd--| provider | 111 * `-----------' `-------------' 112 * 113 * where there are potentially many ppp processes running off of the 114 * same PPPoE node. 115 * The exec-<pid> hook isn't made 'till we Spawn(). 116 */ 117 118 char *epath, *spath; 119 struct ngpppoe_init_data *data; 120 const struct hooklist *hlist; 121 const struct nodeinfo *ninfo; 122 const struct linkinfo *nlink; 123 struct ngm_mkpeer mkp; 124 struct ng_mesg *resp; 125 u_char rbuf[2048]; 126 int f, plen; 127 128 /* 129 * Ask for a list of hooks attached to the "ether" node. This node should 130 * magically exist as a way of hooking stuff onto an ethernet device 131 */ 132 epath = (char *)alloca(strlen(iface) + 2); 133 sprintf(epath, "%s:", iface); 134 135 if (debug) 136 fprintf(stderr, "Sending NGM_LISTHOOKS to %s\n", epath); 137 138 if (NgSendMsg(cs, epath, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0) < 0) { 139 if (errno == ENOENT) 140 fprintf(stderr, "%s Cannot send a netgraph message: Invalid interface\n", 141 epath); 142 else 143 fprintf(stderr, "%s Cannot send a netgraph message: %s\n", 144 epath, strerror(errno)); 145 return EX_UNAVAILABLE; 146 } 147 148 /* Get our list back */ 149 resp = (struct ng_mesg *)rbuf; 150 if (NgRecvMsg(cs, resp, sizeof rbuf, NULL) < 0) { 151 perror("Cannot get netgraph response"); 152 return EX_UNAVAILABLE; 153 } 154 155 hlist = (const struct hooklist *)resp->data; 156 ninfo = &hlist->nodeinfo; 157 158 if (debug) 159 fprintf(stderr, "Got reply from id [%x]: Type %s with %d hooks\n", 160 ninfo->id, ninfo->type, ninfo->hooks); 161 162 /* Make sure we've got the right type of node */ 163 if (strncmp(ninfo->type, NG_ETHER_NODE_TYPE, sizeof NG_ETHER_NODE_TYPE - 1)) { 164 fprintf(stderr, "%s Unexpected node type ``%s'' (wanted ``" 165 NG_ETHER_NODE_TYPE "'')\n", epath, ninfo->type); 166 return EX_DATAERR; 167 } 168 169 /* look for a hook already attached. */ 170 for (f = 0; f < ninfo->hooks; f++) { 171 nlink = &hlist->link[f]; 172 173 if (debug) 174 fprintf(stderr, " Got [%x]:%s -> [%x]:%s\n", ninfo->id, 175 nlink->ourhook, nlink->nodeinfo.id, nlink->peerhook); 176 177 if (!strcmp(nlink->ourhook, NG_ETHER_HOOK_ORPHAN) || 178 !strcmp(nlink->ourhook, NG_ETHER_HOOK_DIVERT)) { 179 /* 180 * Something is using the data coming out of this `ether' node. 181 * If it's a PPPoE node, we use that node, otherwise we complain that 182 * someone else is using the node. 183 */ 184 if (strcmp(nlink->nodeinfo.type, NG_PPPOE_NODE_TYPE)) { 185 fprintf(stderr, "%s Node type %s is currently active\n", 186 epath, nlink->nodeinfo.type); 187 return EX_UNAVAILABLE; 188 } 189 break; 190 } 191 } 192 193 if (f == ninfo->hooks) { 194 /* 195 * Create a new PPPoE node connected to the `ether' node using 196 * the magic `orphan' and `ethernet' hooks 197 */ 198 snprintf(mkp.type, sizeof mkp.type, "%s", NG_PPPOE_NODE_TYPE); 199 snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", NG_ETHER_HOOK_ORPHAN); 200 snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", NG_PPPOE_HOOK_ETHERNET); 201 202 if (debug) 203 fprintf(stderr, "Send MKPEER: %s%s -> [type %s]:%s\n", epath, 204 mkp.ourhook, mkp.type, mkp.peerhook); 205 206 if (NgSendMsg(cs, epath, NGM_GENERIC_COOKIE, 207 NGM_MKPEER, &mkp, sizeof mkp) < 0) { 208 fprintf(stderr, "%s Cannot create a peer PPPoE node: %s\n", 209 epath, strerror(errno)); 210 return EX_OSERR; 211 } 212 } 213 214 /* Connect the PPPoE node to our socket node. */ 215 snprintf(ngc->path, sizeof ngc->path, "%s%s", epath, NG_ETHER_HOOK_ORPHAN); 216 snprintf(ngc->ourhook, sizeof ngc->ourhook, "pppoe-%ld", (long)getpid()); 217 memcpy(ngc->peerhook, ngc->ourhook, sizeof ngc->peerhook); 218 219 if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, 220 NGM_CONNECT, ngc, sizeof *ngc) < 0) { 221 perror("Cannot CONNECT PPPoE and socket nodes"); 222 return EX_OSERR; 223 } 224 225 plen = strlen(provider); 226 227 data = (struct ngpppoe_init_data *)alloca(sizeof *data + plen + 1); 228 snprintf(data->hook, sizeof data->hook, "%s", ngc->peerhook); 229 strcpy(data->data, provider); 230 data->data_len = plen; 231 232 spath = (char *)alloca(strlen(ngc->peerhook) + 3); 233 strcpy(spath, ".:"); 234 strcpy(spath + 2, ngc->ourhook); 235 236 if (debug) { 237 if (provider) 238 fprintf(stderr, "Sending PPPOE_LISTEN to %s, provider %s\n", 239 spath, provider); 240 else 241 fprintf(stderr, "Sending PPPOE_LISTEN to %s\n", spath); 242 } 243 244 if (NgSendMsg(cs, spath, NGM_PPPOE_COOKIE, NGM_PPPOE_LISTEN, 245 data, sizeof *data + plen) == -1) { 246 fprintf(stderr, "%s: Cannot LISTEN on netgraph node: %s\n", 247 spath, strerror(errno)); 248 return EX_OSERR; 249 } 250 251 return 0; 252} 253 254static void 255Spawn(const char *prog, const char *acname, const char *exec, 256 struct ngm_connect ngc, int cs, int ds, void *request, int sz, 257 int debug) 258{ 259 char msgbuf[sizeof(struct ng_mesg) + sizeof(struct ngpppoe_sts)]; 260 struct ng_mesg *rep = (struct ng_mesg *)msgbuf; 261 struct ngpppoe_sts *sts = (struct ngpppoe_sts *)(msgbuf + sizeof *rep); 262 struct ngpppoe_init_data *data; 263 char unknown[14], *path; 264 const char *msg; 265 int ret, slen; 266 267 switch ((ret = fork())) { 268 case -1: 269 syslog(LOG_ERR, "fork: %m"); 270 break; 271 272 case 0: 273 switch (fork()) { 274 case 0: 275 break; 276 case -1: 277 _exit(errno); 278 default: 279 _exit(0); 280 } 281 close(cs); 282 close(ds); 283 284 /* Create a new socket node */ 285 if (debug) 286 syslog(LOG_INFO, "Creating a new socket node"); 287 288 if (NgMkSockNode(NULL, &cs, &ds) == -1) { 289 syslog(LOG_ERR, "Cannot create netgraph socket node: %m"); 290 _exit(EX_CANTCREAT); 291 } 292 293 /* Connect the PPPoE node to our new socket node. */ 294 snprintf(ngc.ourhook, sizeof ngc.ourhook, "exec-%ld", (long)getpid()); 295 memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook); 296 297 if (debug) 298 syslog(LOG_INFO, "Sending CONNECT from .:%s -> %s.%s", 299 ngc.ourhook, ngc.path, ngc.peerhook); 300 if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, 301 NGM_CONNECT, &ngc, sizeof ngc) < 0) { 302 syslog(LOG_ERR, "Cannot CONNECT PPPoE and socket nodes: %m"); 303 _exit(EX_OSERR); 304 } 305 306 /* 307 * If we tell the socket node not to LINGER, it will go away when 308 * the last hook is removed. 309 */ 310 if (debug) 311 syslog(LOG_INFO, "Sending NGM_SOCK_CMD_NOLINGER to socket"); 312 if (NgSendMsg(cs, ".:", NGM_SOCKET_COOKIE, 313 NGM_SOCK_CMD_NOLINGER, NULL, 0) < 0) { 314 syslog(LOG_ERR, "Cannot send NGM_SOCK_CMD_NOLINGER: %m"); 315 _exit(EX_OSERR); 316 } 317 318 /* Put the PPPoE node into OFFER mode */ 319 slen = strlen(acname); 320 data = (struct ngpppoe_init_data *)alloca(sizeof *data + slen + 1); 321 snprintf(data->hook, sizeof data->hook, "%s", ngc.ourhook); 322 strcpy(data->data, acname); 323 data->data_len = slen; 324 325 path = (char *)alloca(strlen(ngc.ourhook) + 3); 326 strcpy(path, ".:"); 327 strcpy(path + 2, ngc.ourhook); 328 329 syslog(LOG_INFO, "Offering to %s as access concentrator %s", 330 path, acname); 331 if (NgSendMsg(cs, path, NGM_PPPOE_COOKIE, NGM_PPPOE_OFFER, 332 data, sizeof *data + slen) == -1) { 333 syslog(LOG_INFO, "%s: Cannot OFFER on netgraph node: %m", path); 334 _exit(EX_OSERR); 335 } 336 337 /* And send our request data to the waiting node */ 338 if (debug) 339 syslog(LOG_INFO, "Sending original request to %s (%d bytes)", path, sz); 340 if (NgSendData(ds, ngc.ourhook, request, sz) == -1) { 341 syslog(LOG_ERR, "Cannot send original request to %s: %m", path); 342 _exit(EX_OSERR); 343 } 344 345 /* Then wait for a success indication */ 346 347 if (debug) 348 syslog(LOG_INFO, "Waiting for a SUCCESS reply %s", path); 349 350 do { 351 if (NgRecvMsg(cs, rep, sizeof msgbuf, NULL) < 0) { 352 syslog(LOG_ERR, "%s: Cannot receive a message: %m", path); 353 _exit(EX_OSERR); 354 } 355 356 if (rep->header.version != NG_VERSION) { 357 syslog(LOG_ERR, "%ld: Unexpected netgraph version, expected %ld", 358 (long)rep->header.version, (long)NG_VERSION); 359 _exit(EX_PROTOCOL); 360 } 361 362 if (rep->header.typecookie != NGM_PPPOE_COOKIE) { 363 syslog(LOG_INFO, "%ld: Unexpected netgraph cookie, expected %ld", 364 (long)rep->header.typecookie, (long)NGM_PPPOE_COOKIE); 365 continue; 366 } 367 368 switch (rep->header.cmd) { 369 case NGM_PPPOE_SET_FLAG: msg = "SET_FLAG"; break; 370 case NGM_PPPOE_CONNECT: msg = "CONNECT"; break; 371 case NGM_PPPOE_LISTEN: msg = "LISTEN"; break; 372 case NGM_PPPOE_OFFER: msg = "OFFER"; break; 373 case NGM_PPPOE_SUCCESS: msg = "SUCCESS"; break; 374 case NGM_PPPOE_FAIL: msg = "FAIL"; break; 375 case NGM_PPPOE_CLOSE: msg = "CLOSE"; break; 376 case NGM_PPPOE_GET_STATUS: msg = "GET_STATUS"; break; 377 default: 378 snprintf(unknown, sizeof unknown, "<%d>", (int)rep->header.cmd); 379 msg = unknown; 380 break; 381 } 382 383 switch (rep->header.cmd) { 384 case NGM_PPPOE_FAIL: 385 case NGM_PPPOE_CLOSE: 386 syslog(LOG_ERR, "Received NGM_PPPOE_%s (hook \"%s\")", 387 msg, sts->hook); 388 _exit(0); 389 } 390 391 syslog(LOG_INFO, "Received NGM_PPPOE_%s (hook \"%s\")", msg, sts->hook); 392 } while (rep->header.cmd != NGM_PPPOE_SUCCESS); 393 394 dup2(ds, STDIN_FILENO); 395 dup2(ds, STDOUT_FILENO); 396 close(ds); 397 close(cs); 398 399 setsid(); 400 syslog(LOG_INFO, "Executing: %s", exec); 401 execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", exec, NULL); 402 syslog(LOG_ERR, "execlp failed: %m"); 403 _exit(EX_OSFILE); 404 405 default: 406 wait(&ret); 407 errno = ret; 408 if (errno) 409 syslog(LOG_ERR, "Second fork failed: %m"); 410 break; 411 } 412} 413 414int 415main(int argc, char **argv) 416{ 417 char hostname[MAXHOSTNAMELEN]; 418 char response[1024], *exec, rhook[NG_HOOKLEN + 1]; 419 const char *prog, *provider, *acname; 420 struct ngm_connect ngc; 421 int ch, cs, ds, ret, optF, optd, sz, f; 422 423 prog = strrchr(argv[0], '/'); 424 prog = prog ? prog + 1 : argv[0]; 425 exec = NULL; 426 acname = NULL; 427 provider = ""; 428 optF = optd = 0; 429 430 while ((ch = getopt(argc, argv, "a:Fde:p:")) != -1) { 431 switch (ch) { 432 case 'F': 433 optF = 1; 434 break; 435 436 case 'a': 437 acname = optarg; 438 break; 439 440 case 'd': 441 optd = 1; 442 break; 443 444 case 'e': 445 exec = optarg; 446 break; 447 448 case 'p': 449 provider = optarg; 450 break; 451 452 default: 453 return usage(prog); 454 } 455 } 456 457 if (optind >= argc || optind + 2 < argc) 458 return usage(prog); 459 460 if (exec == NULL) { 461 if (provider == NULL) { 462 fprintf(stderr, "%s: Either a provider or an exec command" 463 " must be given\n", prog); 464 return usage(prog); 465 } 466 exec = (char *)alloca(sizeof DEFAULT_EXEC_PREFIX + strlen(provider)); 467 if (exec == NULL) { 468 fprintf(stderr, "%s: Cannot allocate %d bytes\n", prog, 469 (int)(sizeof DEFAULT_EXEC_PREFIX) + strlen(provider)); 470 return EX_OSERR; 471 } 472 strcpy(exec, DEFAULT_EXEC_PREFIX); 473 strcpy(exec + sizeof DEFAULT_EXEC_PREFIX - 1, provider); 474 } 475 476 if (acname == NULL) { 477 char *dot; 478 479 if (gethostname(hostname, sizeof hostname)) 480 strcpy(hostname, "localhost"); 481 else if ((dot = strchr(hostname, '.'))) 482 *dot = '\0'; 483 484 acname = hostname; 485 } 486 487#ifndef NOKLDLOAD 488 if (modfind("netgraph") == -1) { 489 fputs("Can't run without options NETGRAPH in the kernel\n", stderr); 490 return EX_UNAVAILABLE; 491 } 492 493 if (modfind("ng_socket") == -1 && kldload("ng_socket") == -1) { 494 perror("kldload: ng_socket"); 495 return EX_UNAVAILABLE; 496 } 497#endif 498 499 /* Create a socket node */ 500 if (NgMkSockNode(NULL, &cs, &ds) == -1) { 501 perror("Cannot create netgraph socket node"); 502 return EX_CANTCREAT; 503 } 504 505 /* Connect it up (and fill in `ngc') */ 506 if ((ret = ConfigureNode(prog, argv[optind], provider, cs, ds, 507 optd, &ngc)) != 0) { 508 close(cs); 509 close(ds); 510 return ret; 511 } 512 513 if (!optF && daemon(1, 0) == -1) { 514 perror("daemon()"); 515 return EX_OSERR; 516 } 517 518 openlog(prog, LOG_PID | (optF ? LOG_PERROR : 0), LOG_DAEMON); 519 520 signal(SIGHUP, Fairwell); 521 signal(SIGINT, Fairwell); 522 signal(SIGQUIT, Fairwell); 523 signal(SIGTERM, Fairwell); 524 525 while (1) { 526 if (*provider) 527 syslog(LOG_INFO, "Listening as provider %s", provider); 528 else 529 syslog(LOG_INFO, "Listening"); 530 531 switch (sz = NgRecvData(ds, response, sizeof response, rhook)) { 532 case -1: 533 syslog(LOG_INFO, "NgRecvData: %m"); 534 break; 535 case 0: 536 syslog(LOG_INFO, "NgRecvData: socket closed"); 537 break; 538 default: 539 if (optd) { 540 char *dbuf, *ptr; 541 542 ptr = dbuf = alloca(sz * 2 + 1); 543 for (f = 0; f < sz; f++, ptr += 2) 544 sprintf(ptr, "%02x", (u_char)response[f]); 545 *ptr = '\0'; 546 syslog(LOG_INFO, "Got %d bytes of data: %s", sz, dbuf); 547 } 548 } 549 if (sz <= 0) { 550 ret = EX_UNAVAILABLE; 551 break; 552 } 553 Spawn(prog, acname, exec, ngc, cs, ds, response, sz, optd); 554 } 555 556 return ret; 557} 558