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