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