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