Home | History | Annotate | Line # | Download | only in bootpgw
bootpgw.c revision 1.11
      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.11 2002/09/18 23:16:14 mycroft 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 	int n, ba_len, ra_len;
    156 	int nfound;
    157 	struct pollfd set[1];
    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 = n * 60000;
    294 			/*
    295 			 * If the actual timeout is zero, pass INFTIM
    296 			 * to poll so it blocks indefinitely, otherwise,
    297 			 * use the actual timeout value.
    298 			 */
    299 			timeout = (n > 0) ? actualtimeout : INFTIM;
    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 = INFTIM;
    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 	set[0].fd = s;
    435 	set[0].events = POLLIN;
    436 	for (;;) {
    437 		nfound = poll(set, 1, timeout);
    438 		if (nfound < 0) {
    439 			if (errno != EINTR) {
    440 				report(LOG_ERR, "poll: %s", get_errmsg());
    441 			}
    442 			continue;
    443 		}
    444 		if (nfound == 0) {
    445 			report(LOG_INFO, "exiting after %d minute%s of inactivity",
    446 				   actualtimeout / 60000,
    447 				   actualtimeout == 60000 ? "" : "s");
    448 			exit(0);
    449 		}
    450 		ra_len = sizeof(clnt_addr);
    451 		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
    452 					 (struct sockaddr *) &clnt_addr, &ra_len);
    453 		if (n <= 0) {
    454 			continue;
    455 		}
    456 		if (debug > 3) {
    457 			report(LOG_INFO, "recvd pkt from IP addr %s",
    458 				   inet_ntoa(clnt_addr.sin_addr));
    459 		}
    460 		if (n < sizeof(struct bootp)) {
    461 			if (debug) {
    462 				report(LOG_INFO, "received short packet");
    463 			}
    464 			continue;
    465 		}
    466 		pktlen = n;
    467 
    468 		switch (bp->bp_op) {
    469 		case BOOTREQUEST:
    470 			handle_request();
    471 			break;
    472 		case BOOTREPLY:
    473 			handle_reply();
    474 			break;
    475 		}
    476 	}
    477 }
    478 
    479 
    481 
    482 
    483 /*
    484  * Print "usage" message and exit
    485  */
    486 
    487 static void
    488 usage(void)
    489 {
    490 	fprintf(stderr,
    491 			"usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
    492 	fprintf(stderr, "\t -d n\tset debug level\n");
    493 	fprintf(stderr, "\t -h n\tset max hop count\n");
    494 	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
    495 	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
    496 	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
    497 	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
    498 	exit(1);
    499 }
    500 
    501 
    503 
    504 /*
    505  * Process BOOTREQUEST packet.
    506  *
    507  * Note, this just forwards the request to a real server.
    508  */
    509 static void
    510 handle_request(void)
    511 {
    512 	struct bootp *bp = (struct bootp *) pktbuf;
    513 #if 0
    514 	struct ifreq *ifr;
    515 #endif
    516 	u_short secs, hops;
    517 
    518 	/* XXX - SLIP init: Set bp_ciaddr = clnt_addr here? */
    519 
    520 	if (debug) {
    521 		report(LOG_INFO, "request from %s",
    522 			   inet_ntoa(clnt_addr.sin_addr));
    523 	}
    524 	/* Has the client been waiting long enough? */
    525 	secs = ntohs(bp->bp_secs);
    526 	if (secs < minwait)
    527 		return;
    528 
    529 	/* Has this packet hopped too many times? */
    530 	hops = ntohs(bp->bp_hops);
    531 	if (++hops > maxhops) {
    532 		report(LOG_NOTICE, "request from %s reached hop limit",
    533 			   inet_ntoa(clnt_addr.sin_addr));
    534 		return;
    535 	}
    536 	bp->bp_hops = htons(hops);
    537 
    538 	/*
    539 	 * Here one might discard a request from the same subnet as the
    540 	 * real server, but we can assume that the real server will send
    541 	 * a reply to the client before it waits for minwait seconds.
    542 	 */
    543 
    544 	/* If gateway address is not set, put in local interface addr. */
    545 	if (bp->bp_giaddr.s_addr == 0) {
    546 #if 0	/* BUG */
    547 		struct sockaddr_in *sip;
    548 		/*
    549 		 * XXX - This picks the wrong interface when the receive addr
    550 		 * is the broadcast address.  There is no  portable way to
    551 		 * find out which interface a broadcast was received on. -gwr
    552 		 * (Thanks to <walker (at) zk3.dec.com> for finding this bug!)
    553 		 */
    554 		ifr = getif(s, &clnt_addr.sin_addr);
    555 		if (!ifr) {
    556 			report(LOG_NOTICE, "no interface for request from %s",
    557 				   inet_ntoa(clnt_addr.sin_addr));
    558 			return;
    559 		}
    560 		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
    561 		bp->bp_giaddr = sip->sin_addr;
    562 #else	/* BUG */
    563 		/*
    564 		 * XXX - Just set "giaddr" to our "official" IP address.
    565 		 * RFC 1532 says giaddr MUST be set to the address of the
    566 		 * interface on which the request was received.  Setting
    567 		 * it to our "default" IP address is not strictly correct,
    568 		 * but is good enough to allow the real BOOTP server to
    569 		 * get the reply back here.  Then, before we forward the
    570 		 * reply to the client, the giaddr field is corrected.
    571 		 * (In case the client uses giaddr, which it should not.)
    572 		 * See handle_reply()
    573 		 */
    574 		bp->bp_giaddr = my_ip_addr;
    575 #endif	/* BUG */
    576 
    577 		/*
    578 		 * XXX - DHCP says to insert a subnet mask option into the
    579 		 * options area of the request (if vendor magic == std).
    580 		 */
    581 	}
    582 	/* Set up socket address for send. */
    583 	serv_addr.sin_family = AF_INET;
    584 	serv_addr.sin_port = htons(bootps_port);
    585 
    586 	/* Send reply with same size packet as request used. */
    587 	if (sendto(s, pktbuf, pktlen, 0,
    588 			   (struct sockaddr *) &serv_addr,
    589 			   sizeof(serv_addr)) < 0)
    590 	{
    591 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
    592 	}
    593 }
    594 
    595 
    597 
    598 /*
    599  * Process BOOTREPLY packet.
    600  */
    601 static void
    602 handle_reply(void)
    603 {
    604 	struct bootp *bp = (struct bootp *) pktbuf;
    605 	struct ifreq *ifr;
    606 	struct sockaddr_in *sip;
    607 	u_char canon_haddr[MAXHADDRLEN];
    608 	unsigned char *ha;
    609 	int len;
    610 
    611 	if (debug) {
    612 		report(LOG_INFO, "   reply for %s",
    613 			   inet_ntoa(bp->bp_yiaddr));
    614 	}
    615 	/* Make sure client is directly accessible. */
    616 	ifr = getif(s, &(bp->bp_yiaddr));
    617 	if (!ifr) {
    618 		report(LOG_NOTICE, "no interface for reply to %s",
    619 			   inet_ntoa(bp->bp_yiaddr));
    620 		return;
    621 	}
    622 #if 1	/* Experimental (see BUG above) */
    623 /* #ifdef CATER_TO_OLD_CLIENTS ? */
    624 	/*
    625 	 * The giaddr field has been set to our "default" IP address
    626 	 * which might not be on the same interface as the client.
    627 	 * In case the client looks at giaddr, (which it should not)
    628 	 * giaddr is now set to the address of the correct interface.
    629 	 */
    630 	sip = (struct sockaddr_in *) &(ifr->ifr_addr);
    631 	bp->bp_giaddr = sip->sin_addr;
    632 #endif
    633 
    634 	/* Set up socket address for send to client. */
    635 	clnt_addr.sin_family = AF_INET;
    636 	clnt_addr.sin_addr = bp->bp_yiaddr;
    637 	clnt_addr.sin_port = htons(bootpc_port);
    638 
    639 	/* Create an ARP cache entry for the client. */
    640 	ha = bp->bp_chaddr;
    641 	len = bp->bp_hlen;
    642 	if (len > MAXHADDRLEN)
    643 		len = MAXHADDRLEN;
    644 	if (bp->bp_htype == HTYPE_IEEE802) {
    645 		haddr_conv802(ha, canon_haddr, len);
    646 		ha = canon_haddr;
    647 	}
    648 	if (debug > 1)
    649 		report(LOG_INFO, "setarp %s - %s",
    650 			   inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
    651 	setarp(s, &bp->bp_yiaddr, ha, len);
    652 
    653 	/* Send reply with same size packet as request used. */
    654 	if (sendto(s, pktbuf, pktlen, 0,
    655 			   (struct sockaddr *) &clnt_addr,
    656 			   sizeof(clnt_addr)) < 0)
    657 	{
    658 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
    659 	}
    660 }
    661 
    662 /*
    663  * Local Variables:
    664  * tab-width: 4
    665  * c-indent-level: 4
    666  * c-argdecl-indent: 4
    667  * c-continued-statement-offset: 4
    668  * c-continued-brace-offset: -4
    669  * c-label-offset: -4
    670  * c-brace-offset: 0
    671  * End:
    672  */
    673