OpenSolaris_b135/lib/libnisdb/ldap_op.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 <synch.h>
#include <strings.h>
#include <sys/time.h>
#include <ctype.h>

#include "ldap_op.h"
#include "ldap_util.h"
#include "ldap_structs.h"
#include "ldap_ruleval.h"
#include "ldap_attr.h"
#include "ldap_print.h"
#include "ldap_glob.h"

#include "nis_parse_ldap_conf.h"

#ifndef LDAPS_PORT
#define	LDAPS_PORT	636
#endif

static int setupConList(char *serverList, char *who,
			char *cred, auth_method_t method);


/*
 * Build one of our internal LDAP search structures, containing copies of
 * the supplied input. return NULL in case of error.
 *
 * If 'filter' is NULL, build an AND-filter using the filter components.
 */
__nis_ldap_search_t *
buildLdapSearch(char *base, int scope, int numFilterComps, char **filterComp,
		char *filter, char **attrs, int attrsonly, int isDN) {
	__nis_ldap_search_t	*ls;
	char			**a;
	int			i, na, err = 0;
	char			*myself = "buildLdapSearch";

	ls = am(myself, sizeof (*ls));
	if (ls == 0)
		return (0);

	ls->base = sdup(myself, T, base);
	if (ls->base == 0 && base != 0)
		err++;
	ls->scope = scope;

	if (filterComp != 0 && numFilterComps > 0) {
		ls->filterComp = am(myself, numFilterComps *
					sizeof (ls->filterComp[0]));
		if (ls->filterComp == 0) {
			err++;
			numFilterComps = 0;
		}
		for (i = 0; i < numFilterComps; i++) {
			ls->filterComp[i] = sdup(myself, T, filterComp[i]);
			if (ls->filterComp[i] == 0 && filterComp[i] != 0)
				err++;
		}
		ls->numFilterComps = numFilterComps;
		if (filter == 0) {
			ls->filter = concatenateFilterComps(ls->numFilterComps,
					ls->filterComp);
			if (ls->filter == 0)
				err++;
		}
	} else {
		ls->filterComp = 0;
		ls->numFilterComps = 0;
		ls->filter = sdup(myself, T, filter);
		if (ls->filter == 0 && filter != 0)
			err++;
	}

	if (attrs != 0) {
		for (na = 0, a = attrs; *a != 0; a++, na++);
		ls->attrs = am(myself, (na + 1) * sizeof (ls->attrs[0]));
		if (ls->attrs != 0) {
			for (i = 0; i < na; i++) {
				ls->attrs[i] = sdup(myself, T, attrs[i]);
				if (ls->attrs[i] == 0 && attrs[i] != 0)
					err++;
			}
			ls->attrs[na] = 0;
			ls->numAttrs = na;
		} else {
			err++;
		}
	} else {
		ls->attrs = 0;
		ls->numAttrs = 0;
	}

	ls->attrsonly = attrsonly;
	ls->isDN = isDN;

	if (err > 0) {
		freeLdapSearch(ls);
		ls = 0;
	}

	return (ls);
}

void
freeLdapSearch(__nis_ldap_search_t *ls) {
	int	i;

	if (ls == 0)
		return;

	sfree(ls->base);
	if (ls->filterComp != 0) {
		for (i = 0; i < ls->numFilterComps; i++) {
			sfree(ls->filterComp[i]);
		}
		sfree(ls->filterComp);
	}
	sfree(ls->filter);
	if (ls->attrs != 0) {
		for (i = 0; i < ls->numAttrs; i++) {
			sfree(ls->attrs[i]);
		}
		sfree(ls->attrs);
	}

	free(ls);
}

/*
 * Given a table mapping, and a rule/value pointer,
 * return an LDAP search structure with values suitable for use
 * by ldap_search() or (if dn != 0) ldap_modify(). The rule/value
 * may be modified.
 *
 * If dn != 0 and *dn == 0, the function attemps to return a pointer
 * to the DN. This may necessitate an ldapSearch, if the rule set doesn't
 * produce a DN directly.
 *
 * if dn == 0, and the rule set produces a DN as well as other attribute/
 * value pairs, the function returns an LDAP search structure with the
 * DN only.
 *
 * If 'fromLDAP' is set, the caller wants base/scope/filter from
 * t->objectDN->read; otherwise, from t->objectDN->write.
 *
 * If 'rv' is NULL, the caller wants an enumeration of the container.
 *
 * Note that this function only creates a search structure for 't' itself;
 * if there are alternative mappings for the table, those must be handled
 * by our caller.
 */
__nis_ldap_search_t *
createLdapRequest(__nis_table_mapping_t *t,
		__nis_rule_value_t *rv, char **dn, int fromLDAP,
		int *res, __nis_object_dn_t *obj_dn) {
	int			i, j;
	__nis_ldap_search_t	*ls = 0;
	char			**locDN;
	int			numLocDN, stat = 0, count = 0;
	char			*myself = "createLdapRequest";
	__nis_object_dn_t 	*objectDN = NULL;

	if (t == 0)
		return (0);

	if (obj_dn == NULL)
		objectDN = t->objectDN;
	else
		objectDN = obj_dn;

	if (rv == 0) {
		char	*base;
		char	*filter;

		if (fromLDAP) {
			base = objectDN->read.base;
			filter = makeFilter(objectDN->read.attrs);
		} else {
			base = objectDN->write.base;
			filter = makeFilter(objectDN->write.attrs);
		}

		/* Create request to enumerate container */
		ls = buildLdapSearch(base, objectDN->read.scope, 0, 0, filter,
					0, 0, 0);
		sfree(filter);
		return (ls);
	}

	for (i = 0; i < t->numRulesToLDAP; i++) {
		rv = addLdapRuleValue(t, t->ruleToLDAP[i],
				mit_ldap, mit_nisplus, rv, !fromLDAP, &stat);
		if (rv == 0)
			return (0);
		if (stat == NP_LDAP_RULES_NO_VALUE)
			count++;
		stat = 0;
	}

	/*
	 * If none of the rules produced a value despite
	 * having enough NIS+ columns, return error.
	 */
	if (rv->numAttrs == 0 && count > 0) {
		*res = NP_LDAP_RULES_NO_VALUE;
		return (0);
	}

	/*
	 * 'rv' now contains everything we know about the attributes and
	 * values. Build an LDAP search structure from it.
	 */

	/* Look for a single-valued DN */
	locDN = findDNs(myself, rv, 1,
			fromLDAP ? objectDN->read.base :
					objectDN->write.base,
			&numLocDN);
	if (locDN != 0 && numLocDN == 1) {
		if (dn != 0 && *dn == 0) {
			*dn = locDN[0];
			sfree(locDN);
		} else {
			char	*filter;

			if (fromLDAP)
				filter = makeFilter(objectDN->read.attrs);
			else
				filter = makeFilter(objectDN->write.attrs);
			ls = buildLdapSearch(locDN[0], LDAP_SCOPE_BASE, 0, 0,
						filter, 0, 0, 1);
			sfree(filter);
			freeDNs(locDN, numLocDN);
		}
	} else {
		freeDNs(locDN, numLocDN);
	}

	if (ls != 0) {
		ls->useCon = 1;
		return (ls);
	}

	/*
	 * No DN, or caller wanted a search structure with the non-DN
	 * attributes.
	 */

	/* Initialize search structure */
	{
		char	*filter = (fromLDAP) ?
				makeFilter(objectDN->read.attrs) :
				makeFilter(objectDN->write.attrs);
		char	**ofc;
		int	nofc = 0;

		ofc = makeFilterComp(filter, &nofc);

		if (filter != 0 && ofc == 0) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"%s: Unable to break filter into components: \"%s\"",
				myself, NIL(filter));
			sfree(filter);
			return (0);
		}

		if (fromLDAP)
			ls = buildLdapSearch(objectDN->read.base,
				objectDN->read.scope,
				nofc, ofc, 0, 0, 0, 0);
		else
			ls = buildLdapSearch(objectDN->write.base,
				objectDN->write.scope,
				nofc, ofc, 0, 0, 0, 0);
		sfree(filter);
		freeFilterComp(ofc, nofc);
		if (ls == 0)
			return (0);
	}

	/* Build and add the filter components */
	for (i = 0; i < rv->numAttrs; i++) {
		/* Skip DN */
		if (strcasecmp("dn", rv->attrName[i]) == 0)
			continue;

		/* Skip vt_ber values */
		if (rv->attrVal[i].type == vt_ber)
			continue;

		for (j = 0; j < rv->attrVal[i].numVals; j++) {
			__nis_buffer_t	b = {0, 0};
			char		**tmpComp;

			bp2buf(myself, &b, "%s=%s",
				rv->attrName[i], rv->attrVal[i].val[j].value);
			tmpComp = addFilterComp(b.buf, ls->filterComp,
						&ls->numFilterComps);
			if (tmpComp == 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Unable to add filter component \"%s\"",
					myself, NIL(b.buf));
				sfree(b.buf);
				freeLdapSearch(ls);
				return (0);
			}
			ls->filterComp = tmpComp;
			sfree(b.buf);
		}
	}

	if (ls->numFilterComps > 0) {
		sfree(ls->filter);
		ls->filter = concatenateFilterComps(ls->numFilterComps,
							ls->filterComp);
		if (ls->filter == 0) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"%s: Unable to concatenate filter components",
				myself);
			freeLdapSearch(ls);
			return (0);
		}
	}

	if (dn != 0 && *dn == 0) {
		/*
		 * The caller wants a DN, but we didn't get one from the
		 * the rule set. We have an 'ls', so use it to ldapSearch()
		 * for an entry from which we can extract the DN.
		 */
		__nis_rule_value_t	*rvtmp;
		char			**locDN;
		int			nv = 0, numLocDN;

		rvtmp = ldapSearch(ls, &nv, 0, 0);
		locDN = findDNs(myself, rvtmp, nv, 0, &numLocDN);
		if (locDN != 0 && numLocDN == 1) {
			*dn = locDN[0];
			sfree(locDN);
		} else {
			freeDNs(locDN, numLocDN);
		}
		freeRuleValue(rvtmp, nv);
	}

	ls->useCon = 1;
	return (ls);
}

int	ldapConnAttemptRetryTimeout = 60;	/* seconds */

