1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2015 Gary Mills
  23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #include <synch.h>
  28 #include <strings.h>
  29 #include <sys/time.h>
  30 #include <ctype.h>
  31 
  32 #include "ldap_op.h"
  33 #include "ldap_util.h"
  34 #include "ldap_structs.h"
  35 #include "ldap_ruleval.h"
  36 #include "ldap_attr.h"
  37 #include "ldap_print.h"
  38 #include "ldap_glob.h"
  39 
  40 #include "nis_parse_ldap_conf.h"
  41 
  42 #ifndef LDAPS_PORT
  43 #define LDAPS_PORT      636
  44 #endif
  45 
  46 static int setupConList(char *serverList, char *who,
  47                         char *cred, auth_method_t method);
  48 
  49 
  50 /*
  51  * Build one of our internal LDAP search structures, containing copies of
  52  * the supplied input. return NULL in case of error.
  53  *
  54  * If 'filter' is NULL, build an AND-filter using the filter components.
  55  */
  56 __nis_ldap_search_t *
  57 buildLdapSearch(char *base, int scope, int numFilterComps, char **filterComp,
  58                 char *filter, char **attrs, int attrsonly, int isDN) {
  59         __nis_ldap_search_t     *ls;
  60         char                    **a;
  61         int                     i, na, err = 0;
  62         char                    *myself = "buildLdapSearch";
  63 
  64         ls = am(myself, sizeof (*ls));
  65         if (ls == 0)
  66                 return (0);
  67 
  68         ls->base = sdup(myself, T, base);
  69         if (ls->base == 0 && base != 0)
  70                 err++;
  71         ls->scope = scope;
  72 
  73         if (filterComp != 0 && numFilterComps > 0) {
  74                 ls->filterComp = am(myself, numFilterComps *
  75                                         sizeof (ls->filterComp[0]));
  76                 if (ls->filterComp == 0) {
  77                         err++;
  78                         numFilterComps = 0;
  79                 }
  80                 for (i = 0; i < numFilterComps; i++) {
  81                         ls->filterComp[i] = sdup(myself, T, filterComp[i]);
  82                         if (ls->filterComp[i] == 0 && filterComp[i] != 0)
  83                                 err++;
  84                 }
  85                 ls->numFilterComps = numFilterComps;
  86                 if (filter == 0) {
  87                         ls->filter = concatenateFilterComps(ls->numFilterComps,
  88                                         ls->filterComp);
  89                         if (ls->filter == 0)
  90                                 err++;
  91                 }
  92         } else {
  93                 ls->filterComp = 0;
  94                 ls->numFilterComps = 0;
  95                 ls->filter = sdup(myself, T, filter);
  96                 if (ls->filter == 0 && filter != 0)
  97                         err++;
  98         }
  99 
 100         if (attrs != 0) {
 101                 for (na = 0, a = attrs; *a != 0; a++, na++);
 102                 ls->attrs = am(myself, (na + 1) * sizeof (ls->attrs[0]));
 103                 if (ls->attrs != 0) {
 104                         for (i = 0; i < na; i++) {
 105                                 ls->attrs[i] = sdup(myself, T, attrs[i]);
 106                                 if (ls->attrs[i] == 0 && attrs[i] != 0)
 107                                         err++;
 108                         }
 109                         ls->attrs[na] = 0;
 110                         ls->numAttrs = na;
 111                 } else {
 112                         err++;
 113                 }
 114         } else {
 115                 ls->attrs = 0;
 116                 ls->numAttrs = 0;
 117         }
 118 
 119         ls->attrsonly = attrsonly;
 120         ls->isDN = isDN;
 121 
 122         if (err > 0) {
 123                 freeLdapSearch(ls);
 124                 ls = 0;
 125         }
 126 
 127         return (ls);
 128 }
 129 
 130 void
 131 freeLdapSearch(__nis_ldap_search_t *ls) {
 132         int     i;
 133 
 134         if (ls == 0)
 135                 return;
 136 
 137         sfree(ls->base);
 138         if (ls->filterComp != 0) {
 139                 for (i = 0; i < ls->numFilterComps; i++) {
 140                         sfree(ls->filterComp[i]);
 141                 }
 142                 sfree(ls->filterComp);
 143         }
 144         sfree(ls->filter);
 145         if (ls->attrs != 0) {
 146                 for (i = 0; i < ls->numAttrs; i++) {
 147                         sfree(ls->attrs[i]);
 148                 }
 149                 sfree(ls->attrs);
 150         }
 151 
 152         free(ls);
 153 }
 154 
 155 /*
 156  * Given a table mapping, and a rule/value pointer,
 157  * return an LDAP search structure with values suitable for use
 158  * by ldap_search() or (if dn != 0) ldap_modify(). The rule/value
 159  * may be modified.
 160  *
 161  * If dn != 0 and *dn == 0, the function attemps to return a pointer
 162  * to the DN. This may necessitate an ldapSearch, if the rule set doesn't
 163  * produce a DN directly.
 164  *
 165  * if dn == 0, and the rule set produces a DN as well as other attribute/
 166  * value pairs, the function returns an LDAP search structure with the
 167  * DN only.
 168  *
 169  * If 'fromLDAP' is set, the caller wants base/scope/filter from
 170  * t->objectDN->read; otherwise, from t->objectDN->write.
 171  *
 172  * If 'rv' is NULL, the caller wants an enumeration of the container.
 173  *
 174  * Note that this function only creates a search structure for 't' itself;
 175  * if there are alternative mappings for the table, those must be handled
 176  * by our caller.
 177  */
 178 __nis_ldap_search_t *
 179 createLdapRequest(__nis_table_mapping_t *t,
 180                 __nis_rule_value_t *rv, char **dn, int fromLDAP,
 181                 int *res, __nis_object_dn_t *obj_dn) {
 182         int                     i, j;
 183         __nis_ldap_search_t     *ls = 0;
 184         char                    **locDN;
 185         int                     numLocDN, stat = 0, count = 0;
 186         char                    *myself = "createLdapRequest";
 187         __nis_object_dn_t       *objectDN = NULL;
 188 
 189         if (t == 0)
 190                 return (0);
 191 
 192         if (obj_dn == NULL)
 193                 objectDN = t->objectDN;
 194         else
 195                 objectDN = obj_dn;
 196 
 197         if (rv == 0) {
 198                 char    *base;
 199                 char    *filter;
 200 
 201                 if (fromLDAP) {
 202                         base = objectDN->read.base;
 203                         filter = makeFilter(objectDN->read.attrs);
 204                 } else {
 205                         base = objectDN->write.base;
 206                         filter = makeFilter(objectDN->write.attrs);
 207                 }
 208 
 209                 /* Create request to enumerate container */
 210                 ls = buildLdapSearch(base, objectDN->read.scope, 0, 0, filter,
 211                                         0, 0, 0);
 212                 sfree(filter);
 213                 return (ls);
 214         }
 215 
 216         for (i = 0; i < t->numRulesToLDAP; i++) {
 217                 rv = addLdapRuleValue(t, t->ruleToLDAP[i],
 218                                 mit_ldap, mit_nisplus, rv, !fromLDAP, &stat);
 219                 if (rv == 0)
 220                         return (0);
 221                 if (stat == NP_LDAP_RULES_NO_VALUE)
 222                         count++;
 223                 stat = 0;
 224         }
 225 
 226         /*
 227          * If none of the rules produced a value despite
 228          * having enough NIS+ columns, return error.
 229          */
 230         if (rv->numAttrs == 0 && count > 0) {
 231                 *res = NP_LDAP_RULES_NO_VALUE;
 232                 return (0);
 233         }
 234 
 235         /*
 236          * 'rv' now contains everything we know about the attributes and
 237          * values. Build an LDAP search structure from it.
 238          */
 239 
 240         /* Look for a single-valued DN */
 241         locDN = findDNs(myself, rv, 1,
 242                         fromLDAP ? objectDN->read.base :
 243                                         objectDN->write.base,
 244                         &numLocDN);
 245         if (locDN != 0 && numLocDN == 1) {
 246                 if (dn != 0 && *dn == 0) {
 247                         *dn = locDN[0];
 248                         sfree(locDN);
 249                 } else {
 250                         char    *filter;
 251 
 252                         if (fromLDAP)
 253                                 filter = makeFilter(objectDN->read.attrs);
 254                         else
 255                                 filter = makeFilter(objectDN->write.attrs);
 256                         ls = buildLdapSearch(locDN[0], LDAP_SCOPE_BASE, 0, 0,
 257                                                 filter, 0, 0, 1);
 258                         sfree(filter);
 259                         freeDNs(locDN, numLocDN);
 260                 }
 261         } else {
 262                 freeDNs(locDN, numLocDN);
 263         }
 264 
 265         if (ls != 0) {
 266                 ls->useCon = 1;
 267                 return (ls);
 268         }
 269 
 270         /*
 271          * No DN, or caller wanted a search structure with the non-DN
 272          * attributes.
 273          */
 274 
 275         /* Initialize search structure */
 276         {
 277                 char    *filter = (fromLDAP) ?
 278                                 makeFilter(objectDN->read.attrs) :
 279                                 makeFilter(objectDN->write.attrs);
 280                 char    **ofc;
 281                 int     nofc = 0;
 282 
 283                 ofc = makeFilterComp(filter, &nofc);
 284 
 285                 if (filter != 0 && ofc == 0) {
 286                         logmsg(MSG_NOTIMECHECK, LOG_ERR,
 287                         "%s: Unable to break filter into components: \"%s\"",
 288                                 myself, NIL(filter));
 289                         sfree(filter);
 290                         return (0);
 291                 }
 292 
 293                 if (fromLDAP)
 294                         ls = buildLdapSearch(objectDN->read.base,
 295                                 objectDN->read.scope,
 296                                 nofc, ofc, 0, 0, 0, 0);
 297                 else
 298                         ls = buildLdapSearch(objectDN->write.base,
 299                                 objectDN->write.scope,
 300                                 nofc, ofc, 0, 0, 0, 0);
 301                 sfree(filter);
 302                 freeFilterComp(ofc, nofc);
 303                 if (ls == 0)
 304                         return (0);
 305         }
 306 
 307         /* Build and add the filter components */
 308         for (i = 0; i < rv->numAttrs; i++) {
 309                 /* Skip DN */
 310                 if (strcasecmp("dn", rv->attrName[i]) == 0)
 311                         continue;
 312 
 313                 /* Skip vt_ber values */
 314                 if (rv->attrVal[i].type == vt_ber)
 315                         continue;
 316 
 317                 for (j = 0; j < rv->attrVal[i].numVals; j++) {
 318                         __nis_buffer_t  b = {0, 0};
 319                         char            **tmpComp;
 320 
 321                         bp2buf(myself, &b, "%s=%s",
 322                                 rv->attrName[i], rv->attrVal[i].val[j].value);
 323                         tmpComp = addFilterComp(b.buf, ls->filterComp,
 324                                                 &ls->numFilterComps);
 325                         if (tmpComp == 0) {
 326                                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
 327                                 "%s: Unable to add filter component \"%s\"",
 328                                         myself, NIL(b.buf));
 329                                 sfree(b.buf);
 330                                 freeLdapSearch(ls);
 331                                 return (0);
 332                         }
 333                         ls->filterComp = tmpComp;
 334                         sfree(b.buf);
 335                 }
 336         }
 337 
 338         if (ls->numFilterComps > 0) {
 339                 sfree(ls->filter);
 340                 ls->filter = concatenateFilterComps(ls->numFilterComps,
 341                                                         ls->filterComp);
 342                 if (ls->filter == 0) {
 343                         logmsg(MSG_NOTIMECHECK, LOG_ERR,
 344                         "%s: Unable to concatenate filter components",
 345                                 myself);
 346                         freeLdapSearch(ls);
 347                         return (0);
 348                 }
 349         }
 350 
 351         if (dn != 0 && *dn == 0) {
 352                 /*
 353                  * The caller wants a DN, but we didn't get one from the
 354                  * the rule set. We have an 'ls', so use it to ldapSearch()
 355                  * for an entry from which we can extract the DN.
 356                  */
 357                 __nis_rule_value_t      *rvtmp;
 358                 char                    **locDN;
 359                 int                     nv = 0, numLocDN;
 360 
 361                 rvtmp = ldapSearch(ls, &nv, 0, 0);
 362                 locDN = findDNs(myself, rvtmp, nv, 0, &numLocDN);
 363                 if (locDN != 0 && numLocDN == 1) {
 364                         *dn = locDN[0];
 365                         sfree(locDN);
 366                 } else {
 367                         freeDNs(locDN, numLocDN);
 368                 }
 369                 freeRuleValue(rvtmp, nv);
 370         }
 371 
 372         ls->useCon = 1;
 373         return (ls);
 374 }
 375 
 376 int     ldapConnAttemptRetryTimeout = 60;       /* seconds */
 377 
 378 typedef struct {
 379         LDAP            *ld;
 380         mutex_t         mutex;          /* Mutex for update of structure */
 381         pthread_t       owner;          /* Thread holding mutex */
 382         mutex_t         rcMutex;        /* Mutex for refCount */
 383         int             refCount;       /* Reference count */
 384         int             isBound;        /* Is connection open and usable ? */
 385         time_t          retryTime;      /* When should open be retried */
 386         int             status;         /* Status of last operation */
 387         int             doDis;          /* To be disconnected if refCount==0 */
 388         int             doDel;          /* To be deleted if refCount zero */
 389         int             onList;         /* True if on the 'ldapCon' list */
 390         char            *sp;            /* server string */
 391         char            *who;
 392         char            *cred;
 393         auth_method_t   method;
 394         int             port;
 395         struct timeval  bindTimeout;
 396         struct timeval  searchTimeout;
 397         struct timeval  modifyTimeout;
 398         struct timeval  addTimeout;
 399         struct timeval  deleteTimeout;
 400         int             simplePage;     /* Can do simple-page */
 401         int             vlv;            /* Can do VLV */
 402         uint_t          batchFrom;      /* # entries read in one operation */
 403         void            *next;
 404 } __nis_ldap_conn_t;
 405 
 406 /*
 407  * List of connections, 'ldapCon', protected by an RW lock.
 408  *
 409  * The following locking scheme is used:
 410  *
 411  * (1)  Find a connection structure to use to talk to LDAP
 412  *              Rlock list
 413  *                      Locate structure
 414  *                      Acquire 'mutex'
 415  *                              Acquire 'rcMutex'
 416  *                                      update refCount
 417  *                              Release 'rcMutex'
 418  *                      release 'mutex'
 419  *              Unlock list
 420  *              Use structure
 421  *              Release structure when done
 422  * (2)  Insert/delete structure(s) on/from list
 423  *              Wlock list
 424  *                      Insert/delete structure; if deleting, must
 425  *                      acquire 'mutex', and 'rcMutex' (in that order),
 426  *                      and 'refCount' must be zero.
 427  *              Unlock list
 428  * (3)  Modify structure
 429  *              Find structure
 430  *              Acquire 'mutex'
 431  *                      Modify (except refCount)
 432  *              Release 'mutex'
 433  *              Release structure
 434  */
 435 
 436 __nis_ldap_conn_t               *ldapCon = 0;
 437 __nis_ldap_conn_t               *ldapReferralCon = 0;
 438 static rwlock_t                 ldapConLock = DEFAULTRWLOCK;
 439 static rwlock_t                 referralConLock = DEFAULTRWLOCK;
 440 
 441 void
 442 exclusiveLC(__nis_ldap_conn_t *lc) {
 443         pthread_t       me = pthread_self();
 444         int             stat;
 445 
 446         if (lc == 0)
 447                 return;
 448 
 449         stat = mutex_trylock(&lc->mutex);
 450         if (stat == EBUSY && lc->owner != me)
 451                 mutex_lock(&lc->mutex);
 452 
 453         lc->owner = me;
 454 }
 455 
 456 /* Return 1 if mutex held by this thread, 0 otherwise */
 457 int
 458 assertExclusive(__nis_ldap_conn_t *lc) {
 459         pthread_t       me;
 460         int             stat;
 461 
 462         if (lc == 0)
 463                 return (0);
 464 
 465         stat = mutex_trylock(&lc->mutex);
 466 
 467         if (stat == 0) {
 468                 mutex_unlock(&lc->mutex);
 469                 return (0);
 470         }
 471 
 472         me = pthread_self();
 473         if (stat != EBUSY || lc->owner != me)
 474                 return (0);
 475 
 476         return (1);
 477 }
 478 
 479 void
 480 releaseLC(__nis_ldap_conn_t *lc) {
 481         pthread_t       me = pthread_self();
 482 
 483         if (lc == 0 || lc->owner != me)
 484                 return;
 485 
 486         lc->owner = 0;
 487         (void) mutex_unlock(&lc->mutex);
 488 }
 489 
 490 void
 491 incrementRC(__nis_ldap_conn_t *lc) {
 492         if (lc == 0)
 493                 return;
 494 
 495         (void) mutex_lock(&lc->rcMutex);
 496         lc->refCount++;
 497         (void) mutex_unlock(&lc->rcMutex);
 498 }
 499 
 500 void
 501 decrementRC(__nis_ldap_conn_t *lc) {
 502         if (lc == 0)
 503                 return;
 504 
 505         (void) mutex_lock(&lc->rcMutex);
 506         if (lc->refCount > 0)
 507                 lc->refCount--;
 508         (void) mutex_unlock(&lc->rcMutex);
 509 }
 510 
 511 /* Accept a server/port indication, and call ldap_init() */
 512 static LDAP *
 513 ldapInit(char *srv, int port, bool_t use_ssl) {
 514         LDAP                    *ld;
 515         int                     ldapVersion = LDAP_VERSION3;
 516         int                     derefOption = LDAP_DEREF_ALWAYS;
 517         int                     timelimit = proxyInfo.search_time_limit;
 518         int                     sizelimit = proxyInfo.search_size_limit;
 519 
 520         if (srv == 0)
 521                 return (0);
 522 
 523         if (use_ssl) {
 524                 ld = ldapssl_init(srv, port, 1);
 525         } else {
 526                 ld = ldap_init(srv, port);
 527         }
 528 
 529         if (ld != 0) {
 530                 (void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
 531                                         &ldapVersion);
 532                 (void) ldap_set_option(ld, LDAP_OPT_DEREF, &derefOption);
 533                 (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
 534                 (void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &timelimit);
 535                 (void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &sizelimit);
 536                 (void) ldap_set_option(ld, LDAP_OPT_REBIND_ARG, 0);
 537         }
 538 
 539         return (ld);
 540 }
 541 
 542 /*
 543  * Bind the specified LDAP structure per the supplied authentication.
 544  * Note: tested with none, simple, and digest_md5. May or may not
 545  * work with other authentication methods, mostly depending on whether
 546  * or not 'who' and 'cred' contain sufficient information.
 547  */
 548 static int
 549 ldapBind(LDAP **ldP, char *who, char *cred, auth_method_t method,
 550                 struct timeval timeout) {
 551         int             ret;
 552         LDAP            *ld;
 553         char            *myself = "ldapBind";
 554 
 555         if (ldP == 0 || (ld = *ldP) == 0)
 556                 return (LDAP_PARAM_ERROR);
 557 
 558         if (method == none) {
 559                 /* No ldap_bind() required (or even possible) */
 560                 ret = LDAP_SUCCESS;
 561         } else if (method == simple) {
 562                 struct timeval  tv;
 563                 LDAPMessage     *msg = 0;
 564 
 565                 tv = timeout;
 566                 ret = ldap_bind(ld, who, cred, LDAP_AUTH_SIMPLE);
 567                 if (ret != -1) {
 568                         ret = ldap_result(ld, ret, 0, &tv, &msg);
 569                         if (ret == 0) {
 570                                 ret = LDAP_TIMEOUT;
 571                         } else if (ret == -1) {
 572                                 (void) ldap_get_option(ld,
 573                                                         LDAP_OPT_ERROR_NUMBER,
 574                                                         &ret);
 575                         } else {
 576                                 ret = ldap_result2error(ld, msg, 0);
 577                         }
 578                         if (msg != 0)
 579                                 (void) ldap_msgfree(msg);
 580                 } else {
 581                         (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER,
 582                                                 &ret);
 583                 }
 584         } else if (method == cram_md5) {
 585                 /* Note: there is only a synchronous call for cram-md5 */
 586                 struct berval ber_cred;
 587 
 588                 ber_cred.bv_len = strlen(cred);
 589                 ber_cred.bv_val = cred;
 590                 ret = ldap_sasl_cram_md5_bind_s(ld, who, &ber_cred, NULL, NULL);
 591         } else if (method == digest_md5) {
 592                 /* Note: there is only a synchronous call for digest-md5 */
 593                 struct berval ber_cred;
 594 
 595                 ber_cred.bv_len = strlen(cred);
 596                 ber_cred.bv_val = cred;
 597                 ret = ldap_x_sasl_digest_md5_bind_s(ld, who, &ber_cred, NULL,
 598                         NULL);
 599         } else {
 600                 ret = LDAP_AUTH_METHOD_NOT_SUPPORTED;
 601         }
 602 
 603         if (ret != LDAP_SUCCESS) {
 604                 (void) ldap_unbind_s(ld);
 605                 *ldP = 0;
 606                 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
 607                         "%s: Unable to bind as: %s: %s",
 608                         myself, who, ldap_err2string(ret));
 609         }
 610 
 611         return (ret);
 612 }
 613 
 614 /*
 615  * Free 'lc' and all related memory. Caller must hold the exclusive lock.
 616  * Return LDAP_UNAVAILABLE upon success, in which case the caller mustn't
 617  * try to use the structure pointer in any way.
 618  */
 619 static int
 620 freeCon(__nis_ldap_conn_t *lc) {
 621 
 622         if (!assertExclusive(lc))
 623                 return (LDAP_PARAM_ERROR);
 624 
 625         incrementRC(lc);
 626 
 627         /* Must be unused, unbound, and not on the 'ldapCon' list */
 628         if (lc->onList || lc->refCount != 1 || lc->isBound) {
 629                 lc->doDel++;
 630                 decrementRC(lc);
 631                 return (LDAP_BUSY);
 632         }
 633 
 634         sfree(lc->sp);
 635         sfree(lc->who);
 636         sfree(lc->cred);
 637 
 638         /* Delete structure with both mutex:es held */
 639 
 640         free(lc);
 641 
 642         return (LDAP_UNAVAILABLE);
 643 }
 644 
 645 /*
 646  * Disconnect the specified LDAP connection. Caller must have acquired 'mutex'.
 647  *
 648  * On return, if the status is LDAP_UNAVAILABLE, the caller must not touch
 649  * the structure in any way.
 650  */
 651 static int
 652 disconnectCon(__nis_ldap_conn_t *lc) {
 653         int     stat;
 654         char    *myself = "disconnectCon";
 655 
 656         if (lc == 0)
 657                 return (LDAP_SUCCESS);
 658 
 659         if (!assertExclusive(lc))
 660                 return (LDAP_UNAVAILABLE);
 661 
 662         if (lc->doDis) {
 663 
 664                 /* Increment refCount to protect against interference */
 665                 incrementRC(lc);
 666                 /* refCount must be one (i.e., just us) */
 667                 if (lc->refCount != 1) {
 668                         /*
 669                          * In use; already marked for disconnect,
 670                          * so do nothing.
 671                          */
 672                         decrementRC(lc);
 673                         return (LDAP_BUSY);
 674                 }
 675 
 676                 stat = ldap_unbind_s(lc->ld);
 677                 if (stat == LDAP_SUCCESS) {
 678                         lc->ld = 0;
 679                         lc->isBound = 0;
 680                         lc->doDis = 0;
 681                         /* Reset simple page and vlv indication */
 682                         lc->simplePage = 0;
 683                         lc->vlv = 0;
 684                 } else if (verbose) {
 685                         logmsg(MSG_NOTIMECHECK, LOG_ERR,
 686                                 "%s: ldap_unbind_s() => %d (%s)",
 687                                 myself, stat, ldap_err2string(stat));
 688                 }
 689 
 690                 decrementRC(lc);
 691         }
 692 
 693         if (lc->doDel) {
 694                 if (LDAP_UNAVAILABLE == freeCon(lc))
 695                         stat = LDAP_UNAVAILABLE;
 696         }
 697 
 698         return (stat);
 699 }
 700 
 701 /*
 702  * controlSupported will determine for a given connection whether a set
 703  * of controls is supported or not. The input parameters:
 704  *      lc      The connection
 705  *      ctrl    A an array of OID strings, the terminal string should be NULL
 706  * The returned values if LDAP_SUCCESS is returned:
 707  *      supported       A caller supplied array which will be set to TRUE or
 708  *                      FALSE depending on whether the corresponding control
 709  *                      is reported as supported.
 710  * Returns LDAP_SUCCESS if the supportedControl attribute is read.
 711  */
 712 
 713 static int
 714 controlSupported(__nis_ldap_conn_t *lc, char **ctrl, bool_t *supported) {
 715         LDAPMessage     *res, *e;
 716         char            *attr[2], *a, **val;
 717         int             stat, i;
 718         BerElement      *ber = 0;
 719         char            *myself = "controlSupported";
 720 
 721         attr[0] = "supportedControl";
 722         attr[1] = 0;
 723 
 724         stat = ldap_search_st(lc->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)",
 725                                 attr, 0, &lc->searchTimeout, &res);
 726         if (stat != LDAP_SUCCESS) {
 727                 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
 728         "%s: Unable to retrieve supported control information for %s: %s",
 729                         myself, NIL(lc->sp), ldap_err2string(stat));
 730                 return (stat);
 731         }
 732 
 733         e = ldap_first_entry(lc->ld, res);
 734         if (e != 0) {
 735                 a = ldap_first_attribute(lc->ld, e, &ber);
 736                 if (a != 0) {
 737                         val = ldap_get_values(lc->ld, e, a);
 738                         if (val == 0) {
 739                                 ldap_memfree(a);
 740                                 if (ber != 0)
 741                                         ber_free(ber, 0);
 742                         }
 743                 }
 744         }
 745         if (e == 0 || a == 0 || val == 0) {
 746                 ldap_msgfree(res);
 747                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
 748                         "%s: Unable to get root DSE for %s",
 749                         myself, NIL(lc->sp));
 750                 return (LDAP_OPERATIONS_ERROR);
 751         }
 752 
 753         while (*ctrl != NULL) {
 754                 *supported = FALSE;
 755                 for (i = 0; val[i] != 0; i++) {
 756                         if (strstr(val[i], *ctrl) != 0) {
 757                                 *supported = TRUE;
 758                                 break;
 759                         }
 760                 }
 761                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
 762                         "%s: %s: %s: %s",
 763                         myself, NIL(lc->sp), NIL(*ctrl),
 764                         *supported ? "enabled" : "disabled");
 765                 ctrl++;
 766                 supported++;
 767         }
 768 
 769         ldap_value_free(val);
 770         ldap_memfree(a);
 771         if (ber != 0)
 772                 ber_free(ber, 0);
 773         ldap_msgfree(res);
 774 
 775         return (stat);
 776 }
 777 
 778 /*
 779  * Connect the LDAP connection 'lc'. Caller must have acquired the 'mutex',
 780  * and the refCount must be zero.
 781  *
 782  * On return, if the status is LDAP_UNAVAILABLE, the caller must not touch
 783  * the structure in any way.
 784  */
 785 static int
 786 connectCon(__nis_ldap_conn_t *lc, int check_ctrl) {
 787         struct timeval  tp;
 788         int             stat;
 789         bool_t          supported[2] = {FALSE, FALSE};
 790         char            *ctrl[3] = {LDAP_CONTROL_SIMPLE_PAGE,
 791                                         LDAP_CONTROL_VLVREQUEST,
 792                                         NULL};
 793 
 794         if (lc == 0)
 795                 return (LDAP_SUCCESS);
 796 
 797         if (!assertExclusive(lc))
 798                 return (LDAP_PARAM_ERROR);
 799 
 800         incrementRC(lc);
 801         if (lc->refCount != 1) {
 802                 /*
 803                  * Don't want to step on structure when it's used by someone
 804                  * else.
 805                  */
 806                 decrementRC(lc);
 807                 return (LDAP_BUSY);
 808         }
 809 
 810         (void) gettimeofday(&tp, 0);
 811 
 812         if (lc->ld != 0) {
 813                 /* Try to disconnect */
 814                 lc->doDis++;
 815                 decrementRC(lc);
 816                 /* disconnctCon() will do the delete if required */
 817                 stat = disconnectCon(lc);
 818                 if (stat != LDAP_SUCCESS)
 819                         return (stat);
 820                 incrementRC(lc);
 821                 if (lc->refCount != 1 || lc->ld != 0) {
 822                         decrementRC(lc);
 823                         return (lc->ld != 0) ? LDAP_SUCCESS :
 824                                                 LDAP_BUSY;
 825                 }
 826         } else if (tp.tv_sec < lc->retryTime) {
 827                 /* Too early to retry connect */
 828                 decrementRC(lc);
 829                 return (LDAP_SERVER_DOWN);
 830         }
 831 
 832         /* Set new retry time in case we fail below */
 833         lc->retryTime = tp.tv_sec + ldapConnAttemptRetryTimeout;
 834 
 835         lc->ld = ldapInit(lc->sp, lc->port, proxyInfo.tls_method != no_tls);
 836         if (lc->ld == 0) {
 837                 decrementRC(lc);
 838                 return (LDAP_LOCAL_ERROR);
 839         }
 840 
 841         stat = lc->status = ldapBind(&lc->ld, lc->who, lc->cred, lc->method,
 842                 lc->bindTimeout);
 843         if (lc->status == LDAP_SUCCESS) {
 844                 lc->isBound = 1;
 845                 lc->retryTime = 0;
 846                 if (check_ctrl) {
 847                         (void) controlSupported(lc, ctrl, supported);
 848                         lc->simplePage = supported[0];
 849                         lc->vlv = supported[1];
 850                         lc->batchFrom = 50000;
 851                 }
 852         }
 853 
 854         decrementRC(lc);
 855 
 856         return (stat);
 857 }
 858 
 859 /*
 860  * Find and return a connection believed to be OK.
 861  */
 862 static __nis_ldap_conn_t *
 863 findCon(int *stat) {
 864         __nis_ldap_conn_t       *lc;
 865         int                     ldapStat;
 866         char                    *myself = "findCon";
 867 
 868         if (stat == 0)
 869                 stat = &ldapStat;
 870 
 871         (void) rw_rdlock(&ldapConLock);
 872 
 873         if (ldapCon == 0) {
 874                 /* Probably first call; try to set up the connection list */
 875                 (void) rw_unlock(&ldapConLock);
 876                 if ((*stat = setupConList(proxyInfo.default_servers,
 877                                         proxyInfo.proxy_dn,
 878                                         proxyInfo.proxy_passwd,
 879                                         proxyInfo.auth_method)) !=
 880                                         LDAP_SUCCESS)
 881                         return (0);
 882                 (void) rw_rdlock(&ldapConLock);
 883         }
 884 
 885         for (lc = ldapCon; lc != 0; lc = lc->next) {
 886                 exclusiveLC(lc);
 887                 if (!lc->isBound) {
 888                         *stat = connectCon(lc, 1);
 889                         if (*stat != LDAP_SUCCESS) {
 890                                 if (*stat != LDAP_UNAVAILABLE) {
 891                                         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
 892                 "%s: Cannot open connection to LDAP server (%s): %s",
 893                                                 myself, NIL(lc->sp),
 894                                                 ldap_err2string(*stat));
 895                                         releaseLC(lc);
 896                                 }
 897                                 continue;
 898                         }
 899                 } else if (lc->doDis || lc->doDel) {
 900                         *stat = disconnectCon(lc);
 901                         if (*stat != LDAP_UNAVAILABLE)
 902                                 releaseLC(lc);
 903                         continue;
 904                 }
 905                 incrementRC(lc);
 906                 releaseLC(lc);
 907                 break;
 908         }
 909 
 910         (void) rw_unlock(&ldapConLock);
 911 
 912         return (lc);
 913 }
 914 
 915 /* Release connection; decrements ref count for the connection */
 916 static void
 917 releaseCon(__nis_ldap_conn_t *lc, int status) {
 918         int     stat;
 919 
 920         if (lc == 0)
 921                 return;
 922 
 923         exclusiveLC(lc);
 924 
 925         lc->status = status;
 926 
 927         decrementRC(lc);
 928 
 929         if (lc->doDis)
 930                 stat = disconnectCon(lc);
 931         else
 932                 stat = LDAP_SUCCESS;
 933 
 934         if (stat != LDAP_UNAVAILABLE)
 935                 releaseLC(lc);
 936 }
 937 
 938 static __nis_ldap_conn_t *
 939 createCon(char *sp, char *who, char *cred, auth_method_t method, int port) {
 940         __nis_ldap_conn_t       *lc;
 941         char                    *myself = "createCon";
 942         char                    *r;
 943 
 944         if (sp == 0)
 945                 return (0);
 946 
 947         lc = am(myself, sizeof (*lc));
 948         if (lc == 0)
 949                 return (0);
 950 
 951         (void) mutex_init(&lc->mutex, 0, 0);
 952         (void) mutex_init(&lc->rcMutex, 0, 0);
 953 
 954         /* If we need to delete 'lc', freeCon() wants the mutex held */
 955         exclusiveLC(lc);
 956 
 957         lc->sp = sdup(myself, T, sp);
 958         if (lc->sp == 0) {
 959                 (void) freeCon(lc);
 960                 return (0);
 961         }
 962 
 963         if ((r = strchr(lc->sp, ']')) != 0) {
 964                 /*
 965                  * IPv6 address. Does libldap want this with the
 966                  * '[' and ']' left in place ? Assume so for now.
 967                  */
 968                 r = strchr(r, ':');
 969         } else {
 970                 r = strchr(lc->sp, ':');
 971         }
 972 
 973         if (r != NULL) {
 974                 *r++ = '\0';
 975                 port = atoi(r);
 976         } else if (port == 0)
 977                 port = proxyInfo.tls_method == ssl_tls ? LDAPS_PORT : LDAP_PORT;
 978 
 979         if (who != 0) {
 980                 lc->who = sdup(myself, T, who);
 981                 if (lc->who == 0) {
 982                         (void) freeCon(lc);
 983                         return (0);
 984                 }
 985         }
 986 
 987         if (cred != 0) {
 988                 lc->cred = sdup(myself, T, cred);
 989                 if (lc->cred == 0) {
 990                         (void) freeCon(lc);
 991                         return (0);
 992                 }
 993         }
 994 
 995         lc->method = method;
 996         lc->port = port;
 997 
 998         lc->bindTimeout = proxyInfo.bind_timeout;
 999         lc->searchTimeout = proxyInfo.search_timeout;
1000         lc->modifyTimeout = proxyInfo.modify_timeout;
1001         lc->addTimeout = proxyInfo.add_timeout;
1002         lc->deleteTimeout = proxyInfo.delete_timeout;
1003 
1004         /* All other fields OK at zero */
1005 
1006         releaseLC(lc);
1007 
1008         return (lc);
1009 }
1010 
1011 static int
1012 setupConList(char *serverList, char *who, char *cred, auth_method_t method) {
1013         char                    *sls, *sl, *s, *e;
1014         __nis_ldap_conn_t       *lc, *tmp;
1015         char                    *myself = "setupConList";
1016 
1017         if (serverList == 0)
1018                 return (LDAP_PARAM_ERROR);
1019 
1020         (void) rw_wrlock(&ldapConLock);
1021 
1022         if (ldapCon != 0) {
1023                 /* Assume we've already been called and done the set-up */
1024                 (void) rw_unlock(&ldapConLock);
1025                 return (LDAP_SUCCESS);
1026         }
1027 
1028         /* Work on a copy of 'serverList' */
1029         sl = sls = sdup(myself, T, serverList);
1030         if (sl == 0) {
1031                 (void) rw_unlock(&ldapConLock);
1032                 return (LDAP_NO_MEMORY);
1033         }
1034 
1035         /* Remove leading white space */
1036         for (; *sl == ' ' || *sl == '\t'; sl++);
1037 
1038         /* Create connection for each server on the list */
1039         for (s = sl; *s != '\0'; s = e+1) {
1040                 int     l;
1041 
1042                 /* Find end of server/port token */
1043                 for (e = s; *e != ' ' && *e != '\t' && *e != '\0'; e++);
1044                 if (*e != '\0')
1045                         *e = '\0';
1046                 else
1047                         e--;
1048                 l = slen(s);
1049 
1050                 if (l > 0) {
1051                         lc = createCon(s, who, cred, method, 0);
1052                         if (lc == 0) {
1053                                 free(sls);
1054                                 (void) rw_unlock(&ldapConLock);
1055                                 return (LDAP_NO_MEMORY);
1056                         }
1057                         lc->onList = 1;
1058                         if (ldapCon == 0) {
1059                                 ldapCon = lc;
1060                         } else {
1061                                 /* Insert at end of list */
1062                                 for (tmp = ldapCon; tmp->next != 0;
1063                                         tmp = tmp->next);
1064                                 tmp->next = lc;
1065                         }
1066                 }
1067         }
1068 
1069         free(sls);
1070 
1071         (void) rw_unlock(&ldapConLock);
1072 
1073         return (LDAP_SUCCESS);
1074 }
1075 
1076 static bool_t
1077 is_same_connection(__nis_ldap_conn_t *lc, LDAPURLDesc *ludpp)
1078 {
1079         return (strcasecmp(ludpp->lud_host, lc->sp) == 0 &&
1080             ludpp->lud_port == lc->port);
1081 }
1082 
1083 static __nis_ldap_conn_t *
1084 find_connection_from_list(__nis_ldap_conn_t *list,
1085                         LDAPURLDesc *ludpp, int *stat)
1086 {
1087         int                     ldapStat;
1088         __nis_ldap_conn_t       *lc     = NULL;
1089         if (stat == 0)
1090                 stat = &ldapStat;
1091 
1092         *stat = LDAP_SUCCESS;
1093 
1094         for (lc = list; lc != 0; lc = lc->next) {
1095                 exclusiveLC(lc);
1096                 if (is_same_connection(lc, ludpp)) {
1097                         if (!lc->isBound) {
1098                                 *stat = connectCon(lc, 1);
1099                                 if (*stat != LDAP_SUCCESS) {
1100                                         releaseLC(lc);
1101                                         continue;
1102                                 }
1103                         } else if (lc->doDis || lc->doDel) {
1104                                 (void) disconnectCon(lc);
1105                                 releaseLC(lc);
1106                                 continue;
1107                         }
1108                         incrementRC(lc);
1109                         releaseLC(lc);
1110                         break;
1111                 }
1112                 releaseLC(lc);
1113         }
1114         return (lc);
1115 }
1116 
1117 static __nis_ldap_conn_t *
1118 findReferralCon(char **referralsp, int *stat)
1119 {
1120         __nis_ldap_conn_t       *lc     = NULL;
1121         __nis_ldap_conn_t       *tmp;
1122         int                     ldapStat;
1123         int                     i;
1124         LDAPURLDesc             *ludpp  = NULL;
1125         char                    *myself = "findReferralCon";
1126 
1127         if (stat == 0)
1128                 stat = &ldapStat;
1129 
1130         *stat = LDAP_SUCCESS;
1131 
1132         /*
1133          * We have the referral lock - to prevent multiple
1134          * threads from creating a referred connection simultaneously
1135          *
1136          * Note that this code assumes that the ldapCon list is a
1137          * static list - that it has previously been created
1138          * (otherwise we wouldn't have gotten a referral) and that
1139          * it will neither grow or shrink - elements may have new
1140          * connections or unbound. If this assumption is no longer valid,
1141          * the locking needs to be reworked.
1142          */
1143         (void) rw_rdlock(&referralConLock);
1144 
1145         for (i = 0; referralsp[i] != NULL; i++) {
1146                 if (ldap_url_parse(referralsp[i], &ludpp) != LDAP_SUCCESS)
1147                         continue;
1148                 /* Ignore referrals if not at the appropriate tls level */
1149 #ifdef LDAP_URL_OPT_SECURE
1150                 if (ludpp->lud_options & LDAP_URL_OPT_SECURE) {
1151                         if (proxyInfo.tls_method != ssl_tls) {
1152                                 ldap_free_urldesc(ludpp);
1153                                 continue;
1154                         }
1155                 } else {
1156                         if (proxyInfo.tls_method != no_tls) {
1157                                 ldap_free_urldesc(ludpp);
1158                                 continue;
1159                         }
1160                 }
1161 #endif
1162 
1163                 /* Determine if we already have a connection to the server */
1164                 lc = find_connection_from_list(ldapReferralCon, ludpp, stat);
1165                 if (lc == NULL)
1166                         lc = find_connection_from_list(ldapCon, ludpp, stat);
1167                 ldap_free_urldesc(ludpp);
1168                 if (lc != NULL) {
1169                         (void) rw_unlock(&referralConLock);
1170                         return (lc);
1171                 }
1172         }
1173 
1174         for (i = 0; referralsp[i] != NULL; i++) {
1175                 if (ldap_url_parse(referralsp[i], &ludpp) != LDAP_SUCCESS)
1176                         continue;
1177                 /* Ignore referrals if not at the appropriate tls level */
1178 #ifdef LDAP_URL_OPT_SECURE
1179                 if (ludpp->lud_options & LDAP_URL_OPT_SECURE) {
1180                         if (proxyInfo.tls_method != ssl_tls) {
1181                                 ldap_free_urldesc(ludpp);
1182                                 continue;
1183                         }
1184                 } else {
1185                         if (proxyInfo.tls_method != no_tls) {
1186                                 ldap_free_urldesc(ludpp);
1187                                 continue;
1188                         }
1189                 }
1190 #endif
1191                 lc = createCon(ludpp->lud_host, proxyInfo.proxy_dn,
1192                     proxyInfo.proxy_passwd,
1193                     proxyInfo.auth_method,
1194                     ludpp->lud_port);
1195                 if (lc == 0) {
1196                         ldap_free_urldesc(ludpp);
1197                         (void) rw_unlock(&referralConLock);
1198                         *stat = LDAP_NO_MEMORY;
1199                         logmsg(MSG_NOTIMECHECK, LOG_INFO,
1200                             "%s: Could not connect to host: %s",
1201                             myself, NIL(ludpp->lud_host));
1202                         return (NULL);
1203                 }
1204 
1205                 lc->onList = 1;
1206                 if (ldapReferralCon == 0) {
1207                         ldapReferralCon = lc;
1208                 } else {
1209                         /* Insert at end of list */
1210                         for (tmp = ldapReferralCon; tmp->next != 0;
1211                             tmp = tmp->next) {}
1212                         tmp->next = lc;
1213                 }
1214                 lc = find_connection_from_list(ldapReferralCon, ludpp, stat);
1215                 ldap_free_urldesc(ludpp);
1216                 if (lc != NULL)
1217                         break;
1218         }
1219         (void) rw_unlock(&referralConLock);
1220         if (lc == NULL) {
1221                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
1222                     "%s: Could not find a connection to %s, ...",
1223                     myself, NIL(referralsp[0]));
1224         }
1225 
1226         return (lc);
1227 }
1228 
1229 /*
1230  * Find and return a connection believed to be OK and ensure children
1231  * will never use parent's connection.
1232  */
1233 static __nis_ldap_conn_t *
1234 findYPCon(__nis_ldap_search_t *ls, int *stat) {
1235         __nis_ldap_conn_t       *lc, *newlc;
1236         int                     ldapStat, newstat;
1237         char                    *myself = "findYPCon";
1238 
1239         if (stat == 0)
1240                 stat = &ldapStat;
1241 
1242         (void) rw_rdlock(&ldapConLock);
1243 
1244         if (ldapCon == 0) {
1245                 /* Probably first call; try to set up the connection list */
1246                 (void) rw_unlock(&ldapConLock);
1247                 if ((*stat = setupConList(proxyInfo.default_servers,
1248                                         proxyInfo.proxy_dn,
1249                                         proxyInfo.proxy_passwd,
1250                                         proxyInfo.auth_method)) !=
1251                                         LDAP_SUCCESS)
1252                         return (0);
1253                 (void) rw_rdlock(&ldapConLock);
1254         }
1255 
1256         for (lc = ldapCon; lc != 0; lc = lc->next) {
1257                 exclusiveLC(lc);
1258 
1259                 if (lc->isBound && (lc->doDis || lc->doDel)) {
1260                         *stat = disconnectCon(lc);
1261                         if (*stat != LDAP_UNAVAILABLE)
1262                                 releaseLC(lc);
1263                         continue;
1264                 }
1265 
1266                 /*
1267                  * Use a new connection for all cases except when
1268                  * requested by the main thread in the parent ypserv
1269                  * process.
1270                  */
1271                 if (ls->useCon == 0) {
1272                         newlc = createCon(lc->sp, lc->who, lc->cred,
1273                                                 lc->method, lc->port);
1274                         if (!newlc) {
1275                                 releaseLC(lc);
1276                                 continue;
1277                         }
1278                         if (lc->ld != 0) {
1279                                 newlc->simplePage = lc->simplePage;
1280                                 newlc->vlv = lc->vlv;
1281                                 newlc->batchFrom = lc->batchFrom;
1282                         }
1283                         releaseLC(lc);
1284                         exclusiveLC(newlc);
1285                         newstat = connectCon(newlc, 0);
1286                         if (newstat != LDAP_SUCCESS) {
1287                                 if (newstat != LDAP_UNAVAILABLE) {
1288                                         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1289                         "%s: Cannot open connection to LDAP server (%s): %s",
1290                                                 myself, NIL(newlc->sp),
1291                                                 ldap_err2string(*stat));
1292                                 }
1293                                 (void) freeCon(newlc);
1294                                 newlc = 0;
1295                                 continue;
1296                         }
1297 
1298                         /*
1299                          * No need to put newlc on the ldapCon list as this
1300                          * connection will be freed after use.
1301                          */
1302                         newlc->onList = 0;
1303 
1304                         lc = newlc;
1305                 } else  if (!lc->isBound) {
1306                         *stat = connectCon(lc, 1);
1307                         if (*stat != LDAP_SUCCESS) {
1308                                 if (*stat != LDAP_UNAVAILABLE) {
1309                                         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1310                 "%s: Cannot open connection to LDAP server (%s): %s",
1311                                                 myself, NIL(lc->sp),
1312                                                 ldap_err2string(*stat));
1313                                         releaseLC(lc);
1314                                 }
1315                                 continue;
1316                         }
1317                 }
1318 
1319                 incrementRC(lc);
1320                 releaseLC(lc);
1321                 break;
1322         }
1323 
1324         (void) rw_unlock(&ldapConLock);
1325 
1326         return (lc);
1327 }
1328 
1329 #define SORTKEYLIST     "cn uid"
1330 
1331 /*
1332  * Perform an LDAP search operation per 'ls', adding the result(s) to
1333  * a copy of the 'rvIn' structure; the copy becomes the return value.
1334  * The caller must deallocate both 'rvIn' and the result, if any.
1335  *
1336  * On entry, '*numValues' contains a hint regarding the expected
1337  * number of entries. Zero is the same as one, and negative values
1338  * imply no information. This is used to decide whether or not to
1339  * try an indexed search.
1340  *
1341  * On successful (non-NULL) return, '*numValues' contains the number
1342  * of __nis_rule_value_t elements in the returned array, and '*stat'
1343  * the LDAP operations status.
1344  */
1345 __nis_rule_value_t *
1346 ldapSearch(__nis_ldap_search_t *ls, int *numValues, __nis_rule_value_t *rvIn,
1347                 int *ldapStat) {
1348         __nis_rule_value_t      *rv = 0;
1349         int                     stat, numEntries, numVals, tnv, done, lprEc;
1350         LDAPMessage             *msg = 0, *m;
1351         __nis_ldap_conn_t       *lc;
1352         struct timeval          tv, start, now;
1353         LDAPsortkey             **sortKeyList = 0;
1354         LDAPControl             *ctrls[3], *sortCtrl = 0, *vlvCtrl = 0;
1355         LDAPControl             **retCtrls = 0;
1356         LDAPVirtualList         vList;
1357         struct berval           *spCookie = 0;
1358         int                     doVLV = 0;
1359         int                     doSP = 0;
1360         long                    index;
1361         char                    *myself = "ldapSearch";
1362         bool_t                  follow_referral =
1363                                         proxyInfo.follow_referral == follow;
1364         int                     doIndex = 1;
1365         char                    **referralsp = NULL;
1366 
1367         ctrls[0] = ctrls[1] = ctrls[2] = 0;
1368 
1369         if (ldapStat == 0)
1370                 ldapStat = &stat;
1371 
1372         if (ls == 0) {
1373                 *ldapStat = LDAP_PARAM_ERROR;
1374                 return (0);
1375         }
1376 
1377         if (yp2ldap) {
1378                 /* make sure the parent's connection is not used by child */
1379                 if ((lc = findYPCon(ls, ldapStat)) == 0) {
1380                         *ldapStat = LDAP_SERVER_DOWN;
1381                         return (0);
1382                 }
1383         } else {
1384                 if ((lc = findCon(ldapStat)) == 0) {
1385                         *ldapStat = LDAP_SERVER_DOWN;
1386                         return (0);
1387                 }
1388         }
1389 
1390         if (numValues != 0 && (*numValues == 0 || *numValues == 1))
1391                 doIndex = 0;
1392 
1393 retry_new_conn:
1394         /* Prefer VLV over simple page, and SP over nothing */
1395         if (doIndex && lc->vlv) {
1396                 stat = ldap_create_sort_keylist(&sortKeyList, SORTKEYLIST);
1397                 if (stat != LDAP_SUCCESS) {
1398                         logmsg(MSG_NOTIMECHECK, LOG_INFO,
1399                                 "%s: Error creating sort keylist: %s",
1400                                 myself, ldap_err2string(stat));
1401                         freeRuleValue(rv, numVals);
1402                         *ldapStat = stat;
1403                         rv = 0;
1404                         goto retry_noVLV;
1405                 }
1406                 stat = ldap_create_sort_control(lc->ld, sortKeyList, 1,
1407                                                 &sortCtrl);
1408                 if (stat == LDAP_SUCCESS) {
1409                         vList.ldvlist_before_count = 0;
1410                         vList.ldvlist_after_count = lc->batchFrom - 1;
1411                         vList.ldvlist_attrvalue = 0;
1412                         vList.ldvlist_extradata = 0;
1413                         index = 1;
1414                         doVLV = 1;
1415                 } else {
1416                         ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
1417                         logmsg(MSG_NOTIMECHECK, LOG_INFO,
1418                                 "%s: Error creating VLV sort control: %s",
1419                                 myself, ldap_err2string(stat));
1420                         freeRuleValue(rv, numVals);
1421                         *ldapStat = stat;
1422                         rv = 0;
1423                 }
1424         }
1425 
1426 retry_noVLV:
1427 
1428         if (doIndex && !doVLV && lc->simplePage) {
1429                 spCookie = am(myself, sizeof (*spCookie));
1430                 if (spCookie != 0 &&
1431                                 (spCookie->bv_val = sdup(myself, T, "")) != 0) {
1432                         spCookie->bv_len = 0;
1433                         doSP = 1;
1434                 } else {
1435                         logmsg(MSG_NOTIMECHECK, LOG_INFO,
1436         "%s: No memory for simple page cookie; using un-paged LDAP search",
1437                                 myself);
1438                         freeRuleValue(rv, numVals);
1439                         *ldapStat = stat;
1440                         rv = 0;
1441                         goto cleanup;
1442                 }
1443         }
1444 
1445         if (!doVLV && !doSP)
1446                 ctrls[0] = ctrls[1] = 0;
1447 
1448         numVals = 0;
1449         done = 0;
1450 
1451         if (ls->timeout.tv_sec || ls->timeout.tv_usec) {
1452                 tv = ls->timeout;
1453         } else {
1454                 tv = lc->searchTimeout;
1455         }
1456         (void) gettimeofday(&start, 0);
1457 
1458         do {
1459                 /* don't do vlv or simple page for base level searches */
1460                 if (doVLV && ls->base != LDAP_SCOPE_BASE) {
1461                         vList.ldvlist_index = index;
1462                         vList.ldvlist_size = 0;
1463                         if (vlvCtrl != 0)
1464                                 ldap_control_free(vlvCtrl);
1465                         stat = ldap_create_virtuallist_control(lc->ld,
1466                                         &vList, &vlvCtrl);
1467                         if (stat != LDAP_SUCCESS) {
1468                                 ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1469                                                 &stat);
1470                                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1471                                 "%s: Error creating VLV at index %ld: %s",
1472                                         myself, index, ldap_err2string(stat));
1473                                 *ldapStat = stat;
1474                                 freeRuleValue(rv, numVals);
1475                                 rv = 0;
1476                                 goto cleanup;
1477                         }
1478                         ctrls[0] = sortCtrl;
1479                         ctrls[1] = vlvCtrl;
1480                         ctrls[2] = 0;
1481                         stat = ldap_search_ext_s(lc->ld, ls->base,
1482                                         ls->scope, ls->filter, ls->attrs,
1483                                         ls->attrsonly, ctrls, 0, &tv,
1484                                         proxyInfo.search_size_limit, &msg);
1485                 /* don't do vlv or simple page for base level searches */
1486                 } else if (doSP && ls->base != LDAP_SCOPE_BASE) {
1487                         if (ctrls[0] != 0)
1488                                 ldap_control_free(ctrls[0]);
1489                         stat = ldap_create_page_control(lc->ld,
1490                                         lc->batchFrom, spCookie, 0, &ctrls[0]);
1491                         if (stat != LDAP_SUCCESS) {
1492                                 ber_bvfree(spCookie);
1493                                 spCookie = 0;
1494                                 ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1495                                                 &stat);
1496                                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1497                                         "%s: Simple page error: %s",
1498                                         myself, ldap_err2string(stat));
1499                                 freeRuleValue(rv, numVals);
1500                                 *ldapStat = stat;
1501                                 rv = 0;
1502                                 goto cleanup;
1503                         }
1504                         ctrls[1] = 0;
1505                         stat = ldap_search_ext_s(lc->ld, ls->base,
1506                                         ls->scope, ls->filter, ls->attrs,
1507                                         ls->attrsonly, ctrls, 0, &tv,
1508                                         proxyInfo.search_size_limit, &msg);
1509                 } else {
1510                         stat = ldap_search_st(lc->ld, ls->base, ls->scope,
1511                                         ls->filter, ls->attrs, ls->attrsonly,
1512                                         &tv, &msg);
1513                 }
1514                 if (stat == LDAP_SUCCESS)
1515                         ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
1516 
1517                 if (stat == LDAP_SERVER_DOWN) {
1518                         lc->doDis++;
1519                         releaseCon(lc, stat);
1520                         lc = (yp2ldap)?findYPCon(ls, ldapStat):
1521                                 findCon(ldapStat);
1522                         if (lc == 0) {
1523                                 *ldapStat = LDAP_SERVER_DOWN;
1524                                 rv =  0;
1525                                 goto cleanup;
1526                         }
1527                         goto retry_new_conn;
1528                 }
1529 
1530                 if (stat == LDAP_REFERRAL && follow_referral) {
1531                         (void) ldap_parse_result(lc->ld, msg, NULL, NULL, NULL,
1532                                 &referralsp, NULL, 0);
1533                         if (referralsp != NULL) {
1534                                 /* We support at most one level of referrals */
1535                                 follow_referral = FALSE;
1536                                 releaseCon(lc, stat);
1537                                 lc = findReferralCon(referralsp, &stat);
1538                                 ldap_value_free(referralsp);
1539                                 if (lc == NULL) {
1540                                         freeRuleValue(rv, numVals);
1541                                         rv = 0;
1542                                         *ldapStat = stat;
1543                                         goto cleanup;
1544                                 }
1545                                 stat = LDAP_SUCCESS;
1546                                 goto retry_new_conn;
1547                         }
1548                 }
1549                 *ldapStat = stat;
1550 
1551                 if (*ldapStat == LDAP_NO_SUCH_OBJECT) {
1552                         freeRuleValue(rv, numVals);
1553                         rv = 0;
1554                         goto cleanup;
1555                 } else if (doVLV && *ldapStat == LDAP_INSUFFICIENT_ACCESS) {
1556                         /*
1557                          * The LDAP server (at least Netscape 4.x) can return
1558                          * LDAP_INSUFFICIENT_ACCESS when VLV is supported,
1559                          * but not for the bind DN specified. So, just in
1560                          * case, we clean up, and try again without VLV.
1561                          */
1562                         doVLV = 0;
1563                         if (msg != 0) {
1564                                 (void) ldap_msgfree(msg);
1565                                 msg = 0;
1566                         }
1567                         if (ctrls[0] != 0) {
1568                                 ldap_control_free(ctrls[0]);
1569                                 ctrls[0] = 0;
1570                         }
1571                         if (ctrls[1] != 0) {
1572                                 ldap_control_free(ctrls[1]);
1573                                 ctrls[1] = 0;
1574                         }
1575                         logmsg(MSG_VLV_INSUFF_ACC, LOG_WARNING,
1576         "%s: VLV insufficient access from server %s; retrying without VLV",
1577                                 myself, NIL(lc->sp));
1578                         goto retry_noVLV;
1579                 } else if (*ldapStat != LDAP_SUCCESS) {
1580                         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1581                                 "ldap_search(0x%x,\n\t\"%s\",\n\t %d,",
1582                                 lc->ld, NIL(ls->base), ls->scope);
1583                         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1584                                 "\t\"%s\",\n\t0x%x,\n\t%d) => %d (%s)",
1585                                 NIL(ls->filter), ls->attrs, ls->attrsonly,
1586                                 *ldapStat, ldap_err2string(stat));
1587                         freeRuleValue(rv, numVals);
1588                         rv = 0;
1589                         goto cleanup;
1590                 }
1591 
1592                 numEntries = ldap_count_entries(lc->ld, msg);
1593                 if (numEntries == 0 && *ldapStat == LDAP_SUCCESS) {
1594                         /*
1595                          * This is a bit weird, but the server (or, at least,
1596                          * ldap_search_ext()) can sometimes return
1597                          * LDAP_SUCCESS and no entries when it didn't
1598                          * find what we were looking for. Seems it ought to
1599                          * return LDAP_NO_SUCH_OBJECT or some such.
1600                          */
1601                         freeRuleValue(rv, numVals);
1602                         rv = 0;
1603                         *ldapStat = LDAP_NO_SUCH_OBJECT;
1604                         goto cleanup;
1605                 }
1606 
1607                 tnv = numVals + numEntries;
1608                 if ((rv = growRuleValue(numVals, tnv, rv, rvIn)) == 0) {
1609                         *ldapStat = LDAP_NO_MEMORY;
1610                         goto cleanup;
1611                 }
1612 
1613                 for (m = ldap_first_entry(lc->ld, msg); m != 0;
1614                                 m = ldap_next_entry(lc->ld, m), numVals++) {
1615                         char            *nm;
1616                         BerElement      *ber = 0;
1617 
1618                         if (numVals > tnv) {
1619                                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
1620                                 "%s: Inconsistent LDAP entry count > %d",
1621                                         myself, numEntries);
1622                                 break;
1623                         }
1624 
1625                         nm = ldap_get_dn(lc->ld, m);
1626                         if (nm == 0 || addSAttr2RuleValue("dn", nm,
1627                                         &rv[numVals])) {
1628                                 sfree(nm);
1629                                 *ldapStat = LDAP_NO_MEMORY;
1630                                 freeRuleValue(rv, tnv);
1631                                 rv = 0;
1632                                 goto cleanup;
1633                         }
1634                         sfree(nm);
1635 
1636                         for (nm = ldap_first_attribute(lc->ld, m, &ber);
1637                                         nm != 0;
1638                                 nm = ldap_next_attribute(lc->ld, m, ber)) {
1639                                 struct berval   **val;
1640                                 int             i, nv;
1641 
1642                                 val = ldap_get_values_len(lc->ld, m, nm);
1643                                 nv = (val == 0) ? 0 :
1644                                                 ldap_count_values_len(val);
1645                                 for (i = 0; i < nv; i++) {
1646                                         /*
1647                                          * Since we don't know if the value is
1648                                          * BER-encoded or not, we mark it as a
1649                                          * string. All is well as long as we
1650                                          * don't insist on 'vt_ber' when
1651                                          * interpreting.
1652                                          */
1653                                         if (addAttr2RuleValue(vt_string, nm,
1654                                                         val[i]->bv_val,
1655                                                         val[i]->bv_len,
1656                                                         &rv[numVals])) {
1657                                                 if (ber != 0)
1658                                                         ber_free(ber, 0);
1659                                                 ldap_value_free_len(val);
1660                                                 *ldapStat = LDAP_NO_MEMORY;
1661                                                 freeRuleValue(rv, tnv);
1662                                                 rv = 0;
1663                                                 goto cleanup;
1664                                         }
1665                                 }
1666                                 ldap_memfree(nm);
1667                                 if (val != 0)
1668                                         ldap_value_free_len(val);
1669                         }
1670                         if (ber != 0)
1671                                 ber_free(ber, 0);
1672                 }
1673 
1674                 if (numVals != tnv) {
1675                         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1676                 "%s: Inconsistent LDAP entry count, found = %d, expected %d",
1677                                 myself, numVals, tnv);
1678                 }
1679 
1680                 if (doVLV) {
1681                         stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
1682                                                 &retCtrls, 0);
1683                         if (stat != LDAP_SUCCESS) {
1684                                 ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1685                                                 &stat);
1686                                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1687                                         "%s: VLV parse result error: %s",
1688                                         myself, ldap_err2string(stat));
1689                                 *ldapStat = stat;
1690                                 freeRuleValue(rv, tnv);
1691                                 rv = 0;
1692                                 goto cleanup;
1693                         }
1694                         if (retCtrls != 0) {
1695                                 unsigned long   targetPosP = 0;
1696                                 unsigned long   listSize = 0;
1697 
1698                                 stat = ldap_parse_virtuallist_control(lc->ld,
1699                                         retCtrls, &targetPosP, &listSize,
1700                                         &lprEc);
1701                                 if (stat == LDAP_SUCCESS) {
1702                                         index = targetPosP + lc->batchFrom;
1703                                         if (index >= listSize)
1704                                                 done = 1;
1705                                 }
1706                                 ldap_controls_free(retCtrls);
1707                                 retCtrls = 0;
1708                         } else {
1709                                 done = 1;
1710                         }
1711                 } else if (doSP) {
1712                         stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
1713                                                 &retCtrls, 0);
1714                         if (stat != LDAP_SUCCESS) {
1715                                 ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1716                                                 &stat);
1717                                 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1718                                 "%s: Simple page parse result error: %s",
1719                                         myself, ldap_err2string(stat));
1720                                 *ldapStat = stat;
1721                                 freeRuleValue(rv, tnv);
1722                                 rv = 0;
1723                                 goto cleanup;
1724                         }
1725                         if (retCtrls != 0) {
1726                                 unsigned int    count;
1727 
1728                                 if (spCookie != 0) {
1729                                         ber_bvfree(spCookie);
1730                                         spCookie = 0;
1731                                 }
1732                                 stat = ldap_parse_page_control(lc->ld,
1733                                                 retCtrls, &count, &spCookie);
1734                                 if (stat == LDAP_SUCCESS) {
1735                                         if (spCookie == 0 ||
1736                                                 spCookie->bv_val == 0 ||
1737                                                 spCookie->bv_len == 0)
1738                                                 done = 1;
1739                                 }
1740                                 ldap_controls_free(retCtrls);
1741                                 retCtrls = 0;
1742                         } else {
1743                                 done = 1;
1744                         }
1745                 } else {
1746                         done = 1;
1747                 }
1748 
1749                 (void) ldap_msgfree(msg);
1750                 msg = 0;
1751 
1752                 /*
1753                  * If we're using VLV or SP, the timeout should apply
1754                  * to all calls as an aggregate, so we need to reduce
1755                  * 'tv' with the time spent on this chunk of data.
1756                  */
1757                 if (!done) {
1758                         struct timeval  tmp;
1759 
1760                         (void) gettimeofday(&now, 0);
1761                         tmp = now;
1762                         now.tv_sec -= start.tv_sec;
1763                         now.tv_usec -= start.tv_usec;
1764                         if (now.tv_usec < 0) {
1765                                 now.tv_usec += 1000000;
1766                                 now.tv_sec -= 1;
1767                         }
1768                         tv.tv_sec -= now.tv_sec;
1769                         tv.tv_usec -= now.tv_usec;
1770                         if (tv.tv_usec < 0) {
1771                                 tv.tv_usec += 1000000;
1772                                 tv.tv_sec -= 1;
1773                         }
1774                         if (tv.tv_sec < 0) {
1775                                 *ldapStat = LDAP_TIMEOUT;
1776                                 freeRuleValue(rv, tnv);
1777                                 rv = 0;
1778                                 goto cleanup;
1779                         }
1780                         start = tmp;
1781                 }
1782 
1783         } while (!done);
1784 
1785         if (numValues != 0)
1786                 *numValues = numVals;
1787 
1788 cleanup:
1789         if (NULL != lc) {
1790                 if (yp2ldap && ls->useCon == 0) {
1791                         /* Disconnect and free the connection */
1792                         lc->doDis++;
1793                         lc->doDel++;
1794                         releaseCon(lc, stat);
1795                         releaseLC(lc);
1796 
1797                 } else {
1798                         releaseCon(lc, stat);
1799                 }
1800         }
1801         if (msg != 0)
1802                 (void) ldap_msgfree(msg);
1803         if (ctrls[0] != 0)
1804                 ldap_control_free(ctrls[0]);
1805         if (ctrls[1] != 0)
1806                 ldap_control_free(ctrls[1]);
1807         if (spCookie != 0)
1808                 ber_bvfree(spCookie);
1809         if (sortKeyList != 0)
1810                 ldap_free_sort_keylist(sortKeyList);
1811 
1812         return (rv);
1813 }
1814 
1815 static void
1816 freeLdapModEntry(LDAPMod *m) {
1817 
1818         if (m == 0)
1819                 return;
1820 
1821         sfree(m->mod_type);
1822         if ((m->mod_op & LDAP_MOD_BVALUES) == 0) {
1823                 char    **v = m->mod_values;
1824 
1825                 if (v != 0) {
1826                         while (*v != 0) {
1827                                 sfree(*v);
1828                                 v++;
1829                         }
1830                         free(m->mod_values);
1831                 }
1832         } else {
1833                 struct berval   **b = m->mod_bvalues;
1834 
1835                 if (b != 0) {
1836                         while (*b != 0) {
1837                                 sfree((*b)->bv_val);
1838                                 free(*b);
1839                                 b++;
1840                         }
1841                         free(m->mod_bvalues);
1842                 }
1843         }
1844 
1845         free(m);
1846 }
1847 
1848 static void
1849 freeLdapMod(LDAPMod **mods) {
1850         LDAPMod         *m, **org = mods;
1851 
1852         if (mods == 0)
1853                 return;
1854 
1855         while ((m = *mods) != 0) {
1856                 freeLdapModEntry(m);
1857                 mods++;
1858         }
1859 
1860         free(org);
1861 }
1862 
1863 /*
1864  * Convert a rule-value structure to the corresponding LDAPMod.
1865  * If 'add' is set, attributes/values are added; object classes
1866  * are also added. If 'add' is cleared, attributes/values are modified,
1867  * and 'oc' controls whether or not object classes are added.
1868  */
1869 LDAPMod **
1870 search2LdapMod(__nis_rule_value_t *rv, int add, int oc) {
1871         LDAPMod         **mods;
1872         int             i, j, nm;
1873         char            *myself = "search2LdapMod";
1874 
1875         if (rv == 0 || rv->numAttrs <= 0)
1876                 return (0);
1877 
1878         mods = am(myself, (rv->numAttrs + 1) * sizeof (mods[0]));
1879         if (mods == 0)
1880                 return (0);
1881 
1882         for (i = 0, nm = 0; i < rv->numAttrs; i++) {
1883                 int     isOc;
1884                 /*
1885                  * If we're creating an LDAPMod array for an add operation,
1886                  * just skip attributes that should be deleted.
1887                  */
1888                 if (add && rv->attrVal[i].numVals < 0)
1889                         continue;
1890 
1891                 /*
1892                  * Skip DN; it's specified separately to ldap_modify()
1893                  * and ldap_add(), and mustn't appear among the
1894                  * attributes to be modified/added.
1895                  */
1896                 if (strcasecmp("dn", rv->attrName[i]) == 0)
1897                         continue;
1898 
1899                 /*
1900                  * If modifying, and 'oc' is off, skip object class
1901                  * attributes.
1902                  */
1903                 isOc = (strcasecmp("objectclass", rv->attrName[i]) == 0);
1904                 if (!add && !oc && isOc)
1905                         continue;
1906 
1907                 mods[nm] = am(myself, sizeof (*mods[nm]));
1908                 if (mods[nm] == 0) {
1909                         freeLdapMod(mods);
1910                         return (0);
1911                 }
1912 
1913                 /* 'mod_type' is the attribute name */
1914                 mods[nm]->mod_type = sdup(myself, T, rv->attrName[i]);
1915                 if (mods[nm]->mod_type == 0) {
1916                         freeLdapMod(mods);
1917                         return (0);
1918                 }
1919 
1920                 /*
1921                  * numVals < 0 means attribute and all values should
1922                  * be deleted.
1923                  */
1924                 if (rv->attrVal[i].numVals < 0) {
1925                         mods[nm]->mod_op = LDAP_MOD_DELETE;
1926                         mods[nm]->mod_values = 0;
1927                         nm++;
1928                         continue;
1929                 }
1930 
1931                 /* objectClass attributes always added */
1932                 mods[nm]->mod_op = (add) ? 0 : ((isOc) ? 0 : LDAP_MOD_REPLACE);
1933 
1934                 if (rv->attrVal[i].type == vt_string) {
1935                         /*
1936                          * mods[]->mod_values is a NULL-terminated array
1937                          * of (char *)'s.
1938                          */
1939                         mods[nm]->mod_values = am(myself,
1940                                         (rv->attrVal[i].numVals + 1) *
1941                                         sizeof (mods[nm]->mod_values[0]));
1942                         if (mods[nm]->mod_values == 0) {
1943                                 freeLdapMod(mods);
1944                                 return (0);
1945                         }
1946                         for (j = 0; j < rv->attrVal[i].numVals; j++) {
1947                                 /*
1948                                  * Just in case the string isn't NUL
1949                                  * terminated, add one byte to the
1950                                  * allocated length; am() will initialize
1951                                  * the buffer to zero.
1952                                  */
1953                                 mods[nm]->mod_values[j] = am(myself,
1954                                         rv->attrVal[i].val[j].length + 1);
1955                                 if (mods[nm]->mod_values[j] == 0) {
1956                                         freeLdapMod(mods);
1957                                         return (0);
1958                                 }
1959                                 memcpy(mods[nm]->mod_values[j],
1960                                         rv->attrVal[i].val[j].value,
1961                                         rv->attrVal[i].val[j].length);
1962                         }
1963                 } else {
1964                         mods[nm]->mod_op |= LDAP_MOD_BVALUES;
1965                         mods[nm]->mod_bvalues = am(myself,
1966                                         (rv->attrVal[i].numVals+1) *
1967                                         sizeof (mods[nm]->mod_bvalues[0]));
1968                         if (mods[nm]->mod_bvalues == 0) {
1969                                 freeLdapMod(mods);
1970                                 return (0);
1971                         }
1972                         for (j = 0; j < rv->attrVal[i].numVals; j++) {
1973                                 mods[nm]->mod_bvalues[j] = am(myself,
1974                                         sizeof (*mods[nm]->mod_bvalues[j]));
1975                                 if (mods[nm]->mod_bvalues[j] == 0) {
1976                                         freeLdapMod(mods);
1977                                         return (0);
1978                                 }
1979                                 mods[nm]->mod_bvalues[j]->bv_val = am(myself,
1980                                         rv->attrVal[i].val[j].length);
1981                                 if (mods[nm]->mod_bvalues[j]->bv_val == 0) {
1982                                         freeLdapMod(mods);
1983                                         return (0);
1984                                 }
1985                                 mods[nm]->mod_bvalues[j]->bv_len =
1986                                         rv->attrVal[i].val[j].length;
1987                                 memcpy(mods[nm]->mod_bvalues[j]->bv_val,
1988                                         rv->attrVal[i].val[j].value,
1989                                         mods[nm]->mod_bvalues[j]->bv_len);
1990                         }
1991                 }
1992                 nm++;
1993         }
1994 
1995         return (mods);
1996 }
1997 
1998 /*
1999  * Remove 'value' from 'val'. If value==0, remove the entire
2000  * __nis_single_value_t array from 'val'.
2001  */
2002 static void
2003 removeSingleValue(__nis_value_t *val, void *value, int length) {
2004         int     i;
2005 
2006         if (val == 0)
2007                 return;
2008 
2009         if (value == 0) {
2010                 for (i = 0; i < val->numVals; i++) {
2011                         sfree(val->val[i].value);
2012                 }
2013                 sfree(val->val);
2014                 val->val = 0;
2015                 val->numVals = 0;
2016                 return;
2017         }
2018 
2019         for (i = 0; i < val->numVals; i++) {
2020                 if (val->val[i].value == 0 || (val->val[i].length != length))
2021                         continue;
2022                 if (memcmp(val->val[i].value, value, length) != 0)
2023                         continue;
2024                 sfree(val->val[i].value);
2025                 if (i != (val->numVals - 1)) {
2026                         (void) memmove(&val->val[i], &val->val[i+1],
2027                                 (val->numVals - 1 - i) * sizeof (val->val[0]));
2028                 }
2029                 val->numVals -= 1;
2030                 break;
2031         }
2032 }
2033 
2034 /*
2035  * Helper function for LdapModify
2036  * When a modify operation fails with an object class violation,
2037  * the most probable reason is that the attributes we're modifying are new,
2038  * and the needed object class are not present. So, try the modify again,
2039  * but add the object classes this time.
2040  */
2041 
2042 static int
2043 ldapModifyObjectClass(__nis_ldap_conn_t **lc, char *dn,
2044                 __nis_rule_value_t *rvIn, char *objClassAttrs)
2045 {
2046         LDAPMod                 **mods = 0;
2047         int                     msgid;
2048         int                     lderr;
2049         struct timeval          tv;
2050         int                     stat;
2051         LDAPMessage             *msg = 0;
2052         char                    **referralsp = NULL;
2053         __nis_rule_value_t      *rv, *rvldap;
2054         __nis_ldap_search_t     *ls;
2055         int                     i, ocrv, ocrvldap, nv;
2056         char                    *oc[2] = { "objectClass", 0};
2057         char                    *myself = "ldapModifyObjectClass";
2058 
2059         rv = initRuleValue(1, rvIn);
2060         if (rv == 0)
2061                 return (LDAP_NO_MEMORY);
2062 
2063         delAttrFromRuleValue(rv, "objectClass");
2064         rv = addObjectClasses(rv, objClassAttrs);
2065         if (rv == 0) {
2066                 stat = LDAP_OPERATIONS_ERROR;
2067                 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
2068                     "%s: addObjectClasses failed for %s",
2069                     myself, NIL(dn));
2070                 goto cleanup;
2071         }
2072 
2073         /*
2074          * Before adding the object classes whole-sale, try retrieving
2075          * the entry specified by the 'dn'. If it exists, we filter out
2076          * those object classes that already are present in LDAP from our
2077          * update.
2078          */
2079         ls = buildLdapSearch(dn, LDAP_SCOPE_BASE, 0, 0, "objectClass=*",
2080             oc, 0, 1);
2081         if (ls == 0) {
2082                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
2083                     "%s: Unable to build DN search for \"%s\"",
2084                     myself, NIL(dn));
2085                 /* Fall through to try just adding the object classes */
2086                 goto addObjectClasses;
2087         }
2088 
2089         nv = 0;
2090         rvldap = ldapSearch(ls, &nv, 0, &lderr);
2091         freeLdapSearch(ls);
2092         if (rvldap == 0) {
2093                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
2094                     "%s: No data for DN search (\"%s\"); LDAP status %d",
2095                     myself, NIL(dn), lderr);
2096                 /* Fall through to try just adding the object classes */
2097                 goto addObjectClasses;
2098         }
2099 
2100         /*
2101          * Find the indices of the 'objectClass' attribute
2102          * in 'rvldap' and 'rv'.
2103          */
2104         for (i = 0, ocrvldap = -1; i < rvldap->numAttrs; i++) {
2105                 if (rvldap->attrName[i] != 0 &&
2106                     strcasecmp("objectClass", rvldap->attrName[i]) == 0) {
2107                         ocrvldap = i;
2108                         break;
2109                 }
2110         }
2111         for (i = 0, ocrv = -1; i < rv->numAttrs; i++) {
2112                 if (rv->attrName[i] != 0 &&
2113                     strcasecmp("objectClass", rv->attrName[i]) == 0) {
2114                         ocrv = i;
2115                         break;
2116                 }
2117         }
2118 
2119         /*
2120          * Remove those object classes that already exist
2121          * in LDAP (i.e., in 'rvldap') from 'rv'.
2122          */
2123         if (ocrv >= 0 && ocrvldap >= 0) {
2124                 for (i = 0; i < rvldap->attrVal[ocrvldap].numVals; i++) {
2125                         removeSingleValue(&rv->attrVal[ocrv],
2126                             rvldap->attrVal[ocrvldap].val[i].value,
2127                             rvldap->attrVal[ocrvldap].val[i].length);
2128                 }
2129                 /*
2130                  * If no 'objectClass' values left in 'rv', delete
2131                  * 'objectClass' from 'rv'.
2132                  */
2133                 if (rv->attrVal[ocrv].numVals == 0)
2134                         delAttrFromRuleValue(rv, "objectClass");
2135         }
2136 
2137         /*
2138          * 'rv' now contains the update we want to make, with just the
2139          * object class(es) that need to be added. Fall through to the
2140          * actual LDAP modify operation.
2141          */
2142         freeRuleValue(rvldap, 1);
2143 
2144 addObjectClasses:
2145 
2146         mods = search2LdapMod(rv, 0, 1);
2147         if (mods == 0) {
2148                 stat = LDAP_OPERATIONS_ERROR;
2149                 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
2150         "%s: Unable to create LDAP modify changes with object classes for %s",
2151                     myself, NIL(dn));
2152                 goto cleanup;
2153         }
2154         msgid = ldap_modify((*lc)->ld, dn, mods);
2155         if (msgid != -1) {
2156                 tv = (*lc)->modifyTimeout;
2157                 stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
2158                 if (stat == 0) {
2159                         stat = LDAP_TIMEOUT;
2160                 } else if (stat == -1) {
2161                         (void) ldap_get_option((*lc)->ld,
2162                             LDAP_OPT_ERROR_NUMBER, &stat);
2163                 } else {
2164                         stat = ldap_parse_result((*lc)->ld, msg, &lderr, NULL,
2165                             NULL, &referralsp, NULL, 0);
2166                         if (stat == LDAP_SUCCESS)
2167                                 stat = lderr;
2168                         stat = ldap_result2error((*lc)->ld, msg, 0);
2169                 }
2170         } else {
2171                 (void) ldap_get_option((*lc)->ld, LDAP_OPT_ERROR_NUMBER,
2172                     &stat);
2173         }
2174         if (proxyInfo.follow_referral == follow &&
2175             stat == LDAP_REFERRAL && referralsp != NULL) {
2176                 releaseCon(*lc, stat);
2177                 if (msg != NULL)
2178                         (void) ldap_msgfree(msg);
2179                 msg = NULL;
2180                 *lc = findReferralCon(referralsp, &stat);
2181                 ldap_value_free(referralsp);
2182                 referralsp = NULL;
2183                 if (*lc == NULL)
2184                         goto cleanup;
2185                 msgid = ldap_modify((*lc)->ld, dn, mods);
2186                 if (msgid == -1) {
2187                         (void) ldap_get_option((*lc)->ld,
2188                             LDAP_OPT_ERROR_NUMBER, &stat);
2189                         goto cleanup;
2190                 }
2191                 stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
2192                 if (stat == 0) {
2193                         stat = LDAP_TIMEOUT;
2194                 } else if (stat == -1) {
2195                         (void) ldap_get_option((*lc)->ld,
2196                             LDAP_OPT_ERROR_NUMBER, &stat);
2197                 } else {
2198                         stat = ldap_parse_result((*lc)->ld, msg, &lderr,
2199                             NULL, NULL, NULL, NULL, 0);
2200                         if (stat == LDAP_SUCCESS)
2201                                 stat = lderr;
2202                 }
2203         }
2204 cleanup:
2205         if (mods != 0)
2206                 freeLdapMod(mods);
2207         freeRuleValue(rv, 1);
2208         return (stat);
2209 }
2210 
2211 /*
2212  * Modify the specified 'dn' per the attribute names/values in 'rv'.
2213  * If 'rv' is NULL, we attempt to delete the entire entry.
2214  *
2215  * The 'objClassAttrs' parameter is needed if the entry must be added
2216  * (i.e., created), or a modify fails with an object class violation.
2217  *
2218  * If 'addFirst' is set, we try an add before a modify; modify before
2219  * add otherwise (ignored if we're deleting).
2220  */
2221 int
2222 ldapModify(char *dn, __nis_rule_value_t *rv, char *objClassAttrs,
2223                 int addFirst) {
2224         int                     stat, add = 0;
2225         LDAPMod                 **mods = 0;
2226         __nis_ldap_conn_t       *lc;
2227         struct timeval          tv;
2228         LDAPMessage             *msg = 0;
2229         int                     msgid;
2230         int                     lderr;
2231         char                    **referralsp = NULL;
2232         bool_t                  delete = FALSE;
2233 
2234         if (dn == 0)
2235                 return (LDAP_PARAM_ERROR);
2236 
2237         if ((lc = findCon(&stat)) == 0)
2238                 return (stat);
2239 
2240         if (rv == 0) {
2241                 delete = TRUE;
2242                 /* Simple case: if rv == 0, try to delete the entire entry */
2243                 msgid = ldap_delete(lc->ld, dn);
2244                 if (msgid == -1) {
2245                         (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2246                                                 &stat);
2247                         goto cleanup;
2248                 }
2249                 tv = lc->deleteTimeout;
2250                 stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2251 
2252                 if (stat == 0) {
2253                         stat = LDAP_TIMEOUT;
2254                 } else if (stat == -1) {
2255                         (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2256                                                 &stat);
2257                 } else {
2258                         stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2259                                 NULL, &referralsp, NULL, 0);
2260                         if (stat == LDAP_SUCCESS)
2261                                 stat = lderr;
2262                 }
2263                 if (proxyInfo.follow_referral == follow &&
2264                                 stat == LDAP_REFERRAL && referralsp != NULL) {
2265                         releaseCon(lc, stat);
2266                         if (msg != NULL)
2267                                 (void) ldap_msgfree(msg);
2268                         msg = NULL;
2269                         lc = findReferralCon(referralsp, &stat);
2270                         ldap_value_free(referralsp);
2271                         if (lc == NULL)
2272                                 goto cleanup;
2273                         msgid = ldap_delete(lc->ld, dn);
2274                         if (msgid == -1) {
2275                                 (void) ldap_get_option(lc->ld,
2276                                         LDAP_OPT_ERROR_NUMBER, &stat);
2277                                 goto cleanup;
2278                         }
2279                         stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2280                         if (stat == 0) {
2281                                 stat = LDAP_TIMEOUT;
2282                         } else if (stat == -1) {
2283                                 (void) ldap_get_option(lc->ld,
2284                                         LDAP_OPT_ERROR_NUMBER, &stat);
2285                         } else {
2286                                 stat = ldap_parse_result(lc->ld, msg, &lderr,
2287                                         NULL, NULL, NULL, NULL, 0);
2288                                 if (stat == LDAP_SUCCESS)
2289                                         stat = lderr;
2290                         }
2291                 }
2292                 /* No such object means someone else has done our job */
2293                 if (stat == LDAP_NO_SUCH_OBJECT)
2294                         stat = LDAP_SUCCESS;
2295         } else {
2296                 if (addFirst) {
2297                         stat = ldapAdd(dn, rv, objClassAttrs, lc);
2298                         lc = NULL;
2299                         if (stat != LDAP_ALREADY_EXISTS)
2300                                 goto cleanup;
2301                         if ((lc = findCon(&stat)) == 0)
2302                                 return (stat);
2303                 }
2304 
2305                 /*
2306                  * First try the modify without specifying object classes
2307                  * (i.e., assume they're already present).
2308                  */
2309                 mods = search2LdapMod(rv, 0, 0);
2310                 if (mods == 0) {
2311                         stat = LDAP_PARAM_ERROR;
2312                         goto cleanup;
2313                 }
2314 
2315                 msgid = ldap_modify(lc->ld, dn, mods);
2316                 if (msgid == -1) {
2317                         (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2318                                                 &stat);
2319                         goto cleanup;
2320                 }
2321                 tv = lc->modifyTimeout;
2322                 stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2323                 if (stat == 0) {
2324                         stat = LDAP_TIMEOUT;
2325                 } else if (stat == -1) {
2326                         (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2327                                                 &stat);
2328                 } else {
2329                         stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2330                                 NULL, &referralsp, NULL, 0);
2331                         if (stat == LDAP_SUCCESS)
2332                                 stat = lderr;
2333                 }
2334                 if (proxyInfo.follow_referral == follow &&
2335                                 stat == LDAP_REFERRAL && referralsp != NULL) {
2336                         releaseCon(lc, stat);
2337                         if (msg != NULL)
2338                                 (void) ldap_msgfree(msg);
2339                         msg = NULL;
2340                         lc = findReferralCon(referralsp, &stat);
2341                         ldap_value_free(referralsp);
2342                         referralsp = NULL;
2343                         if (lc == NULL)
2344                                 goto cleanup;
2345                         msgid = ldap_modify(lc->ld, dn, mods);
2346                         if (msgid == -1) {
2347                                 (void) ldap_get_option(lc->ld,
2348                                         LDAP_OPT_ERROR_NUMBER, &stat);
2349                                 goto cleanup;
2350                         }
2351                         stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2352                         if (stat == 0) {
2353                                 stat = LDAP_TIMEOUT;
2354                         } else if (stat == -1) {
2355                                 (void) ldap_get_option(lc->ld,
2356                                         LDAP_OPT_ERROR_NUMBER, &stat);
2357                         } else {
2358                                 stat = ldap_parse_result(lc->ld, msg, &lderr,
2359                                         NULL, NULL, NULL, NULL, 0);
2360                                 if (stat == LDAP_SUCCESS)
2361                                         stat = lderr;
2362                         }
2363                 }
2364 
2365                 /*
2366                  * If the modify failed with an object class violation,
2367                  * the most probable reason is that at least on of the
2368                  * attributes we're modifying didn't exist before, and
2369                  * neither did its object class. So, try the modify again,
2370                  * but add the object classes this time.
2371                  */
2372                 if (stat == LDAP_OBJECT_CLASS_VIOLATION &&
2373                                 objClassAttrs != 0) {
2374                         freeLdapMod(mods);
2375                         mods = 0;
2376                         stat = ldapModifyObjectClass(&lc, dn, rv,
2377                                 objClassAttrs);
2378                 }
2379 
2380                 if (stat == LDAP_NO_SUCH_ATTRIBUTE) {
2381                         /*
2382                          * If there was at least one attribute delete, then
2383                          * the cause of this error could be that said attribute
2384                          * didn't exist in LDAP. So, do things the slow way,
2385                          * and try to delete one attribute at a time.
2386                          */
2387                         int                     d, numDelete, st;
2388                         __nis_rule_value_t      *rvt;
2389 
2390                         for (d = 0, numDelete = 0; d < rv->numAttrs; d++) {
2391                                 if (rv->attrVal[d].numVals < 0)
2392                                         numDelete++;
2393                         }
2394 
2395                         /* If there's just one, we've already tried */
2396                         if (numDelete <= 1)
2397                                 goto cleanup;
2398 
2399                         /* Make a copy of the rule value */
2400                         rvt = initRuleValue(1, rv);
2401                         if (rvt == 0)
2402                                 goto cleanup;
2403 
2404                         /*
2405                          * Remove all delete attributes from the tmp
2406                          * rule value.
2407                          */
2408                         for (d = 0; d < rv->numAttrs; d++) {
2409                                 if (rv->attrVal[d].numVals < 0) {
2410                                         delAttrFromRuleValue(rvt,
2411                                                 rv->attrName[d]);
2412                                 }
2413                         }
2414 
2415                         /*
2416                          * Now put the attributes back in one by one, and
2417                          * invoke ourselves.
2418                          */
2419                         for (d = 0; d < rv->numAttrs; d++) {
2420                                 if (rv->attrVal[d].numVals >= 0)
2421                                         continue;
2422                                 st = addAttr2RuleValue(rv->attrVal[d].type,
2423                                         rv->attrName[d], 0, 0, rvt);
2424                                 if (st != 0) {
2425                                         logmsg(MSG_NOMEM, LOG_ERR,
2426                                         "%s: Error deleting \"%s\" for \"%s\"",
2427                                                 NIL(rv->attrName[d]), NIL(dn));
2428                                         stat = LDAP_NO_MEMORY;
2429                                         freeRuleValue(rvt, 1);
2430                                         goto cleanup;
2431                                 }
2432                                 stat = ldapModify(dn, rvt, objClassAttrs, 0);
2433                                 if (stat != LDAP_SUCCESS &&
2434                                         stat != LDAP_NO_SUCH_ATTRIBUTE) {
2435                                         freeRuleValue(rvt, 1);
2436                                         goto cleanup;
2437                                 }
2438                                 delAttrFromRuleValue(rvt, rv->attrName[d]);
2439                         }
2440 
2441                         /*
2442                          * If we got here, then all attributes that should
2443                          * be deleted either have been, or didn't exist. For
2444                          * our purposes, the latter is as good as the former.
2445                          */
2446                         stat = LDAP_SUCCESS;
2447                         freeRuleValue(rvt, 1);
2448                 }
2449 
2450                 if (stat == LDAP_NO_SUCH_OBJECT && !addFirst) {
2451                         /*
2452                          * Entry doesn't exist, so try an ldap_add(). If the
2453                          * ldap_add() also fails, that could be because someone
2454                          * else added it between our modify and add operations.
2455                          * If so, we consider that foreign add to be
2456                          * authoritative (meaning we don't retry our modify).
2457                          *
2458                          * Also, if all modify operations specified by 'mods'
2459                          * are deletes, LDAP_NO_SUCH_OBJECT is a kind of
2460                          * success; we certainly don't want to create the
2461                          * entry.
2462                          */
2463                         int     allDelete;
2464                         LDAPMod **m;
2465 
2466                         for (m = mods, allDelete = 1; *m != 0 && allDelete;
2467                                         m++) {
2468                                 if (((*m)->mod_op & LDAP_MOD_DELETE) == 0)
2469                                         allDelete = 0;
2470                         }
2471 
2472                         add = 1;
2473 
2474                         if (allDelete) {
2475                                 stat = LDAP_SUCCESS;
2476                         } else if (objClassAttrs == 0) {
2477                                 /* Now we need it, so this is fatal */
2478                                 stat = LDAP_PARAM_ERROR;
2479                         } else {
2480                                 stat = ldapAdd(dn, rv, objClassAttrs, lc);
2481                                 lc = NULL;
2482                         }
2483                 }
2484         }
2485 
2486 cleanup:
2487         if (stat != LDAP_SUCCESS) {
2488                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
2489                         "%s(0x%x (%s), \"%s\") => %d (%s)\n",
2490                         !delete ? (add ? "ldap_add" : "ldap_modify") :
2491                                 "ldap_delete",
2492                         lc != NULL ? lc->ld : 0,
2493                         lc != NULL ? NIL(lc->sp) : "nil",
2494                         dn, stat, ldap_err2string(stat));
2495         }
2496 
2497         releaseCon(lc, stat);
2498         freeLdapMod(mods);
2499         if (msg != 0)
2500                 (void) ldap_msgfree(msg);
2501 
2502         return (stat);
2503 }
2504 
2505 /*
2506  * Create the entry specified by 'dn' to have the values per 'rv'.
2507  * The 'objClassAttrs' are the extra object classes we need when
2508  * creating an entry.
2509  *
2510  * If 'lc' is non-NULL, we use that connection; otherwise, we find
2511  * our own. CAUTION: This connection will be released on return. Regardless
2512  * of return value, this connection should not subsequently used by the
2513  * caller.
2514  *
2515  * Returns an LDAP status.
2516  */
2517 int
2518 ldapAdd(char *dn, __nis_rule_value_t *rv, char *objClassAttrs, void *lcv) {
2519         int                     stat;
2520         LDAPMod                 **mods = 0;
2521         struct timeval          tv;
2522         LDAPMessage             *msg = 0;
2523         __nis_ldap_conn_t       *lc = lcv;
2524         int                     msgid;
2525         int                     lderr;
2526         char                    **referralsp = NULL;
2527 
2528         if (dn == 0 || rv == 0 || objClassAttrs == 0) {
2529                 releaseCon(lc, LDAP_SUCCESS);
2530                 return (LDAP_PARAM_ERROR);
2531         }
2532 
2533         if (lc == 0) {
2534                 if ((lc = findCon(&stat)) == 0)
2535                         return (stat);
2536         }
2537 
2538         rv = addObjectClasses(rv, objClassAttrs);
2539         if (rv == 0) {
2540                 stat = LDAP_OPERATIONS_ERROR;
2541                 goto cleanup;
2542         }
2543 
2544         mods = search2LdapMod(rv, 1, 0);
2545         if (mods == 0) {
2546                 stat = LDAP_OPERATIONS_ERROR;
2547                 goto cleanup;
2548         }
2549 
2550         msgid = ldap_add(lc->ld, dn, mods);
2551         if (msgid == -1) {
2552                 (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
2553                 goto cleanup;
2554         }
2555         tv = lc->addTimeout;
2556         stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2557         if (stat == 0) {
2558                 stat = LDAP_TIMEOUT;
2559         } else if (stat == -1) {
2560                 (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
2561         } else {
2562                 stat = ldap_parse_result(lc->ld, msg, &lderr, NULL, NULL,
2563                         &referralsp, NULL, 0);
2564                 if (stat == LDAP_SUCCESS)
2565                         stat = lderr;
2566         }
2567         if (proxyInfo.follow_referral == follow && stat == LDAP_REFERRAL &&
2568                         referralsp != NULL) {
2569                 releaseCon(lc, stat);
2570                 if (msg != NULL)
2571                         (void) ldap_msgfree(msg);
2572                 msg = NULL;
2573                 lc = findReferralCon(referralsp, &stat);
2574                 ldap_value_free(referralsp);
2575                 if (lc == NULL)
2576                         goto cleanup;
2577                 msgid = ldap_add(lc->ld, dn, mods);
2578                 if (msgid == -1) {
2579                         (void) ldap_get_option(lc->ld,
2580                                 LDAP_OPT_ERROR_NUMBER, &stat);
2581                         goto cleanup;
2582                 }
2583                 stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2584                 if (stat == 0) {
2585                         stat = LDAP_TIMEOUT;
2586                 } else if (stat == -1) {
2587                         (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2588                                                 &stat);
2589                 } else {
2590                         stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2591                                 NULL, NULL, NULL, 0);
2592                         if (stat == LDAP_SUCCESS)
2593                                 stat = lderr;
2594                 }
2595         }
2596 
2597 cleanup:
2598         if (stat != LDAP_SUCCESS) {
2599                 logmsg(MSG_NOTIMECHECK, LOG_INFO,
2600                         "ldap_add(0x%x (%s), \"%s\") => %d (%s)\n",
2601                         lc != NULL ? lc->ld : 0,
2602                         lc != NULL ? NIL(lc->sp) : "nil",
2603                         dn, stat, ldap_err2string(stat));
2604         }
2605 
2606         releaseCon(lc, stat);
2607         freeLdapMod(mods);
2608         if (msg != 0)
2609                 (void) ldap_msgfree(msg);
2610 
2611         return (stat);
2612 }
2613 
2614 /*
2615  * Change the entry at 'oldDn' to have the new DN (not RDN) 'dn'.
2616  * Returns an LDAP error status.
2617  */
2618 int
2619 ldapChangeDN(char *oldDn, char *dn) {
2620         int                     stat;
2621         __nis_ldap_conn_t       *lc;
2622         int                     i, j, lo, ln;
2623         char                    *rdn;
2624         int                     msgid;
2625         int                     lderr;
2626         struct timeval          tv;
2627         LDAPMessage             *msg = 0;
2628         char                    **referralsp = NULL;
2629         char                    *myself = "ldapChangeDN";
2630 
2631         if ((lo = slen(oldDn)) <= 0 || (ln = slen(dn)) <= 0)
2632                 return (LDAP_PARAM_ERROR);
2633 
2634         if (strcasecmp(oldDn, dn) == 0)
2635                 return (LDAP_SUCCESS);
2636 
2637         if ((lc = findCon(&stat)) == 0)
2638                 return (stat);
2639 
2640         rdn = sdup(myself, T, dn);
2641         if (rdn == 0) {
2642                 releaseCon(lc, LDAP_SUCCESS);
2643                 return (LDAP_NO_MEMORY);
2644         }
2645 
2646         /* Compare old and new DN from the end */
2647         for (i = lo-1, j = ln-1; i >= 0 && j >= 0; i--, j--) {
2648                 if (tolower(oldDn[i]) != tolower(rdn[j])) {
2649                         /*
2650                          * Terminate 'rdn' after this character in order
2651                          * to snip off the portion of the new DN that is
2652                          * the same as the old DN. What remains in 'rdn'
2653                          * is the relative DN.
2654                          */
2655                         rdn[j+1] = '\0';
2656                         break;
2657                 }
2658         }
2659 
2660         stat = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL, &msgid);
2661 
2662         if (msgid != -1) {
2663                 tv = lc->modifyTimeout;
2664                 stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2665                 if (stat == 0) {
2666                         stat = LDAP_TIMEOUT;
2667                 } else if (stat == -1) {
2668                         (void) ldap_get_option(lc->ld,
2669                                 LDAP_OPT_ERROR_NUMBER, &stat);
2670                 } else {
2671                         stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2672                                 NULL, &referralsp, NULL, 0);
2673                         if (stat == LDAP_SUCCESS)
2674                                 stat = lderr;
2675                         stat = ldap_result2error(lc->ld, msg, 0);
2676                 }
2677         } else {
2678                 (void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2679                         &stat);
2680         }
2681         if (proxyInfo.follow_referral == follow &&
2682                         stat == LDAP_REFERRAL && referralsp != NULL) {
2683                 releaseCon(lc, stat);
2684                 if (msg != NULL)
2685                         (void) ldap_msgfree(msg);
2686                 msg = NULL;
2687                 lc = findReferralCon(referralsp, &stat);
2688                 ldap_value_free(referralsp);
2689                 referralsp = NULL;
2690                 if (lc == NULL)
2691                         goto cleanup;
2692                 msgid = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL,
2693                         &msgid);
2694                 if (msgid == -1) {
2695                         (void) ldap_get_option(lc->ld,
2696                                 LDAP_OPT_ERROR_NUMBER, &stat);
2697                         goto cleanup;
2698                 }
2699                 stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2700                 if (stat == 0) {
2701                         stat = LDAP_TIMEOUT;
2702                 } else if (stat == -1) {
2703                         (void) ldap_get_option(lc->ld,
2704                                 LDAP_OPT_ERROR_NUMBER, &stat);
2705                 } else {
2706                         stat = ldap_parse_result(lc->ld, msg, &lderr,
2707                                 NULL, NULL, NULL, NULL, 0);
2708                         if (stat == LDAP_SUCCESS)
2709                                 stat = lderr;
2710                 }
2711         }
2712 
2713 cleanup:
2714         if (msg != NULL)
2715                 (void) ldap_msgfree(msg);
2716 
2717 #if     1
2718         fprintf(stderr, "%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s\n",
2719                 myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
2720                 ldap_err2string(stat));
2721         logmsg(MSG_NOTIMECHECK, LOG_WARNING,
2722                 "%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s",
2723                 myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
2724                 ldap_err2string(stat));
2725 #endif
2726 
2727         if (stat == LDAP_NO_SUCH_OBJECT) {
2728                 /*
2729                  * Fine from our point of view, since all we want to do
2730                  * is to make sure that an update to the new DN doesn't
2731                  * leave the old entry around.
2732                  */
2733                 stat = LDAP_SUCCESS;
2734         }
2735 
2736         releaseCon(lc, stat);
2737         sfree(rdn);
2738 
2739         return (stat);
2740 }