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