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