Home | History | Annotate | Line # | Download | only in bootpgw
bootpgw.c revision 1.9
      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 
     89 
     90 /*
     91  * Externals, forward declarations, and global variables
     92  */
     93 
     94 static void usage(void);
     95 static void handle_reply(void);
     96 static void handle_request(void);
     97 int main(int, char **);
     98 
     99 /*
    100  * IP port numbers for client and server obtained from /etc/services
    101  */
    102 
    103 u_short bootps_port, bootpc_port;
    104 
    105 
    106 /*
    107  * Internet socket and interface config structures
    108  */
    109 
    110 struct sockaddr_in bind_addr;	/* Listening */
    111 struct sockaddr_in clnt_addr;	/* client address */
    112 struct sockaddr_in serv_addr;	/* server address */
    113 
    114 
    115 /*
    116  * option defaults
    117  */
    118 int debug = 0;					/* Debugging flag (level) */
    119 struct timeval actualtimeout =
    120 {								/* fifteen minutes */
    121 	15 * 60L,					/* tv_sec */
    122 	0							/* tv_usec */
    123 };
    124 u_int maxhops = 4;				/* Number of hops allowed for requests. */
    125 u_int minwait = 3;				/* Number of seconds client must wait before
    126 						   its bootrequest packets are forwarded. */
    127 
    128 /*
    129  * General
    130  */
    131 
    132 int s;							/* Socket file descriptor */
    133 char *pktbuf;					/* Receive packet buffer */
    134 int pktlen;
    135 char *progname;
    136 char *servername;
    137 
    138 char myhostname[MAXHOSTNAMELEN + 1];
    139 struct in_addr my_ip_addr;
    140 
    141 
    143 
    144 
    145 /*
    146  * Initialization such as command-line processing is done and then the
    147  * main server loop is started.
    148  */
    149 
    150 int
    151 main(int argc, char **argv)
    152 {
    153 	struct timeval *timeout;
    154 	struct bootp *bp;
    155 	struct servent *servp;
    156 	struct hostent *hep;
    157 	char *stmp;
    158 	int n, ba_len, ra_len;
    159 	int nfound, readfds;
    160 	int standalone;
    161 
    162 	progname = strrchr(argv[0], '/');
    163 	if (progname) progname++;
    164 	else progname = argv[0];
    165 
    166 	/*
    167 	 * Initialize logging.
    168 	 */
    169 	report_init(0);				/* uses progname */
    170 
    171 	/*
    172 	 * Log startup
    173 	 */
    174 	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
    175 
    176 	/* Debugging for compilers with struct padding. */
    177 	assert(sizeof(struct bootp) == BP_MINPKTSZ);
    178 
    179 	/* Get space for receiving packets and composing replies. */
    180 	pktbuf = malloc(MAX_MSG_SIZE);
    181 	if (!pktbuf) {
    182 		report(LOG_ERR, "malloc failed");
    183 		exit(1);
    184 	}
    185 	bp = (struct bootp *) pktbuf;
    186 
    187 	/*
    188 	 * Check to see if a socket was passed to us from inetd.
    189 	 *
    190 	 * Use getsockname() to determine if descriptor 0 is indeed a socket
    191 	 * (and thus we are probably a child of inetd) or if it is instead
    192 	 * something else and we are running standalone.
    193 	 */
    194 	s = 0;
    195 	ba_len = sizeof(bind_addr);
    196 	bzero((char *) &bind_addr, ba_len);
    197 	errno = 0;
    198 	standalone = TRUE;
    199 	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
    200 		/*
    201 		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
    202 		 */
    203 		if (bind_addr.sin_family == AF_INET) {
    204 			standalone = FALSE;
    205 			bootps_port = ntohs(bind_addr.sin_port);
    206 		} else {
    207 			/* Some other type of socket? */
    208 			report(LOG_INFO, "getsockname: not an INET socket");
    209 		}
    210 	}
    211 	/*
    212 	 * Set defaults that might be changed by option switches.
    213 	 */
    214 	stmp = NULL;
    215 	timeout = &actualtimeout;
    216 	gethostname(myhostname, sizeof(myhostname));
    217 	myhostname[sizeof(myhostname) - 1] = '\0';
    218 	hep = gethostbyname(myhostname);
    219 	if (!hep) {
    220 		printf("Can not get my IP address\n");
    221 		exit(1);
    222 	}
    223 	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
    224 
    225 	/*
    226 	 * Read switches.
    227 	 */
    228 	for (argc--, argv++; argc > 0; argc--, argv++) {
    229 		if (argv[0][0] != '-')
    230 			break;
    231 		switch (argv[0][1]) {
    232 
    233 		case 'd':				/* debug level */
    234 			if (argv[0][2]) {
    235 				stmp = &(argv[0][2]);
    236 			} else if (argv[1] && argv[1][0] == '-') {
    237 				/*
    238 				 * Backwards-compatible behavior:
    239 				 * no parameter, so just increment the debug flag.
    240 				 */
    241 				debug++;
    242 				break;
    243 			} else {
    244 				argc--;
    245 				argv++;
    246 				stmp = argv[0];
    247 			}
    248 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
    249 				fprintf(stderr,
    250 						"%s: invalid debug level\n", progname);
    251 				break;
    252 			}
    253 			debug = n;
    254 			break;
    255 
    256 		case 'h':				/* hop count limit */
    257 			if (argv[0][2]) {
    258 				stmp = &(argv[0][2]);
    259 			} else {
    260 				argc--;
    261 				argv++;
    262 				stmp = argv[0];
    263 			}
    264 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
    265 				(n < 0) || (n > 16))
    266 			{
    267 				fprintf(stderr,
    268 						"bootpgw: invalid hop count limit\n");
    269 				break;
    270 			}
    271 			maxhops = (u_int)n;
    272 			break;
    273 
    274 		case 'i':				/* inetd mode */
    275 			standalone = FALSE;
    276 			break;
    277 
    278 		case 's':				/* standalone mode */
    279 			standalone = TRUE;
    280 			break;
    281 
    282 		case 't':				/* timeout */
    283 			if (argv[0][2]) {
    284 				stmp = &(argv[0][2]);
    285 			} else {
    286 				argc--;
    287 				argv++;
    288 				stmp = argv[0];
    289 			}
    290 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
    291 				fprintf(stderr,
    292 						"%s: invalid timeout specification\n", progname);
    293 				break;
    294 			}
    295 			actualtimeout.tv_sec = (int32) (60 * n);
    296 			/*
    297 			 * If the actual timeout is zero, pass a NULL pointer
    298 			 * to select so it blocks indefinitely, otherwise,
    299 			 * point to the actual timeout value.
    300 			 */
    301 			timeout = (n > 0) ? &actualtimeout : NULL;
    302 			break;
    303 
    304 		case 'w':				/* wait time */
    305 			if (argv[0][2]) {
    306 				stmp = &(argv[0][2]);
    307 			} else {
    308 				argc--;
    309 				argv++;
    310 				stmp = argv[0];
    311 			}
    312 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
    313 				(n < 0) || (n > 60))
    314 			{
    315 				fprintf(stderr,
    316 						"bootpgw: invalid wait time\n");
    317 				break;
    318 			}
    319 			minwait = (u_int)n;
    320 			break;
    321 
    322 		default:
    323 			fprintf(stderr, "%s: unknown switch: -%c\n",
    324 					progname, argv[0][1]);
    325 			usage();
    326 			break;
    327 
    328 		} /* switch */
    329 	} /* for args */
    330 
    331 	/* Make sure server name argument is suplied. */
    332 	servername = argv[0];
    333 	if (!servername) {
    334 		fprintf(stderr, "bootpgw: missing server name\n");
    335 		usage();
    336 	}
    337 	/*
    338 	 * Get address of real bootp server.
    339 	 */
    340 	if (inet_aton(servername, &serv_addr.sin_addr) == 0) {
    341 		hep = gethostbyname(servername);
    342 		if (!hep) {
    343 			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
    344 			exit(1);
    345 		}
    346 		memcpy(&serv_addr.sin_addr, hep->h_addr,
    347 		    sizeof(serv_addr.sin_addr));
    348 	}
    349 
    350 	if (standalone) {
    351 		/*
    352 		 * Go into background and disassociate from controlling terminal.
    353 		 * XXX - This is not the POSIX way (Should use setsid). -gwr
    354 		 */
    355 		if (debug < 3) {
    356 			if (fork())
    357 				exit(0);
    358 #ifdef	NO_SETSID
    359 			setpgrp(0,0);
    360 #ifdef TIOCNOTTY
    361 			n = open("/dev/tty", O_RDWR);
    362 			if (n >= 0) {
    363 				ioctl(n, TIOCNOTTY, (char *) 0);
    364 				(void) close(n);
    365 			}
    366 #endif	/* TIOCNOTTY */
    367 #else	/* SETSID */
    368 			if (setsid() < 0)
    369 				perror("setsid");
    370 #endif	/* SETSID */
    371 		} /* if debug < 3 */
    372 		/*
    373 		 * Nuke any timeout value
    374 		 */
    375 		timeout = NULL;
    376 
    377 		/*
    378 		 * Here, bootpd would do:
    379 		 *	chdir
    380 		 *	tzone_init
    381 		 *	rdtab_init
    382 		 *	readtab
    383 		 */
    384 
    385 		/*
    386 		 * Create a socket.
    387 		 */
    388 		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    389 			report(LOG_ERR, "socket: %s", get_network_errmsg());
    390 			exit(1);
    391 		}
    392 		/*
    393 		 * Get server's listening port number
    394 		 */
    395 		servp = getservbyname("bootps", "udp");
    396 		if (servp) {
    397 			bootps_port = ntohs((u_short) servp->s_port);
    398 		} else {
    399 			bootps_port = (u_short) IPPORT_BOOTPS;
    400 			report(LOG_ERR,
    401 				   "udp/bootps: unknown service -- assuming port %d",
    402 				   bootps_port);
    403 		}
    404 
    405 		/*
    406 		 * Bind socket to BOOTPS port.
    407 		 */
    408 		bind_addr.sin_family = AF_INET;
    409 		bind_addr.sin_port = htons(bootps_port);
    410 		bind_addr.sin_addr.s_addr = INADDR_ANY;
    411 		if (bind(s, (struct sockaddr *) &bind_addr,
    412 				 sizeof(bind_addr)) < 0)
    413 		{
    414 			report(LOG_ERR, "bind: %s", get_network_errmsg());
    415 			exit(1);
    416 		}
    417 	} /* if standalone */
    418 	/*
    419 	 * Get destination port number so we can reply to client
    420 	 */
    421 	servp = getservbyname("bootpc", "udp");
    422 	if (servp) {
    423 		bootpc_port = ntohs(servp->s_port);
    424 	} else {
    425 		report(LOG_ERR,
    426 			   "udp/bootpc: unknown service -- assuming port %d",
    427 			   IPPORT_BOOTPC);
    428 		bootpc_port = (u_short) IPPORT_BOOTPC;
    429 	}
    430 
    431 	/* no signal catchers */
    432 
    433 	/*
    434 	 * Process incoming requests.
    435 	 */
    436 	for (;;) {
    437 		readfds = 1 << s;
    438 		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout);
    439 		if (nfound < 0) {
    440 			if (errno != EINTR) {
    441 				report(LOG_ERR, "select: %s", get_errmsg());
    442 			}
    443 			continue;
    444 		}
    445 		if (!(readfds & (1 << s))) {
    446 			report(LOG_INFO, "exiting after %ld minutes of inactivity",
    447 				   actualtimeout.tv_sec / 60);
    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