xref: /dragonfly/libexec/bootpd/bootpgw/bootpgw.c (revision 21c1c48a)
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.3.2.1 2000/12/11 01:03:21 obrien 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 <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 		fprintf(stderr, "bootpgw: can't get hostname\n");
221 		exit(1);
222 	}
223 	hostname = my_uname.nodename;
224 
225 	hep = gethostbyname(hostname);
226 	if (!hep) {
227 		printf("Can not get my IP address\n");
228 		exit(1);
229 	}
230 	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
231 
232 	/*
233 	 * Read switches.
234 	 */
235 	for (argc--, argv++; argc > 0; argc--, argv++) {
236 		if (argv[0][0] != '-')
237 			break;
238 		switch (argv[0][1]) {
239 
240 		case 'd':				/* debug level */
241 			if (argv[0][2]) {
242 				stmp = &(argv[0][2]);
243 			} else if (argv[1] && argv[1][0] == '-') {
244 				/*
245 				 * Backwards-compatible behavior:
246 				 * no parameter, so just increment the debug flag.
247 				 */
248 				debug++;
249 				break;
250 			} else {
251 				argc--;
252 				argv++;
253 				stmp = argv[0];
254 			}
255 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
256 				fprintf(stderr,
257 						"%s: invalid debug level\n", progname);
258 				break;
259 			}
260 			debug = n;
261 			break;
262 
263 		case 'h':				/* hop count limit */
264 			if (argv[0][2]) {
265 				stmp = &(argv[0][2]);
266 			} else {
267 				argc--;
268 				argv++;
269 				stmp = argv[0];
270 			}
271 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
272 				(n < 0) || (n > 16))
273 			{
274 				fprintf(stderr,
275 						"bootpgw: invalid hop count limit\n");
276 				break;
277 			}
278 			maxhops = (u_char)n;
279 			break;
280 
281 		case 'i':				/* inetd mode */
282 			standalone = FALSE;
283 			break;
284 
285 		case 's':				/* standalone mode */
286 			standalone = TRUE;
287 			break;
288 
289 		case 't':				/* timeout */
290 			if (argv[0][2]) {
291 				stmp = &(argv[0][2]);
292 			} else {
293 				argc--;
294 				argv++;
295 				stmp = argv[0];
296 			}
297 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
298 				fprintf(stderr,
299 						"%s: invalid timeout specification\n", progname);
300 				break;
301 			}
302 			actualtimeout.tv_sec = (int32) (60 * n);
303 			/*
304 			 * If the actual timeout is zero, pass a NULL pointer
305 			 * to select so it blocks indefinitely, otherwise,
306 			 * point to the actual timeout value.
307 			 */
308 			timeout = (n > 0) ? &actualtimeout : NULL;
309 			break;
310 
311 		case 'w':				/* wait time */
312 			if (argv[0][2]) {
313 				stmp = &(argv[0][2]);
314 			} else {
315 				argc--;
316 				argv++;
317 				stmp = argv[0];
318 			}
319 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
320 				(n < 0) || (n > 60))
321 			{
322 				fprintf(stderr,
323 						"bootpgw: invalid wait time\n");
324 				break;
325 			}
326 			minwait = (u_int)n;
327 			break;
328 
329 		default:
330 			fprintf(stderr, "%s: unknown switch: -%c\n",
331 					progname, argv[0][1]);
332 			usage();
333 			break;
334 
335 		} /* switch */
336 	} /* for args */
337 
338 	/* Make sure server name argument is suplied. */
339 	servername = argv[0];
340 	if (!servername) {
341 		fprintf(stderr, "bootpgw: missing server name\n");
342 		usage();
343 	}
344 	/*
345 	 * Get address of real bootp server.
346 	 */
347 	if (isdigit(servername[0]))
348 		server_ipa = inet_addr(servername);
349 	else {
350 		hep = gethostbyname(servername);
351 		if (!hep) {
352 			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
353 			exit(1);
354 		}
355 		bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
356 	}
357 
358 	if (standalone) {
359 		/*
360 		 * Go into background and disassociate from controlling terminal.
361 		 * XXX - This is not the POSIX way (Should use setsid). -gwr
362 		 */
363 		if (debug < 3) {
364 			if (fork())
365 				exit(0);
366 #ifdef	NO_SETSID
367 			setpgrp(0,0);
368 #ifdef TIOCNOTTY
369 			n = open(_PATH_TTY, O_RDWR);
370 			if (n >= 0) {
371 				ioctl(n, TIOCNOTTY, NULL);
372 				(void) close(n);
373 			}
374 #endif	/* TIOCNOTTY */
375 #else	/* SETSID */
376 			if (setsid() < 0)
377 				perror("setsid");
378 #endif	/* SETSID */
379 		} /* if debug < 3 */
380 		/*
381 		 * Nuke any timeout value
382 		 */
383 		timeout = NULL;
384 
385 		/*
386 		 * Here, bootpd would do:
387 		 *	chdir
388 		 *	tzone_init
389 		 *	rdtab_init
390 		 *	readtab
391 		 */
392 
393 		/*
394 		 * Create a socket.
395 		 */
396 		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
397 			report(LOG_ERR, "socket: %s", get_network_errmsg());
398 			exit(1);
399 		}
400 		/*
401 		 * Get server's listening port number
402 		 */
403 		servp = getservbyname("bootps", "udp");
404 		if (servp) {
405 			bootps_port = ntohs((u_short) servp->s_port);
406 		} else {
407 			bootps_port = (u_short) IPPORT_BOOTPS;
408 			report(LOG_ERR,
409 				   "udp/bootps: unknown service -- assuming port %d",
410 				   bootps_port);
411 		}
412 
413 		/*
414 		 * Bind socket to BOOTPS port.
415 		 */
416 		bind_addr.sin_family = AF_INET;
417 		bind_addr.sin_port = htons(bootps_port);
418 		bind_addr.sin_addr.s_addr = INADDR_ANY;
419 		if (bind(s, (struct sockaddr *) &bind_addr,
420 				 sizeof(bind_addr)) < 0)
421 		{
422 			report(LOG_ERR, "bind: %s", get_network_errmsg());
423 			exit(1);
424 		}
425 	} /* if standalone */
426 	/*
427 	 * Get destination port number so we can reply to client
428 	 */
429 	servp = getservbyname("bootpc", "udp");
430 	if (servp) {
431 		bootpc_port = ntohs(servp->s_port);
432 	} else {
433 		report(LOG_ERR,
434 			   "udp/bootpc: unknown service -- assuming port %d",
435 			   IPPORT_BOOTPC);
436 		bootpc_port = (u_short) IPPORT_BOOTPC;
437 	}
438 
439 	/* no signal catchers */
440 
441 	/*
442 	 * Process incoming requests.
443 	 */
444 	for (;;) {
445 		struct timeval tv;
446 
447 		readfds = 1 << s;
448 		if (timeout)
449 			tv = *timeout;
450 
451 		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,
452 						(timeout) ? &tv : NULL);
453 		if (nfound < 0) {
454 			if (errno != EINTR) {
455 				report(LOG_ERR, "select: %s", get_errmsg());
456 			}
457 			continue;
458 		}
459 		if (!(readfds & (1 << s))) {
460 			report(LOG_INFO, "exiting after %ld minutes of inactivity",
461 				   actualtimeout.tv_sec / 60);
462 			exit(0);
463 		}
464 		ra_len = sizeof(recv_addr);
465 		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
466 					 (struct sockaddr *) &recv_addr, &ra_len);
467 		if (n <= 0) {
468 			continue;
469 		}
470 		if (debug > 3) {
471 			report(LOG_INFO, "recvd pkt from IP addr %s",
472 				   inet_ntoa(recv_addr.sin_addr));
473 		}
474 		if (n < sizeof(struct bootp)) {
475 			if (debug) {
476 				report(LOG_INFO, "received short packet");
477 			}
478 			continue;
479 		}
480 		pktlen = n;
481 
482 		switch (bp->bp_op) {
483 		case BOOTREQUEST:
484 			handle_request();
485 			break;
486 		case BOOTREPLY:
487 			handle_reply();
488 			break;
489 		}
490 	}
491 	return 0;
492 }
493 
494 
495 
496 
497 /*
498  * Print "usage" message and exit
499  */
500 
501 static void
502 usage(void)
503 {
504 	fprintf(stderr,
505 			"usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
506 	fprintf(stderr, "\t -d n\tset debug level\n");
507 	fprintf(stderr, "\t -h n\tset max hop count\n");
508 	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
509 	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
510 	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
511 	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
512 	exit(1);
513 }
514 
515 
516 
517 /*
518  * Process BOOTREQUEST packet.
519  *
520  * Note, this just forwards the request to a real server.
521  */
522 static void
523 handle_request(void)
524 {
525 	struct bootp *bp = (struct bootp *) pktbuf;
526 	u_short secs;
527         u_char hops;
528 
529 	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
530 
531 	if (debug) {
532 		report(LOG_INFO, "request from %s",
533 			   inet_ntoa(recv_addr.sin_addr));
534 	}
535 	/* Has the client been waiting long enough? */
536 	secs = ntohs(bp->bp_secs);
537 	if (secs < minwait)
538 		return;
539 
540 	/* Has this packet hopped too many times? */
541 	hops = bp->bp_hops;
542 	if (++hops > maxhops) {
543 		report(LOG_NOTICE, "reqest from %s reached hop limit",
544 			   inet_ntoa(recv_addr.sin_addr));
545 		return;
546 	}
547 	bp->bp_hops = hops;
548 
549 	/*
550 	 * Here one might discard a request from the same subnet as the
551 	 * real server, but we can assume that the real server will send
552 	 * a reply to the client before it waits for minwait seconds.
553 	 */
554 
555 	/* If gateway address is not set, put in local interface addr. */
556 	if (bp->bp_giaddr.s_addr == 0) {
557 #if 0	/* BUG */
558 		struct sockaddr_in *sip;
559 		struct ifreq *ifr;
560 		/*
561 		 * XXX - This picks the wrong interface when the receive addr
562 		 * is the broadcast address.  There is no  portable way to
563 		 * find out which interface a broadcast was received on. -gwr
564 		 * (Thanks to <walker@zk3.dec.com> for finding this bug!)
565 		 */
566 		ifr = getif(s, &recv_addr.sin_addr);
567 		if (!ifr) {
568 			report(LOG_NOTICE, "no interface for request from %s",
569 				   inet_ntoa(recv_addr.sin_addr));
570 			return;
571 		}
572 		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
573 		bp->bp_giaddr = sip->sin_addr;
574 #else	/* BUG */
575 		/*
576 		 * XXX - Just set "giaddr" to our "official" IP address.
577 		 * RFC 1532 says giaddr MUST be set to the address of the
578 		 * interface on which the request was received.  Setting
579 		 * it to our "default" IP address is not strictly correct,
580 		 * but is good enough to allow the real BOOTP server to
581 		 * get the reply back here.  Then, before we forward the
582 		 * reply to the client, the giaddr field is corrected.
583 		 * (In case the client uses giaddr, which it should not.)
584 		 * See handle_reply()
585 		 */
586 		bp->bp_giaddr = my_ip_addr;
587 #endif	/* BUG */
588 
589 		/*
590 		 * XXX - DHCP says to insert a subnet mask option into the
591 		 * options area of the request (if vendor magic == std).
592 		 */
593 	}
594 	/* Set up socket address for send. */
595 	send_addr.sin_family = AF_INET;
596 	send_addr.sin_port = htons(bootps_port);
597 	send_addr.sin_addr.s_addr = server_ipa;
598 
599 	/* Send reply with same size packet as request used. */
600 	if (sendto(s, pktbuf, pktlen, 0,
601 			   (struct sockaddr *) &send_addr,
602 			   sizeof(send_addr)) < 0)
603 	{
604 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
605 	}
606 }
607 
608 
609 
610 /*
611  * Process BOOTREPLY packet.
612  */
613 static void
614 handle_reply(void)
615 {
616 	struct bootp *bp = (struct bootp *) pktbuf;
617 	struct ifreq *ifr;
618 	struct sockaddr_in *sip;
619 	unsigned char *ha;
620 	int len, haf;
621 
622 	if (debug) {
623 		report(LOG_INFO, "   reply for %s",
624 			   inet_ntoa(bp->bp_yiaddr));
625 	}
626 	/* Make sure client is directly accessible. */
627 	ifr = getif(s, &(bp->bp_yiaddr));
628 	if (!ifr) {
629 		report(LOG_NOTICE, "no interface for reply to %s",
630 			   inet_ntoa(bp->bp_yiaddr));
631 		return;
632 	}
633 #if 1	/* Experimental (see BUG above) */
634 /* #ifdef CATER_TO_OLD_CLIENTS ? */
635 	/*
636 	 * The giaddr field has been set to our "default" IP address
637 	 * which might not be on the same interface as the client.
638 	 * In case the client looks at giaddr, (which it should not)
639 	 * giaddr is now set to the address of the correct interface.
640 	 */
641 	sip = (struct sockaddr_in *) &(ifr->ifr_addr);
642 	bp->bp_giaddr = sip->sin_addr;
643 #endif
644 
645 	/* Set up socket address for send to client. */
646 	send_addr.sin_family = AF_INET;
647 	send_addr.sin_addr = bp->bp_yiaddr;
648 	send_addr.sin_port = htons(bootpc_port);
649 
650 	/* Create an ARP cache entry for the client. */
651 	ha = bp->bp_chaddr;
652 	len = bp->bp_hlen;
653 	if (len > MAXHADDRLEN)
654 		len = MAXHADDRLEN;
655 	haf = (int) bp->bp_htype;
656 	if (haf == 0)
657 		haf = HTYPE_ETHERNET;
658 
659 	if (debug > 1)
660 		report(LOG_INFO, "setarp %s - %s",
661 			   inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
662 	setarp(s, &bp->bp_yiaddr, haf, ha, len);
663 
664 	/* Send reply with same size packet as request used. */
665 	if (sendto(s, pktbuf, pktlen, 0,
666 			   (struct sockaddr *) &send_addr,
667 			   sizeof(send_addr)) < 0)
668 	{
669 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
670 	}
671 }
672 
673 /*
674  * Local Variables:
675  * tab-width: 4
676  * c-indent-level: 4
677  * c-argdecl-indent: 4
678  * c-continued-statement-offset: 4
679  * c-continued-brace-offset: -4
680  * c-label-offset: -4
681  * c-brace-offset: 0
682  * End:
683  */
684