diff options
Diffstat (limited to 'usr/src/lib/libadutils/common/adutils.c')
-rw-r--r-- | usr/src/lib/libadutils/common/adutils.c | 1693 |
1 files changed, 1693 insertions, 0 deletions
diff --git a/usr/src/lib/libadutils/common/adutils.c b/usr/src/lib/libadutils/common/adutils.c new file mode 100644 index 0000000000..d838858671 --- /dev/null +++ b/usr/src/lib/libadutils/common/adutils.c @@ -0,0 +1,1693 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <alloca.h> +#include <string.h> +#include <strings.h> +#include <lber.h> +#include <sasl/sasl.h> +#include <string.h> +#include <ctype.h> +#include <synch.h> +#include <atomic.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <sys/u8_textprep.h> +#include <sys/varargs.h> +#include "libadutils.h" +#include "adutils_impl.h" + +/* List of DSs, needed by the idle connection reaper thread */ +static pthread_mutex_t adhostlock = PTHREAD_MUTEX_INITIALIZER; +static adutils_host_t *host_head = NULL; + +/* + * List of query state structs -- needed so we can "route" LDAP results + * to the right context if multiple threads should be using the same + * connection concurrently + */ +static pthread_mutex_t qstatelock = PTHREAD_MUTEX_INITIALIZER; +static adutils_query_state_t *qstatehead = NULL; + +static char *adutils_sid_ber2str(BerValue *bvalues); +static void adutils_lookup_batch_unlock(adutils_query_state_t **state); +static void delete_ds(adutils_ad_t *ad, const char *host, int port); + +typedef struct binary_attrs { + const char *name; + char *(*ber2str)(BerValue *bvalues); +} binary_attrs_t; + +static binary_attrs_t binattrs[] = { + {"objectSID", adutils_sid_ber2str}, + {NULL, NULL} +}; + +void +adutils_set_log(int pri, bool_t syslog, bool_t degraded) +{ + idmap_log_stderr(pri); + idmap_log_syslog(syslog); + idmap_log_degraded(degraded); +} + +/* + * Turn "foo.bar.com" into "dc=foo,dc=bar,dc=com" + */ +static +char * +adutils_dns2dn(const char *dns) +{ + int nameparts; + return (ldap_dns_to_dn((char *)dns, &nameparts)); +} + +/* + * Turn "dc=foo,dc=bar,dc=com" into "foo.bar.com"; ignores any other + * attributes (CN, etc...). + */ +char * +adutils_dn2dns(const char *dn) +{ + char **rdns = NULL; + char **attrs = NULL; + char **labels = NULL; + char *dns = NULL; + char **rdn, **attr, **label; + int maxlabels = 5; + int nlabels = 0; + int dnslen; + + /* + * There is no reverse of ldap_dns_to_dn() in our libldap, so we + * have to do the hard work here for now. + */ + + /* + * This code is much too liberal: it looks for "dc" attributes + * in all RDNs of the DN. In theory this could cause problems + * if people were to use "dc" in nodes other than the root of + * the tree, but in practice noone, least of all Active + * Directory, does that. + * + * On the other hand, this code is much too conservative: it + * does not make assumptions about ldap_explode_dn(), and _that_ + * is the true for looking at every attr of every RDN. + * + * Since we only ever look at dc and those must be DNS labels, + * at least until we get around to supporting IDN here we + * shouldn't see escaped labels from AD nor from libldap, though + * the spec (RFC2253) does allow libldap to escape things that + * don't need escaping -- if that should ever happen then + * libldap will need a spanking, and we can take care of that. + */ + + /* Explode a DN into RDNs */ + if ((rdns = ldap_explode_dn(dn, 0)) == NULL) + return (NULL); + + labels = calloc(maxlabels + 1, sizeof (char *)); + label = labels; + + for (rdn = rdns; *rdn != NULL; rdn++) { + if (attrs != NULL) + ldap_value_free(attrs); + + /* Explode each RDN, look for DC attr, save val as DNS label */ + if ((attrs = ldap_explode_rdn(rdn[0], 0)) == NULL) + goto done; + + for (attr = attrs; *attr != NULL; attr++) { + if (strncasecmp(*attr, "dc=", 3) != 0) + continue; + + /* Found a DNS label */ + labels[nlabels++] = strdup((*attr) + 3); + + if (nlabels == maxlabels) { + char **tmp; + tmp = realloc(labels, + sizeof (char *) * (maxlabels + 1)); + + if (tmp == NULL) + goto done; + + labels = tmp; + labels[nlabels] = NULL; + } + + /* There should be just one DC= attr per-RDN */ + break; + } + } + + /* + * Got all the labels, now join with '.' + * + * We need room for nlabels - 1 periods ('.'), one nul + * terminator, and the strlen() of each label. + */ + dnslen = nlabels; + for (label = labels; *label != NULL; label++) + dnslen += strlen(*label); + + if ((dns = malloc(dnslen)) == NULL) + goto done; + + *dns = '\0'; + + for (label = labels; *label != NULL; label++) { + (void) strlcat(dns, *label, dnslen); + /* + * NOTE: the last '.' won't be appended -- there's no room + * for it! + */ + (void) strlcat(dns, ".", dnslen); + } + +done: + if (labels != NULL) { + for (label = labels; *label != NULL; label++) + free(*label); + free(labels); + } + if (attrs != NULL) + ldap_value_free(attrs); + if (rdns != NULL) + ldap_value_free(rdns); + + return (dns); +} + +/* + * Convert a binary SID in a BerValue to a adutils_sid_t + */ +static +int +getsid(BerValue *bval, adutils_sid_t *sidp) +{ + int i, j; + uchar_t *v; + uint32_t a; + + /* + * The binary format of a SID is as follows: + * + * byte #0: version, always 0x01 + * byte #1: RID count, always <= 0x0f + * bytes #2-#7: SID authority, big-endian 48-bit unsigned int + * + * followed by RID count RIDs, each a little-endian, unsigned + * 32-bit int. + */ + /* + * Sanity checks: must have at least one RID, version must be + * 0x01, and the length must be 8 + rid count * 4 + */ + if (bval->bv_len > 8 && bval->bv_val[0] == 0x01 && + bval->bv_len == 1 + 1 + 6 + bval->bv_val[1] * 4) { + v = (uchar_t *)bval->bv_val; + sidp->version = v[0]; + sidp->sub_authority_count = v[1]; + sidp->authority = + /* big endian -- so start from the left */ + ((u_longlong_t)v[2] << 40) | + ((u_longlong_t)v[3] << 32) | + ((u_longlong_t)v[4] << 24) | + ((u_longlong_t)v[5] << 16) | + ((u_longlong_t)v[6] << 8) | + (u_longlong_t)v[7]; + for (i = 0; i < sidp->sub_authority_count; i++) { + j = 8 + (i * 4); + /* little endian -- so start from the right */ + a = (v[j + 3] << 24) | (v[j + 2] << 16) | + (v[j + 1] << 8) | (v[j]); + sidp->sub_authorities[i] = a; + } + return (0); + } + return (-1); +} + +/* + * Convert a adutils_sid_t to S-1-... + */ +static +char * +sid2txt(adutils_sid_t *sidp) +{ + int rlen, i, len; + char *str, *cp; + + if (sidp->version != 1) + return (NULL); + + len = sizeof ("S-1-") - 1; + + /* + * We could optimize like so, but, why? + * if (sidp->authority < 10) + * len += 2; + * else if (sidp->authority < 100) + * len += 3; + * else + * len += snprintf(NULL, 0"%llu", sidp->authority); + */ + len += snprintf(NULL, 0, "%llu", sidp->authority); + + /* Max length of a uint32_t printed out in ASCII is 10 bytes */ + len += 1 + (sidp->sub_authority_count + 1) * 10; + + if ((cp = str = malloc(len)) == NULL) + return (NULL); + + rlen = snprintf(str, len, "S-1-%llu", sidp->authority); + + cp += rlen; + len -= rlen; + + for (i = 0; i < sidp->sub_authority_count; i++) { + assert(len > 0); + rlen = snprintf(cp, len, "-%u", sidp->sub_authorities[i]); + cp += rlen; + len -= rlen; + assert(len >= 0); + } + + return (str); +} + +/* + * Convert a adutils_sid_t to on-the-wire encoding + */ +static +int +sid2binsid(adutils_sid_t *sid, uchar_t *binsid, int binsidlen) +{ + uchar_t *p; + int i; + uint64_t a; + uint32_t r; + + if (sid->version != 1 || + binsidlen != (1 + 1 + 6 + sid->sub_authority_count * 4)) + return (-1); + + p = binsid; + *p++ = 0x01; /* version */ + /* sub authority count */ + *p++ = sid->sub_authority_count; + /* Authority */ + a = sid->authority; + /* big-endian -- start from left */ + *p++ = (a >> 40) & 0xFF; + *p++ = (a >> 32) & 0xFF; + *p++ = (a >> 24) & 0xFF; + *p++ = (a >> 16) & 0xFF; + *p++ = (a >> 8) & 0xFF; + *p++ = a & 0xFF; + + /* sub-authorities */ + for (i = 0; i < sid->sub_authority_count; i++) { + r = sid->sub_authorities[i]; + /* little-endian -- start from right */ + *p++ = (r & 0x000000FF); + *p++ = (r & 0x0000FF00) >> 8; + *p++ = (r & 0x00FF0000) >> 16; + *p++ = (r & 0xFF000000) >> 24; + } + + return (0); +} + +/* + * Convert a stringified SID (S-1-...) into a hex-encoded version of the + * on-the-wire encoding, but with each pair of hex digits pre-pended + * with a '\', so we can pass this to libldap. + */ +int +adutils_txtsid2hexbinsid(const char *txt, const uint32_t *rid, + char *hexbinsid, int hexbinsidlen) +{ + adutils_sid_t sid = { 0 }; + int i, j; + const char *cp; + char *ecp; + u_longlong_t a; + unsigned long r; + uchar_t *binsid, b, hb; + + /* Only version 1 SIDs please */ + if (strncmp(txt, "S-1-", strlen("S-1-")) != 0) + return (-1); + + if (strlen(txt) < (strlen("S-1-") + 1)) + return (-1); + + /* count '-'s */ + for (j = 0, cp = strchr(txt, '-'); + cp != NULL && *cp != '\0'; + j++, cp = strchr(cp + 1, '-')) { + /* can't end on a '-' */ + if (*(cp + 1) == '\0') + return (-1); + } + + /* Adjust count for version and authority */ + j -= 2; + + /* we know the version number and RID count */ + sid.version = 1; + sid.sub_authority_count = (rid != NULL) ? j + 1 : j; + + /* must have at least one RID, but not too many */ + if (sid.sub_authority_count < 1 || + sid.sub_authority_count > ADUTILS_SID_MAX_SUB_AUTHORITIES) + return (-1); + + /* check that we only have digits and '-' */ + if (strspn(txt + 1, "0123456789-") < (strlen(txt) - 1)) + return (-1); + + cp = txt + strlen("S-1-"); + + /* 64-bit safe parsing of unsigned 48-bit authority value */ + errno = 0; + a = strtoull(cp, &ecp, 10); + + /* errors parsing the authority or too many bits */ + if (cp == ecp || (a == 0 && errno == EINVAL) || + (a == ULLONG_MAX && errno == ERANGE) || + (a & 0x0000ffffffffffffULL) != a) + return (-1); + + cp = ecp; + + sid.authority = (uint64_t)a; + + for (i = 0; i < j; i++) { + if (*cp++ != '-') + return (-1); + /* 64-bit safe parsing of unsigned 32-bit RID */ + errno = 0; + r = strtoul(cp, &ecp, 10); + /* errors parsing the RID or too many bits */ + if (cp == ecp || (r == 0 && errno == EINVAL) || + (r == ULONG_MAX && errno == ERANGE) || + (r & 0xffffffffUL) != r) + return (-1); + sid.sub_authorities[i] = (uint32_t)r; + cp = ecp; + } + + /* check that all of the string SID has been consumed */ + if (*cp != '\0') + return (-1); + + if (rid != NULL) + sid.sub_authorities[j] = *rid; + + j = 1 + 1 + 6 + sid.sub_authority_count * 4; + + if (hexbinsidlen < (j * 3)) + return (-2); + + /* binary encode the SID */ + binsid = (uchar_t *)alloca(j); + (void) sid2binsid(&sid, binsid, j); + + /* hex encode, with a backslash before each byte */ + for (ecp = hexbinsid, i = 0; i < j; i++) { + b = binsid[i]; + *ecp++ = '\\'; + hb = (b >> 4) & 0xF; + *ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A'); + hb = b & 0xF; + *ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A'); + } + *ecp = '\0'; + + return (0); +} + +static +char * +convert_bval2sid(BerValue *bval, uint32_t *rid) +{ + adutils_sid_t sid; + + if (getsid(bval, &sid) < 0) + return (NULL); + + /* + * If desired and if the SID is what should be a domain/computer + * user or group SID (i.e., S-1-5-w-x-y-z-<user/group RID>) then + * save the last RID and truncate the SID + */ + if (rid != NULL && sid.authority == 5 && sid.sub_authority_count == 5) + *rid = sid.sub_authorities[--sid.sub_authority_count]; + return (sid2txt(&sid)); +} + + +/* + * Return a NUL-terminated stringified SID from the value of an + * objectSid attribute and put the last RID in *rid. + */ +char * +adutils_bv_objsid2sidstr(BerValue *bval, uint32_t *rid) +{ + char *sid; + + if (bval == NULL) + return (NULL); + /* objectSid is single valued */ + if ((sid = convert_bval2sid(bval, rid)) == NULL) + return (NULL); + return (sid); +} + +static +char * +adutils_sid_ber2str(BerValue *bval) +{ + return (adutils_bv_objsid2sidstr(bval, NULL)); +} + + +/* Return a NUL-terminated string from the Ber value */ +char * +adutils_bv_name2str(BerValue *bval) +{ + char *s; + + if (bval == NULL || bval->bv_val == NULL) + return (NULL); + if ((s = malloc(bval->bv_len + 1)) == NULL) + return (NULL); + (void) snprintf(s, bval->bv_len + 1, "%.*s", bval->bv_len, + bval->bv_val); + return (s); +} + +/*ARGSUSED*/ +static +int +saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts) +{ + sasl_interact_t *interact; + + if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE) + return (LDAP_PARAM_ERROR); + + /* There should be no extra arguemnts for SASL/GSSAPI authentication */ + for (interact = prompts; interact->id != SASL_CB_LIST_END; + interact++) { + interact->result = NULL; + interact->len = 0; + } + return (LDAP_SUCCESS); +} + + +#define ADCONN_TIME 300 + +/* + * Idle connection reaping side of connection management + */ +void +adutils_reap_idle_connections() +{ + adutils_host_t *adh; + time_t now; + + (void) pthread_mutex_lock(&adhostlock); + now = time(NULL); + for (adh = host_head; adh != NULL; adh = adh->next) { + (void) pthread_mutex_lock(&adh->lock); + if (adh->ref == 0 && adh->idletime != 0 && + adh->idletime + ADCONN_TIME < now) { + if (adh->ld) { + (void) ldap_unbind(adh->ld); + adh->ld = NULL; + adh->idletime = 0; + adh->ref = 0; + } + } + (void) pthread_mutex_unlock(&adh->lock); + } + (void) pthread_mutex_unlock(&adhostlock); +} + + +adutils_rc +adutils_ad_alloc(adutils_ad_t **new_ad, const char *default_domain, + adutils_ad_partition_t part) +{ + adutils_ad_t *ad; + + *new_ad = NULL; + + if ((default_domain == NULL || *default_domain == '\0') && + part != ADUTILS_AD_GLOBAL_CATALOG) + return (ADUTILS_ERR_DOMAIN); + if ((ad = calloc(1, sizeof (*ad))) == NULL) + return (ADUTILS_ERR_MEMORY); + ad->ref = 1; + ad->partition = part; + if (default_domain == NULL) + default_domain = ""; + if ((ad->dflt_w2k_dom = strdup(default_domain)) == NULL) + goto err; + if (pthread_mutex_init(&ad->lock, NULL) != 0) + goto err; + *new_ad = ad; + return (ADUTILS_SUCCESS); + +err: + if (ad->dflt_w2k_dom != NULL) + free(ad->dflt_w2k_dom); + free(ad); + return (ADUTILS_ERR_MEMORY); +} + +void +adutils_ad_free(adutils_ad_t **ad) +{ + adutils_host_t *p; + adutils_host_t *prev; + + if (ad == NULL || *ad == NULL) + return; + + (void) pthread_mutex_lock(&(*ad)->lock); + + if (atomic_dec_32_nv(&(*ad)->ref) > 0) { + (void) pthread_mutex_unlock(&(*ad)->lock); + *ad = NULL; + return; + } + + (void) pthread_mutex_lock(&adhostlock); + prev = NULL; + p = host_head; + while (p != NULL) { + if (p->owner != (*ad)) { + prev = p; + p = p->next; + continue; + } else { + delete_ds((*ad), p->host, p->port); + if (prev == NULL) + p = host_head; + else + p = prev->next; + } + } + (void) pthread_mutex_unlock(&adhostlock); + + (void) pthread_mutex_unlock(&(*ad)->lock); + (void) pthread_mutex_destroy(&(*ad)->lock); + + free((*ad)->dflt_w2k_dom); + free(*ad); + + *ad = NULL; +} + +static +int +open_conn(adutils_host_t *adh, int timeoutsecs) +{ + int zero = 0; + int ldversion, rc; + int timeoutms = timeoutsecs * 1000; + + if (adh == NULL) + return (0); + + (void) pthread_mutex_lock(&adh->lock); + + if (!adh->dead && adh->ld != NULL) + /* done! */ + goto out; + + if (adh->ld != NULL) { + (void) ldap_unbind(adh->ld); + adh->ld = NULL; + } + adh->num_requests = 0; + + atomic_inc_64(&adh->generation); + + /* Open and bind an LDAP connection */ + adh->ld = ldap_init(adh->host, adh->port); + if (adh->ld == NULL) { + idmapdlog(LOG_INFO, "ldap_init() to server " + "%s port %d failed. (%s)", adh->host, + adh->port, strerror(errno)); + goto out; + } + ldversion = LDAP_VERSION3; + (void) ldap_set_option(adh->ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion); + (void) ldap_set_option(adh->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); + (void) ldap_set_option(adh->ld, LDAP_OPT_TIMELIMIT, &zero); + (void) ldap_set_option(adh->ld, LDAP_OPT_SIZELIMIT, &zero); + (void) ldap_set_option(adh->ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms); + (void) ldap_set_option(adh->ld, LDAP_OPT_RESTART, LDAP_OPT_ON); + rc = ldap_sasl_interactive_bind_s(adh->ld, "" /* binddn */, + adh->saslmech, NULL, NULL, adh->saslflags, &saslcallback, + NULL); + + if (rc != LDAP_SUCCESS) { + (void) ldap_unbind(adh->ld); + adh->ld = NULL; + idmapdlog(LOG_INFO, "ldap_sasl_interactive_bind_s() to server " + "%s port %d failed. (%s)", adh->host, adh->port, + ldap_err2string(rc)); + } + + idmapdlog(LOG_DEBUG, "Using global catalog server %s:%d", + adh->host, adh->port); + +out: + if (adh->ld != NULL) { + atomic_inc_32(&adh->ref); + adh->idletime = time(NULL); + adh->dead = 0; + (void) pthread_mutex_unlock(&adh->lock); + return (1); + } + + (void) pthread_mutex_unlock(&adh->lock); + return (0); +} + + +/* + * Connection management: find an open connection or open one + */ +static +adutils_host_t * +get_conn(adutils_ad_t *ad) +{ + adutils_host_t *adh = NULL; + int tries; + int dscount = 0; + int timeoutsecs = ADUTILS_LDAP_OPEN_TIMEOUT; + +retry: + (void) pthread_mutex_lock(&adhostlock); + + if (host_head == NULL) { + (void) pthread_mutex_unlock(&adhostlock); + goto out; + } + + if (dscount == 0) { + /* + * First try: count the number of DSes. + * + * Integer overflow is not an issue -- we can't have so many + * DSes because they won't fit even DNS over TCP, and SMF + * shouldn't let you set so many. + */ + for (adh = host_head, tries = 0; adh != NULL; adh = adh->next) { + if (adh->owner == ad) + dscount++; + } + + if (dscount == 0) { + (void) pthread_mutex_unlock(&adhostlock); + goto out; + } + + tries = dscount * 3; /* three tries per-ds */ + + /* + * Begin round-robin at the next DS in the list after the last + * one that we had a connection to, else start with the first + * DS in the list. + */ + adh = ad->last_adh; + } + + /* + * Round-robin -- pick the next one on the list; if the list + * changes on us, no big deal, we'll just potentially go + * around the wrong number of times. + */ + for (;;) { + if (adh != NULL && adh->ld != NULL && !adh->dead) + break; + if (adh == NULL || (adh = adh->next) == NULL) + adh = host_head; + if (adh->owner == ad) + break; + } + + ad->last_adh = adh; + (void) pthread_mutex_unlock(&adhostlock); + + /* Found suitable DS, open it if not already opened */ + if (open_conn(adh, timeoutsecs)) + return (adh); + + tries--; + if ((tries % dscount) == 0) + timeoutsecs *= 2; + if (tries > 0) + goto retry; + +out: + idmapdlog(LOG_NOTICE, "Couldn't open an LDAP connection to any global " + "catalog server!"); + return (NULL); +} + +static +void +release_conn(adutils_host_t *adh) +{ + int delete = 0; + + (void) pthread_mutex_lock(&adh->lock); + if (atomic_dec_32_nv(&adh->ref) == 0) { + if (adh->owner == NULL) + delete = 1; + adh->idletime = time(NULL); + } + (void) pthread_mutex_unlock(&adh->lock); + + /* Free this host if its owner no longer exists. */ + if (delete) { + (void) pthread_mutex_lock(&adhostlock); + delete_ds(NULL, adh->host, adh->port); + (void) pthread_mutex_unlock(&adhostlock); + } +} + +/* + * Create a adutils_host_t, populate it and add it to the list of hosts. + */ +adutils_rc +adutils_add_ds(adutils_ad_t *ad, const char *host, int port) +{ + adutils_host_t *p; + adutils_host_t *new = NULL; + int ret; + adutils_rc rc; + + (void) pthread_mutex_lock(&adhostlock); + for (p = host_head; p != NULL; p = p->next) { + if (p->owner != ad) + continue; + + if (strcmp(host, p->host) == 0 && p->port == port) { + /* already added */ + rc = ADUTILS_SUCCESS; + goto err; + } + } + + rc = ADUTILS_ERR_MEMORY; + + /* add new entry */ + new = (adutils_host_t *)calloc(1, sizeof (*new)); + if (new == NULL) + goto err; + new->owner = ad; + new->port = port; + new->dead = 0; + new->max_requests = 80; + new->num_requests = 0; + if ((new->host = strdup(host)) == NULL) + goto err; + new->saslflags = LDAP_SASL_INTERACTIVE; + new->saslmech = "GSSAPI"; + + if ((ret = pthread_mutex_init(&new->lock, NULL)) != 0) { + free(new->host); + new->host = NULL; + errno = ret; + rc = ADUTILS_ERR_INTERNAL; + goto err; + } + + /* link in */ + rc = ADUTILS_SUCCESS; + new->next = host_head; + host_head = new; + +err: + (void) pthread_mutex_unlock(&adhostlock); + + if (rc != 0 && new != NULL) { + if (new->host != NULL) { + (void) pthread_mutex_destroy(&new->lock); + free(new->host); + } + free(new); + } + + return (rc); +} + +/* + * Free a DS configuration. + * Caller must lock the adhostlock mutex + */ +static +void +delete_ds(adutils_ad_t *ad, const char *host, int port) +{ + adutils_host_t **p, *q; + + for (p = &host_head; *p != NULL; p = &((*p)->next)) { + if ((*p)->owner != ad || strcmp(host, (*p)->host) != 0 || + (*p)->port != port) + continue; + /* found */ + + (void) pthread_mutex_lock(&((*p)->lock)); + if ((*p)->ref > 0) { + /* + * Still in use. Set its owner to NULL so + * that it can be freed when its ref count + * becomes 0. + */ + (*p)->owner = NULL; + (void) pthread_mutex_unlock(&((*p)->lock)); + break; + } + (void) pthread_mutex_unlock(&((*p)->lock)); + + q = *p; + *p = (*p)->next; + + (void) pthread_mutex_destroy(&q->lock); + + if (q->ld) + (void) ldap_unbind(q->ld); + if (q->host) + free(q->host); + free(q); + break; + } + +} + +adutils_rc +adutils_lookup_batch_start(adutils_ad_t *ad, int nqueries, + adutils_ldap_res_search_cb ldap_res_search_cb, + void *ldap_res_search_argp, + adutils_query_state_t **state) +{ + adutils_query_state_t *new_state; + adutils_host_t *adh = NULL; + + if (ad == NULL) + return (ADUTILS_ERR_INTERNAL); + + *state = NULL; + adh = get_conn(ad); + if (adh == NULL) + return (ADUTILS_ERR_RETRIABLE_NET_ERR); + + new_state = calloc(1, sizeof (adutils_query_state_t) + + (nqueries - 1) * sizeof (adutils_q_t)); + if (new_state == NULL) + return (ADUTILS_ERR_MEMORY); + + /* + * Save default domain from the ad object so that we don't + * have to access the 'ad' object later. + */ + new_state->default_domain = strdup(adh->owner->dflt_w2k_dom); + if (new_state->default_domain == NULL) { + free(new_state); + return (ADUTILS_ERR_MEMORY); + } + + if (ad->partition == ADUTILS_AD_DATA) + new_state->basedn = adutils_dns2dn(new_state->default_domain); + else + new_state->basedn = strdup(""); + if (new_state->basedn == NULL) { + free(new_state->default_domain); + free(new_state); + return (ADUTILS_ERR_MEMORY); + } + + new_state->ref_cnt = 1; + new_state->qadh = adh; + new_state->qcount = nqueries; + new_state->qadh_gen = adh->generation; + new_state->qlastsent = 0; + new_state->ldap_res_search_cb = ldap_res_search_cb; + new_state->ldap_res_search_argp = ldap_res_search_argp; + (void) pthread_cond_init(&new_state->cv, NULL); + + (void) pthread_mutex_lock(&qstatelock); + new_state->next = qstatehead; + qstatehead = new_state; + (void) pthread_mutex_unlock(&qstatelock); + *state = new_state; + + return (ADUTILS_SUCCESS); +} + +/* + * Find the adutils_query_state_t to which a given LDAP result msgid on a + * given connection belongs. This routine increaments the reference count + * so that the object can not be freed. adutils_lookup_batch_unlock() + * must be called to decreament the reference count. + */ +static +int +msgid2query(adutils_host_t *adh, int msgid, + adutils_query_state_t **state, int *qid) +{ + adutils_query_state_t *p; + int i; + int ret; + + (void) pthread_mutex_lock(&qstatelock); + for (p = qstatehead; p != NULL; p = p->next) { + if (p->qadh != adh || adh->generation != p->qadh_gen) + continue; + for (i = 0; i < p->qcount; i++) { + if ((p->queries[i]).msgid == msgid) { + if (!p->qdead) { + p->ref_cnt++; + *state = p; + *qid = i; + ret = 1; + } else + ret = 0; + (void) pthread_mutex_unlock(&qstatelock); + return (ret); + } + } + } + (void) pthread_mutex_unlock(&qstatelock); + return (0); +} + +static +int +check_for_binary_attrs(const char *attr) +{ + int i; + for (i = 0; binattrs[i].name != NULL; i++) { + if (strcasecmp(binattrs[i].name, attr) == 0) + return (i); + } + return (-1); +} + +static +void +free_entry(adutils_entry_t *entry) +{ + int i, j; + adutils_attr_t *ap; + + if (entry == NULL) + return; + if (entry->attr_nvpairs == NULL) { + free(entry); + return; + } + for (i = 0; i < entry->num_nvpairs; i++) { + ap = &entry->attr_nvpairs[i]; + if (ap->attr_name == NULL) { + ldap_value_free(ap->attr_values); + continue; + } + if (check_for_binary_attrs(ap->attr_name) >= 0) { + free(ap->attr_name); + if (ap->attr_values == NULL) + continue; + for (j = 0; j < ap->num_values; j++) + free(ap->attr_values[j]); + free(ap->attr_values); + } else if (strcasecmp(ap->attr_name, "dn") == 0) { + free(ap->attr_name); + ldap_memfree(ap->attr_values[0]); + free(ap->attr_values); + } else { + free(ap->attr_name); + ldap_value_free(ap->attr_values); + } + } + free(entry->attr_nvpairs); + free(entry); +} + +void +adutils_freeresult(adutils_result_t **result) +{ + adutils_entry_t *e, *next; + + if (result == NULL || *result == NULL) + return; + if ((*result)->entries == NULL) { + free(*result); + *result = NULL; + return; + } + for (e = (*result)->entries; e != NULL; e = next) { + next = e->next; + free_entry(e); + } + free(*result); + *result = NULL; +} + +const adutils_entry_t * +adutils_getfirstentry(adutils_result_t *result) +{ + if (result != NULL) + return (result->entries); + return (NULL); +} + + +char ** +adutils_getattr(const adutils_entry_t *entry, const char *attrname) +{ + int i; + adutils_attr_t *ap; + + if (entry == NULL || entry->attr_nvpairs == NULL) + return (NULL); + for (i = 0; i < entry->num_nvpairs; i++) { + ap = &entry->attr_nvpairs[i]; + if (ap->attr_name != NULL && + strcasecmp(ap->attr_name, attrname) == 0) + return (ap->attr_values); + } + return (NULL); +} + + +/* + * Queue LDAP result for the given query. + * + * Return values: + * 0 success + * -1 ignore result + * -2 error + */ +static +int +make_entry(adutils_q_t *q, adutils_host_t *adh, LDAPMessage *search_res, + adutils_entry_t **entry) +{ + BerElement *ber = NULL; + BerValue **bvalues = NULL; + char **strvalues; + char *attr = NULL, *dn = NULL, *domain = NULL; + adutils_entry_t *ep; + adutils_attr_t *ap; + int i, j, b, err = 0, ret = -2; + + *entry = NULL; + + /* Check that this is the domain that we were looking for */ + if ((dn = ldap_get_dn(adh->ld, search_res)) == NULL) + return (-2); + if ((domain = adutils_dn2dns(dn)) == NULL) { + ldap_memfree(dn); + return (-2); + } + if (q->edomain != NULL) { + if (u8_strcmp(q->edomain, domain, 0, U8_STRCMP_CI_LOWER, + U8_UNICODE_LATEST, &err) != 0 || err != 0) { + ldap_memfree(dn); + free(domain); + return (-1); + } + } + free(domain); + + /* Allocate memory for the entry */ + if ((ep = calloc(1, sizeof (*ep))) == NULL) + goto out; + + /* For 'dn' */ + ep->num_nvpairs = 1; + + /* Count the number of name-value pairs for this entry */ + for (attr = ldap_first_attribute(adh->ld, search_res, &ber); + attr != NULL; + attr = ldap_next_attribute(adh->ld, search_res, ber)) { + ep->num_nvpairs++; + ldap_memfree(attr); + } + ber_free(ber, 0); + ber = NULL; + + /* Allocate array for the attribute name-value pairs */ + ep->attr_nvpairs = calloc(ep->num_nvpairs, sizeof (*ep->attr_nvpairs)); + if (ep->attr_nvpairs == NULL) { + ep->num_nvpairs = 0; + goto out; + } + + /* For dn */ + ap = &ep->attr_nvpairs[0]; + if ((ap->attr_name = strdup("dn")) == NULL) + goto out; + ap->num_values = 1; + ap->attr_values = calloc(ap->num_values, sizeof (*ap->attr_values)); + if (ap->attr_values == NULL) { + ap->num_values = 0; + goto out; + } + ap->attr_values[0] = dn; + dn = NULL; + + for (attr = ldap_first_attribute(adh->ld, search_res, &ber), i = 1; + attr != NULL; + ldap_memfree(attr), i++, + attr = ldap_next_attribute(adh->ld, search_res, ber)) { + ap = &ep->attr_nvpairs[i]; + if ((ap->attr_name = strdup(attr)) == NULL) + goto out; + + if ((b = check_for_binary_attrs(attr)) >= 0) { + bvalues = + ldap_get_values_len(adh->ld, search_res, attr); + if (bvalues == NULL) + continue; + ap->num_values = ldap_count_values_len(bvalues); + if (ap->num_values == 0) { + ldap_value_free_len(bvalues); + bvalues = NULL; + continue; + } + ap->attr_values = calloc(ap->num_values, + sizeof (*ap->attr_values)); + if (ap->attr_values == NULL) { + ap->num_values = 0; + goto out; + } + for (j = 0; j < ap->num_values; j++) { + ap->attr_values[j] = + binattrs[b].ber2str(bvalues[j]); + if (ap->attr_values[j] == NULL) + goto out; + } + ldap_value_free_len(bvalues); + bvalues = NULL; + continue; + } + + strvalues = ldap_get_values(adh->ld, search_res, attr); + if (strvalues == NULL) + continue; + ap->num_values = ldap_count_values(strvalues); + if (ap->num_values == 0) { + ldap_value_free(strvalues); + continue; + } + ap->attr_values = strvalues; + } + + ret = 0; +out: + ldap_memfree(attr); + ldap_memfree(dn); + ber_free(ber, 0); + ldap_value_free_len(bvalues); + if (ret < 0) + free_entry(ep); + else + *entry = ep; + return (ret); +} + +/* + * Put the search result onto the given adutils_q_t. + * Returns: 0 success + * < 0 error + */ +static +int +add_entry(adutils_host_t *adh, adutils_q_t *q, LDAPMessage *search_res) +{ + int ret = -1; + adutils_entry_t *entry = NULL; + adutils_result_t *res; + + ret = make_entry(q, adh, search_res, &entry); + if (ret < -1) { + *q->rc = ADUTILS_ERR_MEMORY; + goto out; + } else if (ret == -1) { + /* ignore result */ + goto out; + } + if (*q->result == NULL) { + res = calloc(1, sizeof (*res)); + if (res == NULL) { + *q->rc = ADUTILS_ERR_MEMORY; + goto out; + } + res->num_entries = 1; + res->entries = entry; + *q->result = res; + } else { + res = *q->result; + entry->next = res->entries; + res->entries = entry; + res->num_entries++; + } + *q->rc = ADUTILS_SUCCESS; + entry = NULL; + ret = 0; + +out: + free_entry(entry); + return (ret); +} + +/* + * Try to get a result; if there is one, find the corresponding + * adutils_q_t and process the result. + * + * Returns: 0 success + * -1 error + */ +static +int +get_adobject_batch(adutils_host_t *adh, struct timeval *timeout) +{ + adutils_query_state_t *query_state; + LDAPMessage *res = NULL; + int rc, ret, msgid, qid; + adutils_q_t *que; + int num; + + (void) pthread_mutex_lock(&adh->lock); + if (adh->dead || adh->num_requests == 0) { + ret = (adh->dead) ? -1 : -2; + (void) pthread_mutex_unlock(&adh->lock); + return (ret); + } + + /* Get one result */ + rc = ldap_result(adh->ld, LDAP_RES_ANY, 0, timeout, &res); + if ((timeout != NULL && timeout->tv_sec > 0 && rc == LDAP_SUCCESS) || + rc < 0) + adh->dead = 1; + + if (rc == LDAP_RES_SEARCH_RESULT && adh->num_requests > 0) + adh->num_requests--; + if (adh->dead) { + num = adh->num_requests; + (void) pthread_mutex_unlock(&adh->lock); + idmapdlog(LOG_DEBUG, + "AD ldap_result error - %d queued requests", num); + return (-1); + } + + switch (rc) { + case LDAP_RES_SEARCH_RESULT: + msgid = ldap_msgid(res); + if (msgid2query(adh, msgid, &query_state, &qid)) { + if (query_state->ldap_res_search_cb != NULL) { + /* + * We use the caller-provided callback + * to process the result. + */ + query_state->ldap_res_search_cb( + adh->ld, &res, rc, qid, + query_state->ldap_res_search_argp); + (void) pthread_mutex_unlock(&adh->lock); + } else { + /* + * No callback. We fallback to our + * default behaviour. All the entries + * gotten from this search have been + * added to the result list during + * LDAP_RES_SEARCH_ENTRY (see below). + * Here we set the return status to + * notfound if the result is still empty. + */ + (void) pthread_mutex_unlock(&adh->lock); + que = &(query_state->queries[qid]); + if (*que->result == NULL) + *que->rc = ADUTILS_ERR_NOTFOUND; + } + atomic_dec_32(&query_state->qinflight); + adutils_lookup_batch_unlock(&query_state); + } else { + num = adh->num_requests; + (void) pthread_mutex_unlock(&adh->lock); + idmapdlog(LOG_DEBUG, + "AD cannot find message ID (%d) " + "- %d queued requests", + msgid, num); + } + (void) ldap_msgfree(res); + ret = 0; + break; + + case LDAP_RES_SEARCH_ENTRY: + msgid = ldap_msgid(res); + if (msgid2query(adh, msgid, &query_state, &qid)) { + if (query_state->ldap_res_search_cb != NULL) { + /* + * We use the caller-provided callback + * to process the entry. + */ + query_state->ldap_res_search_cb( + adh->ld, &res, rc, qid, + query_state->ldap_res_search_argp); + (void) pthread_mutex_unlock(&adh->lock); + } else { + /* + * No callback. We fallback to our + * default behaviour. This entry + * will be added to the result list. + */ + que = &(query_state->queries[qid]); + rc = add_entry(adh, que, res); + (void) pthread_mutex_unlock(&adh->lock); + if (rc < 0) { + idmapdlog(LOG_DEBUG, + "Failed to queue entry by " + "message ID (%d) " + "- %d queued requests", + msgid, num); + } + } + adutils_lookup_batch_unlock(&query_state); + } else { + num = adh->num_requests; + (void) pthread_mutex_unlock(&adh->lock); + idmapdlog(LOG_DEBUG, + "AD cannot find message ID (%d) " + "- %d queued requests", + msgid, num); + } + (void) ldap_msgfree(res); + ret = 0; + break; + + case LDAP_RES_SEARCH_REFERENCE: + /* + * We have no need for these at the moment. Eventually, + * when we query things that we can't expect to find in + * the Global Catalog then we'll need to learn to follow + * references. + */ + (void) pthread_mutex_unlock(&adh->lock); + (void) ldap_msgfree(res); + ret = 0; + break; + + default: + /* timeout or error; treat the same */ + (void) pthread_mutex_unlock(&adh->lock); + ret = -1; + break; + } + + return (ret); +} + +/* + * This routine decreament the reference count of the + * adutils_query_state_t + */ +static void +adutils_lookup_batch_unlock(adutils_query_state_t **state) +{ + /* + * Decrement reference count with qstatelock locked + */ + (void) pthread_mutex_lock(&qstatelock); + (*state)->ref_cnt--; + /* + * If there are no references wakup the allocating thread + */ + if ((*state)->ref_cnt <= 1) + (void) pthread_cond_signal(&(*state)->cv); + (void) pthread_mutex_unlock(&qstatelock); + *state = NULL; +} + +/* + * This routine frees the adutils_query_state_t structure + * If the reference count is greater than 1 it waits + * for the other threads to finish using it. + */ +void +adutils_lookup_batch_release(adutils_query_state_t **state) +{ + adutils_query_state_t **p; + int i; + + if (state == NULL || *state == NULL) + return; + + /* + * Set state to dead to stop further operations. + * Wait for reference count with qstatelock locked + * to get to one. + */ + (void) pthread_mutex_lock(&qstatelock); + (*state)->qdead = 1; + while ((*state)->ref_cnt > 1) { + (void) pthread_cond_wait(&(*state)->cv, &qstatelock); + } + + /* Remove this state struct from the list of state structs */ + for (p = &qstatehead; *p != NULL; p = &(*p)->next) { + if (*p == (*state)) { + *p = (*state)->next; + break; + } + } + (void) pthread_mutex_unlock(&qstatelock); + (void) pthread_cond_destroy(&(*state)->cv); + release_conn((*state)->qadh); + + /* Clear results for queries that failed */ + for (i = 0; i < (*state)->qcount; i++) { + if (*(*state)->queries[i].rc != ADUTILS_SUCCESS) { + adutils_freeresult((*state)->queries[i].result); + } + } + free((*state)->default_domain); + free((*state)->basedn); + free(*state); + *state = NULL; +} + + +/* + * This routine waits for other threads using the + * adutils_query_state_t structure to finish. + * If the reference count is greater than 1 it waits + * for the other threads to finish using it. + */ +static +void +adutils_lookup_batch_wait(adutils_query_state_t *state) +{ + /* + * Set state to dead to stop further operation. + * stating. + * Wait for reference count to get to one + * with qstatelock locked. + */ + (void) pthread_mutex_lock(&qstatelock); + state->qdead = 1; + while (state->ref_cnt > 1) { + (void) pthread_cond_wait(&state->cv, &qstatelock); + } + (void) pthread_mutex_unlock(&qstatelock); +} + +/* + * Process active queries in the AD lookup batch and then finalize the + * result. + */ +adutils_rc +adutils_lookup_batch_end(adutils_query_state_t **state) +{ + int rc = LDAP_SUCCESS; + adutils_rc ad_rc = ADUTILS_SUCCESS; + struct timeval tv; + + tv.tv_sec = ADUTILS_SEARCH_TIMEOUT; + tv.tv_usec = 0; + + /* Process results until done or until timeout, if given */ + while ((*state)->qinflight > 0) { + if ((rc = get_adobject_batch((*state)->qadh, + &tv)) != 0) + break; + } + (*state)->qdead = 1; + /* Wait for other threads processing search result to finish */ + adutils_lookup_batch_wait(*state); + if (rc == -1 || (*state)->qinflight != 0) + ad_rc = ADUTILS_ERR_RETRIABLE_NET_ERR; + adutils_lookup_batch_release(state); + return (ad_rc); +} + +const char * +adutils_lookup_batch_getdefdomain(adutils_query_state_t *state) +{ + return (state->default_domain); +} + +/* + * Send one prepared search, queue up msgid, process what results are + * available + */ +adutils_rc +adutils_lookup_batch_add(adutils_query_state_t *state, + const char *filter, const char **attrs, const char *edomain, + adutils_result_t **result, adutils_rc *rc) +{ + adutils_rc retcode = ADUTILS_SUCCESS; + int lrc, qid; + int num; + int dead; + struct timeval tv; + adutils_q_t *q; + + qid = atomic_inc_32_nv(&state->qlastsent) - 1; + q = &(state->queries[qid]); + + /* + * Remember the expected domain so we can check the results + * against it + */ + q->edomain = edomain; + + /* Remember where to put the results */ + q->result = result; + q->rc = rc; + + /* + * Provide sane defaults for the results in case we never hear + * back from the DS before closing the connection. + */ + *rc = ADUTILS_ERR_RETRIABLE_NET_ERR; + if (result != NULL) + *result = NULL; + + /* Check the number of queued requests first */ + tv.tv_sec = ADUTILS_SEARCH_TIMEOUT; + tv.tv_usec = 0; + while (!state->qadh->dead && + state->qadh->num_requests > state->qadh->max_requests) { + if (get_adobject_batch(state->qadh, &tv) != 0) + break; + } + + /* Send this lookup, don't wait for a result here */ + lrc = LDAP_SUCCESS; + (void) pthread_mutex_lock(&state->qadh->lock); + + if (!state->qadh->dead) { + state->qadh->idletime = time(NULL); + lrc = ldap_search_ext(state->qadh->ld, state->basedn, + LDAP_SCOPE_SUBTREE, filter, (char **)attrs, + 0, NULL, NULL, NULL, -1, &q->msgid); + + if (lrc == LDAP_SUCCESS) { + state->qadh->num_requests++; + } else if (lrc == LDAP_BUSY || lrc == LDAP_UNAVAILABLE || + lrc == LDAP_CONNECT_ERROR || lrc == LDAP_SERVER_DOWN || + lrc == LDAP_UNWILLING_TO_PERFORM) { + retcode = ADUTILS_ERR_RETRIABLE_NET_ERR; + state->qadh->dead = 1; + } else { + retcode = ADUTILS_ERR_OTHER; + state->qadh->dead = 1; + } + } + dead = state->qadh->dead; + num = state->qadh->num_requests; + (void) pthread_mutex_unlock(&state->qadh->lock); + + if (dead) { + if (lrc != LDAP_SUCCESS) + idmapdlog(LOG_DEBUG, + "AD ldap_search_ext error (%s) " + "- %d queued requests", + ldap_err2string(lrc), num); + return (retcode); + } + + atomic_inc_32(&state->qinflight); + + /* + * Reap as many requests as we can _without_ waiting to prevent + * any possible TCP socket buffer starvation deadlocks. + */ + (void) memset(&tv, 0, sizeof (tv)); + while (get_adobject_batch(state->qadh, &tv) == 0) + ; + + return (ADUTILS_SUCCESS); +} + +/* + * Single AD lookup request implemented on top of the batch API. + */ +adutils_rc +adutils_lookup(adutils_ad_t *ad, const char *filter, const char **attrs, + const char *domain, adutils_result_t **result) +{ + adutils_rc rc, brc; + adutils_query_state_t *qs; + + rc = adutils_lookup_batch_start(ad, 1, NULL, NULL, &qs); + if (rc != ADUTILS_SUCCESS) + return (rc); + + rc = adutils_lookup_batch_add(qs, filter, attrs, domain, result, &brc); + if (rc != ADUTILS_SUCCESS) { + adutils_lookup_batch_release(&qs); + return (rc); + } + + rc = adutils_lookup_batch_end(&qs); + if (rc != ADUTILS_SUCCESS) + return (rc); + return (brc); +} |