xref: /titanic_41/usr/src/lib/libnisdb/ldap_op.c (revision bdfc6d18da790deeec2e0eb09c625902defe2498)
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 2004 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 /*
49  * Build one of our internal LDAP search structures, containing copies of
50  * the supplied input. return NULL in case of error.
51  *
52  * If 'filter' is NULL, build an AND-filter using the filter components.
53  */
54 __nis_ldap_search_t *
55 buildLdapSearch(char *base, int scope, int numFilterComps, char **filterComp,
56 		char *filter, char **attrs, int attrsonly, int isDN) {
57 	__nis_ldap_search_t	*ls;
58 	char			**a;
59 	int			i, na, err = 0;
60 	char			*myself = "buildLdapSearch";
61 
62 	ls = am(myself, sizeof (*ls));
63 	if (ls == 0)
64 		return (0);
65 
66 	ls->base = sdup(myself, T, base);
67 	if (ls->base == 0 && base != 0)
68 		err++;
69 	ls->scope = scope;
70 
71 	if (filterComp != 0 && numFilterComps > 0) {
72 		ls->filterComp = am(myself, numFilterComps *
73 					sizeof (ls->filterComp[0]));
74 		if (ls->filterComp == 0) {
75 			err++;
76 			numFilterComps = 0;
77 		}
78 		for (i = 0; i < numFilterComps; i++) {
79 			ls->filterComp[i] = sdup(myself, T, filterComp[i]);
80 			if (ls->filterComp[i] == 0 && filterComp[i] != 0)
81 				err++;
82 		}
83 		ls->numFilterComps = numFilterComps;
84 		if (filter == 0) {
85 			ls->filter = concatenateFilterComps(ls->numFilterComps,
86 					ls->filterComp);
87 			if (ls->filter == 0)
88 				err++;
89 		}
90 	} else {
91 		ls->filterComp = 0;
92 		ls->numFilterComps = 0;
93 		ls->filter = sdup(myself, T, filter);
94 		if (ls->filter == 0 && filter != 0)
95 			err++;
96 	}
97 
98 	if (attrs != 0) {
99 		for (na = 0, a = attrs; *a != 0; a++, na++);
100 		ls->attrs = am(myself, (na + 1) * sizeof (ls->attrs[0]));
101 		if (ls->attrs != 0) {
102 			for (i = 0; i < na; i++) {
103 				ls->attrs[i] = sdup(myself, T, attrs[i]);
104 				if (ls->attrs[i] == 0 && attrs[i] != 0)
105 					err++;
106 			}
107 			ls->attrs[na] = 0;
108 			ls->numAttrs = na;
109 		} else {
110 			err++;
111 		}
112 	} else {
113 		ls->attrs = 0;
114 		ls->numAttrs = 0;
115 	}
116 
117 	ls->attrsonly = attrsonly;
118 	ls->isDN = isDN;
119 
120 	if (err > 0) {
121 		freeLdapSearch(ls);
122 		ls = 0;
123 	}
124 
125 	return (ls);
126 }
127 
128 void
129 freeLdapSearch(__nis_ldap_search_t *ls) {
130 	int	i;
131 
132 	if (ls == 0)
133 		return;
134 
135 	sfree(ls->base);
136 	if (ls->filterComp != 0) {
137 		for (i = 0; i < ls->numFilterComps; i++) {
138 			sfree(ls->filterComp[i]);
139 		}
140 		sfree(ls->filterComp);
141 	}
142 	sfree(ls->filter);
143 	if (ls->attrs != 0) {
144 		for (i = 0; i < ls->numAttrs; i++) {
145 			sfree(ls->attrs[i]);
146 		}
147 		sfree(ls->attrs);
148 	}
149 
150 	free(ls);
151 }
152 
153 /*
154  * Given a table mapping, and a rule/value pointer,
155  * return an LDAP search structure with values suitable for use
156  * by ldap_search() or (if dn != 0) ldap_modify(). The rule/value
157  * may be modified.
158  *
159  * If dn != 0 and *dn == 0, the function attemps to return a pointer
160  * to the DN. This may necessitate an ldapSearch, if the rule set doesn't
161  * produce a DN directly.
162  *
163  * if dn == 0, and the rule set produces a DN as well as other attribute/
164  * value pairs, the function returns an LDAP search structure with the
165  * DN only.
166  *
167  * If 'fromLDAP' is set, the caller wants base/scope/filter from
168  * t->objectDN->read; otherwise, from t->objectDN->write.
169  *
170  * If 'rv' is NULL, the caller wants an enumeration of the container.
171  *
172  * Note that this function only creates a search structure for 't' itself;
173  * if there are alternative mappings for the table, those must be handled
174  * by our caller.
175  */
176 __nis_ldap_search_t *
177 createLdapRequest(__nis_table_mapping_t *t,
178 		__nis_rule_value_t *rv, char **dn, int fromLDAP,
179 		int *res, __nis_object_dn_t *obj_dn) {
180 	int			i, j;
181 	__nis_ldap_search_t	*ls = 0;
182 	char			**locDN;
183 	int			numLocDN, stat = 0, count = 0;
184 	char			*myself = "createLdapRequest";
185 	__nis_object_dn_t 	*objectDN = NULL;
186 
187 	if (t == 0)
188 		return (0);
189 
190 	if (obj_dn == NULL)
191 		objectDN = t->objectDN;
192 	else
193 		objectDN = obj_dn;
194 
195 	if (rv == 0) {
196 		char	*base;
197 		char	*filter;
198 
199 		if (fromLDAP) {
200 			base = objectDN->read.base;
201 			filter = makeFilter(objectDN->read.attrs);
202 		} else {
203 			base = objectDN->write.base;
204 			filter = makeFilter(objectDN->write.attrs);
205 		}
206 
207 		/* Create request to enumerate container */
208 		ls = buildLdapSearch(base, objectDN->read.scope, 0, 0, filter,
209 					0, 0, 0);
210 		sfree(filter);
211 		return (ls);
212 	}
213 
214 	for (i = 0; i < t->numRulesToLDAP; i++) {
215 		rv = addLdapRuleValue(t, t->ruleToLDAP[i],
216 				mit_ldap, mit_nisplus, rv, !fromLDAP, &stat);
217 		if (rv == 0)
218 			return (0);
219 		if (stat == NP_LDAP_RULES_NO_VALUE)
220 			count++;
221 		stat = 0;
222 	}
223 
224 	/*
225 	 * If none of the rules produced a value despite
226 	 * having enough NIS+ columns, return error.
227 	 */
228 	if (rv->numAttrs == 0 && count > 0) {
229 		*res = NP_LDAP_RULES_NO_VALUE;
230 		return (0);
231 	}
232 
233 	/*
234 	 * 'rv' now contains everything we know about the attributes and
235 	 * values. Build an LDAP search structure from it.
236 	 */
237 
238 	/* Look for a single-valued DN */
239 	locDN = findDNs(myself, rv, 1,
240 			fromLDAP ? objectDN->read.base :
241 					objectDN->write.base,
242 			&numLocDN);
243 	if (locDN != 0 && numLocDN == 1) {
244 		if (dn != 0 && *dn == 0) {
245 			*dn = locDN[0];
246 			sfree(locDN);
247 		} else {
248 			char	*filter;
249 
250 			if (fromLDAP)
251 				filter = makeFilter(objectDN->read.attrs);
252 			else
253 				filter = makeFilter(objectDN->write.attrs);
254 			ls = buildLdapSearch(locDN[0], LDAP_SCOPE_BASE, 0, 0,
255 						filter, 0, 0, 1);
256 			sfree(filter);
257 			freeDNs(locDN, numLocDN);
258 		}
259 	} else {
260 		freeDNs(locDN, numLocDN);
261 	}
262 
263 	if (ls != 0) {
264 		ls->useCon = 1;
265 		return (ls);
266 	}
267 
268 	/*
269 	 * No DN, or caller wanted a search structure with the non-DN
270 	 * attributes.
271 	 */
272 
273 	/* Initialize search structure */
274 	{
275 		char	*filter = (fromLDAP) ?
276 				makeFilter(objectDN->read.attrs) :
277 				makeFilter(objectDN->write.attrs);
278 		char	**ofc;
279 		int	nofc = 0;
280 
281 		ofc = makeFilterComp(filter, &nofc);
282 
283 		if (filter != 0 && ofc == 0) {
284 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
285 			"%s: Unable to break filter into components: \"%s\"",
286 				myself, NIL(filter));
287 			sfree(filter);
288 			return (0);
289 		}
290 
291 		if (fromLDAP)
292 			ls = buildLdapSearch(objectDN->read.base,
293 				objectDN->read.scope,
294 				nofc, ofc, 0, 0, 0, 0);
295 		else
296 			ls = buildLdapSearch(objectDN->write.base,
297 				objectDN->write.scope,
298 				nofc, ofc, 0, 0, 0, 0);
299 		sfree(filter);
300 		freeFilterComp(ofc, nofc);
301 		if (ls == 0)
302 			return (0);
303 	}
304 
305 	/* Build and add the filter components */
306 	for (i = 0; i < rv->numAttrs; i++) {
307 		/* Skip DN */
308 		if (strcasecmp("dn", rv->attrName[i]) == 0)
309 			continue;
310 
311 		/* Skip vt_ber values */
312 		if (rv->attrVal[i].type == vt_ber)
313 			continue;
314 
315 		for (j = 0; j < rv->attrVal[i].numVals; j++) {
316 			__nis_buffer_t	b = {0, 0};
317 			char		**tmpComp;
318 
319 			bp2buf(myself, &b, "%s=%s",
320 				rv->attrName[i], rv->attrVal[i].val[j].value);
321 			tmpComp = addFilterComp(b.buf, ls->filterComp,
322 						&ls->numFilterComps);
323 			if (tmpComp == 0) {
324 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
325 				"%s: Unable to add filter component \"%s\"",
326 					myself, NIL(b.buf));
327 				sfree(b.buf);
328 				freeLdapSearch(ls);
329 				return (0);
330 			}
331 			ls->filterComp = tmpComp;
332 			sfree(b.buf);
333 		}
334 	}
335 
336 	if (ls->numFilterComps > 0) {
337 		sfree(ls->filter);
338 		ls->filter = concatenateFilterComps(ls->numFilterComps,
339 							ls->filterComp);
340 		if (ls->filter == 0) {
341 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
342 			"%s: Unable to concatenate filter components",
343 				myself);
344 			freeLdapSearch(ls);
345 			return (0);
346 		}
347 	}
348 
349 	if (dn != 0 && *dn == 0) {
350 		/*
351 		 * The caller wants a DN, but we didn't get one from the
352 		 * the rule set. We have an 'ls', so use it to ldapSearch()
353 		 * for an entry from which we can extract the DN.
354 		 */
355 		__nis_rule_value_t	*rvtmp;
356 		char			**locDN;
357 		int			nv = 0, numLocDN;
358 
359 		rvtmp = ldapSearch(ls, &nv, 0, 0);
360 		locDN = findDNs(myself, rvtmp, nv, 0, &numLocDN);
361 		if (locDN != 0 && numLocDN == 1) {
362 			*dn = locDN[0];
363 			sfree(locDN);
364 		} else {
365 			freeDNs(locDN, numLocDN);
366 		}
367 		freeRuleValue(rvtmp, nv);
368 	}
369 
370 	ls->useCon = 1;
371 	return (ls);
372 }
373 
374 int	ldapConnAttemptRetryTimeout = 60;	/* seconds */
375 
376 typedef struct {
377 	LDAP		*ld;
378 	mutex_t		mutex;		/* Mutex for update of structure */
379 	pthread_t	owner;		/* Thread holding mutex */
380 	mutex_t		rcMutex;	/* Mutex for refCount */
381 	int		refCount;	/* Reference count */
382 	int		isBound;	/* Is connection open and usable ? */
383 	time_t		retryTime;	/* When should open be retried */
384 	int		status;		/* Status of last operation */
385 	int		doDis;		/* To be disconnected if refCount==0 */
386 	int		doDel;		/* To be deleted if refCount zero */
387 	int		onList;		/* True if on the 'ldapCon' list */
388 	char		*sp;		/* server string */
389 	char		*who;
390 	char		*cred;
391 	auth_method_t	method;
392 	int		port;
393 	struct timeval	bindTimeout;
394 	struct timeval	searchTimeout;
395 	struct timeval	modifyTimeout;
396 	struct timeval	addTimeout;
397 	struct timeval	deleteTimeout;
398 	int		simplePage;	/* Can do simple-page */
399 	int		vlv;		/* Can do VLV */
400 	uint_t		batchFrom;	/* # entries read in one operation */
401 	void		*next;
402 } __nis_ldap_conn_t;
403 
404 /*
405  * List of connections, 'ldapCon', protected by an RW lock.
406  *
407  * The following locking scheme is used:
408  *
409  * (1)	Find a connection structure to use to talk to LDAP
410  *		Rlock list
411  *			Locate structure
412  *			Acquire 'mutex'
413  *				Acquire 'rcMutex'
414  *					update refCount
415  *				Release 'rcMutex'
416  *			release 'mutex'
417  *		Unlock list
418  *		Use structure
419  *		Release structure when done
420  * (2)	Insert/delete structure(s) on/from list
421  *		Wlock list
422  *			Insert/delete structure; if deleting, must
423  *			acquire 'mutex', and 'rcMutex' (in that order),
424  *			and 'refCount' must be zero.
425  *		Unlock list
426  * (3)	Modify structure
427  *		Find structure
428  *		Acquire 'mutex'
429  *			Modify (except refCount)
430  *		Release 'mutex'
431  *		Release structure
432  */
433 
434 __nis_ldap_conn_t		*ldapCon = 0;
435 __nis_ldap_conn_t		*ldapReferralCon = 0;
436 static rwlock_t			ldapConLock = DEFAULTRWLOCK;
437 static rwlock_t			referralConLock = DEFAULTRWLOCK;
438 
439 void
440 exclusiveLC(__nis_ldap_conn_t *lc) {
441 	pthread_t	me = pthread_self();
442 	int		stat;
443 
444 	if (lc == 0)
445 		return;
446 
447 	stat = mutex_trylock(&lc->mutex);
448 	if (stat == EBUSY && lc->owner != me)
449 		mutex_lock(&lc->mutex);
450 
451 	lc->owner = me;
452 }
453 
454 /* Return 1 if mutex held by this thread, 0 otherwise */
455 int
456 assertExclusive(__nis_ldap_conn_t *lc) {
457 	pthread_t	me;
458 	int		stat;
459 
460 	if (lc == 0)
461 		return (0);
462 
463 	stat = mutex_trylock(&lc->mutex);
464 
465 	if (stat == 0) {
466 		mutex_unlock(&lc->mutex);
467 		return (0);
468 	}
469 
470 	me = pthread_self();
471 	if (stat != EBUSY || lc->owner != me)
472 		return (0);
473 
474 	return (1);
475 }
476 
477 void
478 releaseLC(__nis_ldap_conn_t *lc) {
479 	pthread_t	me = pthread_self();
480 
481 	if (lc == 0 || lc->owner != me)
482 		return;
483 
484 	lc->owner = 0;
485 	(void) mutex_unlock(&lc->mutex);
486 }
487 
488 void
489 incrementRC(__nis_ldap_conn_t *lc) {
490 	if (lc == 0)
491 		return;
492 
493 	(void) mutex_lock(&lc->rcMutex);
494 	lc->refCount++;
495 	(void) mutex_unlock(&lc->rcMutex);
496 }
497 
498 void
499 decrementRC(__nis_ldap_conn_t *lc) {
500 	if (lc == 0)
501 		return;
502 
503 	(void) mutex_lock(&lc->rcMutex);
504 	if (lc->refCount > 0)
505 		lc->refCount--;
506 	(void) mutex_unlock(&lc->rcMutex);
507 }
508 
509 /* Accept a server/port indication, and call ldap_init() */
510 static LDAP *
511 ldapInit(char *srv, int port, bool_t use_ssl) {
512 	LDAP			*ld;
513 	int			ldapVersion = LDAP_VERSION3;
514 	int			derefOption = LDAP_DEREF_ALWAYS;
515 	int			timelimit = proxyInfo.search_time_limit;
516 	int			sizelimit = proxyInfo.search_size_limit;
517 	char			*myself = "ldapInit";
518 
519 	if (srv == 0)
520 		return (0);
521 
522 	if (use_ssl) {
523 		ld = ldapssl_init(srv, port, 1);
524 	} else {
525 		ld = ldap_init(srv, port);
526 	}
527 
528 	if (ld != 0) {
529 		(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
530 					&ldapVersion);
531 		(void) ldap_set_option(ld, LDAP_OPT_DEREF, &derefOption);
532 		(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
533 		(void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &timelimit);
534 		(void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &sizelimit);
535 		(void) ldap_set_option(ld, LDAP_OPT_REBIND_ARG, 0);
536 	}
537 
538 	return (ld);
539 }
540 
541 /*
542  * Bind the specified LDAP structure per the supplied authentication.
543  * Note: tested with none, simple, and digest_md5. May or may not
544  * work with other authentication methods, mostly depending on whether
545  * or not 'who' and 'cred' contain sufficient information.
546  */
547 static int
548 ldapBind(LDAP **ldP, char *who, char *cred, auth_method_t method,
549 		struct timeval timeout) {
550 	int		ret;
551 	LDAP		*ld;
552 	char		*myself = "ldapBind";
553 
554 	if (ldP == 0 || (ld = *ldP) == 0)
555 		return (LDAP_PARAM_ERROR);
556 
557 	if (method == none) {
558 		/* No ldap_bind() required (or even possible) */
559 		ret = LDAP_SUCCESS;
560 	} else if (method == simple) {
561 		struct timeval	tv;
562 		LDAPMessage	*msg = 0;
563 
564 		tv = timeout;
565 		ret = ldap_bind(ld, who, cred, LDAP_AUTH_SIMPLE);
566 		if (ret != -1) {
567 			ret = ldap_result(ld, ret, 0, &tv, &msg);
568 			if (ret == 0) {
569 				ret = LDAP_TIMEOUT;
570 			} else if (ret == -1) {
571 				(void) ldap_get_option(ld,
572 							LDAP_OPT_ERROR_NUMBER,
573 							&ret);
574 			} else {
575 				ret = ldap_result2error(ld, msg, 0);
576 			}
577 			if (msg != 0)
578 				(void) ldap_msgfree(msg);
579 		} else {
580 			(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER,
581 						&ret);
582 		}
583 	} else if (method == cram_md5) {
584 		/* Note: there is only a synchronous call for cram-md5 */
585 		struct berval ber_cred;
586 
587 		ber_cred.bv_len = strlen(cred);
588 		ber_cred.bv_val = cred;
589 		ret = ldap_sasl_cram_md5_bind_s(ld, who, &ber_cred, NULL, NULL);
590 	} else if (method == digest_md5) {
591 		/* Note: there is only a synchronous call for digest-md5 */
592 		struct berval ber_cred;
593 
594 		ber_cred.bv_len = strlen(cred);
595 		ber_cred.bv_val = cred;
596 		ret = ldap_x_sasl_digest_md5_bind_s(ld, who, &ber_cred, NULL,
597 			NULL);
598 	} else {
599 		ret = LDAP_AUTH_METHOD_NOT_SUPPORTED;
600 	}
601 
602 	if (ret != LDAP_SUCCESS) {
603 		(void) ldap_unbind_s(ld);
604 		*ldP = 0;
605 		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
606 			"%s: Unable to bind as: %s: %s",
607 			myself, who, ldap_err2string(ret));
608 	}
609 
610 	return (ret);
611 }
612 
613 /*
614  * Free 'lc' and all related memory. Caller must hold the exclusive lock.
615  * Return LDAP_UNAVAILABLE upon success, in which case the caller mustn't
616  * try to use the structure pointer in any way.
617  */
618 static int
619 freeCon(__nis_ldap_conn_t *lc) {
620 	char			*myself = "freeCon";
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 (0; *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 	if (ldapStat == 0)
1368 		ldapStat = &stat;
1369 
1370 	if (ls == 0) {
1371 		*ldapStat = LDAP_PARAM_ERROR;
1372 		return (0);
1373 	}
1374 
1375 	if (yp2ldap) {
1376 		/* make sure the parent's connection is not used by child */
1377 		if ((lc = findYPCon(ls, ldapStat)) == 0) {
1378 			*ldapStat = LDAP_SERVER_DOWN;
1379 			return (0);
1380 		}
1381 	} else {
1382 		if ((lc = findCon(ldapStat)) == 0) {
1383 			*ldapStat = LDAP_SERVER_DOWN;
1384 			return (0);
1385 		}
1386 	}
1387 
1388 	if (numValues != 0 && (*numValues == 0 || *numValues == 1))
1389 		doIndex = 0;
1390 
1391 retry_new_conn:
1392 	/* Prefer VLV over simple page, and SP over nothing */
1393 	if (doIndex && lc->vlv) {
1394 		stat = ldap_create_sort_keylist(&sortKeyList, SORTKEYLIST);
1395 		if (stat != LDAP_SUCCESS) {
1396 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1397 				"%s: Error creating sort keylist: %s",
1398 				myself, ldap_err2string(stat));
1399 			freeRuleValue(rv, numVals);
1400 			*ldapStat = stat;
1401 			rv = 0;
1402 			goto retry_noVLV;
1403 		}
1404 		stat = ldap_create_sort_control(lc->ld, sortKeyList, 1,
1405 						&sortCtrl);
1406 		if (stat == LDAP_SUCCESS) {
1407 			vList.ldvlist_before_count = 0;
1408 			vList.ldvlist_after_count = lc->batchFrom - 1;
1409 			vList.ldvlist_attrvalue = 0;
1410 			vList.ldvlist_extradata = 0;
1411 			index = 1;
1412 			doVLV = 1;
1413 		} else {
1414 			ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
1415 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1416 				"%s: Error creating VLV sort control: %s",
1417 				myself, ldap_err2string(stat));
1418 			freeRuleValue(rv, numVals);
1419 			*ldapStat = stat;
1420 			rv = 0;
1421 		}
1422 	}
1423 
1424 retry_noVLV:
1425 
1426 	if (doIndex && !doVLV && lc->simplePage) {
1427 		spCookie = am(myself, sizeof (*spCookie));
1428 		if (spCookie != 0 &&
1429 				(spCookie->bv_val = sdup(myself, T, "")) != 0) {
1430 			spCookie->bv_len = 0;
1431 			doSP = 1;
1432 		} else {
1433 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1434 	"%s: No memory for simple page cookie; using un-paged LDAP search",
1435 				myself);
1436 			freeRuleValue(rv, numVals);
1437 			*ldapStat = stat;
1438 			rv = 0;
1439 			goto cleanup;
1440 		}
1441 	}
1442 
1443 	if (!doVLV && !doSP)
1444 		ctrls[0] = ctrls[1] = 0;
1445 
1446 	numVals = 0;
1447 	done = 0;
1448 
1449 	if (ls->timeout.tv_sec || ls->timeout.tv_usec) {
1450 		tv = ls->timeout;
1451 	} else {
1452 		tv = lc->searchTimeout;
1453 	}
1454 	(void) gettimeofday(&start, 0);
1455 
1456 	do {
1457 		/* don't do vlv or simple page for base level searches */
1458 		if (doVLV && ls->base != LDAP_SCOPE_BASE) {
1459 			vList.ldvlist_index = index;
1460 			vList.ldvlist_size = 0;
1461 			if (vlvCtrl != 0)
1462 				ldap_control_free(vlvCtrl);
1463 			stat = ldap_create_virtuallist_control(lc->ld,
1464 					&vList, &vlvCtrl);
1465 			if (stat != LDAP_SUCCESS) {
1466 				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1467 						&stat);
1468 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1469 				"%s: Error creating VLV at index %ld: %s",
1470 					myself, index, ldap_err2string(stat));
1471 				*ldapStat = stat;
1472 				freeRuleValue(rv, numVals);
1473 				rv = 0;
1474 				goto cleanup;
1475 			}
1476 			ctrls[0] = sortCtrl;
1477 			ctrls[1] = vlvCtrl;
1478 			ctrls[2] = 0;
1479 			stat = ldap_search_ext_s(lc->ld, ls->base,
1480 					ls->scope, ls->filter, ls->attrs,
1481 					ls->attrsonly, ctrls, 0, &tv,
1482 					proxyInfo.search_size_limit, &msg);
1483 		/* don't do vlv or simple page for base level searches */
1484 		} else if (doSP && ls->base != LDAP_SCOPE_BASE) {
1485 			if (ctrls[0] != 0)
1486 				ldap_control_free(ctrls[0]);
1487 			stat = ldap_create_page_control(lc->ld,
1488 					lc->batchFrom, spCookie, 0, &ctrls[0]);
1489 			if (stat != LDAP_SUCCESS) {
1490 				ber_bvfree(spCookie);
1491 				spCookie = 0;
1492 				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1493 						&stat);
1494 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1495 					"%s: Simple page error: %s",
1496 					myself, ldap_err2string(stat));
1497 				freeRuleValue(rv, numVals);
1498 				*ldapStat = stat;
1499 				rv = 0;
1500 				goto cleanup;
1501 			}
1502 			ctrls[1] = 0;
1503 			stat = ldap_search_ext_s(lc->ld, ls->base,
1504 					ls->scope, ls->filter, ls->attrs,
1505 					ls->attrsonly, ctrls, 0, &tv,
1506 					proxyInfo.search_size_limit, &msg);
1507 		} else {
1508 			stat = ldap_search_st(lc->ld, ls->base, ls->scope,
1509 					ls->filter, ls->attrs, ls->attrsonly,
1510 					&tv, &msg);
1511 		}
1512 		if (stat == LDAP_SUCCESS)
1513 			ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
1514 
1515 		if (stat == LDAP_SERVER_DOWN) {
1516 			lc->doDis++;
1517 			releaseCon(lc, stat);
1518 			lc = (yp2ldap)?findYPCon(ls, ldapStat):
1519 				findCon(ldapStat);
1520 			if (lc == 0) {
1521 				*ldapStat = LDAP_SERVER_DOWN;
1522 				rv =  0;
1523 				goto cleanup;
1524 			}
1525 			goto retry_new_conn;
1526 		}
1527 
1528 		if (stat == LDAP_REFERRAL && follow_referral) {
1529 			(void) ldap_parse_result(lc->ld, msg, NULL, NULL, NULL,
1530 				&referralsp, NULL, 0);
1531 			if (referralsp != NULL) {
1532 				/* We support at most one level of referrals */
1533 				follow_referral = FALSE;
1534 				releaseCon(lc, stat);
1535 				lc = findReferralCon(referralsp, &stat);
1536 				ldap_value_free(referralsp);
1537 				if (lc == NULL) {
1538 					freeRuleValue(rv, numVals);
1539 					rv = 0;
1540 					*ldapStat = stat;
1541 					goto cleanup;
1542 				}
1543 				stat = LDAP_SUCCESS;
1544 				goto retry_new_conn;
1545 			}
1546 		}
1547 		*ldapStat = stat;
1548 
1549 		if (*ldapStat == LDAP_NO_SUCH_OBJECT) {
1550 			freeRuleValue(rv, numVals);
1551 			rv = 0;
1552 			goto cleanup;
1553 		} else if (doVLV && *ldapStat == LDAP_INSUFFICIENT_ACCESS) {
1554 			/*
1555 			 * The LDAP server (at least Netscape 4.x) can return
1556 			 * LDAP_INSUFFICIENT_ACCESS when VLV is supported,
1557 			 * but not for the bind DN specified. So, just in
1558 			 * case, we clean up, and try again without VLV.
1559 			 */
1560 			doVLV = 0;
1561 			if (msg != 0) {
1562 				(void) ldap_msgfree(msg);
1563 				msg = 0;
1564 			}
1565 			if (ctrls[0] != 0) {
1566 				ldap_control_free(ctrls[0]);
1567 				ctrls[0] = 0;
1568 			}
1569 			if (ctrls[1] != 0) {
1570 				ldap_control_free(ctrls[1]);
1571 				ctrls[1] = 0;
1572 			}
1573 			logmsg(MSG_VLV_INSUFF_ACC, LOG_WARNING,
1574 	"%s: VLV insufficient access from server %s; retrying without VLV",
1575 				myself, NIL(lc->sp));
1576 			goto retry_noVLV;
1577 		} else if (*ldapStat != LDAP_SUCCESS) {
1578 			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1579 				"ldap_search(0x%x,\n\t\"%s\",\n\t %d,",
1580 				lc->ld, NIL(ls->base), ls->scope);
1581 			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1582 				"\t\"%s\",\n\t0x%x,\n\t%d) => %d (%s)",
1583 				NIL(ls->filter), ls->attrs, ls->attrsonly,
1584 				*ldapStat, ldap_err2string(stat));
1585 			freeRuleValue(rv, numVals);
1586 			rv = 0;
1587 			goto cleanup;
1588 		}
1589 
1590 		numEntries = ldap_count_entries(lc->ld, msg);
1591 		if (numEntries == 0 && *ldapStat == LDAP_SUCCESS) {
1592 			/*
1593 			 * This is a bit weird, but the server (or, at least,
1594 			 * ldap_search_ext()) can sometimes return
1595 			 * LDAP_SUCCESS and no entries when it didn't
1596 			 * find what we were looking for. Seems it ought to
1597 			 * return LDAP_NO_SUCH_OBJECT or some such.
1598 			 */
1599 			freeRuleValue(rv, numVals);
1600 			rv = 0;
1601 			*ldapStat = LDAP_NO_SUCH_OBJECT;
1602 			goto cleanup;
1603 		}
1604 
1605 		tnv = numVals + numEntries;
1606 		if ((rv = growRuleValue(numVals, tnv, rv, rvIn)) == 0) {
1607 			*ldapStat = LDAP_NO_MEMORY;
1608 			goto cleanup;
1609 		}
1610 
1611 		for (m = ldap_first_entry(lc->ld, msg); m != 0;
1612 				m = ldap_next_entry(lc->ld, m), numVals++) {
1613 			char		*nm;
1614 			BerElement	*ber = 0;
1615 
1616 			if (numVals > tnv) {
1617 				logmsg(MSG_NOTIMECHECK, LOG_INFO,
1618 				"%s: Inconsistent LDAP entry count > %d",
1619 					myself, numEntries);
1620 				break;
1621 			}
1622 
1623 			nm = ldap_get_dn(lc->ld, m);
1624 			if (nm == 0 || addSAttr2RuleValue("dn", nm,
1625 					&rv[numVals])) {
1626 				sfree(nm);
1627 				*ldapStat = LDAP_NO_MEMORY;
1628 				freeRuleValue(rv, tnv);
1629 				rv = 0;
1630 				goto cleanup;
1631 			}
1632 			sfree(nm);
1633 
1634 			for (nm = ldap_first_attribute(lc->ld, m, &ber);
1635 					nm != 0;
1636 				nm = ldap_next_attribute(lc->ld, m, ber)) {
1637 				struct berval	**val;
1638 				int		i, nv;
1639 
1640 				val = ldap_get_values_len(lc->ld, m, nm);
1641 				nv = (val == 0) ? 0 :
1642 						ldap_count_values_len(val);
1643 				for (i = 0; i < nv; i++) {
1644 					/*
1645 					 * Since we don't know if the value is
1646 					 * BER-encoded or not, we mark it as a
1647 					 * string. All is well as long as we
1648 					 * don't insist on 'vt_ber' when
1649 					 * interpreting.
1650 					 */
1651 					if (addAttr2RuleValue(vt_string, nm,
1652 							val[i]->bv_val,
1653 							val[i]->bv_len,
1654 							&rv[numVals])) {
1655 						if (ber != 0)
1656 							ber_free(ber, 0);
1657 						ldap_value_free_len(val);
1658 						*ldapStat = LDAP_NO_MEMORY;
1659 						freeRuleValue(rv, tnv);
1660 						rv = 0;
1661 						goto cleanup;
1662 					}
1663 				}
1664 				/*
1665 				 * XXX the ldap_first_attribute(3LDAP) man
1666 				 * page says that the ldap_first_attribute/
1667 				 * ldap_next_attribute should be treated as
1668 				 * static, but the libldap.so.4 code mallocs
1669 				 * (and it's not TSD). So, in order to avoid
1670 				 * a leak, we free the return value.
1671 				 */
1672 				ldap_memfree(nm);
1673 				if (val != 0)
1674 					ldap_value_free_len(val);
1675 			}
1676 			/*
1677 			 * XXX ldap_next_attribute(3LDAP) says that the 'ber'
1678 			 * pointer is freed when it returns NULL, but that's
1679 			 * not implemented in the libldap.so.4 code, so we
1680 			 * free it here in order to avoid a memory leak.
1681 			 */
1682 			if (ber != 0)
1683 				ber_free(ber, 0);
1684 		}
1685 
1686 		if (numVals != tnv) {
1687 			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1688 		"%s: Inconsistent LDAP entry count, found = %d, expected %d",
1689 				myself, numVals, tnv);
1690 		}
1691 
1692 		if (doVLV) {
1693 			stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
1694 						&retCtrls, 0);
1695 			if (stat != LDAP_SUCCESS) {
1696 				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1697 						&stat);
1698 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1699 					"%s: VLV parse result error: %s",
1700 					myself, ldap_err2string(stat));
1701 				*ldapStat = stat;
1702 				freeRuleValue(rv, tnv);
1703 				rv = 0;
1704 				goto cleanup;
1705 			}
1706 			if (retCtrls != 0) {
1707 				unsigned long	targetPosP = 0;
1708 				unsigned long	listSize = 0;
1709 
1710 				stat = ldap_parse_virtuallist_control(lc->ld,
1711 					retCtrls, &targetPosP, &listSize,
1712 					&lprEc);
1713 				if (stat == LDAP_SUCCESS) {
1714 					index = targetPosP + lc->batchFrom;
1715 					if (index >= listSize)
1716 						done = 1;
1717 				}
1718 				ldap_controls_free(retCtrls);
1719 				retCtrls = 0;
1720 			} else {
1721 				done = 1;
1722 			}
1723 		} else if (doSP) {
1724 			stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
1725 						&retCtrls, 0);
1726 			if (stat != LDAP_SUCCESS) {
1727 				ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
1728 						&stat);
1729 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1730 				"%s: Simple page parse result error: %s",
1731 					myself, ldap_err2string(stat));
1732 				*ldapStat = stat;
1733 				freeRuleValue(rv, tnv);
1734 				rv = 0;
1735 				goto cleanup;
1736 			}
1737 			if (retCtrls != 0) {
1738 				unsigned int	count;
1739 
1740 				if (spCookie != 0) {
1741 					ber_bvfree(spCookie);
1742 					spCookie = 0;
1743 				}
1744 				stat = ldap_parse_page_control(lc->ld,
1745 						retCtrls, &count, &spCookie);
1746 				if (stat == LDAP_SUCCESS) {
1747 					if (spCookie == 0 ||
1748 						spCookie->bv_val == 0 ||
1749 						spCookie->bv_len == 0)
1750 						done = 1;
1751 				}
1752 				ldap_controls_free(retCtrls);
1753 				retCtrls = 0;
1754 			} else {
1755 				done = 1;
1756 			}
1757 		} else {
1758 			done = 1;
1759 		}
1760 
1761 		(void) ldap_msgfree(msg);
1762 		msg = 0;
1763 
1764 		/*
1765 		 * If we're using VLV or SP, the timeout should apply
1766 		 * to all calls as an aggregate, so we need to reduce
1767 		 * 'tv' with the time spent on this chunk of data.
1768 		 */
1769 		if (!done) {
1770 			struct timeval	tmp;
1771 
1772 			(void) gettimeofday(&now, 0);
1773 			tmp = now;
1774 			now.tv_sec -= start.tv_sec;
1775 			now.tv_usec -= start.tv_usec;
1776 			if (now.tv_usec < 0) {
1777 				now.tv_usec += 1000000;
1778 				now.tv_sec -= 1;
1779 			}
1780 			tv.tv_sec -= now.tv_sec;
1781 			tv.tv_usec -= now.tv_usec;
1782 			if (tv.tv_usec < 0) {
1783 				tv.tv_usec += 1000000;
1784 				tv.tv_sec -= 1;
1785 			}
1786 			if (tv.tv_sec < 0) {
1787 				*ldapStat = LDAP_TIMEOUT;
1788 				freeRuleValue(rv, tnv);
1789 				rv = 0;
1790 				goto cleanup;
1791 			}
1792 			start = tmp;
1793 		}
1794 
1795 	} while (!done);
1796 
1797 	if (numValues != 0)
1798 		*numValues = numVals;
1799 
1800 cleanup:
1801 	if (NULL != lc) {
1802 		if (yp2ldap && ls->useCon == 0) {
1803 			/* Disconnect and free the connection */
1804 			lc->doDis++;
1805 			lc->doDel++;
1806 			releaseCon(lc, stat);
1807 			releaseLC(lc);
1808 
1809 		} else {
1810 			releaseCon(lc, stat);
1811 		}
1812 	}
1813 	if (msg != 0)
1814 		(void) ldap_msgfree(msg);
1815 	if (ctrls[0] != 0)
1816 		ldap_control_free(ctrls[0]);
1817 	if (ctrls[1] != 0)
1818 		ldap_control_free(ctrls[1]);
1819 	if (spCookie != 0)
1820 		ber_bvfree(spCookie);
1821 	if (sortKeyList != 0)
1822 		ldap_free_sort_keylist(sortKeyList);
1823 
1824 	return (rv);
1825 }
1826 
1827 static void
1828 freeLdapModEntry(LDAPMod *m) {
1829 
1830 	if (m == 0)
1831 		return;
1832 
1833 	sfree(m->mod_type);
1834 	if ((m->mod_op & LDAP_MOD_BVALUES) == 0) {
1835 		char	**v = m->mod_values;
1836 
1837 		if (v != 0) {
1838 			while (*v != 0) {
1839 				sfree(*v);
1840 				v++;
1841 			}
1842 			free(m->mod_values);
1843 		}
1844 	} else {
1845 		struct berval	**b = m->mod_bvalues;
1846 
1847 		if (b != 0) {
1848 			while (*b != 0) {
1849 				sfree((*b)->bv_val);
1850 				free(*b);
1851 				b++;
1852 			}
1853 			free(m->mod_bvalues);
1854 		}
1855 	}
1856 
1857 	free(m);
1858 }
1859 
1860 static void
1861 freeLdapMod(LDAPMod **mods) {
1862 	LDAPMod		*m, **org = mods;
1863 
1864 	if (mods == 0)
1865 		return;
1866 
1867 	while ((m = *mods) != 0) {
1868 		freeLdapModEntry(m);
1869 		mods++;
1870 	}
1871 
1872 	free(org);
1873 }
1874 
1875 /*
1876  * Convert a rule-value structure to the corresponding LDAPMod.
1877  * If 'add' is set, attributes/values are added; object classes
1878  * are also added. If 'add' is cleared, attributes/values are modified,
1879  * and 'oc' controls whether or not object classes are added.
1880  */
1881 LDAPMod **
1882 search2LdapMod(__nis_rule_value_t *rv, int add, int oc) {
1883 	LDAPMod		**mods;
1884 	int		i, j, nm;
1885 	char		*myself = "search2LdapMod";
1886 
1887 	if (rv == 0 || rv->numAttrs <= 0)
1888 		return (0);
1889 
1890 	mods = am(myself, (rv->numAttrs + 1) * sizeof (mods[0]));
1891 	if (mods == 0)
1892 		return (0);
1893 
1894 	for (i = 0, nm = 0; i < rv->numAttrs; i++) {
1895 		int	isOc;
1896 		/*
1897 		 * If we're creating an LDAPMod array for an add operation,
1898 		 * just skip attributes that should be deleted.
1899 		 */
1900 		if (add && rv->attrVal[i].numVals < 0)
1901 			continue;
1902 
1903 		/*
1904 		 * Skip DN; it's specified separately to ldap_modify()
1905 		 * and ldap_add(), and mustn't appear among the
1906 		 * attributes to be modified/added.
1907 		 */
1908 		if (strcasecmp("dn", rv->attrName[i]) == 0)
1909 			continue;
1910 
1911 		/*
1912 		 * If modifying, and 'oc' is off, skip object class
1913 		 * attributes.
1914 		 */
1915 		isOc = (strcasecmp("objectclass", rv->attrName[i]) == 0);
1916 		if (!add && !oc && isOc)
1917 			continue;
1918 
1919 		mods[nm] = am(myself, sizeof (*mods[nm]));
1920 		if (mods[nm] == 0) {
1921 			freeLdapMod(mods);
1922 			return (0);
1923 		}
1924 
1925 		/* 'mod_type' is the attribute name */
1926 		mods[nm]->mod_type = sdup(myself, T, rv->attrName[i]);
1927 		if (mods[nm]->mod_type == 0) {
1928 			freeLdapMod(mods);
1929 			return (0);
1930 		}
1931 
1932 		/*
1933 		 * numVals < 0 means attribute and all values should
1934 		 * be deleted.
1935 		 */
1936 		if (rv->attrVal[i].numVals < 0) {
1937 			mods[nm]->mod_op = LDAP_MOD_DELETE;
1938 			mods[nm]->mod_values = 0;
1939 			nm++;
1940 			continue;
1941 		}
1942 
1943 		/* objectClass attributes always added */
1944 		mods[nm]->mod_op = (add) ? 0 : ((isOc) ? 0 : LDAP_MOD_REPLACE);
1945 
1946 		if (rv->attrVal[i].type == vt_string) {
1947 			/*
1948 			 * mods[]->mod_values is a NULL-terminated array
1949 			 * of (char *)'s.
1950 			 */
1951 			mods[nm]->mod_values = am(myself,
1952 					(rv->attrVal[i].numVals + 1) *
1953 					sizeof (mods[nm]->mod_values[0]));
1954 			if (mods[nm]->mod_values == 0) {
1955 				freeLdapMod(mods);
1956 				return (0);
1957 			}
1958 			for (j = 0; j < rv->attrVal[i].numVals; j++) {
1959 				/*
1960 				 * Just in case the string isn't NUL
1961 				 * terminated, add one byte to the
1962 				 * allocated length; am() will initialize
1963 				 * the buffer to zero.
1964 				 */
1965 				mods[nm]->mod_values[j] = am(myself,
1966 					rv->attrVal[i].val[j].length + 1);
1967 				if (mods[nm]->mod_values[j] == 0) {
1968 					freeLdapMod(mods);
1969 					return (0);
1970 				}
1971 				memcpy(mods[nm]->mod_values[j],
1972 					rv->attrVal[i].val[j].value,
1973 					rv->attrVal[i].val[j].length);
1974 			}
1975 		} else {
1976 			mods[nm]->mod_op |= LDAP_MOD_BVALUES;
1977 			mods[nm]->mod_bvalues = am(myself,
1978 					(rv->attrVal[i].numVals+1) *
1979 					sizeof (mods[nm]->mod_bvalues[0]));
1980 			if (mods[nm]->mod_bvalues == 0) {
1981 				freeLdapMod(mods);
1982 				return (0);
1983 			}
1984 			for (j = 0; j < rv->attrVal[i].numVals; j++) {
1985 				mods[nm]->mod_bvalues[j] = am(myself,
1986 					sizeof (*mods[nm]->mod_bvalues[j]));
1987 				if (mods[nm]->mod_bvalues[j] == 0) {
1988 					freeLdapMod(mods);
1989 					return (0);
1990 				}
1991 				mods[nm]->mod_bvalues[j]->bv_val = am(myself,
1992 					rv->attrVal[i].val[j].length);
1993 				if (mods[nm]->mod_bvalues[j]->bv_val == 0) {
1994 					freeLdapMod(mods);
1995 					return (0);
1996 				}
1997 				mods[nm]->mod_bvalues[j]->bv_len =
1998 					rv->attrVal[i].val[j].length;
1999 				memcpy(mods[nm]->mod_bvalues[j]->bv_val,
2000 					rv->attrVal[i].val[j].value,
2001 					mods[nm]->mod_bvalues[j]->bv_len);
2002 			}
2003 		}
2004 		nm++;
2005 	}
2006 
2007 	return (mods);
2008 }
2009 
2010 /*
2011  * Remove 'value' from 'val'. If value==0, remove the entire
2012  * __nis_single_value_t array from 'val'.
2013  */
2014 static void
2015 removeSingleValue(__nis_value_t *val, void *value, int length) {
2016 	int	i;
2017 
2018 	if (val == 0)
2019 		return;
2020 
2021 	if (value == 0) {
2022 		for (i = 0; i < val->numVals; i++) {
2023 			sfree(val->val[i].value);
2024 		}
2025 		sfree(val->val);
2026 		val->val = 0;
2027 		val->numVals = 0;
2028 		return;
2029 	}
2030 
2031 	for (i = 0; i < val->numVals; i++) {
2032 		if (val->val[i].value == 0 || (val->val[i].length != length))
2033 			continue;
2034 		if (memcmp(val->val[i].value, value, length) != 0)
2035 			continue;
2036 		sfree(val->val[i].value);
2037 		if (i != (val->numVals - 1)) {
2038 			(void) memmove(&val->val[i], &val->val[i+1],
2039 				(val->numVals - 1 - i) * sizeof (val->val[0]));
2040 		}
2041 		val->numVals -= 1;
2042 		break;
2043 	}
2044 }
2045 
2046 /*
2047  * Helper function for LdapModify
2048  * When a modify operation fails with an object class violation,
2049  * the most probable reason is that the attributes we're modifying are new,
2050  * and the needed object class are not present. So, try the modify again,
2051  * but add the object classes this time.
2052  */
2053 
2054 static int
2055 ldapModifyObjectClass(__nis_ldap_conn_t **lc, char *dn,
2056 		__nis_rule_value_t *rvIn, char *objClassAttrs)
2057 {
2058 	LDAPMod			**mods = 0;
2059 	int			msgid;
2060 	int			lderr;
2061 	struct timeval		tv;
2062 	int			stat;
2063 	LDAPMessage		*msg = 0;
2064 	char			**referralsp = NULL;
2065 	__nis_rule_value_t	*rv, *rvldap;
2066 	__nis_ldap_search_t	*ls;
2067 	int			i, ocrv, ocrvldap, nv;
2068 	char			*oc[2] = { "objectClass", 0};
2069 	char			*myself = "ldapModifyObjectClass";
2070 
2071 	rv = initRuleValue(1, rvIn);
2072 	if (rv == 0)
2073 		return (LDAP_NO_MEMORY);
2074 
2075 	delAttrFromRuleValue(rv, "objectClass");
2076 	rv = addObjectClasses(rv, objClassAttrs);
2077 	if (rv == 0) {
2078 		stat = LDAP_OPERATIONS_ERROR;
2079 		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
2080 			"%s: addObjectClasses failed for %s",
2081 			myself, NIL(dn));
2082 		goto cleanup;
2083 	}
2084 
2085 	/*
2086 	 * Before adding the object classes whole-sale, try retrieving
2087 	 * the entry specified by the 'dn'. If it exists, we filter out
2088 	 * those object classes that already are present in LDAP from our
2089 	 * update.
2090 	 */
2091 	ls = buildLdapSearch(dn, LDAP_SCOPE_BASE, 0, 0, "objectClass=*",
2092 				oc, 0, 1);
2093 	if (ls == 0) {
2094 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
2095 			"%s: Unable to build DN search for \"%s\"",
2096 			myself, NIL(dn));
2097 		/* Fall through to try just adding the object classes */
2098 		goto addObjectClasses;
2099 	}
2100 
2101 	nv = 0;
2102 	rvldap = ldapSearch(ls, &nv, 0, &lderr);
2103 	freeLdapSearch(ls);
2104 	if (rvldap == 0) {
2105 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
2106 			"%s: No data for DN search (\"%s\"); LDAP status %d",
2107 			myself, NIL(dn), lderr);
2108 		/* Fall through to try just adding the object classes */
2109 		goto addObjectClasses;
2110 	}
2111 
2112 	/*
2113 	 * Find the indices of the 'objectClass' attribute
2114 	 * in 'rvldap' and 'rv'.
2115 	 */
2116 	for (i = 0, ocrvldap = -1; i < rvldap->numAttrs; i++) {
2117 		if (rvldap->attrName[i] != 0 &&
2118 			strcasecmp("objectClass", rvldap->attrName[i]) == 0) {
2119 			ocrvldap = i;
2120 			break;
2121 		}
2122 	}
2123 	for (i = 0, ocrv = -1; i < rv->numAttrs; i++) {
2124 		if (rv->attrName[i] != 0 &&
2125 			strcasecmp("objectClass", rv->attrName[i]) == 0) {
2126 			ocrv = i;
2127 			break;
2128 		}
2129 	}
2130 
2131 	/*
2132 	 * Remove those object classes that already exist
2133 	 * in LDAP (i.e., in 'rvldap') from 'rv'.
2134 	 */
2135 	if (ocrv >= 0 && ocrvldap >= 0) {
2136 		for (i = 0; i < rvldap->attrVal[ocrvldap].numVals; i++) {
2137 			removeSingleValue(&rv->attrVal[ocrv],
2138 				rvldap->attrVal[ocrvldap].val[i].value,
2139 				rvldap->attrVal[ocrvldap].val[i].length);
2140 		}
2141 		/*
2142 		 * If no 'objectClass' values left in 'rv', delete
2143 		 * 'objectClass' from 'rv'.
2144 		 */
2145 		if (rv->attrVal[ocrv].numVals == 0)
2146 			delAttrFromRuleValue(rv, "objectClass");
2147 	}
2148 
2149 	/*
2150 	 * 'rv' now contains the update we want to make, with just the
2151 	 * object class(es) that need to be added. Fall through to the
2152 	 * actual LDAP modify operation.
2153 	 */
2154 	freeRuleValue(rvldap, 1);
2155 
2156 addObjectClasses:
2157 
2158 	mods = search2LdapMod(rv, 0, 1);
2159 	if (mods == 0) {
2160 		stat = LDAP_OPERATIONS_ERROR;
2161 		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
2162 	"%s: Unable to create LDAP modify changes with object classes for %s",
2163 			myself, NIL(dn));
2164 		goto cleanup;
2165 	}
2166 	msgid = ldap_modify((*lc)->ld, dn, mods);
2167 	if (msgid != -1) {
2168 		tv = (*lc)->modifyTimeout;
2169 		stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
2170 		if (stat == 0) {
2171 			stat = LDAP_TIMEOUT;
2172 		} else if (stat == -1) {
2173 			(void) ldap_get_option((*lc)->ld,
2174 				LDAP_OPT_ERROR_NUMBER, &stat);
2175 		} else {
2176 			stat = ldap_parse_result((*lc)->ld, msg, &lderr, NULL,
2177 				NULL, &referralsp, NULL, 0);
2178 			if (stat == LDAP_SUCCESS)
2179 				stat = lderr;
2180 			stat = ldap_result2error((*lc)->ld, msg, 0);
2181 		}
2182 	} else {
2183 		(void) ldap_get_option((*lc)->ld, LDAP_OPT_ERROR_NUMBER,
2184 			&stat);
2185 	}
2186 	if (proxyInfo.follow_referral == follow &&
2187 			stat == LDAP_REFERRAL && referralsp != NULL) {
2188 		releaseCon(*lc, stat);
2189 		if (msg != NULL)
2190 			(void) ldap_msgfree(msg);
2191 		msg = NULL;
2192 		*lc = findReferralCon(referralsp, &stat);
2193 		ldap_value_free(referralsp);
2194 		referralsp = NULL;
2195 		if (*lc == NULL)
2196 			goto cleanup;
2197 		msgid = ldap_modify((*lc)->ld, dn, mods);
2198 		if (msgid == -1) {
2199 			(void) ldap_get_option((*lc)->ld,
2200 				LDAP_OPT_ERROR_NUMBER, &stat);
2201 			goto cleanup;
2202 		}
2203 		stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
2204 		if (stat == 0) {
2205 			stat = LDAP_TIMEOUT;
2206 		} else if (stat == -1) {
2207 			(void) ldap_get_option((*lc)->ld,
2208 				LDAP_OPT_ERROR_NUMBER, &stat);
2209 		} else {
2210 			stat = ldap_parse_result((*lc)->ld, msg, &lderr,
2211 				NULL, NULL, NULL, NULL, 0);
2212 			if (stat == LDAP_SUCCESS)
2213 				stat = lderr;
2214 		}
2215 	}
2216 cleanup:
2217 	if (mods != 0)
2218 		freeLdapMod(mods);
2219 	freeRuleValue(rv, 1);
2220 	return (stat);
2221 }
2222 
2223 /*
2224  * Modify the specified 'dn' per the attribute names/values in 'rv'.
2225  * If 'rv' is NULL, we attempt to delete the entire entry.
2226  *
2227  * The 'objClassAttrs' parameter is needed if the entry must be added
2228  * (i.e., created), or a modify fails with an object class violation.
2229  *
2230  * If 'addFirst' is set, we try an add before a modify; modify before
2231  * add otherwise (ignored if we're deleting).
2232  */
2233 int
2234 ldapModify(char *dn, __nis_rule_value_t *rv, char *objClassAttrs,
2235 		int addFirst) {
2236 	int			stat, add = 0;
2237 	LDAPMod			**mods = 0;
2238 	__nis_ldap_conn_t	*lc;
2239 	struct timeval		tv;
2240 	LDAPMessage		*msg = 0;
2241 	char			*myself = "ldapModify";
2242 	int			msgid;
2243 	int			lderr;
2244 	char			**referralsp = NULL;
2245 	bool_t			delete = FALSE;
2246 
2247 	if (dn == 0)
2248 		return (LDAP_PARAM_ERROR);
2249 
2250 	if ((lc = findCon(&stat)) == 0)
2251 		return (stat);
2252 
2253 	if (rv == 0) {
2254 		delete = TRUE;
2255 		/* Simple case: if rv == 0, try to delete the entire entry */
2256 		msgid = ldap_delete(lc->ld, dn);
2257 		if (msgid == -1) {
2258 			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2259 						&stat);
2260 			goto cleanup;
2261 		}
2262 		tv = lc->deleteTimeout;
2263 		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2264 
2265 		if (stat == 0) {
2266 			stat = LDAP_TIMEOUT;
2267 		} else if (stat == -1) {
2268 			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2269 						&stat);
2270 		} else {
2271 			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2272 				NULL, &referralsp, NULL, 0);
2273 			if (stat == LDAP_SUCCESS)
2274 				stat = lderr;
2275 		}
2276 		if (proxyInfo.follow_referral == follow &&
2277 				stat == LDAP_REFERRAL && referralsp != NULL) {
2278 			releaseCon(lc, stat);
2279 			if (msg != NULL)
2280 				(void) ldap_msgfree(msg);
2281 			msg = NULL;
2282 			lc = findReferralCon(referralsp, &stat);
2283 			ldap_value_free(referralsp);
2284 			if (lc == NULL)
2285 				goto cleanup;
2286 			msgid = ldap_delete(lc->ld, dn);
2287 			if (msgid == -1) {
2288 				(void) ldap_get_option(lc->ld,
2289 					LDAP_OPT_ERROR_NUMBER, &stat);
2290 				goto cleanup;
2291 			}
2292 			stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2293 			if (stat == 0) {
2294 				stat = LDAP_TIMEOUT;
2295 			} else if (stat == -1) {
2296 				(void) ldap_get_option(lc->ld,
2297 					LDAP_OPT_ERROR_NUMBER, &stat);
2298 			} else {
2299 				stat = ldap_parse_result(lc->ld, msg, &lderr,
2300 					NULL, NULL, NULL, NULL, 0);
2301 				if (stat == LDAP_SUCCESS)
2302 					stat = lderr;
2303 			}
2304 		}
2305 		/* No such object means someone else has done our job */
2306 		if (stat == LDAP_NO_SUCH_OBJECT)
2307 			stat = LDAP_SUCCESS;
2308 	} else {
2309 		if (addFirst) {
2310 			stat = ldapAdd(dn, rv, objClassAttrs, lc);
2311 			lc = NULL;
2312 			if (stat != LDAP_ALREADY_EXISTS)
2313 				goto cleanup;
2314 			if ((lc = findCon(&stat)) == 0)
2315 				return (stat);
2316 		}
2317 
2318 		/*
2319 		 * First try the modify without specifying object classes
2320 		 * (i.e., assume they're already present).
2321 		 */
2322 		mods = search2LdapMod(rv, 0, 0);
2323 		if (mods == 0) {
2324 			stat = LDAP_PARAM_ERROR;
2325 			goto cleanup;
2326 		}
2327 
2328 		msgid = ldap_modify(lc->ld, dn, mods);
2329 		if (msgid == -1) {
2330 			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2331 						&stat);
2332 			goto cleanup;
2333 		}
2334 		tv = lc->modifyTimeout;
2335 		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2336 		if (stat == 0) {
2337 			stat = LDAP_TIMEOUT;
2338 		} else if (stat == -1) {
2339 			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2340 						&stat);
2341 		} else {
2342 			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2343 				NULL, &referralsp, NULL, 0);
2344 			if (stat == LDAP_SUCCESS)
2345 				stat = lderr;
2346 		}
2347 		if (proxyInfo.follow_referral == follow &&
2348 				stat == LDAP_REFERRAL && referralsp != NULL) {
2349 			releaseCon(lc, stat);
2350 			if (msg != NULL)
2351 				(void) ldap_msgfree(msg);
2352 			msg = NULL;
2353 			lc = findReferralCon(referralsp, &stat);
2354 			ldap_value_free(referralsp);
2355 			referralsp = NULL;
2356 			if (lc == NULL)
2357 				goto cleanup;
2358 			msgid = ldap_modify(lc->ld, dn, mods);
2359 			if (msgid == -1) {
2360 				(void) ldap_get_option(lc->ld,
2361 					LDAP_OPT_ERROR_NUMBER, &stat);
2362 				goto cleanup;
2363 			}
2364 			stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2365 			if (stat == 0) {
2366 				stat = LDAP_TIMEOUT;
2367 			} else if (stat == -1) {
2368 				(void) ldap_get_option(lc->ld,
2369 					LDAP_OPT_ERROR_NUMBER, &stat);
2370 			} else {
2371 				stat = ldap_parse_result(lc->ld, msg, &lderr,
2372 					NULL, NULL, NULL, NULL, 0);
2373 				if (stat == LDAP_SUCCESS)
2374 					stat = lderr;
2375 			}
2376 		}
2377 
2378 		/*
2379 		 * If the modify failed with an object class violation,
2380 		 * the most probable reason is that at least on of the
2381 		 * attributes we're modifying didn't exist before, and
2382 		 * neither did its object class. So, try the modify again,
2383 		 * but add the object classes this time.
2384 		 */
2385 		if (stat == LDAP_OBJECT_CLASS_VIOLATION &&
2386 				objClassAttrs != 0) {
2387 			freeLdapMod(mods);
2388 			mods = 0;
2389 			stat = ldapModifyObjectClass(&lc, dn, rv,
2390 				objClassAttrs);
2391 		}
2392 
2393 		if (stat == LDAP_NO_SUCH_ATTRIBUTE) {
2394 			/*
2395 			 * If there was at least one attribute delete, then
2396 			 * the cause of this error could be that said attribute
2397 			 * didn't exist in LDAP. So, do things the slow way,
2398 			 * and try to delete one attribute at a time.
2399 			 */
2400 			int			d, numDelete, st;
2401 			__nis_rule_value_t	*rvt;
2402 
2403 			for (d = 0, numDelete = 0; d < rv->numAttrs; d++) {
2404 				if (rv->attrVal[d].numVals < 0)
2405 					numDelete++;
2406 			}
2407 
2408 			/* If there's just one, we've already tried */
2409 			if (numDelete <= 1)
2410 				goto cleanup;
2411 
2412 			/* Make a copy of the rule value */
2413 			rvt = initRuleValue(1, rv);
2414 			if (rvt == 0)
2415 				goto cleanup;
2416 
2417 			/*
2418 			 * Remove all delete attributes from the tmp
2419 			 * rule value.
2420 			 */
2421 			for (d = 0; d < rv->numAttrs; d++) {
2422 				if (rv->attrVal[d].numVals < 0) {
2423 					delAttrFromRuleValue(rvt,
2424 						rv->attrName[d]);
2425 				}
2426 			}
2427 
2428 			/*
2429 			 * Now put the attributes back in one by one, and
2430 			 * invoke ourselves.
2431 			 */
2432 			for (d = 0; d < rv->numAttrs; d++) {
2433 				if (rv->attrVal[d].numVals >= 0)
2434 					continue;
2435 				st = addAttr2RuleValue(rv->attrVal[d].type,
2436 					rv->attrName[d], 0, 0, rvt);
2437 				if (st != 0) {
2438 					logmsg(MSG_NOMEM, LOG_ERR,
2439 					"%s: Error deleting \"%s\" for \"%s\"",
2440 						NIL(rv->attrName[d]), NIL(dn));
2441 					stat = LDAP_NO_MEMORY;
2442 					freeRuleValue(rvt, 1);
2443 					goto cleanup;
2444 				}
2445 				stat = ldapModify(dn, rvt, objClassAttrs, 0);
2446 				if (stat != LDAP_SUCCESS &&
2447 					stat != LDAP_NO_SUCH_ATTRIBUTE) {
2448 					freeRuleValue(rvt, 1);
2449 					goto cleanup;
2450 				}
2451 				delAttrFromRuleValue(rvt, rv->attrName[d]);
2452 			}
2453 
2454 			/*
2455 			 * If we got here, then all attributes that should
2456 			 * be deleted either have been, or didn't exist. For
2457 			 * our purposes, the latter is as good as the former.
2458 			 */
2459 			stat = LDAP_SUCCESS;
2460 			freeRuleValue(rvt, 1);
2461 		}
2462 
2463 		if (stat == LDAP_NO_SUCH_OBJECT && !addFirst) {
2464 			/*
2465 			 * Entry doesn't exist, so try an ldap_add(). If the
2466 			 * ldap_add() also fails, that could be because someone
2467 			 * else added it between our modify and add operations.
2468 			 * If so, we consider that foreign add to be
2469 			 * authoritative (meaning we don't retry our modify).
2470 			 *
2471 			 * Also, if all modify operations specified by 'mods'
2472 			 * are deletes, LDAP_NO_SUCH_OBJECT is a kind of
2473 			 * success; we certainly don't want to create the
2474 			 * entry.
2475 			 */
2476 			int	allDelete;
2477 			LDAPMod	**m;
2478 
2479 			for (m = mods, allDelete = 1; *m != 0 && allDelete;
2480 					m++) {
2481 				if (((*m)->mod_op & LDAP_MOD_DELETE) == 0)
2482 					allDelete = 0;
2483 			}
2484 
2485 			add = 1;
2486 
2487 			if (allDelete) {
2488 				stat = LDAP_SUCCESS;
2489 			} else if (objClassAttrs == 0) {
2490 				/* Now we need it, so this is fatal */
2491 				stat = LDAP_PARAM_ERROR;
2492 			} else {
2493 				stat = ldapAdd(dn, rv, objClassAttrs, lc);
2494 				lc = NULL;
2495 			}
2496 		}
2497 	}
2498 
2499 cleanup:
2500 	if (stat != LDAP_SUCCESS) {
2501 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
2502 			"%s(0x%x (%s), \"%s\") => %d (%s)\n",
2503 			!delete ? (add ? "ldap_add" : "ldap_modify") :
2504 				"ldap_delete",
2505 			lc != NULL ? lc->ld : 0,
2506 			lc != NULL ? NIL(lc->sp) : "nil",
2507 			dn, stat, ldap_err2string(stat));
2508 	}
2509 
2510 	releaseCon(lc, stat);
2511 	freeLdapMod(mods);
2512 	if (msg != 0)
2513 		(void) ldap_msgfree(msg);
2514 
2515 	return (stat);
2516 }
2517 
2518 /*
2519  * Create the entry specified by 'dn' to have the values per 'rv'.
2520  * The 'objClassAttrs' are the extra object classes we need when
2521  * creating an entry.
2522  *
2523  * If 'lc' is non-NULL, we use that connection; otherwise, we find
2524  * our own. CAUTION: This connection will be released on return. Regardless
2525  * of return value, this connection should not subsequently used by the
2526  * caller.
2527  *
2528  * Returns an LDAP status.
2529  */
2530 int
2531 ldapAdd(char *dn, __nis_rule_value_t *rv, char *objClassAttrs, void *lcv) {
2532 	int			stat;
2533 	LDAPMod			**mods = 0;
2534 	struct timeval		tv;
2535 	LDAPMessage		*msg = 0;
2536 	__nis_ldap_conn_t	*lc = lcv;
2537 	int			msgid;
2538 	int			lderr;
2539 	char			**referralsp = NULL;
2540 
2541 	if (dn == 0 || rv == 0 || objClassAttrs == 0) {
2542 		releaseCon(lc, LDAP_SUCCESS);
2543 		return (LDAP_PARAM_ERROR);
2544 	}
2545 
2546 	if (lc == 0) {
2547 		if ((lc = findCon(&stat)) == 0)
2548 			return (stat);
2549 	}
2550 
2551 	rv = addObjectClasses(rv, objClassAttrs);
2552 	if (rv == 0) {
2553 		stat = LDAP_OPERATIONS_ERROR;
2554 		goto cleanup;
2555 	}
2556 
2557 	mods = search2LdapMod(rv, 1, 0);
2558 	if (mods == 0) {
2559 		stat = LDAP_OPERATIONS_ERROR;
2560 		goto cleanup;
2561 	}
2562 
2563 	msgid = ldap_add(lc->ld, dn, mods);
2564 	if (msgid == -1) {
2565 		(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
2566 		goto cleanup;
2567 	}
2568 	tv = lc->addTimeout;
2569 	stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2570 	if (stat == 0) {
2571 		stat = LDAP_TIMEOUT;
2572 	} else if (stat == -1) {
2573 		(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
2574 	} else {
2575 		stat = ldap_parse_result(lc->ld, msg, &lderr, NULL, NULL,
2576 			&referralsp, NULL, 0);
2577 		if (stat == LDAP_SUCCESS)
2578 			stat = lderr;
2579 	}
2580 	if (proxyInfo.follow_referral == follow && stat == LDAP_REFERRAL &&
2581 			referralsp != NULL) {
2582 		releaseCon(lc, stat);
2583 		if (msg != NULL)
2584 			(void) ldap_msgfree(msg);
2585 		msg = NULL;
2586 		lc = findReferralCon(referralsp, &stat);
2587 		ldap_value_free(referralsp);
2588 		if (lc == NULL)
2589 			goto cleanup;
2590 		msgid = ldap_add(lc->ld, dn, mods);
2591 		if (msgid == -1) {
2592 			(void) ldap_get_option(lc->ld,
2593 				LDAP_OPT_ERROR_NUMBER, &stat);
2594 			goto cleanup;
2595 		}
2596 		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2597 		if (stat == 0) {
2598 			stat = LDAP_TIMEOUT;
2599 		} else if (stat == -1) {
2600 			(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2601 						&stat);
2602 		} else {
2603 			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2604 				NULL, NULL, NULL, 0);
2605 			if (stat == LDAP_SUCCESS)
2606 				stat = lderr;
2607 		}
2608 	}
2609 
2610 cleanup:
2611 	if (stat != LDAP_SUCCESS) {
2612 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
2613 			"ldap_add(0x%x (%s), \"%s\") => %d (%s)\n",
2614 			lc != NULL ? lc->ld : 0,
2615 			lc != NULL ? NIL(lc->sp) : "nil",
2616 			dn, stat, ldap_err2string(stat));
2617 	}
2618 
2619 	releaseCon(lc, stat);
2620 	freeLdapMod(mods);
2621 	if (msg != 0)
2622 		(void) ldap_msgfree(msg);
2623 
2624 	return (stat);
2625 }
2626 
2627 /*
2628  * Change the entry at 'oldDn' to have the new DN (not RDN) 'dn'.
2629  * Returns an LDAP error status.
2630  */
2631 int
2632 ldapChangeDN(char *oldDn, char *dn) {
2633 	int			stat;
2634 	__nis_ldap_conn_t	*lc;
2635 	int			i, j, lo, ln;
2636 	char			*rdn;
2637 	int			msgid;
2638 	int			lderr;
2639 	struct timeval		tv;
2640 	LDAPMessage		*msg = 0;
2641 	char			**referralsp = NULL;
2642 	char			*myself = "ldapChangeDN";
2643 
2644 	if ((lo = slen(oldDn)) <= 0 || (ln = slen(dn)) <= 0)
2645 		return (LDAP_PARAM_ERROR);
2646 
2647 	if (strcasecmp(oldDn, dn) == 0)
2648 		return (LDAP_SUCCESS);
2649 
2650 	if ((lc = findCon(&stat)) == 0)
2651 		return (stat);
2652 
2653 	rdn = sdup(myself, T, dn);
2654 	if (rdn == 0) {
2655 		releaseCon(lc, LDAP_SUCCESS);
2656 		return (LDAP_NO_MEMORY);
2657 	}
2658 
2659 	/* Compare old and new DN from the end */
2660 	for (i = lo-1, j = ln-1; i >= 0 && j >= 0; i--, j--) {
2661 		if (tolower(oldDn[i]) != tolower(rdn[j])) {
2662 			/*
2663 			 * Terminate 'rdn' after this character in order
2664 			 * to snip off the portion of the new DN that is
2665 			 * the same as the old DN. What remains in 'rdn'
2666 			 * is the relative DN.
2667 			 */
2668 			rdn[j+1] = '\0';
2669 			break;
2670 		}
2671 	}
2672 
2673 	stat = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL, &msgid);
2674 
2675 	if (msgid != -1) {
2676 		tv = lc->modifyTimeout;
2677 		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2678 		if (stat == 0) {
2679 			stat = LDAP_TIMEOUT;
2680 		} else if (stat == -1) {
2681 			(void) ldap_get_option(lc->ld,
2682 				LDAP_OPT_ERROR_NUMBER, &stat);
2683 		} else {
2684 			stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
2685 				NULL, &referralsp, NULL, 0);
2686 			if (stat == LDAP_SUCCESS)
2687 				stat = lderr;
2688 			stat = ldap_result2error(lc->ld, msg, 0);
2689 		}
2690 	} else {
2691 		(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
2692 			&stat);
2693 	}
2694 	if (proxyInfo.follow_referral == follow &&
2695 			stat == LDAP_REFERRAL && referralsp != NULL) {
2696 		releaseCon(lc, stat);
2697 		if (msg != NULL)
2698 			(void) ldap_msgfree(msg);
2699 		msg = NULL;
2700 		lc = findReferralCon(referralsp, &stat);
2701 		ldap_value_free(referralsp);
2702 		referralsp = NULL;
2703 		if (lc == NULL)
2704 			goto cleanup;
2705 		msgid = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL,
2706 			&msgid);
2707 		if (msgid == -1) {
2708 			(void) ldap_get_option(lc->ld,
2709 				LDAP_OPT_ERROR_NUMBER, &stat);
2710 			goto cleanup;
2711 		}
2712 		stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
2713 		if (stat == 0) {
2714 			stat = LDAP_TIMEOUT;
2715 		} else if (stat == -1) {
2716 			(void) ldap_get_option(lc->ld,
2717 				LDAP_OPT_ERROR_NUMBER, &stat);
2718 		} else {
2719 			stat = ldap_parse_result(lc->ld, msg, &lderr,
2720 				NULL, NULL, NULL, NULL, 0);
2721 			if (stat == LDAP_SUCCESS)
2722 				stat = lderr;
2723 		}
2724 	}
2725 
2726 cleanup:
2727 	if (msg != NULL)
2728 		(void) ldap_msgfree(msg);
2729 
2730 #if	1
2731 	fprintf(stderr, "%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s\n",
2732 		myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
2733 		ldap_err2string(stat));
2734 	logmsg(MSG_NOTIMECHECK, LOG_WARNING,
2735 		"%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s",
2736 		myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
2737 		ldap_err2string(stat));
2738 #endif
2739 
2740 	if (stat == LDAP_NO_SUCH_OBJECT) {
2741 		/*
2742 		 * Fine from our point of view, since all we want to do
2743 		 * is to make sure that an update to the new DN doesn't
2744 		 * leave the old entry around.
2745 		 */
2746 		stat = LDAP_SUCCESS;
2747 	}
2748 
2749 	releaseCon(lc, stat);
2750 	sfree(rdn);
2751 
2752 	return (stat);
2753 }
2754