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