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