summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/bootp.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/bootp.c')
-rw-r--r--usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/bootp.c407
1 files changed, 407 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/bootp.c b/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/bootp.c
new file mode 100644
index 0000000000..dbb99da607
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/bootp.c
@@ -0,0 +1,407 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <arpa/inet.h>
+#include <nss_dbdefs.h>
+#include <netinet/dhcp.h>
+#include <netdb.h>
+#include <dhcp_symbol.h>
+#include "dhcpd.h"
+#include "per_dnet.h"
+#include "interfaces.h"
+#include <locale.h>
+
+/*
+ * This file contains the code which implements the BOOTP compatibility.
+ */
+
+/*
+ * We are guaranteed that the packet received is a BOOTP request packet,
+ * e.g., *NOT* a DHCP packet.
+ */
+void
+bootp(dsvc_clnt_t *pcd, PKT_LIST *plp)
+{
+ boolean_t result, existing_offer = B_FALSE;
+ int err, write_error = DSVC_SUCCESS, flags = 0;
+ int pkt_len;
+ uint_t crecords = 0, irecords = 0, srecords = 0, clen;
+ uint32_t query;
+ PKT *rep_pktp = NULL;
+ IF *ifp = pcd->ifp;
+ dsvc_dnet_t *pnd = pcd->pnd;
+ uchar_t *optp;
+ dn_rec_t dn, ndn, *dnp;
+ dn_rec_list_t *dncp = NULL, *dnip = NULL, *dnlp = NULL;
+ struct in_addr ciaddr;
+ struct in_addr no_ip; /* network order IP */
+ ENCODE *ecp, *hecp;
+ MACRO *mp, *nmp, *cmp;
+ time_t now = time(NULL);
+ DHCP_MSG_CATEGORIES log;
+ struct hostent h, *hp;
+ char ntoab[INET_ADDRSTRLEN], cipbuf[INET_ADDRSTRLEN];
+ char cidbuf[DHCP_MAX_OPT_SIZE];
+ char hbuf[NSS_BUFLEN_HOSTS];
+
+#ifdef DEBUG
+ dhcpmsg(LOG_DEBUG, "BOOTP request received on %s\n", ifp->nm);
+#endif /* DEBUG */
+
+ if (pcd->off_ip.s_addr != htonl(INADDR_ANY) &&
+ PCD_OFFER_TIMEOUT(pcd, now))
+ purge_offer(pcd, B_TRUE, B_TRUE);
+
+ if (pcd->off_ip.s_addr != htonl(INADDR_ANY)) {
+ existing_offer = B_TRUE;
+ dnlp = pcd->dnlp;
+ assert(dnlp != NULL);
+ dnp = dnlp->dnl_rec;
+ no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
+ crecords = 1;
+ } else {
+ /*
+ * Try to find a CID entry for the client. We don't care about
+ * lease info here, since a BOOTP client always has a permanent
+ * lease. We also don't care about the entry owner either,
+ * unless we end up allocating a new entry for the client.
+ */
+ DSVC_QINIT(query);
+
+ DSVC_QEQ(query, DN_QCID);
+ (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len);
+ dn.dn_cid_len = pcd->cid_len;
+
+ DSVC_QEQ(query, DN_QFBOOTP_ONLY);
+ dn.dn_flags = DN_FBOOTP_ONLY;
+
+ /*
+ * If a client address (ciaddr) is given, we simply trust that
+ * the client knows what it's doing, and we use that IP address
+ * to locate the client's record. If we can't find the client's
+ * record, then we keep silent. If the client id of the record
+ * doesn't match this client, then either the client or our
+ * database is inconsistent, and we'll ignore it (notice
+ * message generated).
+ */
+ ciaddr.s_addr = plp->pkt->ciaddr.s_addr;
+ if (ciaddr.s_addr != htonl(INADDR_ANY)) {
+ DSVC_QEQ(query, DN_QCIP);
+ dn.dn_cip.s_addr = ntohl(ciaddr.s_addr);
+ }
+
+ dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query,
+ -1, &dn, (void **)&dncp, S_CID);
+ if (dnlp != NULL) {
+ crecords = 1;
+ dnp = dnlp->dnl_rec;
+ if (dnp->dn_flags & DN_FUNUSABLE)
+ goto leave_bootp;
+ no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
+ }
+ }
+
+ (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
+
+ if (crecords == 0 && !be_automatic) {
+ if (verbose) {
+ dhcpmsg(LOG_INFO, "BOOTP client: %1$s is looking for "
+ "a configuration on net %2$s\n", pcd->cidbuf,
+ pnd->network);
+ }
+ goto leave_bootp;
+ }
+
+ /*
+ * If the client thinks it knows who it is (ciaddr), and this doesn't
+ * match our registered IP address, then display an error message and
+ * give up.
+ */
+ if (ciaddr.s_addr != htonl(INADDR_ANY) && crecords == 0) {
+ /*
+ * If the client specified an IP address, then let's check
+ * whether it is available, since we have no CID mapping
+ * registered for this client. If it is available and
+ * unassigned but owned by a different server, we ignore the
+ * client.
+ */
+ DSVC_QINIT(query);
+
+ DSVC_QEQ(query, DN_QCIP);
+ dn.dn_cip.s_addr = ntohl(ciaddr.s_addr);
+ (void) inet_ntop(AF_INET, &ciaddr, cipbuf, sizeof (cipbuf));
+
+ DSVC_QEQ(query, DN_QFBOOTP_ONLY);
+ dn.dn_flags = DN_FBOOTP_ONLY;
+
+ dnip = NULL;
+ dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query,
+ -1, &dn, (void **)&dncp, S_CID);
+ if (dnlp == NULL) {
+ /*
+ * We have no record of this client's IP address, thus
+ * we really can't respond to this client, because it
+ * doesn't have a configuration.
+ */
+ if (verbose) {
+ dhcpmsg(LOG_INFO, "No configuration for BOOTP "
+ "client: %1$s. IP address: %2$s not "
+ "administered by this server.\n",
+ pcd->cidbuf, inet_ntop(AF_INET, &ciaddr,
+ ntoab, sizeof (ntoab)));
+ }
+ goto leave_bootp;
+ } else
+ irecords = 1;
+
+ dnp = dnlp->dnl_rec;
+ if (dnp->dn_flags & DN_FUNUSABLE)
+ goto leave_bootp;
+
+ if (dn.dn_cid_len != 0) {
+ if (dn.dn_cid_len != pcd->cid_len || memcmp(dn.dn_cid,
+ pcd->cid, pcd->cid_len) != 0) {
+ if (verbose) {
+ clen = sizeof (cidbuf);
+ (void) octet_to_hexascii(dn.dn_cid,
+ dn.dn_cid_len, cidbuf, &clen);
+ dhcpmsg(LOG_INFO, "BOOTP client: %1$s "
+ "thinks it owns %2$s, but that "
+ "address belongs to %3$s. Ignoring "
+ "client.\n", pcd->cidbuf, cipbuf,
+ cidbuf);
+ }
+ goto leave_bootp;
+ }
+ } else {
+ if (match_ownerip(htonl(dn.dn_sip.s_addr)) == NULL) {
+ if (verbose) {
+ no_ip.s_addr =
+ htonl(dnp->dn_sip.s_addr);
+ dhcpmsg(LOG_INFO, "BOOTP client: %1$s "
+ "believes it owns %2$s. That "
+ "address is free, but is owned by "
+ "DHCP server %3$s. Ignoring "
+ "client.\n", pcd->cidbuf, cipbuf,
+ inet_ntop(AF_INET, &no_ip, ntoab,
+ sizeof (ntoab)));
+ }
+ goto leave_bootp;
+ }
+ }
+ no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
+ (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
+ }
+
+ if (crecords == 0) {
+ /*
+ * The dhcp-network table did not have any matching entries.
+ * Try to allocate a new one if possible.
+ */
+ if (irecords == 0 && select_offer(pnd, plp, pcd, &dnlp)) {
+ dnp = dnlp->dnl_rec;
+ no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
+ (void) inet_ntop(AF_INET, &no_ip, cipbuf,
+ sizeof (cipbuf));
+ srecords = 1;
+ }
+ }
+
+ if (crecords == 0 && irecords == 0 && srecords == 0) {
+ dhcpmsg(LOG_NOTICE,
+ "(%1$s) No more BOOTP IP addresses for %2$s network.\n",
+ pcd->cidbuf, pnd->network);
+ goto leave_bootp;
+ }
+
+ /* Check the address. But only if client doesn't know its address. */
+ ndn = *dnp; /* struct copy */
+ no_ip.s_addr = htonl(ndn.dn_cip.s_addr);
+ (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
+ if (ciaddr.s_addr == htonl(INADDR_ANY)) {
+ if ((ifp->flags & IFF_NOARP) == 0)
+ (void) set_arp(ifp, &no_ip, NULL, 0, DHCP_ARP_DEL);
+ if (!noping) {
+ /*
+ * If icmp echo check fails,
+ * let the plp fall by the wayside.
+ */
+ errno = icmp_echo_check(&no_ip, &result);
+ if (errno != 0) {
+ dhcpmsg(LOG_ERR, "ICMP ECHO check cannot be "
+ "performed for: %s, ignoring\n", cipbuf);
+ goto leave_bootp;
+ }
+ if (result) {
+ dhcpmsg(LOG_ERR, "ICMP ECHO reply to BOOTP "
+ "OFFER candidate: %s, disabling.\n",
+ cipbuf);
+
+ ndn.dn_flags |= DN_FUNUSABLE;
+
+ if ((err = dhcp_modify_dd_entry(pnd->dh,
+ dnp, &ndn)) == DSVC_SUCCESS) {
+ /* Keep the cached entry current. */
+ *dnp = ndn; /* struct copy */
+ }
+
+ logtrans(P_BOOTP, L_ICMP_ECHO, 0, no_ip,
+ server_ip, plp);
+
+ goto leave_bootp;
+ }
+ }
+ }
+
+ /*
+ * It is possible that the client could specify a REQUEST list,
+ * but then it would be a DHCP client, wouldn't it? Only copy the
+ * std option list, since that potentially could be changed by
+ * load_options().
+ */
+ ecp = NULL;
+ if (!no_dhcptab) {
+ open_macros();
+ if ((nmp = get_macro(pnd->network)) != NULL)
+ ecp = dup_encode_list(nmp->head);
+ if ((mp = get_macro(dnp->dn_macro)) != NULL)
+ ecp = combine_encodes(ecp, mp->head, ENC_DONT_COPY);
+ if ((cmp = get_macro(pcd->cidbuf)) != NULL)
+ ecp = combine_encodes(ecp, cmp->head, ENC_DONT_COPY);
+
+ /* If dhcptab configured to return hostname, do so. */
+ if (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) !=
+ NULL) {
+ hp = gethostbyaddr_r((char *)&ndn.dn_cip,
+ sizeof (struct in_addr), AF_INET, &h, hbuf,
+ sizeof (hbuf), &err);
+ if (hp != NULL) {
+ hecp = make_encode(DSYM_STANDARD,
+ CD_HOSTNAME, strlen(hp->h_name),
+ hp->h_name, ENC_COPY);
+ replace_encode(&ecp, hecp, ENC_DONT_COPY);
+ }
+ }
+ }
+
+ /* Produce a BOOTP reply. */
+ rep_pktp = gen_bootp_pkt(sizeof (PKT), plp->pkt);
+
+ rep_pktp->op = BOOTREPLY;
+ optp = rep_pktp->options;
+
+ /*
+ * Set the client's "your" IP address if client doesn't know it,
+ * otherwise echo the client's ciaddr back to him.
+ */
+ if (ciaddr.s_addr == htonl(INADDR_ANY))
+ rep_pktp->yiaddr.s_addr = htonl(ndn.dn_cip.s_addr);
+ else
+ rep_pktp->ciaddr.s_addr = ciaddr.s_addr;
+
+ /*
+ * Omit lease time options implicitly, e.g.
+ * ~(DHCP_DHCP_CLNT | DHCP_SEND_LEASE)
+ */
+
+ if (!plp->rfc1048)
+ flags |= DHCP_NON_RFC1048;
+
+ /* Now load in configured options. */
+ pkt_len = load_options(flags, plp, rep_pktp, sizeof (PKT), optp, ecp,
+ NULL);
+
+ free_encode_list(ecp);
+ if (!no_dhcptab)
+ close_macros();
+
+ if (pkt_len < sizeof (PKT))
+ pkt_len = sizeof (PKT);
+
+ /*
+ * Only perform a write if we have selected an entry not yet
+ * assigned to the client (a matching DN_FBOOTP_ONLY entry from
+ * ip address lookup, or an unassigned entry from select_offer()).
+ */
+ if (srecords > 0 || irecords > 0) {
+ (void) memcpy(&ndn.dn_cid, pcd->cid, pcd->cid_len);
+ ndn.dn_cid_len = pcd->cid_len;
+
+ write_error = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn);
+
+ /* Keep state of the cached entry current. */
+ *dnp = ndn; /* struct copy */
+
+ log = L_ASSIGN;
+ } else {
+ if (verbose) {
+ dhcpmsg(LOG_INFO, "Database write unnecessary for "
+ "BOOTP client: %1$s, %2$s\n",
+ pcd->cidbuf, cipbuf);
+ }
+ log = L_REPLY;
+ }
+
+ if (write_error == DSVC_SUCCESS) {
+ if (send_reply(ifp, rep_pktp, pkt_len, &no_ip) != 0) {
+ dhcpmsg(LOG_ERR,
+ "Reply to BOOTP client %1$s with %2$s failed.\n",
+ pcd->cidbuf, cipbuf);
+ } else {
+ /* Note that the conversation has completed. */
+ pcd->state = ACK;
+
+ (void) update_offer(pcd, dnlp, 0, &no_ip, B_TRUE);
+ existing_offer = B_TRUE;
+ }
+
+ logtrans(P_BOOTP, log, ndn.dn_lease, no_ip, server_ip, plp);
+ }
+
+leave_bootp:
+ if (rep_pktp != NULL)
+ free(rep_pktp);
+ if (dncp != NULL)
+ dhcp_free_dd_list(pnd->dh, dncp);
+ if (dnip != NULL)
+ dhcp_free_dd_list(pnd->dh, dnip);
+ if (dnlp != NULL && !existing_offer)
+ dhcp_free_dd_list(pnd->dh, dnlp);
+}