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