1 /* 2 * bootpgw.c - BOOTP GateWay 3 * This program forwards BOOTP Request packets to a BOOTP server. 4 */ 5 6 /************************************************************************ 7 Copyright 1988, 1991 by Carnegie Mellon University 8 9 All Rights Reserved 10 11 Permission to use, copy, modify, and distribute this software and its 12 documentation for any purpose and without fee is hereby granted, provided 13 that the above copyright notice appear in all copies and that both that 14 copyright notice and this permission notice appear in supporting 15 documentation, and that the name of Carnegie Mellon University not be used 16 in advertising or publicity pertaining to distribution of the software 17 without specific, written prior permission. 18 19 CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 20 SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 21 IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL 22 DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 23 PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 24 ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 25 SOFTWARE. 26 ************************************************************************/ 27 28 #include <sys/cdefs.h> 29 #ifndef lint 30 __RCSID("$NetBSD: bootpgw.c,v 1.9 2002/07/13 23:59:45 wiz Exp $"); 31 #endif 32 33 /* 34 * BOOTPGW is typically used to forward BOOTP client requests from 35 * one subnet to a BOOTP server on a different subnet. 36 */ 37 38 #include <sys/types.h> 39 #include <sys/param.h> 40 #include <sys/socket.h> 41 #include <sys/ioctl.h> 42 #include <sys/file.h> 43 #include <sys/time.h> 44 #include <sys/stat.h> 45 46 #include <net/if.h> 47 #include <netinet/in.h> 48 #include <arpa/inet.h> /* inet_ntoa */ 49 50 #ifndef NO_UNISTD 51 #include <unistd.h> 52 #endif 53 #include <stdlib.h> 54 #include <signal.h> 55 #include <stdio.h> 56 #include <string.h> 57 #include <errno.h> 58 #include <ctype.h> 59 #include <netdb.h> 60 #include <syslog.h> 61 #include <assert.h> 62 63 #ifdef NO_SETSID 64 # include <fcntl.h> /* for O_RDONLY, etc */ 65 #endif 66 67 #ifndef USE_BFUNCS 68 # include <memory.h> 69 /* Yes, memcpy is OK here (no overlapped copies). */ 70 # define bcopy(a,b,c) memcpy(b,a,c) 71 # define bzero(p,l) memset(p,0,l) 72 # define bcmp(a,b,c) memcmp(a,b,c) 73 #endif 74 75 #include "bootp.h" 76 #include "getif.h" 77 #include "hwaddr.h" 78 #include "report.h" 79 #include "patchlevel.h" 80 81 /* Local definitions: */ 82 #define MAX_MSG_SIZE (3*512) /* Maximum packet size */ 83 #define TRUE 1 84 #define FALSE 0 85 #define get_network_errmsg get_errmsg 86 87 88 89 /* 90 * Externals, forward declarations, and global variables 91 */ 92 93 static void usage(void); 94 static void handle_reply(void); 95 static void handle_request(void); 96 int main(int, char **); 97 98 /* 99 * IP port numbers for client and server obtained from /etc/services 100 */ 101 102 u_short bootps_port, bootpc_port; 103 104 105 /* 106 * Internet socket and interface config structures 107 */ 108 109 struct sockaddr_in bind_addr; /* Listening */ 110 struct sockaddr_in clnt_addr; /* client address */ 111 struct sockaddr_in serv_addr; /* server address */ 112 113 114 /* 115 * option defaults 116 */ 117 int debug = 0; /* Debugging flag (level) */ 118 struct timeval actualtimeout = 119 { /* fifteen minutes */ 120 15 * 60L, /* tv_sec */ 121 0 /* tv_usec */ 122 }; 123 u_int maxhops = 4; /* Number of hops allowed for requests. */ 124 u_int minwait = 3; /* Number of seconds client must wait before 125 its bootrequest packets are forwarded. */ 126 127 /* 128 * General 129 */ 130 131 int s; /* Socket file descriptor */ 132 char *pktbuf; /* Receive packet buffer */ 133 int pktlen; 134 char *progname; 135 char *servername; 136 137 char myhostname[MAXHOSTNAMELEN + 1]; 138 struct in_addr my_ip_addr; 139 140 141 142 143 /* 144 * Initialization such as command-line processing is done and then the 145 * main server loop is started. 146 */ 147 148 int 149 main(int argc, char **argv) 150 { 151 struct timeval *timeout; 152 struct bootp *bp; 153 struct servent *servp; 154 struct hostent *hep; 155 char *stmp; 156 int n, ba_len, ra_len; 157 int nfound, readfds; 158 int standalone; 159 160 progname = strrchr(argv[0], '/'); 161 if (progname) progname++; 162 else progname = argv[0]; 163 164 /* 165 * Initialize logging. 166 */ 167 report_init(0); /* uses progname */ 168 169 /* 170 * Log startup 171 */ 172 report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL); 173 174 /* Debugging for compilers with struct padding. */ 175 assert(sizeof(struct bootp) == BP_MINPKTSZ); 176 177 /* Get space for receiving packets and composing replies. */ 178 pktbuf = malloc(MAX_MSG_SIZE); 179 if (!pktbuf) { 180 report(LOG_ERR, "malloc failed"); 181 exit(1); 182 } 183 bp = (struct bootp *) pktbuf; 184 185 /* 186 * Check to see if a socket was passed to us from inetd. 187 * 188 * Use getsockname() to determine if descriptor 0 is indeed a socket 189 * (and thus we are probably a child of inetd) or if it is instead 190 * something else and we are running standalone. 191 */ 192 s = 0; 193 ba_len = sizeof(bind_addr); 194 bzero((char *) &bind_addr, ba_len); 195 errno = 0; 196 standalone = TRUE; 197 if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) { 198 /* 199 * Descriptor 0 is a socket. Assume we are a child of inetd. 200 */ 201 if (bind_addr.sin_family == AF_INET) { 202 standalone = FALSE; 203 bootps_port = ntohs(bind_addr.sin_port); 204 } else { 205 /* Some other type of socket? */ 206 report(LOG_INFO, "getsockname: not an INET socket"); 207 } 208 } 209 /* 210 * Set defaults that might be changed by option switches. 211 */ 212 stmp = NULL; 213 timeout = &actualtimeout; 214 gethostname(myhostname, sizeof(myhostname)); 215 myhostname[sizeof(myhostname) - 1] = '\0'; 216 hep = gethostbyname(myhostname); 217 if (!hep) { 218 printf("Can not get my IP address\n"); 219 exit(1); 220 } 221 bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr)); 222 223 /* 224 * Read switches. 225 */ 226 for (argc--, argv++; argc > 0; argc--, argv++) { 227 if (argv[0][0] != '-') 228 break; 229 switch (argv[0][1]) { 230 231 case 'd': /* debug level */ 232 if (argv[0][2]) { 233 stmp = &(argv[0][2]); 234 } else if (argv[1] && argv[1][0] == '-') { 235 /* 236 * Backwards-compatible behavior: 237 * no parameter, so just increment the debug flag. 238 */ 239 debug++; 240 break; 241 } else { 242 argc--; 243 argv++; 244 stmp = argv[0]; 245 } 246 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { 247 fprintf(stderr, 248 "%s: invalid debug level\n", progname); 249 break; 250 } 251 debug = n; 252 break; 253 254 case 'h': /* hop count limit */ 255 if (argv[0][2]) { 256 stmp = &(argv[0][2]); 257 } else { 258 argc--; 259 argv++; 260 stmp = argv[0]; 261 } 262 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || 263 (n < 0) || (n > 16)) 264 { 265 fprintf(stderr, 266 "bootpgw: invalid hop count limit\n"); 267 break; 268 } 269 maxhops = (u_int)n; 270 break; 271 272 case 'i': /* inetd mode */ 273 standalone = FALSE; 274 break; 275 276 case 's': /* standalone mode */ 277 standalone = TRUE; 278 break; 279 280 case 't': /* timeout */ 281 if (argv[0][2]) { 282 stmp = &(argv[0][2]); 283 } else { 284 argc--; 285 argv++; 286 stmp = argv[0]; 287 } 288 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { 289 fprintf(stderr, 290 "%s: invalid timeout specification\n", progname); 291 break; 292 } 293 actualtimeout.tv_sec = (int32) (60 * n); 294 /* 295 * If the actual timeout is zero, pass a NULL pointer 296 * to select so it blocks indefinitely, otherwise, 297 * point to the actual timeout value. 298 */ 299 timeout = (n > 0) ? &actualtimeout : NULL; 300 break; 301 302 case 'w': /* wait time */ 303 if (argv[0][2]) { 304 stmp = &(argv[0][2]); 305 } else { 306 argc--; 307 argv++; 308 stmp = argv[0]; 309 } 310 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || 311 (n < 0) || (n > 60)) 312 { 313 fprintf(stderr, 314 "bootpgw: invalid wait time\n"); 315 break; 316 } 317 minwait = (u_int)n; 318 break; 319 320 default: 321 fprintf(stderr, "%s: unknown switch: -%c\n", 322 progname, argv[0][1]); 323 usage(); 324 break; 325 326 } /* switch */ 327 } /* for args */ 328 329 /* Make sure server name argument is suplied. */ 330 servername = argv[0]; 331 if (!servername) { 332 fprintf(stderr, "bootpgw: missing server name\n"); 333 usage(); 334 } 335 /* 336 * Get address of real bootp server. 337 */ 338 if (inet_aton(servername, &serv_addr.sin_addr) == 0) { 339 hep = gethostbyname(servername); 340 if (!hep) { 341 fprintf(stderr, "bootpgw: can't get addr for %s\n", servername); 342 exit(1); 343 } 344 memcpy(&serv_addr.sin_addr, hep->h_addr, 345 sizeof(serv_addr.sin_addr)); 346 } 347 348 if (standalone) { 349 /* 350 * Go into background and disassociate from controlling terminal. 351 * XXX - This is not the POSIX way (Should use setsid). -gwr 352 */ 353 if (debug < 3) { 354 if (fork()) 355 exit(0); 356 #ifdef NO_SETSID 357 setpgrp(0,0); 358 #ifdef TIOCNOTTY 359 n = open("/dev/tty", O_RDWR); 360 if (n >= 0) { 361 ioctl(n, TIOCNOTTY, (char *) 0); 362 (void) close(n); 363 } 364 #endif /* TIOCNOTTY */ 365 #else /* SETSID */ 366 if (setsid() < 0) 367 perror("setsid"); 368 #endif /* SETSID */ 369 } /* if debug < 3 */ 370 /* 371 * Nuke any timeout value 372 */ 373 timeout = NULL; 374 375 /* 376 * Here, bootpd would do: 377 * chdir 378 * tzone_init 379 * rdtab_init 380 * readtab 381 */ 382 383 /* 384 * Create a socket. 385 */ 386 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 387 report(LOG_ERR, "socket: %s", get_network_errmsg()); 388 exit(1); 389 } 390 /* 391 * Get server's listening port number 392 */ 393 servp = getservbyname("bootps", "udp"); 394 if (servp) { 395 bootps_port = ntohs((u_short) servp->s_port); 396 } else { 397 bootps_port = (u_short) IPPORT_BOOTPS; 398 report(LOG_ERR, 399 "udp/bootps: unknown service -- assuming port %d", 400 bootps_port); 401 } 402 403 /* 404 * Bind socket to BOOTPS port. 405 */ 406 bind_addr.sin_family = AF_INET; 407 bind_addr.sin_port = htons(bootps_port); 408 bind_addr.sin_addr.s_addr = INADDR_ANY; 409 if (bind(s, (struct sockaddr *) &bind_addr, 410 sizeof(bind_addr)) < 0) 411 { 412 report(LOG_ERR, "bind: %s", get_network_errmsg()); 413 exit(1); 414 } 415 } /* if standalone */ 416 /* 417 * Get destination port number so we can reply to client 418 */ 419 servp = getservbyname("bootpc", "udp"); 420 if (servp) { 421 bootpc_port = ntohs(servp->s_port); 422 } else { 423 report(LOG_ERR, 424 "udp/bootpc: unknown service -- assuming port %d", 425 IPPORT_BOOTPC); 426 bootpc_port = (u_short) IPPORT_BOOTPC; 427 } 428 429 /* no signal catchers */ 430 431 /* 432 * Process incoming requests. 433 */ 434 for (;;) { 435 readfds = 1 << s; 436 nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout); 437 if (nfound < 0) { 438 if (errno != EINTR) { 439 report(LOG_ERR, "select: %s", get_errmsg()); 440 } 441 continue; 442 } 443 if (!(readfds & (1 << s))) { 444 report(LOG_INFO, "exiting after %ld minutes of inactivity", 445 actualtimeout.tv_sec / 60); 446 exit(0); 447 } 448 ra_len = sizeof(clnt_addr); 449 n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0, 450 (struct sockaddr *) &clnt_addr, &ra_len); 451 if (n <= 0) { 452 continue; 453 } 454 if (debug > 3) { 455 report(LOG_INFO, "recvd pkt from IP addr %s", 456 inet_ntoa(clnt_addr.sin_addr)); 457 } 458 if (n < sizeof(struct bootp)) { 459 if (debug) { 460 report(LOG_INFO, "received short packet"); 461 } 462 continue; 463 } 464 pktlen = n; 465 466 switch (bp->bp_op) { 467 case BOOTREQUEST: 468 handle_request(); 469 break; 470 case BOOTREPLY: 471 handle_reply(); 472 break; 473 } 474 } 475 } 476 477 478 479 480 /* 481 * Print "usage" message and exit 482 */ 483 484 static void 485 usage(void) 486 { 487 fprintf(stderr, 488 "usage: bootpgw [-d level] [-i] [-s] [-t timeout] server\n"); 489 fprintf(stderr, "\t -d n\tset debug level\n"); 490 fprintf(stderr, "\t -h n\tset max hop count\n"); 491 fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n"); 492 fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n"); 493 fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n"); 494 fprintf(stderr, "\t -w n\tset min wait time (secs)\n"); 495 exit(1); 496 } 497 498 499 500 /* 501 * Process BOOTREQUEST packet. 502 * 503 * Note, this just forwards the request to a real server. 504 */ 505 static void 506 handle_request(void) 507 { 508 struct bootp *bp = (struct bootp *) pktbuf; 509 #if 0 510 struct ifreq *ifr; 511 #endif 512 u_short secs, hops; 513 514 /* XXX - SLIP init: Set bp_ciaddr = clnt_addr here? */ 515 516 if (debug) { 517 report(LOG_INFO, "request from %s", 518 inet_ntoa(clnt_addr.sin_addr)); 519 } 520 /* Has the client been waiting long enough? */ 521 secs = ntohs(bp->bp_secs); 522 if (secs < minwait) 523 return; 524 525 /* Has this packet hopped too many times? */ 526 hops = ntohs(bp->bp_hops); 527 if (++hops > maxhops) { 528 report(LOG_NOTICE, "request from %s reached hop limit", 529 inet_ntoa(clnt_addr.sin_addr)); 530 return; 531 } 532 bp->bp_hops = htons(hops); 533 534 /* 535 * Here one might discard a request from the same subnet as the 536 * real server, but we can assume that the real server will send 537 * a reply to the client before it waits for minwait seconds. 538 */ 539 540 /* If gateway address is not set, put in local interface addr. */ 541 if (bp->bp_giaddr.s_addr == 0) { 542 #if 0 /* BUG */ 543 struct sockaddr_in *sip; 544 /* 545 * XXX - This picks the wrong interface when the receive addr 546 * is the broadcast address. There is no portable way to 547 * find out which interface a broadcast was received on. -gwr 548 * (Thanks to <walker@zk3.dec.com> for finding this bug!) 549 */ 550 ifr = getif(s, &clnt_addr.sin_addr); 551 if (!ifr) { 552 report(LOG_NOTICE, "no interface for request from %s", 553 inet_ntoa(clnt_addr.sin_addr)); 554 return; 555 } 556 sip = (struct sockaddr_in *) &(ifr->ifr_addr); 557 bp->bp_giaddr = sip->sin_addr; 558 #else /* BUG */ 559 /* 560 * XXX - Just set "giaddr" to our "official" IP address. 561 * RFC 1532 says giaddr MUST be set to the address of the 562 * interface on which the request was received. Setting 563 * it to our "default" IP address is not strictly correct, 564 * but is good enough to allow the real BOOTP server to 565 * get the reply back here. Then, before we forward the 566 * reply to the client, the giaddr field is corrected. 567 * (In case the client uses giaddr, which it should not.) 568 * See handle_reply() 569 */ 570 bp->bp_giaddr = my_ip_addr; 571 #endif /* BUG */ 572 573 /* 574 * XXX - DHCP says to insert a subnet mask option into the 575 * options area of the request (if vendor magic == std). 576 */ 577 } 578 /* Set up socket address for send. */ 579 serv_addr.sin_family = AF_INET; 580 serv_addr.sin_port = htons(bootps_port); 581 582 /* Send reply with same size packet as request used. */ 583 if (sendto(s, pktbuf, pktlen, 0, 584 (struct sockaddr *) &serv_addr, 585 sizeof(serv_addr)) < 0) 586 { 587 report(LOG_ERR, "sendto: %s", get_network_errmsg()); 588 } 589 } 590 591 592 593 /* 594 * Process BOOTREPLY packet. 595 */ 596 static void 597 handle_reply(void) 598 { 599 struct bootp *bp = (struct bootp *) pktbuf; 600 struct ifreq *ifr; 601 struct sockaddr_in *sip; 602 u_char canon_haddr[MAXHADDRLEN]; 603 unsigned char *ha; 604 int len; 605 606 if (debug) { 607 report(LOG_INFO, " reply for %s", 608 inet_ntoa(bp->bp_yiaddr)); 609 } 610 /* Make sure client is directly accessible. */ 611 ifr = getif(s, &(bp->bp_yiaddr)); 612 if (!ifr) { 613 report(LOG_NOTICE, "no interface for reply to %s", 614 inet_ntoa(bp->bp_yiaddr)); 615 return; 616 } 617 #if 1 /* Experimental (see BUG above) */ 618 /* #ifdef CATER_TO_OLD_CLIENTS ? */ 619 /* 620 * The giaddr field has been set to our "default" IP address 621 * which might not be on the same interface as the client. 622 * In case the client looks at giaddr, (which it should not) 623 * giaddr is now set to the address of the correct interface. 624 */ 625 sip = (struct sockaddr_in *) &(ifr->ifr_addr); 626 bp->bp_giaddr = sip->sin_addr; 627 #endif 628 629 /* Set up socket address for send to client. */ 630 clnt_addr.sin_family = AF_INET; 631 clnt_addr.sin_addr = bp->bp_yiaddr; 632 clnt_addr.sin_port = htons(bootpc_port); 633 634 /* Create an ARP cache entry for the client. */ 635 ha = bp->bp_chaddr; 636 len = bp->bp_hlen; 637 if (len > MAXHADDRLEN) 638 len = MAXHADDRLEN; 639 if (bp->bp_htype == HTYPE_IEEE802) { 640 haddr_conv802(ha, canon_haddr, len); 641 ha = canon_haddr; 642 } 643 if (debug > 1) 644 report(LOG_INFO, "setarp %s - %s", 645 inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len)); 646 setarp(s, &bp->bp_yiaddr, ha, len); 647 648 /* Send reply with same size packet as request used. */ 649 if (sendto(s, pktbuf, pktlen, 0, 650 (struct sockaddr *) &clnt_addr, 651 sizeof(clnt_addr)) < 0) 652 { 653 report(LOG_ERR, "sendto: %s", get_network_errmsg()); 654 } 655 } 656 657 /* 658 * Local Variables: 659 * tab-width: 4 660 * c-indent-level: 4 661 * c-argdecl-indent: 4 662 * c-continued-statement-offset: 4 663 * c-continued-brace-offset: -4 664 * c-label-offset: -4 665 * c-brace-offset: 0 666 * End: 667 */ 668