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