xref: /freebsd/libexec/bootpd/bootpgw/bootpgw.c (revision edf8578117e8844e02c0121147f45e4609b30680)
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 /*
29  * BOOTPGW is typically used to forward BOOTP client requests from
30  * one subnet to a BOOTP server on a different subnet.
31  */
32 
33 #include <sys/cdefs.h>
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/socket.h>
37 #include <sys/ioctl.h>
38 #include <sys/file.h>
39 #include <sys/time.h>
40 #include <sys/stat.h>
41 #include <sys/utsname.h>
42 
43 #include <net/if.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>	/* inet_ntoa */
46 
47 #ifndef	NO_UNISTD
48 #include <unistd.h>
49 #endif
50 
51 #include <err.h>
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 <paths.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 #include "bootp.h"
68 #include "getif.h"
69 #include "hwaddr.h"
70 #include "report.h"
71 #include "patchlevel.h"
72 
73 /* Local definitions: */
74 #define MAX_MSG_SIZE			(3*512)	/* Maximum packet size */
75 #define TRUE 1
76 #define FALSE 0
77 #define get_network_errmsg get_errmsg
78 
79 
80 
81 /*
82  * Externals, forward declarations, and global variables
83  */
84 
85 static void usage(void) __dead2;
86 static void handle_reply(void);
87 static void handle_request(void);
88 
89 /*
90  * IP port numbers for client and server obtained from /etc/services
91  */
92 
93 u_short bootps_port, bootpc_port;
94 
95 
96 /*
97  * Internet socket and interface config structures
98  */
99 
100 struct sockaddr_in bind_addr;	/* Listening */
101 struct sockaddr_in recv_addr;	/* Packet source */
102 struct sockaddr_in send_addr;	/*  destination */
103 
104 
105 /*
106  * option defaults
107  */
108 int debug = 0;					/* Debugging flag (level) */
109 struct timeval actualtimeout =
110 {								/* fifteen minutes */
111 	15 * 60L,					/* tv_sec */
112 	0							/* tv_usec */
113 };
114 u_char maxhops = 4;				/* Number of hops allowed for requests. */
115 u_int minwait = 3;				/* Number of seconds client must wait before
116 						   its bootrequest packets are forwarded. */
117 int arpmod = TRUE;				/* modify the ARP table */
118 
119 /*
120  * General
121  */
122 
123 int s;							/* Socket file descriptor */
124 char *pktbuf;					/* Receive packet buffer */
125 int pktlen;
126 char *progname;
127 char *servername;
128 int32 server_ipa;				/* Real server IP address, network order. */
129 
130 struct in_addr my_ip_addr;
131 
132 struct utsname my_uname;
133 char *hostname;
134 
135 
136 
137 
138 
139 /*
140  * Initialization such as command-line processing is done and then the
141  * main server loop is started.
142  */
143 
144 int
145 main(int argc, char **argv)
146 {
147 	struct timeval *timeout;
148 	struct bootp *bp;
149 	struct servent *servp;
150 	struct hostent *hep;
151 	char *stmp;
152 	int n, ba_len, ra_len;
153 	int nfound, readfds;
154 	int standalone;
155 
156 	progname = strrchr(argv[0], '/');
157 	if (progname) progname++;
158 	else progname = argv[0];
159 
160 	/*
161 	 * Initialize logging.
162 	 */
163 	report_init(0);				/* uses progname */
164 
165 	/*
166 	 * Log startup
167 	 */
168 	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
169 
170 	/* Debugging for compilers with struct padding. */
171 	assert(sizeof(struct bootp) == BP_MINPKTSZ);
172 
173 	/* Get space for receiving packets and composing replies. */
174 	pktbuf = malloc(MAX_MSG_SIZE);
175 	if (!pktbuf) {
176 		report(LOG_ERR, "malloc failed");
177 		exit(1);
178 	}
179 	bp = (struct bootp *) pktbuf;
180 
181 	/*
182 	 * Check to see if a socket was passed to us from inetd.
183 	 *
184 	 * Use getsockname() to determine if descriptor 0 is indeed a socket
185 	 * (and thus we are probably a child of inetd) or if it is instead
186 	 * something else and we are running standalone.
187 	 */
188 	s = 0;
189 	ba_len = sizeof(bind_addr);
190 	bzero((char *) &bind_addr, ba_len);
191 	errno = 0;
192 	standalone = TRUE;
193 	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
194 		/*
195 		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
196 		 */
197 		if (bind_addr.sin_family == AF_INET) {
198 			standalone = FALSE;
199 			bootps_port = ntohs(bind_addr.sin_port);
200 		} else {
201 			/* Some other type of socket? */
202 			report(LOG_INFO, "getsockname: not an INET socket");
203 		}
204 	}
205 	/*
206 	 * Set defaults that might be changed by option switches.
207 	 */
208 	stmp = NULL;
209 	timeout = &actualtimeout;
210 
211 	if (uname(&my_uname) < 0)
212 		errx(1, "can't get hostname");
213 	hostname = my_uname.nodename;
214 
215 	hep = gethostbyname(hostname);
216 	if (!hep) {
217 		printf("Can not get my IP address\n");
218 		exit(1);
219 	}
220 	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
221 
222 	/*
223 	 * Read switches.
224 	 */
225 	for (argc--, argv++; argc > 0; argc--, argv++) {
226 		if (argv[0][0] != '-')
227 			break;
228 		switch (argv[0][1]) {
229 
230 		case 'a':				/* don't modify the ARP table */
231 			arpmod = FALSE;
232 			break;
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 				warnx("invalid debug level");
250 				break;
251 			}
252 			debug = n;
253 			break;
254 
255 		case 'h':				/* hop count limit */
256 			if (argv[0][2]) {
257 				stmp = &(argv[0][2]);
258 			} else {
259 				argc--;
260 				argv++;
261 				stmp = argv[0];
262 			}
263 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
264 				(n < 0) || (n > 16))
265 			{
266 				warnx("invalid hop count limit");
267 				break;
268 			}
269 			maxhops = (u_char)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 				warnx("invalid timeout specification");
290 				break;
291 			}
292 			actualtimeout.tv_sec = (int32) (60 * n);
293 			/*
294 			 * If the actual timeout is zero, pass a NULL pointer
295 			 * to select so it blocks indefinitely, otherwise,
296 			 * point to the actual timeout value.
297 			 */
298 			timeout = (n > 0) ? &actualtimeout : NULL;
299 			break;
300 
301 		case 'w':				/* wait time */
302 			if (argv[0][2]) {
303 				stmp = &(argv[0][2]);
304 			} else {
305 				argc--;
306 				argv++;
307 				stmp = argv[0];
308 			}
309 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
310 				(n < 0) || (n > 60))
311 			{
312 				warnx("invalid wait time");
313 				break;
314 			}
315 			minwait = (u_int)n;
316 			break;
317 
318 		default:
319 			warnx("unknown switch: -%c", argv[0][1]);
320 			usage();
321 			break;
322 
323 		} /* switch */
324 	} /* for args */
325 
326 	/* Make sure server name argument is suplied. */
327 	servername = argv[0];
328 	if (!servername) {
329 		warnx("missing server name");
330 		usage();
331 	}
332 	/*
333 	 * Get address of real bootp server.
334 	 */
335 	if (isdigit(servername[0]))
336 		server_ipa = inet_addr(servername);
337 	else {
338 		hep = gethostbyname(servername);
339 		if (!hep)
340 			errx(1, "can't get addr for %s", servername);
341 		bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
342 	}
343 
344 	if (standalone) {
345 		/*
346 		 * Go into background and disassociate from controlling terminal.
347 		 * XXX - This is not the POSIX way (Should use setsid). -gwr
348 		 */
349 		if (debug < 3) {
350 			if (fork())
351 				exit(0);
352 #ifdef	NO_SETSID
353 			setpgrp(0,0);
354 #ifdef TIOCNOTTY
355 			n = open(_PATH_TTY, O_RDWR);
356 			if (n >= 0) {
357 				ioctl(n, TIOCNOTTY, (char *) 0);
358 				(void) close(n);
359 			}
360 #endif	/* TIOCNOTTY */
361 #else	/* SETSID */
362 			if (setsid() < 0)
363 				perror("setsid");
364 #endif	/* SETSID */
365 		} /* if debug < 3 */
366 		/*
367 		 * Nuke any timeout value
368 		 */
369 		timeout = NULL;
370 
371 		/*
372 		 * Here, bootpd would do:
373 		 *	chdir
374 		 *	tzone_init
375 		 *	rdtab_init
376 		 *	readtab
377 		 */
378 
379 		/*
380 		 * Create a socket.
381 		 */
382 		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
383 			report(LOG_ERR, "socket: %s", get_network_errmsg());
384 			exit(1);
385 		}
386 		/*
387 		 * Get server's listening port number
388 		 */
389 		servp = getservbyname("bootps", "udp");
390 		if (servp) {
391 			bootps_port = ntohs((u_short) servp->s_port);
392 		} else {
393 			bootps_port = (u_short) IPPORT_BOOTPS;
394 			report(LOG_ERR,
395 			   "bootps/udp: unknown service -- using port %d",
396 				   bootps_port);
397 		}
398 
399 		/*
400 		 * Bind socket to BOOTPS port.
401 		 */
402 		bind_addr.sin_family = AF_INET;
403 		bind_addr.sin_port = htons(bootps_port);
404 		bind_addr.sin_addr.s_addr = INADDR_ANY;
405 		if (bind(s, (struct sockaddr *) &bind_addr,
406 				 sizeof(bind_addr)) < 0)
407 		{
408 			report(LOG_ERR, "bind: %s", get_network_errmsg());
409 			exit(1);
410 		}
411 	} /* if standalone */
412 	/*
413 	 * Get destination port number so we can reply to client
414 	 */
415 	servp = getservbyname("bootpc", "udp");
416 	if (servp) {
417 		bootpc_port = ntohs(servp->s_port);
418 	} else {
419 		report(LOG_ERR,
420 			   "bootpc/udp: unknown service -- using port %d",
421 			   IPPORT_BOOTPC);
422 		bootpc_port = (u_short) IPPORT_BOOTPC;
423 	}
424 
425 	/* no signal catchers */
426 
427 	/*
428 	 * Process incoming requests.
429 	 */
430 	for (;;) {
431 		struct timeval tv;
432 
433 		readfds = 1 << s;
434 		if (timeout)
435 			tv = *timeout;
436 
437 		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,
438 						(timeout) ? &tv : NULL);
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 				   (long)(actualtimeout.tv_sec / 60));
448 			exit(0);
449 		}
450 		ra_len = sizeof(recv_addr);
451 		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
452 					 (struct sockaddr *) &recv_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(recv_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 	return 0;
478 }
479 
480 
481 
482 
483 /*
484  * Print "usage" message and exit
485  */
486 
487 static void
488 usage()
489 {
490 	fprintf(stderr,
491 		"usage: bootpgw [-a] [-i | -s] [-d level] [-h count] [-t timeout]\n"
492 		"               [-w time] server\n");
493 	fprintf(stderr, "\t -a\tdon't modify ARP table\n");
494 	fprintf(stderr, "\t -d n\tset debug level\n");
495 	fprintf(stderr, "\t -h n\tset max hop count\n");
496 	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
497 	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
498 	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
499 	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
500 	exit(1);
501 }
502 
503 
504 
505 /*
506  * Process BOOTREQUEST packet.
507  *
508  * Note, this just forwards the request to a real server.
509  */
510 static void
511 handle_request()
512 {
513 	struct bootp *bp = (struct bootp *) pktbuf;
514 	u_short secs;
515         u_char hops;
516 
517 	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
518 
519 	if (debug) {
520 		report(LOG_INFO, "request from %s",
521 			   inet_ntoa(recv_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 = bp->bp_hops;
530 	if (++hops > maxhops) {
531 		report(LOG_NOTICE, "request from %s reached hop limit",
532 			   inet_ntoa(recv_addr.sin_addr));
533 		return;
534 	}
535 	bp->bp_hops = 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 		struct ifreq *ifr;
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@zk3.dec.com> for finding this bug!)
553 		 */
554 		ifr = getif(s, &recv_addr.sin_addr);
555 		if (!ifr) {
556 			report(LOG_NOTICE, "no interface for request from %s",
557 				   inet_ntoa(recv_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 	send_addr.sin_family = AF_INET;
584 	send_addr.sin_port = htons(bootps_port);
585 	send_addr.sin_addr.s_addr = server_ipa;
586 
587 	/* Send reply with same size packet as request used. */
588 	if (sendto(s, pktbuf, pktlen, 0,
589 			   (struct sockaddr *) &send_addr,
590 			   sizeof(send_addr)) < 0)
591 	{
592 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
593 	}
594 }
595 
596 
597 
598 /*
599  * Process BOOTREPLY packet.
600  */
601 static void
602 handle_reply()
603 {
604 	struct bootp *bp = (struct bootp *) pktbuf;
605 	struct ifreq *ifr;
606 	struct sockaddr_in *sip;
607 	unsigned char *ha;
608 	int len, haf;
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 	send_addr.sin_family = AF_INET;
635 	send_addr.sin_addr = bp->bp_yiaddr;
636 	send_addr.sin_port = htons(bootpc_port);
637 
638 	if (arpmod) {
639 		/* Create an ARP cache entry for the client. */
640 		ha = bp->bp_chaddr;
641 		len = bp->bp_hlen;
642 		struct in_addr dst;
643 
644 		if (len > MAXHADDRLEN)
645 			len = MAXHADDRLEN;
646 		haf = (int) bp->bp_htype;
647 		if (haf == 0)
648 			haf = HTYPE_ETHERNET;
649 
650 		if (debug > 1)
651 			report(LOG_INFO, "setarp %s - %s",
652 				   inet_ntoa(dst), haddrtoa(ha, len));
653 		setarp(s, &dst, haf, ha, len);
654 	}
655 
656 	/* Send reply with same size packet as request used. */
657 	if (sendto(s, pktbuf, pktlen, 0,
658 			   (struct sockaddr *) &send_addr,
659 			   sizeof(send_addr)) < 0)
660 	{
661 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
662 	}
663 }
664 
665 /*
666  * Local Variables:
667  * tab-width: 4
668  * c-indent-level: 4
669  * c-argdecl-indent: 4
670  * c-continued-statement-offset: 4
671  * c-continued-brace-offset: -4
672  * c-label-offset: -4
673  * c-brace-offset: 0
674  * End:
675  */
676