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