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