xref: /freebsd/libexec/bootpd/bootpgw/bootpgw.c (revision daf1cffce2e07931f27c6c6998652e90df6ba87e)
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_char 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 int
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_char)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 	return 0;
498 }
499 
500 
501 
502 
503 /*
504  * Print "usage" message and exit
505  */
506 
507 static void
508 usage()
509 {
510 	fprintf(stderr,
511 			"usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
512 	fprintf(stderr, "\t -d n\tset debug level\n");
513 	fprintf(stderr, "\t -h n\tset max hop count\n");
514 	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
515 	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
516 	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
517 	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
518 	exit(1);
519 }
520 
521 
522 
523 /*
524  * Process BOOTREQUEST packet.
525  *
526  * Note, this just forwards the request to a real server.
527  */
528 static void
529 handle_request()
530 {
531 	struct bootp *bp = (struct bootp *) pktbuf;
532 	u_short secs;
533         u_char hops;
534 
535 	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
536 
537 	if (debug) {
538 		report(LOG_INFO, "request from %s",
539 			   inet_ntoa(recv_addr.sin_addr));
540 	}
541 	/* Has the client been waiting long enough? */
542 	secs = ntohs(bp->bp_secs);
543 	if (secs < minwait)
544 		return;
545 
546 	/* Has this packet hopped too many times? */
547 	hops = bp->bp_hops;
548 	if (++hops > maxhops) {
549 		report(LOG_NOTICE, "reqest from %s reached hop limit",
550 			   inet_ntoa(recv_addr.sin_addr));
551 		return;
552 	}
553 	bp->bp_hops = hops;
554 
555 	/*
556 	 * Here one might discard a request from the same subnet as the
557 	 * real server, but we can assume that the real server will send
558 	 * a reply to the client before it waits for minwait seconds.
559 	 */
560 
561 	/* If gateway address is not set, put in local interface addr. */
562 	if (bp->bp_giaddr.s_addr == 0) {
563 #if 0	/* BUG */
564 		struct sockaddr_in *sip;
565 		struct ifreq *ifr;
566 		/*
567 		 * XXX - This picks the wrong interface when the receive addr
568 		 * is the broadcast address.  There is no  portable way to
569 		 * find out which interface a broadcast was received on. -gwr
570 		 * (Thanks to <walker@zk3.dec.com> for finding this bug!)
571 		 */
572 		ifr = getif(s, &recv_addr.sin_addr);
573 		if (!ifr) {
574 			report(LOG_NOTICE, "no interface for request from %s",
575 				   inet_ntoa(recv_addr.sin_addr));
576 			return;
577 		}
578 		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
579 		bp->bp_giaddr = sip->sin_addr;
580 #else	/* BUG */
581 		/*
582 		 * XXX - Just set "giaddr" to our "official" IP address.
583 		 * RFC 1532 says giaddr MUST be set to the address of the
584 		 * interface on which the request was received.  Setting
585 		 * it to our "default" IP address is not strictly correct,
586 		 * but is good enough to allow the real BOOTP server to
587 		 * get the reply back here.  Then, before we forward the
588 		 * reply to the client, the giaddr field is corrected.
589 		 * (In case the client uses giaddr, which it should not.)
590 		 * See handle_reply()
591 		 */
592 		bp->bp_giaddr = my_ip_addr;
593 #endif	/* BUG */
594 
595 		/*
596 		 * XXX - DHCP says to insert a subnet mask option into the
597 		 * options area of the request (if vendor magic == std).
598 		 */
599 	}
600 	/* Set up socket address for send. */
601 	send_addr.sin_family = AF_INET;
602 	send_addr.sin_port = htons(bootps_port);
603 	send_addr.sin_addr.s_addr = server_ipa;
604 
605 	/* Send reply with same size packet as request used. */
606 	if (sendto(s, pktbuf, pktlen, 0,
607 			   (struct sockaddr *) &send_addr,
608 			   sizeof(send_addr)) < 0)
609 	{
610 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
611 	}
612 }
613 
614 
615 
616 /*
617  * Process BOOTREPLY packet.
618  */
619 static void
620 handle_reply()
621 {
622 	struct bootp *bp = (struct bootp *) pktbuf;
623 	struct ifreq *ifr;
624 	struct sockaddr_in *sip;
625 	unsigned char *ha;
626 	int len, haf;
627 
628 	if (debug) {
629 		report(LOG_INFO, "   reply for %s",
630 			   inet_ntoa(bp->bp_yiaddr));
631 	}
632 	/* Make sure client is directly accessible. */
633 	ifr = getif(s, &(bp->bp_yiaddr));
634 	if (!ifr) {
635 		report(LOG_NOTICE, "no interface for reply to %s",
636 			   inet_ntoa(bp->bp_yiaddr));
637 		return;
638 	}
639 #if 1	/* Experimental (see BUG above) */
640 /* #ifdef CATER_TO_OLD_CLIENTS ? */
641 	/*
642 	 * The giaddr field has been set to our "default" IP address
643 	 * which might not be on the same interface as the client.
644 	 * In case the client looks at giaddr, (which it should not)
645 	 * giaddr is now set to the address of the correct interface.
646 	 */
647 	sip = (struct sockaddr_in *) &(ifr->ifr_addr);
648 	bp->bp_giaddr = sip->sin_addr;
649 #endif
650 
651 	/* Set up socket address for send to client. */
652 	send_addr.sin_family = AF_INET;
653 	send_addr.sin_addr = bp->bp_yiaddr;
654 	send_addr.sin_port = htons(bootpc_port);
655 
656 	/* Create an ARP cache entry for the client. */
657 	ha = bp->bp_chaddr;
658 	len = bp->bp_hlen;
659 	if (len > MAXHADDRLEN)
660 		len = MAXHADDRLEN;
661 	haf = (int) bp->bp_htype;
662 	if (haf == 0)
663 		haf = HTYPE_ETHERNET;
664 
665 	if (debug > 1)
666 		report(LOG_INFO, "setarp %s - %s",
667 			   inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
668 	setarp(s, &bp->bp_yiaddr, haf, ha, len);
669 
670 	/* Send reply with same size packet as request used. */
671 	if (sendto(s, pktbuf, pktlen, 0,
672 			   (struct sockaddr *) &send_addr,
673 			   sizeof(send_addr)) < 0)
674 	{
675 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
676 	}
677 }
678 
679 /*
680  * Local Variables:
681  * tab-width: 4
682  * c-indent-level: 4
683  * c-argdecl-indent: 4
684  * c-continued-statement-offset: 4
685  * c-continued-brace-offset: -4
686  * c-label-offset: -4
687  * c-brace-offset: 0
688  * End:
689  */
690