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