root/usr/src/lib/libnisdb/ldap_op.c
/*
 * 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 2015 Gary Mills
 * 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;

        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) {

        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 (; *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;
                                        }
                                }
                                ldap_memfree(nm);
                                if (val != 0)
                                        ldap_value_free_len(val);
                        }
                        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;
        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);
}