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