xref: /illumos-gate/usr/src/cmd/idmap/idmapd/adutils.c (revision f52e84dfce89ba2ea0ebb6a191b4466da3012efd)
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 /*
23  * Copyright 2008 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 /*
30  * Processes name2sid & sid2name batched lookups for a given user or
31  * computer from an AD Directory server using GSSAPI authentication
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <alloca.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <lber.h>
40 #include <ldap.h>
41 #include <sasl/sasl.h>
42 #include <string.h>
43 #include <ctype.h>
44 #include <pthread.h>
45 #include <synch.h>
46 #include <atomic.h>
47 #include <errno.h>
48 #include <assert.h>
49 #include <limits.h>
50 #include <sys/u8_textprep.h>
51 #include "nldaputils.h"
52 #include "idmapd.h"
53 
54 /*
55  * Internal data structures for this code
56  */
57 
58 /* Attribute names and filter format strings */
59 #define	SAN		"sAMAccountName"
60 #define	OBJSID		"objectSid"
61 #define	OBJCLASS	"objectClass"
62 #define	SANFILTER	"(sAMAccountName=%.*s)"
63 #define	OBJSIDFILTER	"(objectSid=%s)"
64 
65 /*
66  * This should really be in some <sys/sid.h> file or so; we have a
67  * private version of sid_t, and so must other components of ON until we
68  * rationalize this.
69  */
70 typedef struct sid {
71 	uchar_t		version;
72 	uchar_t		sub_authority_count;
73 	uint64_t	authority;  /* really, 48-bits */
74 	rid_t		sub_authorities[SID_MAX_SUB_AUTHORITIES];
75 } sid_t;
76 
77 /* A single DS */
78 typedef struct ad_host {
79 	struct ad_host		*next;
80 	ad_t			*owner;		/* ad_t to which this belongs */
81 	pthread_mutex_t		lock;
82 	LDAP			*ld;		/* LDAP connection */
83 	uint32_t		ref;		/* ref count */
84 	time_t			idletime;	/* time since last activity */
85 	int			dead;		/* error on LDAP connection */
86 	/*
87 	 * Used to distinguish between different instances of LDAP
88 	 * connections to this same DS.  We need this so we never mix up
89 	 * results for a given msgID from one connection with those of
90 	 * another earlier connection where two batch state structures
91 	 * share this ad_host object but used different LDAP connections
92 	 * to send their LDAP searches.
93 	 */
94 	uint64_t		generation;
95 
96 	/* LDAP DS info */
97 	char			*host;
98 	int			port;
99 
100 	/* hardwired to SASL GSSAPI only for now */
101 	char			*saslmech;
102 	unsigned		saslflags;
103 
104 	/* Number of outstanding search requests */
105 	uint32_t		max_requests;
106 	uint32_t		num_requests;
107 } ad_host_t;
108 
109 /* A set of DSs for a given AD partition; ad_t typedef comes from  adutil.h */
110 struct ad {
111 	char			*dflt_w2k_dom;	/* used to qualify bare names */
112 	pthread_mutex_t		lock;
113 	uint32_t		ref;
114 	ad_host_t		*last_adh;
115 	idmap_ad_partition_t	partition;	/* Data or global catalog? */
116 };
117 
118 /*
119  * A place to put the results of a batched (async) query
120  *
121  * There is one of these for every query added to a batch object
122  * (idmap_query_state, see below).
123  */
124 typedef struct idmap_q {
125 	/*
126 	 * data used for validating search result entries for name->SID
127 	 * lookups
128 	 */
129 	char			*ecanonname;	/* expected canon name */
130 	char			*edomain;	/* expected domain name */
131 	int			eunixtype;	/* expected unix type */
132 	/* results */
133 	char			**canonname;	/* actual canon name */
134 	char			**domain;	/* name of domain of object */
135 	char			**sid;		/* stringified SID */
136 	rid_t			*rid;		/* RID */
137 	int			*sid_type;	/* user or group SID? */
138 	char			**unixname;	/* unixname for name mapping */
139 	char			**dn;		/* DN of entry */
140 	char			**attr;		/* Attr for name mapping */
141 	char			**value;	/* value for name mapping */
142 	idmap_retcode		*rc;
143 
144 	/* lookup state */
145 	int			msgid;		/* LDAP message ID */
146 	/*
147 	 * The LDAP search entry result is placed here to be processed
148 	 * when the search done result is received.
149 	 */
150 	LDAPMessage		*search_res;	/* The LDAP search result */
151 } idmap_q_t;
152 
153 /* Batch context structure; typedef is in header file */
154 struct idmap_query_state {
155 	idmap_query_state_t	*next;
156 	int			qcount;		/* how many queries */
157 	int			ref_cnt;	/* reference count */
158 	pthread_cond_t		cv;		/* Condition wait variable */
159 	uint32_t		qlastsent;
160 	uint32_t		qinflight;	/* how many queries in flight */
161 	uint16_t		qdead;		/* oops, lost LDAP connection */
162 	ad_host_t		*qadh;		/* LDAP connection */
163 	uint64_t		qadh_gen;	/* same as qadh->generation */
164 	const char		*ad_unixuser_attr;
165 	const char		*ad_unixgroup_attr;
166 	idmap_q_t		queries[1];	/* array of query results */
167 };
168 
169 /*
170  * List of query state structs -- needed so we can "route" LDAP results
171  * to the right context if multiple threads should be using the same
172  * connection concurrently
173  */
174 static idmap_query_state_t	*qstatehead = NULL;
175 static pthread_mutex_t		qstatelock = PTHREAD_MUTEX_INITIALIZER;
176 
177 /*
178  * List of DSs, needed by the idle connection reaper thread
179  */
180 static ad_host_t	*host_head = NULL;
181 static pthread_t	reaperid = 0;
182 static pthread_mutex_t	adhostlock = PTHREAD_MUTEX_INITIALIZER;
183 
184 
185 static void
186 idmap_lookup_unlock_batch(idmap_query_state_t **state);
187 
188 static void
189 delete_ds(ad_t *ad, const char *host, int port);
190 
191 /*ARGSUSED*/
192 static int
193 idmap_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
194 {
195 	sasl_interact_t	*interact;
196 
197 	if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
198 		return (LDAP_PARAM_ERROR);
199 
200 	/* There should be no extra arguemnts for SASL/GSSAPI authentication */
201 	for (interact = prompts; interact->id != SASL_CB_LIST_END;
202 	    interact++) {
203 		interact->result = NULL;
204 		interact->len = 0;
205 	}
206 	return (LDAP_SUCCESS);
207 }
208 
209 
210 /*
211  * Turn "dc=foo,dc=bar,dc=com" into "foo.bar.com"; ignores any other
212  * attributes (CN, etc...).  We don't need the reverse, for now.
213  */
214 static
215 char *
216 dn2dns(const char *dn)
217 {
218 	char **rdns = NULL;
219 	char **attrs = NULL;
220 	char **labels = NULL;
221 	char *dns = NULL;
222 	char **rdn, **attr, **label;
223 	int maxlabels = 5;
224 	int nlabels = 0;
225 	int dnslen;
226 
227 	/*
228 	 * There is no reverse of ldap_dns_to_dn() in our libldap, so we
229 	 * have to do the hard work here for now.
230 	 */
231 
232 	/*
233 	 * This code is much too liberal: it looks for "dc" attributes
234 	 * in all RDNs of the DN.  In theory this could cause problems
235 	 * if people were to use "dc" in nodes other than the root of
236 	 * the tree, but in practice noone, least of all Active
237 	 * Directory, does that.
238 	 *
239 	 * On the other hand, this code is much too conservative: it
240 	 * does not make assumptions about ldap_explode_dn(), and _that_
241 	 * is the true for looking at every attr of every RDN.
242 	 *
243 	 * Since we only ever look at dc and those must be DNS labels,
244 	 * at least until we get around to supporting IDN here we
245 	 * shouldn't see escaped labels from AD nor from libldap, though
246 	 * the spec (RFC2253) does allow libldap to escape things that
247 	 * don't need escaping -- if that should ever happen then
248 	 * libldap will need a spanking, and we can take care of that.
249 	 */
250 
251 	/* Explode a DN into RDNs */
252 	if ((rdns = ldap_explode_dn(dn, 0)) == NULL)
253 		return (NULL);
254 
255 	labels = calloc(maxlabels + 1, sizeof (char *));
256 	label = labels;
257 
258 	for (rdn = rdns; *rdn != NULL; rdn++) {
259 		if (attrs != NULL)
260 			ldap_value_free(attrs);
261 
262 		/* Explode each RDN, look for DC attr, save val as DNS label */
263 		if ((attrs = ldap_explode_rdn(rdn[0], 0)) == NULL)
264 			goto done;
265 
266 		for (attr = attrs; *attr != NULL; attr++) {
267 			if (strncasecmp(*attr, "dc=", 3) != 0)
268 				continue;
269 
270 			/* Found a DNS label */
271 			labels[nlabels++] = strdup((*attr) + 3);
272 
273 			if (nlabels == maxlabels) {
274 				char **tmp;
275 				tmp = realloc(labels,
276 				    sizeof (char *) * (maxlabels + 1));
277 
278 				if (tmp == NULL)
279 					goto done;
280 
281 				labels = tmp;
282 				labels[nlabels] = NULL;
283 			}
284 
285 			/* There should be just one DC= attr per-RDN */
286 			break;
287 		}
288 	}
289 
290 	/*
291 	 * Got all the labels, now join with '.'
292 	 *
293 	 * We need room for nlabels - 1 periods ('.'), one nul
294 	 * terminator, and the strlen() of each label.
295 	 */
296 	dnslen = nlabels;
297 	for (label = labels; *label != NULL; label++)
298 		dnslen += strlen(*label);
299 
300 	if ((dns = malloc(dnslen)) == NULL)
301 		goto done;
302 
303 	*dns = '\0';
304 
305 	for (label = labels; *label != NULL; label++) {
306 		(void) strlcat(dns, *label, dnslen);
307 		/*
308 		 * NOTE: the last '.' won't be appended -- there's no room
309 		 * for it!
310 		 */
311 		(void) strlcat(dns, ".", dnslen);
312 	}
313 
314 done:
315 	if (labels != NULL) {
316 		for (label = labels; *label != NULL; label++)
317 			free(*label);
318 		free(labels);
319 	}
320 	if (attrs != NULL)
321 		ldap_value_free(attrs);
322 	if (rdns != NULL)
323 		ldap_value_free(rdns);
324 
325 	return (dns);
326 }
327 
328 /*
329  * Keep connection management simple for now, extend or replace later
330  * with updated libsldap code.
331  */
332 #define	ADREAPERSLEEP	60
333 #define	ADCONN_TIME	300
334 
335 /*
336  * Idle connection reaping side of connection management
337  *
338  * Every minute wake up and look for connections that have been idle for
339  * five minutes or more and close them.
340  */
341 /*ARGSUSED*/
342 static
343 void
344 adreaper(void *arg)
345 {
346 	ad_host_t	*adh;
347 	time_t		now;
348 	timespec_t	ts;
349 
350 	ts.tv_sec = ADREAPERSLEEP;
351 	ts.tv_nsec = 0;
352 
353 	for (;;) {
354 		/*
355 		 * nanosleep(3RT) is thead-safe (no SIGALRM) and more
356 		 * portable than usleep(3C)
357 		 */
358 		(void) nanosleep(&ts, NULL);
359 		(void) pthread_mutex_lock(&adhostlock);
360 		now = time(NULL);
361 		for (adh = host_head; adh != NULL; adh = adh->next) {
362 			(void) pthread_mutex_lock(&adh->lock);
363 			if (adh->ref == 0 && adh->idletime != 0 &&
364 			    adh->idletime + ADCONN_TIME < now) {
365 				if (adh->ld) {
366 					(void) ldap_unbind(adh->ld);
367 					adh->ld = NULL;
368 					adh->idletime = 0;
369 					adh->ref = 0;
370 				}
371 			}
372 			(void) pthread_mutex_unlock(&adh->lock);
373 		}
374 		(void) pthread_mutex_unlock(&adhostlock);
375 	}
376 }
377 
378 int
379 idmap_ad_alloc(ad_t **new_ad, const char *default_domain,
380 		idmap_ad_partition_t part)
381 {
382 	ad_t *ad;
383 
384 	*new_ad = NULL;
385 
386 	if ((default_domain == NULL || *default_domain == '\0') &&
387 	    part != IDMAP_AD_GLOBAL_CATALOG)
388 		return (-1);
389 
390 	if ((ad = calloc(1, sizeof (ad_t))) == NULL)
391 		return (-1);
392 
393 	ad->ref = 1;
394 	ad->partition = part;
395 
396 	if (default_domain == NULL)
397 		default_domain = "";
398 
399 	if ((ad->dflt_w2k_dom = strdup(default_domain)) == NULL)
400 		goto err;
401 
402 	if (pthread_mutex_init(&ad->lock, NULL) != 0)
403 		goto err;
404 
405 	*new_ad = ad;
406 
407 	return (0);
408 err:
409 	if (ad->dflt_w2k_dom != NULL)
410 		free(ad->dflt_w2k_dom);
411 	free(ad);
412 	return (-1);
413 }
414 
415 
416 void
417 idmap_ad_free(ad_t **ad)
418 {
419 	ad_host_t *p;
420 	ad_host_t *prev;
421 
422 	if (ad == NULL || *ad == NULL)
423 		return;
424 
425 	(void) pthread_mutex_lock(&(*ad)->lock);
426 
427 	if (atomic_dec_32_nv(&(*ad)->ref) > 0) {
428 		(void) pthread_mutex_unlock(&(*ad)->lock);
429 		*ad = NULL;
430 		return;
431 	}
432 
433 	(void) pthread_mutex_lock(&adhostlock);
434 	prev = NULL;
435 	p = host_head;
436 	while (p != NULL) {
437 		if (p->owner != (*ad)) {
438 			prev = p;
439 			p = p->next;
440 			continue;
441 		} else {
442 			delete_ds((*ad), p->host, p->port);
443 			if (prev == NULL)
444 				p = host_head;
445 			else
446 				p = prev->next;
447 		}
448 	}
449 	(void) pthread_mutex_unlock(&adhostlock);
450 
451 	(void) pthread_mutex_unlock(&(*ad)->lock);
452 	(void) pthread_mutex_destroy(&(*ad)->lock);
453 
454 	free((*ad)->dflt_w2k_dom);
455 	free(*ad);
456 
457 	*ad = NULL;
458 }
459 
460 
461 static
462 int
463 idmap_open_conn(ad_host_t *adh, int timeoutsecs)
464 {
465 	int zero = 0;
466 	int ldversion, rc;
467 	int timeoutms = timeoutsecs * 1000;
468 
469 	if (adh == NULL)
470 		return (0);
471 
472 	(void) pthread_mutex_lock(&adh->lock);
473 
474 	if (!adh->dead && adh->ld != NULL)
475 		/* done! */
476 		goto out;
477 
478 	if (adh->ld != NULL) {
479 		(void) ldap_unbind(adh->ld);
480 		adh->ld = NULL;
481 	}
482 	adh->num_requests = 0;
483 
484 	atomic_inc_64(&adh->generation);
485 
486 	/* Open and bind an LDAP connection */
487 	adh->ld = ldap_init(adh->host, adh->port);
488 	if (adh->ld == NULL) {
489 		idmapdlog(LOG_INFO, "ldap_init() to server "
490 		    "%s port %d failed. (%s)", adh->host,
491 		    adh->port, strerror(errno));
492 		goto out;
493 	}
494 	ldversion = LDAP_VERSION3;
495 	(void) ldap_set_option(adh->ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion);
496 	(void) ldap_set_option(adh->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
497 	(void) ldap_set_option(adh->ld, LDAP_OPT_TIMELIMIT, &zero);
498 	(void) ldap_set_option(adh->ld, LDAP_OPT_SIZELIMIT, &zero);
499 	(void) ldap_set_option(adh->ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms);
500 	(void) ldap_set_option(adh->ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
501 	rc = ldap_sasl_interactive_bind_s(adh->ld, "" /* binddn */,
502 	    adh->saslmech, NULL, NULL, adh->saslflags, &idmap_saslcallback,
503 	    NULL);
504 
505 	if (rc != LDAP_SUCCESS) {
506 		(void) ldap_unbind(adh->ld);
507 		adh->ld = NULL;
508 		idmapdlog(LOG_INFO, "ldap_sasl_interactive_bind_s() to server "
509 		    "%s port %d failed. (%s)", adh->host, adh->port,
510 		    ldap_err2string(rc));
511 	}
512 
513 	idmapdlog(LOG_DEBUG, "Using global catalog server %s:%d",
514 	    adh->host, adh->port);
515 
516 out:
517 	if (adh->ld != NULL) {
518 		atomic_inc_32(&adh->ref);
519 		adh->idletime = time(NULL);
520 		adh->dead = 0;
521 		(void) pthread_mutex_unlock(&adh->lock);
522 		return (1);
523 	}
524 
525 	(void) pthread_mutex_unlock(&adh->lock);
526 	return (0);
527 }
528 
529 
530 /*
531  * Connection management: find an open connection or open one
532  */
533 static
534 ad_host_t *
535 idmap_get_conn(ad_t *ad)
536 {
537 	ad_host_t	*adh = NULL;
538 	int		tries;
539 	int		dscount = 0;
540 	int		timeoutsecs = IDMAPD_LDAP_OPEN_TIMEOUT;
541 
542 retry:
543 	(void) pthread_mutex_lock(&adhostlock);
544 
545 	if (host_head == NULL) {
546 		(void) pthread_mutex_unlock(&adhostlock);
547 		goto out;
548 	}
549 
550 	if (dscount == 0) {
551 		/*
552 		 * First try: count the number of DSes.
553 		 *
554 		 * Integer overflow is not an issue -- we can't have so many
555 		 * DSes because they won't fit even DNS over TCP, and SMF
556 		 * shouldn't let you set so many.
557 		 */
558 		for (adh = host_head, tries = 0; adh != NULL; adh = adh->next) {
559 			if (adh->owner == ad)
560 				dscount++;
561 		}
562 
563 		if (dscount == 0) {
564 			(void) pthread_mutex_unlock(&adhostlock);
565 			goto out;
566 		}
567 
568 		tries = dscount * 3;	/* three tries per-ds */
569 
570 		/*
571 		 * Begin round-robin at the next DS in the list after the last
572 		 * one that we had a connection to, else start with the first
573 		 * DS in the list.
574 		 */
575 		adh = ad->last_adh;
576 	}
577 
578 	/*
579 	 * Round-robin -- pick the next one on the list; if the list
580 	 * changes on us, no big deal, we'll just potentially go
581 	 * around the wrong number of times.
582 	 */
583 	for (;;) {
584 		if (adh != NULL && adh->ld != NULL && !adh->dead)
585 			break;
586 		if (adh == NULL || (adh = adh->next) == NULL)
587 			adh = host_head;
588 		if (adh->owner == ad)
589 			break;
590 	}
591 
592 	ad->last_adh = adh;
593 	(void) pthread_mutex_unlock(&adhostlock);
594 
595 
596 	/* Found suitable DS, open it if not already opened */
597 	if (idmap_open_conn(adh, timeoutsecs))
598 		return (adh);
599 
600 	tries--;
601 
602 	if ((tries % dscount) == 0)
603 		timeoutsecs *= 2;
604 
605 	if (tries > 0)
606 		goto retry;
607 
608 out:
609 	idmapdlog(LOG_NOTICE, "Couldn't open an LDAP connection to any global "
610 	    "catalog server!");
611 
612 	return (NULL);
613 }
614 
615 static
616 void
617 idmap_release_conn(ad_host_t *adh)
618 {
619 	(void) pthread_mutex_lock(&adh->lock);
620 	if (atomic_dec_32_nv(&adh->ref) == 0)
621 		adh->idletime = time(NULL);
622 	(void) pthread_mutex_unlock(&adh->lock);
623 }
624 
625 /*
626  * Take ad_host_config_t information, create a ad_host_t,
627  * populate it and add it to the list of hosts.
628  */
629 
630 int
631 idmap_add_ds(ad_t *ad, const char *host, int port)
632 {
633 	ad_host_t	*p;
634 	ad_host_t	*new = NULL;
635 	int		ret = -1;
636 
637 	if (port == 0)
638 		port = (int)ad->partition;
639 
640 	(void) pthread_mutex_lock(&adhostlock);
641 	for (p = host_head; p != NULL; p = p->next) {
642 		if (p->owner != ad)
643 			continue;
644 
645 		if (strcmp(host, p->host) == 0 && p->port == port) {
646 			/* already added */
647 			ret = 0;
648 			goto err;
649 		}
650 	}
651 
652 	/* add new entry */
653 	new = (ad_host_t *)calloc(1, sizeof (ad_host_t));
654 	if (new == NULL)
655 		goto err;
656 	new->owner = ad;
657 	new->port = port;
658 	new->dead = 0;
659 	new->max_requests = 80;
660 	new->num_requests = 0;
661 	if ((new->host = strdup(host)) == NULL)
662 		goto err;
663 
664 	/* default to SASL GSSAPI only for now */
665 	new->saslflags = LDAP_SASL_INTERACTIVE;
666 	new->saslmech = "GSSAPI";
667 
668 	if ((ret = pthread_mutex_init(&new->lock, NULL)) != 0) {
669 		free(new->host);
670 		new->host = NULL;
671 		errno = ret;
672 		ret = -1;
673 		goto err;
674 	}
675 
676 	/* link in */
677 	new->next = host_head;
678 	host_head = new;
679 
680 	/* Start reaper if it doesn't exist */
681 	if (reaperid == 0)
682 		(void) pthread_create(&reaperid, NULL,
683 		    (void *(*)(void *))adreaper, (void *)NULL);
684 
685 err:
686 	(void) pthread_mutex_unlock(&adhostlock);
687 
688 	if (ret != 0 && new != NULL) {
689 		if (new->host != NULL) {
690 			(void) pthread_mutex_destroy(&new->lock);
691 			free(new->host);
692 		}
693 		free(new);
694 	}
695 
696 	return (ret);
697 }
698 
699 /*
700  * Free a DS configuration.
701  * Caller must lock the adhostlock mutex
702  */
703 static void
704 delete_ds(ad_t *ad, const char *host, int port)
705 {
706 	ad_host_t	**p, *q;
707 
708 	for (p = &host_head; *p != NULL; p = &((*p)->next)) {
709 		if ((*p)->owner != ad || strcmp(host, (*p)->host) != 0 ||
710 		    (*p)->port != port)
711 			continue;
712 		/* found */
713 		if ((*p)->ref > 0)
714 			break;	/* still in use */
715 
716 		q = *p;
717 		*p = (*p)->next;
718 
719 		(void) pthread_mutex_destroy(&q->lock);
720 
721 		if (q->ld)
722 			(void) ldap_unbind(q->ld);
723 		if (q->host)
724 			free(q->host);
725 		free(q);
726 		break;
727 	}
728 
729 }
730 
731 
732 /*
733  * Convert a binary SID in a BerValue to a sid_t
734  */
735 static
736 int
737 idmap_getsid(BerValue *bval, sid_t *sidp)
738 {
739 	int		i, j;
740 	uchar_t		*v;
741 	uint32_t	a;
742 
743 	/*
744 	 * The binary format of a SID is as follows:
745 	 *
746 	 * byte #0: version, always 0x01
747 	 * byte #1: RID count, always <= 0x0f
748 	 * bytes #2-#7: SID authority, big-endian 48-bit unsigned int
749 	 *
750 	 * followed by RID count RIDs, each a little-endian, unsigned
751 	 * 32-bit int.
752 	 */
753 	/*
754 	 * Sanity checks: must have at least one RID, version must be
755 	 * 0x01, and the length must be 8 + rid count * 4
756 	 */
757 	if (bval->bv_len > 8 && bval->bv_val[0] == 0x01 &&
758 	    bval->bv_len == 1 + 1 + 6 + bval->bv_val[1] * 4) {
759 		v = (uchar_t *)bval->bv_val;
760 		sidp->version = v[0];
761 		sidp->sub_authority_count = v[1];
762 		sidp->authority =
763 		    /* big endian -- so start from the left */
764 		    ((u_longlong_t)v[2] << 40) |
765 		    ((u_longlong_t)v[3] << 32) |
766 		    ((u_longlong_t)v[4] << 24) |
767 		    ((u_longlong_t)v[5] << 16) |
768 		    ((u_longlong_t)v[6] << 8) |
769 		    (u_longlong_t)v[7];
770 		for (i = 0; i < sidp->sub_authority_count; i++) {
771 			j = 8 + (i * 4);
772 			/* little endian -- so start from the right */
773 			a = (v[j + 3] << 24) | (v[j + 2] << 16) |
774 			    (v[j + 1] << 8) | (v[j]);
775 			sidp->sub_authorities[i] = a;
776 		}
777 		return (0);
778 	}
779 	return (-1);
780 }
781 
782 /*
783  * Convert a sid_t to S-1-...
784  */
785 static
786 char *
787 idmap_sid2txt(sid_t *sidp)
788 {
789 	int	rlen, i, len;
790 	char	*str, *cp;
791 
792 	if (sidp->version != 1)
793 		return (NULL);
794 
795 	len = sizeof ("S-1-") - 1;
796 
797 	/*
798 	 * We could optimize like so, but, why?
799 	 *	if (sidp->authority < 10)
800 	 *		len += 2;
801 	 *	else if (sidp->authority < 100)
802 	 *		len += 3;
803 	 *	else
804 	 *		len += snprintf(NULL, 0"%llu", sidp->authority);
805 	 */
806 	len += snprintf(NULL, 0, "%llu", sidp->authority);
807 
808 	/* Max length of a uint32_t printed out in ASCII is 10 bytes */
809 	len += 1 + (sidp->sub_authority_count + 1) * 10;
810 
811 	if ((cp = str = malloc(len)) == NULL)
812 		return (NULL);
813 
814 	rlen = snprintf(str, len, "S-1-%llu", sidp->authority);
815 
816 	cp += rlen;
817 	len -= rlen;
818 
819 	for (i = 0; i < sidp->sub_authority_count; i++) {
820 		assert(len > 0);
821 		rlen = snprintf(cp, len, "-%u", sidp->sub_authorities[i]);
822 		cp += rlen;
823 		len -= rlen;
824 		assert(len >= 0);
825 	}
826 
827 	return (str);
828 }
829 
830 /*
831  * Convert a sid_t to on-the-wire encoding
832  */
833 static
834 int
835 idmap_sid2binsid(sid_t *sid, uchar_t *binsid, int binsidlen)
836 {
837 	uchar_t		*p;
838 	int		i;
839 	uint64_t	a;
840 	uint32_t	r;
841 
842 	if (sid->version != 1 ||
843 	    binsidlen != (1 + 1 + 6 + sid->sub_authority_count * 4))
844 		return (-1);
845 
846 	p = binsid;
847 	*p++ = 0x01;		/* version */
848 	/* sub authority count */
849 	*p++ = sid->sub_authority_count;
850 	/* Authority */
851 	a = sid->authority;
852 	/* big-endian -- start from left */
853 	*p++ = (a >> 40) & 0xFF;
854 	*p++ = (a >> 32) & 0xFF;
855 	*p++ = (a >> 24) & 0xFF;
856 	*p++ = (a >> 16) & 0xFF;
857 	*p++ = (a >> 8) & 0xFF;
858 	*p++ = a & 0xFF;
859 
860 	/* sub-authorities */
861 	for (i = 0; i < sid->sub_authority_count; i++) {
862 		r = sid->sub_authorities[i];
863 		/* little-endian -- start from right */
864 		*p++ = (r & 0x000000FF);
865 		*p++ = (r & 0x0000FF00) >> 8;
866 		*p++ = (r & 0x00FF0000) >> 16;
867 		*p++ = (r & 0xFF000000) >> 24;
868 	}
869 
870 	return (0);
871 }
872 
873 /*
874  * Convert a stringified SID (S-1-...) into a hex-encoded version of the
875  * on-the-wire encoding, but with each pair of hex digits pre-pended
876  * with a '\', so we can pass this to libldap.
877  */
878 static
879 int
880 idmap_txtsid2hexbinsid(const char *txt, const rid_t *rid,
881 	char *hexbinsid, int hexbinsidlen)
882 {
883 	sid_t		sid = { 0 };
884 	int		i, j;
885 	const char	*cp;
886 	char		*ecp;
887 	u_longlong_t	a;
888 	unsigned long	r;
889 	uchar_t		*binsid, b, hb;
890 
891 	/* Only version 1 SIDs please */
892 	if (strncmp(txt, "S-1-", strlen("S-1-")) != 0)
893 		return (-1);
894 
895 	if (strlen(txt) < (strlen("S-1-") + 1))
896 		return (-1);
897 
898 	/* count '-'s */
899 	for (j = 0, cp = strchr(txt, '-');
900 	    cp != NULL && *cp != '\0';
901 	    j++, cp = strchr(cp + 1, '-')) {
902 		/* can't end on a '-' */
903 		if (*(cp + 1) == '\0')
904 			return (-1);
905 	}
906 
907 	/* Adjust count for version and authority */
908 	j -= 2;
909 
910 	/* we know the version number and RID count */
911 	sid.version = 1;
912 	sid.sub_authority_count = (rid != NULL) ? j + 1 : j;
913 
914 	/* must have at least one RID, but not too many */
915 	if (sid.sub_authority_count < 1 ||
916 	    sid.sub_authority_count > SID_MAX_SUB_AUTHORITIES)
917 		return (-1);
918 
919 	/* check that we only have digits and '-' */
920 	if (strspn(txt + 1, "0123456789-") < (strlen(txt) - 1))
921 		return (-1);
922 
923 	cp = txt + strlen("S-1-");
924 
925 	/* 64-bit safe parsing of unsigned 48-bit authority value */
926 	errno = 0;
927 	a = strtoull(cp, &ecp, 10);
928 
929 	/* errors parsing the authority or too many bits */
930 	if (cp == ecp || (a == 0 && errno == EINVAL) ||
931 	    (a == ULLONG_MAX && errno == ERANGE) ||
932 	    (a & 0x0000ffffffffffffULL) != a)
933 		return (-1);
934 
935 	cp = ecp;
936 
937 	sid.authority = (uint64_t)a;
938 
939 	for (i = 0; i < j; i++) {
940 		if (*cp++ != '-')
941 			return (-1);
942 		/* 64-bit safe parsing of unsigned 32-bit RID */
943 		errno = 0;
944 		r = strtoul(cp, &ecp, 10);
945 		/* errors parsing the RID or too many bits */
946 		if (cp == ecp || (r == 0 && errno == EINVAL) ||
947 		    (r == ULONG_MAX && errno == ERANGE) ||
948 		    (r & 0xffffffffUL) != r)
949 			return (-1);
950 		sid.sub_authorities[i] = (uint32_t)r;
951 		cp = ecp;
952 	}
953 
954 	/* check that all of the string SID has been consumed */
955 	if (*cp != '\0')
956 		return (-1);
957 
958 	if (rid != NULL)
959 		sid.sub_authorities[j] = *rid;
960 
961 	j = 1 + 1 + 6 + sid.sub_authority_count * 4;
962 
963 	if (hexbinsidlen < (j * 3))
964 		return (-2);
965 
966 	/* binary encode the SID */
967 	binsid = (uchar_t *)alloca(j);
968 	(void) idmap_sid2binsid(&sid, binsid, j);
969 
970 	/* hex encode, with a backslash before each byte */
971 	for (ecp = hexbinsid, i = 0; i < j; i++) {
972 		b = binsid[i];
973 		*ecp++ = '\\';
974 		hb = (b >> 4) & 0xF;
975 		*ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A');
976 		hb = b & 0xF;
977 		*ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A');
978 	}
979 	*ecp = '\0';
980 
981 	return (0);
982 }
983 
984 static
985 char *
986 convert_bval2sid(BerValue *bval, rid_t *rid)
987 {
988 	sid_t	sid;
989 
990 	if (idmap_getsid(bval, &sid) < 0)
991 		return (NULL);
992 
993 	/*
994 	 * If desired and if the SID is what should be a domain/computer
995 	 * user or group SID (i.e., S-1-5-w-x-y-z-<user/group RID>) then
996 	 * save the last RID and truncate the SID
997 	 */
998 	if (rid != NULL && sid.authority == 5 && sid.sub_authority_count == 5)
999 		*rid = sid.sub_authorities[--sid.sub_authority_count];
1000 	return (idmap_sid2txt(&sid));
1001 }
1002 
1003 
1004 idmap_retcode
1005 idmap_lookup_batch_start(ad_t *ad, int nqueries, idmap_query_state_t **state)
1006 {
1007 	idmap_query_state_t *new_state;
1008 	ad_host_t	*adh = NULL;
1009 
1010 	*state = NULL;
1011 
1012 	if (ad == NULL)
1013 		return (IDMAP_ERR_INTERNAL);
1014 
1015 	adh = idmap_get_conn(ad);
1016 	if (adh == NULL)
1017 		return (IDMAP_ERR_RETRIABLE_NET_ERR);
1018 
1019 	new_state = calloc(1, sizeof (idmap_query_state_t) +
1020 	    (nqueries - 1) * sizeof (idmap_q_t));
1021 
1022 	if (new_state == NULL)
1023 		return (IDMAP_ERR_MEMORY);
1024 
1025 	new_state->ref_cnt = 1;
1026 	new_state->qadh = adh;
1027 	new_state->qcount = nqueries;
1028 	new_state->qadh_gen = adh->generation;
1029 	/* should be -1, but the atomic routines want unsigned */
1030 	new_state->qlastsent = 0;
1031 	(void) pthread_cond_init(&new_state->cv, NULL);
1032 
1033 	(void) pthread_mutex_lock(&qstatelock);
1034 	new_state->next = qstatehead;
1035 	qstatehead = new_state;
1036 	(void) pthread_mutex_unlock(&qstatelock);
1037 
1038 	*state = new_state;
1039 
1040 	return (IDMAP_SUCCESS);
1041 }
1042 
1043 /*
1044  * Set unixuser_attr and unixgroup_attr for AD-based name mapping
1045  */
1046 void
1047 idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
1048 		const char *unixuser_attr, const char *unixgroup_attr)
1049 {
1050 	state->ad_unixuser_attr = unixuser_attr;
1051 	state->ad_unixgroup_attr = unixgroup_attr;
1052 }
1053 
1054 /*
1055  * Find the idmap_query_state_t to which a given LDAP result msgid on a
1056  * given connection belongs. This routine increaments the reference count
1057  * so that the object can not be freed. idmap_lookup_unlock_batch()
1058  * must be called to decreament the reference count.
1059  */
1060 static
1061 int
1062 idmap_msgid2query(ad_host_t *adh, int msgid,
1063 	idmap_query_state_t **state, int *qid)
1064 {
1065 	idmap_query_state_t	*p;
1066 	int			i;
1067 	int			ret;
1068 
1069 	(void) pthread_mutex_lock(&qstatelock);
1070 	for (p = qstatehead; p != NULL; p = p->next) {
1071 		if (p->qadh != adh || adh->generation != p->qadh_gen)
1072 			continue;
1073 		for (i = 0; i < p->qcount; i++) {
1074 			if ((p->queries[i]).msgid == msgid) {
1075 				if (!p->qdead) {
1076 					p->ref_cnt++;
1077 					*state = p;
1078 					*qid = i;
1079 					ret = 1;
1080 				} else
1081 					ret = 0;
1082 				(void) pthread_mutex_unlock(&qstatelock);
1083 				return (ret);
1084 			}
1085 		}
1086 	}
1087 	(void) pthread_mutex_unlock(&qstatelock);
1088 	return (0);
1089 }
1090 
1091 /*
1092  * Put the the search result onto the correct idmap_q_t given the LDAP result
1093  * msgid
1094  * Returns:	0 success
1095  *		-1 already has a search result
1096  *		-2 cant find message id
1097  */
1098 static
1099 int
1100 idmap_quesearchresbymsgid(ad_host_t *adh, int msgid, LDAPMessage *search_res)
1101 {
1102 	idmap_query_state_t	*p;
1103 	int			i;
1104 	int			res;
1105 
1106 	(void) pthread_mutex_lock(&qstatelock);
1107 	for (p = qstatehead; p != NULL; p = p->next) {
1108 		if (p->qadh != adh || adh->generation != p->qadh_gen)
1109 			continue;
1110 		for (i = 0; i < p->qcount; i++) {
1111 			if ((p->queries[i]).msgid == msgid) {
1112 				if (p->queries[i].search_res == NULL) {
1113 					if (!p->qdead) {
1114 						p->queries[i].search_res =
1115 						    search_res;
1116 						res = 0;
1117 					} else
1118 						res = -2;
1119 				} else
1120 					res = -1;
1121 				(void) pthread_mutex_unlock(&qstatelock);
1122 				return (res);
1123 			}
1124 		}
1125 	}
1126 	(void) pthread_mutex_unlock(&qstatelock);
1127 	return (-2);
1128 }
1129 
1130 
1131 /*
1132  * Take parsed attribute values from a search result entry and check if
1133  * it is the result that was desired and, if so, set the result fields
1134  * of the given idmap_q_t.
1135  *
1136  * Frees the unused char * values.
1137  */
1138 static
1139 void
1140 idmap_setqresults(idmap_q_t *q, char *san, char *dn, const char *attr,
1141 	char *sid, rid_t rid, int sid_type, char *unixname)
1142 {
1143 	char *domain;
1144 	int err1, err2;
1145 
1146 	assert(dn != NULL);
1147 
1148 	if ((domain = dn2dns(dn)) == NULL)
1149 		goto out;
1150 
1151 	if (q->ecanonname != NULL && san != NULL) {
1152 		/* Check that this is the canonname that we were looking for */
1153 		if (u8_strcmp(q->ecanonname, san, 0,
1154 		    U8_STRCMP_CI_LOWER, /* no normalization, for now */
1155 		    U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
1156 			goto out;
1157 	}
1158 
1159 	if (q->edomain != NULL) {
1160 		/* Check that this is the domain that we were looking for */
1161 		if (u8_strcmp(q->edomain, domain, 0, U8_STRCMP_CI_LOWER,
1162 		    U8_UNICODE_LATEST, &err2) != 0 || err2 != 0)
1163 			goto out;
1164 	}
1165 
1166 	/* Copy the DN and attr and value */
1167 	if (q->dn != NULL)
1168 		*q->dn = strdup(dn);
1169 
1170 	if (q->attr != NULL && attr != NULL)
1171 		*q->attr = strdup(attr);
1172 
1173 	if (q->value != NULL && unixname != NULL)
1174 		*q->value = strdup(unixname);
1175 
1176 	/* Set results */
1177 	if (q->sid) {
1178 		*q->sid = sid;
1179 		sid = NULL;
1180 	}
1181 	if (q->rid)
1182 		*q->rid = rid;
1183 	if (q->sid_type)
1184 		*q->sid_type = sid_type;
1185 	if (q->unixname) {
1186 		*q->unixname = unixname;
1187 		unixname = NULL;
1188 	}
1189 	if (q->domain != NULL) {
1190 		*q->domain = domain;
1191 		domain = NULL;
1192 	}
1193 	if (q->canonname != NULL) {
1194 		/*
1195 		 * The caller may be replacing the given winname by its
1196 		 * canonical name and therefore free any old name before
1197 		 * overwriting the field by the canonical name.
1198 		 */
1199 		free(*q->canonname);
1200 		*q->canonname = san;
1201 		san = NULL;
1202 	}
1203 
1204 	/* Always have q->rc; idmap_extract_object() asserts this */
1205 	*q->rc = IDMAP_SUCCESS;
1206 
1207 out:
1208 	/* Free unused attribute values */
1209 	free(san);
1210 	free(sid);
1211 	free(domain);
1212 	free(unixname);
1213 }
1214 
1215 /*
1216  * The following three functions extract objectSid, sAMAccountName and
1217  * objectClass attribute values and, in the case of objectSid and
1218  * objectClass, parse them.
1219  *
1220  * idmap_setqresults() takes care of dealing with the result entry's DN.
1221  */
1222 
1223 /*
1224  * Return a NUL-terminated stringified SID from the value of an
1225  * objectSid attribute and put the last RID in *rid.
1226  */
1227 static
1228 char *
1229 idmap_bv_objsid2sidstr(BerValue **bvalues, rid_t *rid)
1230 {
1231 	char *sid;
1232 
1233 	if (bvalues == NULL)
1234 		return (NULL);
1235 	/* objectSid is single valued */
1236 	if ((sid = convert_bval2sid(bvalues[0], rid)) == NULL)
1237 		return (NULL);
1238 	return (sid);
1239 }
1240 
1241 /*
1242  * Return a NUL-terminated string from the value of a sAMAccountName
1243  * or unixname attribute.
1244  */
1245 static
1246 char *
1247 idmap_bv_name2str(BerValue **bvalues)
1248 {
1249 	char *s;
1250 
1251 	if (bvalues == NULL || bvalues[0] == NULL ||
1252 	    bvalues[0]->bv_val == NULL)
1253 		return (NULL);
1254 
1255 	if ((s = malloc(bvalues[0]->bv_len + 1)) == NULL)
1256 		return (NULL);
1257 
1258 	(void) snprintf(s, bvalues[0]->bv_len + 1, "%.*s", bvalues[0]->bv_len,
1259 	    bvalues[0]->bv_val);
1260 
1261 	return (s);
1262 }
1263 
1264 
1265 #define	BVAL_CASEEQ(bv, str) \
1266 		(((*(bv))->bv_len == (sizeof (str) - 1)) && \
1267 		    strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
1268 
1269 /*
1270  * Extract the class of the result entry.  Returns 1 on success, 0 on
1271  * failure.
1272  */
1273 static
1274 int
1275 idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
1276 {
1277 	BerValue	**cbval;
1278 
1279 	*sid_type = _IDMAP_T_OTHER;
1280 	if (bvalues == NULL)
1281 		return (0);
1282 
1283 	/*
1284 	 * We iterate over all the values because computer is a
1285 	 * sub-class of user.
1286 	 */
1287 	for (cbval = bvalues; *cbval != NULL; cbval++) {
1288 		if (BVAL_CASEEQ(cbval, "Computer")) {
1289 			*sid_type = _IDMAP_T_COMPUTER;
1290 			break;
1291 		} else if (BVAL_CASEEQ(cbval, "Group")) {
1292 			*sid_type = _IDMAP_T_GROUP;
1293 			break;
1294 		} else if (BVAL_CASEEQ(cbval, "USER")) {
1295 			*sid_type = _IDMAP_T_USER;
1296 			/* Continue looping -- this may be a computer yet */
1297 		}
1298 		/*
1299 		 * "else if (*sid_type = _IDMAP_T_USER)" then this is a
1300 		 * new sub-class of user -- what to do with it??
1301 		 */
1302 	}
1303 
1304 	return (1);
1305 }
1306 
1307 /*
1308  * Handle a given search result entry
1309  */
1310 static
1311 void
1312 idmap_extract_object(idmap_query_state_t *state, int qid, LDAPMessage *res)
1313 {
1314 	BerElement		*ber = NULL;
1315 	BerValue		**bvalues;
1316 	ad_host_t		*adh;
1317 	idmap_q_t		*q;
1318 	char			*attr;
1319 	const char		*unixuser_attr = NULL;
1320 	const char		*unixgroup_attr = NULL;
1321 	char			*unixuser = NULL;
1322 	char			*unixgroup = NULL;
1323 	char			*dn = NULL;
1324 	char			*san = NULL;
1325 	char			*sid = NULL;
1326 	rid_t			rid = 0;
1327 	int			sid_type = _IDMAP_T_UNDEF;
1328 	int			has_class, has_san, has_sid;
1329 	int			has_unixuser, has_unixgroup;
1330 	int			num;
1331 
1332 	adh = state->qadh;
1333 
1334 	(void) pthread_mutex_lock(&adh->lock);
1335 
1336 	q = &(state->queries[qid]);
1337 
1338 	assert(q->rc != NULL);
1339 
1340 	if (adh->dead || (dn = ldap_get_dn(adh->ld, res)) == NULL) {
1341 		num = adh->num_requests;
1342 		(void) pthread_mutex_unlock(&adh->lock);
1343 		idmapdlog(LOG_DEBUG,
1344 		    "AD error decoding search result - %d queued requests",
1345 		    num);
1346 		return;
1347 	}
1348 
1349 	assert(q->domain == NULL || *q->domain == NULL);
1350 
1351 	/*
1352 	 * If the caller has requested unixname then determine the
1353 	 * AD attribute name that will have the unixname.
1354 	 */
1355 	if (q->unixname != NULL) {
1356 		if (q->eunixtype == _IDMAP_T_USER)
1357 			unixuser_attr = state->ad_unixuser_attr;
1358 		else if (q->eunixtype == _IDMAP_T_GROUP)
1359 			unixgroup_attr = state->ad_unixgroup_attr;
1360 		else if (q->eunixtype == _IDMAP_T_UNDEF) {
1361 			/*
1362 			 * This is the case where we don't know
1363 			 * before hand whether we need unixuser
1364 			 * or unixgroup. This will be determined
1365 			 * by the "sid_type" (i.e whether the given
1366 			 * winname is user or group). If sid_type
1367 			 * turns out to be user we will return
1368 			 * unixuser (if found) and if it is a group
1369 			 * we will return unixgroup (if found). We
1370 			 * lookup for both ad_unixuser_attr and
1371 			 * ad_unixgroup_attr and discard one of them
1372 			 * after we know the "sidtype". This
1373 			 * supports the following type of lookups.
1374 			 *
1375 			 * Example:
1376 			 *   $idmap show -c winname:foo
1377 			 * In the above example, idmap will
1378 			 * return uid if winname is winuser
1379 			 * and gid if winname is wingroup.
1380 			 */
1381 			unixuser_attr = state->ad_unixuser_attr;
1382 			unixgroup_attr = state->ad_unixgroup_attr;
1383 		}
1384 	}
1385 
1386 	has_class = has_san = has_sid = has_unixuser = has_unixgroup = 0;
1387 	for (attr = ldap_first_attribute(adh->ld, res, &ber); attr != NULL;
1388 	    attr = ldap_next_attribute(adh->ld, res, ber)) {
1389 		bvalues = NULL;	/* for memory management below */
1390 
1391 		/*
1392 		 * If this is an attribute we are looking for and
1393 		 * haven't seen it yet, parse it
1394 		 */
1395 		if (q->sid != NULL && !has_sid &&
1396 		    strcasecmp(attr, OBJSID) == 0) {
1397 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1398 			sid = idmap_bv_objsid2sidstr(bvalues, &rid);
1399 			has_sid = (sid != NULL);
1400 		} else if (!has_san && strcasecmp(attr, SAN) == 0) {
1401 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1402 			san = idmap_bv_name2str(bvalues);
1403 			has_san = (san != NULL);
1404 		} else if (!has_class && strcasecmp(attr, OBJCLASS) == 0) {
1405 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1406 			has_class = idmap_bv_objclass2sidtype(bvalues,
1407 			    &sid_type);
1408 			if (has_class && q->unixname != NULL &&
1409 			    q->eunixtype == _IDMAP_T_UNDEF) {
1410 				/*
1411 				 * This is the case where we didn't
1412 				 * know whether we wanted unixuser or
1413 				 * unixgroup as described above.
1414 				 * Now since we know the "sid_type"
1415 				 * we discard the unwanted value
1416 				 * if it was retrieved before we
1417 				 * got here.
1418 				 */
1419 				if (sid_type == _IDMAP_T_USER) {
1420 					free(unixgroup);
1421 					unixgroup_attr = unixgroup = NULL;
1422 				} else if (sid_type == _IDMAP_T_GROUP) {
1423 					free(unixuser);
1424 					unixuser_attr = unixuser = NULL;
1425 				} else {
1426 					free(unixuser);
1427 					free(unixgroup);
1428 					unixuser_attr = unixuser = NULL;
1429 					unixgroup_attr = unixgroup = NULL;
1430 				}
1431 			}
1432 		} else if (!has_unixuser && unixuser_attr != NULL &&
1433 		    strcasecmp(attr, unixuser_attr) == 0) {
1434 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1435 			unixuser = idmap_bv_name2str(bvalues);
1436 			has_unixuser = (unixuser != NULL);
1437 
1438 		} else if (!has_unixgroup && unixgroup_attr != NULL &&
1439 		    strcasecmp(attr, unixgroup_attr) == 0) {
1440 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1441 			unixgroup = idmap_bv_name2str(bvalues);
1442 			has_unixgroup = (unixgroup != NULL);
1443 		}
1444 
1445 		if (bvalues != NULL)
1446 			ldap_value_free_len(bvalues);
1447 		ldap_memfree(attr);
1448 
1449 		if (has_class && has_san &&
1450 		    (q->sid == NULL || has_sid) &&
1451 		    (unixuser_attr == NULL || has_unixuser) &&
1452 		    (unixgroup_attr == NULL || has_unixgroup)) {
1453 			/* Got what we need */
1454 			break;
1455 		}
1456 	}
1457 
1458 	(void) pthread_mutex_unlock(&adh->lock);
1459 
1460 	if (!has_class) {
1461 		/*
1462 		 * Didn't find objectclass. Something's wrong with our
1463 		 * AD data.
1464 		 */
1465 		free(san);
1466 		free(sid);
1467 		free(unixuser);
1468 		free(unixgroup);
1469 	} else {
1470 		/*
1471 		 * Either we got what we needed and came out of the loop
1472 		 * early OR we completed the loop in which case we didn't
1473 		 * find some attributes that we were looking for. In either
1474 		 * case set the result with what we got.
1475 		 */
1476 		idmap_setqresults(q, san, dn,
1477 		    (unixuser != NULL) ? unixuser_attr : unixgroup_attr,
1478 		    sid, rid, sid_type,
1479 		    (unixuser != NULL) ? unixuser : unixgroup);
1480 	}
1481 
1482 	if (ber != NULL)
1483 		ber_free(ber, 0);
1484 
1485 	ldap_memfree(dn);
1486 }
1487 
1488 /*
1489  * Try to get a result; if there is one, find the corresponding
1490  * idmap_q_t and process the result.
1491  *
1492  * Returns:	0 success
1493  *		-1 error
1494  *		-2 queue empty
1495  */
1496 static
1497 int
1498 idmap_get_adobject_batch(ad_host_t *adh, struct timeval *timeout)
1499 {
1500 	idmap_query_state_t	*query_state;
1501 	LDAPMessage		*res = NULL;
1502 	int			rc, ret, msgid, qid;
1503 	idmap_q_t		*que;
1504 	int			num;
1505 
1506 	(void) pthread_mutex_lock(&adh->lock);
1507 	if (adh->dead || adh->num_requests == 0) {
1508 		if (adh->dead)
1509 			ret = -1;
1510 		else
1511 			ret = -2;
1512 		(void) pthread_mutex_unlock(&adh->lock);
1513 		return (ret);
1514 	}
1515 
1516 	/* Get one result */
1517 	rc = ldap_result(adh->ld, LDAP_RES_ANY, 0,
1518 	    timeout, &res);
1519 	if ((timeout != NULL && timeout->tv_sec > 0 && rc == LDAP_SUCCESS) ||
1520 	    rc < 0)
1521 		adh->dead = 1;
1522 
1523 	if (rc == LDAP_RES_SEARCH_RESULT && adh->num_requests > 0)
1524 		adh->num_requests--;
1525 
1526 	if (adh->dead) {
1527 		num = adh->num_requests;
1528 		(void) pthread_mutex_unlock(&adh->lock);
1529 		idmapdlog(LOG_DEBUG,
1530 		    "AD ldap_result error - %d queued requests", num);
1531 		return (-1);
1532 	}
1533 	switch (rc) {
1534 	case LDAP_RES_SEARCH_RESULT:
1535 		/* We should have the LDAP replies for some search... */
1536 		msgid = ldap_msgid(res);
1537 		if (idmap_msgid2query(adh, msgid, &query_state, &qid)) {
1538 			(void) pthread_mutex_unlock(&adh->lock);
1539 			que = &(query_state->queries[qid]);
1540 			if (que->search_res != NULL) {
1541 				idmap_extract_object(query_state, qid,
1542 				    que->search_res);
1543 				(void) ldap_msgfree(que->search_res);
1544 				que->search_res = NULL;
1545 			} else
1546 				*que->rc = IDMAP_ERR_NOTFOUND;
1547 			/* ...so we can decrement qinflight */
1548 			atomic_dec_32(&query_state->qinflight);
1549 			idmap_lookup_unlock_batch(&query_state);
1550 		} else {
1551 			num = adh->num_requests;
1552 			(void) pthread_mutex_unlock(&adh->lock);
1553 			idmapdlog(LOG_DEBUG,
1554 			    "AD cannot find message ID  - %d queued requests",
1555 			    num);
1556 		}
1557 		(void) ldap_msgfree(res);
1558 		ret = 0;
1559 		break;
1560 
1561 	case LDAP_RES_SEARCH_REFERENCE:
1562 		/*
1563 		 * We have no need for these at the moment.  Eventually,
1564 		 * when we query things that we can't expect to find in
1565 		 * the Global Catalog then we'll need to learn to follow
1566 		 * references.
1567 		 */
1568 		(void) pthread_mutex_unlock(&adh->lock);
1569 		(void) ldap_msgfree(res);
1570 		ret = 0;
1571 		break;
1572 
1573 	case LDAP_RES_SEARCH_ENTRY:
1574 		/* Got a result - queue it */
1575 		msgid = ldap_msgid(res);
1576 		rc = idmap_quesearchresbymsgid(adh, msgid, res);
1577 		num = adh->num_requests;
1578 		(void) pthread_mutex_unlock(&adh->lock);
1579 		if (rc == -1) {
1580 			idmapdlog(LOG_DEBUG,
1581 			    "AD already has search result - %d queued requests",
1582 			    num);
1583 			(void) ldap_msgfree(res);
1584 		} else if (rc == -2) {
1585 			idmapdlog(LOG_DEBUG,
1586 			    "AD cannot queue by message ID  "
1587 			    "- %d queued requests", num);
1588 			(void) ldap_msgfree(res);
1589 		}
1590 		ret = 0;
1591 		break;
1592 
1593 	default:
1594 		/* timeout or error; treat the same */
1595 		(void) pthread_mutex_unlock(&adh->lock);
1596 		ret = -1;
1597 		break;
1598 	}
1599 
1600 	return (ret);
1601 }
1602 
1603 /*
1604  * This routine decreament the reference count of the
1605  * idmap_query_state_t
1606  */
1607 static void
1608 idmap_lookup_unlock_batch(idmap_query_state_t **state)
1609 {
1610 	/*
1611 	 * Decrement reference count with qstatelock locked
1612 	 */
1613 	(void) pthread_mutex_lock(&qstatelock);
1614 	(*state)->ref_cnt--;
1615 	/*
1616 	 * If there are no references wakup the allocating thread
1617 	 */
1618 	if ((*state)->ref_cnt <= 1)
1619 		(void) pthread_cond_signal(&(*state)->cv);
1620 	(void) pthread_mutex_unlock(&qstatelock);
1621 	*state = NULL;
1622 }
1623 
1624 static
1625 void
1626 idmap_cleanup_batch(idmap_query_state_t *batch)
1627 {
1628 	int i;
1629 
1630 	for (i = 0; i < batch->qcount; i++) {
1631 		if (batch->queries[i].ecanonname != NULL)
1632 			free(batch->queries[i].ecanonname);
1633 		batch->queries[i].ecanonname = NULL;
1634 		if (batch->queries[i].edomain != NULL)
1635 			free(batch->queries[i].edomain);
1636 		batch->queries[i].edomain = NULL;
1637 	}
1638 }
1639 
1640 /*
1641  * This routine frees the idmap_query_state_t structure
1642  * If the reference count is greater than 1 it waits
1643  * for the other threads to finish using it.
1644  */
1645 void
1646 idmap_lookup_release_batch(idmap_query_state_t **state)
1647 {
1648 	idmap_query_state_t **p;
1649 
1650 	/*
1651 	 * Set state to dead to stop further operations.
1652 	 * Wait for reference count with qstatelock locked
1653 	 * to get to one.
1654 	 */
1655 	(void) pthread_mutex_lock(&qstatelock);
1656 	(*state)->qdead = 1;
1657 	while ((*state)->ref_cnt > 1) {
1658 		(void) pthread_cond_wait(&(*state)->cv, &qstatelock);
1659 	}
1660 
1661 	/* Remove this state struct from the list of state structs */
1662 	for (p = &qstatehead; *p != NULL; p = &(*p)->next) {
1663 		if (*p == (*state)) {
1664 			*p = (*state)->next;
1665 			break;
1666 		}
1667 	}
1668 	(void) pthread_mutex_unlock(&qstatelock);
1669 
1670 	idmap_cleanup_batch(*state);
1671 
1672 	(void) pthread_cond_destroy(&(*state)->cv);
1673 
1674 	idmap_release_conn((*state)->qadh);
1675 
1676 	free(*state);
1677 	*state = NULL;
1678 }
1679 
1680 
1681 /*
1682  * This routine waits for other threads using the
1683  * idmap_query_state_t structure to finish.
1684  * If the reference count is greater than 1 it waits
1685  * for the other threads to finish using it.
1686  */
1687 static
1688 void
1689 idmap_lookup_wait_batch(idmap_query_state_t *state)
1690 {
1691 	/*
1692 	 * Set state to dead to stop further operation.
1693 	 * stating.
1694 	 * Wait for reference count to get to one
1695 	 * with qstatelock locked.
1696 	 */
1697 	(void) pthread_mutex_lock(&qstatelock);
1698 	state->qdead = 1;
1699 	while (state->ref_cnt > 1) {
1700 		(void) pthread_cond_wait(&state->cv, &qstatelock);
1701 	}
1702 	(void) pthread_mutex_unlock(&qstatelock);
1703 }
1704 
1705 
1706 idmap_retcode
1707 idmap_lookup_batch_end(idmap_query_state_t **state)
1708 {
1709 	int		    rc = LDAP_SUCCESS;
1710 	idmap_retcode	    retcode = IDMAP_SUCCESS;
1711 	struct timeval	    timeout;
1712 
1713 	timeout.tv_sec = IDMAPD_SEARCH_TIMEOUT;
1714 	timeout.tv_usec = 0;
1715 
1716 	/* Process results until done or until timeout, if given */
1717 	while ((*state)->qinflight > 0) {
1718 		if ((rc = idmap_get_adobject_batch((*state)->qadh,
1719 		    &timeout)) != 0)
1720 			break;
1721 	}
1722 	(*state)->qdead = 1;
1723 	/* Wait for other threads proceesing search result to finish */
1724 	idmap_lookup_wait_batch(*state);
1725 
1726 	if (rc == -1 || (*state)->qinflight != 0)
1727 		retcode = IDMAP_ERR_RETRIABLE_NET_ERR;
1728 
1729 	idmap_lookup_release_batch(state);
1730 
1731 	return (retcode);
1732 }
1733 
1734 /*
1735  * Send one prepared search, queue up msgid, process what results are
1736  * available
1737  */
1738 static
1739 idmap_retcode
1740 idmap_batch_add1(idmap_query_state_t *state, const char *filter,
1741 	char *ecanonname, char *edomain, int eunixtype,
1742 	char **dn, char **attr, char **value,
1743 	char **canonname, char **dname,
1744 	char **sid, rid_t *rid, int *sid_type, char **unixname,
1745 	idmap_retcode *rc)
1746 {
1747 	idmap_retcode	retcode = IDMAP_SUCCESS;
1748 	int		lrc, qid, i;
1749 	int		num;
1750 	int		dead;
1751 	struct timeval	tv;
1752 	idmap_q_t	*q;
1753 	static char	*attrs[] = {
1754 		SAN,
1755 		OBJSID,
1756 		OBJCLASS,
1757 		NULL,	/* placeholder for unixname attr */
1758 		NULL,	/* placeholder for unixname attr */
1759 		NULL
1760 	};
1761 
1762 	qid = atomic_inc_32_nv(&state->qlastsent) - 1;
1763 
1764 	q = &(state->queries[qid]);
1765 
1766 	/*
1767 	 * Remember the expected canonname so we can check the results
1768 	 * agains it
1769 	 */
1770 	q->ecanonname = ecanonname;
1771 	q->edomain = edomain;
1772 	q->eunixtype = eunixtype;
1773 
1774 	/* Remember where to put the results */
1775 	q->canonname = canonname;
1776 	q->sid = sid;
1777 	q->domain = dname;
1778 	q->rid = rid;
1779 	q->sid_type = sid_type;
1780 	q->rc = rc;
1781 	q->unixname = unixname;
1782 	q->dn = dn;
1783 	q->attr = attr;
1784 	q->value = value;
1785 
1786 	/* Add unixuser/unixgroup attribute names to the attrs list */
1787 	if (unixname != NULL) {
1788 		i = 3;
1789 		if (eunixtype != _IDMAP_T_GROUP &&
1790 		    state->ad_unixuser_attr != NULL)
1791 			attrs[i++] = (char *)state->ad_unixuser_attr;
1792 		if (eunixtype != _IDMAP_T_USER &&
1793 		    state->ad_unixgroup_attr != NULL)
1794 			attrs[i] = (char *)state->ad_unixgroup_attr;
1795 	}
1796 
1797 	/*
1798 	 * Provide sane defaults for the results in case we never hear
1799 	 * back from the DS before closing the connection.
1800 	 *
1801 	 * In particular we default the result to indicate a retriable
1802 	 * error.  The first complete matching result entry will cause
1803 	 * this to be set to IDMAP_SUCCESS, and the end of the results
1804 	 * for this search will cause this to indicate "not found" if no
1805 	 * result entries arrived or no complete ones matched the lookup
1806 	 * we were doing.
1807 	 */
1808 	*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
1809 	if (sid_type != NULL)
1810 		*sid_type = _IDMAP_T_OTHER;
1811 	if (sid != NULL)
1812 		*sid = NULL;
1813 	if (dname != NULL)
1814 		*dname = NULL;
1815 	if (rid != NULL)
1816 		*rid = 0;
1817 	if (dn != NULL)
1818 		*dn = NULL;
1819 	if (attr != NULL)
1820 		*attr = NULL;
1821 	if (value != NULL)
1822 		*value = NULL;
1823 
1824 	/* Check the number of queued requests first */
1825 	tv.tv_sec = IDMAPD_SEARCH_TIMEOUT;
1826 	tv.tv_usec = 0;
1827 	while (!state->qadh->dead &&
1828 	    state->qadh->num_requests > state->qadh->max_requests) {
1829 		if (idmap_get_adobject_batch(state->qadh, &tv) != 0)
1830 			break;
1831 	}
1832 
1833 	/*
1834 	 * Don't set *canonname to NULL because it may be pointing to the
1835 	 * given winname. Later on if we get a canonical name from AD the
1836 	 * old name if any will be freed before assigning the new name.
1837 	 */
1838 
1839 	/* Send this lookup, don't wait for a result here */
1840 	lrc = LDAP_SUCCESS;
1841 	(void) pthread_mutex_lock(&state->qadh->lock);
1842 
1843 	if (!state->qadh->dead) {
1844 		state->qadh->idletime = time(NULL);
1845 		lrc = ldap_search_ext(state->qadh->ld, "",
1846 		    LDAP_SCOPE_SUBTREE, filter, attrs, 0, NULL, NULL,
1847 		    NULL, -1, &q->msgid);
1848 
1849 		if (lrc == LDAP_SUCCESS) {
1850 			state->qadh->num_requests++;
1851 		} else if (lrc == LDAP_BUSY || lrc == LDAP_UNAVAILABLE ||
1852 		    lrc == LDAP_CONNECT_ERROR || lrc == LDAP_SERVER_DOWN ||
1853 		    lrc == LDAP_UNWILLING_TO_PERFORM) {
1854 			retcode = IDMAP_ERR_RETRIABLE_NET_ERR;
1855 			state->qadh->dead = 1;
1856 		} else {
1857 			retcode = IDMAP_ERR_OTHER;
1858 			state->qadh->dead = 1;
1859 		}
1860 	}
1861 	dead = state->qadh->dead;
1862 	num = state->qadh->num_requests;
1863 	(void) pthread_mutex_unlock(&state->qadh->lock);
1864 
1865 	if (dead) {
1866 		if (lrc != LDAP_SUCCESS)
1867 			idmapdlog(LOG_DEBUG,
1868 			    "AD ldap_search_ext error (%s) "
1869 			    "- %d queued requests",
1870 			    ldap_err2string(lrc), num);
1871 		return (retcode);
1872 	}
1873 
1874 	atomic_inc_32(&state->qinflight);
1875 
1876 	/*
1877 	 * Reap as many requests as we can _without_ waiting
1878 	 *
1879 	 * We do this to prevent any possible TCP socket buffer
1880 	 * starvation deadlocks.
1881 	 */
1882 	(void) memset(&tv, 0, sizeof (tv));
1883 	while (idmap_get_adobject_batch(state->qadh, &tv) == 0)
1884 		;
1885 
1886 	return (IDMAP_SUCCESS);
1887 }
1888 
1889 idmap_retcode
1890 idmap_name2sid_batch_add1(idmap_query_state_t *state,
1891 	const char *name, const char *dname, int eunixtype,
1892 	char **dn, char **attr, char **value,
1893 	char **canonname, char **sid, rid_t *rid,
1894 	int *sid_type, char **unixname, idmap_retcode *rc)
1895 {
1896 	idmap_retcode	retcode;
1897 	int		len, samAcctNameLen;
1898 	char		*filter = NULL, *s_name;
1899 	char		*ecanonname, *edomain; /* expected canonname */
1900 
1901 	/*
1902 	 * Strategy: search the global catalog for user/group by
1903 	 * sAMAccountName = user/groupname with "" as the base DN and by
1904 	 * userPrincipalName = user/groupname@domain.  The result
1905 	 * entries will be checked to conform to the name and domain
1906 	 * name given here.  The DN, sAMAccountName, userPrincipalName,
1907 	 * objectSid and objectClass of the result entries are all we
1908 	 * need to figure out which entries match the lookup, the SID of
1909 	 * the user/group and whether it is a user or a group.
1910 	 */
1911 
1912 	/*
1913 	 * We need the name and the domain name separately and as
1914 	 * name@domain.  We also allow the domain to be provided
1915 	 * separately.
1916 	 */
1917 	samAcctNameLen = strlen(name);
1918 
1919 	if ((ecanonname = strdup(name)) == NULL)
1920 		return (IDMAP_ERR_MEMORY);
1921 
1922 	if (dname == NULL || *dname == '\0') {
1923 		if ((dname = strchr(name, '@')) != NULL) {
1924 			/* 'name' is qualified with a domain name */
1925 			if ((edomain = strdup(dname + 1)) == NULL) {
1926 				free(ecanonname);
1927 				return (IDMAP_ERR_MEMORY);
1928 			}
1929 			*strchr(ecanonname, '@') = '\0';
1930 		} else {
1931 			/*
1932 			 * 'name' not qualified and dname not given
1933 			 *
1934 			 * Note: ad->dflt_w2k_dom cannot be NULL - see
1935 			 * idmap_ad_alloc()
1936 			 */
1937 			if (*state->qadh->owner->dflt_w2k_dom == '\0') {
1938 				free(ecanonname);
1939 				return (IDMAP_ERR_DOMAIN);
1940 			}
1941 			edomain = strdup(state->qadh->owner->dflt_w2k_dom);
1942 			if (edomain == NULL) {
1943 				free(ecanonname);
1944 				return (IDMAP_ERR_MEMORY);
1945 			}
1946 		}
1947 	} else {
1948 		if ((edomain = strdup(dname)) == NULL) {
1949 			free(ecanonname);
1950 			return (IDMAP_ERR_MEMORY);
1951 		}
1952 	}
1953 
1954 	s_name = sanitize_for_ldap_filter(name);
1955 	if (s_name == NULL) {
1956 		free(ecanonname);
1957 		free(edomain);
1958 		return (IDMAP_ERR_MEMORY);
1959 	}
1960 
1961 	/* Assemble filter */
1962 	len = snprintf(NULL, 0, SANFILTER, samAcctNameLen, s_name) + 1;
1963 	if ((filter = (char *)malloc(len)) == NULL) {
1964 		free(ecanonname);
1965 		free(edomain);
1966 		if (s_name != name)
1967 			free(s_name);
1968 		return (IDMAP_ERR_MEMORY);
1969 	}
1970 	(void) snprintf(filter, len, SANFILTER, samAcctNameLen, s_name);
1971 	if (s_name != name)
1972 		free(s_name);
1973 
1974 	retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
1975 	    eunixtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
1976 	    unixname, rc);
1977 
1978 	free(filter);
1979 
1980 	return (retcode);
1981 }
1982 
1983 idmap_retcode
1984 idmap_sid2name_batch_add1(idmap_query_state_t *state,
1985 	const char *sid, const rid_t *rid, int eunixtype,
1986 	char **dn, char **attr, char **value,
1987 	char **name, char **dname, int *sid_type,
1988 	char **unixname, idmap_retcode *rc)
1989 {
1990 	idmap_retcode	retcode;
1991 	int		flen, ret;
1992 	char		*filter = NULL;
1993 	char		cbinsid[MAXHEXBINSID + 1];
1994 
1995 	/*
1996 	 * Strategy: search [the global catalog] for user/group by
1997 	 * objectSid = SID with empty base DN.  The DN, sAMAccountName
1998 	 * and objectClass of the result are all we need to figure out
1999 	 * the name of the SID and whether it is a user, a group or a
2000 	 * computer.
2001 	 */
2002 
2003 	ret = idmap_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
2004 	if (ret != 0)
2005 		return (IDMAP_ERR_SID);
2006 
2007 	/* Assemble filter */
2008 	flen = snprintf(NULL, 0, OBJSIDFILTER, cbinsid) + 1;
2009 	if ((filter = (char *)malloc(flen)) == NULL)
2010 		return (IDMAP_ERR_MEMORY);
2011 	(void) snprintf(filter, flen, OBJSIDFILTER, cbinsid);
2012 
2013 	retcode = idmap_batch_add1(state, filter, NULL, NULL, eunixtype,
2014 	    dn, attr, value, name, dname, NULL, NULL, sid_type, unixname, rc);
2015 
2016 	free(filter);
2017 
2018 	return (retcode);
2019 }
2020 
2021 idmap_retcode
2022 idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
2023 	const char *unixname, int is_user, int is_wuser,
2024 	char **dn, char **attr, char **value,
2025 	char **sid, rid_t *rid, char **name,
2026 	char **dname, int *sid_type, idmap_retcode *rc)
2027 {
2028 	idmap_retcode	retcode;
2029 	int		len, ulen;
2030 	char		*filter = NULL, *s_unixname;
2031 	const char	*attrname = NULL;
2032 
2033 	/* Get unixuser or unixgroup AD attribute name */
2034 	attrname = (is_user) ?
2035 	    state->ad_unixuser_attr : state->ad_unixgroup_attr;
2036 	if (attrname == NULL)
2037 		return (IDMAP_ERR_NOTFOUND);
2038 
2039 	s_unixname = sanitize_for_ldap_filter(unixname);
2040 	if (s_unixname == NULL)
2041 		return (IDMAP_ERR_MEMORY);
2042 
2043 	/*  Assemble filter */
2044 	ulen = strlen(unixname);
2045 	len = snprintf(NULL, 0, "(&(objectclass=%s)(%s=%.*s))",
2046 	    is_wuser ? "user" : "group", attrname, ulen, s_unixname) + 1;
2047 	if ((filter = (char *)malloc(len)) == NULL) {
2048 		if (s_unixname != unixname)
2049 			free(s_unixname);
2050 		return (IDMAP_ERR_MEMORY);
2051 	}
2052 	(void) snprintf(filter, len, "(&(objectclass=%s)(%s=%.*s))",
2053 	    is_wuser ? "user" : "group", attrname, ulen, s_unixname);
2054 	if (s_unixname != unixname)
2055 		free(s_unixname);
2056 
2057 	retcode = idmap_batch_add1(state, filter, NULL, NULL,
2058 	    _IDMAP_T_UNDEF, dn, NULL, NULL, name, dname, sid, rid, sid_type,
2059 	    NULL, rc);
2060 
2061 	if (retcode == IDMAP_SUCCESS && attr != NULL) {
2062 		if ((*attr = strdup(attrname)) == NULL)
2063 			retcode = IDMAP_ERR_MEMORY;
2064 	}
2065 
2066 	if (retcode == IDMAP_SUCCESS && value != NULL) {
2067 		if (ulen > 0) {
2068 			if ((*value = strdup(unixname)) == NULL)
2069 				retcode = IDMAP_ERR_MEMORY;
2070 		}
2071 		else
2072 			*value = NULL;
2073 	}
2074 
2075 	free(filter);
2076 
2077 	return (retcode);
2078 }
2079