diff options
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.c | 407 |
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); +} |