bootpgw.c revision 13572
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/*
29 * BOOTPGW is typically used to forward BOOTP client requests from
30 * one subnet to a BOOTP server on a different subnet.
31 */
32
33#include <sys/types.h>
34#include <sys/param.h>
35#include <sys/socket.h>
36#include <sys/ioctl.h>
37#include <sys/file.h>
38#include <sys/time.h>
39#include <sys/stat.h>
40#include <sys/utsname.h>
41
42#include <net/if.h>
43#include <netinet/in.h>
44#include <arpa/inet.h>	/* inet_ntoa */
45
46#ifndef	NO_UNISTD
47#include <unistd.h>
48#endif
49
50#include <stdlib.h>
51#include <signal.h>
52#include <stdio.h>
53#include <string.h>
54#include <errno.h>
55#include <ctype.h>
56#include <netdb.h>
57#include <syslog.h>
58#include <assert.h>
59
60#ifdef	NO_SETSID
61# include <fcntl.h>		/* for O_RDONLY, etc */
62#endif
63
64#ifndef	USE_BFUNCS
65# include <memory.h>
66/* Yes, memcpy is OK here (no overlapped copies). */
67# define bcopy(a,b,c)    memcpy(b,a,c)
68# define bzero(p,l)      memset(p,0,l)
69# define bcmp(a,b,c)     memcmp(a,b,c)
70#endif
71
72#include "bootp.h"
73#include "getif.h"
74#include "hwaddr.h"
75#include "report.h"
76#include "patchlevel.h"
77
78/* Local definitions: */
79#define MAX_MSG_SIZE			(3*512)	/* Maximum packet size */
80#define TRUE 1
81#define FALSE 0
82#define get_network_errmsg get_errmsg
83
84
85
86/*
87 * Externals, forward declarations, and global variables
88 */
89
90#ifdef	__STDC__
91#define P(args) args
92#else
93#define P(args) ()
94#endif
95
96static void usage P((void));
97static void handle_reply P((void));
98static void handle_request P((void));
99
100#undef	P
101
102/*
103 * IP port numbers for client and server obtained from /etc/services
104 */
105
106u_short bootps_port, bootpc_port;
107
108
109/*
110 * Internet socket and interface config structures
111 */
112
113struct sockaddr_in bind_addr;	/* Listening */
114struct sockaddr_in recv_addr;	/* Packet source */
115struct sockaddr_in send_addr;	/*  destination */
116
117
118/*
119 * option defaults
120 */
121int debug = 0;					/* Debugging flag (level) */
122struct timeval actualtimeout =
123{								/* fifteen minutes */
124	15 * 60L,					/* tv_sec */
125	0							/* tv_usec */
126};
127u_int maxhops = 4;				/* Number of hops allowed for requests. */
128u_int minwait = 3;				/* Number of seconds client must wait before
129						   its bootrequest packets are forwarded. */
130
131/*
132 * General
133 */
134
135int s;							/* Socket file descriptor */
136char *pktbuf;					/* Receive packet buffer */
137int pktlen;
138char *progname;
139char *servername;
140int32 server_ipa;				/* Real server IP address, network order. */
141
142struct in_addr my_ip_addr;
143
144struct utsname my_uname;
145char *hostname;
146
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
156void
157main(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
225	if (uname(&my_uname) < 0) {
226		fprintf(stderr, "bootpgw: can't get hostname\n");
227		exit(1);
228	}
229	hostname = my_uname.nodename;
230
231	hep = gethostbyname(hostname);
232	if (!hep) {
233		printf("Can not get my IP address\n");
234		exit(1);
235	}
236	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
237
238	/*
239	 * Read switches.
240	 */
241	for (argc--, argv++; argc > 0; argc--, argv++) {
242		if (argv[0][0] != '-')
243			break;
244		switch (argv[0][1]) {
245
246		case 'd':				/* debug level */
247			if (argv[0][2]) {
248				stmp = &(argv[0][2]);
249			} else if (argv[1] && argv[1][0] == '-') {
250				/*
251				 * Backwards-compatible behavior:
252				 * no parameter, so just increment the debug flag.
253				 */
254				debug++;
255				break;
256			} else {
257				argc--;
258				argv++;
259				stmp = argv[0];
260			}
261			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
262				fprintf(stderr,
263						"%s: invalid debug level\n", progname);
264				break;
265			}
266			debug = n;
267			break;
268
269		case 'h':				/* hop count limit */
270			if (argv[0][2]) {
271				stmp = &(argv[0][2]);
272			} else {
273				argc--;
274				argv++;
275				stmp = argv[0];
276			}
277			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
278				(n < 0) || (n > 16))
279			{
280				fprintf(stderr,
281						"bootpgw: invalid hop count limit\n");
282				break;
283			}
284			maxhops = (u_int)n;
285			break;
286
287		case 'i':				/* inetd mode */
288			standalone = FALSE;
289			break;
290
291		case 's':				/* standalone mode */
292			standalone = TRUE;
293			break;
294
295		case 't':				/* timeout */
296			if (argv[0][2]) {
297				stmp = &(argv[0][2]);
298			} else {
299				argc--;
300				argv++;
301				stmp = argv[0];
302			}
303			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
304				fprintf(stderr,
305						"%s: invalid timeout specification\n", progname);
306				break;
307			}
308			actualtimeout.tv_sec = (int32) (60 * n);
309			/*
310			 * If the actual timeout is zero, pass a NULL pointer
311			 * to select so it blocks indefinitely, otherwise,
312			 * point to the actual timeout value.
313			 */
314			timeout = (n > 0) ? &actualtimeout : NULL;
315			break;
316
317		case 'w':				/* wait time */
318			if (argv[0][2]) {
319				stmp = &(argv[0][2]);
320			} else {
321				argc--;
322				argv++;
323				stmp = argv[0];
324			}
325			if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
326				(n < 0) || (n > 60))
327			{
328				fprintf(stderr,
329						"bootpgw: invalid wait time\n");
330				break;
331			}
332			minwait = (u_int)n;
333			break;
334
335		default:
336			fprintf(stderr, "%s: unknown switch: -%c\n",
337					progname, argv[0][1]);
338			usage();
339			break;
340
341		} /* switch */
342	} /* for args */
343
344	/* Make sure server name argument is suplied. */
345	servername = argv[0];
346	if (!servername) {
347		fprintf(stderr, "bootpgw: missing server name\n");
348		usage();
349	}
350	/*
351	 * Get address of real bootp server.
352	 */
353	if (isdigit(servername[0]))
354		server_ipa = inet_addr(servername);
355	else {
356		hep = gethostbyname(servername);
357		if (!hep) {
358			fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
359			exit(1);
360		}
361		bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
362	}
363
364	if (standalone) {
365		/*
366		 * Go into background and disassociate from controlling terminal.
367		 * XXX - This is not the POSIX way (Should use setsid). -gwr
368		 */
369		if (debug < 3) {
370			if (fork())
371				exit(0);
372#ifdef	NO_SETSID
373			setpgrp(0,0);
374#ifdef TIOCNOTTY
375			n = open("/dev/tty", O_RDWR);
376			if (n >= 0) {
377				ioctl(n, TIOCNOTTY, (char *) 0);
378				(void) close(n);
379			}
380#endif	/* TIOCNOTTY */
381#else	/* SETSID */
382			if (setsid() < 0)
383				perror("setsid");
384#endif	/* SETSID */
385		} /* if debug < 3 */
386		/*
387		 * Nuke any timeout value
388		 */
389		timeout = NULL;
390
391		/*
392		 * Here, bootpd would do:
393		 *	chdir
394		 *	tzone_init
395		 *	rdtab_init
396		 *	readtab
397		 */
398
399		/*
400		 * Create a socket.
401		 */
402		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
403			report(LOG_ERR, "socket: %s", get_network_errmsg());
404			exit(1);
405		}
406		/*
407		 * Get server's listening port number
408		 */
409		servp = getservbyname("bootps", "udp");
410		if (servp) {
411			bootps_port = ntohs((u_short) servp->s_port);
412		} else {
413			bootps_port = (u_short) IPPORT_BOOTPS;
414			report(LOG_ERR,
415				   "udp/bootps: unknown service -- assuming port %d",
416				   bootps_port);
417		}
418
419		/*
420		 * Bind socket to BOOTPS port.
421		 */
422		bind_addr.sin_family = AF_INET;
423		bind_addr.sin_port = htons(bootps_port);
424		bind_addr.sin_addr.s_addr = INADDR_ANY;
425		if (bind(s, (struct sockaddr *) &bind_addr,
426				 sizeof(bind_addr)) < 0)
427		{
428			report(LOG_ERR, "bind: %s", get_network_errmsg());
429			exit(1);
430		}
431	} /* if standalone */
432	/*
433	 * Get destination port number so we can reply to client
434	 */
435	servp = getservbyname("bootpc", "udp");
436	if (servp) {
437		bootpc_port = ntohs(servp->s_port);
438	} else {
439		report(LOG_ERR,
440			   "udp/bootpc: unknown service -- assuming port %d",
441			   IPPORT_BOOTPC);
442		bootpc_port = (u_short) IPPORT_BOOTPC;
443	}
444
445	/* no signal catchers */
446
447	/*
448	 * Process incoming requests.
449	 */
450	for (;;) {
451		struct timeval tv;
452
453		readfds = 1 << s;
454		if (timeout)
455			tv = *timeout;
456
457		nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,
458						(timeout) ? &tv : NULL);
459		if (nfound < 0) {
460			if (errno != EINTR) {
461				report(LOG_ERR, "select: %s", get_errmsg());
462			}
463			continue;
464		}
465		if (!(readfds & (1 << s))) {
466			report(LOG_INFO, "exiting after %ld minutes of inactivity",
467				   actualtimeout.tv_sec / 60);
468			exit(0);
469		}
470		ra_len = sizeof(recv_addr);
471		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
472					 (struct sockaddr *) &recv_addr, &ra_len);
473		if (n <= 0) {
474			continue;
475		}
476		if (debug > 3) {
477			report(LOG_INFO, "recvd pkt from IP addr %s",
478				   inet_ntoa(recv_addr.sin_addr));
479		}
480		if (n < sizeof(struct bootp)) {
481			if (debug) {
482				report(LOG_INFO, "received short packet");
483			}
484			continue;
485		}
486		pktlen = n;
487
488		switch (bp->bp_op) {
489		case BOOTREQUEST:
490			handle_request();
491			break;
492		case BOOTREPLY:
493			handle_reply();
494			break;
495		}
496	}
497}
498
499
500
501
502/*
503 * Print "usage" message and exit
504 */
505
506static void
507usage()
508{
509	fprintf(stderr,
510			"usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
511	fprintf(stderr, "\t -d n\tset debug level\n");
512	fprintf(stderr, "\t -h n\tset max hop count\n");
513	fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
514	fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
515	fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
516	fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
517	exit(1);
518}
519
520
521
522/*
523 * Process BOOTREQUEST packet.
524 *
525 * Note, this just forwards the request to a real server.
526 */
527static void
528handle_request()
529{
530	struct bootp *bp = (struct bootp *) pktbuf;
531	u_short secs, hops;
532
533	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
534
535	if (debug) {
536		report(LOG_INFO, "request from %s",
537			   inet_ntoa(recv_addr.sin_addr));
538	}
539	/* Has the client been waiting long enough? */
540	secs = ntohs(bp->bp_secs);
541	if (secs < minwait)
542		return;
543
544	/* Has this packet hopped too many times? */
545	hops = ntohs(bp->bp_hops);
546	if (++hops > maxhops) {
547		report(LOG_NOTICE, "reqest from %s reached hop limit",
548			   inet_ntoa(recv_addr.sin_addr));
549		return;
550	}
551	bp->bp_hops = htons(hops);
552
553	/*
554	 * Here one might discard a request from the same subnet as the
555	 * real server, but we can assume that the real server will send
556	 * a reply to the client before it waits for minwait seconds.
557	 */
558
559	/* If gateway address is not set, put in local interface addr. */
560	if (bp->bp_giaddr.s_addr == 0) {
561#if 0	/* BUG */
562		struct sockaddr_in *sip;
563		struct ifreq *ifr;
564		/*
565		 * XXX - This picks the wrong interface when the receive addr
566		 * is the broadcast address.  There is no  portable way to
567		 * find out which interface a broadcast was received on. -gwr
568		 * (Thanks to <walker@zk3.dec.com> for finding this bug!)
569		 */
570		ifr = getif(s, &recv_addr.sin_addr);
571		if (!ifr) {
572			report(LOG_NOTICE, "no interface for request from %s",
573				   inet_ntoa(recv_addr.sin_addr));
574			return;
575		}
576		sip = (struct sockaddr_in *) &(ifr->ifr_addr);
577		bp->bp_giaddr = sip->sin_addr;
578#else	/* BUG */
579		/*
580		 * XXX - Just set "giaddr" to our "official" IP address.
581		 * RFC 1532 says giaddr MUST be set to the address of the
582		 * interface on which the request was received.  Setting
583		 * it to our "default" IP address is not strictly correct,
584		 * but is good enough to allow the real BOOTP server to
585		 * get the reply back here.  Then, before we forward the
586		 * reply to the client, the giaddr field is corrected.
587		 * (In case the client uses giaddr, which it should not.)
588		 * See handle_reply()
589		 */
590		bp->bp_giaddr = my_ip_addr;
591#endif	/* BUG */
592
593		/*
594		 * XXX - DHCP says to insert a subnet mask option into the
595		 * options area of the request (if vendor magic == std).
596		 */
597	}
598	/* Set up socket address for send. */
599	send_addr.sin_family = AF_INET;
600	send_addr.sin_port = htons(bootps_port);
601	send_addr.sin_addr.s_addr = server_ipa;
602
603	/* Send reply with same size packet as request used. */
604	if (sendto(s, pktbuf, pktlen, 0,
605			   (struct sockaddr *) &send_addr,
606			   sizeof(send_addr)) < 0)
607	{
608		report(LOG_ERR, "sendto: %s", get_network_errmsg());
609	}
610}
611
612
613
614/*
615 * Process BOOTREPLY packet.
616 */
617static void
618handle_reply()
619{
620	struct bootp *bp = (struct bootp *) pktbuf;
621	struct ifreq *ifr;
622	struct sockaddr_in *sip;
623	unsigned char *ha;
624	int len, haf;
625
626	if (debug) {
627		report(LOG_INFO, "   reply for %s",
628			   inet_ntoa(bp->bp_yiaddr));
629	}
630	/* Make sure client is directly accessible. */
631	ifr = getif(s, &(bp->bp_yiaddr));
632	if (!ifr) {
633		report(LOG_NOTICE, "no interface for reply to %s",
634			   inet_ntoa(bp->bp_yiaddr));
635		return;
636	}
637#if 1	/* Experimental (see BUG above) */
638/* #ifdef CATER_TO_OLD_CLIENTS ? */
639	/*
640	 * The giaddr field has been set to our "default" IP address
641	 * which might not be on the same interface as the client.
642	 * In case the client looks at giaddr, (which it should not)
643	 * giaddr is now set to the address of the correct interface.
644	 */
645	sip = (struct sockaddr_in *) &(ifr->ifr_addr);
646	bp->bp_giaddr = sip->sin_addr;
647#endif
648
649	/* Set up socket address for send to client. */
650	send_addr.sin_family = AF_INET;
651	send_addr.sin_addr = bp->bp_yiaddr;
652	send_addr.sin_port = htons(bootpc_port);
653
654	/* Create an ARP cache entry for the client. */
655	ha = bp->bp_chaddr;
656	len = bp->bp_hlen;
657	if (len > MAXHADDRLEN)
658		len = MAXHADDRLEN;
659	haf = (int) bp->bp_htype;
660	if (haf == 0)
661		haf = HTYPE_ETHERNET;
662
663	if (debug > 1)
664		report(LOG_INFO, "setarp %s - %s",
665			   inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
666	setarp(s, &bp->bp_yiaddr, haf, ha, len);
667
668	/* Send reply with same size packet as request used. */
669	if (sendto(s, pktbuf, pktlen, 0,
670			   (struct sockaddr *) &send_addr,
671			   sizeof(send_addr)) < 0)
672	{
673		report(LOG_ERR, "sendto: %s", get_network_errmsg());
674	}
675}
676
677/*
678 * Local Variables:
679 * tab-width: 4
680 * c-indent-level: 4
681 * c-argdecl-indent: 4
682 * c-continued-statement-offset: 4
683 * c-continued-brace-offset: -4
684 * c-label-offset: -4
685 * c-brace-offset: 0
686 * End:
687 */
688