bootpgw.c revision 1.7 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.7 1998/03/14 04:39:53 lukem 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 #ifdef __STDC__
95 #define P(args) args
96 #else
97 #define P(args) ()
98 #endif
99
100 static void usage P((void));
101 static void handle_reply P((void));
102 static void handle_request P((void));
103 int main P((int, char **));
104
105 #undef P
106
107 /*
108 * IP port numbers for client and server obtained from /etc/services
109 */
110
111 u_short bootps_port, bootpc_port;
112
113
114 /*
115 * Internet socket and interface config structures
116 */
117
118 struct sockaddr_in bind_addr; /* Listening */
119 struct sockaddr_in clnt_addr; /* client address */
120 struct sockaddr_in serv_addr; /* server address */
121
122
123 /*
124 * option defaults
125 */
126 int debug = 0; /* Debugging flag (level) */
127 struct timeval actualtimeout =
128 { /* fifteen minutes */
129 15 * 60L, /* tv_sec */
130 0 /* tv_usec */
131 };
132 u_int maxhops = 4; /* Number of hops allowed for requests. */
133 u_int minwait = 3; /* Number of seconds client must wait before
134 its bootrequest packets are forwarded. */
135
136 /*
137 * General
138 */
139
140 int s; /* Socket file descriptor */
141 char *pktbuf; /* Receive packet buffer */
142 int pktlen;
143 char *progname;
144 char *servername;
145
146 char myhostname[64];
147 struct in_addr my_ip_addr;
148
149
151
152
153 /*
154 * Initialization such as command-line processing is done and then the
155 * main server loop is started.
156 */
157
158 int
159 main(argc, argv)
160 int argc;
161 char **argv;
162 {
163 struct timeval *timeout;
164 struct bootp *bp;
165 struct servent *servp;
166 struct hostent *hep;
167 char *stmp;
168 int n, ba_len, ra_len;
169 int nfound, readfds;
170 int standalone;
171
172 progname = strrchr(argv[0], '/');
173 if (progname) progname++;
174 else progname = argv[0];
175
176 /*
177 * Initialize logging.
178 */
179 report_init(0); /* uses progname */
180
181 /*
182 * Log startup
183 */
184 report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
185
186 /* Debugging for compilers with struct padding. */
187 assert(sizeof(struct bootp) == BP_MINPKTSZ);
188
189 /* Get space for receiving packets and composing replies. */
190 pktbuf = malloc(MAX_MSG_SIZE);
191 if (!pktbuf) {
192 report(LOG_ERR, "malloc failed");
193 exit(1);
194 }
195 bp = (struct bootp *) pktbuf;
196
197 /*
198 * Check to see if a socket was passed to us from inetd.
199 *
200 * Use getsockname() to determine if descriptor 0 is indeed a socket
201 * (and thus we are probably a child of inetd) or if it is instead
202 * something else and we are running standalone.
203 */
204 s = 0;
205 ba_len = sizeof(bind_addr);
206 bzero((char *) &bind_addr, ba_len);
207 errno = 0;
208 standalone = TRUE;
209 if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
210 /*
211 * Descriptor 0 is a socket. Assume we are a child of inetd.
212 */
213 if (bind_addr.sin_family == AF_INET) {
214 standalone = FALSE;
215 bootps_port = ntohs(bind_addr.sin_port);
216 } else {
217 /* Some other type of socket? */
218 report(LOG_INFO, "getsockname: not an INET socket");
219 }
220 }
221 /*
222 * Set defaults that might be changed by option switches.
223 */
224 stmp = NULL;
225 timeout = &actualtimeout;
226 gethostname(myhostname, sizeof(myhostname));
227 hep = gethostbyname(myhostname);
228 if (!hep) {
229 printf("Can not get my IP address\n");
230 exit(1);
231 }
232 bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
233
234 /*
235 * Read switches.
236 */
237 for (argc--, argv++; argc > 0; argc--, argv++) {
238 if (argv[0][0] != '-')
239 break;
240 switch (argv[0][1]) {
241
242 case 'd': /* debug level */
243 if (argv[0][2]) {
244 stmp = &(argv[0][2]);
245 } else if (argv[1] && argv[1][0] == '-') {
246 /*
247 * Backwards-compatible behavior:
248 * no parameter, so just increment the debug flag.
249 */
250 debug++;
251 break;
252 } else {
253 argc--;
254 argv++;
255 stmp = argv[0];
256 }
257 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
258 fprintf(stderr,
259 "%s: invalid debug level\n", progname);
260 break;
261 }
262 debug = n;
263 break;
264
265 case 'h': /* hop count limit */
266 if (argv[0][2]) {
267 stmp = &(argv[0][2]);
268 } else {
269 argc--;
270 argv++;
271 stmp = argv[0];
272 }
273 if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
274 (n < 0) || (n > 16))
275 {
276 fprintf(stderr,
277 "bootpgw: invalid hop count limit\n");
278 break;
279 }
280 maxhops = (u_int)n;
281 break;
282
283 case 'i': /* inetd mode */
284 standalone = FALSE;
285 break;
286
287 case 's': /* standalone mode */
288 standalone = TRUE;
289 break;
290
291 case 't': /* timeout */
292 if (argv[0][2]) {
293 stmp = &(argv[0][2]);
294 } else {
295 argc--;
296 argv++;
297 stmp = argv[0];
298 }
299 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
300 fprintf(stderr,
301 "%s: invalid timeout specification\n", progname);
302 break;
303 }
304 actualtimeout.tv_sec = (int32) (60 * n);
305 /*
306 * If the actual timeout is zero, pass a NULL pointer
307 * to select so it blocks indefinitely, otherwise,
308 * point to the actual timeout value.
309 */
310 timeout = (n > 0) ? &actualtimeout : NULL;
311 break;
312
313 case 'w': /* wait time */
314 if (argv[0][2]) {
315 stmp = &(argv[0][2]);
316 } else {
317 argc--;
318 argv++;
319 stmp = argv[0];
320 }
321 if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
322 (n < 0) || (n > 60))
323 {
324 fprintf(stderr,
325 "bootpgw: invalid wait time\n");
326 break;
327 }
328 minwait = (u_int)n;
329 break;
330
331 default:
332 fprintf(stderr, "%s: unknown switch: -%c\n",
333 progname, argv[0][1]);
334 usage();
335 break;
336
337 } /* switch */
338 } /* for args */
339
340 /* Make sure server name argument is suplied. */
341 servername = argv[0];
342 if (!servername) {
343 fprintf(stderr, "bootpgw: missing server name\n");
344 usage();
345 }
346 /*
347 * Get address of real bootp server.
348 */
349 if (inet_aton(servername, &serv_addr.sin_addr) == 0) {
350 hep = gethostbyname(servername);
351 if (!hep) {
352 fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
353 exit(1);
354 }
355 memcpy(&serv_addr.sin_addr, hep->h_addr,
356 sizeof(serv_addr.sin_addr));
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("/dev/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 readfds = 1 << s;
447 nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout);
448 if (nfound < 0) {
449 if (errno != EINTR) {
450 report(LOG_ERR, "select: %s", get_errmsg());
451 }
452 continue;
453 }
454 if (!(readfds & (1 << s))) {
455 report(LOG_INFO, "exiting after %ld minutes of inactivity",
456 actualtimeout.tv_sec / 60);
457 exit(0);
458 }
459 ra_len = sizeof(clnt_addr);
460 n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
461 (struct sockaddr *) &clnt_addr, &ra_len);
462 if (n <= 0) {
463 continue;
464 }
465 if (debug > 3) {
466 report(LOG_INFO, "recvd pkt from IP addr %s",
467 inet_ntoa(clnt_addr.sin_addr));
468 }
469 if (n < sizeof(struct bootp)) {
470 if (debug) {
471 report(LOG_INFO, "received short packet");
472 }
473 continue;
474 }
475 pktlen = n;
476
477 switch (bp->bp_op) {
478 case BOOTREQUEST:
479 handle_request();
480 break;
481 case BOOTREPLY:
482 handle_reply();
483 break;
484 }
485 }
486 }
487
488
490
491
492 /*
493 * Print "usage" message and exit
494 */
495
496 static void
497 usage()
498 {
499 fprintf(stderr,
500 "usage: bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
501 fprintf(stderr, "\t -d n\tset debug level\n");
502 fprintf(stderr, "\t -h n\tset max hop count\n");
503 fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
504 fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
505 fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
506 fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
507 exit(1);
508 }
509
510
512
513 /*
514 * Process BOOTREQUEST packet.
515 *
516 * Note, this just forwards the request to a real server.
517 */
518 static void
519 handle_request()
520 {
521 struct bootp *bp = (struct bootp *) pktbuf;
522 #if 0
523 struct ifreq *ifr;
524 #endif
525 u_short secs, hops;
526
527 /* XXX - SLIP init: Set bp_ciaddr = clnt_addr here? */
528
529 if (debug) {
530 report(LOG_INFO, "request from %s",
531 inet_ntoa(clnt_addr.sin_addr));
532 }
533 /* Has the client been waiting long enough? */
534 secs = ntohs(bp->bp_secs);
535 if (secs < minwait)
536 return;
537
538 /* Has this packet hopped too many times? */
539 hops = ntohs(bp->bp_hops);
540 if (++hops > maxhops) {
541 report(LOG_NOTICE, "request from %s reached hop limit",
542 inet_ntoa(clnt_addr.sin_addr));
543 return;
544 }
545 bp->bp_hops = htons(hops);
546
547 /*
548 * Here one might discard a request from the same subnet as the
549 * real server, but we can assume that the real server will send
550 * a reply to the client before it waits for minwait seconds.
551 */
552
553 /* If gateway address is not set, put in local interface addr. */
554 if (bp->bp_giaddr.s_addr == 0) {
555 #if 0 /* BUG */
556 struct sockaddr_in *sip;
557 /*
558 * XXX - This picks the wrong interface when the receive addr
559 * is the broadcast address. There is no portable way to
560 * find out which interface a broadcast was received on. -gwr
561 * (Thanks to <walker (at) zk3.dec.com> for finding this bug!)
562 */
563 ifr = getif(s, &clnt_addr.sin_addr);
564 if (!ifr) {
565 report(LOG_NOTICE, "no interface for request from %s",
566 inet_ntoa(clnt_addr.sin_addr));
567 return;
568 }
569 sip = (struct sockaddr_in *) &(ifr->ifr_addr);
570 bp->bp_giaddr = sip->sin_addr;
571 #else /* BUG */
572 /*
573 * XXX - Just set "giaddr" to our "official" IP address.
574 * RFC 1532 says giaddr MUST be set to the address of the
575 * interface on which the request was received. Setting
576 * it to our "default" IP address is not strictly correct,
577 * but is good enough to allow the real BOOTP server to
578 * get the reply back here. Then, before we forward the
579 * reply to the client, the giaddr field is corrected.
580 * (In case the client uses giaddr, which it should not.)
581 * See handle_reply()
582 */
583 bp->bp_giaddr = my_ip_addr;
584 #endif /* BUG */
585
586 /*
587 * XXX - DHCP says to insert a subnet mask option into the
588 * options area of the request (if vendor magic == std).
589 */
590 }
591 /* Set up socket address for send. */
592 serv_addr.sin_family = AF_INET;
593 serv_addr.sin_port = htons(bootps_port);
594
595 /* Send reply with same size packet as request used. */
596 if (sendto(s, pktbuf, pktlen, 0,
597 (struct sockaddr *) &serv_addr,
598 sizeof(serv_addr)) < 0)
599 {
600 report(LOG_ERR, "sendto: %s", get_network_errmsg());
601 }
602 }
603
604
606
607 /*
608 * Process BOOTREPLY packet.
609 */
610 static void
611 handle_reply()
612 {
613 struct bootp *bp = (struct bootp *) pktbuf;
614 struct ifreq *ifr;
615 struct sockaddr_in *sip;
616 u_char canon_haddr[MAXHADDRLEN];
617 unsigned char *ha;
618 int len;
619
620 if (debug) {
621 report(LOG_INFO, " reply for %s",
622 inet_ntoa(bp->bp_yiaddr));
623 }
624 /* Make sure client is directly accessible. */
625 ifr = getif(s, &(bp->bp_yiaddr));
626 if (!ifr) {
627 report(LOG_NOTICE, "no interface for reply to %s",
628 inet_ntoa(bp->bp_yiaddr));
629 return;
630 }
631 #if 1 /* Experimental (see BUG above) */
632 /* #ifdef CATER_TO_OLD_CLIENTS ? */
633 /*
634 * The giaddr field has been set to our "default" IP address
635 * which might not be on the same interface as the client.
636 * In case the client looks at giaddr, (which it should not)
637 * giaddr is now set to the address of the correct interface.
638 */
639 sip = (struct sockaddr_in *) &(ifr->ifr_addr);
640 bp->bp_giaddr = sip->sin_addr;
641 #endif
642
643 /* Set up socket address for send to client. */
644 clnt_addr.sin_family = AF_INET;
645 clnt_addr.sin_addr = bp->bp_yiaddr;
646 clnt_addr.sin_port = htons(bootpc_port);
647
648 /* Create an ARP cache entry for the client. */
649 ha = bp->bp_chaddr;
650 len = bp->bp_hlen;
651 if (len > MAXHADDRLEN)
652 len = MAXHADDRLEN;
653 if (bp->bp_htype == HTYPE_IEEE802) {
654 haddr_conv802(ha, canon_haddr, len);
655 ha = canon_haddr;
656 }
657 if (debug > 1)
658 report(LOG_INFO, "setarp %s - %s",
659 inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
660 setarp(s, &bp->bp_yiaddr, ha, len);
661
662 /* Send reply with same size packet as request used. */
663 if (sendto(s, pktbuf, pktlen, 0,
664 (struct sockaddr *) &clnt_addr,
665 sizeof(clnt_addr)) < 0)
666 {
667 report(LOG_ERR, "sendto: %s", get_network_errmsg());
668 }
669 }
670
671 /*
672 * Local Variables:
673 * tab-width: 4
674 * c-indent-level: 4
675 * c-argdecl-indent: 4
676 * c-continued-statement-offset: 4
677 * c-continued-brace-offset: -4
678 * c-label-offset: -4
679 * c-brace-offset: 0
680 * End:
681 */
682