typedef struct {
	LDAP		*ld;
	mutex_t		mutex;		/* Mutex for update of structure */
	pthread_t	owner;		/* Thread holding mutex */
	mutex_t		rcMutex;	/* Mutex for refCount */
	int		refCount;	/* Reference count */
	int		isBound;	/* Is connection open and usable ? */
	time_t		retryTime;	/* When should open be retried */
	int		status;		/* Status of last operation */
	int		doDis;		/* To be disconnected if refCount==0 */
	int		doDel;		/* To be deleted if refCount zero */
	int		onList;		/* True if on the 'ldapCon' list */
	char		*sp;		/* server string */
	char		*who;
	char		*cred;
	auth_method_t	method;
	int		port;
	struct timeval	bindTimeout;
	struct timeval	searchTimeout;
	struct timeval	modifyTimeout;
	struct timeval	addTimeout;
	struct timeval	deleteTimeout;
	int		simplePage;	/* Can do simple-page */
	int		vlv;		/* Can do VLV */
	uint_t		batchFrom;	/* # entries read in one operation */
	void		*next;
} __nis_ldap_conn_t;

/*
 * List of connections, 'ldapCon', protected by an RW lock.
 *
 * The following locking scheme is used:
 *
 * (1)	Find a connection structure to use to talk to LDAP
 *		Rlock list
 *			Locate structure
 *			Acquire 'mutex'
 *				Acquire 'rcMutex'
 *					update refCount
 *				Release 'rcMutex'
 *			release 'mutex'
 *		Unlock list
 *		Use structure
 *		Release structure when done
 * (2)	Insert/delete structure(s) on/from list
 *		Wlock list
 *			Insert/delete structure; if deleting, must
 *			acquire 'mutex', and 'rcMutex' (in that order),
 *			and 'refCount' must be zero.
 *		Unlock list
 * (3)	Modify structure
 *		Find structure
 *		Acquire 'mutex'
 *			Modify (except refCount)
 *		Release 'mutex'
 *		Release structure
 */

__nis_ldap_conn_t		*ldapCon = 0;
__nis_ldap_conn_t		*ldapReferralCon = 0;
static rwlock_t			ldapConLock = DEFAULTRWLOCK;
static rwlock_t			referralConLock = DEFAULTRWLOCK;

void
exclusiveLC(__nis_ldap_conn_t *lc) {
	pthread_t	me = pthread_self();
	int		stat;

	if (lc == 0)
		return;

	stat = mutex_trylock(&lc->mutex);
	if (stat == EBUSY && lc->owner != me)
		mutex_lock(&lc->mutex);

	lc->owner = me;
}

/* Return 1 if mutex held by this thread, 0 otherwise */
int
assertExclusive(__nis_ldap_conn_t *lc) {
	pthread_t	me;
	int		stat;

	if (lc == 0)
		return (0);

	stat = mutex_trylock(&lc->mutex);

	if (stat == 0) {
		mutex_unlock(&lc->mutex);
		return (0);
	}

	me = pthread_self();
	if (stat != EBUSY || lc->owner != me)
		return (0);

	return (1);
}

void
releaseLC(__nis_ldap_conn_t *lc) {
	pthread_t	me = pthread_self();

	if (lc == 0 || lc->owner != me)
		return;

	lc->owner = 0;
	(void) mutex_unlock(&lc->mutex);
}

void
incrementRC(__nis_ldap_conn_t *lc) {
	if (lc == 0)
		return;

	(void) mutex_lock(&lc->rcMutex);
	lc->refCount++;
	(void) mutex_unlock(&lc->rcMutex);
}

void
decrementRC(__nis_ldap_conn_t *lc) {
	if (lc == 0)
		return;

	(void) mutex_lock(&lc->rcMutex);
	if (lc->refCount > 0)
		lc->refCount--;
	(void) mutex_unlock(&lc->rcMutex);
}

/* Accept a server/port indication, and call ldap_init() */
static LDAP *
ldapInit(char *srv, int port, bool_t use_ssl) {
	LDAP			*ld;
	int			ldapVersion = LDAP_VERSION3;
	int			derefOption = LDAP_DEREF_ALWAYS;
	int			timelimit = proxyInfo.search_time_limit;
	int			sizelimit = proxyInfo.search_size_limit;
	char			*myself = "ldapInit";

	if (srv == 0)
		return (0);

	if (use_ssl) {
		ld = ldapssl_init(srv, port, 1);
	} else {
		ld = ldap_init(srv, port);
	}

	if (ld != 0) {
		(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
					&ldapVersion);
		(void) ldap_set_option(ld, LDAP_OPT_DEREF, &derefOption);
		(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
		(void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &timelimit);
		(void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &sizelimit);
		(void) ldap_set_option(ld, LDAP_OPT_REBIND_ARG, 0);
	}

	return (ld);
}

/*
 * Bind the specified LDAP structure per the supplied authentication.
 * Note: tested with none, simple, and digest_md5. May or may not
 * work with other authentication methods, mostly depending on whether
 * or not 'who' and 'cred' contain sufficient information.
 */
static int
ldapBind(LDAP **ldP, char *who, char *cred, auth_method_t method,
		struct timeval timeout) {
	int		ret;
	LDAP		*ld;
	char		*myself = "ldapBind";

	if (ldP == 0 || (ld = *ldP) == 0)
		return (LDAP_PARAM_ERROR);

	if (method == none) {
		/* No ldap_bind() required (or even possible) */
		ret = LDAP_SUCCESS;
	} else if (method == simple) {
		struct timeval	tv;
		LDAPMessage	*msg = 0;

		tv = timeout;
		ret = ldap_bind(ld, who, cred, LDAP_AUTH_SIMPLE);
		if (ret != -1) {
			ret = ldap_result(ld, ret, 0, &tv, &msg);
			if (ret == 0) {
				ret = LDAP_TIMEOUT;
			} else if (ret == -1) {
				(void) ldap_get_option(ld,
							LDAP_OPT_ERROR_NUMBER,
							&ret);
			} else {
				ret = ldap_result2error(ld, msg, 0);
			}
			if (msg != 0)
				(void) ldap_msgfree(msg);
		} else {
			(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER,
						&ret);
		}
	} else if (method == cram_md5) {
		/* Note: there is only a synchronous call for cram-md5 */
		struct berval ber_cred;

		ber_cred.bv_len = strlen(cred);
		ber_cred.bv_val = cred;
		ret = ldap_sasl_cram_md5_bind_s(ld, who, &ber_cred, NULL, NULL);
	} else if (method == digest_md5) {
		/* Note: there is only a synchronous call for digest-md5 */
		struct berval ber_cred;

		ber_cred.bv_len = strlen(cred);
		ber_cred.bv_val = cred;
		ret = ldap_x_sasl_digest_md5_bind_s(ld, who, &ber_cred, NULL,
			NULL);
	} else {
		ret = LDAP_AUTH_METHOD_NOT_SUPPORTED;
	}

	if (ret != LDAP_SUCCESS) {
		(void) ldap_unbind_s(ld);
		*ldP = 0;
		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
			"%s: Unable to bind as: %s: %s",
			myself, who, ldap_err2string(ret));
	}

	return (ret);
}

/*
 * Free 'lc' and all related memory. Caller must hold the exclusive lock.
 * Return LDAP_UNAVAILABLE upon success, in which case the caller mustn't
 * try to use the structure pointer in any way.
 */
static int
freeCon(__nis_ldap_conn_t *lc) {
	char			*myself = "freeCon";

	if (!assertExclusive(lc))
		return (LDAP_PARAM_ERROR);

	incrementRC(lc);

	/* Must be unused, unbound, and not on the 'ldapCon' list */
	if (lc->onList || lc->refCount != 1 || lc->isBound) {
		lc->doDel++;
		decrementRC(lc);
		return (LDAP_BUSY);
	}

	sfree(lc->sp);
	sfree(lc->who);
	sfree(lc->cred);

	/* Delete structure with both mutex:es held */

	free(lc);

	return (LDAP_UNAVAILABLE);
}

/*
 * Disconnect the specified LDAP connection. Caller must have acquired 'mutex'.
 *
 * On return, if the status is LDAP_UNAVAILABLE, the caller must not touch
 * the structure in any way.
 */
static int
disconnectCon(__nis_ldap_conn_t *lc) {
	int	stat;
	char	*myself = "disconnectCon";

	if (lc == 0)
		return (LDAP_SUCCESS);

	if (!assertExclusive(lc))
		return (LDAP_UNAVAILABLE);

	if (lc->doDis) {

		/* Increment refCount to protect against interference */
		incrementRC(lc);
		/* refCount must be one (i.e., just us) */
		if (lc->refCount != 1) {
			/*
			 * In use; already marked for disconnect,
			 * so do nothing.
			 */
			decrementRC(lc);
			return (LDAP_BUSY);
		}

		stat = ldap_unbind_s(lc->ld);
		if (stat == LDAP_SUCCESS) {
			lc->ld = 0;
			lc->isBound = 0;
			lc->doDis = 0;
			/* Reset simple page and vlv indication */
			lc->simplePage = 0;
			lc->vlv = 0;
		} else if (verbose) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: ldap_unbind_s() => %d (%s)",
				myself, stat, ldap_err2string(stat));
		}

		decrementRC(lc);
	}

	if (lc->doDel) {
		if (LDAP_UNAVAILABLE == freeCon(lc))
			stat = LDAP_UNAVAILABLE;
	}

	return (stat);
}

/*
 * controlSupported will determine for a given connection whether a set
 * of controls is supported or not. The input parameters:
 *	lc	The connection
 *	ctrl	A an array of OID strings, the terminal string should be NULL
 * The returned values if LDAP_SUCCESS is returned:
 *	supported	A caller supplied array which will be set to TRUE or
 *			FALSE depending on whether the corresponding control
 *			is reported as supported.
 * Returns LDAP_SUCCESS if the supportedControl attribute is read.
 */

