summaryrefslogtreecommitdiffstats
path: root/libexec/bootpd/bootpgw/bootpgw.c
diff options
context:
space:
mode:
Diffstat (limited to 'libexec/bootpd/bootpgw/bootpgw.c')
-rw-r--r--libexec/bootpd/bootpgw/bootpgw.c687
1 files changed, 687 insertions, 0 deletions
diff --git a/libexec/bootpd/bootpgw/bootpgw.c b/libexec/bootpd/bootpgw/bootpgw.c
new file mode 100644
index 0000000..353d917
--- /dev/null
+++ b/libexec/bootpd/bootpgw/bootpgw.c
@@ -0,0 +1,687 @@
+/*
+ * bootpgw.c - BOOTP GateWay
+ * This program forwards BOOTP Request packets to a BOOTP server.
+ */
+
+/************************************************************************
+ Copyright 1988, 1991 by Carnegie Mellon University
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation, and that the name of Carnegie Mellon University not be used
+in advertising or publicity pertaining to distribution of the software
+without specific, written prior permission.
+
+CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+************************************************************************/
+
+/*
+ * BOOTPGW is typically used to forward BOOTP client requests from
+ * one subnet to a BOOTP server on a different subnet.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h> /* inet_ntoa */
+
+#ifndef NO_UNISTD
+#include <unistd.h>
+#endif
+
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <assert.h>
+
+#ifdef NO_SETSID
+# include <fcntl.h> /* for O_RDONLY, etc */
+#endif
+
+#ifndef USE_BFUNCS
+# include <memory.h>
+/* Yes, memcpy is OK here (no overlapped copies). */
+# define bcopy(a,b,c) memcpy(b,a,c)
+# define bzero(p,l) memset(p,0,l)
+# define bcmp(a,b,c) memcmp(a,b,c)
+#endif
+
+#include "bootp.h"
+#include "getif.h"
+#include "hwaddr.h"
+#include "report.h"
+#include "patchlevel.h"
+
+/* Local definitions: */
+#define MAX_MSG_SIZE (3*512) /* Maximum packet size */
+#define TRUE 1
+#define FALSE 0
+#define get_network_errmsg get_errmsg
+
+
+
+/*
+ * Externals, forward declarations, and global variables
+ */
+
+#ifdef __STDC__
+#define P(args) args
+#else
+#define P(args) ()
+#endif
+
+static void usage P((void));
+static void handle_reply P((void));
+static void handle_request P((void));
+
+#undef P
+
+/*
+ * IP port numbers for client and server obtained from /etc/services
+ */
+
+u_short bootps_port, bootpc_port;
+
+
+/*
+ * Internet socket and interface config structures
+ */
+
+struct sockaddr_in bind_addr; /* Listening */
+struct sockaddr_in recv_addr; /* Packet source */
+struct sockaddr_in send_addr; /* destination */
+
+
+/*
+ * option defaults
+ */
+int debug = 0; /* Debugging flag (level) */
+struct timeval actualtimeout =
+{ /* fifteen minutes */
+ 15 * 60L, /* tv_sec */
+ 0 /* tv_usec */
+};
+u_int maxhops = 4; /* Number of hops allowed for requests. */
+u_int minwait = 3; /* Number of seconds client must wait before
+ its bootrequest packets are forwarded. */
+
+/*
+ * General
+ */
+
+int s; /* Socket file descriptor */
+char *pktbuf; /* Receive packet buffer */
+int pktlen;
+char *progname;
+char *servername;
+int32 server_ipa; /* Real server IP address, network order. */
+
+struct in_addr my_ip_addr;
+
+struct utsname my_uname;
+char *hostname;
+
+
+
+
+
+/*
+ * Initialization such as command-line processing is done and then the
+ * main server loop is started.
+ */
+
+void
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ struct timeval *timeout;
+ struct bootp *bp;
+ struct servent *servp;
+ struct hostent *hep;
+ char *stmp;
+ int n, ba_len, ra_len;
+ int nfound, readfds;
+ int standalone;
+
+ progname = strrchr(argv[0], '/');
+ if (progname) progname++;
+ else progname = argv[0];
+
+ /*
+ * Initialize logging.
+ */
+ report_init(0); /* uses progname */
+
+ /*
+ * Log startup
+ */
+ report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
+
+ /* Debugging for compilers with struct padding. */
+ assert(sizeof(struct bootp) == BP_MINPKTSZ);
+
+ /* Get space for receiving packets and composing replies. */
+ pktbuf = malloc(MAX_MSG_SIZE);
+ if (!pktbuf) {
+ report(LOG_ERR, "malloc failed");
+ exit(1);
+ }
+ bp = (struct bootp *) pktbuf;
+
+ /*
+ * Check to see if a socket was passed to us from inetd.
+ *
+ * Use getsockname() to determine if descriptor 0 is indeed a socket
+ * (and thus we are probably a child of inetd) or if it is instead
+ * something else and we are running standalone.
+ */
+ s = 0;
+ ba_len = sizeof(bind_addr);
+ bzero((char *) &bind_addr, ba_len);
+ errno = 0;
+ standalone = TRUE;
+ if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
+ /*
+ * Descriptor 0 is a socket. Assume we are a child of inetd.
+ */
+ if (bind_addr.sin_family == AF_INET) {
+ standalone = FALSE;
+ bootps_port = ntohs(bind_addr.sin_port);
+ } else {
+ /* Some other type of socket? */
+ report(LOG_INFO, "getsockname: not an INET socket");
+ }
+ }
+ /*
+ * Set defaults that might be changed by option switches.
+ */
+ stmp = NULL;
+ timeout = &actualtimeout;
+
+ if (uname(&my_uname) < 0) {
+ fprintf(stderr, "bootpgw: can't get hostname\n");
+ exit(1);
+ }
+ hostname = my_uname.nodename;
+
+ hep = gethostbyname(hostname);
+ if (!hep) {
+ printf("Can not get my IP address\n");
+ exit(1);
+ }
+ bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
+
+ /*
+ * Read switches.
+ */
+ for (argc--, argv++; argc > 0; argc--, argv++) {
+ if (argv[0][0] != '-')
+ break;
+ switch (argv[0][1]) {
+
+ case 'd': /* debug level */
+ if (argv[0][2]) {
+ stmp = &(argv[0][2]);
+ } else if (argv[1] && argv[1][0] == '-') {
+ /*
+ * Backwards-compatible behavior:
+ * no parameter, so just increment the debug flag.
+ */
+ debug++;
+ break;
+ } else {
+ argc--;
+ argv++;
+ stmp = argv[0];
+ }
+ if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
+ fprintf(stderr,
+ "%s: invalid debug level\n", progname);
+ break;
+ }
+ debug = n;
+ break;
+
+ case 'h': /* hop count limit */
+ if (argv[0][2]) {
+ stmp = &(argv[0][2]);
+ } else {
+ argc--;
+ argv++;
+ stmp = argv[0];
+ }
+ if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
+ (n < 0) || (n > 16))
+ {
+ fprintf(stderr,
+ "bootpgw: invalid hop count limit\n");
+ break;
+ }
+ maxhops = (u_int)n;
+ break;
+
+ case 'i': /* inetd mode */
+ standalone = FALSE;
+ break;
+
+ case 's': /* standalone mode */
+ standalone = TRUE;
+ break;
+
+ case 't': /* timeout */
+ if (argv[0][2]) {
+ stmp = &(argv[0][2]);
+ } else {
+ argc--;
+ argv++;
+ stmp = argv[0];
+ }
+ if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
+ fprintf(stderr,
+ "%s: invalid timeout specification\n", progname);
+ break;
+ }
+ actualtimeout.tv_sec = (int32) (60 * n);
+ /*
+ * If the actual timeout is zero, pass a NULL pointer
+ * to select so it blocks indefinitely, otherwise,
+ * point to the actual timeout value.
+ */
+ timeout = (n > 0) ? &actualtimeout : NULL;
+ break;
+
+ case 'w': /* wait time */
+ if (argv[0][2]) {
+ stmp = &(argv[0][2]);
+ } else {
+ argc--;
+ argv++;
+ stmp = argv[0];
+ }
+ if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
+ (n < 0) || (n > 60))
+ {
+ fprintf(stderr,
+ "bootpgw: invalid wait time\n");
+ break;
+ }
+ minwait = (u_int)n;
+ break;
+
+ default:
+ fprintf(stderr, "%s: unknown switch: -%c\n",
+ progname, argv[0][1]);
+ usage();
+ break;
+
+ } /* switch */
+ } /* for args */
+
+ /* Make sure server name argument is suplied. */
+ servername = argv[0];
+ if (!servername) {
+ fprintf(stderr, "bootpgw: missing server name\n");
+ usage();
+ }
+ /*
+ * Get address of real bootp server.
+ */
+ if (isdigit(servername[0]))
+ server_ipa = inet_addr(servername);
+ else {
+ hep = gethostbyname(servername);
+ if (!hep) {
+ fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
+ exit(1);
+ }
+ bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
+ }
+
+ if (standalone) {
+ /*
+ * Go into background and disassociate from controlling terminal.
+ * XXX - This is not the POSIX way (Should use setsid). -gwr
+ */
+ if (debug < 3) {
+ if (fork())
+ exit(0);
+#ifdef NO_SETSID
+ setpgrp(0,0);
+#ifdef TIOCNOTTY
+ n = open("/dev/tty", O_RDWR);
+ if (n >= 0) {
+ ioctl(n, TIOCNOTTY, (char *) 0);
+ (void) close(n);
+ }
+#endif /* TIOCNOTTY */
+#else /* SETSID */
+ if (setsid() < 0)
+ perror("setsid");
+#endif /* SETSID */
+ } /* if debug < 3 */
+ /*
+ * Nuke any timeout value
+ */
+ timeout = NULL;
+
+ /*
+ * Here, bootpd would do:
+ * chdir
+ * tzone_init
+ * rdtab_init
+ * readtab
+ */
+
+ /*
+ * Create a socket.
+ */
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ report(LOG_ERR, "socket: %s", get_network_errmsg());
+ exit(1);
+ }
+ /*
+ * Get server's listening port number
+ */
+ servp = getservbyname("bootps", "udp");
+ if (servp) {
+ bootps_port = ntohs((u_short) servp->s_port);
+ } else {
+ bootps_port = (u_short) IPPORT_BOOTPS;
+ report(LOG_ERR,
+ "udp/bootps: unknown service -- assuming port %d",
+ bootps_port);
+ }
+
+ /*
+ * Bind socket to BOOTPS port.
+ */
+ bind_addr.sin_family = AF_INET;
+ bind_addr.sin_port = htons(bootps_port);
+ bind_addr.sin_addr.s_addr = INADDR_ANY;
+ if (bind(s, (struct sockaddr *) &bind_addr,
+ sizeof(bind_addr)) < 0)
+ {
+ report(LOG_ERR, "bind: %s", get_network_errmsg());
+ exit(1);
+ }
+ } /* if standalone */
+ /*
+ * Get destination port number so we can reply to client
+ */
+ servp = getservbyname("bootpc", "udp");
+ if (servp) {
+ bootpc_port = ntohs(servp->s_port);
+ } else {
+ report(LOG_ERR,
+ "udp/bootpc: unknown service -- assuming port %d",
+ IPPORT_BOOTPC);
+ bootpc_port = (u_short) IPPORT_BOOTPC;
+ }
+
+ /* no signal catchers */
+
+ /*
+ * Process incoming requests.
+ */
+ for (;;) {
+ struct timeval tv;
+
+ readfds = 1 << s;
+ if (timeout)
+ tv = *timeout;
+
+ nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,
+ (timeout) ? &tv : NULL);
+ if (nfound < 0) {
+ if (errno != EINTR) {
+ report(LOG_ERR, "select: %s", get_errmsg());
+ }
+ continue;
+ }
+ if (!(readfds & (1 << s))) {
+ report(LOG_INFO, "exiting after %ld minutes of inactivity",
+ actualtimeout.tv_sec / 60);
+ exit(0);
+ }
+ ra_len = sizeof(recv_addr);
+ n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
+ (struct sockaddr *) &recv_addr, &ra_len);
+ if (n <= 0) {
+ continue;
+ }
+ if (debug > 3) {
+ report(LOG_INFO, "recvd pkt from IP addr %s",
+ inet_ntoa(recv_addr.sin_addr));
+ }
+ if (n < sizeof(struct bootp)) {
+ if (debug) {
+ report(LOG_INFO, "received short packet");
+ }
+ continue;
+ }
+ pktlen = n;
+
+ switch (bp->bp_op) {
+ case BOOTREQUEST:
+ handle_request();
+ break;
+ case BOOTREPLY:
+ handle_reply();
+ break;
+ }
+ }
+}
+
+
+
+
+/*
+ * Print "usage" message and exit
+ */
+
+static void
+usage()
+{
+ fprintf(stderr,
+ "usage: bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
+ fprintf(stderr, "\t -d n\tset debug level\n");
+ fprintf(stderr, "\t -h n\tset max hop count\n");
+ fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
+ fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
+ fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
+ fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
+ exit(1);
+}
+
+
+
+/*
+ * Process BOOTREQUEST packet.
+ *
+ * Note, this just forwards the request to a real server.
+ */
+static void
+handle_request()
+{
+ struct bootp *bp = (struct bootp *) pktbuf;
+ u_short secs, hops;
+
+ /* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
+
+ if (debug) {
+ report(LOG_INFO, "request from %s",
+ inet_ntoa(recv_addr.sin_addr));
+ }
+ /* Has the client been waiting long enough? */
+ secs = ntohs(bp->bp_secs);
+ if (secs < minwait)
+ return;
+
+ /* Has this packet hopped too many times? */
+ hops = ntohs(bp->bp_hops);
+ if (++hops > maxhops) {
+ report(LOG_NOTICE, "reqest from %s reached hop limit",
+ inet_ntoa(recv_addr.sin_addr));
+ return;
+ }
+ bp->bp_hops = htons(hops);
+
+ /*
+ * Here one might discard a request from the same subnet as the
+ * real server, but we can assume that the real server will send
+ * a reply to the client before it waits for minwait seconds.
+ */
+
+ /* If gateway address is not set, put in local interface addr. */
+ if (bp->bp_giaddr.s_addr == 0) {
+#if 0 /* BUG */
+ struct sockaddr_in *sip;
+ struct ifreq *ifr;
+ /*
+ * XXX - This picks the wrong interface when the receive addr
+ * is the broadcast address. There is no portable way to
+ * find out which interface a broadcast was received on. -gwr
+ * (Thanks to <walker@zk3.dec.com> for finding this bug!)
+ */
+ ifr = getif(s, &recv_addr.sin_addr);
+ if (!ifr) {
+ report(LOG_NOTICE, "no interface for request from %s",
+ inet_ntoa(recv_addr.sin_addr));
+ return;
+ }
+ sip = (struct sockaddr_in *) &(ifr->ifr_addr);
+ bp->bp_giaddr = sip->sin_addr;
+#else /* BUG */
+ /*
+ * XXX - Just set "giaddr" to our "official" IP address.
+ * RFC 1532 says giaddr MUST be set to the address of the
+ * interface on which the request was received. Setting
+ * it to our "default" IP address is not strictly correct,
+ * but is good enough to allow the real BOOTP server to
+ * get the reply back here. Then, before we forward the
+ * reply to the client, the giaddr field is corrected.
+ * (In case the client uses giaddr, which it should not.)
+ * See handle_reply()
+ */
+ bp->bp_giaddr = my_ip_addr;
+#endif /* BUG */
+
+ /*
+ * XXX - DHCP says to insert a subnet mask option into the
+ * options area of the request (if vendor magic == std).
+ */
+ }
+ /* Set up socket address for send. */
+ send_addr.sin_family = AF_INET;
+ send_addr.sin_port = htons(bootps_port);
+ send_addr.sin_addr.s_addr = server_ipa;
+
+ /* Send reply with same size packet as request used. */
+ if (sendto(s, pktbuf, pktlen, 0,
+ (struct sockaddr *) &send_addr,
+ sizeof(send_addr)) < 0)
+ {
+ report(LOG_ERR, "sendto: %s", get_network_errmsg());
+ }
+}
+
+
+
+/*
+ * Process BOOTREPLY packet.
+ */
+static void
+handle_reply()
+{
+ struct bootp *bp = (struct bootp *) pktbuf;
+ struct ifreq *ifr;
+ struct sockaddr_in *sip;
+ unsigned char *ha;
+ int len, haf;
+
+ if (debug) {
+ report(LOG_INFO, " reply for %s",
+ inet_ntoa(bp->bp_yiaddr));
+ }
+ /* Make sure client is directly accessible. */
+ ifr = getif(s, &(bp->bp_yiaddr));
+ if (!ifr) {
+ report(LOG_NOTICE, "no interface for reply to %s",
+ inet_ntoa(bp->bp_yiaddr));
+ return;
+ }
+#if 1 /* Experimental (see BUG above) */
+/* #ifdef CATER_TO_OLD_CLIENTS ? */
+ /*
+ * The giaddr field has been set to our "default" IP address
+ * which might not be on the same interface as the client.
+ * In case the client looks at giaddr, (which it should not)
+ * giaddr is now set to the address of the correct interface.
+ */
+ sip = (struct sockaddr_in *) &(ifr->ifr_addr);
+ bp->bp_giaddr = sip->sin_addr;
+#endif
+
+ /* Set up socket address for send to client. */
+ send_addr.sin_family = AF_INET;
+ send_addr.sin_addr = bp->bp_yiaddr;
+ send_addr.sin_port = htons(bootpc_port);
+
+ /* Create an ARP cache entry for the client. */
+ ha = bp->bp_chaddr;
+ len = bp->bp_hlen;
+ if (len > MAXHADDRLEN)
+ len = MAXHADDRLEN;
+ haf = (int) bp->bp_htype;
+ if (haf == 0)
+ haf = HTYPE_ETHERNET;
+
+ if (debug > 1)
+ report(LOG_INFO, "setarp %s - %s",
+ inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
+ setarp(s, &bp->bp_yiaddr, haf, ha, len);
+
+ /* Send reply with same size packet as request used. */
+ if (sendto(s, pktbuf, pktlen, 0,
+ (struct sockaddr *) &send_addr,
+ sizeof(send_addr)) < 0)
+ {
+ report(LOG_ERR, "sendto: %s", get_network_errmsg());
+ }
+}
+
+/*
+ * Local Variables:
+ * tab-width: 4
+ * c-indent-level: 4
+ * c-argdecl-indent: 4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: -4
+ * c-label-offset: -4
+ * c-brace-offset: 0
+ * End:
+ */
OpenPOWER on IntegriCloud