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