static int
controlSupported(__nis_ldap_conn_t *lc, char **ctrl, bool_t *supported) {
	LDAPMessage	*res, *e;
	char		*attr[2], *a, **val;
	int		stat, i;
	BerElement	*ber = 0;
	char		*myself = "controlSupported";

	attr[0] = "supportedControl";
	attr[1] = 0;

	stat = ldap_search_st(lc->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)",
				attr, 0, &lc->searchTimeout, &res);
	if (stat != LDAP_SUCCESS) {
		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
	"%s: Unable to retrieve supported control information for %s: %s",
			myself, NIL(lc->sp), ldap_err2string(stat));
		return (stat);
	}

	e = ldap_first_entry(lc->ld, res);
	if (e != 0) {
		a = ldap_first_attribute(lc->ld, e, &ber);
		if (a != 0) {
			val = ldap_get_values(lc->ld, e, a);
			if (val == 0) {
				ldap_memfree(a);
				if (ber != 0)
					ber_free(ber, 0);
			}
		}
	}
	if (e == 0 || a == 0 || val == 0) {
		ldap_msgfree(res);
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
			"%s: Unable to get root DSE for %s",
			myself, NIL(lc->sp));
		return (LDAP_OPERATIONS_ERROR);
	}

	while (*ctrl != NULL) {
		*supported = FALSE;
		for (i = 0; val[i] != 0; i++) {
			if (strstr(val[i], *ctrl) != 0) {
				*supported = TRUE;
				break;
			}
		}
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
			"%s: %s: %s: %s",
			myself, NIL(lc->sp), NIL(*ctrl),
			*supported ? "enabled" : "disabled");
		ctrl++;
		supported++;
	}

	ldap_value_free(val);
	ldap_memfree(a);
	if (ber != 0)
		ber_free(ber, 0);
	ldap_msgfree(res);

	return (stat);
}

/*
 * Connect the LDAP connection 'lc'. Caller must have acquired the 'mutex',
 * and the refCount must be zero.
 *
 * On return, if the status is LDAP_UNAVAILABLE, the caller must not touch
 * the structure in any way.
 */
static int
connectCon(__nis_ldap_conn_t *lc, int check_ctrl) {
	struct timeval	tp;
	int		stat;
	bool_t		supported[2] = {FALSE, FALSE};
	char		*ctrl[3] = {LDAP_CONTROL_SIMPLE_PAGE,
					LDAP_CONTROL_VLVREQUEST,
					NULL};

	if (lc == 0)
		return (LDAP_SUCCESS);

	if (!assertExclusive(lc))
		return (LDAP_PARAM_ERROR);

	incrementRC(lc);
	if (lc->refCount != 1) {
		/*
		 * Don't want to step on structure when it's used by someone
		 * else.
		 */
		decrementRC(lc);
		return (LDAP_BUSY);
	}

	(void) gettimeofday(&tp, 0);

	if (lc->ld != 0) {
		/* Try to disconnect */
		lc->doDis++;
		decrementRC(lc);
		/* disconnctCon() will do the delete if required */
		stat = disconnectCon(lc);
		if (stat != LDAP_SUCCESS)
			return (stat);
		incrementRC(lc);
		if (lc->refCount != 1 || lc->ld != 0) {
			decrementRC(lc);
			return (lc->ld != 0) ? LDAP_SUCCESS :
						LDAP_BUSY;
		}
	} else if (tp.tv_sec < lc->retryTime) {
		/* Too early to retry connect */
		decrementRC(lc);
		return (LDAP_SERVER_DOWN);
	}

	/* Set new retry time in case we fail below */
	lc->retryTime = tp.tv_sec + ldapConnAttemptRetryTimeout;

	lc->ld = ldapInit(lc->sp, lc->port, proxyInfo.tls_method != no_tls);
	if (lc->ld == 0) {
		decrementRC(lc);
		return (LDAP_LOCAL_ERROR);
	}

	stat = lc->status = ldapBind(&lc->ld, lc->who, lc->cred, lc->method,
		lc->bindTimeout);
	if (lc->status == LDAP_SUCCESS) {
		lc->isBound = 1;
		lc->retryTime = 0;
		if (check_ctrl) {
			(void) controlSupported(lc, ctrl, supported);
			lc->simplePage = supported[0];
			lc->vlv = supported[1];
			lc->batchFrom = 50000;
		}
	}

	decrementRC(lc);

	return (stat);
}

/*
 * Find and return a connection believed to be OK.
 */
static __nis_ldap_conn_t *
findCon(int *stat) {
	__nis_ldap_conn_t	*lc;
	int			ldapStat;
	char			*myself = "findCon";

	if (stat == 0)
		stat = &ldapStat;

	(void) rw_rdlock(&ldapConLock);

	if (ldapCon == 0) {
		/* Probably first call; try to set up the connection list */
		(void) rw_unlock(&ldapConLock);
		if ((*stat = setupConList(proxyInfo.default_servers,
					proxyInfo.proxy_dn,
					proxyInfo.proxy_passwd,
					proxyInfo.auth_method)) !=
					LDAP_SUCCESS)
			return (0);
		(void) rw_rdlock(&ldapConLock);
	}

	for (lc = ldapCon; lc != 0; lc = lc->next) {
		exclusiveLC(lc);
		if (!lc->isBound) {
			*stat = connectCon(lc, 1);
			if (*stat != LDAP_SUCCESS) {
				if (*stat != LDAP_UNAVAILABLE) {
					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
		"%s: Cannot open connection to LDAP server (%s): %s",
						myself, NIL(lc->sp),
						ldap_err2string(*stat));
					releaseLC(lc);
				}
				continue;
			}
		} else if (lc->doDis || lc->doDel) {
			*stat = disconnectCon(lc);
			if (*stat != LDAP_UNAVAILABLE)
				releaseLC(lc);
			continue;
		}
		incrementRC(lc);
		releaseLC(lc);
		break;
	}

	(void) rw_unlock(&ldapConLock);

	return (lc);
}

/* Release connection; decrements ref count for the connection */
static void
releaseCon(__nis_ldap_conn_t *lc, int status) {
	int	stat;

	if (lc == 0)
		return;

	exclusiveLC(lc);

	lc->status = status;

	decrementRC(lc);

	if (lc->doDis)
		stat = disconnectCon(lc);
	else
		stat = LDAP_SUCCESS;

	if (stat != LDAP_UNAVAILABLE)
		releaseLC(lc);
}

static __nis_ldap_conn_t *
createCon(char *sp, char *who, char *cred, auth_method_t method, int port) {
	__nis_ldap_conn_t	*lc;
	char			*myself = "createCon";
	char			*r;

	if (sp == 0)
		return (0);

	lc = am(myself, sizeof (*lc));
	if (lc == 0)
		return (0);

	(void) mutex_init(&lc->mutex, 0, 0);
	(void) mutex_init(&lc->rcMutex, 0, 0);

	/* If we need to delete 'lc', freeCon() wants the mutex held */
	exclusiveLC(lc);

	lc->sp = sdup(myself, T, sp);
	if (lc->sp == 0) {
		(void) freeCon(lc);
		return (0);
	}

	if ((r = strchr(lc->sp, ']')) != 0) {
		/*
		 * IPv6 address. Does libldap want this with the
		 * '[' and ']' left in place ? Assume so for now.
		 */
		r = strchr(r, ':');
	} else {
		r = strchr(lc->sp, ':');
	}

	if (r != NULL) {
		*r++ = '\0';
		port = atoi(r);
	} else if (port == 0)
		port = proxyInfo.tls_method == ssl_tls ? LDAPS_PORT : LDAP_PORT;

	if (who != 0) {
		lc->who = sdup(myself, T, who);
		if (lc->who == 0) {
			(void) freeCon(lc);
			return (0);
		}
	}

	if (cred != 0) {
		lc->cred = sdup(myself, T, cred);
		if (lc->cred == 0) {
			(void) freeCon(lc);
			return (0);
		}
	}

	lc->method = method;
	lc->port = port;

	lc->bindTimeout = proxyInfo.bind_timeout;
	lc->searchTimeout = proxyInfo.search_timeout;
	lc->modifyTimeout = proxyInfo.modify_timeout;
	lc->addTimeout = proxyInfo.add_timeout;
	lc->deleteTimeout = proxyInfo.delete_timeout;

	/* All other fields OK at zero */

	releaseLC(lc);

	return (lc);
}

static int
setupConList(char *serverList, char *who, char *cred, auth_method_t method) {
	char			*sls, *sl, *s, *e;
	__nis_ldap_conn_t	*lc, *tmp;
	char			*myself = "setupConList";

	if (serverList == 0)
		return (LDAP_PARAM_ERROR);

	(void) rw_wrlock(&ldapConLock);

	if (ldapCon != 0) {
		/* Assume we've already been called and done the set-up */
		(void) rw_unlock(&ldapConLock);
		return (LDAP_SUCCESS);
	}

	/* Work on a copy of 'serverList' */
	sl = sls = sdup(myself, T, serverList);
	if (sl == 0) {
		(void) rw_unlock(&ldapConLock);
		return (LDAP_NO_MEMORY);
	}

	/* Remove leading white space */
	for (0; *sl == ' ' || *sl == '\t'; sl++);

	/* Create connection for each server on the list */
	for (s = sl; *s != '\0'; s = e+1) {
		int	l;

		/* Find end of server/port token */
		for (e = s; *e != ' ' && *e != '\t' && *e != '\0'; e++);
		if (*e != '\0')
			*e = '\0';
		else
			e--;
		l = slen(s);

		if (l > 0) {
			lc = createCon(s, who, cred, method, 0);
			if (lc == 0) {
				free(sls);
				(void) rw_unlock(&ldapConLock);
				return (LDAP_NO_MEMORY);
			}
			lc->onList = 1;
			if (ldapCon == 0) {
				ldapCon = lc;
			} else {
				/* Insert at end of list */
				for (tmp = ldapCon; tmp->next != 0;
					tmp = tmp->next);
				tmp->next = lc;
			}
		}
	}

	free(sls);

	(void) rw_unlock(&ldapConLock);

	return (LDAP_SUCCESS);
}

static bool_t
is_same_connection(__nis_ldap_conn_t *lc, LDAPURLDesc *ludpp)
{
	return (strcasecmp(ludpp->lud_host, lc->sp) == 0 &&
	    ludpp->lud_port == lc->port);
}

