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