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