bootpgw.c revision 1.11 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.11 2002/09/18 23:16:14 mycroft 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 int n, ba_len, ra_len;
156 int nfound;
157 struct pollfd set[1];
158 int standalone;
159
160 progname = strrchr(argv[0], '/');
161 if (progname) progname++;
162 else progname = argv[0];
163
164 /*
165 * Initialize logging.
166 */
167 report_init(0); /* uses progname */
168
169 /*
170 * Log startup
171 */
172 report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
173
174 /* Debugging for compilers with struct padding. */
175 assert(sizeof(struct bootp) == BP_MINPKTSZ);
176
177 /* Get space for receiving packets and composing replies. */
178 pktbuf = malloc(MAX_MSG_SIZE);
179 if (!pktbuf) {
180 report(LOG_ERR, "malloc failed");
181 exit(1);
182 }
183 bp = (struct bootp *) pktbuf;
184
185 /*
186 * Check to see if a socket was passed to us from inetd.
187 *
188 * Use getsockname() to determine if descriptor 0 is indeed a socket
189 * (and thus we are probably a child of inetd) or if it is instead
190 * something else and we are running standalone.
191 */
192 s = 0;
193 ba_len = sizeof(bind_addr);
194 bzero((char *) &bind_addr, ba_len);
195 errno = 0;
196 standalone = TRUE;
197 if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
198 /*
199 * Descriptor 0 is a socket. Assume we are a child of inetd.
200 */
201 if (bind_addr.sin_family == AF_INET) {
202 standalone = FALSE;
203 bootps_port = ntohs(bind_addr.sin_port);
204 } else {
205 /* Some other type of socket? */
206 report(LOG_INFO, "getsockname: not an INET socket");
207 }
208 }
209 /*
210 * Set defaults that might be changed by option switches.
211 */
212 stmp = NULL;
213 timeout = actualtimeout;
214 gethostname(myhostname, sizeof(myhostname));
215 myhostname[sizeof(myhostname) - 1] = '\0';
216 hep = gethostbyname(myhostname);
217 if (!hep) {
218 printf("Can not get my IP address\n");
219 exit(1);
220 }
221 bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
222
223 /*
224 * Read switches.
225 */
226 for (argc--, argv++; argc > 0; argc--, argv++) {
227 if (argv[0][0] != '-')
228 break;
229 switch (argv[0][1]) {
230
231 case 'd': /* debug level */
232 if (argv[0][2]) {
233 stmp = &(argv[0][2]);
234 } else if (argv[1] && argv[1][0] == '-') {
235 /*
236 * Backwards-compatible behavior:
237 * no parameter, so just increment the debug flag.
238 */
239 debug++;
240 break;
241 } else {
242 argc--;
243 argv++;
244 stmp = argv[0];
245 }
246 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
247 fprintf(stderr,
248 "%s: invalid debug level\n", progname);
249 break;
250 }
251 debug = n;
252 break;
253
254 case 'h': /* hop count limit */
255 if (argv[0][2]) {
256 stmp = &(argv[0][2]);
257 } else {
258 argc--;
259 argv++;
260 stmp = argv[0];
261 }
262 if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
263 (n < 0) || (n > 16))
264 {
265 fprintf(stderr,
266 "bootpgw: invalid hop count limit\n");
267 break;
268 }
269 maxhops = (u_int)n;
270 break;
271
272 case 'i': /* inetd mode */
273 standalone = FALSE;
274 break;
275
276 case 's': /* standalone mode */
277 standalone = TRUE;
278 break;
279
280 case 't': /* timeout */
281 if (argv[0][2]) {
282 stmp = &(argv[0][2]);
283 } else {
284 argc--;
285 argv++;
286 stmp = argv[0];
287 }
288 if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
289 fprintf(stderr,
290 "%s: invalid timeout specification\n", progname);
291 break;
292 }
293 actualtimeout = n * 60000;
294 /*
295 * If the actual timeout is zero, pass INFTIM
296 * to poll so it blocks indefinitely, otherwise,
297 * use the actual timeout value.
298 */
299 timeout = (n > 0) ? actualtimeout : INFTIM;
300 break;
301
302 case 'w': /* wait time */
303 if (argv[0][2]) {
304 stmp = &(argv[0][2]);
305 } else {
306 argc--;
307 argv++;
308 stmp = argv[0];
309 }
310 if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
311 (n < 0) || (n > 60))
312 {
313 fprintf(stderr,
314 "bootpgw: invalid wait time\n");
315 break;
316 }
317 minwait = (u_int)n;
318 break;
319
320 default:
321 fprintf(stderr, "%s: unknown switch: -%c\n",
322 progname, argv[0][1]);
323 usage();
324 break;
325
326 } /* switch */
327 } /* for args */
328
329 /* Make sure server name argument is suplied. */
330 servername = argv[0];
331 if (!servername) {
332 fprintf(stderr, "bootpgw: missing server name\n");
333 usage();
334 }
335 /*
336 * Get address of real bootp server.
337 */
338 if (inet_aton(servername, &serv_addr.sin_addr) == 0) {
339 hep = gethostbyname(servername);
340 if (!hep) {
341 fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
342 exit(1);
343 }
344 memcpy(&serv_addr.sin_addr, hep->h_addr,
345 sizeof(serv_addr.sin_addr));
346 }
347
348 if (standalone) {
349 /*
350 * Go into background and disassociate from controlling terminal.
351 * XXX - This is not the POSIX way (Should use setsid). -gwr
352 */
353 if (debug < 3) {
354 if (fork())
355 exit(0);
356 #ifdef NO_SETSID
357 setpgrp(0,0);
358 #ifdef TIOCNOTTY
359 n = open("/dev/tty", O_RDWR);
360 if (n >= 0) {
361 ioctl(n, TIOCNOTTY, (char *) 0);
362 (void) close(n);
363 }
364 #endif /* TIOCNOTTY */
365 #else /* SETSID */
366 if (setsid() < 0)
367 perror("setsid");
368 #endif /* SETSID */
369 } /* if debug < 3 */
370 /*
371 * Nuke any timeout value
372 */
373 timeout = INFTIM;
374
375 /*
376 * Here, bootpd would do:
377 * chdir
378 * tzone_init
379 * rdtab_init
380 * readtab
381 */
382
383 /*
384 * Create a socket.
385 */
386 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
387 report(LOG_ERR, "socket: %s", get_network_errmsg());
388 exit(1);
389 }
390 /*
391 * Get server's listening port number
392 */
393 servp = getservbyname("bootps", "udp");
394 if (servp) {
395 bootps_port = ntohs((u_short) servp->s_port);
396 } else {
397 bootps_port = (u_short) IPPORT_BOOTPS;
398 report(LOG_ERR,
399 "udp/bootps: unknown service -- assuming port %d",
400 bootps_port);
401 }
402
403 /*
404 * Bind socket to BOOTPS port.
405 */
406 bind_addr.sin_family = AF_INET;
407 bind_addr.sin_port = htons(bootps_port);
408 bind_addr.sin_addr.s_addr = INADDR_ANY;
409 if (bind(s, (struct sockaddr *) &bind_addr,
410 sizeof(bind_addr)) < 0)
411 {
412 report(LOG_ERR, "bind: %s", get_network_errmsg());
413 exit(1);
414 }
415 } /* if standalone */
416 /*
417 * Get destination port number so we can reply to client
418 */
419 servp = getservbyname("bootpc", "udp");
420 if (servp) {
421 bootpc_port = ntohs(servp->s_port);
422 } else {
423 report(LOG_ERR,
424 "udp/bootpc: unknown service -- assuming port %d",
425 IPPORT_BOOTPC);
426 bootpc_port = (u_short) IPPORT_BOOTPC;
427 }
428
429 /* no signal catchers */
430
431 /*
432 * Process incoming requests.
433 */
434 set[0].fd = s;
435 set[0].events = POLLIN;
436 for (;;) {
437 nfound = poll(set, 1, timeout);
438 if (nfound < 0) {
439 if (errno != EINTR) {
440 report(LOG_ERR, "poll: %s", get_errmsg());
441 }
442 continue;
443 }
444 if (nfound == 0) {
445 report(LOG_INFO, "exiting after %d minute%s of inactivity",
446 actualtimeout / 60000,
447 actualtimeout == 60000 ? "" : "s");
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