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