bootpgw.c revision 97418
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
11Permission to use, copy, modify, and distribute this software and its
12documentation for any purpose and without fee is hereby granted, provided
13that the above copyright notice appear in all copies and that both that
14copyright notice and this permission notice appear in supporting
15documentation, and that the name of Carnegie Mellon University not be used
16in advertising or publicity pertaining to distribution of the software
17without specific, written prior permission.
18
19CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
20SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
21IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
22DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
23PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
24ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
25SOFTWARE.
26************************************************************************/
27
28/* $FreeBSD: head/libexec/bootpd/bootpgw/bootpgw.c 97418 2002-05-28 18:37:43Z alfred $ */
29
30/*
31 * BOOTPGW is typically used to forward BOOTP client requests from
32 * one subnet to a BOOTP server on a different subnet.
33 */
34
35#include <sys/types.h>
36#include <sys/param.h>
37#include <sys/socket.h>
38#include <sys/ioctl.h>
39#include <sys/file.h>
40#include <sys/time.h>
41#include <sys/stat.h>
42#include <sys/utsname.h>
43
44#include <net/if.h>
45#include <netinet/in.h>
46#include <arpa/inet.h>	/* inet_ntoa */
47
48#ifndef	NO_UNISTD
49#include <unistd.h>
50#endif
51
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 <paths.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
93static void usage(void);
94static void handle_reply(void);
95static void handle_request(void);
96
97/*
98 * IP port numbers for client and server obtained from /etc/services
99 */
100
101u_short bootps_port, bootpc_port;
102
103
104/*
105 * Internet socket and interface config structures
106 */
107
108struct sockaddr_in bind_addr;	/* Listening */
109struct sockaddr_in recv_addr;	/* Packet source */
110struct sockaddr_in send_addr;	/*  destination */
111
112
113/*
114 * option defaults
115 */
116int debug = 0;					/* Debugging flag (level) */
117struct timeval actualtimeout =
118{								/* fifteen minutes */
119	15 * 60L,					/* tv_sec */
120	0							/* tv_usec */
121};
122u_char maxhops = 4;				/* Number of hops allowed for requests. */
123u_int minwait = 3;				/* Number of seconds client must wait before
124						   its bootrequest packets are forwarded. */
125
126/*
127 * General
128 */
129
130int s;							/* Socket file descriptor */
131char *pktbuf;					/* Receive packet buffer */
132int pktlen;
133char *progname;
134char *servername;
135int32 server_ipa;				/* Real server IP address, network order. */
136
137struct in_addr my_ip_addr;
138
139struct utsname my_uname;
140char *hostname;
141
142
143
144
145
146/*
147 * Initialization such as command-line processing is done and then the
148 * main server loop is started.
149 */
150
151int
152main(argc, argv)
153	int argc;
154	char **argv;
155{
156	struct timeval *timeout;
157	struct bootp *bp;
158	struct servent *servp;
159	struct hostent *hep;
160	char *stmp;
161	int n, ba_len, ra_len;
162	int nfound, readfds;
163	int standalone;
164
165	progname = strrchr(argv[0], '/');
166	if (progname) progname++;
167	else progname = argv[0];
168
169	/*
170	 * Initialize logging.
171	 */
172	report_init(0);				/* uses progname */
173
174	/*
175	 * Log startup
176	 */
177	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
178
179	/* Debugging for compilers with struct padding. */
180	assert(sizeof(struct bootp) == BP_MINPKTSZ);
181
182	/* Get space for receiving packets and composing replies. */
183	pktbuf = malloc(MAX_MSG_SIZE);
184	if (!pktbuf) {
185		report(LOG_ERR, "malloc failed");
186		exit(1);
187	}
188	bp = (struct bootp *) pktbuf;
189
190	/*
191	 * Check to see if a socket was passed to us from inetd.
192	 *
193	 * Use getsockname() to determine if descriptor 0 is indeed a socket
194	 * (and thus we are probably a child of inetd) or if it is instead
195	 * something else and we are running standalone.
196	 */
197	s = 0;
198	ba_len = sizeof(bind_addr);
199	bzero((char *) &bind_addr, ba_len);
200	errno = 0;
201	standalone = TRUE;
202	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
203		/*
204		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
205		 */
206		if (bind_addr.sin_family == AF_INET) {
207			standalone = FALSE;
208			bootps_port = ntohs(bind_addr.sin_port);
209		} else {
210			/* Some other type of socket? */
211			report(LOG_INFO, "getsockname: not an INET socket");
212		}
213	}
214	/*
215	 * Set defaults that might be changed by option switches.
216	 */
217	stmp = NULL;
218	timeout = &actualtimeout;
219
220	if (uname(&my_uname) < 0) {
221		fprintf(stderr, "bootpgw: can't get hostname\n");
222		exit(1);
223	}
224	hostname = my_uname.nodename;
225
226	hep = gethostbyname(hostname);
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_char)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 (isdigit(servername[0]))
349		server_ipa = inet_addr(servername);
350	else {
351		hep = gethostbyname(servername);
352		if (!hep) {
353			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
354			exit(1);
355		}
356		bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
357	}
358
359	if (standalone) {
360		/*
361		 * Go into background and disassociate from controlling terminal.
362		 * XXX - This is not the POSIX way (Should use setsid). -gwr
363		 */
364		if (debug < 3) {
365			if (fork())
366				exit(0);
367#ifdef	NO_SETSID
368			setpgrp(0,0);
369#ifdef TIOCNOTTY
370			n = open(_PATH_TTY, O_RDWR);
371			if (n >= 0) {
372				ioctl(n, TIOCNOTTY, (char *) 0);
373				(void) close(n);
374			}
375#endif	/* TIOCNOTTY */
376#else	/* SETSID */
377			if (setsid() < 0)
378				perror("setsid");
379#endif	/* SETSID */
380		} /* if debug < 3 */
381		/*
382		 * Nuke any timeout value
383		 */
384		timeout = NULL;
385
386		/*
387		 * Here, bootpd would do:
388		 *	chdir
389		 *	tzone_init
390		 *	rdtab_init
391		 *	readtab
392		 */
393
394		/*
395		 * Create a socket.
396		 */
397		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
398			report(LOG_ERR, "socket: %s", get_network_errmsg());
399			exit(1);
400		}
401		/*
402		 * Get server's listening port number
403		 */
404		servp = getservbyname("bootps", "udp");
405		if (servp) {
406			bootps_port = ntohs((u_short) servp->s_port);
407		} else {
408			bootps_port = (u_short) IPPORT_BOOTPS;
409			report(LOG_ERR,
410				   "udp/bootps: unknown service -- assuming port %d",
411				   bootps_port);
412		}
413
414		/*
415		 * Bind socket to BOOTPS port.
416		 */
417		bind_addr.sin_family = AF_INET;
418		bind_addr.sin_port = htons(bootps_port);
419		bind_addr.sin_addr.s_addr = INADDR_ANY;
420		if (bind(s, (struct sockaddr *) &bind_addr,
421				 sizeof(bind_addr)) < 0)
422		{
423			report(LOG_ERR, "bind: %s", get_network_errmsg());
424			exit(1);
425		}
426	} /* if standalone */
427	/*
428	 * Get destination port number so we can reply to client
429	 */
430	servp = getservbyname("bootpc", "udp");
431	if (servp) {
432		bootpc_port = ntohs(servp->s_port);
433	} else {
434		report(LOG_ERR,
435			   "udp/bootpc: unknown service -- assuming port %d",
436			   IPPORT_BOOTPC);
437		bootpc_port = (u_short) IPPORT_BOOTPC;
438	}
439
440	/* no signal catchers */
441
442	/*
443	 * Process incoming requests.
444	 */
445	for (;;) {
446		struct timeval tv;
447
448		readfds = 1 << s;
449		if (timeout)
450			tv = *timeout;
451
452		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,
453						(timeout) ? &tv : NULL);
454		if (nfound < 0) {
455			if (errno != EINTR) {
456				report(LOG_ERR, "select: %s", get_errmsg());
457			}
458			continue;
459		}
460		if (!(readfds & (1 << s))) {
461			report(LOG_INFO, "exiting after %ld minutes of inactivity",
462				   actualtimeout.tv_sec / 60);
463			exit(0);
464		}
465		ra_len = sizeof(recv_addr);
466		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
467					 (struct sockaddr *) &recv_addr, &ra_len);
468		if (n <= 0) {
469			continue;
470		}
471		if (debug > 3) {
472			report(LOG_INFO, "recvd pkt from IP addr %s",
473				   inet_ntoa(recv_addr.sin_addr));
474		}
475		if (n < sizeof(struct bootp)) {
476			if (debug) {
477				report(LOG_INFO, "received short packet");
478			}
479			continue;
480		}
481		pktlen = n;
482
483		switch (bp->bp_op) {
484		case BOOTREQUEST:
485			handle_request();
486			break;
487		case BOOTREPLY:
488			handle_reply();
489			break;
490		}
491	}
492	return 0;
493}
494
495
496
497
498/*
499 * Print "usage" message and exit
500 */
501
502static void
503usage()
504{
505	fprintf(stderr,
506			"usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
507	fprintf(stderr, "\t -d n\tset debug level\n");
508	fprintf(stderr, "\t -h n\tset max hop count\n");
509	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
510	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
511	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
512	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
513	exit(1);
514}
515
516
517
518/*
519 * Process BOOTREQUEST packet.
520 *
521 * Note, this just forwards the request to a real server.
522 */
523static void
524handle_request()
525{
526	struct bootp *bp = (struct bootp *) pktbuf;
527	u_short secs;
528        u_char hops;
529
530	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
531
532	if (debug) {
533		report(LOG_INFO, "request from %s",
534			   inet_ntoa(recv_addr.sin_addr));
535	}
536	/* Has the client been waiting long enough? */
537	secs = ntohs(bp->bp_secs);
538	if (secs < minwait)
539		return;
540
541	/* Has this packet hopped too many times? */
542	hops = bp->bp_hops;
543	if (++hops > maxhops) {
544		report(LOG_NOTICE, "reqest from %s reached hop limit",
545			   inet_ntoa(recv_addr.sin_addr));
546		return;
547	}
548	bp->bp_hops = hops;
549
550	/*
551	 * Here one might discard a request from the same subnet as the
552	 * real server, but we can assume that the real server will send
553	 * a reply to the client before it waits for minwait seconds.
554	 */
555
556	/* If gateway address is not set, put in local interface addr. */
557	if (bp->bp_giaddr.s_addr == 0) {
558#if 0	/* BUG */
559		struct sockaddr_in *sip;
560		struct ifreq *ifr;
561		/*
562		 * XXX - This picks the wrong interface when the receive addr
563		 * is the broadcast address.  There is no  portable way to
564		 * find out which interface a broadcast was received on. -gwr
565		 * (Thanks to <walker@zk3.dec.com> for finding this bug!)
566		 */
567		ifr = getif(s, &recv_addr.sin_addr);
568		if (!ifr) {
569			report(LOG_NOTICE, "no interface for request from %s",
570				   inet_ntoa(recv_addr.sin_addr));
571			return;
572		}
573		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
574		bp->bp_giaddr = sip->sin_addr;
575#else	/* BUG */
576		/*
577		 * XXX - Just set "giaddr" to our "official" IP address.
578		 * RFC 1532 says giaddr MUST be set to the address of the
579		 * interface on which the request was received.  Setting
580		 * it to our "default" IP address is not strictly correct,
581		 * but is good enough to allow the real BOOTP server to
582		 * get the reply back here.  Then, before we forward the
583		 * reply to the client, the giaddr field is corrected.
584		 * (In case the client uses giaddr, which it should not.)
585		 * See handle_reply()
586		 */
587		bp->bp_giaddr = my_ip_addr;
588#endif	/* BUG */
589
590		/*
591		 * XXX - DHCP says to insert a subnet mask option into the
592		 * options area of the request (if vendor magic == std).
593		 */
594	}
595	/* Set up socket address for send. */
596	send_addr.sin_family = AF_INET;
597	send_addr.sin_port = htons(bootps_port);
598	send_addr.sin_addr.s_addr = server_ipa;
599
600	/* Send reply with same size packet as request used. */
601	if (sendto(s, pktbuf, pktlen, 0,
602			   (struct sockaddr *) &send_addr,
603			   sizeof(send_addr)) < 0)
604	{
605		report(LOG_ERR, "sendto: %s", get_network_errmsg());
606	}
607}
608
609
610
611/*
612 * Process BOOTREPLY packet.
613 */
614static void
615handle_reply()
616{
617	struct bootp *bp = (struct bootp *) pktbuf;
618	struct ifreq *ifr;
619	struct sockaddr_in *sip;
620	unsigned char *ha;
621	int len, haf;
622
623	if (debug) {
624		report(LOG_INFO, "   reply for %s",
625			   inet_ntoa(bp->bp_yiaddr));
626	}
627	/* Make sure client is directly accessible. */
628	ifr = getif(s, &(bp->bp_yiaddr));
629	if (!ifr) {
630		report(LOG_NOTICE, "no interface for reply to %s",
631			   inet_ntoa(bp->bp_yiaddr));
632		return;
633	}
634#if 1	/* Experimental (see BUG above) */
635/* #ifdef CATER_TO_OLD_CLIENTS ? */
636	/*
637	 * The giaddr field has been set to our "default" IP address
638	 * which might not be on the same interface as the client.
639	 * In case the client looks at giaddr, (which it should not)
640	 * giaddr is now set to the address of the correct interface.
641	 */
642	sip = (struct sockaddr_in *) &(ifr->ifr_addr);
643	bp->bp_giaddr = sip->sin_addr;
644#endif
645
646	/* Set up socket address for send to client. */
647	send_addr.sin_family = AF_INET;
648	send_addr.sin_addr = bp->bp_yiaddr;
649	send_addr.sin_port = htons(bootpc_port);
650
651	/* Create an ARP cache entry for the client. */
652	ha = bp->bp_chaddr;
653	len = bp->bp_hlen;
654	if (len > MAXHADDRLEN)
655		len = MAXHADDRLEN;
656	haf = (int) bp->bp_htype;
657	if (haf == 0)
658		haf = HTYPE_ETHERNET;
659
660	if (debug > 1)
661		report(LOG_INFO, "setarp %s - %s",
662			   inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
663	setarp(s, &bp->bp_yiaddr, haf, ha, len);
664
665	/* Send reply with same size packet as request used. */
666	if (sendto(s, pktbuf, pktlen, 0,
667			   (struct sockaddr *) &send_addr,
668			   sizeof(send_addr)) < 0)
669	{
670		report(LOG_ERR, "sendto: %s", get_network_errmsg());
671	}
672}
673
674/*
675 * Local Variables:
676 * tab-width: 4
677 * c-indent-level: 4
678 * c-argdecl-indent: 4
679 * c-continued-statement-offset: 4
680 * c-continued-brace-offset: -4
681 * c-label-offset: -4
682 * c-brace-offset: 0
683 * End:
684 */
685