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