2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * PSARC/2004/154 nfsmapid DNS enhancements implementation.
2N/A *
2N/A * As per RFC 3530, file owner and group attributes in version 4 of the
2N/A * NFS protocol are no longer exchanged between client and server as 32
2N/A * bit integral values. Instead, owner and group file attributes are
2N/A * exchanged between client and server as UTF8 strings of form
2N/A *
2N/A * 'user@domain' (ie. "joeblow@central.sun.com")
2N/A * 'group@domain' (ie. "staff@central.sun.com")
2N/A *
2N/A * This NFSv4 feature is far beyond anything NFSv2/v3 ever provided, as
2N/A * being able to describe a user with a unique string identifier provides
2N/A * a much more powerful and administrative friendly way of dealing with
2N/A * overlaps in the uid/gid number spaces. That notwithstanding, dealing
2N/A * with issues of correctly mapping user and group ownership in a cross-
2N/A * domain environment has proven a difficult problem to solve, since
2N/A * dealing with different permutations of client naming configurations
2N/A * (ie. NIS only, LDAP only, etc.) have bloated the problem. Thus, users
2N/A * utilizing clients and servers that have the 'domain' portion of the
2N/A * UTF8 attribute string configured differently than its peer server and
2N/A * client accordingly, will experience watching their files owned by the
2N/A * 'nobody' user and group. This is due to the fact that the 'domain's
2N/A * don't match and the nfsmapid daemon treats the attribute strings as
2N/A * unknown user(s) or group(s) (even though the actual uid/gid's may exist
2N/A * in the executing daemon's system). Please refer to PSARC/2004/154 for
2N/A * further background and motivation for these enhancements.
2N/A *
2N/A * The latest implementation of the nfsmapid daemon relies on a DNS TXT
2N/A * record. The behavior of nfsmapid is to first use the NFSMAPID_DOMAIN
2N/A * configuration option in /etc/default/nfs. If the option has not been
2N/A * set, then the nfsmapid daemon queries the configured DNS domain server
2N/A * for the _nfsv4idmapdomain TXT record. If the record exists, then the
2N/A * record's value is used as the 'domain' portion of the UTF8 attribute
2N/A * strings. If the TXT record has not been configured in the DNS server,
2N/A * then the daemon falls back to using the DNS domain name itself as the
2N/A * 'domain' portion of the attribute strings. Lastly, if the configured
2N/A * DNS server is unresponsive, the nfsmapid daemon falls back to using
2N/A * the DNS domain name as the 'domain' portion of the attribute strings,
2N/A * and fires up a query thread to keep contacting the DNS server until
2N/A * it responds with either a TXT record, or a lack thereof, in which
2N/A * case, nfsmapid just continues to utilize the DNS domain name.
2N/A */
2N/A#define __LIBMAPID_IMPL
2N/A#include <nfs/mapid.h>
2N/A#include <libscf.h>
2N/A#include <limits.h>
2N/A#include <rpcsvc/daemon_utils.h>
2N/A#include "smfcfg.h"
2N/A
2N/A#pragma init(_lib_init)
2N/A#pragma fini(_lib_fini)
2N/A
2N/A/*
2N/A * DEBUG Only
2N/A * Decode any resolver errors and print out message to log
2N/A */
2N/Astatic int
2N/Aresolv_error(void)
2N/A{
2N/A#ifndef DEBUG
2N/A
2N/A return (h_errno);
2N/A
2N/A#else /* DEBUG */
2N/A
2N/A static uint64_t msg_done[NS_ERRS] = {0};
2N/A
2N/A switch (h_errno) {
2N/A case NETDB_INTERNAL:
2N/A syslog(LOG_ERR, EMSG_NETDB_INTERNAL, strerror(errno));
2N/A break;
2N/A
2N/A case HOST_NOT_FOUND:
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A msg_done[h_errno]++;
2N/A if (!(msg_done[h_errno] % NFSMAPID_SLOG_RATE))
2N/A syslog(LOG_ERR, EMSG_HOST_NOT_FOUND, s_dname);
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A break;
2N/A
2N/A case TRY_AGAIN:
2N/A /*
2N/A * Nameserver is not responding.
2N/A * Try again after a given timeout.
2N/A */
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A msg_done[h_errno]++;
2N/A if (!(msg_done[h_errno] % NFSMAPID_SLOG_RATE))
2N/A syslog(LOG_ERR, EMSG_TRY_AGAIN, s_dname);
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A break;
2N/A
2N/A case NO_RECOVERY:
2N/A /*
2N/A * This msg only really happens once, due
2N/A * to s_dns_disabled flag (see below)
2N/A */
2N/A syslog(LOG_ERR, EMSG_NO_RECOVERY, hstrerror(h_errno));
2N/A break;
2N/A
2N/A case NO_DATA:
2N/A /*
2N/A * No entries in the nameserver for
2N/A * the specific record or record type.
2N/A */
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A msg_done[h_errno]++;
2N/A if (!(msg_done[h_errno] % NFSMAPID_SLOG_RATE))
2N/A syslog(LOG_ERR, EMSG_NO_DATA, NFSMAPID_DNS_RR, s_dname);
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A break;
2N/A
2N/A case NETDB_SUCCESS:
2N/A default:
2N/A break;
2N/A }
2N/A return (h_errno);
2N/A
2N/A#endif /* DEBUG */
2N/A}
2N/A
2N/A/*
2N/A * Reset the global state variables used for the TXT record.
2N/A * Having these values reset to zero helps nfsmapid confirm
2N/A * that a valid DNS TXT record was not found; in which case,
2N/A * it would fall back to using the configured DNS domain name.
2N/A *
2N/A * If a valid DNS TXT record _was_ found, but subsequent contact
2N/A * to the DNS server is somehow hindered, the previous DNS TXT
2N/A * RR value continues to be used. Thus, in such instances, we
2N/A * forego clearing the global config variables so nfsmapid can
2N/A * continue to use a valid DNS TXT RR while contact to the DNS
2N/A * server is reestablished.
2N/A */
2N/Astatic void
2N/Aresolv_txt_reset(void)
2N/A{
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A bzero(s_txt_rr, sizeof (s_txt_rr));
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A if (!dns_txt_cached) {
2N/A dns_txt_domain_len = 0;
2N/A bzero(dns_txt_domain, DNAMEMAX);
2N/A }
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A}
2N/A
2N/A/*
2N/A * Initialize resolver and populate &s_res struct
2N/A *
2N/A * DNS Domain is saved off sysdns_domain in case we
2N/A * need to fall back to using the DNS domain name as
2N/A * the v4 attribute string domain.
2N/A */
2N/Astatic int
2N/Aresolv_init(void)
2N/A{
2N/A size_t len;
2N/A int n;
2N/A struct __res_state res;
2N/A
2N/A (void) mutex_lock(&s_res_lock);
2N/A bzero(&s_res, sizeof (struct __res_state));
2N/A n = h_errno = errno = 0;
2N/A if ((n = res_ninit(&s_res)) < 0) {
2N/A (void) mutex_unlock(&s_res_lock);
2N/A (void) resolv_error();
2N/A return (n);
2N/A }
2N/A res = s_res;
2N/A (void) mutex_unlock(&s_res_lock);
2N/A
2N/A len = strlen(res.defdname) + 1;
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A bzero(s_dname, sizeof (s_dname));
2N/A (void) snprintf(s_dname, len, "%s", res.defdname);
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A (void) snprintf(sysdns_domain, len, "%s", res.defdname);
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Search criteria assumptions:
2N/A *
2N/A * The onus will fall on the sysadmins to correctly configure the TXT
2N/A * record in the DNS domain where the box currently resides in order
2N/A * for the record to be found. However, if they sysadmin chooses to
2N/A * add the 'search' key to /etc/resolv.conf, then resolv_search()
2N/A * _will_ traverse up the DNS tree as specified in the 'search' key.
2N/A * Otherwise, we'll default the domain to the DNS domain itself.
2N/A */
2N/Astatic int
2N/Aresolv_search(void)
2N/A{
2N/A int len;
2N/A ans_t ans = {0};
2N/A struct __res_state res;
2N/A int type = T_TXT;
2N/A int class = C_IN;
2N/A
2N/A (void) mutex_lock(&s_res_lock);
2N/A res = s_res;
2N/A (void) mutex_unlock(&s_res_lock);
2N/A
2N/A /*
2N/A * Avoid holding locks across the res_nsearch() call to
2N/A * prevent stalling threads during network partitions.
2N/A */
2N/A len = h_errno = errno = 0;
2N/A if ((len = res_nsearch(&res, NFSMAPID_DNS_RR, class, type,
2N/A ans.buf, sizeof (ans))) < 0)
2N/A return (resolv_error());
2N/A
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A s_ans = ans;
2N/A s_anslen = len;
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A return (NETDB_SUCCESS);
2N/A}
2N/A
2N/A/*
2N/A * Free all resolver state information stored in s_res
2N/A */
2N/Astatic void
2N/Aresolv_destroy(void)
2N/A{
2N/A (void) mutex_lock(&s_res_lock);
2N/A res_ndestroy(&s_res);
2N/A (void) mutex_unlock(&s_res_lock);
2N/A}
2N/A
2N/A/*
2N/A * Skip one DNS record
2N/A */
2N/Astatic uchar_t *
2N/Aresolv_skip_rr(uchar_t *p, uchar_t *eom)
2N/A{
2N/A int t;
2N/A int dlen;
2N/A
2N/A /*
2N/A * Skip compressed name
2N/A */
2N/A errno = 0;
2N/A if ((t = dn_skipname(p, eom)) < 0) {
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s", strerror(errno));
2N/A#endif
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * Advance pointer and make sure
2N/A * we're still within the message
2N/A */
2N/A p += t;
2N/A if ((p + RRFIXEDSZ) > eom)
2N/A return (NULL);
2N/A
2N/A /*
2N/A * Now, just skip over the rr fields
2N/A */
2N/A p += INT16SZ; /* type */
2N/A p += INT16SZ; /* class */
2N/A p += INT32SZ; /* ttl */
2N/A dlen = ns_get16(p);
2N/A p += INT16SZ;
2N/A p += dlen; /* dlen */
2N/A if (p > eom)
2N/A return (NULL);
2N/A
2N/A return (p);
2N/A}
2N/A
2N/A/*
2N/A * Process one TXT record.
2N/A *
2N/A * nfsmapid queries the DNS server for the specific _nfsv4idmapdomain
2N/A * TXT record. Thus, if the TXT record exists, the answer section of
2N/A * the DNS response carries the TXT record's value. Thus, we check that
2N/A * the value is indeed a valid domain and set the modular s_txt_rr
2N/A * global to the domain value.
2N/A */
2N/Astatic void
2N/Aresolve_process_txt(uchar_t *p, int dlen)
2N/A{
2N/A char *rr_base = (char *)(p + 1);
2N/A char *rr_end = (char *)(p + dlen);
2N/A size_t len = rr_end - rr_base;
2N/A#ifdef DEBUG
2N/A static uint64_t msg_done = 0;
2N/A#endif
2N/A char tmp_txt_rr[DNAMEMAX];
2N/A
2N/A if (len >= DNAMEMAX)
2N/A return; /* process next TXT RR */
2N/A
2N/A /*
2N/A * make sure we have a clean buf since
2N/A * we may've processed several TXT rr's
2N/A */
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A bzero(s_txt_rr, sizeof (s_txt_rr));
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A (void) strncpy(tmp_txt_rr, rr_base, len);
2N/A tmp_txt_rr[len] = '\0';
2N/A
2N/A /*
2N/A * If there is a record and it's a valid domain, we're done.
2N/A */
2N/A if (rr_base[0] != '\0' && mapid_stdchk_domain(tmp_txt_rr) > 0) {
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A (void) strncpy(s_txt_rr, rr_base, len);
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "TXT (Rec):\t%s", s_txt_rr);
2N/A
2N/A } else if (!(msg_done++ % NFSMAPID_SLOG_RATE)) {
2N/A /*
2N/A * Otherwise, log the error
2N/A */
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A syslog(LOG_ERR, EMSG_DNS_RR_INVAL, NFSMAPID_DNS_RR, s_dname);
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A#endif
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Decode any answer received from the DNS server. This interface is
2N/A * capable of much more than just decoding TXT records. We maintain
2N/A * focus on TXT rr's for now, but this will probably change once we
2N/A * get the IETF approved application specific DNS RR.
2N/A *
2N/A * Here's an example of the TXT record we're decoding (as would appear
2N/A * in the DNS zone file):
2N/A *
2N/A * _nfsv4idmapdomain IN TXT "sun.com"
2N/A *
2N/A * Once the IETF application specific DNS RR is granted, we should only
2N/A * be changing the record flavor, but all should pretty much stay the
2N/A * same.
2N/A */
2N/Astatic void
2N/Aresolv_decode(void)
2N/A{
2N/A uchar_t *buf;
2N/A HEADER *hp;
2N/A uchar_t name[DNAMEMAX];
2N/A uchar_t *eom;
2N/A uchar_t *p;
2N/A int n;
2N/A uint_t qd_cnt;
2N/A uint_t an_cnt;
2N/A uint_t ns_cnt;
2N/A uint_t ar_cnt;
2N/A uint_t cnt;
2N/A uint_t type;
2N/A int dlen;
2N/A ans_t answer = {0};
2N/A int answer_len = 0;
2N/A
2N/A /*
2N/A * Check the HEADER for any signs of errors
2N/A * and extract the answer counts for later.
2N/A */
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A answer = s_ans;
2N/A answer_len = s_anslen;
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A buf = (uchar_t *)&answer.buf;
2N/A hp = (HEADER *)&answer.hdr;
2N/A eom = (uchar_t *)(buf + answer_len);
2N/A if (hp->rcode != NOERROR) {
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "errno: %s", strerror(errno));
2N/A syslog(LOG_ERR, "h_errno: %s", hstrerror(h_errno));
2N/A#endif
2N/A return;
2N/A }
2N/A qd_cnt = ntohs(hp->qdcount);
2N/A an_cnt = ntohs(hp->ancount);
2N/A ns_cnt = ntohs(hp->nscount);
2N/A ar_cnt = ntohs(hp->arcount);
2N/A
2N/A /*
2N/A * skip query entries
2N/A */
2N/A p = (uchar_t *)(buf + HFIXEDSZ);
2N/A errno = 0;
2N/A while (qd_cnt-- > 0) {
2N/A n = dn_skipname(p, eom);
2N/A if (n < 0) {
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s", strerror(errno));
2N/A#endif
2N/A return;
2N/A }
2N/A p += n;
2N/A p += INT16SZ; /* type */
2N/A p += INT16SZ; /* class */
2N/A }
2N/A
2N/A#ifdef DEBUG
2N/A /*
2N/A * If debugging... print query only once.
2N/A * NOTE: Don't advance pointer... this is done
2N/A * in while() loop on a per record basis !
2N/A */
2N/A n = h_errno = errno = 0;
2N/A n = dn_expand(buf, eom, p, (char *)name, sizeof (name));
2N/A if (n < 0) {
2N/A (void) resolv_error();
2N/A return;
2N/A }
2N/A syslog(LOG_ERR, "Query:\t\t%-30s", name);
2N/A#endif
2N/A
2N/A /*
2N/A * Process actual answer(s).
2N/A */
2N/A cnt = an_cnt;
2N/A while (cnt-- > 0 && p < eom) {
2N/A /* skip the name field */
2N/A n = dn_expand(buf, eom, p, (char *)name, sizeof (name));
2N/A if (n < 0) {
2N/A (void) resolv_error();
2N/A return;
2N/A }
2N/A p += n;
2N/A
2N/A if ((p + 3 * INT16SZ + INT32SZ) > eom)
2N/A return;
2N/A
2N/A type = ns_get16(p);
2N/A p += INT16SZ;
2N/A p += INT16SZ + INT32SZ; /* skip class & ttl */
2N/A dlen = ns_get16(p);
2N/A p += INT16SZ;
2N/A
2N/A if ((p + dlen) > eom)
2N/A return;
2N/A
2N/A switch (type) {
2N/A case T_TXT:
2N/A resolve_process_txt(p, dlen);
2N/A break;
2N/A
2N/A default:
2N/A /*
2N/A * Advance to next answer record for any
2N/A * other record types. Again, this will
2N/A * probably change (see block comment).
2N/A */
2N/A p += dlen;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Skip name server and additional records for now.
2N/A */
2N/A cnt = ns_cnt + ar_cnt;
2N/A if (cnt > 0) {
2N/A while (--cnt != 0 && p < eom) {
2N/A p = resolv_skip_rr(p, eom);
2N/A if (p == NULL)
2N/A return;
2N/A }
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * If a valid TXT record entry exists, s_txt_rr contains the domain
2N/A * value (as set in resolv_process_txt) and we extract the value into
2N/A * dns_txt_domain (the exported global). If there was _no_ valid TXT
2N/A * entry, we simply return and check_domain() will default to the
2N/A * DNS domain since we did resolv_txt_reset() first.
2N/A */
2N/Astatic void
2N/Aresolv_get_txt_data()
2N/A{
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A if (s_txt_rr[0] != '\0') {
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A (void) snprintf(dns_txt_domain, strlen(s_txt_rr) + 1, "%s",
2N/A s_txt_rr);
2N/A dns_txt_domain_len = strlen(dns_txt_domain);
2N/A dns_txt_cached = 1;
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A }
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A}
2N/A
2N/Astatic void
2N/Adomain_sync(cb_t *argp, char *dname)
2N/A{
2N/A int dlen = 0;
2N/A void *(*fcn)(void *) = NULL;
2N/A int sighup = 0;
2N/A int domchg = 0;
2N/A
2N/A /*
2N/A * Make sure values passed are sane and initialize accordingly.
2N/A */
2N/A if (dname != NULL)
2N/A dlen = strlen(dname);
2N/A if (argp) {
2N/A if (argp->fcn)
2N/A fcn = argp->fcn;
2N/A if (argp->signal)
2N/A sighup = argp->signal;
2N/A }
2N/A
2N/A /*
2N/A * Update the library's mapid_domain variable if 'dname' is different.
2N/A */
2N/A if (dlen != 0 && strncasecmp(dname, mapid_domain, NS_MAXCDNAME)) {
2N/A (void) rw_wrlock(&mapid_domain_lock);
2N/A (void) strncpy(mapid_domain, dname, NS_MAXCDNAME);
2N/A mapid_domain_len = dlen;
2N/A (void) rw_unlock(&mapid_domain_lock);
2N/A domchg++;
2N/A }
2N/A
2N/A /*
2N/A * If the caller gave us a valid callback routine, we
2N/A * instantiate it to announce the domain change, but
2N/A * only if either the domain changed _or_ the caller
2N/A * was issued a SIGHUP.
2N/A */
2N/A if (fcn != NULL && (sighup || domchg))
2N/A (void) fcn((void *)mapid_domain);
2N/A}
2N/A
2N/A/*
2N/A * Thread to keep pinging DNS server for TXT record if nfsmapid's
2N/A * initial attempt at contact with server failed. We could potentially
2N/A * have a substantial number of NFSv4 clients and having all of them
2N/A * hammering on an already unresponsive DNS server would not help
2N/A * things. So, we limit the number of live query threads to at most
2N/A * 1 at any one time to keep things from getting out of hand.
2N/A */
2N/A/* ARGSUSED */
2N/Astatic void *
2N/Aresolv_query_thread(void *arg)
2N/A{
2N/A unsigned int nap_time;
2N/A
2N/A#ifdef DEBUG
2N/A char *whoami = "query_thread";
2N/A
2N/A syslog(LOG_ERR, "%s active !", whoami);
2N/A#endif
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A nap_time = s_dns_tout;
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A for (;;) {
2N/A (void) sleep(nap_time);
2N/A
2N/A resolv_txt_reset();
2N/A if (resolv_init() < 0) {
2N/A /*
2N/A * Failed to initialize resolver. Do not
2N/A * query DNS server.
2N/A */
2N/A goto thr_reset;
2N/A }
2N/A switch (resolv_search()) {
2N/A case NETDB_SUCCESS:
2N/A resolv_decode();
2N/A resolv_get_txt_data();
2N/A
2N/A /*
2N/A * This is a bit different than what we
2N/A * do in get_dns_txt_domain(), where we
2N/A * simply return and let the caller
2N/A * access dns_txt_domain directly.
2N/A *
2N/A * Here we invoke the callback routine
2N/A * provided by the caller to the
2N/A * mapid_reeval_domain() interface via
2N/A * the cb_t's fcn param.
2N/A */
2N/A domain_sync((cb_t *)arg, dns_txt_domain);
2N/A goto thr_okay;
2N/A
2N/A case NO_DATA:
2N/A /*
2N/A * DNS is up now, but does not have
2N/A * the NFSV4IDMAPDOMAIN TXT record.
2N/A */
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s: DNS has no TXT Record", whoami);
2N/A#endif
2N/A goto thr_reset;
2N/A
2N/A case NO_RECOVERY:
2N/A /*
2N/A * Non-Recoverable error occurred. No sense
2N/A * in keep pinging the DNS server at this
2N/A * point, so we disable any further contact.
2N/A */
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, EMSG_DNS_DISABLE, whoami);
2N/A#endif
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A s_dns_disabled = TRUE;
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A goto thr_reset;
2N/A
2N/A case HOST_NOT_FOUND:
2N/A /*
2N/A * Authoritative NS not responding...
2N/A * keep trying for non-authoritative reply
2N/A */
2N/A /*FALLTHROUGH*/
2N/A
2N/A case TRY_AGAIN:
2N/A /* keep trying */
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s: retrying...", whoami);
2N/A#endif
2N/A break;
2N/A
2N/A case NETDB_INTERNAL:
2N/A default:
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s: Internal resolver error: %s",
2N/A whoami, strerror(errno));
2N/A#endif
2N/A goto thr_reset;
2N/A }
2N/A
2N/A resolv_destroy();
2N/A }
2N/Athr_reset:
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A dns_txt_cached = 0;
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A resolv_txt_reset();
2N/A
2N/Athr_okay:
2N/A resolv_destroy();
2N/A /* mark thread as done */
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A s_dns_qthr_created = FALSE;
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A (void) thr_exit(NULL);
2N/A /*NOTREACHED*/
2N/A return (NULL);
2N/A}
2N/A
2N/A/*
2N/A * nfsmapid's interface into the resolver for getting the TXT record.
2N/A *
2N/A * Key concepts:
2N/A *
2N/A * o If the DNS server is available and the TXT record is found, we
2N/A * simply decode the output and fill the exported dns_txt_domain
2N/A * global, so our caller can configure the daemon appropriately.
2N/A *
2N/A * o If the TXT record is not found, then having done resolv_txt_reset()
2N/A * first will allow our caller to recognize that the exported globals
2N/A * are empty and thus configure nfsmapid to use the default DNS domain.
2N/A *
2N/A * o Having no /etc/resolv.conf file is pretty much a show stopper, since
2N/A * there is no name server address information. We return since we've
2N/A * already have reset the TXT global state.
2N/A *
2N/A * o If a previous call to the DNS server resulted in an unrecoverable
2N/A * error, then we disable further contact to the DNS server and return.
2N/A * Having the TXT global state already reset guarantees that our caller
2N/A * will fall back to the right configuration.
2N/A *
2N/A * o Query thread creation is throttled by s_dns_qthr_created. We mitigate
2N/A * the problem of an already unresponsive DNS server by allowing at most
2N/A * 1 outstanding query thread since we could potentially have a substantial
2N/A * amount of clients hammering on the same DNS server attempting to get
2N/A * the TXT record.
2N/A */
2N/Astatic void
2N/Aget_dns_txt_domain(cb_t *argp)
2N/A{
2N/A int err;
2N/A#ifdef DEBUG
2N/A static uint64_t msg_done = 0;
2N/A char *whoami = "get_dns_txt_domain";
2N/A#endif
2N/A long thr_flags = THR_DETACHED;
2N/A struct stat st;
2N/A
2N/A /*
2N/A * We reset TXT variables first in case /etc/resolv.conf
2N/A * is missing or we've had unrecoverable resolver errors,
2N/A * we'll default to get_dns_domain(). If a previous DNS
2N/A * TXT RR was found, don't clear it until we're certain
2N/A * that contact can be made to the DNS server (see block
2N/A * comment atop resolv_txt_reset). If we're responding to
2N/A * a SIGHUP signal, force a reset of the cached copy.
2N/A */
2N/A if (argp && argp->signal) {
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A dns_txt_cached = 0;
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A }
2N/A resolv_txt_reset();
2N/A
2N/A errno = 0;
2N/A if (stat(_PATH_RESCONF, &st) < 0 && errno == ENOENT) {
2N/A /*
2N/A * If /etc/resolv.conf is not there, then we'll
2N/A * get the domain from domainname(1M). No real
2N/A * reason to query DNS or fire a thread since we
2N/A * have no nameserver addresses.
2N/A */
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A dns_txt_cached = 0;
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A resolv_txt_reset();
2N/A return;
2N/A }
2N/A
2N/A (void) rw_rdlock(&s_dns_impl_lock);
2N/A if (s_dns_disabled) {
2N/A /*
2N/A * If there were non-recoverable problems with DNS,
2N/A * we have stopped querying DNS entirely. See
2N/A * NO_RECOVERY clause below.
2N/A */
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s: DNS queries disabled", whoami);
2N/A#endif
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A return;
2N/A }
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A if (resolv_init() < 0) {
2N/A /*
2N/A * Failed to initialize resolver. Do not
2N/A * query DNS server.
2N/A */
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A dns_txt_cached = 0;
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A resolv_txt_reset();
2N/A return;
2N/A }
2N/A switch (resolv_search()) {
2N/A case NETDB_SUCCESS:
2N/A /*
2N/A * If there _is_ a TXT record, we let
2N/A * our caller set the global state.
2N/A */
2N/A resolv_decode();
2N/A resolv_get_txt_data();
2N/A break;
2N/A
2N/A case TRY_AGAIN:
2N/A if (argp == NULL || argp->fcn == NULL)
2N/A /*
2N/A * If no valid argument was passed or
2N/A * callback defined, don't fire thread
2N/A */
2N/A break;
2N/A
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A if (s_dns_qthr_created) {
2N/A /*
2N/A * We may have lots of clients, so we don't
2N/A * want to bog down the DNS server with tons
2N/A * of requests... lest it becomes even more
2N/A * unresponsive, so limit 1 thread to query
2N/A * DNS at a time.
2N/A */
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, "%s: query thread already active",
2N/A whoami);
2N/A#endif
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * DNS did not respond ! Set timeout and kick off
2N/A * thread to try op again after s_dns_tout seconds.
2N/A * We've made sure that we don't have an already
2N/A * running thread above.
2N/A */
2N/A s_dns_tout = NFSMAPID_DNS_TOUT_SECS;
2N/A err = thr_create(NULL, 0, resolv_query_thread, (void *)argp,
2N/A thr_flags, &s_dns_qthread);
2N/A if (!err) {
2N/A s_dns_qthr_created = TRUE;
2N/A }
2N/A#ifdef DEBUG
2N/A else {
2N/A msg_done++;
2N/A if (!(msg_done % NFSMAPID_SLOG_RATE))
2N/A syslog(LOG_ERR, EMSG_DNS_THREAD_ERROR);
2N/A }
2N/A#endif
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A break;
2N/A
2N/A case NO_RECOVERY:
2N/A#ifdef DEBUG
2N/A syslog(LOG_ERR, EMSG_DNS_DISABLE, whoami);
2N/A#endif
2N/A (void) rw_wrlock(&s_dns_impl_lock);
2N/A s_dns_disabled = TRUE;
2N/A (void) rw_unlock(&s_dns_impl_lock);
2N/A
2N/A /*FALLTHROUGH*/
2N/A
2N/A default:
2N/A /*
2N/A * For any other errors... DNS is responding, but
2N/A * either it has no data, or some other problem is
2N/A * occuring. At any rate, the TXT domain should not
2N/A * be used, so we default to the DNS domain.
2N/A */
2N/A (void) rw_wrlock(&s_dns_data_lock);
2N/A dns_txt_cached = 0;
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A resolv_txt_reset();
2N/A break;
2N/A }
2N/A
2N/A resolv_destroy();
2N/A}
2N/A
2N/Astatic int
2N/Aget_mtime(const char *fname, timestruc_t *mtim)
2N/A{
2N/A struct stat st;
2N/A int err;
2N/A
2N/A if ((err = stat(fname, &st)) != 0)
2N/A return (err);
2N/A
2N/A *mtim = st.st_mtim;
2N/A return (0);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * trim_wspace is a destructive interface; it is up to
2N/A * the caller to save off an original copy if needed.
2N/A */
2N/Astatic char *
2N/Atrim_wspace(char *dp)
2N/A{
2N/A char *r;
2N/A char *ndp;
2N/A
2N/A /*
2N/A * Any empty domain is not valid
2N/A */
2N/A if (dp == NULL)
2N/A return (NULL);
2N/A
2N/A /*
2N/A * Skip leading blanks
2N/A */
2N/A for (ndp = dp; *ndp != '\0'; ndp++) {
2N/A if (!isspace(*ndp))
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * If we reached the end of the string w/o
2N/A * finding a non-blank char, return error
2N/A */
2N/A if (*ndp == '\0')
2N/A return (NULL);
2N/A
2N/A /*
2N/A * Find next blank in string
2N/A */
2N/A for (r = ndp; *r != '\0'; r++) {
2N/A if (isspace(*r))
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * No more blanks found, we are done
2N/A */
2N/A if (*r == '\0')
2N/A return (ndp);
2N/A
2N/A /*
2N/A * Terminate string at blank
2N/A */
2N/A *r++ = '\0';
2N/A
2N/A /*
2N/A * Skip any trailing spaces
2N/A */
2N/A while (*r != '\0') {
2N/A /*
2N/A * If a non-blank is found, it is an
2N/A * illegal domain (embedded blanks).
2N/A */
2N/A if (!isspace(*r))
2N/A return (NULL);
2N/A r++;
2N/A }
2N/A return (ndp);
2N/A}
2N/A
2N/Astatic void
2N/Aget_nfs_domain(void)
2N/A{
2N/A char value[NS_MAXCDNAME];
2N/A int ret, bufsz = NS_MAXCDNAME;
2N/A
2N/A /*
2N/A * Get NFSMAPID_DOMAIN property value from SMF.
2N/A */
2N/A bzero(value, NS_MAXCDNAME);
2N/A ret = nfs_smf_get_prop("nfsmapid_domain", value, DEFAULT_INSTANCE,
2N/A SCF_TYPE_ASTRING, NFSMAPID, &bufsz);
2N/A if (ret == 0 && *value != NULL) {
2N/A char *dp = NULL;
2N/A#ifdef DEBUG
2N/A char *whoami = "get_nfs_domain";
2N/A char orig[NS_MAXCDNAME] = {0};
2N/A (void) strncpy(orig, value, NS_MAXCDNAME);
2N/A#endif
2N/A /*
2N/A * NFSMAPID_DOMAIN was set, so it's time for validation. If
2N/A * it's okay, then update NFS domain and return. If not,
2N/A * bail (syslog in DEBUG). We make nfsmapid more a bit
2N/A * more forgiving of trailing and leading white space.
2N/A */
2N/A if ((dp = trim_wspace(value)) != NULL) {
2N/A if (mapid_stdchk_domain(dp) > 0) {
2N/A nfs_domain_len = strlen(dp);
2N/A (void) strncpy(nfs_domain, dp, NS_MAXCDNAME);
2N/A nfs_domain[NS_MAXCDNAME] = '\0';
2N/A return;
2N/A }
2N/A }
2N/A#ifdef DEBUG
2N/A if (orig[0] != '\0') {
2N/A syslog(LOG_ERR, gettext("%s: Invalid domain name \"%s\""
2N/A " found in SMF."), whoami, orig);
2N/A }
2N/A#endif
2N/A }
2N/A /*
2N/A * So the NFS SMF parameter nfsmapid_domain cannot be obtained or
2N/A * there is an invalid nfsmapid_domain property value.
2N/A * Time to zap current NFS domain info.
2N/A */
2N/A ZAP_DOMAIN(nfs);
2N/A}
2N/A
2N/Astatic void
2N/Aget_dns_domain(void)
2N/A{
2N/A timestruc_t ntime = {0};
2N/A
2N/A /*
2N/A * If we can't get stats for the config file, then
2N/A * zap the DNS domain info. If mtime hasn't changed,
2N/A * then there's no work to do, so just return.
2N/A */
2N/A errno = 0;
2N/A if (get_mtime(_PATH_RESCONF, &ntime) != 0) {
2N/A switch (errno) {
2N/A case ENOENT:
2N/A /*
2N/A * The resolver defaults to obtaining the
2N/A * domain off of the NIS domainname(1M) if
2N/A * /etc/resolv.conf does not exist, so we
2N/A * move forward.
2N/A */
2N/A break;
2N/A
2N/A default:
2N/A ZAP_DOMAIN(dns);
2N/A return;
2N/A }
2N/A } else if (TIMESTRUC_EQ(ntime, dns_mtime))
2N/A return;
2N/A
2N/A /*
2N/A * Re-initialize resolver to zap DNS domain from previous
2N/A * resolv_init() calls.
2N/A */
2N/A (void) resolv_init();
2N/A
2N/A /*
2N/A * Update cached DNS domain. No need for validation since
2N/A * domain comes from resolver. If resolver doesn't return the
2N/A * domain, then zap the DNS domain. This shouldn't ever happen,
2N/A * and if it does, the machine has bigger problems (so no need
2N/A * to generate a message that says DNS appears to be broken).
2N/A */
2N/A (void) rw_rdlock(&s_dns_data_lock);
2N/A if (sysdns_domain[0] != '\0') {
2N/A (void) strncpy(dns_domain, sysdns_domain, NS_MAXCDNAME);
2N/A dns_domain_len = strlen(sysdns_domain);
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A dns_mtime = ntime;
2N/A resolv_destroy();
2N/A return;
2N/A }
2N/A (void) rw_unlock(&s_dns_data_lock);
2N/A
2N/A ZAP_DOMAIN(dns);
2N/A
2N/A resolv_destroy();
2N/A
2N/A}
2N/A
2N/A/*
2N/A * PSARC 2005/487 Contracted Sun Private Interface
2N/A * mapid_stdchk_domain()
2N/A * Changes must be reviewed by Solaris File Sharing
2N/A * Changes must be communicated to contract-2005-487-01@sun.com
2N/A *
2N/A * Based on the recommendations from RFC1033 and RFC1035, check
2N/A * if a given domain name string is valid. Return values are:
2N/A *
2N/A * 1 = valid domain name
2N/A * 0 = invalid domain name (or invalid embedded character)
2N/A * -1 = domain length > NS_MAXCDNAME
2N/A */
2N/Aint
2N/Amapid_stdchk_domain(const char *ds)
2N/A{
2N/A int i;
2N/A size_t len;
2N/A
2N/A if (ds[0] == '\0')
2N/A return (0);
2N/A else
2N/A len = strlen(ds) - 1;
2N/A
2N/A /*
2N/A * 1st _AND_ last char _must_ be alphanumeric.
2N/A * We check for other valid chars below.
2N/A */
2N/A if ((!isalpha(ds[0]) && !isdigit(ds[0])) ||
2N/A (!isalpha(ds[len]) && !isdigit(ds[len])))
2N/A return (0);
2N/A
2N/A for (i = 0; *ds && i <= NS_MAXCDNAME; i++, ds++) {
2N/A if (!isalpha(*ds) && !isdigit(*ds) &&
2N/A (*ds != '.') && (*ds != '-') && (*ds != '_'))
2N/A return (0);
2N/A }
2N/A return (i == (NS_MAXCDNAME + 1) ? -1 : 1);
2N/A}
2N/A
2N/A/*
2N/A * PSARC 2005/487 Consolidation Private
2N/A * mapid_reeval_domain()
2N/A * Changes must be reviewed by Solaris File Sharing
2N/A */
2N/Avoid
2N/Amapid_reeval_domain(cb_t *arg)
2N/A{
2N/A char *domain = NULL;
2N/A
2N/A get_nfs_domain();
2N/A if (nfs_domain_len != 0) {
2N/A domain = nfs_domain;
2N/A goto dsync;
2N/A }
2N/A
2N/A get_dns_txt_domain(arg);
2N/A if (dns_txt_domain_len != 0)
2N/A domain = dns_txt_domain;
2N/A else {
2N/A /*
2N/A * We're either here because:
2N/A *
2N/A * . NFSMAPID_DOMAIN was not set in /etc/default/nfs
2N/A * . No suitable DNS TXT resource record exists
2N/A * . DNS server is not responding to requests
2N/A *
2N/A * in either case, we want to default to using the
2N/A * system configured DNS domain. If this fails, then
2N/A * dns_domain will be empty and dns_domain_len will
2N/A * be 0.
2N/A */
2N/A get_dns_domain();
2N/A domain = dns_domain;
2N/A }
2N/A
2N/Adsync:
2N/A domain_sync(arg, domain);
2N/A}
2N/A
2N/A/*
2N/A * PSARC 2005/487 Consolidation Private
2N/A * mapid_get_domain()
2N/A * Changes must be reviewed by Solaris File Sharing
2N/A *
2N/A * The use of TSD in mapid_get_domain() diverges slightly from the typical
2N/A * TSD use, since here, the benefit of doing TSD is mostly to allocate
2N/A * a per-thread buffer that will be utilized by other up-calls to the
2N/A * daemon.
2N/A *
2N/A * In doors, the thread used for the upcall never really exits, hence
2N/A * the typical destructor function defined via thr_keycreate() will
2N/A * never be called. Thus, we only use TSD to allocate the per-thread
2N/A * buffer and fill it up w/the configured 'mapid_domain' on each call.
2N/A * This still alleviates the problem of having the caller free any
2N/A * malloc'd space.
2N/A */
2N/Achar *
2N/Amapid_get_domain(void)
2N/A{
2N/A void *tsd = NULL;
2N/A
2N/A (void) thr_getspecific(s_thr_key, &tsd);
2N/A if (tsd == NULL) {
2N/A tsd = malloc(NS_MAXCDNAME+1);
2N/A if (tsd != NULL) {
2N/A (void) rw_rdlock(&mapid_domain_lock);
2N/A (void) strncpy((char *)tsd, mapid_domain, NS_MAXCDNAME);
2N/A (void) rw_unlock(&mapid_domain_lock);
2N/A (void) thr_setspecific(s_thr_key, tsd);
2N/A }
2N/A } else {
2N/A (void) rw_rdlock(&mapid_domain_lock);
2N/A (void) strncpy((char *)tsd, mapid_domain, NS_MAXCDNAME);
2N/A (void) rw_unlock(&mapid_domain_lock);
2N/A }
2N/A return ((char *)tsd);
2N/A}
2N/A
2N/A/*
2N/A * PSARC 2005/487 Contracted Sun Private Interface
2N/A * mapid_derive_domain()
2N/A * Changes must be reviewed by Solaris File Sharing
2N/A * Changes must be communicated to contract-2005-487-01@sun.com
2N/A *
2N/A * This interface is called solely via sysidnfs4 iff no
2N/A * NFSMAPID_DOMAIN was found. So, there is no ill effect
2N/A * of having the reeval function call get_nfs_domain().
2N/A */
2N/Achar *
2N/Amapid_derive_domain(void)
2N/A{
2N/A cb_t cb = {0};
2N/A
2N/A _lib_init();
2N/A mapid_reeval_domain(&cb);
2N/A return (mapid_get_domain());
2N/A}
2N/A
2N/Avoid
2N/A_lib_init(void)
2N/A{
2N/A (void) resolv_init(); /* May fail! */
2N/A (void) rwlock_init(&mapid_domain_lock, USYNC_THREAD, NULL);
2N/A (void) thr_keycreate(&s_thr_key, NULL);
2N/A lib_init_done++;
2N/A resolv_destroy();
2N/A}
2N/A
2N/Avoid
2N/A_lib_fini(void)
2N/A{
2N/A resolv_destroy();
2N/A}