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