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