static __nis_ldap_conn_t *
find_connection_from_list(__nis_ldap_conn_t *list,
			LDAPURLDesc *ludpp, int *stat)
{
	int			ldapStat;
	__nis_ldap_conn_t	*lc	= NULL;
	if (stat == 0)
		stat = &ldapStat;

	*stat = LDAP_SUCCESS;

	for (lc = list; lc != 0; lc = lc->next) {
		exclusiveLC(lc);
		if (is_same_connection(lc, ludpp)) {
			if (!lc->isBound) {
				*stat = connectCon(lc, 1);
				if (*stat != LDAP_SUCCESS) {
					releaseLC(lc);
					continue;
				}
			} else if (lc->doDis || lc->doDel) {
				(void) disconnectCon(lc);
				releaseLC(lc);
				continue;
			}
			incrementRC(lc);
			releaseLC(lc);
			break;
		}
		releaseLC(lc);
	}
	return (lc);
}

static __nis_ldap_conn_t *
findReferralCon(char **referralsp, int *stat)
{
	__nis_ldap_conn_t	*lc	= NULL;
	__nis_ldap_conn_t	*tmp;
	int			ldapStat;
	int			i;
	LDAPURLDesc		*ludpp	= NULL;
	char			*myself = "findReferralCon";

	if (stat == 0)
		stat = &ldapStat;

	*stat = LDAP_SUCCESS;

	/*
	 * We have the referral lock - to prevent multiple
	 * threads from creating a referred connection simultaneously
	 *
	 * Note that this code assumes that the ldapCon list is a
	 * static list - that it has previously been created
	 * (otherwise we wouldn't have gotten a referral) and that
	 * it will neither grow or shrink - elements may have new
	 * connections or unbound. If this assumption is no longer valid,
	 * the locking needs to be reworked.
	 */
	(void) rw_rdlock(&referralConLock);

	for (i = 0; referralsp[i] != NULL; i++) {
		if (ldap_url_parse(referralsp[i], &ludpp) != LDAP_SUCCESS)
			continue;
		/* Ignore referrals if not at the appropriate tls level */
#ifdef LDAP_URL_OPT_SECURE
		if (ludpp->lud_options & LDAP_URL_OPT_SECURE) {
			if (proxyInfo.tls_method != ssl_tls) {
				ldap_free_urldesc(ludpp);
				continue;
			}
		} else {
			if (proxyInfo.tls_method != no_tls) {
				ldap_free_urldesc(ludpp);
				continue;
			}
		}
#endif

		/* Determine if we already have a connection to the server */
		lc = find_connection_from_list(ldapReferralCon, ludpp, stat);
		if (lc == NULL)
			lc = find_connection_from_list(ldapCon, ludpp, stat);
		ldap_free_urldesc(ludpp);
		if (lc != NULL) {
			(void) rw_unlock(&referralConLock);
			return (lc);
		}
	}

	for (i = 0; referralsp[i] != NULL; i++) {
		if (ldap_url_parse(referralsp[i], &ludpp) != LDAP_SUCCESS)
			continue;
		/* Ignore referrals if not at the appropriate tls level */
#ifdef LDAP_URL_OPT_SECURE
		if (ludpp->lud_options & LDAP_URL_OPT_SECURE) {
			if (proxyInfo.tls_method != ssl_tls) {
				ldap_free_urldesc(ludpp);
				continue;
			}
		} else {
			if (proxyInfo.tls_method != no_tls) {
				ldap_free_urldesc(ludpp);
				continue;
			}
		}
#endif
		lc = createCon(ludpp->lud_host, proxyInfo.proxy_dn,
		    proxyInfo.proxy_passwd,
		    proxyInfo.auth_method,
		    ludpp->lud_port);
		if (lc == 0) {
			ldap_free_urldesc(ludpp);
			(void) rw_unlock(&referralConLock);
			*stat = LDAP_NO_MEMORY;
			logmsg(MSG_NOTIMECHECK, LOG_INFO,
			    "%s: Could not connect to host: %s",
			    myself, NIL(ludpp->lud_host));
			return (NULL);
		}

		lc->onList = 1;
		if (ldapReferralCon == 0) {
			ldapReferralCon = lc;
		} else {
			/* Insert at end of list */
			for (tmp = ldapReferralCon; tmp->next != 0;
			    tmp = tmp->next) {}
			tmp->next = lc;
		}
		lc = find_connection_from_list(ldapReferralCon, ludpp, stat);
		ldap_free_urldesc(ludpp);
		if (lc != NULL)
			break;
	}
	(void) rw_unlock(&referralConLock);
	if (lc == NULL) {
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
		    "%s: Could not find a connection to %s, ...",
		    myself, NIL(referralsp[0]));
	}

	return (lc);
}

/*
 * Find and return a connection believed to be OK and ensure children
 * will never use parent's connection.
 */
static __nis_ldap_conn_t *
findYPCon(__nis_ldap_search_t *ls, int *stat) {
	__nis_ldap_conn_t	*lc, *newlc;
	int			ldapStat, newstat;
	char			*myself = "findYPCon";

	if (stat == 0)
		stat = &ldapStat;

	(void) rw_rdlock(&ldapConLock);

	if (ldapCon == 0) {
		/* Probably first call; try to set up the connection list */
		(void) rw_unlock(&ldapConLock);
		if ((*stat = setupConList(proxyInfo.default_servers,
					proxyInfo.proxy_dn,
					proxyInfo.proxy_passwd,
					proxyInfo.auth_method)) !=
					LDAP_SUCCESS)
			return (0);
		(void) rw_rdlock(&ldapConLock);
	}

	for (lc = ldapCon; lc != 0; lc = lc->next) {
		exclusiveLC(lc);

		if (lc->isBound && (lc->doDis || lc->doDel)) {
			*stat = disconnectCon(lc);
			if (*stat != LDAP_UNAVAILABLE)
				releaseLC(lc);
			continue;
		}

		/*
		 * Use a new connection for all cases except when
		 * requested by the main thread in the parent ypserv
		 * process.
		 */
		if (ls->useCon == 0) {
			newlc = createCon(lc->sp, lc->who, lc->cred,
						lc->method, lc->port);
			if (!newlc) {
				releaseLC(lc);
				continue;
			}
			if (lc->ld != 0) {
				newlc->simplePage = lc->simplePage;
				newlc->vlv = lc->vlv;
				newlc->batchFrom = lc->batchFrom;
			}
			releaseLC(lc);
			exclusiveLC(newlc);
			newstat = connectCon(newlc, 0);
			if (newstat != LDAP_SUCCESS) {
				if (newstat != LDAP_UNAVAILABLE) {
					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
			"%s: Cannot open connection to LDAP server (%s): %s",
						myself, NIL(newlc->sp),
						ldap_err2string(*stat));
				}
				(void) freeCon(newlc);
				newlc = 0;
				continue;
			}

			/*
			 * No need to put newlc on the ldapCon list as this
			 * connection will be freed after use.
			 */
			newlc->onList = 0;

			lc = newlc;
		} else  if (!lc->isBound) {
			*stat = connectCon(lc, 1);
			if (*stat != LDAP_SUCCESS) {
				if (*stat != LDAP_UNAVAILABLE) {
					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
		"%s: Cannot open connection to LDAP server (%s): %s",
						myself, NIL(lc->sp),
						ldap_err2string(*stat));
					releaseLC(lc);
				}
				continue;
			}
		}

		incrementRC(lc);
		releaseLC(lc);
		break;
	}

	(void) rw_unlock(&ldapConLock);

	return (lc);
}

#define	SORTKEYLIST	"cn uid"

/*
 * Perform an LDAP search operation per 'ls', adding the result(s) to
 * a copy of the 'rvIn' structure; the copy becomes the return value.
 * The caller must deallocate both 'rvIn' and the result, if any.
 *
 * On entry, '*numValues' contains a hint regarding the expected
 * number of entries. Zero is the same as one, and negative values
 * imply no information. This is used to decide whether or not to
 * try an indexed search.
 *
 * On successful (non-NULL) return, '*numValues' contains the number
 * of __nis_rule_value_t elements in the returned array, and '*stat'
 * the LDAP operations status.
 */
