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