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