bootpgw.c revision 1.8 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.8 1998/07/06 07:02:17 mrg 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[MAXHOSTNAMELEN + 1];
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 myhostname[sizeof(myhostname) - 1] = '\0';
228 hep = gethostbyname(myhostname);
229 if (!hep) {
230 printf("Can not get my IP address\n");
231 exit(1);
232 }
233 bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
234
235 /*
236 * Read switches.
237 */
238 for (argc--, argv++; argc > 0; argc--, argv++) {
239 if (argv[0][0] != '-')
240 break;
241 switch (argv[0][1]) {
242
243 case 'd': /* debug level */
244 if (argv[0][2]) {
245 stmp = &(argv[0][2]);
246 } else if (argv[1] && argv[1][0] == '-') {
247 /*
248 * Backwards-compatible behavior:
249 * no parameter, so just increment the debug flag.
250 */
251 debug++;
252 break;
253 } else {
254 argc--;
255 argv++;
256 stmp = argv[0];
257 }
258 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
259 fprintf(stderr,
260 "%s: invalid debug level\n", progname);
261 break;
262 }
263 debug = n;
264 break;
265
266 case 'h': /* hop count limit */
267 if (argv[0][2]) {
268 stmp = &(argv[0][2]);
269 } else {
270 argc--;
271 argv++;
272 stmp = argv[0];
273 }
274 if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
275 (n < 0) || (n > 16))
276 {
277 fprintf(stderr,
278 "bootpgw: invalid hop count limit\n");
279 break;
280 }
281 maxhops = (u_int)n;
282 break;
283
284 case 'i': /* inetd mode */
285 standalone = FALSE;
286 break;
287
288 case 's': /* standalone mode */
289 standalone = TRUE;
290 break;
291
292 case 't': /* timeout */
293 if (argv[0][2]) {
294 stmp = &(argv[0][2]);
295 } else {
296 argc--;
297 argv++;
298 stmp = argv[0];
299 }
300 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
301 fprintf(stderr,
302 "%s: invalid timeout specification\n", progname);
303 break;
304 }
305 actualtimeout.tv_sec = (int32) (60 * n);
306 /*
307 * If the actual timeout is zero, pass a NULL pointer
308 * to select so it blocks indefinitely, otherwise,
309 * point to the actual timeout value.
310 */
311 timeout = (n > 0) ? &actualtimeout : NULL;
312 break;
313
314 case 'w': /* wait time */
315 if (argv[0][2]) {
316 stmp = &(argv[0][2]);
317 } else {
318 argc--;
319 argv++;
320 stmp = argv[0];
321 }
322 if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
323 (n < 0) || (n > 60))
324 {
325 fprintf(stderr,
326 "bootpgw: invalid wait time\n");
327 break;
328 }
329 minwait = (u_int)n;
330 break;
331
332 default:
333 fprintf(stderr, "%s: unknown switch: -%c\n",
334 progname, argv[0][1]);
335 usage();
336 break;
337
338 } /* switch */
339 } /* for args */
340
341 /* Make sure server name argument is suplied. */
342 servername = argv[0];
343 if (!servername) {
344 fprintf(stderr, "bootpgw: missing server name\n");
345 usage();
346 }
347 /*
348 * Get address of real bootp server.
349 */
350 if (inet_aton(servername, &serv_addr.sin_addr) == 0) {
351 hep = gethostbyname(servername);
352 if (!hep) {
353 fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
354 exit(1);
355 }
356 memcpy(&serv_addr.sin_addr, hep->h_addr,
357 sizeof(serv_addr.sin_addr));
358 }
359
360 if (standalone) {
361 /*
362 * Go into background and disassociate from controlling terminal.
363 * XXX - This is not the POSIX way (Should use setsid). -gwr
364 */
365 if (debug < 3) {
366 if (fork())
367 exit(0);
368 #ifdef NO_SETSID
369 setpgrp(0,0);
370 #ifdef TIOCNOTTY
371 n = open("/dev/tty", O_RDWR);
372 if (n >= 0) {
373 ioctl(n, TIOCNOTTY, (char *) 0);
374 (void) close(n);
375 }
376 #endif /* TIOCNOTTY */
377 #else /* SETSID */
378 if (setsid() < 0)
379 perror("setsid");
380 #endif /* SETSID */
381 } /* if debug < 3 */
382 /*
383 * Nuke any timeout value
384 */
385 timeout = NULL;
386
387 /*
388 * Here, bootpd would do:
389 * chdir
390 * tzone_init
391 * rdtab_init
392 * readtab
393 */
394
395 /*
396 * Create a socket.
397 */
398 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
399 report(LOG_ERR, "socket: %s", get_network_errmsg());
400 exit(1);
401 }
402 /*
403 * Get server's listening port number
404 */
405 servp = getservbyname("bootps", "udp");
406 if (servp) {
407 bootps_port = ntohs((u_short) servp->s_port);
408 } else {
409 bootps_port = (u_short) IPPORT_BOOTPS;
410 report(LOG_ERR,
411 "udp/bootps: unknown service -- assuming port %d",
412 bootps_port);
413 }
414
415 /*
416 * Bind socket to BOOTPS port.
417 */
418 bind_addr.sin_family = AF_INET;
419 bind_addr.sin_port = htons(bootps_port);
420 bind_addr.sin_addr.s_addr = INADDR_ANY;
421 if (bind(s, (struct sockaddr *) &bind_addr,
422 sizeof(bind_addr)) < 0)
423 {
424 report(LOG_ERR, "bind: %s", get_network_errmsg());
425 exit(1);
426 }
427 } /* if standalone */
428 /*
429 * Get destination port number so we can reply to client
430 */
431 servp = getservbyname("bootpc", "udp");
432 if (servp) {
433 bootpc_port = ntohs(servp->s_port);
434 } else {
435 report(LOG_ERR,
436 "udp/bootpc: unknown service -- assuming port %d",
437 IPPORT_BOOTPC);
438 bootpc_port = (u_short) IPPORT_BOOTPC;
439 }
440
441 /* no signal catchers */
442
443 /*
444 * Process incoming requests.
445 */
446 for (;;) {
447 readfds = 1 << s;
448 nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout);
449 if (nfound < 0) {
450 if (errno != EINTR) {
451 report(LOG_ERR, "select: %s", get_errmsg());
452 }
453 continue;
454 }
455 if (!(readfds & (1 << s))) {
456 report(LOG_INFO, "exiting after %ld minutes of inactivity",
457 actualtimeout.tv_sec / 60);
458 exit(0);
459 }
460 ra_len = sizeof(clnt_addr);
461 n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
462 (struct sockaddr *) &clnt_addr, &ra_len);
463 if (n <= 0) {
464 continue;
465 }
466 if (debug > 3) {
467 report(LOG_INFO, "recvd pkt from IP addr %s",
468 inet_ntoa(clnt_addr.sin_addr));
469 }
470 if (n < sizeof(struct bootp)) {
471 if (debug) {
472 report(LOG_INFO, "received short packet");
473 }
474 continue;
475 }
476 pktlen = n;
477
478 switch (bp->bp_op) {
479 case BOOTREQUEST:
480 handle_request();
481 break;
482 case BOOTREPLY:
483 handle_reply();
484 break;
485 }
486 }
487 }
488
489
491
492
493 /*
494 * Print "usage" message and exit
495 */
496
497 static void
498 usage()
499 {
500 fprintf(stderr,
501 "usage: bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
502 fprintf(stderr, "\t -d n\tset debug level\n");
503 fprintf(stderr, "\t -h n\tset max hop count\n");
504 fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
505 fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
506 fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
507 fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
508 exit(1);
509 }
510
511
513
514 /*
515 * Process BOOTREQUEST packet.
516 *
517 * Note, this just forwards the request to a real server.
518 */
519 static void
520 handle_request()
521 {
522 struct bootp *bp = (struct bootp *) pktbuf;
523 #if 0
524 struct ifreq *ifr;
525 #endif
526 u_short secs, hops;
527
528 /* XXX - SLIP init: Set bp_ciaddr = clnt_addr here? */
529
530 if (debug) {
531 report(LOG_INFO, "request from %s",
532 inet_ntoa(clnt_addr.sin_addr));
533 }
534 /* Has the client been waiting long enough? */
535 secs = ntohs(bp->bp_secs);
536 if (secs < minwait)
537 return;
538
539 /* Has this packet hopped too many times? */
540 hops = ntohs(bp->bp_hops);
541 if (++hops > maxhops) {
542 report(LOG_NOTICE, "request from %s reached hop limit",
543 inet_ntoa(clnt_addr.sin_addr));
544 return;
545 }
546 bp->bp_hops = htons(hops);
547
548 /*
549 * Here one might discard a request from the same subnet as the
550 * real server, but we can assume that the real server will send
551 * a reply to the client before it waits for minwait seconds.
552 */
553
554 /* If gateway address is not set, put in local interface addr. */
555 if (bp->bp_giaddr.s_addr == 0) {
556 #if 0 /* BUG */
557 struct sockaddr_in *sip;
558 /*
559 * XXX - This picks the wrong interface when the receive addr
560 * is the broadcast address. There is no portable way to
561 * find out which interface a broadcast was received on. -gwr
562 * (Thanks to <walker (at) zk3.dec.com> for finding this bug!)
563 */
564 ifr = getif(s, &clnt_addr.sin_addr);
565 if (!ifr) {
566 report(LOG_NOTICE, "no interface for request from %s",
567 inet_ntoa(clnt_addr.sin_addr));
568 return;
569 }
570 sip = (struct sockaddr_in *) &(ifr->ifr_addr);
571 bp->bp_giaddr = sip->sin_addr;
572 #else /* BUG */
573 /*
574 * XXX - Just set "giaddr" to our "official" IP address.
575 * RFC 1532 says giaddr MUST be set to the address of the
576 * interface on which the request was received. Setting
577 * it to our "default" IP address is not strictly correct,
578 * but is good enough to allow the real BOOTP server to
579 * get the reply back here. Then, before we forward the
580 * reply to the client, the giaddr field is corrected.
581 * (In case the client uses giaddr, which it should not.)
582 * See handle_reply()
583 */
584 bp->bp_giaddr = my_ip_addr;
585 #endif /* BUG */
586
587 /*
588 * XXX - DHCP says to insert a subnet mask option into the
589 * options area of the request (if vendor magic == std).
590 */
591 }
592 /* Set up socket address for send. */
593 serv_addr.sin_family = AF_INET;
594 serv_addr.sin_port = htons(bootps_port);
595
596 /* Send reply with same size packet as request used. */
597 if (sendto(s, pktbuf, pktlen, 0,
598 (struct sockaddr *) &serv_addr,
599 sizeof(serv_addr)) < 0)
600 {
601 report(LOG_ERR, "sendto: %s", get_network_errmsg());
602 }
603 }
604
605
607
608 /*
609 * Process BOOTREPLY packet.
610 */
611 static void
612 handle_reply()
613 {
614 struct bootp *bp = (struct bootp *) pktbuf;
615 struct ifreq *ifr;
616 struct sockaddr_in *sip;
617 u_char canon_haddr[MAXHADDRLEN];
618 unsigned char *ha;
619 int len;
620
621 if (debug) {
622 report(LOG_INFO, " reply for %s",
623 inet_ntoa(bp->bp_yiaddr));
624 }
625 /* Make sure client is directly accessible. */
626 ifr = getif(s, &(bp->bp_yiaddr));
627 if (!ifr) {
628 report(LOG_NOTICE, "no interface for reply to %s",
629 inet_ntoa(bp->bp_yiaddr));
630 return;
631 }
632 #if 1 /* Experimental (see BUG above) */
633 /* #ifdef CATER_TO_OLD_CLIENTS ? */
634 /*
635 * The giaddr field has been set to our "default" IP address
636 * which might not be on the same interface as the client.
637 * In case the client looks at giaddr, (which it should not)
638 * giaddr is now set to the address of the correct interface.
639 */
640 sip = (struct sockaddr_in *) &(ifr->ifr_addr);
641 bp->bp_giaddr = sip->sin_addr;
642 #endif
643
644 /* Set up socket address for send to client. */
645 clnt_addr.sin_family = AF_INET;
646 clnt_addr.sin_addr = bp->bp_yiaddr;
647 clnt_addr.sin_port = htons(bootpc_port);
648
649 /* Create an ARP cache entry for the client. */
650 ha = bp->bp_chaddr;
651 len = bp->bp_hlen;
652 if (len > MAXHADDRLEN)
653 len = MAXHADDRLEN;
654 if (bp->bp_htype == HTYPE_IEEE802) {
655 haddr_conv802(ha, canon_haddr, len);
656 ha = canon_haddr;
657 }
658 if (debug > 1)
659 report(LOG_INFO, "setarp %s - %s",
660 inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
661 setarp(s, &bp->bp_yiaddr, ha, len);
662
663 /* Send reply with same size packet as request used. */
664 if (sendto(s, pktbuf, pktlen, 0,
665 (struct sockaddr *) &clnt_addr,
666 sizeof(clnt_addr)) < 0)
667 {
668 report(LOG_ERR, "sendto: %s", get_network_errmsg());
669 }
670 }
671
672 /*
673 * Local Variables:
674 * tab-width: 4
675 * c-indent-level: 4
676 * c-argdecl-indent: 4
677 * c-continued-statement-offset: 4
678 * c-continued-brace-offset: -4
679 * c-label-offset: -4
680 * c-brace-offset: 0
681 * End:
682 */
683