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