xref: /netbsd/usr.sbin/bootp/bootpgw/bootpgw.c (revision bf9ec67e)
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.8 1998/07/06 07:02:17 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 
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 #ifdef	__STDC__
94 #define P(args) args
95 #else
96 #define P(args) ()
97 #endif
98 
99 static void usage P((void));
100 static void handle_reply P((void));
101 static void handle_request P((void));
102 int main P((int, char **));
103 
104 #undef	P
105 
106 /*
107  * IP port numbers for client and server obtained from /etc/services
108  */
109 
110 u_short bootps_port, bootpc_port;
111 
112 
113 /*
114  * Internet socket and interface config structures
115  */
116 
117 struct sockaddr_in bind_addr;	/* Listening */
118 struct sockaddr_in clnt_addr;	/* client address */
119 struct sockaddr_in serv_addr;	/* server address */
120 
121 
122 /*
123  * option defaults
124  */
125 int debug = 0;					/* Debugging flag (level) */
126 struct timeval actualtimeout =
127 {								/* fifteen minutes */
128 	15 * 60L,					/* tv_sec */
129 	0							/* tv_usec */
130 };
131 u_int maxhops = 4;				/* Number of hops allowed for requests. */
132 u_int minwait = 3;				/* Number of seconds client must wait before
133 						   its bootrequest packets are forwarded. */
134 
135 /*
136  * General
137  */
138 
139 int s;							/* Socket file descriptor */
140 char *pktbuf;					/* Receive packet buffer */
141 int pktlen;
142 char *progname;
143 char *servername;
144 
145 char myhostname[MAXHOSTNAMELEN + 1];
146 struct in_addr my_ip_addr;
147 
148 
149 
150 
151 /*
152  * Initialization such as command-line processing is done and then the
153  * main server loop is started.
154  */
155 
156 int
157 main(argc, argv)
158 	int argc;
159 	char **argv;
160 {
161 	struct timeval *timeout;
162 	struct bootp *bp;
163 	struct servent *servp;
164 	struct hostent *hep;
165 	char *stmp;
166 	int n, ba_len, ra_len;
167 	int nfound, readfds;
168 	int standalone;
169 
170 	progname = strrchr(argv[0], '/');
171 	if (progname) progname++;
172 	else progname = argv[0];
173 
174 	/*
175 	 * Initialize logging.
176 	 */
177 	report_init(0);				/* uses progname */
178 
179 	/*
180 	 * Log startup
181 	 */
182 	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
183 
184 	/* Debugging for compilers with struct padding. */
185 	assert(sizeof(struct bootp) == BP_MINPKTSZ);
186 
187 	/* Get space for receiving packets and composing replies. */
188 	pktbuf = malloc(MAX_MSG_SIZE);
189 	if (!pktbuf) {
190 		report(LOG_ERR, "malloc failed");
191 		exit(1);
192 	}
193 	bp = (struct bootp *) pktbuf;
194 
195 	/*
196 	 * Check to see if a socket was passed to us from inetd.
197 	 *
198 	 * Use getsockname() to determine if descriptor 0 is indeed a socket
199 	 * (and thus we are probably a child of inetd) or if it is instead
200 	 * something else and we are running standalone.
201 	 */
202 	s = 0;
203 	ba_len = sizeof(bind_addr);
204 	bzero((char *) &bind_addr, ba_len);
205 	errno = 0;
206 	standalone = TRUE;
207 	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
208 		/*
209 		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
210 		 */
211 		if (bind_addr.sin_family == AF_INET) {
212 			standalone = FALSE;
213 			bootps_port = ntohs(bind_addr.sin_port);
214 		} else {
215 			/* Some other type of socket? */
216 			report(LOG_INFO, "getsockname: not an INET socket");
217 		}
218 	}
219 	/*
220 	 * Set defaults that might be changed by option switches.
221 	 */
222 	stmp = NULL;
223 	timeout = &actualtimeout;
224 	gethostname(myhostname, sizeof(myhostname));
225 	myhostname[sizeof(myhostname) - 1] = '\0';
226 	hep = gethostbyname(myhostname);
227 	if (!hep) {
228 		printf("Can not get my IP address\n");
229 		exit(1);
230 	}
231 	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
232 
233 	/*
234 	 * Read switches.
235 	 */
236 	for (argc--, argv++; argc > 0; argc--, argv++) {
237 		if (argv[0][0] != '-')
238 			break;
239 		switch (argv[0][1]) {
240 
241 		case 'd':				/* debug level */
242 			if (argv[0][2]) {
243 				stmp = &(argv[0][2]);
244 			} else if (argv[1] && argv[1][0] == '-') {
245 				/*
246 				 * Backwards-compatible behavior:
247 				 * no parameter, so just increment the debug flag.
248 				 */
249 				debug++;
250 				break;
251 			} else {
252 				argc--;
253 				argv++;
254 				stmp = argv[0];
255 			}
256 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
257 				fprintf(stderr,
258 						"%s: invalid debug level\n", progname);
259 				break;
260 			}
261 			debug = n;
262 			break;
263 
264 		case 'h':				/* hop count limit */
265 			if (argv[0][2]) {
266 				stmp = &(argv[0][2]);
267 			} else {
268 				argc--;
269 				argv++;
270 				stmp = argv[0];
271 			}
272 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
273 				(n < 0) || (n > 16))
274 			{
275 				fprintf(stderr,
276 						"bootpgw: invalid hop count limit\n");
277 				break;
278 			}
279 			maxhops = (u_int)n;
280 			break;
281 
282 		case 'i':				/* inetd mode */
283 			standalone = FALSE;
284 			break;
285 
286 		case 's':				/* standalone mode */
287 			standalone = TRUE;
288 			break;
289 
290 		case 't':				/* timeout */
291 			if (argv[0][2]) {
292 				stmp = &(argv[0][2]);
293 			} else {
294 				argc--;
295 				argv++;
296 				stmp = argv[0];
297 			}
298 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
299 				fprintf(stderr,
300 						"%s: invalid timeout specification\n", progname);
301 				break;
302 			}
303 			actualtimeout.tv_sec = (int32) (60 * n);
304 			/*
305 			 * If the actual timeout is zero, pass a NULL pointer
306 			 * to select so it blocks indefinitely, otherwise,
307 			 * point to the actual timeout value.
308 			 */
309 			timeout = (n > 0) ? &actualtimeout : NULL;
310 			break;
311 
312 		case 'w':				/* wait time */
313 			if (argv[0][2]) {
314 				stmp = &(argv[0][2]);
315 			} else {
316 				argc--;
317 				argv++;
318 				stmp = argv[0];
319 			}
320 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
321 				(n < 0) || (n > 60))
322 			{
323 				fprintf(stderr,
324 						"bootpgw: invalid wait time\n");
325 				break;
326 			}
327 			minwait = (u_int)n;
328 			break;
329 
330 		default:
331 			fprintf(stderr, "%s: unknown switch: -%c\n",
332 					progname, argv[0][1]);
333 			usage();
334 			break;
335 
336 		} /* switch */
337 	} /* for args */
338 
339 	/* Make sure server name argument is suplied. */
340 	servername = argv[0];
341 	if (!servername) {
342 		fprintf(stderr, "bootpgw: missing server name\n");
343 		usage();
344 	}
345 	/*
346 	 * Get address of real bootp server.
347 	 */
348 	if (inet_aton(servername, &serv_addr.sin_addr) == 0) {
349 		hep = gethostbyname(servername);
350 		if (!hep) {
351 			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
352 			exit(1);
353 		}
354 		memcpy(&serv_addr.sin_addr, hep->h_addr,
355 		    sizeof(serv_addr.sin_addr));
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("/dev/tty", O_RDWR);
370 			if (n >= 0) {
371 				ioctl(n, TIOCNOTTY, (char *) 0);
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 		readfds = 1 << s;
446 		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout);
447 		if (nfound < 0) {
448 			if (errno != EINTR) {
449 				report(LOG_ERR, "select: %s", get_errmsg());
450 			}
451 			continue;
452 		}
453 		if (!(readfds & (1 << s))) {
454 			report(LOG_INFO, "exiting after %ld minutes of inactivity",
455 				   actualtimeout.tv_sec / 60);
456 			exit(0);
457 		}
458 		ra_len = sizeof(clnt_addr);
459 		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
460 					 (struct sockaddr *) &clnt_addr, &ra_len);
461 		if (n <= 0) {
462 			continue;
463 		}
464 		if (debug > 3) {
465 			report(LOG_INFO, "recvd pkt from IP addr %s",
466 				   inet_ntoa(clnt_addr.sin_addr));
467 		}
468 		if (n < sizeof(struct bootp)) {
469 			if (debug) {
470 				report(LOG_INFO, "received short packet");
471 			}
472 			continue;
473 		}
474 		pktlen = n;
475 
476 		switch (bp->bp_op) {
477 		case BOOTREQUEST:
478 			handle_request();
479 			break;
480 		case BOOTREPLY:
481 			handle_reply();
482 			break;
483 		}
484 	}
485 }
486 
487 
488 
489 
490 /*
491  * Print "usage" message and exit
492  */
493 
494 static void
495 usage()
496 {
497 	fprintf(stderr,
498 			"usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
499 	fprintf(stderr, "\t -d n\tset debug level\n");
500 	fprintf(stderr, "\t -h n\tset max hop count\n");
501 	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
502 	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
503 	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
504 	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
505 	exit(1);
506 }
507 
508 
509 
510 /*
511  * Process BOOTREQUEST packet.
512  *
513  * Note, this just forwards the request to a real server.
514  */
515 static void
516 handle_request()
517 {
518 	struct bootp *bp = (struct bootp *) pktbuf;
519 #if 0
520 	struct ifreq *ifr;
521 #endif
522 	u_short secs, hops;
523 
524 	/* XXX - SLIP init: Set bp_ciaddr = clnt_addr here? */
525 
526 	if (debug) {
527 		report(LOG_INFO, "request from %s",
528 			   inet_ntoa(clnt_addr.sin_addr));
529 	}
530 	/* Has the client been waiting long enough? */
531 	secs = ntohs(bp->bp_secs);
532 	if (secs < minwait)
533 		return;
534 
535 	/* Has this packet hopped too many times? */
536 	hops = ntohs(bp->bp_hops);
537 	if (++hops > maxhops) {
538 		report(LOG_NOTICE, "request from %s reached hop limit",
539 			   inet_ntoa(clnt_addr.sin_addr));
540 		return;
541 	}
542 	bp->bp_hops = htons(hops);
543 
544 	/*
545 	 * Here one might discard a request from the same subnet as the
546 	 * real server, but we can assume that the real server will send
547 	 * a reply to the client before it waits for minwait seconds.
548 	 */
549 
550 	/* If gateway address is not set, put in local interface addr. */
551 	if (bp->bp_giaddr.s_addr == 0) {
552 #if 0	/* BUG */
553 		struct sockaddr_in *sip;
554 		/*
555 		 * XXX - This picks the wrong interface when the receive addr
556 		 * is the broadcast address.  There is no  portable way to
557 		 * find out which interface a broadcast was received on. -gwr
558 		 * (Thanks to <walker@zk3.dec.com> for finding this bug!)
559 		 */
560 		ifr = getif(s, &clnt_addr.sin_addr);
561 		if (!ifr) {
562 			report(LOG_NOTICE, "no interface for request from %s",
563 				   inet_ntoa(clnt_addr.sin_addr));
564 			return;
565 		}
566 		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
567 		bp->bp_giaddr = sip->sin_addr;
568 #else	/* BUG */
569 		/*
570 		 * XXX - Just set "giaddr" to our "official" IP address.
571 		 * RFC 1532 says giaddr MUST be set to the address of the
572 		 * interface on which the request was received.  Setting
573 		 * it to our "default" IP address is not strictly correct,
574 		 * but is good enough to allow the real BOOTP server to
575 		 * get the reply back here.  Then, before we forward the
576 		 * reply to the client, the giaddr field is corrected.
577 		 * (In case the client uses giaddr, which it should not.)
578 		 * See handle_reply()
579 		 */
580 		bp->bp_giaddr = my_ip_addr;
581 #endif	/* BUG */
582 
583 		/*
584 		 * XXX - DHCP says to insert a subnet mask option into the
585 		 * options area of the request (if vendor magic == std).
586 		 */
587 	}
588 	/* Set up socket address for send. */
589 	serv_addr.sin_family = AF_INET;
590 	serv_addr.sin_port = htons(bootps_port);
591 
592 	/* Send reply with same size packet as request used. */
593 	if (sendto(s, pktbuf, pktlen, 0,
594 			   (struct sockaddr *) &serv_addr,
595 			   sizeof(serv_addr)) < 0)
596 	{
597 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
598 	}
599 }
600 
601 
602 
603 /*
604  * Process BOOTREPLY packet.
605  */
606 static void
607 handle_reply()
608 {
609 	struct bootp *bp = (struct bootp *) pktbuf;
610 	struct ifreq *ifr;
611 	struct sockaddr_in *sip;
612 	u_char canon_haddr[MAXHADDRLEN];
613 	unsigned char *ha;
614 	int len;
615 
616 	if (debug) {
617 		report(LOG_INFO, "   reply for %s",
618 			   inet_ntoa(bp->bp_yiaddr));
619 	}
620 	/* Make sure client is directly accessible. */
621 	ifr = getif(s, &(bp->bp_yiaddr));
622 	if (!ifr) {
623 		report(LOG_NOTICE, "no interface for reply to %s",
624 			   inet_ntoa(bp->bp_yiaddr));
625 		return;
626 	}
627 #if 1	/* Experimental (see BUG above) */
628 /* #ifdef CATER_TO_OLD_CLIENTS ? */
629 	/*
630 	 * The giaddr field has been set to our "default" IP address
631 	 * which might not be on the same interface as the client.
632 	 * In case the client looks at giaddr, (which it should not)
633 	 * giaddr is now set to the address of the correct interface.
634 	 */
635 	sip = (struct sockaddr_in *) &(ifr->ifr_addr);
636 	bp->bp_giaddr = sip->sin_addr;
637 #endif
638 
639 	/* Set up socket address for send to client. */
640 	clnt_addr.sin_family = AF_INET;
641 	clnt_addr.sin_addr = bp->bp_yiaddr;
642 	clnt_addr.sin_port = htons(bootpc_port);
643 
644 	/* Create an ARP cache entry for the client. */
645 	ha = bp->bp_chaddr;
646 	len = bp->bp_hlen;
647 	if (len > MAXHADDRLEN)
648 		len = MAXHADDRLEN;
649 	if (bp->bp_htype == HTYPE_IEEE802) {
650 		haddr_conv802(ha, canon_haddr, len);
651 		ha = canon_haddr;
652 	}
653 	if (debug > 1)
654 		report(LOG_INFO, "setarp %s - %s",
655 			   inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
656 	setarp(s, &bp->bp_yiaddr, ha, len);
657 
658 	/* Send reply with same size packet as request used. */
659 	if (sendto(s, pktbuf, pktlen, 0,
660 			   (struct sockaddr *) &clnt_addr,
661 			   sizeof(clnt_addr)) < 0)
662 	{
663 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
664 	}
665 }
666 
667 /*
668  * Local Variables:
669  * tab-width: 4
670  * c-indent-level: 4
671  * c-argdecl-indent: 4
672  * c-continued-statement-offset: 4
673  * c-continued-brace-offset: -4
674  * c-label-offset: -4
675  * c-brace-offset: 0
676  * End:
677  */
678