/* * Assign a key to cliptr, or output cliptr's value as a key. * On assignment the value is specified in valstr in hexascii; * on output its value is printed in hexascii, provided the key * was entered at the interpreter (not obtained from OBP and * thus hidden). */ static int clkey(cli_ent_t *cliptr, char *valstr, boolean_t out) { uint_t len, vmax; if (out) { char buffer[2 * WANBOOT_MAXKEYLEN + 1]; if (!CLF_ISHIDDEN(cliptr)) { len = cliptr->varlen * 2 + 1; (void) octet_to_hexascii(cliptr->varptr, cliptr->varlen, buffer, &len); printf("%s", buffer); } else { printf("*HIDDEN*"); } return (CLI_CONT); } else { len = strlen(valstr); vmax = cliptr->varmax; if (len != vmax * 2 || hexascii_to_octet(valstr, len, cliptr->varptr, &vmax) != 0) { return (CLI_FAIL); } cliptr->varlen = vmax; CLF_CLRHIDDEN(cliptr); return (CLI_SET); } }
/* * Assign a client-id to cliptr, or output cliptr's value as a client-id. * On assignment the value is specified in valstr, either in hexascii or * as a quoted string; on output its value is printed in hexascii. */ static int clcid(cli_ent_t *cliptr, char *valstr, boolean_t out) { uint_t len, vmax; boolean_t hexascii = B_TRUE; char buffer[2 * WB_MAX_CID_LEN + 1]; if (out) { len = cliptr->varlen * 2 + 1; (void) octet_to_hexascii(cliptr->varptr, cliptr->varlen, buffer, &len); printf("%s", buffer); return (CLI_CONT); } else { len = strlen(valstr); vmax = cliptr->varmax - 1; /* space for the prefix */ /* * Check whether the value is a quoted string; if so, strip * the quotes and note that it's not in hexascii. */ if ((valstr[0] == '"' || valstr[0] == '\'') && valstr[len-1] == valstr[0]) { hexascii = B_FALSE; ++valstr; len -= 2; valstr[len] = '\0'; } else { /* * If the value contains any non-hex digits assume * that it's not in hexascii. */ char *p; for (p = valstr; *p != '\0'; ++p) { if (!isxdigit(*p)) { hexascii = B_FALSE; break; } } } if (hexascii) { if (len > vmax * 2 || hexascii_to_octet(valstr, len, (char *)(cliptr->varptr), &vmax) != 0) { return (CLI_FAIL); } cliptr->varlen = vmax; } else { if (len > vmax) { return (CLI_FAIL); } bcopy(valstr, cliptr->varptr, len); cliptr->varlen = len; } return (CLI_SET); } }
/* * dhcp_lookup_dd_classify: perform lookup_dd(), or use existing records * if supplied, and classify the results based on the type of search criteria * being employed. Centralized policy for DN_FMANUAL and DN_FUNUSABLE flag * processing are implemented here. Classification is specialized * based on these specific search criteria: * * S_CID A CID match is requested. Perform DN_FMANUAL and * DN_FUNUSABLE processing. * S_FREE A search for free records. Only examine first * matching record. * S_LRU A search for lru records. Perform sort if needed, * and only examine first matching record. * * A matching record is detached and returned if found (ok || * manual + unusable). Other successful matches are returned in recordsp as * a cache. */ void * dhcp_lookup_dd_classify(dsvc_dnet_t *pnd, boolean_t partial, uint_t query, int count, const dn_rec_t *targetp, void **recordsp, int searchtype) { int err; uint_t rec_cnt = 0, manual = 0; dn_rec_t *dnp; dn_rec_list_t *nlp = NULL, *dnlp = NULL; dn_rec_list_t *unulp = NULL; /* list of unusables, !manual */ dn_rec_list_t *unu_m_lp = NULL; /* list of unusable + manual */ dn_rec_list_t *m_lp = NULL; /* list of manual records */ dn_rec_list_t *cachep = NULL; /* match cache */ struct in_addr swapaddr; char ntoab[INET_ADDRSTRLEN]; /* * Lookup records matching the specified criteria, or use * records from a previous lookup supplied for classification. */ if (*recordsp == NULL) { TNF_PROBE_1_DEBUG(classify, "classify classify", "classify_query%debug 'in func classify'", tnf_long, query, query); err = dhcp_lookup_dd(pnd->dh, partial, query, count, targetp, (void **)recordsp, &rec_cnt); TNF_PROBE_1_DEBUG(classify_cid_end, "classify classify_end", "classify_end%debug 'in func classify'", tnf_long, rec_cnt, rec_cnt); /* * If any error occurs, mark the dsvc_dnet_t table * for immediate close and reopen. Let the protocol * perform recover, rather than attempting time-consuming * in-place error recovery. */ if (err != DSVC_SUCCESS) { (void) mutex_lock(&pnd->pnd_mtx); pnd->flags |= DHCP_PND_ERROR; hash_Dtime(pnd->hand, 0); (void) mutex_unlock(&pnd->pnd_mtx); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "classify failure %s\n", dhcpsvc_errmsg(err)); #endif /* DEBUG */ *recordsp = NULL; return (NULL); } /* * For LRU classification, sort returned records based * on dn_lease field. Discards records with valid lease * times; adjusts rec_cnt accordingly. */ if (searchtype & S_LRU) *recordsp = get_lrusort(pnd, *recordsp, &rec_cnt); } /* * Record classification: scan through all records, performing * DN_FUNUSABLE and DN_FMANUAL processing. Note that most of the * work has been performed by the datastore query. Remove the matching * entry from the singlely-linked record list, for return. Free any * non-matching entries prior to the match. Pass back any additional * entries after the match in the recordsp pointer for possible re-use * by the caching code. */ for (nlp = detach_dnrec_from_list(NULL, *recordsp, (dn_rec_list_t **)recordsp); nlp != NULL; nlp = detach_dnrec_from_list(NULL, *recordsp, (dn_rec_list_t **)recordsp)) { /* * If we find that there is a DN_FMANUAL entry that is * DN_FUNUSABLE, we fail the request, when performing a * CID search, even though there may be other CID matches. In * the CID case, those other CID matches are errors, because * there should be one and only one record for a client if that * record is marked as being DN_FMANUALly assigned. We tell * the user how many of those CID matches there are. If there * are no DN_FMANUAL records, the first matching record which * is USABLE wins. */ dnp = nlp->dnl_rec; if (dnp->dn_flags & DN_FUNUSABLE) { if ((searchtype & (S_CID|S_FREE|S_LRU)) == S_CID) { char cidbuf[DHCP_MAX_OPT_SIZE]; uint_t blen = sizeof (cidbuf); (void) octet_to_hexascii(targetp->dn_cid, targetp->dn_cid_len, cidbuf, &blen); swapaddr.s_addr = htonl(dnp->dn_cip.s_addr); dhcpmsg(LOG_NOTICE, "(%1$s,%2$s) " "currently marked as unusable.\n", cidbuf, inet_ntop(AF_INET, &swapaddr, ntoab, sizeof (ntoab))); } /* build list of unusable records */ if (dnp->dn_flags & DN_FMANUAL) { attach_dnrec_to_list(nlp, &unu_m_lp); manual++; } else attach_dnrec_to_list(nlp, &unulp); } else { if (dnp->dn_flags & DN_FMANUAL) { attach_dnrec_to_list(nlp, &m_lp); manual++; } else attach_dnrec_to_list(nlp, &cachep); /* * These searches do not require examining all * matches. */ if (searchtype & (S_FREE|S_LRU)) break; } } /* * Warnings are printed for CID searches which end with * DN_FUNUSABLE|DN_FMANUAL match(es). */ if (m_lp != NULL || unu_m_lp != NULL) { if (manual > 1) { char cidbuf[DHCP_MAX_OPT_SIZE]; uint_t blen = sizeof (cidbuf); (void) octet_to_hexascii(targetp->dn_cid, targetp->dn_cid_len, cidbuf, &blen); dhcpmsg(LOG_WARNING, "Manual allocation (%1$s) has %2$d other MANUAL" " records. It should have 0.\n", cidbuf, manual - 1); } if (unu_m_lp != NULL) { dnlp = detach_dnrec_from_list(NULL, unu_m_lp, &unu_m_lp); } else dnlp = detach_dnrec_from_list(NULL, m_lp, &m_lp); } /* Free any unusable entries */ if (unulp != NULL) dhcp_free_dd_list(pnd->dh, unulp); /* any other... */ if (dnlp == NULL) dnlp = detach_dnrec_from_list(NULL, cachep, &cachep); /* * Return any unused elements for possible caching use. These are * the additional manual + unusable (as punishment for having * multiple items), manual, and and any others. */ if (cachep != NULL) attach_dnrec_to_list(cachep, (dn_rec_list_t **)recordsp); if (m_lp != NULL) attach_dnrec_to_list(m_lp, (dn_rec_list_t **)recordsp); if (unu_m_lp != NULL) attach_dnrec_to_list(unu_m_lp, (dn_rec_list_t **)recordsp); /* * Return one of the matching record(s). */ return (dnlp); }
/* * 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]; ciaddr.s_addr = htonl(INADDR_ANY); #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); }