__nis_rule_value_t *
ldapSearch(__nis_ldap_search_t *ls, int *numValues, __nis_rule_value_t *rvIn,
		int *ldapStat) {
	__nis_rule_value_t	*rv = 0;
	int			stat, numEntries, numVals, tnv, done, lprEc;
	LDAPMessage		*msg = 0, *m;
	__nis_ldap_conn_t	*lc;
	struct timeval		tv, start, now;
	LDAPsortkey		**sortKeyList = 0;
	LDAPControl		*ctrls[3], *sortCtrl = 0, *vlvCtrl = 0;
	LDAPControl		**retCtrls = 0;
	LDAPVirtualList		vList;
	struct berval		*spCookie = 0;
	int			doVLV = 0;
	int			doSP = 0;
	long			index;
	char			*myself = "ldapSearch";
	bool_t			follow_referral =
					proxyInfo.follow_referral == follow;
	int			doIndex = 1;
	char			**referralsp = NULL;

	ctrls[0] = ctrls[1] = ctrls[2] = 0;

	if (ldapStat == 0)
		ldapStat = &stat;

	if (ls == 0) {
		*ldapStat = LDAP_PARAM_ERROR;
		return (0);
	}

	if (yp2ldap) {
		/* make sure the parent's connection is not used by child */
		if ((lc = findYPCon(ls, ldapStat)) == 0) {
			*ldapStat = LDAP_SERVER_DOWN;
			return (0);
		}
	} else {
		if ((lc = findCon(ldapStat)) == 0) {
			*ldapStat = LDAP_SERVER_DOWN;
			return (0);
		}
	}

	if (numValues != 0 && (*numValues == 0 || *numValues == 1))
		doIndex = 0;

retry_new_conn:
	/* Prefer VLV over simple page, and SP over nothing */
	if (doIndex && lc->vlv) {
		stat = ldap_create_sort_keylist(&sortKeyList, SORTKEYLIST);
		if (stat != LDAP_SUCCESS) {
			logmsg(MSG_NOTIMECHECK, LOG_INFO,
				"%s: Error creating sort keylist: %s",
				myself, ldap_err2string(stat));
			freeRuleValue(rv, numVals);
			*ldapStat = stat;
			rv = 0;
			goto retry_noVLV;
		}
		stat = ldap_create_sort_control(lc->ld, sortKeyList, 1,
						&sortCtrl);
		if (stat == LDAP_SUCCESS) {
			vList.ldvlist_before_count = 0;
			vList.ldvlist_after_count = lc->batchFrom - 1;
			vList.ldvlist_attrvalue = 0;
			vList.ldvlist_extradata = 0;
			index = 1;
			doVLV = 1;
		} else {
			ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
			logmsg(MSG_NOTIMECHECK, LOG_INFO,
				"%s: Error creating VLV sort control: %s",
				myself, ldap_err2string(stat));
			freeRuleValue(rv, numVals);
			*ldapStat = stat;
			rv = 0;
		}
	}

retry_noVLV:

	if (doIndex && !doVLV && lc->simplePage) {
		spCookie = am(myself, sizeof (*spCookie));
		if (spCookie != 0 &&
				(spCookie->bv_val = sdup(myself, T, "")) != 0) {
			spCookie->bv_len = 0;
			doSP = 1;
		} else {
			logmsg(MSG_NOTIMECHECK, LOG_INFO,
	"%s: No memory for simple page cookie; using un-paged LDAP search",
				myself);
			freeRuleValue(rv, numVals);
			*ldapStat = stat;
			rv = 0;
			goto cleanup;
		}
	}

	if (!doVLV && !doSP)
		ctrls[0] = ctrls[1] = 0;

	numVals = 0;
	done = 0;

	if (ls->timeout.tv_sec || ls->timeout.tv_usec) {
		tv = ls->timeout;
	} else {
		tv = lc->searchTimeout;
	}
	(void) gettimeofday(&start, 0);

	do {
		/* don't do vlv or simple page for base level searches */
		if (doVLV && ls->base != LDAP_SCOPE_BASE) {
			vList.ldvlist_index = index;
			vList.ldvlist_size = 0;
			if (vlvCtrl != 0)
				ldap_control_free(vlvCtrl);
			stat = ldap_create_virtuallist_control(lc->ld,
					&vList, &vlvCtrl);
			if (stat != LDAP_SUCCESS) {
				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Error creating VLV at index %ld: %s",
					myself, index, ldap_err2string(stat));
				*ldapStat = stat;
				freeRuleValue(rv, numVals);
				rv = 0;
				goto cleanup;
			}
			ctrls[0] = sortCtrl;
			ctrls[1] = vlvCtrl;
			ctrls[2] = 0;
			stat = ldap_search_ext_s(lc->ld, ls->base,
					ls->scope, ls->filter, ls->attrs,
					ls->attrsonly, ctrls, 0, &tv,
					proxyInfo.search_size_limit, &msg);
		/* don't do vlv or simple page for base level searches */
		} else if (doSP && ls->base != LDAP_SCOPE_BASE) {
			if (ctrls[0] != 0)
				ldap_control_free(ctrls[0]);
			stat = ldap_create_page_control(lc->ld,
					lc->batchFrom, spCookie, 0, &ctrls[0]);
			if (stat != LDAP_SUCCESS) {
				ber_bvfree(spCookie);
				spCookie = 0;
				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"%s: Simple page error: %s",
					myself, ldap_err2string(stat));
				freeRuleValue(rv, numVals);
				*ldapStat = stat;
				rv = 0;
				goto cleanup;
			}
			ctrls[1] = 0;
			stat = ldap_search_ext_s(lc->ld, ls->base,
					ls->scope, ls->filter, ls->attrs,
					ls->attrsonly, ctrls, 0, &tv,
					proxyInfo.search_size_limit, &msg);
		} else {
			stat = ldap_search_st(lc->ld, ls->base, ls->scope,
					ls->filter, ls->attrs, ls->attrsonly,
					&tv, &msg);
		}
		if (stat == LDAP_SUCCESS)
			ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);

		if (stat == LDAP_SERVER_DOWN) {
			lc->doDis++;
			releaseCon(lc, stat);
			lc = (yp2ldap)?findYPCon(ls, ldapStat):
				findCon(ldapStat);
			if (lc == 0) {
				*ldapStat = LDAP_SERVER_DOWN;
				rv =  0;
				goto cleanup;
			}
			goto retry_new_conn;
		}

		if (stat == LDAP_REFERRAL && follow_referral) {
			(void) ldap_parse_result(lc->ld, msg, NULL, NULL, NULL,
				&referralsp, NULL, 0);
			if (referralsp != NULL) {
				/* We support at most one level of referrals */
				follow_referral = FALSE;
				releaseCon(lc, stat);
				lc = findReferralCon(referralsp, &stat);
				ldap_value_free(referralsp);
				if (lc == NULL) {
					freeRuleValue(rv, numVals);
					rv = 0;
					*ldapStat = stat;
					goto cleanup;
				}
				stat = LDAP_SUCCESS;
				goto retry_new_conn;
			}
		}
		*ldapStat = stat;

		if (*ldapStat == LDAP_NO_SUCH_OBJECT) {
			freeRuleValue(rv, numVals);
			rv = 0;
			goto cleanup;
		} else if (doVLV && *ldapStat == LDAP_INSUFFICIENT_ACCESS) {
			/*
			 * The LDAP server (at least Netscape 4.x) can return
			 * LDAP_INSUFFICIENT_ACCESS when VLV is supported,
			 * but not for the bind DN specified. So, just in
			 * case, we clean up, and try again without VLV.
			 */
			doVLV = 0;
			if (msg != 0) {
				(void) ldap_msgfree(msg);
				msg = 0;
			}
			if (ctrls[0] != 0) {
				ldap_control_free(ctrls[0]);
				ctrls[0] = 0;
			}
			if (ctrls[1] != 0) {
				ldap_control_free(ctrls[1]);
				ctrls[1] = 0;
			}
			logmsg(MSG_VLV_INSUFF_ACC, LOG_WARNING,
	"%s: VLV insufficient access from server %s; retrying without VLV",
				myself, NIL(lc->sp));
			goto retry_noVLV;
		} else if (*ldapStat != LDAP_SUCCESS) {
			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
				"ldap_search(0x%x,\n\t\"%s\",\n\t %d,",
				lc->ld, NIL(ls->base), ls->scope);
			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
				"\t\"%s\",\n\t0x%x,\n\t%d) => %d (%s)",
				NIL(ls->filter), ls->attrs, ls->attrsonly,
				*ldapStat, ldap_err2string(stat));
			freeRuleValue(rv, numVals);
			rv = 0;
			goto cleanup;
		}

		numEntries = ldap_count_entries(lc->ld, msg);
		if (numEntries == 0 && *ldapStat == LDAP_SUCCESS) {
			/*
			 * This is a bit weird, but the server (or, at least,
			 * ldap_search_ext()) can sometimes return
			 * LDAP_SUCCESS and no entries when it didn't
			 * find what we were looking for. Seems it ought to
			 * return LDAP_NO_SUCH_OBJECT or some such.
			 */
			freeRuleValue(rv, numVals);
			rv = 0;
			*ldapStat = LDAP_NO_SUCH_OBJECT;
			goto cleanup;
		}

		tnv = numVals + numEntries;
		if ((rv = growRuleValue(numVals, tnv, rv, rvIn)) == 0) {
			*ldapStat = LDAP_NO_MEMORY;
			goto cleanup;
		}

		for (m = ldap_first_entry(lc->ld, msg); m != 0;
				m = ldap_next_entry(lc->ld, m), numVals++) {
			char		*nm;
			BerElement	*ber = 0;

			if (numVals > tnv) {
				logmsg(MSG_NOTIMECHECK, LOG_INFO,
				"%s: Inconsistent LDAP entry count > %d",
					myself, numEntries);
				break;
			}

			nm = ldap_get_dn(lc->ld, m);
			if (nm == 0 || addSAttr2RuleValue("dn", nm,
					&rv[numVals])) {
				sfree(nm);
				*ldapStat = LDAP_NO_MEMORY;
				freeRuleValue(rv, tnv);
				rv = 0;
				goto cleanup;
			}
			sfree(nm);

			for (nm = ldap_first_attribute(lc->ld, m, &ber);
					nm != 0;
				nm = ldap_next_attribute(lc->ld, m, ber)) {
				struct berval	**val;
				int		i, nv;

				val = ldap_get_values_len(lc->ld, m, nm);
				nv = (val == 0) ? 0 :
						ldap_count_values_len(val);
				for (i = 0; i < nv; i++) {
					/*
					 * Since we don't know if the value is
					 * BER-encoded or not, we mark it as a
					 * string. All is well as long as we
					 * don't insist on 'vt_ber' when
					 * interpreting.
					 */
					if (addAttr2RuleValue(vt_string, nm,
							val[i]->bv_val,
							val[i]->bv_len,
							&rv[numVals])) {
						if (ber != 0)
							ber_free(ber, 0);
						ldap_value_free_len(val);
						*ldapStat = LDAP_NO_MEMORY;
						freeRuleValue(rv, tnv);
						rv = 0;
						goto cleanup;
					}
				}
				/*
				 * XXX the ldap_first_attribute(3LDAP) man
				 * page says that the ldap_first_attribute/
				 * ldap_next_attribute should be treated as
				 * static, but the libldap.so.4 code mallocs
				 * (and it's not TSD). So, in order to avoid
				 * a leak, we free the return value.
				 */
				ldap_memfree(nm);
				if (val != 0)
					ldap_value_free_len(val);
			}
			/*
			 * XXX ldap_next_attribute(3LDAP) says that the 'ber'
			 * pointer is freed when it returns NULL, but that's
			 * not implemented in the libldap.so.4 code, so we
			 * free it here in order to avoid a memory leak.
			 */
			if (ber != 0)
				ber_free(ber, 0);
		}

		if (numVals != tnv) {
			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
		"%s: Inconsistent LDAP entry count, found = %d, expected %d",
				myself, numVals, tnv);
		}

		if (doVLV) {
			stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
						&retCtrls, 0);
			if (stat != LDAP_SUCCESS) {
				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"%s: VLV parse result error: %s",
					myself, ldap_err2string(stat));
				*ldapStat = stat;
				freeRuleValue(rv, tnv);
				rv = 0;
				goto cleanup;
			}
			if (retCtrls != 0) {
				unsigned long	targetPosP = 0;
				unsigned long	listSize = 0;

				stat = ldap_parse_virtuallist_control(lc->ld,
					retCtrls, &targetPosP, &listSize,
					&lprEc);
				if (stat == LDAP_SUCCESS) {
					index = targetPosP + lc->batchFrom;
					if (index >= listSize)
						done = 1;
				}
				ldap_controls_free(retCtrls);
				retCtrls = 0;
			} else {
				done = 1;
			}
		} else if (doSP) {
			stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
						&retCtrls, 0);
			if (stat != LDAP_SUCCESS) {
				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Simple page parse result error: %s",
					myself, ldap_err2string(stat));
				*ldapStat = stat;
				freeRuleValue(rv, tnv);
				rv = 0;
				goto cleanup;
			}
			if (retCtrls != 0) {
				unsigned int	count;

				if (spCookie != 0) {
					ber_bvfree(spCookie);
					spCookie = 0;
				}
				stat = ldap_parse_page_control(lc->ld,
						retCtrls, &count, &spCookie);
				if (stat == LDAP_SUCCESS) {
					if (spCookie == 0 ||
						spCookie->bv_val == 0 ||
						spCookie->bv_len == 0)
						done = 1;
				}
				ldap_controls_free(retCtrls);
				retCtrls = 0;
			} else {
				done = 1;
			}
		} else {
			done = 1;
		}

		(void) ldap_msgfree(msg);
		msg = 0;

		/*
		 * If we're using VLV or SP, the timeout should apply
		 * to all calls as an aggregate, so we need to reduce
		 * 'tv' with the time spent on this chunk of data.
		 */
		if (!done) {
			struct timeval	tmp;

			(void) gettimeofday(&now, 0);
			tmp = now;
			now.tv_sec -= start.tv_sec;
			now.tv_usec -= start.tv_usec;
			if (now.tv_usec < 0) {
				now.tv_usec += 1000000;
				now.tv_sec -= 1;
			}
			tv.tv_sec -= now.tv_sec;
			tv.tv_usec -= now.tv_usec;
			if (tv.tv_usec < 0) {
				tv.tv_usec += 1000000;
				tv.tv_sec -= 1;
			}
			if (tv.tv_sec < 0) {
				*ldapStat = LDAP_TIMEOUT;
				freeRuleValue(rv, tnv);
				rv = 0;
				goto cleanup;
			}
			start = tmp;
		}

	} while (!done);

	if (numValues != 0)
		*numValues = numVals;

