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