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