cleanup:
	if (NULL != lc) {
		if (yp2ldap && ls->useCon == 0) {
			/* Disconnect and free the connection */
			lc->doDis++;
			lc->doDel++;
			releaseCon(lc, stat);
			releaseLC(lc);

		} else {
			releaseCon(lc, stat);
		}
	}
	if (msg != 0)
		(void) ldap_msgfree(msg);
	if (ctrls[0] != 0)
		ldap_control_free(ctrls[0]);
	if (ctrls[1] != 0)
		ldap_control_free(ctrls[1]);
	if (spCookie != 0)
		ber_bvfree(spCookie);
	if (sortKeyList != 0)
		ldap_free_sort_keylist(sortKeyList);

	return (rv);
}

static void
freeLdapModEntry(LDAPMod *m) {

	if (m == 0)
		return;

	sfree(m->mod_type);
	if ((m->mod_op & LDAP_MOD_BVALUES) == 0) {
		char	**v = m->mod_values;

		if (v != 0) {
			while (*v != 0) {
				sfree(*v);
				v++;
			}
			free(m->mod_values);
		}
	} else {
		struct berval	**b = m->mod_bvalues;

		if (b != 0) {
			while (*b != 0) {
				sfree((*b)->bv_val);
				free(*b);
				b++;
			}
			free(m->mod_bvalues);
		}
	}

	free(m);
}

static void
freeLdapMod(LDAPMod **mods) {
	LDAPMod		*m, **org = mods;

	if (mods == 0)
		return;

	while ((m = *mods) != 0) {
		freeLdapModEntry(m);
		mods++;
	}

	free(org);
}

/*
 * Convert a rule-value structure to the corresponding LDAPMod.
 * If 'add' is set, attributes/values are added; object classes
 * are also added. If 'add' is cleared, attributes/values are modified,
 * and 'oc' controls whether or not object classes are added.
 */
LDAPMod **
search2LdapMod(__nis_rule_value_t *rv, int add, int oc) {
	LDAPMod		**mods;
	int		i, j, nm;
	char		*myself = "search2LdapMod";

	if (rv == 0 || rv->numAttrs <= 0)
		return (0);

	mods = am(myself, (rv->numAttrs + 1) * sizeof (mods[0]));
	if (mods == 0)
		return (0);

	for (i = 0, nm = 0; i < rv->numAttrs; i++) {
		int	isOc;
		/*
		 * If we're creating an LDAPMod array for an add operation,
		 * just skip attributes that should be deleted.
		 */
		if (add && rv->attrVal[i].numVals < 0)
			continue;

		/*
		 * Skip DN; it's specified separately to ldap_modify()
		 * and ldap_add(), and mustn't appear among the
		 * attributes to be modified/added.
		 */
		if (strcasecmp("dn", rv->attrName[i]) == 0)
			continue;

		/*
		 * If modifying, and 'oc' is off, skip object class
		 * attributes.
		 */
		isOc = (strcasecmp("objectclass", rv->attrName[i]) == 0);
		if (!add && !oc && isOc)
			continue;

		mods[nm] = am(myself, sizeof (*mods[nm]));
		if (mods[nm] == 0) {
			freeLdapMod(mods);
			return (0);
		}

		/* 'mod_type' is the attribute name */
		mods[nm]->mod_type = sdup(myself, T, rv->attrName[i]);
		if (mods[nm]->mod_type == 0) {
			freeLdapMod(mods);
			return (0);
		}

		/*
		 * numVals < 0 means attribute and all values should
		 * be deleted.
		 */
		if (rv->attrVal[i].numVals < 0) {
			mods[nm]->mod_op = LDAP_MOD_DELETE;
			mods[nm]->mod_values = 0;
			nm++;
			continue;
		}

		/* objectClass attributes always added */
		mods[nm]->mod_op = (add) ? 0 : ((isOc) ? 0 : LDAP_MOD_REPLACE);

		if (rv->attrVal[i].type == vt_string) {
			/*
			 * mods[]->mod_values is a NULL-terminated array
			 * of (char *)'s.
			 */
			mods[nm]->mod_values = am(myself,
					(rv->attrVal[i].numVals + 1) *
					sizeof (mods[nm]->mod_values[0]));
			if (mods[nm]->mod_values == 0) {
				freeLdapMod(mods);
				return (0);
			}
			for (j = 0; j < rv->attrVal[i].numVals; j++) {
				/*
				 * Just in case the string isn't NUL
				 * terminated, add one byte to the
				 * allocated length; am() will initialize
				 * the buffer to zero.
				 */
				mods[nm]->mod_values[j] = am(myself,
					rv->attrVal[i].val[j].length + 1);
				if (mods[nm]->mod_values[j] == 0) {
					freeLdapMod(mods);
					return (0);
				}
				memcpy(mods[nm]->mod_values[j],
					rv->attrVal[i].val[j].value,
					rv->attrVal[i].val[j].length);
			}
		} else {
			mods[nm]->mod_op |= LDAP_MOD_BVALUES;
			mods[nm]->mod_bvalues = am(myself,
					(rv->attrVal[i].numVals+1) *
					sizeof (mods[nm]->mod_bvalues[0]));
			if (mods[nm]->mod_bvalues == 0) {
				freeLdapMod(mods);
				return (0);
			}
			for (j = 0; j < rv->attrVal[i].numVals; j++) {
				mods[nm]->mod_bvalues[j] = am(myself,
					sizeof (*mods[nm]->mod_bvalues[j]));
				if (mods[nm]->mod_bvalues[j] == 0) {
					freeLdapMod(mods);
					return (0);
				}
				mods[nm]->mod_bvalues[j]->bv_val = am(myself,
					rv->attrVal[i].val[j].length);
				if (mods[nm]->mod_bvalues[j]->bv_val == 0) {
					freeLdapMod(mods);
					return (0);
				}
				mods[nm]->mod_bvalues[j]->bv_len =
					rv->attrVal[i].val[j].length;
				memcpy(mods[nm]->mod_bvalues[j]->bv_val,
					rv->attrVal[i].val[j].value,
					mods[nm]->mod_bvalues[j]->bv_len);
			}
		}
		nm++;
	}

	return (mods);
}

/*
 * Remove 'value' from 'val'. If value==0, remove the entire
 * __nis_single_value_t array from 'val'.
 */
static void
removeSingleValue(__nis_value_t *val, void *value, int length) {
	int	i;

	if (val == 0)
		return;

	if (value == 0) {
		for (i = 0; i < val->numVals; i++) {
			sfree(val->val[i].value);
		}
		sfree(val->val);
		val->val = 0;
		val->numVals = 0;
		return;
	}

	for (i = 0; i < val->numVals; i++) {
		if (val->val[i].value == 0 || (val->val[i].length != length))
			continue;
		if (memcmp(val->val[i].value, value, length) != 0)
			continue;
		sfree(val->val[i].value);
		if (i != (val->numVals - 1)) {
			(void) memmove(&val->val[i], &val->val[i+1],
				(val->numVals - 1 - i) * sizeof (val->val[0]));
		}
		val->numVals -= 1;
		break;
	}
}

/*
 * Helper function for LdapModify
 * When a modify operation fails with an object class violation,
 * the most probable reason is that the attributes we're modifying are new,
 * and the needed object class are not present. So, try the modify again,
 * but add the object classes this time.
 */

