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