xref: /illumos-gate/usr/src/cmd/idmap/idmapd/adutils.c (revision 84decf41)
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 2007 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 "idmapd.h"
51 
52 /*
53  * Internal data structures for this code
54  */
55 
56 /* Attribute names and filter format strings */
57 #define	OBJECTSID	"objectSid"
58 #define	OBJECTSIDFILTER	"(objectSid=%s)"
59 #define	SAMACCOUNTNAME	"sAMAccountName"
60 #define	SANFILTER	"(sAMAccountName=%.*s)"
61 #define	OBJECTCLASS	"objectClass"
62 
63 /*
64  * This should really be in some <sys/sid.h> file or so; we have a
65  * private version of sid_t, and so must other components of ON until we
66  * rationalize this.
67  */
68 typedef struct sid {
69 	uchar_t		version;
70 	uchar_t		sub_authority_count;
71 	uint64_t	authority;  /* really, 48-bits */
72 	rid_t		sub_authorities[SID_MAX_SUB_AUTHORITIES];
73 } sid_t;
74 
75 /* A single DS */
76 typedef struct ad_host {
77 	struct ad_host		*next;
78 	ad_t			*owner;		/* ad_t to which this belongs */
79 	pthread_mutex_t		lock;
80 	LDAP			*ld;		/* LDAP connection */
81 	uint32_t		ref;		/* ref count */
82 	time_t			idletime;	/* time since last activity */
83 	int			dead;		/* error on LDAP connection */
84 	/*
85 	 * Used to distinguish between different instances of LDAP
86 	 * connections to this same DS.  We need this so we never mix up
87 	 * results for a given msgID from one connection with those of
88 	 * another earlier connection where two batch state structures
89 	 * share this ad_host object but used different LDAP connections
90 	 * to send their LDAP searches.
91 	 */
92 	uint64_t		generation;
93 
94 	/* LDAP DS info */
95 	char			*host;
96 	int			port;
97 
98 	/* hardwired to SASL GSSAPI only for now */
99 	char			*saslmech;
100 	unsigned		saslflags;
101 } ad_host_t;
102 
103 /* A set of DSs for a given AD partition; ad_t typedef comes from  adutil.h */
104 struct ad {
105 	char			*dflt_w2k_dom;	/* used to qualify bare names */
106 	char			*basedn;	/* derived from dflt domain */
107 	pthread_mutex_t		lock;
108 	uint32_t		ref;
109 	idmap_ad_partition_t	partition;	/* Data or global catalog? */
110 };
111 
112 /*
113  * A place to put the results of a batched (async) query
114  *
115  * There is one of these for every query added to a batch object
116  * (idmap_query_state, see below).
117  */
118 typedef struct idmap_q {
119 	char			**result;	/* name or stringified SID */
120 	char			**domain;	/* name of domain of object */
121 	rid_t			*rid;		/* for n2s, if not NULL */
122 	int			*sid_type;	/* if not NULL */
123 	idmap_retcode		*rc;
124 	int			msgid;		/* LDAP message ID */
125 	/*
126 	 * Bitfield containing state needed to know when we're done
127 	 * processing search results related to this query's LDAP
128 	 * searches.  Mostly self-explanatory.
129 	 */
130 	uint16_t		n2s : 1;	/* name->SID or SID->name? */
131 	uint16_t		got_reply : 1;
132 	uint16_t		got_results : 1;
133 	uint16_t		got_objectSid : 1;
134 	uint16_t		got_objectClass : 1;
135 	uint16_t		got_samAcctName : 1;
136 } idmap_q_t;
137 
138 /* Batch context structure; typedef is in header file */
139 struct idmap_query_state {
140 	idmap_query_state_t	*next;
141 	int			qcount;		/* how many queries */
142 	int			ref_cnt;	/* reference count */
143 	pthread_cond_t		cv;		/* Condition wait variable */
144 	uint32_t		qlastsent;
145 	uint32_t		qinflight;	/* how many queries in flight */
146 	uint16_t		qdead;		/* oops, lost LDAP connection */
147 	ad_host_t		*qadh;		/* LDAP connection */
148 	uint64_t		qadh_gen;	/* same as qadh->generation */
149 	idmap_q_t		queries[1];	/* array of query results */
150 };
151 
152 /*
153  * List of query state structs -- needed so we can "route" LDAP results
154  * to the right context if multiple threads should be using the same
155  * connection concurrently
156  */
157 static idmap_query_state_t	*qstatehead = NULL;
158 static pthread_mutex_t		qstatelock = PTHREAD_MUTEX_INITIALIZER;
159 
160 /*
161  * List of DSs, needed by the idle connection reaper thread
162  */
163 static ad_host_t	*host_head = NULL;
164 static pthread_t	reaperid = 0;
165 static pthread_mutex_t	adhostlock = PTHREAD_MUTEX_INITIALIZER;
166 
167 
168 static void
169 idmap_lookup_unlock_batch(idmap_query_state_t **state);
170 
171 
172 
173 /*ARGSUSED*/
174 static int
175 idmap_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts) {
176 	sasl_interact_t	*interact;
177 
178 	if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
179 		return (LDAP_PARAM_ERROR);
180 
181 	/* There should be no extra arguemnts for SASL/GSSAPI authentication */
182 	for (interact = prompts; interact->id != SASL_CB_LIST_END;
183 			interact++) {
184 		interact->result = NULL;
185 		interact->len = 0;
186 	}
187 	return (LDAP_SUCCESS);
188 }
189 
190 /* Turn "foo.bar.com" into "dc=foo,dc=bar,dc=com" */
191 static
192 char *
193 dns2dn(const char *dns)
194 {
195 	int nameparts;
196 
197 	/* Sigh, ldap_dns_to_dn()'s first arg should be a const char * */
198 	return (ldap_dns_to_dn((char *)dns, &nameparts));
199 }
200 
201 /*
202  * Turn "dc=foo,dc=bar,dc=com" into "foo.bar.com"; ignores any other
203  * attributes (CN, etc...)
204  */
205 static
206 char *
207 dn2dns(const char *dn)
208 {
209 	char **rdns = NULL;
210 	char **attrs = NULL;
211 	char **labels = NULL;
212 	char *dns = NULL;
213 	char **rdn, **attr, **label;
214 	int maxlabels = 5;
215 	int nlabels = 0;
216 	int dnslen;
217 
218 	/*
219 	 * There is no reverse of ldap_dns_to_dn() in our libldap, so we
220 	 * have to do the hard work here for now.
221 	 */
222 
223 	/*
224 	 * This code is much too liberal: it looks for "dc" attributes
225 	 * in all RDNs of the DN.  In theory this could cause problems
226 	 * if people were to use "dc" in nodes other than the root of
227 	 * the tree, but in practice noone, least of all Active
228 	 * Directory, does that.
229 	 *
230 	 * On the other hand, this code is much too conservative: it
231 	 * does not make assumptions about ldap_explode_dn(), and _that_
232 	 * is the true for looking at every attr of every RDN.
233 	 *
234 	 * Since we only ever look at dc and those must be DNS labels,
235 	 * at least until we get around to supporting IDN here we
236 	 * shouldn't see escaped labels from AD nor from libldap, though
237 	 * the spec (RFC2253) does allow libldap to escape things that
238 	 * don't need escaping -- if that should ever happen then
239 	 * libldap will need a spanking, and we can take care of that.
240 	 */
241 
242 	/* Explode a DN into RDNs */
243 	if ((rdns = ldap_explode_dn(dn, 0)) == NULL)
244 		return (NULL);
245 
246 	labels = calloc(maxlabels + 1, sizeof (char *));
247 	label = labels;
248 
249 	for (rdn = rdns; *rdn != NULL; rdn++) {
250 		if (attrs != NULL)
251 			ldap_value_free(attrs);
252 
253 		/* Explode each RDN, look for DC attr, save val as DNS label */
254 		if ((attrs = ldap_explode_rdn(rdn[0], 0)) == NULL)
255 			goto done;
256 
257 		for (attr = attrs; *attr != NULL; attr++) {
258 			if (strncasecmp(*attr, "dc=", 3) != 0)
259 				continue;
260 
261 			/* Found a DNS label */
262 			labels[nlabels++] = strdup((*attr) + 3);
263 
264 			if (nlabels == maxlabels) {
265 				char **tmp;
266 				tmp = realloc(labels,
267 				    sizeof (char *) * (maxlabels + 1));
268 
269 				if (tmp == NULL)
270 					goto done;
271 
272 				labels = tmp;
273 				labels[nlabels] = NULL;
274 			}
275 
276 			/* There should be just one DC= attr per-RDN */
277 			break;
278 		}
279 	}
280 
281 	/*
282 	 * Got all the labels, now join with '.'
283 	 *
284 	 * We need room for nlabels - 1 periods ('.'), one nul
285 	 * terminator, and the strlen() of each label.
286 	 */
287 	dnslen = nlabels;
288 	for (label = labels; *label != NULL; label++)
289 		dnslen += strlen(*label);
290 
291 	if ((dns = malloc(dnslen)) == NULL)
292 		goto done;
293 
294 	*dns = '\0';
295 
296 	for (label = labels; *label != NULL; label++) {
297 		(void) strlcat(dns, *label, dnslen);
298 		/*
299 		 * NOTE: the last '.' won't be appended -- there's no room
300 		 * for it!
301 		 */
302 		(void) strlcat(dns, ".", dnslen);
303 	}
304 
305 done:
306 	if (labels != NULL) {
307 		for (label = labels; *label != NULL; label++)
308 			free(*label);
309 		free(labels);
310 	}
311 	if (attrs != NULL)
312 		ldap_value_free(attrs);
313 	if (rdns != NULL)
314 		ldap_value_free(rdns);
315 
316 	return (dns);
317 }
318 
319 /*
320  * Keep connection management simple for now, extend or replace later
321  * with updated libsldap code.
322  */
323 #define	ADREAPERSLEEP	60
324 #define	ADCONN_TIME	300
325 
326 /*
327  * Idle connection reaping side of connection management
328  *
329  * Every minute wake up and look for connections that have been idle for
330  * five minutes or more and close them.
331  */
332 /*ARGSUSED*/
333 static
334 void
335 adreaper(void *arg)
336 {
337 	ad_host_t	*adh;
338 	time_t		now;
339 	timespec_t	ts;
340 
341 	ts.tv_sec = ADREAPERSLEEP;
342 	ts.tv_nsec = 0;
343 
344 	for (;;) {
345 		/*
346 		 * nanosleep(3RT) is thead-safe (no SIGALRM) and more
347 		 * portable than usleep(3C)
348 		 */
349 		(void) nanosleep(&ts, NULL);
350 		(void) pthread_mutex_lock(&adhostlock);
351 		now = time(NULL);
352 		for (adh = host_head; adh != NULL; adh = adh->next) {
353 			(void) pthread_mutex_lock(&adh->lock);
354 			if (adh->ref == 0 && adh->idletime != 0 &&
355 			    adh->idletime + ADCONN_TIME < now) {
356 				if (adh->ld) {
357 					(void) ldap_unbind(adh->ld);
358 					adh->ld = NULL;
359 					adh->idletime = 0;
360 					adh->ref = 0;
361 				}
362 			}
363 			(void) pthread_mutex_unlock(&adh->lock);
364 		}
365 		(void) pthread_mutex_unlock(&adhostlock);
366 	}
367 }
368 
369 int
370 idmap_ad_alloc(ad_t **new_ad, const char *default_domain,
371 		idmap_ad_partition_t part)
372 {
373 	ad_t *ad;
374 
375 	*new_ad = NULL;
376 
377 	if ((default_domain == NULL || *default_domain == '\0') &&
378 	    part != IDMAP_AD_GLOBAL_CATALOG)
379 		return (-1);
380 
381 	if ((ad = calloc(1, sizeof (ad_t))) == NULL)
382 		return (-1);
383 
384 	ad->ref = 1;
385 	ad->partition = part;
386 
387 	/*
388 	 * If default_domain is NULL, deal, deferring errors until
389 	 * idmap_lookup_batch_start() -- this makes it easier on the
390 	 * caller, who can simply observe lookups failing as opposed to
391 	 * having to conditionalize calls to lookups according to
392 	 * whether it has a non-NULL ad_t *.
393 	 */
394 	if (default_domain == NULL)
395 		default_domain = "";
396 
397 	if ((ad->dflt_w2k_dom = strdup(default_domain)) == NULL)
398 		goto err;
399 
400 	/* If default_domain is empty, deal; see above */
401 	if (*default_domain == '\0') {
402 		if ((ad->basedn = strdup("")) == NULL)
403 			goto err;
404 	} else if ((ad->basedn = dns2dn(default_domain)) == NULL) {
405 		goto err;
406 	}
407 
408 	if (pthread_mutex_init(&ad->lock, NULL) != 0)
409 		goto err;
410 
411 	*new_ad = ad;
412 
413 	return (0);
414 err:
415 	if (ad->dflt_w2k_dom != NULL)
416 		free(ad->dflt_w2k_dom);
417 	if (ad->basedn != NULL)
418 		free(ad->basedn);
419 	free(ad);
420 	return (-1);
421 }
422 
423 
424 void
425 idmap_ad_free(ad_t **ad)
426 {
427 	ad_host_t *p;
428 
429 	if (ad == NULL || *ad == NULL)
430 		return;
431 
432 	(void) pthread_mutex_lock(&(*ad)->lock);
433 
434 	if (atomic_dec_32_nv(&(*ad)->ref) > 0) {
435 		(void) pthread_mutex_unlock(&(*ad)->lock);
436 		*ad = NULL;
437 		return;
438 	}
439 
440 	for (p = host_head; p != NULL; p = p->next) {
441 		if (p->owner != (*ad))
442 			continue;
443 		idmap_delete_ds((*ad), p->host, p->port);
444 	}
445 
446 	free((*ad)->basedn);
447 
448 	(void) pthread_mutex_unlock(&(*ad)->lock);
449 	(void) pthread_mutex_destroy(&(*ad)->lock);
450 
451 	free(*ad);
452 
453 	*ad = NULL;
454 }
455 
456 static
457 int
458 idmap_open_conn(ad_host_t *adh)
459 {
460 	int	rc, ldversion;
461 
462 	if (adh->dead && adh->ld != NULL) {
463 		(void) ldap_unbind(adh->ld);
464 		adh->ld = NULL;
465 		adh->dead = 0;
466 	}
467 
468 	if (adh->ld == NULL) {
469 		int zero = 0;
470 		int timeoutms = 30 * 1000;
471 
472 		atomic_inc_64(&adh->generation);
473 		adh->ld = ldap_init(adh->host, adh->port);
474 		if (adh->ld == NULL)
475 			return (-1);
476 
477 		ldversion = LDAP_VERSION3;
478 		(void) ldap_set_option(adh->ld, LDAP_OPT_PROTOCOL_VERSION,
479 		    &ldversion);
480 
481 		(void) ldap_set_option(adh->ld, LDAP_OPT_REFERRALS,
482 		    LDAP_OPT_OFF);
483 		(void) ldap_set_option(adh->ld, LDAP_OPT_TIMELIMIT, &zero);
484 		(void) ldap_set_option(adh->ld, LDAP_OPT_SIZELIMIT, &zero);
485 		/* setup TCP/IP connect timeout */
486 		(void) ldap_set_option(adh->ld, LDAP_X_OPT_CONNECT_TIMEOUT,
487 		    &timeoutms);
488 		(void) ldap_set_option(adh->ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
489 		rc = ldap_sasl_interactive_bind_s(adh->ld,
490 		    "" /* binddn */, adh->saslmech, NULL, NULL, adh->saslflags,
491 		    &idmap_saslcallback, NULL /* defaults */);
492 
493 		if (rc != LDAP_SUCCESS) {
494 			idmapdlog(LOG_ERR, "ldap_sasl_interactive_bind_s() "
495 			    "to server %s:%d failed. (%s)",
496 			    adh->host, adh->port, ldap_err2string(rc));
497 			return (rc);
498 		}
499 	}
500 
501 	adh->idletime = time(NULL);
502 
503 	return (LDAP_SUCCESS);
504 }
505 
506 
507 /*
508  * Connection management: find an open connection or open one
509  */
510 static
511 ad_host_t *
512 idmap_get_conn(const ad_t *ad)
513 {
514 	ad_host_t	*adh = NULL;
515 	int		rc;
516 
517 	(void) pthread_mutex_lock(&adhostlock);
518 
519 	/*
520 	 * Search for any ad_host_t, preferably one with an open
521 	 * connection
522 	 */
523 	for (adh = host_head; adh != NULL; adh = adh->next) {
524 		if (adh->owner == ad) {
525 			break;
526 		}
527 	}
528 
529 	if (adh != NULL)
530 		atomic_inc_32(&adh->ref);
531 
532 	(void) pthread_mutex_unlock(&adhostlock);
533 
534 	if (adh == NULL)
535 		return (NULL);
536 
537 	/* found connection, open it if not opened */
538 	(void) pthread_mutex_lock(&adh->lock);
539 	rc = idmap_open_conn(adh);
540 	(void) pthread_mutex_unlock(&adh->lock);
541 	if (rc != LDAP_SUCCESS)
542 		return (NULL);
543 
544 	return (adh);
545 }
546 
547 static
548 void
549 idmap_release_conn(ad_host_t *adh)
550 {
551 	(void) pthread_mutex_lock(&adh->lock);
552 	if (atomic_dec_32_nv(&adh->ref) == 0)
553 		adh->idletime = time(NULL);
554 	(void) pthread_mutex_unlock(&adh->lock);
555 }
556 
557 /*
558  * Take ad_host_config_t information, create a ad_host_t,
559  * populate it and add it to the list of hosts.
560  */
561 
562 int
563 idmap_add_ds(ad_t *ad, const char *host, int port)
564 {
565 	ad_host_t	*p;
566 	ad_host_t	*new = NULL;
567 	int		ret = -1;
568 
569 	if (port == 0)
570 		port = (int)ad->partition;
571 
572 	(void) pthread_mutex_lock(&adhostlock);
573 	for (p = host_head; p != NULL; p = p->next) {
574 		if (p->owner != ad)
575 			continue;
576 
577 		if (strcmp(host, p->host) == 0 && p->port == port) {
578 			/* already added */
579 			ret = -2;
580 			goto err;
581 		}
582 	}
583 
584 	/* add new entry */
585 	new = (ad_host_t *)calloc(1, sizeof (ad_host_t));
586 	if (new == NULL)
587 		goto err;
588 	new->owner = ad;
589 	new->port = port;
590 	new->dead = 0;
591 	if ((new->host = strdup(host)) == NULL)
592 		goto err;
593 
594 	/* default to SASL GSSAPI only for now */
595 	new->saslflags = LDAP_SASL_INTERACTIVE;
596 	new->saslmech = "GSSAPI";
597 
598 	if ((ret = pthread_mutex_init(&new->lock, NULL)) != 0) {
599 		free(new->host);
600 		new->host = NULL;
601 		errno = ret;
602 		ret = -1;
603 		goto err;
604 	}
605 
606 	/* link in */
607 	new->next = host_head;
608 	host_head = new;
609 
610 	/* Start reaper if it doesn't exist */
611 	if (reaperid == 0)
612 		(void) pthread_create(&reaperid, NULL,
613 		    (void *(*)(void *))adreaper, (void *)NULL);
614 
615 err:
616 	(void) pthread_mutex_unlock(&adhostlock);
617 
618 	if (ret != 0 && new != NULL) {
619 		if (new->host != NULL) {
620 			(void) pthread_mutex_destroy(&new->lock);
621 			free(new->host);
622 		}
623 		free(new);
624 	}
625 
626 	return (ret);
627 }
628 
629 /*
630  * free a DS configuration
631  */
632 void
633 idmap_delete_ds(ad_t *ad, const char *host, int port)
634 {
635 	ad_host_t	**p, *q;
636 
637 	(void) pthread_mutex_lock(&adhostlock);
638 	for (p = &host_head; *p != NULL; p = &((*p)->next)) {
639 		if ((*p)->owner != ad || strcmp(host, (*p)->host) != 0 ||
640 		    (*p)->port != port)
641 			continue;
642 		/* found */
643 		if (atomic_dec_32_nv(&((*p)->ref)) > 0)
644 			break;	/* still in use */
645 
646 		q = *p;
647 		*p = (*p)->next;
648 
649 		(void) pthread_mutex_destroy(&q->lock);
650 
651 		if (q->ld)
652 			(void) ldap_unbind(q->ld);
653 		if (q->host)
654 			free(q->host);
655 		free(q);
656 		break;
657 	}
658 	(void) pthread_mutex_unlock(&adhostlock);
659 }
660 
661 /*
662  * Convert a binary SID in a BerValue to a sid_t
663  */
664 static
665 int
666 idmap_getsid(BerValue *bval, sid_t *sidp)
667 {
668 	int		i, j;
669 	uchar_t		*v;
670 	uint32_t	a;
671 
672 	/*
673 	 * The binary format of a SID is as follows:
674 	 *
675 	 * byte #0: version, always 0x01
676 	 * byte #1: RID count, always <= 0x0f
677 	 * bytes #2-#7: SID authority, big-endian 48-bit unsigned int
678 	 *
679 	 * followed by RID count RIDs, each a little-endian, unsigned
680 	 * 32-bit int.
681 	 */
682 	/*
683 	 * Sanity checks: must have at least one RID, version must be
684 	 * 0x01, and the length must be 8 + rid count * 4
685 	 */
686 	if (bval->bv_len > 8 && bval->bv_val[0] == 0x01 &&
687 	    bval->bv_len == 1 + 1 + 6 + bval->bv_val[1] * 4) {
688 		v = (uchar_t *)bval->bv_val;
689 		sidp->version = v[0];
690 		sidp->sub_authority_count = v[1];
691 		sidp->authority =
692 		    /* big endian -- so start from the left */
693 		    ((u_longlong_t)v[2] << 40) |
694 		    ((u_longlong_t)v[3] << 32) |
695 		    ((u_longlong_t)v[4] << 24) |
696 		    ((u_longlong_t)v[5] << 16) |
697 		    ((u_longlong_t)v[6] << 8) |
698 		    (u_longlong_t)v[7];
699 		for (i = 0; i < sidp->sub_authority_count; i++) {
700 			j = 8 + (i * 4);
701 			/* little endian -- so start from the right */
702 			a = (v[j + 3] << 24) | (v[j + 2] << 16) |
703 			    (v[j + 1] << 8) | (v[j]);
704 			sidp->sub_authorities[i] = a;
705 		}
706 		return (0);
707 	}
708 	return (-1);
709 }
710 
711 /*
712  * Convert a sid_t to S-1-...
713  */
714 static
715 char *
716 idmap_sid2txt(sid_t *sidp)
717 {
718 	int	rlen, i, len;
719 	char	*str, *cp;
720 
721 	if (sidp->version != 1)
722 		return (NULL);
723 
724 	len = sizeof ("S-1-") - 1;
725 
726 	/*
727 	 * We could optimize like so, but, why?
728 	 *	if (sidp->authority < 10)
729 	 *		len += 2;
730 	 *	else if (sidp->authority < 100)
731 	 *		len += 3;
732 	 *	else
733 	 *		len += snprintf(NULL, 0"%llu", sidp->authority);
734 	 */
735 	len += snprintf(NULL, 0, "%llu", sidp->authority);
736 
737 	/* Max length of a uint32_t printed out in ASCII is 10 bytes */
738 	len += 1 + (sidp->sub_authority_count + 1) * 10;
739 
740 	if ((cp = str = malloc(len)) == NULL)
741 		return (NULL);
742 
743 	rlen = snprintf(str, len, "S-1-%llu", sidp->authority);
744 
745 	cp += rlen;
746 	len -= rlen;
747 
748 	for (i = 0; i < sidp->sub_authority_count; i++) {
749 		assert(len > 0);
750 		rlen = snprintf(cp, len, "-%u", sidp->sub_authorities[i]);
751 		cp += rlen;
752 		len -= rlen;
753 		assert(len >= 0);
754 	}
755 
756 	return (str);
757 }
758 
759 /*
760  * Convert a sid_t to on-the-wire encoding
761  */
762 static
763 int
764 idmap_sid2binsid(sid_t *sid, uchar_t *binsid, int binsidlen)
765 {
766 	uchar_t		*p;
767 	int		i;
768 	uint64_t	a;
769 	uint32_t	r;
770 
771 	if (sid->version != 1 ||
772 	    binsidlen != (1 + 1 + 6 + sid->sub_authority_count * 4))
773 		return (-1);
774 
775 	p = binsid;
776 	*p++ = 0x01;		/* version */
777 	/* sub authority count */
778 	*p++ = sid->sub_authority_count;
779 	/* Authority */
780 	a = sid->authority;
781 	/* big-endian -- start from left */
782 	*p++ = (a >> 40) & 0xFF;
783 	*p++ = (a >> 32) & 0xFF;
784 	*p++ = (a >> 24) & 0xFF;
785 	*p++ = (a >> 16) & 0xFF;
786 	*p++ = (a >> 8) & 0xFF;
787 	*p++ = a & 0xFF;
788 
789 	/* sub-authorities */
790 	for (i = 0; i < sid->sub_authority_count; i++) {
791 		r = sid->sub_authorities[i];
792 		/* little-endian -- start from right */
793 		*p++ = (r & 0x000000FF);
794 		*p++ = (r & 0x0000FF00) >> 8;
795 		*p++ = (r & 0x00FF0000) >> 16;
796 		*p++ = (r & 0xFF000000) >> 24;
797 	}
798 
799 	return (0);
800 }
801 
802 /*
803  * Convert a stringified SID (S-1-...) into a hex-encoded version of the
804  * on-the-wire encoding, but with each pair of hex digits pre-pended
805  * with a '\', so we can pass this to libldap.
806  */
807 static
808 int
809 idmap_txtsid2hexbinsid(const char *txt, const rid_t *rid,
810 	char *hexbinsid, int hexbinsidlen)
811 {
812 	sid_t		sid = { 0 };
813 	int		i, j;
814 	const char	*cp;
815 	char		*ecp;
816 	u_longlong_t	a;
817 	unsigned long	r;
818 	uchar_t		*binsid, b, hb;
819 
820 	/* Only version 1 SIDs please */
821 	if (strncmp(txt, "S-1-", strlen("S-1-")) != 0)
822 		return (-1);
823 
824 	if (strlen(txt) < (strlen("S-1-") + 1))
825 		return (-1);
826 
827 	/* count '-'s */
828 	for (j = 0, cp = strchr(txt, '-');
829 	    cp != NULL && *cp != '\0';
830 	    j++, cp = strchr(cp + 1, '-')) {
831 		/* can't end on a '-' */
832 		if (*(cp + 1) == '\0')
833 			return (-1);
834 	}
835 
836 	/* Adjust count for version and authority */
837 	j -= 2;
838 
839 	/* we know the version number and RID count */
840 	sid.version = 1;
841 	sid.sub_authority_count = (rid != NULL) ? j + 1 : j;
842 
843 	/* must have at least one RID, but not too many */
844 	if (sid.sub_authority_count < 1 ||
845 	    sid.sub_authority_count > SID_MAX_SUB_AUTHORITIES)
846 		return (-1);
847 
848 	/* check that we only have digits and '-' */
849 	if (strspn(txt + 1, "0123456789-") < (strlen(txt) - 1))
850 		return (-1);
851 
852 	cp = txt + strlen("S-1-");
853 
854 	/* 64-bit safe parsing of unsigned 48-bit authority value */
855 	errno = 0;
856 	a = strtoull(cp, &ecp, 10);
857 
858 	/* errors parsing the authority or too many bits */
859 	if (cp == ecp || (a == 0 && errno == EINVAL) ||
860 	    (a == ULLONG_MAX && errno == ERANGE) ||
861 	    (a & 0x0000ffffffffffffULL) != a)
862 		return (-1);
863 
864 	cp = ecp;
865 
866 	sid.authority = (uint64_t)a;
867 
868 	for (i = 0; i < j; i++) {
869 		if (*cp++ != '-')
870 			return (-1);
871 		/* 64-bit safe parsing of unsigned 32-bit RID */
872 		errno = 0;
873 		r = strtoul(cp, &ecp, 10);
874 		/* errors parsing the RID or too many bits */
875 		if (cp == ecp || (r == 0 && errno == EINVAL) ||
876 		    (r == ULONG_MAX && errno == ERANGE) ||
877 		    (r & 0xffffffffUL) != r)
878 			return (-1);
879 		sid.sub_authorities[i] = (uint32_t)r;
880 		cp = ecp;
881 	}
882 
883 	/* check that all of the string SID has been consumed */
884 	if (*cp != '\0')
885 		return (-1);
886 
887 	if (rid != NULL)
888 		sid.sub_authorities[j] = *rid;
889 
890 	j = 1 + 1 + 6 + sid.sub_authority_count * 4;
891 
892 	if (hexbinsidlen < (j * 3))
893 		return (-2);
894 
895 	/* binary encode the SID */
896 	binsid = (uchar_t *)alloca(j);
897 	(void) idmap_sid2binsid(&sid, binsid, j);
898 
899 	/* hex encode, with a backslash before each byte */
900 	for (ecp = hexbinsid, i = 0; i < j; i++) {
901 		b = binsid[i];
902 		*ecp++ = '\\';
903 		hb = (b >> 4) & 0xF;
904 		*ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A');
905 		hb = b & 0xF;
906 		*ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A');
907 	}
908 	*ecp = '\0';
909 
910 	return (0);
911 }
912 
913 static
914 char *
915 convert_bval2sid(BerValue *bval, rid_t *rid)
916 {
917 	sid_t	sid;
918 
919 	if (idmap_getsid(bval, &sid) < 0)
920 		return (NULL);
921 
922 	/*
923 	 * If desired and if the SID is what should be a domain/computer
924 	 * user or group SID (i.e., S-1-5-w-x-y-z-<user/group RID>) then
925 	 * save the last RID and truncate the SID
926 	 */
927 	if (rid != NULL && sid.authority == 5 && sid.sub_authority_count == 5)
928 		*rid = sid.sub_authorities[--sid.sub_authority_count];
929 	return (idmap_sid2txt(&sid));
930 }
931 
932 
933 idmap_retcode
934 idmap_lookup_batch_start(ad_t *ad, int nqueries, idmap_query_state_t **state)
935 {
936 	idmap_query_state_t *new_state;
937 	ad_host_t	*adh = NULL;
938 
939 	*state = NULL;
940 
941 	if (*ad->dflt_w2k_dom == '\0')
942 		return (-1);
943 
944 	adh = idmap_get_conn(ad);
945 	if (adh == NULL)
946 		return (IDMAP_ERR_OTHER);
947 
948 	new_state = calloc(1, sizeof (idmap_query_state_t) +
949 	    (nqueries - 1) * sizeof (idmap_q_t));
950 
951 	if (new_state == NULL)
952 		return (IDMAP_ERR_MEMORY);
953 
954 	new_state->ref_cnt = 1;
955 	new_state->qadh = adh;
956 	new_state->qcount = nqueries;
957 	new_state->qadh_gen = adh->generation;
958 	/* should be -1, but the atomic routines want unsigned */
959 	new_state->qlastsent = 0;
960 	(void) pthread_cond_init(&new_state->cv, NULL);
961 
962 	(void) pthread_mutex_lock(&qstatelock);
963 	new_state->next = qstatehead;
964 	qstatehead = new_state;
965 	(void) pthread_mutex_unlock(&qstatelock);
966 
967 	*state = new_state;
968 
969 	return (IDMAP_SUCCESS);
970 }
971 
972 /*
973  * Find the idmap_query_state_t to which a given LDAP result msgid on a
974  * given connection belongs. This routine increaments the reference count
975  * so that the object can not be freed. idmap_lookup_unlock_batch()
976  * must be called to decreament the reference count.
977  */
978 static
979 int
980 idmap_msgid2query(ad_host_t *adh, int msgid,
981 	idmap_query_state_t **state, int *qid)
982 {
983 	idmap_query_state_t *p;
984 	int		    i;
985 
986 	(void) pthread_mutex_lock(&qstatelock);
987 	for (p = qstatehead; p != NULL; p = p->next) {
988 		if (p->qadh != adh || adh->generation != p->qadh_gen)
989 			continue;
990 		for (i = 0; i < p->qcount; i++) {
991 			if ((p->queries[i]).msgid == msgid) {
992 				p->ref_cnt++;
993 				*state = p;
994 				*qid = i;
995 				(void) pthread_mutex_unlock(&qstatelock);
996 				return (1);
997 			}
998 		}
999 	}
1000 	(void) pthread_mutex_unlock(&qstatelock);
1001 	return (0);
1002 }
1003 
1004 /*
1005  * Handle an objectSid attr from a result
1006  */
1007 static
1008 void
1009 idmap_bv_objsid2sidstr(BerValue **bvalues, idmap_q_t *q)
1010 {
1011 	if (bvalues == NULL)
1012 		return;
1013 	/* objectSid is single valued */
1014 	*(q->result) = convert_bval2sid(bvalues[0], q->rid);
1015 	q->got_objectSid = 1;
1016 }
1017 
1018 /*
1019  * Handle a sAMAccountName attr from a result
1020  */
1021 static
1022 void
1023 idmap_bv_samaccountname2name(BerValue **bvalues, idmap_q_t *q, const char *dn)
1024 {
1025 	char *result, *domain;
1026 	int len;
1027 
1028 	if (bvalues == NULL)
1029 		return;
1030 
1031 	if ((domain = dn2dns(dn)) == NULL)
1032 		return;
1033 
1034 	if (bvalues == NULL || bvalues[0] == NULL ||
1035 	    bvalues[0]->bv_val == NULL)
1036 		return;
1037 
1038 	len = bvalues[0]->bv_len + 1;
1039 
1040 	if (q->domain != NULL)
1041 		*(q->domain) = domain;
1042 	else
1043 		len += strlen(domain) + 1;
1044 
1045 	if ((result = malloc(len)) == NULL) {
1046 		if (q->domain != NULL)
1047 			*(q->domain) = NULL;
1048 		free(domain);
1049 		return;
1050 	}
1051 
1052 	(void) memcpy(result, bvalues[0]->bv_val, (size_t)bvalues[0]->bv_len);
1053 	result[bvalues[0]->bv_len] = '\0';
1054 
1055 	if (q->domain == NULL) {
1056 		(void) strlcat(result, "@", len);
1057 		(void) strlcat(result, domain, len);
1058 		free(domain);
1059 	}
1060 
1061 	*(q->result) = result;
1062 	q->got_samAcctName = 1;
1063 }
1064 
1065 
1066 #define	BVAL_CASEEQ(bv, str) \
1067 		(((*(bv))->bv_len == (sizeof (str) - 1)) && \
1068 		    strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
1069 
1070 /*
1071  * Handle an objectClass attr from a result
1072  */
1073 static
1074 void
1075 idmap_bv_objclass2sidtype(BerValue **bvalues, idmap_q_t *q)
1076 {
1077 	BerValue	**cbval;
1078 
1079 	if (bvalues == NULL)
1080 		return;
1081 
1082 	for (cbval = bvalues; *cbval != NULL; cbval++) {
1083 		/* don't clobber sid_type */
1084 		if (*(q->sid_type) == _IDMAP_T_COMPUTER ||
1085 		    *(q->sid_type) == _IDMAP_T_GROUP ||
1086 		    *(q->sid_type) == _IDMAP_T_USER)
1087 			continue;
1088 
1089 		if (BVAL_CASEEQ(cbval, "Computer")) {
1090 			*(q->sid_type) = _IDMAP_T_COMPUTER;
1091 			return;
1092 		} else if (BVAL_CASEEQ(cbval, "Group")) {
1093 			*(q->sid_type) = _IDMAP_T_GROUP;
1094 		} else if (BVAL_CASEEQ(cbval, "USER")) {
1095 			*(q->sid_type) = _IDMAP_T_USER;
1096 		} else
1097 			*(q->sid_type) = _IDMAP_T_OTHER;
1098 		q->got_objectClass = 1;
1099 	}
1100 }
1101 
1102 /*
1103  * Handle a given search result entry
1104  */
1105 static
1106 void
1107 idmap_extract_object(idmap_query_state_t *state, int qid, LDAPMessage *res)
1108 {
1109 	char			*dn, *attr;
1110 	BerElement		*ber = NULL;
1111 	BerValue		**bvalues;
1112 	ad_host_t		*adh;
1113 	idmap_q_t		*q;
1114 	idmap_retcode		orc;
1115 
1116 	adh = state->qadh;
1117 
1118 	(void) pthread_mutex_lock(&adh->lock);
1119 
1120 	if (adh->dead || (dn = ldap_get_dn(adh->ld, res)) == NULL) {
1121 		(void) pthread_mutex_unlock(&adh->lock);
1122 		return;
1123 	}
1124 
1125 	q = &(state->queries[qid]);
1126 
1127 	for (attr = ldap_first_attribute(adh->ld, res, &ber); attr != NULL;
1128 	    attr = ldap_next_attribute(adh->ld, res, ber)) {
1129 		orc = *q->rc;
1130 		bvalues = NULL;	/* for memory management below */
1131 
1132 		/*
1133 		 * If this is an attribute we are looking for and
1134 		 * haven't seen it yet, parse it
1135 		 */
1136 		if (orc != IDMAP_SUCCESS && q->n2s && !q->got_objectSid &&
1137 		    strcasecmp(attr, OBJECTSID) == 0) {
1138 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1139 			idmap_bv_objsid2sidstr(bvalues, q);
1140 		} else if (orc != IDMAP_SUCCESS && !q->n2s &&
1141 		    !q->got_samAcctName &&
1142 		    strcasecmp(attr, SAMACCOUNTNAME) == 0) {
1143 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1144 			idmap_bv_samaccountname2name(bvalues, q, dn);
1145 		} else if (orc != IDMAP_SUCCESS && !q->got_objectClass &&
1146 		    strcasecmp(attr, OBJECTCLASS) == 0) {
1147 			bvalues = ldap_get_values_len(adh->ld, res, attr);
1148 			idmap_bv_objclass2sidtype(bvalues, q);
1149 		}
1150 
1151 		if (bvalues != NULL)
1152 			ldap_value_free_len(bvalues);
1153 		ldap_memfree(attr);
1154 
1155 		if (q->n2s)
1156 			*q->rc = (q->got_objectSid &&
1157 			    q->got_objectClass) ?
1158 			    IDMAP_SUCCESS : IDMAP_ERR_NORESULT;
1159 		else
1160 			*q->rc = (q->got_samAcctName &&
1161 			    q->got_objectClass) ?
1162 			    IDMAP_SUCCESS : IDMAP_ERR_NORESULT;
1163 
1164 		if (*q->rc == IDMAP_SUCCESS && *q->result == NULL)
1165 			*q->rc = IDMAP_ERR_NORESULT;
1166 	}
1167 	(void) pthread_mutex_unlock(&adh->lock);
1168 
1169 	/*
1170 	 * If there should be multiple partial results for different
1171 	 * entities (there should not be, but, if it should happen) then
1172 	 * it's possible that they could get mixed up here and we could
1173 	 * get bogus results.  We just mark the query's results as
1174 	 * toxic (IDMAP_ERR_INTERNAL).
1175 	 *
1176 	 * Between this and ignoring results when we've already filled
1177 	 * out a query's results we should be OK.  The first full reply
1178 	 * wins.  In practice we should never get multiple results.
1179 	 */
1180 	if (orc == IDMAP_ERR_INTERNAL)
1181 		*q->rc = IDMAP_ERR_INTERNAL;
1182 	else if (*q->rc != IDMAP_SUCCESS)
1183 		*q->rc = IDMAP_ERR_INTERNAL;
1184 
1185 	if (ber != NULL)
1186 		ber_free(ber, 0);
1187 
1188 	ldap_memfree(dn);
1189 }
1190 
1191 /*
1192  * Try to get a result; if there is one, find the corresponding
1193  * idmap_q_t and process the result.
1194  */
1195 static
1196 int
1197 idmap_get_adobject_batch(ad_host_t *adh, struct timeval *timeout)
1198 {
1199 	idmap_query_state_t	*query_state;
1200 	LDAPMessage		*res = NULL;
1201 	int			rc, ret, msgid, qid;
1202 
1203 	(void) pthread_mutex_lock(&adh->lock);
1204 	if (adh->dead) {
1205 		(void) pthread_mutex_unlock(&adh->lock);
1206 		return (-1);
1207 	}
1208 
1209 	/* Get one result */
1210 	rc = ldap_result(adh->ld, LDAP_RES_ANY, 0,
1211 	    timeout, &res);
1212 	if (rc == LDAP_UNAVAILABLE || rc == LDAP_UNWILLING_TO_PERFORM ||
1213 	    rc == LDAP_CONNECT_ERROR || rc == LDAP_SERVER_DOWN ||
1214 	    rc == LDAP_BUSY)
1215 		adh->dead = 1;
1216 	(void) pthread_mutex_unlock(&adh->lock);
1217 
1218 	if (adh->dead)
1219 		return (-1);
1220 
1221 	switch (rc) {
1222 	case LDAP_RES_SEARCH_RESULT:
1223 		/* We have all the LDAP replies for some search... */
1224 		msgid = ldap_msgid(res);
1225 		if (idmap_msgid2query(adh, msgid,
1226 		    &query_state, &qid)) {
1227 			/* ...so we can decrement qinflight */
1228 			atomic_dec_32(&query_state->qinflight);
1229 			/* we saw at least one reply */
1230 			query_state->queries[qid].got_reply = 1;
1231 			idmap_lookup_unlock_batch(&query_state);
1232 		}
1233 		(void) ldap_msgfree(res);
1234 		ret = 0;
1235 		break;
1236 	case LDAP_RES_SEARCH_REFERENCE:
1237 		/*
1238 		 * We have no need for these at the moment.  Eventually,
1239 		 * when we query things that we can't expect to find in
1240 		 * the Global Catalog then we'll need to learn to follow
1241 		 * references.
1242 		 */
1243 		(void) ldap_msgfree(res);
1244 		ret = 0;
1245 		break;
1246 	case LDAP_RES_SEARCH_ENTRY:
1247 		/* Got a result */
1248 		msgid = ldap_msgid(res);
1249 		if (idmap_msgid2query(adh, msgid,
1250 		    &query_state, &qid)) {
1251 			idmap_extract_object(query_state, qid, res);
1252 			/* we saw at least one result */
1253 			query_state->queries[qid].got_reply = 1;
1254 			query_state->queries[qid].got_results = 1;
1255 			idmap_lookup_unlock_batch(&query_state);
1256 		}
1257 		(void) ldap_msgfree(res);
1258 		ret = 0;
1259 		break;
1260 	default:
1261 		/* timeout or error; treat the same */
1262 		ret = -1;
1263 		break;
1264 	}
1265 
1266 	return (ret);
1267 }
1268 
1269 /*
1270  * This routine decreament the reference count of the
1271  * idmap_query_state_t
1272  */
1273 static void
1274 idmap_lookup_unlock_batch(idmap_query_state_t **state)
1275 {
1276 	/*
1277 	 * Decrement reference count with qstatelock locked
1278 	 */
1279 	(void) pthread_mutex_lock(&qstatelock);
1280 	(*state)->ref_cnt--;
1281 	/*
1282 	 * If there are no references wakup the allocating thread
1283 	 */
1284 	if ((*state)->ref_cnt == 0)
1285 		(void) pthread_cond_signal(&(*state)->cv);
1286 	(void) pthread_mutex_unlock(&qstatelock);
1287 	*state = NULL;
1288 }
1289 
1290 /*
1291  * This routine frees the idmap_query_state_t structure
1292  * If the reference count is greater than 1 it waits
1293  * for the other threads to finish using it.
1294  */
1295 void
1296 idmap_lookup_release_batch(idmap_query_state_t **state)
1297 {
1298 	idmap_query_state_t **p;
1299 
1300 	/*
1301 	 * Decrement reference count with qstatelock locked
1302 	 * and wait for reference count to get to zero
1303 	 */
1304 	(void) pthread_mutex_lock(&qstatelock);
1305 	(*state)->ref_cnt--;
1306 	while ((*state)->ref_cnt > 0) {
1307 		(void) pthread_cond_wait(&(*state)->cv, &qstatelock);
1308 	}
1309 
1310 	/* Remove this state struct from the list of state structs */
1311 	for (p = &qstatehead; *p != NULL; p = &(*p)->next) {
1312 		if (*p == (*state)) {
1313 			*p = (*state)->next;
1314 			break;
1315 		}
1316 	}
1317 	(void) pthread_mutex_unlock(&qstatelock);
1318 
1319 	(void) pthread_cond_destroy(&(*state)->cv);
1320 
1321 	idmap_release_conn((*state)->qadh);
1322 
1323 	free(*state);
1324 	*state = NULL;
1325 }
1326 
1327 idmap_retcode
1328 idmap_lookup_batch_end(idmap_query_state_t **state,
1329 	struct timeval *timeout)
1330 {
1331 	idmap_q_t	    *q;
1332 	int		    i;
1333 	int		    rc = LDAP_SUCCESS;
1334 	idmap_retcode	    retcode = IDMAP_SUCCESS;
1335 
1336 	(*state)->qdead = 1;
1337 
1338 	/* Process results until done or until timeout, if given */
1339 	while ((*state)->qinflight > 0) {
1340 		if ((rc = idmap_get_adobject_batch((*state)->qadh,
1341 		    timeout)) != 0)
1342 			break;
1343 	}
1344 
1345 	if (rc == LDAP_UNAVAILABLE || rc == LDAP_UNWILLING_TO_PERFORM ||
1346 	    rc == LDAP_CONNECT_ERROR || rc == LDAP_SERVER_DOWN ||
1347 	    rc == LDAP_BUSY) {
1348 		retcode = IDMAP_ERR_RETRIABLE_NET_ERR;
1349 		(*state)->qadh->dead = 1;
1350 	}
1351 
1352 	for (i = 0; i < (*state)->qcount; i++) {
1353 		q = &((*state)->queries[i]);
1354 		if (q->got_reply && !q->got_results) {
1355 			if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR)
1356 				*q->rc = IDMAP_ERR_RETRIABLE_NET_ERR;
1357 			else
1358 				*q->rc = IDMAP_ERR_NOTFOUND;
1359 		}
1360 	}
1361 
1362 	idmap_lookup_release_batch(state);
1363 
1364 	return (retcode);
1365 }
1366 
1367 /*
1368  * Send one prepared search, queue up msgid, process what results are
1369  * available
1370  */
1371 static
1372 idmap_retcode
1373 idmap_batch_add1(idmap_query_state_t *state, int n2s,
1374 	const char *filter, const char *basedn,
1375 	char **result, char **dname, rid_t *rid, int *sid_type,
1376 	idmap_retcode *rc)
1377 {
1378 	idmap_retcode	retcode = IDMAP_SUCCESS;
1379 	int		lrc, qid;
1380 	struct timeval	tv;
1381 	idmap_q_t	*q;
1382 
1383 	if (state->qdead) {
1384 		*rc = IDMAP_ERR_NORESULT;
1385 		return (IDMAP_ERR_RETRIABLE_NET_ERR);
1386 	}
1387 
1388 	qid = atomic_inc_32_nv(&state->qlastsent) - 1;
1389 
1390 	q = &(state->queries[qid]);
1391 
1392 	/* Remember where to put the results */
1393 	q->result = result;
1394 	q->domain = dname;
1395 	q->rid = rid;
1396 	q->sid_type = sid_type;
1397 	q->rc = rc;
1398 	q->n2s = n2s ? 1 : 0;
1399 	q->got_objectSid = 0;
1400 	q->got_objectClass = 0;
1401 	q->got_samAcctName = 0;
1402 
1403 	/*
1404 	 * Provide sane defaults for the results in case we never hear
1405 	 * back from the DS before closing the connection.
1406 	 */
1407 	*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
1408 	*sid_type = _IDMAP_T_OTHER;
1409 	*result = NULL;
1410 	if (dname != NULL)
1411 		*dname = NULL;
1412 	if (rid != NULL)
1413 		*rid = 0;
1414 
1415 	/* Send this lookup, don't wait for a result here */
1416 	(void) pthread_mutex_lock(&state->qadh->lock);
1417 
1418 	if (!state->qadh->dead) {
1419 		state->qadh->idletime = time(NULL);
1420 		lrc = ldap_search_ext(state->qadh->ld, basedn,
1421 		    LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL,
1422 		    NULL, -1, &q->msgid);
1423 		if (lrc == LDAP_BUSY || lrc == LDAP_UNAVAILABLE ||
1424 		    lrc == LDAP_CONNECT_ERROR || lrc == LDAP_SERVER_DOWN ||
1425 		    lrc == LDAP_UNWILLING_TO_PERFORM) {
1426 			retcode = IDMAP_ERR_RETRIABLE_NET_ERR;
1427 			state->qadh->dead = 1;
1428 		} else if (lrc != LDAP_SUCCESS) {
1429 			retcode = IDMAP_ERR_OTHER;
1430 			state->qadh->dead = 1;
1431 		}
1432 	}
1433 	(void) pthread_mutex_unlock(&state->qadh->lock);
1434 
1435 	if (state->qadh->dead)
1436 		return (retcode);
1437 
1438 	atomic_inc_32(&state->qinflight);
1439 
1440 	/*
1441 	 * Reap as many requests as we can _without_ waiting
1442 	 *
1443 	 * We do this to prevent any possible TCP socket buffer
1444 	 * starvation deadlocks.
1445 	 */
1446 	(void) memset(&tv, 0, sizeof (tv));
1447 	while (idmap_get_adobject_batch(state->qadh, &tv) == 0)
1448 		;
1449 
1450 	return (IDMAP_SUCCESS);
1451 }
1452 
1453 idmap_retcode
1454 idmap_name2sid_batch_add1(idmap_query_state_t *state,
1455 	const char *name, const char *dname,
1456 	char **sid, rid_t *rid, int *sid_type, idmap_retcode *rc)
1457 {
1458 	idmap_retcode	retcode;
1459 	int		flen, samAcctNameLen;
1460 	char		*filter = NULL;
1461 	char		*basedn = NULL;
1462 	char		*cp;
1463 
1464 	/*
1465 	 * Strategy: search [the global catalog] for user/group by
1466 	 * sAMAccountName = user/groupname with base DN derived from the
1467 	 * domain name.  The objectSid and objectClass of the result are
1468 	 * all we need to figure out the SID of the user/group and
1469 	 * whether it is a user or a group.
1470 	 */
1471 
1472 	/*
1473 	 * Handle optional domain parameter and default domain
1474 	 * semantics.  The get a basedn from the domainname.
1475 	 */
1476 	if (dname == NULL || *dname != '\0') {
1477 		/* domain name not given separately */
1478 		if ((cp = strchr(name, '@')) == NULL) {
1479 			/* nor is the name qualified */
1480 			dname = state->qadh->owner->dflt_w2k_dom;
1481 			basedn = state->qadh->owner->basedn;
1482 			samAcctNameLen = strlen(name);
1483 		} else {
1484 			/* the name is qualified */
1485 			/* LINTED */
1486 			samAcctNameLen = cp - name;
1487 			dname = cp + 1;
1488 		}
1489 	}
1490 
1491 	if (basedn == NULL)
1492 		basedn = dns2dn(dname);
1493 
1494 	/* Assemble filter */
1495 	flen = snprintf(NULL, 0, SANFILTER, samAcctNameLen, name) + 1;
1496 	if ((filter = (char *)malloc(flen)) == NULL) {
1497 		if (basedn != state->qadh->owner->basedn)
1498 			free(basedn);
1499 		return (IDMAP_ERR_MEMORY);
1500 	}
1501 	(void) snprintf(filter, flen, SANFILTER, samAcctNameLen, name);
1502 
1503 	retcode = idmap_batch_add1(state, 1, filter, basedn,
1504 	    sid, NULL, rid, sid_type, rc);
1505 
1506 	if (basedn != state->qadh->owner->basedn)
1507 		free(basedn);
1508 	free(filter);
1509 
1510 	return (retcode);
1511 }
1512 
1513 idmap_retcode
1514 idmap_sid2name_batch_add1(idmap_query_state_t *state,
1515 	const char *sid, const rid_t *rid,
1516 	char **name, char **dname, int *sid_type, idmap_retcode *rc)
1517 {
1518 	idmap_retcode	retcode;
1519 	int		flen, ret;
1520 	char		*filter = NULL;
1521 	char		cbinsid[MAXHEXBINSID + 1];
1522 
1523 	/*
1524 	 * Strategy: search [the global catalog] for user/group by
1525 	 * objectSid = SID with empty base DN.  The DN, sAMAccountName
1526 	 * and objectClass of the result are all we need to figure out
1527 	 * the name of the SID and whether it is a user, a group or a
1528 	 * computer.
1529 	 */
1530 
1531 	ret = idmap_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
1532 	if (ret != 0)
1533 		return (IDMAP_ERR_SID);
1534 
1535 	/* Assemble filter */
1536 	flen = snprintf(NULL, 0, OBJECTSIDFILTER, cbinsid) + 1;
1537 	if ((filter = (char *)malloc(flen)) == NULL)
1538 		return (IDMAP_ERR_MEMORY);
1539 	(void) snprintf(filter, flen, OBJECTSIDFILTER, cbinsid);
1540 
1541 	retcode = idmap_batch_add1(state, 0, filter, NULL, name, dname,
1542 	    NULL, sid_type, rc);
1543 
1544 	free(filter);
1545 
1546 	return (retcode);
1547 }
1548