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