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