1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved.
  23  */
  24 
  25 #include <stdio.h>
  26 #include <stdlib.h>
  27 #include <unistd.h>
  28 #include <stdarg.h>
  29 #include <sys/types.h>
  30 #include <errno.h>
  31 #include <assert.h>
  32 #include <string.h>
  33 #include <syslog.h>
  34 #include <sys/socket.h>
  35 #include <net/if.h>
  36 #include <netinet/in.h>
  37 #include <netinet/if_ether.h>
  38 #include <arpa/inet.h>
  39 #include <nss_dbdefs.h>
  40 #include <netinet/dhcp.h>
  41 #include <netdb.h>
  42 #include <dhcp_symbol.h>
  43 #include "dhcpd.h"
  44 #include "per_dnet.h"
  45 #include "interfaces.h"
  46 #include <locale.h>
  47 
  48 /*
  49  * This file contains the code which implements the BOOTP compatibility.
  50  */
  51 
  52 /*
  53  * We are guaranteed that the packet received is a BOOTP request packet,
  54  * e.g., *NOT* a DHCP packet.
  55  */
  56 void
  57 bootp(dsvc_clnt_t *pcd, PKT_LIST *plp)
  58 {
  59         boolean_t       result, existing_offer = B_FALSE;
  60         int             err, write_error = DSVC_SUCCESS, flags = 0;
  61         int             pkt_len;
  62         uint_t          crecords = 0, irecords = 0, srecords = 0, clen;
  63         uint32_t        query;
  64         PKT             *rep_pktp = NULL;
  65         IF              *ifp = pcd->ifp;
  66         dsvc_dnet_t     *pnd = pcd->pnd;
  67         uchar_t         *optp;
  68         dn_rec_t        dn, ndn, *dnp;
  69         dn_rec_list_t   *dncp = NULL, *dnip = NULL, *dnlp = NULL;
  70         struct in_addr  ciaddr;
  71         struct in_addr  no_ip;  /* network order IP */
  72         ENCODE          *ecp, *hecp;
  73         MACRO           *mp, *nmp, *cmp;
  74         time_t          now = time(NULL);
  75         DHCP_MSG_CATEGORIES     log;
  76         struct          hostent h, *hp;
  77         char            ntoab[INET_ADDRSTRLEN], cipbuf[INET_ADDRSTRLEN];
  78         char            cidbuf[DHCP_MAX_OPT_SIZE];
  79         char            hbuf[NSS_BUFLEN_HOSTS];
  80 
  81         ciaddr.s_addr = htonl(INADDR_ANY);
  82 
  83 #ifdef  DEBUG
  84         dhcpmsg(LOG_DEBUG, "BOOTP request received on %s\n", ifp->nm);
  85 #endif  /* DEBUG */
  86 
  87         if (pcd->off_ip.s_addr != htonl(INADDR_ANY) &&
  88             PCD_OFFER_TIMEOUT(pcd, now))
  89                 purge_offer(pcd, B_TRUE, B_TRUE);
  90 
  91         if (pcd->off_ip.s_addr != htonl(INADDR_ANY)) {
  92                 existing_offer = B_TRUE;
  93                 dnlp = pcd->dnlp;
  94                 assert(dnlp != NULL);
  95                 dnp = dnlp->dnl_rec;
  96                 no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
  97                 crecords = 1;
  98         } else {
  99                 /*
 100                  * Try to find a CID entry for the client. We don't care about
 101                  * lease info here, since a BOOTP client always has a permanent
 102                  * lease. We also don't care about the entry owner either,
 103                  * unless we end up allocating a new entry for the client.
 104                  */
 105                 DSVC_QINIT(query);
 106 
 107                 DSVC_QEQ(query, DN_QCID);
 108                 (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len);
 109                 dn.dn_cid_len = pcd->cid_len;
 110 
 111                 DSVC_QEQ(query, DN_QFBOOTP_ONLY);
 112                 dn.dn_flags = DN_FBOOTP_ONLY;
 113 
 114                 /*
 115                  * If a client address (ciaddr) is given, we simply trust that
 116                  * the client knows what it's doing, and we use that IP address
 117                  * to locate the client's record. If we can't find the client's
 118                  * record, then we keep silent. If the client id of the record
 119                  * doesn't match this client, then either the client or our
 120                  * database is inconsistent, and we'll ignore it (notice
 121                  * message generated).
 122                  */
 123                 ciaddr.s_addr = plp->pkt->ciaddr.s_addr;
 124                 if (ciaddr.s_addr != htonl(INADDR_ANY)) {
 125                         DSVC_QEQ(query, DN_QCIP);
 126                         dn.dn_cip.s_addr = ntohl(ciaddr.s_addr);
 127                 }
 128 
 129                 dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query,
 130                     -1, &dn, (void **)&dncp, S_CID);
 131                 if (dnlp != NULL) {
 132                         crecords = 1;
 133                         dnp = dnlp->dnl_rec;
 134                         if (dnp->dn_flags & DN_FUNUSABLE)
 135                                 goto leave_bootp;
 136                         no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
 137                 }
 138         }
 139 
 140         (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
 141 
 142         if (crecords == 0 && !be_automatic) {
 143                 if (verbose) {
 144                         dhcpmsg(LOG_INFO, "BOOTP client: %1$s is looking for "
 145                             "a configuration on net %2$s\n", pcd->cidbuf,
 146                             pnd->network);
 147                 }
 148                 goto leave_bootp;
 149         }
 150 
 151         /*
 152          * If the client thinks it knows who it is (ciaddr), and this doesn't
 153          * match our registered IP address, then display an error message and
 154          * give up.
 155          */
 156         if (ciaddr.s_addr != htonl(INADDR_ANY) && crecords == 0) {
 157                 /*
 158                  * If the client specified an IP address, then let's check
 159                  * whether it is available, since we have no CID mapping
 160                  * registered for this client. If it is available and
 161                  * unassigned but owned by a different server, we ignore the
 162                  * client.
 163                  */
 164                 DSVC_QINIT(query);
 165 
 166                 DSVC_QEQ(query, DN_QCIP);
 167                 dn.dn_cip.s_addr = ntohl(ciaddr.s_addr);
 168                 (void) inet_ntop(AF_INET, &ciaddr, cipbuf, sizeof (cipbuf));
 169 
 170                 DSVC_QEQ(query, DN_QFBOOTP_ONLY);
 171                 dn.dn_flags = DN_FBOOTP_ONLY;
 172 
 173                 dnip = NULL;
 174                 dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query,
 175                     -1, &dn, (void **)&dncp, S_CID);
 176                 if (dnlp == NULL) {
 177                         /*
 178                          * We have no record of this client's IP address, thus
 179                          * we really can't respond to this client, because it
 180                          * doesn't have a configuration.
 181                          */
 182                         if (verbose) {
 183                                 dhcpmsg(LOG_INFO, "No configuration for BOOTP "
 184                                     "client: %1$s. IP address: %2$s not "
 185                                     "administered by this server.\n",
 186                                     pcd->cidbuf, inet_ntop(AF_INET, &ciaddr,
 187                                     ntoab, sizeof (ntoab)));
 188                         }
 189                         goto leave_bootp;
 190                 } else
 191                         irecords = 1;
 192 
 193                 dnp = dnlp->dnl_rec;
 194                 if (dnp->dn_flags & DN_FUNUSABLE)
 195                         goto leave_bootp;
 196 
 197                 if (dn.dn_cid_len != 0) {
 198                         if (dn.dn_cid_len != pcd->cid_len || memcmp(dn.dn_cid,
 199                             pcd->cid, pcd->cid_len) != 0) {
 200                                 if (verbose) {
 201                                         clen = sizeof (cidbuf);
 202                                         (void) octet_to_hexascii(dn.dn_cid,
 203                                             dn.dn_cid_len, cidbuf, &clen);
 204                                         dhcpmsg(LOG_INFO, "BOOTP client: %1$s "
 205                                             "thinks it owns %2$s, but that "
 206                                             "address belongs to %3$s. Ignoring "
 207                                             "client.\n", pcd->cidbuf, cipbuf,
 208                                             cidbuf);
 209                                 }
 210                                 goto leave_bootp;
 211                         }
 212                 } else {
 213                         if (match_ownerip(htonl(dn.dn_sip.s_addr)) == NULL) {
 214                                 if (verbose) {
 215                                         no_ip.s_addr =
 216                                             htonl(dnp->dn_sip.s_addr);
 217                                         dhcpmsg(LOG_INFO, "BOOTP client: %1$s "
 218                                             "believes it owns %2$s. That "
 219                                             "address is free, but is owned by "
 220                                             "DHCP server %3$s. Ignoring "
 221                                             "client.\n", pcd->cidbuf, cipbuf,
 222                                             inet_ntop(AF_INET, &no_ip, ntoab,
 223                                             sizeof (ntoab)));
 224                                 }
 225                                 goto leave_bootp;
 226                         }
 227                 }
 228                 no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
 229                 (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
 230         }
 231 
 232         if (crecords == 0) {
 233                 /*
 234                  * The dhcp-network table did not have any matching entries.
 235                  * Try to allocate a new one if possible.
 236                  */
 237                 if (irecords == 0 && select_offer(pnd, plp, pcd, &dnlp)) {
 238                         dnp = dnlp->dnl_rec;
 239                         no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
 240                         (void) inet_ntop(AF_INET, &no_ip, cipbuf,
 241                             sizeof (cipbuf));
 242                         srecords = 1;
 243                 }
 244         }
 245 
 246         if (crecords == 0 && irecords == 0 && srecords == 0) {
 247                 dhcpmsg(LOG_NOTICE,
 248                     "(%1$s) No more BOOTP IP addresses for %2$s network.\n",
 249                     pcd->cidbuf, pnd->network);
 250                 goto leave_bootp;
 251         }
 252 
 253         /* Check the address. But only if client doesn't know its address. */
 254         ndn = *dnp;     /* struct copy */
 255         no_ip.s_addr = htonl(ndn.dn_cip.s_addr);
 256         (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
 257         if (ciaddr.s_addr == htonl(INADDR_ANY)) {
 258                 if ((ifp->flags & IFF_NOARP) == 0)
 259                         (void) set_arp(ifp, &no_ip, NULL, 0, DHCP_ARP_DEL);
 260                 if (!noping) {
 261                         /*
 262                          * If icmp echo check fails,
 263                          * let the plp fall by the wayside.
 264                          */
 265                         errno = icmp_echo_check(&no_ip, &result);
 266                         if (errno != 0) {
 267                                 dhcpmsg(LOG_ERR, "ICMP ECHO check cannot be "
 268                                     "performed for: %s, ignoring\n", cipbuf);
 269                                 goto leave_bootp;
 270                         }
 271                         if (result) {
 272                                 dhcpmsg(LOG_ERR, "ICMP ECHO reply to BOOTP "
 273                                     "OFFER candidate: %s, disabling.\n",
 274                                     cipbuf);
 275 
 276                                 ndn.dn_flags |= DN_FUNUSABLE;
 277 
 278                                 if ((err = dhcp_modify_dd_entry(pnd->dh,
 279                                     dnp, &ndn)) == DSVC_SUCCESS) {
 280                                         /* Keep the cached entry current. */
 281                                         *dnp = ndn;    /* struct copy */
 282                                 }
 283 
 284                                 logtrans(P_BOOTP, L_ICMP_ECHO, 0, no_ip,
 285                                     server_ip, plp);
 286 
 287                                 goto leave_bootp;
 288                         }
 289                 }
 290         }
 291 
 292         /*
 293          * It is possible that the client could specify a REQUEST list,
 294          * but then it would be a DHCP client, wouldn't it? Only copy the
 295          * std option list, since that potentially could be changed by
 296          * load_options().
 297          */
 298         ecp = NULL;
 299         if (!no_dhcptab) {
 300                 open_macros();
 301                 if ((nmp = get_macro(pnd->network)) != NULL)
 302                         ecp = dup_encode_list(nmp->head);
 303                 if ((mp = get_macro(dnp->dn_macro)) != NULL)
 304                         ecp = combine_encodes(ecp, mp->head, ENC_DONT_COPY);
 305                 if ((cmp = get_macro(pcd->cidbuf)) != NULL)
 306                         ecp = combine_encodes(ecp, cmp->head, ENC_DONT_COPY);
 307 
 308                 /* If dhcptab configured to return hostname, do so. */
 309                 if (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) !=
 310                     NULL) {
 311                         hp = gethostbyaddr_r((char *)&ndn.dn_cip,
 312                             sizeof (struct in_addr), AF_INET, &h, hbuf,
 313                             sizeof (hbuf), &err);
 314                         if (hp != NULL) {
 315                                 hecp = make_encode(DSYM_STANDARD,
 316                                     CD_HOSTNAME, strlen(hp->h_name),
 317                                     hp->h_name, ENC_COPY);
 318                                 replace_encode(&ecp, hecp, ENC_DONT_COPY);
 319                         }
 320                 }
 321         }
 322 
 323         /* Produce a BOOTP reply. */
 324         rep_pktp = gen_bootp_pkt(sizeof (PKT), plp->pkt);
 325 
 326         rep_pktp->op = BOOTREPLY;
 327         optp = rep_pktp->options;
 328 
 329         /*
 330          * Set the client's "your" IP address if client doesn't know it,
 331          * otherwise echo the client's ciaddr back to him.
 332          */
 333         if (ciaddr.s_addr == htonl(INADDR_ANY))
 334                 rep_pktp->yiaddr.s_addr = htonl(ndn.dn_cip.s_addr);
 335         else
 336                 rep_pktp->ciaddr.s_addr = ciaddr.s_addr;
 337 
 338         /*
 339          * Omit lease time options implicitly, e.g.
 340          * ~(DHCP_DHCP_CLNT | DHCP_SEND_LEASE)
 341          */
 342 
 343         if (!plp->rfc1048)
 344                 flags |= DHCP_NON_RFC1048;
 345 
 346         /* Now load in configured options. */
 347         pkt_len = load_options(flags, plp, rep_pktp, sizeof (PKT), optp, ecp,
 348             NULL);
 349 
 350         free_encode_list(ecp);
 351         if (!no_dhcptab)
 352                 close_macros();
 353 
 354         if (pkt_len < sizeof (PKT))
 355                 pkt_len = sizeof (PKT);
 356 
 357         /*
 358          * Only perform a write if we have selected an entry not yet
 359          * assigned to the client (a matching DN_FBOOTP_ONLY entry from
 360          * ip address lookup, or an unassigned entry from select_offer()).
 361          */
 362         if (srecords > 0 || irecords > 0) {
 363                 (void) memcpy(&ndn.dn_cid, pcd->cid, pcd->cid_len);
 364                 ndn.dn_cid_len = pcd->cid_len;
 365 
 366                 write_error = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn);
 367 
 368                 /* Keep state of the cached entry current. */
 369                 *dnp = ndn;     /* struct copy */
 370 
 371                 log = L_ASSIGN;
 372         } else {
 373                 if (verbose) {
 374                         dhcpmsg(LOG_INFO, "Database write unnecessary for "
 375                             "BOOTP client: %1$s, %2$s\n",
 376                             pcd->cidbuf, cipbuf);
 377                 }
 378                 log = L_REPLY;
 379         }
 380 
 381         if (write_error == DSVC_SUCCESS) {
 382                 if (send_reply(ifp, rep_pktp, pkt_len, &no_ip) != 0) {
 383                         dhcpmsg(LOG_ERR,
 384                             "Reply to BOOTP client %1$s with %2$s failed.\n",
 385                             pcd->cidbuf, cipbuf);
 386                 } else {
 387                         /* Note that the conversation has completed. */
 388                         pcd->state = ACK;
 389 
 390                         (void) update_offer(pcd, &dnlp, 0, &no_ip, B_TRUE);
 391                         existing_offer = B_TRUE;
 392                 }
 393 
 394                 logtrans(P_BOOTP, log, ndn.dn_lease, no_ip, server_ip, plp);
 395         }
 396 
 397 leave_bootp:
 398         if (rep_pktp != NULL)
 399                 free(rep_pktp);
 400         if (dncp != NULL)
 401                 dhcp_free_dd_list(pnd->dh, dncp);
 402         if (dnip != NULL)
 403                 dhcp_free_dd_list(pnd->dh, dnip);
 404         if (dnlp != NULL && !existing_offer)
 405                 dhcp_free_dd_list(pnd->dh, dnlp);
 406 }