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