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