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