xref: /netbsd-src/usr.sbin/bootp/bootpgw/bootpgw.c (revision 6ea46cb5e46c49111a6ecf3bcbe3c7e2730fe9f6)
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 #ifndef lint
29 static char rcsid[] = "$Id: bootpgw.c,v 1.2 1994/08/22 22:14:48 gwr Exp $";
30 #endif
31 
32 /*
33  * BOOTPGW is typically used to forward BOOTP client requests from
34  * one subnet to a BOOTP server on a different subnet.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/socket.h>
40 #include <sys/ioctl.h>
41 #include <sys/file.h>
42 #include <sys/time.h>
43 #include <sys/stat.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 #include <stdlib.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <string.h>
56 #include <errno.h>
57 #include <ctype.h>
58 #include <netdb.h>
59 #include <syslog.h>
60 #include <assert.h>
61 
62 #ifdef	NO_SETSID
63 # include <fcntl.h>		/* for O_RDONLY, etc */
64 #endif
65 
66 #ifndef	USE_BFUNCS
67 # include <memory.h>
68 /* Yes, memcpy is OK here (no overlapped copies). */
69 # define bcopy(a,b,c)    memcpy(b,a,c)
70 # define bzero(p,l)      memset(p,0,l)
71 # define bcmp(a,b,c)     memcmp(a,b,c)
72 #endif
73 
74 #include "bootp.h"
75 #include "getif.h"
76 #include "hwaddr.h"
77 #include "report.h"
78 #include "patchlevel.h"
79 
80 /* Local definitions: */
81 #define MAX_MSG_SIZE			(3*512)	/* Maximum packet size */
82 #define TRUE 1
83 #define FALSE 0
84 #define get_network_errmsg get_errmsg
85 
86 
87 
88 /*
89  * Externals, forward declarations, and global variables
90  */
91 
92 #ifdef	__STDC__
93 #define P(args) args
94 #else
95 #define P(args) ()
96 #endif
97 
98 static void usage P((void));
99 static void handle_reply P((void));
100 static void handle_request P((void));
101 
102 #undef	P
103 
104 /*
105  * IP port numbers for client and server obtained from /etc/services
106  */
107 
108 u_short bootps_port, bootpc_port;
109 
110 
111 /*
112  * Internet socket and interface config structures
113  */
114 
115 struct sockaddr_in bind_addr;	/* Listening */
116 struct sockaddr_in recv_addr;	/* Packet source */
117 struct sockaddr_in send_addr;	/*  destination */
118 
119 
120 /*
121  * option defaults
122  */
123 int debug = 0;					/* Debugging flag (level) */
124 struct timeval actualtimeout =
125 {								/* fifteen minutes */
126 	15 * 60L,					/* tv_sec */
127 	0							/* tv_usec */
128 };
129 u_int maxhops = 4;				/* Number of hops allowed for requests. */
130 u_int minwait = 3;				/* Number of seconds client must wait before
131 						   its bootrequest packets are forwarded. */
132 
133 /*
134  * General
135  */
136 
137 int s;							/* Socket file descriptor */
138 char *pktbuf;					/* Receive packet buffer */
139 int pktlen;
140 char *progname;
141 char *servername;
142 int32 server_ipa;				/* Real server IP address, network order. */
143 
144 char myhostname[64];
145 struct in_addr my_ip_addr;
146 
147 
148 
149 
150 /*
151  * Initialization such as command-line processing is done and then the
152  * main server loop is started.
153  */
154 
155 void
156 main(argc, argv)
157 	int argc;
158 	char **argv;
159 {
160 	struct timeval *timeout;
161 	struct bootp *bp;
162 	struct servent *servp;
163 	struct hostent *hep;
164 	char *stmp;
165 	int n, ba_len, ra_len;
166 	int nfound, readfds;
167 	int standalone;
168 
169 	progname = strrchr(argv[0], '/');
170 	if (progname) progname++;
171 	else progname = argv[0];
172 
173 	/*
174 	 * Initialize logging.
175 	 */
176 	report_init(0);				/* uses progname */
177 
178 	/*
179 	 * Log startup
180 	 */
181 	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
182 
183 	/* Debugging for compilers with struct padding. */
184 	assert(sizeof(struct bootp) == BP_MINPKTSZ);
185 
186 	/* Get space for receiving packets and composing replies. */
187 	pktbuf = malloc(MAX_MSG_SIZE);
188 	if (!pktbuf) {
189 		report(LOG_ERR, "malloc failed");
190 		exit(1);
191 	}
192 	bp = (struct bootp *) pktbuf;
193 
194 	/*
195 	 * Check to see if a socket was passed to us from inetd.
196 	 *
197 	 * Use getsockname() to determine if descriptor 0 is indeed a socket
198 	 * (and thus we are probably a child of inetd) or if it is instead
199 	 * something else and we are running standalone.
200 	 */
201 	s = 0;
202 	ba_len = sizeof(bind_addr);
203 	bzero((char *) &bind_addr, ba_len);
204 	errno = 0;
205 	standalone = TRUE;
206 	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
207 		/*
208 		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
209 		 */
210 		if (bind_addr.sin_family == AF_INET) {
211 			standalone = FALSE;
212 			bootps_port = ntohs(bind_addr.sin_port);
213 		} else {
214 			/* Some other type of socket? */
215 			report(LOG_INFO, "getsockname: not an INET socket");
216 		}
217 	}
218 	/*
219 	 * Set defaults that might be changed by option switches.
220 	 */
221 	stmp = NULL;
222 	timeout = &actualtimeout;
223 	gethostname(myhostname, sizeof(myhostname));
224 	hep = gethostbyname(myhostname);
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 				fprintf(stderr,
256 						"%s: invalid debug level\n", progname);
257 				break;
258 			}
259 			debug = n;
260 			break;
261 
262 		case 'h':				/* hop count limit */
263 			if (argv[0][2]) {
264 				stmp = &(argv[0][2]);
265 			} else {
266 				argc--;
267 				argv++;
268 				stmp = argv[0];
269 			}
270 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
271 				(n < 0) || (n > 16))
272 			{
273 				fprintf(stderr,
274 						"bootpgw: invalid hop count limit\n");
275 				break;
276 			}
277 			maxhops = (u_int)n;
278 			break;
279 
280 		case 'i':				/* inetd mode */
281 			standalone = FALSE;
282 			break;
283 
284 		case 's':				/* standalone mode */
285 			standalone = TRUE;
286 			break;
287 
288 		case 't':				/* timeout */
289 			if (argv[0][2]) {
290 				stmp = &(argv[0][2]);
291 			} else {
292 				argc--;
293 				argv++;
294 				stmp = argv[0];
295 			}
296 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
297 				fprintf(stderr,
298 						"%s: invalid timeout specification\n", progname);
299 				break;
300 			}
301 			actualtimeout.tv_sec = (int32) (60 * n);
302 			/*
303 			 * If the actual timeout is zero, pass a NULL pointer
304 			 * to select so it blocks indefinitely, otherwise,
305 			 * point to the actual timeout value.
306 			 */
307 			timeout = (n > 0) ? &actualtimeout : NULL;
308 			break;
309 
310 		case 'w':				/* wait time */
311 			if (argv[0][2]) {
312 				stmp = &(argv[0][2]);
313 			} else {
314 				argc--;
315 				argv++;
316 				stmp = argv[0];
317 			}
318 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
319 				(n < 0) || (n > 60))
320 			{
321 				fprintf(stderr,
322 						"bootpgw: invalid wait time\n");
323 				break;
324 			}
325 			minwait = (u_int)n;
326 			break;
327 
328 		default:
329 			fprintf(stderr, "%s: unknown switch: -%c\n",
330 					progname, argv[0][1]);
331 			usage();
332 			break;
333 
334 		} /* switch */
335 	} /* for args */
336 
337 	/* Make sure server name argument is suplied. */
338 	servername = argv[0];
339 	if (!servername) {
340 		fprintf(stderr, "bootpgw: missing server name\n");
341 		usage();
342 	}
343 	/*
344 	 * Get address of real bootp server.
345 	 */
346 	if (isdigit(servername[0]))
347 		server_ipa = inet_addr(servername);
348 	else {
349 		hep = gethostbyname(servername);
350 		if (!hep) {
351 			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
352 			exit(1);
353 		}
354 		bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
355 	}
356 
357 	if (standalone) {
358 		/*
359 		 * Go into background and disassociate from controlling terminal.
360 		 * XXX - This is not the POSIX way (Should use setsid). -gwr
361 		 */
362 		if (debug < 3) {
363 			if (fork())
364 				exit(0);
365 #ifdef	NO_SETSID
366 			setpgrp(0,0);
367 #ifdef TIOCNOTTY
368 			n = open("/dev/tty", O_RDWR);
369 			if (n >= 0) {
370 				ioctl(n, TIOCNOTTY, (char *) 0);
371 				(void) close(n);
372 			}
373 #endif	/* TIOCNOTTY */
374 #else	/* SETSID */
375 			if (setsid() < 0)
376 				perror("setsid");
377 #endif	/* SETSID */
378 		} /* if debug < 3 */
379 		/*
380 		 * Nuke any timeout value
381 		 */
382 		timeout = NULL;
383 
384 		/*
385 		 * Here, bootpd would do:
386 		 *	chdir
387 		 *	tzone_init
388 		 *	rdtab_init
389 		 *	readtab
390 		 */
391 
392 		/*
393 		 * Create a socket.
394 		 */
395 		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
396 			report(LOG_ERR, "socket: %s", get_network_errmsg());
397 			exit(1);
398 		}
399 		/*
400 		 * Get server's listening port number
401 		 */
402 		servp = getservbyname("bootps", "udp");
403 		if (servp) {
404 			bootps_port = ntohs((u_short) servp->s_port);
405 		} else {
406 			bootps_port = (u_short) IPPORT_BOOTPS;
407 			report(LOG_ERR,
408 				   "udp/bootps: unknown service -- assuming port %d",
409 				   bootps_port);
410 		}
411 
412 		/*
413 		 * Bind socket to BOOTPS port.
414 		 */
415 		bind_addr.sin_family = AF_INET;
416 		bind_addr.sin_port = htons(bootps_port);
417 		bind_addr.sin_addr.s_addr = INADDR_ANY;
418 		if (bind(s, (struct sockaddr *) &bind_addr,
419 				 sizeof(bind_addr)) < 0)
420 		{
421 			report(LOG_ERR, "bind: %s", get_network_errmsg());
422 			exit(1);
423 		}
424 	} /* if standalone */
425 	/*
426 	 * Get destination port number so we can reply to client
427 	 */
428 	servp = getservbyname("bootpc", "udp");
429 	if (servp) {
430 		bootpc_port = ntohs(servp->s_port);
431 	} else {
432 		report(LOG_ERR,
433 			   "udp/bootpc: unknown service -- assuming port %d",
434 			   IPPORT_BOOTPC);
435 		bootpc_port = (u_short) IPPORT_BOOTPC;
436 	}
437 
438 	/* no signal catchers */
439 
440 	/*
441 	 * Process incoming requests.
442 	 */
443 	for (;;) {
444 		readfds = 1 << s;
445 		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout);
446 		if (nfound < 0) {
447 			if (errno != EINTR) {
448 				report(LOG_ERR, "select: %s", get_errmsg());
449 			}
450 			continue;
451 		}
452 		if (!(readfds & (1 << s))) {
453 			report(LOG_INFO, "exiting after %ld minutes of inactivity",
454 				   actualtimeout.tv_sec / 60);
455 			exit(0);
456 		}
457 		ra_len = sizeof(recv_addr);
458 		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
459 					 (struct sockaddr *) &recv_addr, &ra_len);
460 		if (n <= 0) {
461 			continue;
462 		}
463 		if (debug > 3) {
464 			report(LOG_INFO, "recvd pkt from IP addr %s",
465 				   inet_ntoa(recv_addr.sin_addr));
466 		}
467 		if (n < sizeof(struct bootp)) {
468 			if (debug) {
469 				report(LOG_INFO, "received short packet");
470 			}
471 			continue;
472 		}
473 		pktlen = n;
474 
475 		switch (bp->bp_op) {
476 		case BOOTREQUEST:
477 			handle_request();
478 			break;
479 		case BOOTREPLY:
480 			handle_reply();
481 			break;
482 		}
483 	}
484 }
485 
486 
487 
488 
489 /*
490  * Print "usage" message and exit
491  */
492 
493 static void
494 usage()
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()
516 {
517 	struct bootp *bp = (struct bootp *) pktbuf;
518 	struct ifreq *ifr;
519 	u_short secs, 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 = ntohs(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 = htons(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 		/*
552 		 * XXX - This picks the wrong interface when the receive addr
553 		 * is the broadcast address.  There is no  portable way to
554 		 * find out which interface a broadcast was received on. -gwr
555 		 * (Thanks to <walker@zk3.dec.com> for finding this bug!)
556 		 */
557 		ifr = getif(s, &recv_addr.sin_addr);
558 		if (!ifr) {
559 			report(LOG_NOTICE, "no interface for request from %s",
560 				   inet_ntoa(recv_addr.sin_addr));
561 			return;
562 		}
563 		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
564 		bp->bp_giaddr = sip->sin_addr;
565 #else	/* BUG */
566 		/*
567 		 * XXX - Just set "giaddr" to our "official" IP address.
568 		 * RFC 1532 says giaddr MUST be set to the address of the
569 		 * interface on which the request was received.  Setting
570 		 * it to our "default" IP address is not strictly correct,
571 		 * but is good enough to allow the real BOOTP server to
572 		 * get the reply back here.  Then, before we forward the
573 		 * reply to the client, the giaddr field is corrected.
574 		 * (In case the client uses giaddr, which it should not.)
575 		 * See handle_reply()
576 		 */
577 		bp->bp_giaddr = my_ip_addr;
578 #endif	/* BUG */
579 
580 		/*
581 		 * XXX - DHCP says to insert a subnet mask option into the
582 		 * options area of the request (if vendor magic == std).
583 		 */
584 	}
585 	/* Set up socket address for send. */
586 	send_addr.sin_family = AF_INET;
587 	send_addr.sin_port = htons(bootps_port);
588 	send_addr.sin_addr.s_addr = server_ipa;
589 
590 	/* Send reply with same size packet as request used. */
591 	if (sendto(s, pktbuf, pktlen, 0,
592 			   (struct sockaddr *) &send_addr,
593 			   sizeof(send_addr)) < 0)
594 	{
595 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
596 	}
597 }
598 
599 
600 
601 /*
602  * Process BOOTREPLY packet.
603  */
604 static void
605 handle_reply()
606 {
607 	struct bootp *bp = (struct bootp *) pktbuf;
608 	struct ifreq *ifr;
609 	struct sockaddr_in *sip;
610 	u_char canon_haddr[MAXHADDRLEN];
611 	unsigned char *ha;
612 	int len;
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 	if (bp->bp_htype == HTYPE_IEEE802) {
648 		haddr_conv802(ha, canon_haddr, len);
649 		ha = canon_haddr;
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, 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