static int
ldapModifyObjectClass(__nis_ldap_conn_t **lc, char *dn,
		__nis_rule_value_t *rvIn, char *objClassAttrs)
{
	LDAPMod			**mods = 0;
	int			msgid;
	int			lderr;
	struct timeval		tv;
	int			stat;
	LDAPMessage		*msg = 0;
	char			**referralsp = NULL;
	__nis_rule_value_t	*rv, *rvldap;
	__nis_ldap_search_t	*ls;
	int			i, ocrv, ocrvldap, nv;
	char			*oc[2] = { "objectClass", 0};
	char			*myself = "ldapModifyObjectClass";

	rv = initRuleValue(1, rvIn);
	if (rv == 0)
		return (LDAP_NO_MEMORY);

	delAttrFromRuleValue(rv, "objectClass");
	rv = addObjectClasses(rv, objClassAttrs);
	if (rv == 0) {
		stat = LDAP_OPERATIONS_ERROR;
		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
		    "%s: addObjectClasses failed for %s",
		    myself, NIL(dn));
		goto cleanup;
	}

	/*
	 * Before adding the object classes whole-sale, try retrieving
	 * the entry specified by the 'dn'. If it exists, we filter out
	 * those object classes that already are present in LDAP from our
	 * update.
	 */
	ls = buildLdapSearch(dn, LDAP_SCOPE_BASE, 0, 0, "objectClass=*",
	    oc, 0, 1);
	if (ls == 0) {
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
		    "%s: Unable to build DN search for \"%s\"",
		    myself, NIL(dn));
		/* Fall through to try just adding the object classes */
		goto addObjectClasses;
	}

	nv = 0;
	rvldap = ldapSearch(ls, &nv, 0, &lderr);
	freeLdapSearch(ls);
	if (rvldap == 0) {
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
		    "%s: No data for DN search (\"%s\"); LDAP status %d",
		    myself, NIL(dn), lderr);
		/* Fall through to try just adding the object classes */
		goto addObjectClasses;
	}

	/*
	 * Find the indices of the 'objectClass' attribute
	 * in 'rvldap' and 'rv'.
	 */
	for (i = 0, ocrvldap = -1; i < rvldap->numAttrs; i++) {
		if (rvldap->attrName[i] != 0 &&
		    strcasecmp("objectClass", rvldap->attrName[i]) == 0) {
			ocrvldap = i;
			break;
		}
	}
	for (i = 0, ocrv = -1; i < rv->numAttrs; i++) {
		if (rv->attrName[i] != 0 &&
		    strcasecmp("objectClass", rv->attrName[i]) == 0) {
			ocrv = i;
			break;
		}
	}

	/*
	 * Remove those object classes that already exist
	 * in LDAP (i.e., in 'rvldap') from 'rv'.
	 */
	if (ocrv >= 0 && ocrvldap >= 0) {
		for (i = 0; i < rvldap->attrVal[ocrvldap].numVals; i++) {
			removeSingleValue(&rv->attrVal[ocrv],
			    rvldap->attrVal[ocrvldap].val[i].value,
			    rvldap->attrVal[ocrvldap].val[i].length);
		}
		/*
		 * If no 'objectClass' values left in 'rv', delete
		 * 'objectClass' from 'rv'.
		 */
		if (rv->attrVal[ocrv].numVals == 0)
			delAttrFromRuleValue(rv, "objectClass");
	}

	/*
	 * 'rv' now contains the update we want to make, with just the
	 * object class(es) that need to be added. Fall through to the
	 * actual LDAP modify operation.
	 */
	freeRuleValue(rvldap, 1);

addObjectClasses:

	mods = search2LdapMod(rv, 0, 1);
	if (mods == 0) {
		stat = LDAP_OPERATIONS_ERROR;
		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
	"%s: Unable to create LDAP modify changes with object classes for %s",
		    myself, NIL(dn));
		goto cleanup;
	}
	msgid = ldap_modify((*lc)->ld, dn, mods);
	if (msgid != -1) {
		tv = (*lc)->modifyTimeout;
		stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option((*lc)->ld,
			    LDAP_OPT_ERROR_NUMBER, &stat);
		} else {
			stat = ldap_parse_result((*lc)->ld, msg, &lderr, NULL,
			    NULL, &referralsp, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
			stat = ldap_result2error((*lc)->ld, msg, 0);
		}
	} else {
		(void) ldap_get_option((*lc)->ld, LDAP_OPT_ERROR_NUMBER,
		    &stat);
	}
	if (proxyInfo.follow_referral == follow &&
	    stat == LDAP_REFERRAL && referralsp != NULL) {
		releaseCon(*lc, stat);
		if (msg != NULL)
			(void) ldap_msgfree(msg);
		msg = NULL;
		*lc = findReferralCon(referralsp, &stat);
		ldap_value_free(referralsp);
		referralsp = NULL;
		if (*lc == NULL)
			goto cleanup;
		msgid = ldap_modify((*lc)->ld, dn, mods);
		if (msgid == -1) {
			(void) ldap_get_option((*lc)->ld,
			    LDAP_OPT_ERROR_NUMBER, &stat);
			goto cleanup;
		}
		stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option((*lc)->ld,
			    LDAP_OPT_ERROR_NUMBER, &stat);
		} else {
			stat = ldap_parse_result((*lc)->ld, msg, &lderr,
			    NULL, NULL, NULL, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
		}
	}
cleanup:
	if (mods != 0)
		freeLdapMod(mods);
	freeRuleValue(rv, 1);
	return (stat);
}

/*
 * Modify the specified 'dn' per the attribute names/values in 'rv'.
 * If 'rv' is NULL, we attempt to delete the entire entry.
 *
 * The 'objClassAttrs' parameter is needed if the entry must be added
 * (i.e., created), or a modify fails with an object class violation.
 *
 * If 'addFirst' is set, we try an add before a modify; modify before
 * add otherwise (ignored if we're deleting).
 */
int
ldapModify(char *dn, __nis_rule_value_t *rv, char *objClassAttrs,
		int addFirst) {
	int			stat, add = 0;
	LDAPMod			**mods = 0;
	__nis_ldap_conn_t	*lc;
	struct timeval		tv;
	LDAPMessage		*msg = 0;
	char			*myself = "ldapModify";
	int			msgid;
	int			lderr;
	char			**referralsp = NULL;
	bool_t			delete = FALSE;

	if (dn == 0)
		return (LDAP_PARAM_ERROR);

	if ((lc = findCon(&stat)) == 0)
		return (stat);

	if (rv == 0) {
		delete = TRUE;
		/* Simple case: if rv == 0, try to delete the entire entry */
		msgid = ldap_delete(lc->ld, dn);
		if (msgid == -1) {
			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
			goto cleanup;
		}
		tv = lc->deleteTimeout;
		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);

		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
		} else {
			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
				NULL, &referralsp, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
		}
		if (proxyInfo.follow_referral == follow &&
				stat == LDAP_REFERRAL && referralsp != NULL) {
			releaseCon(lc, stat);
			if (msg != NULL)
				(void) ldap_msgfree(msg);
			msg = NULL;
			lc = findReferralCon(referralsp, &stat);
			ldap_value_free(referralsp);
			if (lc == NULL)
				goto cleanup;
			msgid = ldap_delete(lc->ld, dn);
			if (msgid == -1) {
				(void) ldap_get_option(lc->ld,
					LDAP_OPT_ERROR_NUMBER, &stat);
				goto cleanup;
			}
			stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
			if (stat == 0) {
				stat = LDAP_TIMEOUT;
			} else if (stat == -1) {
				(void) ldap_get_option(lc->ld,
					LDAP_OPT_ERROR_NUMBER, &stat);
			} else {
				stat = ldap_parse_result(lc->ld, msg, &lderr,
					NULL, NULL, NULL, NULL, 0);
				if (stat == LDAP_SUCCESS)
					stat = lderr;
			}
		}
		/* No such object means someone else has done our job */
		if (stat == LDAP_NO_SUCH_OBJECT)
			stat = LDAP_SUCCESS;
	} else {
		if (addFirst) {
			stat = ldapAdd(dn, rv, objClassAttrs, lc);
			lc = NULL;
			if (stat != LDAP_ALREADY_EXISTS)
				goto cleanup;
			if ((lc = findCon(&stat)) == 0)
				return (stat);
		}

		/*
		 * First try the modify without specifying object classes
		 * (i.e., assume they're already present).
		 */
		mods = search2LdapMod(rv, 0, 0);
		if (mods == 0) {
			stat = LDAP_PARAM_ERROR;
			goto cleanup;
		}

		msgid = ldap_modify(lc->ld, dn, mods);
		if (msgid == -1) {
			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
			goto cleanup;
		}
		tv = lc->modifyTimeout;
		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
		} else {
			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
				NULL, &referralsp, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
		}
		if (proxyInfo.follow_referral == follow &&
				stat == LDAP_REFERRAL && referralsp != NULL) {
			releaseCon(lc, stat);
			if (msg != NULL)
				(void) ldap_msgfree(msg);
			msg = NULL;
			lc = findReferralCon(referralsp, &stat);
			ldap_value_free(referralsp);
			referralsp = NULL;
			if (lc == NULL)
				goto cleanup;
			msgid = ldap_modify(lc->ld, dn, mods);
			if (msgid == -1) {
				(void) ldap_get_option(lc->ld,
					LDAP_OPT_ERROR_NUMBER, &stat);
				goto cleanup;
			}
			stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
			if (stat == 0) {
				stat = LDAP_TIMEOUT;
			} else if (stat == -1) {
				(void) ldap_get_option(lc->ld,
					LDAP_OPT_ERROR_NUMBER, &stat);
			} else {
				stat = ldap_parse_result(lc->ld, msg, &lderr,
					NULL, NULL, NULL, NULL, 0);
				if (stat == LDAP_SUCCESS)
					stat = lderr;
			}
		}

		/*
		 * If the modify failed with an object class violation,
		 * the most probable reason is that at least on of the
		 * attributes we're modifying didn't exist before, and
		 * neither did its object class. So, try the modify again,
		 * but add the object classes this time.
		 */
		if (stat == LDAP_OBJECT_CLASS_VIOLATION &&
				objClassAttrs != 0) {
			freeLdapMod(mods);
			mods = 0;
			stat = ldapModifyObjectClass(&lc, dn, rv,
				objClassAttrs);
		}

		if (stat == LDAP_NO_SUCH_ATTRIBUTE) {
			/*
			 * If there was at least one attribute delete, then
			 * the cause of this error could be that said attribute
			 * didn't exist in LDAP. So, do things the slow way,
			 * and try to delete one attribute at a time.
			 */
			int			d, numDelete, st;
			__nis_rule_value_t	*rvt;

			for (d = 0, numDelete = 0; d < rv->numAttrs; d++) {
				if (rv->attrVal[d].numVals < 0)
					numDelete++;
			}

			/* If there's just one, we've already tried */
			if (numDelete <= 1)
				goto cleanup;

			/* Make a copy of the rule value */
			rvt = initRuleValue(1, rv);
			if (rvt == 0)
				goto cleanup;

			/*
			 * Remove all delete attributes from the tmp
			 * rule value.
			 */
			for (d = 0; d < rv->numAttrs; d++) {
				if (rv->attrVal[d].numVals < 0) {
					delAttrFromRuleValue(rvt,
						rv->attrName[d]);
				}
			}

			/*
			 * Now put the attributes back in one by one, and
			 * invoke ourselves.
			 */
			for (d = 0; d < rv->numAttrs; d++) {
				if (rv->attrVal[d].numVals >= 0)
					continue;
				st = addAttr2RuleValue(rv->attrVal[d].type,
					rv->attrName[d], 0, 0, rvt);
				if (st != 0) {
					logmsg(MSG_NOMEM, LOG_ERR,
					"%s: Error deleting \"%s\" for \"%s\"",
						NIL(rv->attrName[d]), NIL(dn));
					stat = LDAP_NO_MEMORY;
					freeRuleValue(rvt, 1);
					goto cleanup;
				}
				stat = ldapModify(dn, rvt, objClassAttrs, 0);
				if (stat != LDAP_SUCCESS &&
					stat != LDAP_NO_SUCH_ATTRIBUTE) {
					freeRuleValue(rvt, 1);
					goto cleanup;
				}
				delAttrFromRuleValue(rvt, rv->attrName[d]);
			}

			/*
			 * If we got here, then all attributes that should
			 * be deleted either have been, or didn't exist. For
			 * our purposes, the latter is as good as the former.
			 */
			stat = LDAP_SUCCESS;
			freeRuleValue(rvt, 1);
		}

		if (stat == LDAP_NO_SUCH_OBJECT && !addFirst) {
			/*
			 * Entry doesn't exist, so try an ldap_add(). If the
			 * ldap_add() also fails, that could be because someone
			 * else added it between our modify and add operations.
			 * If so, we consider that foreign add to be
			 * authoritative (meaning we don't retry our modify).
			 *
			 * Also, if all modify operations specified by 'mods'
			 * are deletes, LDAP_NO_SUCH_OBJECT is a kind of
			 * success; we certainly don't want to create the
			 * entry.
			 */
			int	allDelete;
			LDAPMod	**m;

			for (m = mods, allDelete = 1; *m != 0 && allDelete;
					m++) {
				if (((*m)->mod_op & LDAP_MOD_DELETE) == 0)
					allDelete = 0;
			}

			add = 1;

			if (allDelete) {
				stat = LDAP_SUCCESS;
			} else if (objClassAttrs == 0) {
				/* Now we need it, so this is fatal */
				stat = LDAP_PARAM_ERROR;
			} else {
				stat = ldapAdd(dn, rv, objClassAttrs, lc);
				lc = NULL;
			}
		}
	}

