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