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