cleanup:
	if (stat != LDAP_SUCCESS) {
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
			"%s(0x%x (%s), \"%s\") => %d (%s)\n",
			!delete ? (add ? "ldap_add" : "ldap_modify") :
				"ldap_delete",
			lc != NULL ? lc->ld : 0,
			lc != NULL ? NIL(lc->sp) : "nil",
			dn, stat, ldap_err2string(stat));
	}

	releaseCon(lc, stat);
	freeLdapMod(mods);
	if (msg != 0)
		(void) ldap_msgfree(msg);

	return (stat);
}

/*
 * Create the entry specified by 'dn' to have the values per 'rv'.
 * The 'objClassAttrs' are the extra object classes we need when
 * creating an entry.
 *
 * If 'lc' is non-NULL, we use that connection; otherwise, we find
 * our own. CAUTION: This connection will be released on return. Regardless
 * of return value, this connection should not subsequently used by the
 * caller.
 *
 * Returns an LDAP status.
 */
int
ldapAdd(char *dn, __nis_rule_value_t *rv, char *objClassAttrs, void *lcv) {
	int			stat;
	LDAPMod			**mods = 0;
	struct timeval		tv;
	LDAPMessage		*msg = 0;
	__nis_ldap_conn_t	*lc = lcv;
	int			msgid;
	int			lderr;
	char			**referralsp = NULL;

	if (dn == 0 || rv == 0 || objClassAttrs == 0) {
		releaseCon(lc, LDAP_SUCCESS);
		return (LDAP_PARAM_ERROR);
	}

	if (lc == 0) {
		if ((lc = findCon(&stat)) == 0)
			return (stat);
	}

	rv = addObjectClasses(rv, objClassAttrs);
	if (rv == 0) {
		stat = LDAP_OPERATIONS_ERROR;
		goto cleanup;
	}

	mods = search2LdapMod(rv, 1, 0);
	if (mods == 0) {
		stat = LDAP_OPERATIONS_ERROR;
		goto cleanup;
	}

	msgid = ldap_add(lc->ld, dn, mods);
	if (msgid == -1) {
		(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
		goto cleanup;
	}
	tv = lc->addTimeout;
	stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
	if (stat == 0) {
		stat = LDAP_TIMEOUT;
	} else if (stat == -1) {
		(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
	} else {
		stat = ldap_parse_result(lc->ld, msg, &lderr, NULL, NULL,
			&referralsp, NULL, 0);
		if (stat == LDAP_SUCCESS)
			stat = lderr;
	}
	if (proxyInfo.follow_referral == follow && stat == LDAP_REFERRAL &&
			referralsp != NULL) {
		releaseCon(lc, stat);
		if (msg != NULL)
			(void) ldap_msgfree(msg);
		msg = NULL;
		lc = findReferralCon(referralsp, &stat);
		ldap_value_free(referralsp);
		if (lc == NULL)
			goto cleanup;
		msgid = ldap_add(lc->ld, dn, mods);
		if (msgid == -1) {
			(void) ldap_get_option(lc->ld,
				LDAP_OPT_ERROR_NUMBER, &stat);
			goto cleanup;
		}
		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
						&stat);
		} else {
			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
				NULL, NULL, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
		}
	}

cleanup:
	if (stat != LDAP_SUCCESS) {
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
			"ldap_add(0x%x (%s), \"%s\") => %d (%s)\n",
			lc != NULL ? lc->ld : 0,
			lc != NULL ? NIL(lc->sp) : "nil",
			dn, stat, ldap_err2string(stat));
	}

	releaseCon(lc, stat);
	freeLdapMod(mods);
	if (msg != 0)
		(void) ldap_msgfree(msg);

	return (stat);
}

/*
 * Change the entry at 'oldDn' to have the new DN (not RDN) 'dn'.
 * Returns an LDAP error status.
 */
int
ldapChangeDN(char *oldDn, char *dn) {
	int			stat;
	__nis_ldap_conn_t	*lc;
	int			i, j, lo, ln;
	char			*rdn;
	int			msgid;
	int			lderr;
	struct timeval		tv;
	LDAPMessage		*msg = 0;
	char			**referralsp = NULL;
	char			*myself = "ldapChangeDN";

	if ((lo = slen(oldDn)) <= 0 || (ln = slen(dn)) <= 0)
		return (LDAP_PARAM_ERROR);

	if (strcasecmp(oldDn, dn) == 0)
		return (LDAP_SUCCESS);

	if ((lc = findCon(&stat)) == 0)
		return (stat);

	rdn = sdup(myself, T, dn);
	if (rdn == 0) {
		releaseCon(lc, LDAP_SUCCESS);
		return (LDAP_NO_MEMORY);
	}

	/* Compare old and new DN from the end */
	for (i = lo-1, j = ln-1; i >= 0 && j >= 0; i--, j--) {
		if (tolower(oldDn[i]) != tolower(rdn[j])) {
			/*
			 * Terminate 'rdn' after this character in order
			 * to snip off the portion of the new DN that is
			 * the same as the old DN. What remains in 'rdn'
			 * is the relative DN.
			 */
			rdn[j+1] = '\0';
			break;
		}
	}

	stat = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL, &msgid);

	if (msgid != -1) {
		tv = lc->modifyTimeout;
		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option(lc->ld,
				LDAP_OPT_ERROR_NUMBER, &stat);
		} else {
			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
				NULL, &referralsp, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
			stat = ldap_result2error(lc->ld, msg, 0);
		}
	} else {
		(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
			&stat);
	}
	if (proxyInfo.follow_referral == follow &&
			stat == LDAP_REFERRAL && referralsp != NULL) {
		releaseCon(lc, stat);
		if (msg != NULL)
			(void) ldap_msgfree(msg);
		msg = NULL;
		lc = findReferralCon(referralsp, &stat);
		ldap_value_free(referralsp);
		referralsp = NULL;
		if (lc == NULL)
			goto cleanup;
		msgid = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL,
			&msgid);
		if (msgid == -1) {
			(void) ldap_get_option(lc->ld,
				LDAP_OPT_ERROR_NUMBER, &stat);
			goto cleanup;
		}
		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
		if (stat == 0) {
			stat = LDAP_TIMEOUT;
		} else if (stat == -1) {
			(void) ldap_get_option(lc->ld,
				LDAP_OPT_ERROR_NUMBER, &stat);
		} else {
			stat = ldap_parse_result(lc->ld, msg, &lderr,
				NULL, NULL, NULL, NULL, 0);
			if (stat == LDAP_SUCCESS)
				stat = lderr;
		}
	}

cleanup:
	if (msg != NULL)
		(void) ldap_msgfree(msg);

#if	1
	fprintf(stderr, "%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s\n",
		myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
		ldap_err2string(stat));
	logmsg(MSG_NOTIMECHECK, LOG_WARNING,
		"%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s",
		myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
		ldap_err2string(stat));
#endif

	if (stat == LDAP_NO_SUCH_OBJECT) {
		/*
		 * Fine from our point of view, since all we want to do
		 * is to make sure that an update to the new DN doesn't
		 * leave the old entry around.
		 */
		stat = LDAP_SUCCESS;
	}

	releaseCon(lc, stat);
	sfree(rdn);

	return (stat);
}