xref: /netbsd/usr.sbin/bootp/bootpgw/bootpgw.c (revision c4a72b64)
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.11 2002/09/18 23:16:14 mycroft 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 	int n, ba_len, ra_len;
154 	int nfound;
155 	struct pollfd set[1];
156 	int standalone;
157 
158 	progname = strrchr(argv[0], '/');
159 	if (progname) progname++;
160 	else progname = argv[0];
161 
162 	/*
163 	 * Initialize logging.
164 	 */
165 	report_init(0);				/* uses progname */
166 
167 	/*
168 	 * Log startup
169 	 */
170 	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
171 
172 	/* Debugging for compilers with struct padding. */
173 	assert(sizeof(struct bootp) == BP_MINPKTSZ);
174 
175 	/* Get space for receiving packets and composing replies. */
176 	pktbuf = malloc(MAX_MSG_SIZE);
177 	if (!pktbuf) {
178 		report(LOG_ERR, "malloc failed");
179 		exit(1);
180 	}
181 	bp = (struct bootp *) pktbuf;
182 
183 	/*
184 	 * Check to see if a socket was passed to us from inetd.
185 	 *
186 	 * Use getsockname() to determine if descriptor 0 is indeed a socket
187 	 * (and thus we are probably a child of inetd) or if it is instead
188 	 * something else and we are running standalone.
189 	 */
190 	s = 0;
191 	ba_len = sizeof(bind_addr);
192 	bzero((char *) &bind_addr, ba_len);
193 	errno = 0;
194 	standalone = TRUE;
195 	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
196 		/*
197 		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
198 		 */
199 		if (bind_addr.sin_family == AF_INET) {
200 			standalone = FALSE;
201 			bootps_port = ntohs(bind_addr.sin_port);
202 		} else {
203 			/* Some other type of socket? */
204 			report(LOG_INFO, "getsockname: not an INET socket");
205 		}
206 	}
207 	/*
208 	 * Set defaults that might be changed by option switches.
209 	 */
210 	stmp = NULL;
211 	timeout = actualtimeout;
212 	gethostname(myhostname, sizeof(myhostname));
213 	myhostname[sizeof(myhostname) - 1] = '\0';
214 	hep = gethostbyname(myhostname);
215 	if (!hep) {
216 		printf("Can not get my IP address\n");
217 		exit(1);
218 	}
219 	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
220 
221 	/*
222 	 * Read switches.
223 	 */
224 	for (argc--, argv++; argc > 0; argc--, argv++) {
225 		if (argv[0][0] != '-')
226 			break;
227 		switch (argv[0][1]) {
228 
229 		case 'd':				/* debug level */
230 			if (argv[0][2]) {
231 				stmp = &(argv[0][2]);
232 			} else if (argv[1] && argv[1][0] == '-') {
233 				/*
234 				 * Backwards-compatible behavior:
235 				 * no parameter, so just increment the debug flag.
236 				 */
237 				debug++;
238 				break;
239 			} else {
240 				argc--;
241 				argv++;
242 				stmp = argv[0];
243 			}
244 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
245 				fprintf(stderr,
246 						"%s: invalid debug level\n", progname);
247 				break;
248 			}
249 			debug = n;
250 			break;
251 
252 		case 'h':				/* hop count limit */
253 			if (argv[0][2]) {
254 				stmp = &(argv[0][2]);
255 			} else {
256 				argc--;
257 				argv++;
258 				stmp = argv[0];
259 			}
260 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
261 				(n < 0) || (n > 16))
262 			{
263 				fprintf(stderr,
264 						"bootpgw: invalid hop count limit\n");
265 				break;
266 			}
267 			maxhops = (u_int)n;
268 			break;
269 
270 		case 'i':				/* inetd mode */
271 			standalone = FALSE;
272 			break;
273 
274 		case 's':				/* standalone mode */
275 			standalone = TRUE;
276 			break;
277 
278 		case 't':				/* timeout */
279 			if (argv[0][2]) {
280 				stmp = &(argv[0][2]);
281 			} else {
282 				argc--;
283 				argv++;
284 				stmp = argv[0];
285 			}
286 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
287 				fprintf(stderr,
288 						"%s: invalid timeout specification\n", progname);
289 				break;
290 			}
291 			actualtimeout = n * 60000;
292 			/*
293 			 * If the actual timeout is zero, pass INFTIM
294 			 * to poll so it blocks indefinitely, otherwise,
295 			 * use the actual timeout value.
296 			 */
297 			timeout = (n > 0) ? actualtimeout : INFTIM;
298 			break;
299 
300 		case 'w':				/* wait time */
301 			if (argv[0][2]) {
302 				stmp = &(argv[0][2]);
303 			} else {
304 				argc--;
305 				argv++;
306 				stmp = argv[0];
307 			}
308 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
309 				(n < 0) || (n > 60))
310 			{
311 				fprintf(stderr,
312 						"bootpgw: invalid wait time\n");
313 				break;
314 			}
315 			minwait = (u_int)n;
316 			break;
317 
318 		default:
319 			fprintf(stderr, "%s: unknown switch: -%c\n",
320 					progname, argv[0][1]);
321 			usage();
322 			break;
323 
324 		} /* switch */
325 	} /* for args */
326 
327 	/* Make sure server name argument is suplied. */
328 	servername = argv[0];
329 	if (!servername) {
330 		fprintf(stderr, "bootpgw: missing server name\n");
331 		usage();
332 	}
333 	/*
334 	 * Get address of real bootp server.
335 	 */
336 	if (inet_aton(servername, &serv_addr.sin_addr) == 0) {
337 		hep = gethostbyname(servername);
338 		if (!hep) {
339 			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
340 			exit(1);
341 		}
342 		memcpy(&serv_addr.sin_addr, hep->h_addr,
343 		    sizeof(serv_addr.sin_addr));
344 	}
345 
346 	if (standalone) {
347 		/*
348 		 * Go into background and disassociate from controlling terminal.
349 		 * XXX - This is not the POSIX way (Should use setsid). -gwr
350 		 */
351 		if (debug < 3) {
352 			if (fork())
353 				exit(0);
354 #ifdef	NO_SETSID
355 			setpgrp(0,0);
356 #ifdef TIOCNOTTY
357 			n = open("/dev/tty", O_RDWR);
358 			if (n >= 0) {
359 				ioctl(n, TIOCNOTTY, (char *) 0);
360 				(void) close(n);
361 			}
362 #endif	/* TIOCNOTTY */
363 #else	/* SETSID */
364 			if (setsid() < 0)
365 				perror("setsid");
366 #endif	/* SETSID */
367 		} /* if debug < 3 */
368 		/*
369 		 * Nuke any timeout value
370 		 */
371 		timeout = INFTIM;
372 
373 		/*
374 		 * Here, bootpd would do:
375 		 *	chdir
376 		 *	tzone_init
377 		 *	rdtab_init
378 		 *	readtab
379 		 */
380 
381 		/*
382 		 * Create a socket.
383 		 */
384 		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
385 			report(LOG_ERR, "socket: %s", get_network_errmsg());
386 			exit(1);
387 		}
388 		/*
389 		 * Get server's listening port number
390 		 */
391 		servp = getservbyname("bootps", "udp");
392 		if (servp) {
393 			bootps_port = ntohs((u_short) servp->s_port);
394 		} else {
395 			bootps_port = (u_short) IPPORT_BOOTPS;
396 			report(LOG_ERR,
397 				   "udp/bootps: unknown service -- assuming port %d",
398 				   bootps_port);
399 		}
400 
401 		/*
402 		 * Bind socket to BOOTPS port.
403 		 */
404 		bind_addr.sin_family = AF_INET;
405 		bind_addr.sin_port = htons(bootps_port);
406 		bind_addr.sin_addr.s_addr = INADDR_ANY;
407 		if (bind(s, (struct sockaddr *) &bind_addr,
408 				 sizeof(bind_addr)) < 0)
409 		{
410 			report(LOG_ERR, "bind: %s", get_network_errmsg());
411 			exit(1);
412 		}
413 	} /* if standalone */
414 	/*
415 	 * Get destination port number so we can reply to client
416 	 */
417 	servp = getservbyname("bootpc", "udp");
418 	if (servp) {
419 		bootpc_port = ntohs(servp->s_port);
420 	} else {
421 		report(LOG_ERR,
422 			   "udp/bootpc: unknown service -- assuming port %d",
423 			   IPPORT_BOOTPC);
424 		bootpc_port = (u_short) IPPORT_BOOTPC;
425 	}
426 
427 	/* no signal catchers */
428 
429 	/*
430 	 * Process incoming requests.
431 	 */
432 	set[0].fd = s;
433 	set[0].events = POLLIN;
434 	for (;;) {
435 		nfound = poll(set, 1, timeout);
436 		if (nfound < 0) {
437 			if (errno != EINTR) {
438 				report(LOG_ERR, "poll: %s", get_errmsg());
439 			}
440 			continue;
441 		}
442 		if (nfound == 0) {
443 			report(LOG_INFO, "exiting after %d minute%s of inactivity",
444 				   actualtimeout / 60000,
445 				   actualtimeout == 60000 ? "" : "s");
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