xref: /titanic_51/usr/src/cmd/idmap/idmapd/adutils.c (revision 53a7b6b6763f5865522a76e5e887390a8f4777d7)
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 /*
28  * Processes name2sid & sid2name batched lookups for a given user or
29  * computer from an AD Directory server using GSSAPI authentication
30  */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <alloca.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <lber.h>
38 #include <ldap.h>
39 #include <sasl/sasl.h>
40 #include <string.h>
41 #include <ctype.h>
42 #include <pthread.h>
43 #include <synch.h>
44 #include <atomic.h>
45 #include <errno.h>
46 #include <assert.h>
47 #include <limits.h>
48 #include <time.h>
49 #include <sys/u8_textprep.h>
50 #include "libadutils.h"
51 #include "nldaputils.h"
52 #include "idmapd.h"
53 
54 /* Attribute names and filter format strings */
55 #define	SAN		"sAMAccountName"
56 #define	OBJSID		"objectSid"
57 #define	OBJCLASS	"objectClass"
58 #define	SANFILTER	"(sAMAccountName=%.*s)"
59 #define	OBJSIDFILTER	"(objectSid=%s)"
60 
61 void	idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc,
62 		int qid, void *argp);
63 
64 /*
65  * A place to put the results of a batched (async) query
66  *
67  * There is one of these for every query added to a batch object
68  * (idmap_query_state, see below).
69  */
70 typedef struct idmap_q {
71 	/*
72 	 * data used for validating search result entries for name->SID
73 	 * lookups
74 	 */
75 	char			*ecanonname;	/* expected canon name */
76 	char			*edomain;	/* expected domain name */
77 	int			eunixtype;	/* expected unix type */
78 	/* results */
79 	char			**canonname;	/* actual canon name */
80 	char			**domain;	/* name of domain of object */
81 	char			**sid;		/* stringified SID */
82 	rid_t			*rid;		/* RID */
83 	int			*sid_type;	/* user or group SID? */
84 	char			**unixname;	/* unixname for name mapping */
85 	char			**dn;		/* DN of entry */
86 	char			**attr;		/* Attr for name mapping */
87 	char			**value;	/* value for name mapping */
88 	idmap_retcode		*rc;
89 	adutils_rc		ad_rc;
90 	adutils_result_t	*result;
91 
92 	/*
93 	 * The LDAP search entry result is placed here to be processed
94 	 * when the search done result is received.
95 	 */
96 	LDAPMessage		*search_res;	/* The LDAP search result */
97 } idmap_q_t;
98 
99 /* Batch context structure; typedef is in header file */
100 struct idmap_query_state {
101 	adutils_query_state_t	*qs;
102 	int			qcount;		/* how many queries */
103 	uint32_t		qlastsent;
104 	const char		*ad_unixuser_attr;
105 	const char		*ad_unixgroup_attr;
106 	idmap_q_t		queries[1];	/* array of query results */
107 };
108 
109 static pthread_t	reaperid = 0;
110 
111 /*
112  * Keep connection management simple for now, extend or replace later
113  * with updated libsldap code.
114  */
115 #define	ADREAPERSLEEP	60
116 
117 /*
118  * Idle connection reaping side of connection management
119  *
120  * Every minute wake up and look for connections that have been idle for
121  * five minutes or more and close them.
122  */
123 /*ARGSUSED*/
124 static
125 void
126 adreaper(void *arg)
127 {
128 	timespec_t	ts;
129 
130 	ts.tv_sec = ADREAPERSLEEP;
131 	ts.tv_nsec = 0;
132 
133 	for (;;) {
134 		/*
135 		 * nanosleep(3RT) is thead-safe (no SIGALRM) and more
136 		 * portable than usleep(3C)
137 		 */
138 		(void) nanosleep(&ts, NULL);
139 		adutils_reap_idle_connections();
140 	}
141 }
142 
143 /*
144  * Take ad_host_config_t information, create a ad_host_t,
145  * populate it and add it to the list of hosts.
146  */
147 
148 int
149 idmap_add_ds(adutils_ad_t *ad, const char *host, int port)
150 {
151 	int	ret = -1;
152 
153 	if (adutils_add_ds(ad, host, port) == ADUTILS_SUCCESS)
154 		ret = 0;
155 
156 	/* Start reaper if it doesn't exist */
157 	if (ret == 0 && reaperid == 0)
158 		(void) pthread_create(&reaperid, NULL,
159 		    (void *(*)(void *))adreaper, (void *)NULL);
160 	return (ret);
161 }
162 
163 static
164 idmap_retcode
165 map_adrc2idmaprc(adutils_rc adrc)
166 {
167 	switch (adrc) {
168 	case ADUTILS_SUCCESS:
169 		return (IDMAP_SUCCESS);
170 	case ADUTILS_ERR_NOTFOUND:
171 		return (IDMAP_ERR_NOTFOUND);
172 	case ADUTILS_ERR_MEMORY:
173 		return (IDMAP_ERR_MEMORY);
174 	case ADUTILS_ERR_DOMAIN:
175 		return (IDMAP_ERR_DOMAIN);
176 	case ADUTILS_ERR_OTHER:
177 		return (IDMAP_ERR_OTHER);
178 	case ADUTILS_ERR_RETRIABLE_NET_ERR:
179 		return (IDMAP_ERR_RETRIABLE_NET_ERR);
180 	default:
181 		return (IDMAP_ERR_INTERNAL);
182 	}
183 	/* NOTREACHED */
184 }
185 
186 idmap_retcode
187 idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries,
188 	idmap_query_state_t **state)
189 {
190 	idmap_query_state_t	*new_state;
191 	adutils_rc		rc;
192 
193 	*state = NULL;
194 
195 	if (ad == NULL)
196 		return (IDMAP_ERR_INTERNAL);
197 
198 	new_state = calloc(1, sizeof (idmap_query_state_t) +
199 	    (nqueries - 1) * sizeof (idmap_q_t));
200 	if (new_state == NULL)
201 		return (IDMAP_ERR_MEMORY);
202 
203 	if ((rc = adutils_lookup_batch_start(ad, nqueries,
204 	    idmap_ldap_res_search_cb, new_state, &new_state->qs))
205 	    != ADUTILS_SUCCESS) {
206 		free(new_state);
207 		return (map_adrc2idmaprc(rc));
208 	}
209 
210 	new_state->qcount = nqueries;
211 	*state = new_state;
212 	return (IDMAP_SUCCESS);
213 }
214 
215 /*
216  * Set unixuser_attr and unixgroup_attr for AD-based name mapping
217  */
218 void
219 idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
220 		const char *unixuser_attr, const char *unixgroup_attr)
221 {
222 	state->ad_unixuser_attr = unixuser_attr;
223 	state->ad_unixgroup_attr = unixgroup_attr;
224 }
225 
226 /*
227  * Take parsed attribute values from a search result entry and check if
228  * it is the result that was desired and, if so, set the result fields
229  * of the given idmap_q_t.
230  *
231  * Frees the unused char * values.
232  */
233 static
234 void
235 idmap_setqresults(idmap_q_t *q, char *san, char *dn, const char *attr,
236 	char *sid, rid_t rid, int sid_type, char *unixname)
237 {
238 	char *domain;
239 	int err1, err2;
240 
241 	assert(dn != NULL);
242 
243 	if ((domain = adutils_dn2dns(dn)) == NULL)
244 		goto out;
245 
246 	if (q->ecanonname != NULL && san != NULL) {
247 		/* Check that this is the canonname that we were looking for */
248 		if (u8_strcmp(q->ecanonname, san, 0,
249 		    U8_STRCMP_CI_LOWER, /* no normalization, for now */
250 		    U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
251 			goto out;
252 	}
253 
254 	if (q->edomain != NULL) {
255 		/* Check that this is the domain that we were looking for */
256 		if (u8_strcmp(q->edomain, domain, 0, U8_STRCMP_CI_LOWER,
257 		    U8_UNICODE_LATEST, &err2) != 0 || err2 != 0)
258 			goto out;
259 	}
260 
261 	/* Copy the DN and attr and value */
262 	if (q->dn != NULL)
263 		*q->dn = strdup(dn);
264 
265 	if (q->attr != NULL && attr != NULL)
266 		*q->attr = strdup(attr);
267 
268 	if (q->value != NULL && unixname != NULL)
269 		*q->value = strdup(unixname);
270 
271 	/* Set results */
272 	if (q->sid) {
273 		*q->sid = sid;
274 		sid = NULL;
275 	}
276 	if (q->rid)
277 		*q->rid = rid;
278 	if (q->sid_type)
279 		*q->sid_type = sid_type;
280 	if (q->unixname) {
281 		*q->unixname = unixname;
282 		unixname = NULL;
283 	}
284 	if (q->domain != NULL) {
285 		*q->domain = domain;
286 		domain = NULL;
287 	}
288 	if (q->canonname != NULL) {
289 		/*
290 		 * The caller may be replacing the given winname by its
291 		 * canonical name and therefore free any old name before
292 		 * overwriting the field by the canonical name.
293 		 */
294 		free(*q->canonname);
295 		*q->canonname = san;
296 		san = NULL;
297 	}
298 
299 	q->ad_rc = ADUTILS_SUCCESS;
300 
301 out:
302 	/* Free unused attribute values */
303 	free(san);
304 	free(sid);
305 	free(domain);
306 	free(unixname);
307 }
308 
309 #define	BVAL_CASEEQ(bv, str) \
310 		(((*(bv))->bv_len == (sizeof (str) - 1)) && \
311 		    strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
312 
313 /*
314  * Extract the class of the result entry.  Returns 1 on success, 0 on
315  * failure.
316  */
317 static
318 int
319 idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
320 {
321 	BerValue	**cbval;
322 
323 	*sid_type = _IDMAP_T_OTHER;
324 	if (bvalues == NULL)
325 		return (0);
326 
327 	/*
328 	 * We iterate over all the values because computer is a
329 	 * sub-class of user.
330 	 */
331 	for (cbval = bvalues; *cbval != NULL; cbval++) {
332 		if (BVAL_CASEEQ(cbval, "Computer")) {
333 			*sid_type = _IDMAP_T_COMPUTER;
334 			break;
335 		} else if (BVAL_CASEEQ(cbval, "Group")) {
336 			*sid_type = _IDMAP_T_GROUP;
337 			break;
338 		} else if (BVAL_CASEEQ(cbval, "USER")) {
339 			*sid_type = _IDMAP_T_USER;
340 			/* Continue looping -- this may be a computer yet */
341 		}
342 		/*
343 		 * "else if (*sid_type = _IDMAP_T_USER)" then this is a
344 		 * new sub-class of user -- what to do with it??
345 		 */
346 	}
347 
348 	return (1);
349 }
350 
351 /*
352  * Handle a given search result entry
353  */
354 static
355 void
356 idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q,
357 	LDAPMessage *res, LDAP *ld)
358 {
359 	BerElement		*ber = NULL;
360 	BerValue		**bvalues;
361 	char			*attr;
362 	const char		*unixuser_attr = NULL;
363 	const char		*unixgroup_attr = NULL;
364 	char			*unixuser = NULL;
365 	char			*unixgroup = NULL;
366 	char			*dn = NULL;
367 	char			*san = NULL;
368 	char			*sid = NULL;
369 	rid_t			rid = 0;
370 	int			sid_type = _IDMAP_T_UNDEF;
371 	int			has_class, has_san, has_sid;
372 	int			has_unixuser, has_unixgroup;
373 
374 	assert(q->rc != NULL);
375 
376 	if ((dn = ldap_get_dn(ld, res)) == NULL)
377 		return;
378 
379 	assert(q->domain == NULL || *q->domain == NULL);
380 
381 	/*
382 	 * If the caller has requested unixname then determine the
383 	 * AD attribute name that will have the unixname.
384 	 */
385 	if (q->unixname != NULL) {
386 		if (q->eunixtype == _IDMAP_T_USER)
387 			unixuser_attr = state->ad_unixuser_attr;
388 		else if (q->eunixtype == _IDMAP_T_GROUP)
389 			unixgroup_attr = state->ad_unixgroup_attr;
390 		else if (q->eunixtype == _IDMAP_T_UNDEF) {
391 			/*
392 			 * This is the case where we don't know
393 			 * before hand whether we need unixuser
394 			 * or unixgroup. This will be determined
395 			 * by the "sid_type" (i.e whether the given
396 			 * winname is user or group). If sid_type
397 			 * turns out to be user we will return
398 			 * unixuser (if found) and if it is a group
399 			 * we will return unixgroup (if found). We
400 			 * lookup for both ad_unixuser_attr and
401 			 * ad_unixgroup_attr and discard one of them
402 			 * after we know the "sidtype". This
403 			 * supports the following type of lookups.
404 			 *
405 			 * Example:
406 			 *   $idmap show -c winname:foo
407 			 * In the above example, idmap will
408 			 * return uid if winname is winuser
409 			 * and gid if winname is wingroup.
410 			 */
411 			unixuser_attr = state->ad_unixuser_attr;
412 			unixgroup_attr = state->ad_unixgroup_attr;
413 		}
414 	}
415 
416 	has_class = has_san = has_sid = has_unixuser = has_unixgroup = 0;
417 	for (attr = ldap_first_attribute(ld, res, &ber); attr != NULL;
418 	    attr = ldap_next_attribute(ld, res, ber)) {
419 		bvalues = NULL;	/* for memory management below */
420 
421 		/*
422 		 * If this is an attribute we are looking for and
423 		 * haven't seen it yet, parse it
424 		 */
425 		if (q->sid != NULL && !has_sid &&
426 		    strcasecmp(attr, OBJSID) == 0) {
427 			bvalues = ldap_get_values_len(ld, res, attr);
428 			if (bvalues != NULL) {
429 				sid = adutils_bv_objsid2sidstr(
430 				    bvalues[0], &rid);
431 				has_sid = (sid != NULL);
432 			}
433 		} else if (!has_san && strcasecmp(attr, SAN) == 0) {
434 			bvalues = ldap_get_values_len(ld, res, attr);
435 			if (bvalues != NULL) {
436 				san = adutils_bv_name2str(bvalues[0]);
437 				has_san = (san != NULL);
438 			}
439 		} else if (!has_class && strcasecmp(attr, OBJCLASS) == 0) {
440 			bvalues = ldap_get_values_len(ld, res, attr);
441 			has_class = idmap_bv_objclass2sidtype(bvalues,
442 			    &sid_type);
443 			if (has_class && q->unixname != NULL &&
444 			    q->eunixtype == _IDMAP_T_UNDEF) {
445 				/*
446 				 * This is the case where we didn't
447 				 * know whether we wanted unixuser or
448 				 * unixgroup as described above.
449 				 * Now since we know the "sid_type"
450 				 * we discard the unwanted value
451 				 * if it was retrieved before we
452 				 * got here.
453 				 */
454 				if (sid_type == _IDMAP_T_USER) {
455 					free(unixgroup);
456 					unixgroup_attr = unixgroup = NULL;
457 				} else if (sid_type == _IDMAP_T_GROUP) {
458 					free(unixuser);
459 					unixuser_attr = unixuser = NULL;
460 				} else {
461 					free(unixuser);
462 					free(unixgroup);
463 					unixuser_attr = unixuser = NULL;
464 					unixgroup_attr = unixgroup = NULL;
465 				}
466 			}
467 		} else if (!has_unixuser && unixuser_attr != NULL &&
468 		    strcasecmp(attr, unixuser_attr) == 0) {
469 			bvalues = ldap_get_values_len(ld, res, attr);
470 			if (bvalues != NULL) {
471 				unixuser = adutils_bv_name2str(bvalues[0]);
472 				has_unixuser = (unixuser != NULL);
473 			}
474 
475 		} else if (!has_unixgroup && unixgroup_attr != NULL &&
476 		    strcasecmp(attr, unixgroup_attr) == 0) {
477 			bvalues = ldap_get_values_len(ld, res, attr);
478 			if (bvalues != NULL) {
479 				unixgroup = adutils_bv_name2str(bvalues[0]);
480 				has_unixgroup = (unixgroup != NULL);
481 			}
482 		}
483 
484 		if (bvalues != NULL)
485 			ldap_value_free_len(bvalues);
486 		ldap_memfree(attr);
487 
488 		if (has_class && has_san &&
489 		    (q->sid == NULL || has_sid) &&
490 		    (unixuser_attr == NULL || has_unixuser) &&
491 		    (unixgroup_attr == NULL || has_unixgroup)) {
492 			/* Got what we need */
493 			break;
494 		}
495 	}
496 
497 	if (!has_class) {
498 		/*
499 		 * Didn't find objectclass. Something's wrong with our
500 		 * AD data.
501 		 */
502 		free(san);
503 		free(sid);
504 		free(unixuser);
505 		free(unixgroup);
506 	} else {
507 		/*
508 		 * Either we got what we needed and came out of the loop
509 		 * early OR we completed the loop in which case we didn't
510 		 * find some attributes that we were looking for. In either
511 		 * case set the result with what we got.
512 		 */
513 		idmap_setqresults(q, san, dn,
514 		    (unixuser != NULL) ? unixuser_attr : unixgroup_attr,
515 		    sid, rid, sid_type,
516 		    (unixuser != NULL) ? unixuser : unixgroup);
517 	}
518 
519 	if (ber != NULL)
520 		ber_free(ber, 0);
521 
522 	ldap_memfree(dn);
523 }
524 
525 void
526 idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid,
527 		void *argp)
528 {
529 	idmap_query_state_t	*state = (idmap_query_state_t *)argp;
530 	idmap_q_t		*q = &(state->queries[qid]);
531 
532 	switch (rc) {
533 	case LDAP_RES_SEARCH_RESULT:
534 		if (q->search_res != NULL) {
535 			idmap_extract_object(state, q, q->search_res, ld);
536 			(void) ldap_msgfree(q->search_res);
537 			q->search_res = NULL;
538 		} else
539 			q->ad_rc = ADUTILS_ERR_NOTFOUND;
540 		break;
541 	case LDAP_RES_SEARCH_ENTRY:
542 		if (q->search_res == NULL) {
543 			q->search_res = *res;
544 			*res = NULL;
545 		}
546 		break;
547 	default:
548 		break;
549 	}
550 }
551 
552 static
553 void
554 idmap_cleanup_batch(idmap_query_state_t *batch)
555 {
556 	int i;
557 
558 	for (i = 0; i < batch->qcount; i++) {
559 		if (batch->queries[i].ecanonname != NULL)
560 			free(batch->queries[i].ecanonname);
561 		batch->queries[i].ecanonname = NULL;
562 		if (batch->queries[i].edomain != NULL)
563 			free(batch->queries[i].edomain);
564 		batch->queries[i].edomain = NULL;
565 	}
566 }
567 
568 /*
569  * This routine frees the idmap_query_state_t structure
570  */
571 void
572 idmap_lookup_release_batch(idmap_query_state_t **state)
573 {
574 	if (state == NULL || *state == NULL)
575 		return;
576 	adutils_lookup_batch_release(&(*state)->qs);
577 	idmap_cleanup_batch(*state);
578 	free(*state);
579 	*state = NULL;
580 }
581 
582 idmap_retcode
583 idmap_lookup_batch_end(idmap_query_state_t **state)
584 {
585 	adutils_rc		ad_rc;
586 	int			i;
587 	idmap_query_state_t	*id_qs = *state;
588 
589 	ad_rc = adutils_lookup_batch_end(&id_qs->qs);
590 
591 	/*
592 	 * Map adutils rc to idmap_retcode in each
593 	 * query because consumers in dbutils.c
594 	 * expects idmap_retcode.
595 	 */
596 	for (i = 0; i < id_qs->qcount; i++) {
597 		*id_qs->queries[i].rc =
598 		    map_adrc2idmaprc(id_qs->queries[i].ad_rc);
599 	}
600 	idmap_lookup_release_batch(state);
601 	return (map_adrc2idmaprc(ad_rc));
602 }
603 
604 /*
605  * Send one prepared search, queue up msgid, process what results are
606  * available
607  */
608 static
609 idmap_retcode
610 idmap_batch_add1(idmap_query_state_t *state, const char *filter,
611 	char *ecanonname, char *edomain, int eunixtype,
612 	char **dn, char **attr, char **value,
613 	char **canonname, char **dname,
614 	char **sid, rid_t *rid, int *sid_type, char **unixname,
615 	idmap_retcode *rc)
616 {
617 	adutils_rc	ad_rc;
618 	int		qid, i;
619 	idmap_q_t	*q;
620 	static char	*attrs[] = {
621 		SAN,
622 		OBJSID,
623 		OBJCLASS,
624 		NULL,	/* placeholder for unixname attr */
625 		NULL,	/* placeholder for unixname attr */
626 		NULL
627 	};
628 
629 	qid = atomic_inc_32_nv(&state->qlastsent) - 1;
630 	q = &(state->queries[qid]);
631 
632 	/*
633 	 * Remember the expected canonname, domainname and unix type
634 	 * so we can check the results * against it
635 	 */
636 	q->ecanonname = ecanonname;
637 	q->edomain = edomain;
638 	q->eunixtype = eunixtype;
639 
640 	/* Remember where to put the results */
641 	q->canonname = canonname;
642 	q->sid = sid;
643 	q->domain = dname;
644 	q->rid = rid;
645 	q->sid_type = sid_type;
646 	q->rc = rc;
647 	q->unixname = unixname;
648 	q->dn = dn;
649 	q->attr = attr;
650 	q->value = value;
651 
652 	/* Add unixuser/unixgroup attribute names to the attrs list */
653 	if (unixname != NULL) {
654 		i = 3;
655 		if (eunixtype != _IDMAP_T_GROUP &&
656 		    state->ad_unixuser_attr != NULL)
657 			attrs[i++] = (char *)state->ad_unixuser_attr;
658 		if (eunixtype != _IDMAP_T_USER &&
659 		    state->ad_unixgroup_attr != NULL)
660 			attrs[i] = (char *)state->ad_unixgroup_attr;
661 	}
662 
663 	/*
664 	 * Provide sane defaults for the results in case we never hear
665 	 * back from the DS before closing the connection.
666 	 *
667 	 * In particular we default the result to indicate a retriable
668 	 * error.  The first complete matching result entry will cause
669 	 * this to be set to IDMAP_SUCCESS, and the end of the results
670 	 * for this search will cause this to indicate "not found" if no
671 	 * result entries arrived or no complete ones matched the lookup
672 	 * we were doing.
673 	 */
674 	*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
675 	if (sid_type != NULL)
676 		*sid_type = _IDMAP_T_OTHER;
677 	if (sid != NULL)
678 		*sid = NULL;
679 	if (dname != NULL)
680 		*dname = NULL;
681 	if (rid != NULL)
682 		*rid = 0;
683 	if (dn != NULL)
684 		*dn = NULL;
685 	if (attr != NULL)
686 		*attr = NULL;
687 	if (value != NULL)
688 		*value = NULL;
689 
690 	/*
691 	 * Don't set *canonname to NULL because it may be pointing to the
692 	 * given winname. Later on if we get a canonical name from AD the
693 	 * old name if any will be freed before assigning the new name.
694 	 */
695 
696 	/*
697 	 * Invoke the mother of all APIs i.e. the adutils API
698 	 */
699 	ad_rc = adutils_lookup_batch_add(state->qs, filter,
700 	    (const char **)attrs,
701 	    edomain, &q->result, &q->ad_rc);
702 	return (map_adrc2idmaprc(ad_rc));
703 }
704 
705 idmap_retcode
706 idmap_name2sid_batch_add1(idmap_query_state_t *state,
707 	const char *name, const char *dname, int eunixtype,
708 	char **dn, char **attr, char **value,
709 	char **canonname, char **sid, rid_t *rid,
710 	int *sid_type, char **unixname, idmap_retcode *rc)
711 {
712 	idmap_retcode	retcode;
713 	int		len, samAcctNameLen;
714 	char		*filter = NULL, *s_name;
715 	char		*ecanonname, *edomain; /* expected canonname */
716 
717 	/*
718 	 * Strategy: search the global catalog for user/group by
719 	 * sAMAccountName = user/groupname with "" as the base DN and by
720 	 * userPrincipalName = user/groupname@domain.  The result
721 	 * entries will be checked to conform to the name and domain
722 	 * name given here.  The DN, sAMAccountName, userPrincipalName,
723 	 * objectSid and objectClass of the result entries are all we
724 	 * need to figure out which entries match the lookup, the SID of
725 	 * the user/group and whether it is a user or a group.
726 	 */
727 
728 	/*
729 	 * We need the name and the domain name separately and as
730 	 * name@domain.  We also allow the domain to be provided
731 	 * separately.
732 	 */
733 	samAcctNameLen = strlen(name);
734 
735 	if ((ecanonname = strdup(name)) == NULL)
736 		return (IDMAP_ERR_MEMORY);
737 
738 	if (dname == NULL || *dname == '\0') {
739 		if ((dname = strchr(name, '@')) != NULL) {
740 			/* 'name' is qualified with a domain name */
741 			if ((edomain = strdup(dname + 1)) == NULL) {
742 				free(ecanonname);
743 				return (IDMAP_ERR_MEMORY);
744 			}
745 			*strchr(ecanonname, '@') = '\0';
746 		} else {
747 			/* 'name' not qualified and dname not given */
748 			dname = adutils_lookup_batch_getdefdomain(
749 			    state->qs);
750 			assert(dname != NULL);
751 			if (*dname == '\0') {
752 				free(ecanonname);
753 				return (IDMAP_ERR_DOMAIN);
754 			}
755 			edomain = strdup(dname);
756 			if (edomain == NULL) {
757 				free(ecanonname);
758 				return (IDMAP_ERR_MEMORY);
759 			}
760 		}
761 	} else {
762 		if ((edomain = strdup(dname)) == NULL) {
763 			free(ecanonname);
764 			return (IDMAP_ERR_MEMORY);
765 		}
766 	}
767 
768 	s_name = sanitize_for_ldap_filter(name);
769 	if (s_name == NULL) {
770 		free(ecanonname);
771 		free(edomain);
772 		return (IDMAP_ERR_MEMORY);
773 	}
774 
775 	/* Assemble filter */
776 	len = snprintf(NULL, 0, SANFILTER, samAcctNameLen, s_name) + 1;
777 	if ((filter = (char *)malloc(len)) == NULL) {
778 		free(ecanonname);
779 		free(edomain);
780 		if (s_name != name)
781 			free(s_name);
782 		return (IDMAP_ERR_MEMORY);
783 	}
784 	(void) snprintf(filter, len, SANFILTER, samAcctNameLen, s_name);
785 	if (s_name != name)
786 		free(s_name);
787 
788 	retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
789 	    eunixtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
790 	    unixname, rc);
791 
792 	free(filter);
793 
794 	return (retcode);
795 }
796 
797 idmap_retcode
798 idmap_sid2name_batch_add1(idmap_query_state_t *state,
799 	const char *sid, const rid_t *rid, int eunixtype,
800 	char **dn, char **attr, char **value,
801 	char **name, char **dname, int *sid_type,
802 	char **unixname, idmap_retcode *rc)
803 {
804 	idmap_retcode	retcode;
805 	int		flen, ret;
806 	char		*filter = NULL;
807 	char		cbinsid[ADUTILS_MAXHEXBINSID + 1];
808 
809 	/*
810 	 * Strategy: search [the global catalog] for user/group by
811 	 * objectSid = SID with empty base DN.  The DN, sAMAccountName
812 	 * and objectClass of the result are all we need to figure out
813 	 * the name of the SID and whether it is a user, a group or a
814 	 * computer.
815 	 */
816 
817 	ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
818 	if (ret != 0)
819 		return (IDMAP_ERR_SID);
820 
821 	/* Assemble filter */
822 	flen = snprintf(NULL, 0, OBJSIDFILTER, cbinsid) + 1;
823 	if ((filter = (char *)malloc(flen)) == NULL)
824 		return (IDMAP_ERR_MEMORY);
825 	(void) snprintf(filter, flen, OBJSIDFILTER, cbinsid);
826 
827 	retcode = idmap_batch_add1(state, filter, NULL, NULL, eunixtype,
828 	    dn, attr, value, name, dname, NULL, NULL, sid_type, unixname, rc);
829 
830 	free(filter);
831 
832 	return (retcode);
833 }
834 
835 idmap_retcode
836 idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
837 	const char *unixname, int is_user, int is_wuser,
838 	char **dn, char **attr, char **value,
839 	char **sid, rid_t *rid, char **name,
840 	char **dname, int *sid_type, idmap_retcode *rc)
841 {
842 	idmap_retcode	retcode;
843 	int		len, ulen;
844 	char		*filter = NULL, *s_unixname;
845 	const char	*attrname = NULL;
846 
847 	/* Get unixuser or unixgroup AD attribute name */
848 	attrname = (is_user) ?
849 	    state->ad_unixuser_attr : state->ad_unixgroup_attr;
850 	if (attrname == NULL)
851 		return (IDMAP_ERR_NOTFOUND);
852 
853 	s_unixname = sanitize_for_ldap_filter(unixname);
854 	if (s_unixname == NULL)
855 		return (IDMAP_ERR_MEMORY);
856 
857 	/*  Assemble filter */
858 	ulen = strlen(unixname);
859 	len = snprintf(NULL, 0, "(&(objectclass=%s)(%s=%.*s))",
860 	    is_wuser ? "user" : "group", attrname, ulen, s_unixname) + 1;
861 	if ((filter = (char *)malloc(len)) == NULL) {
862 		if (s_unixname != unixname)
863 			free(s_unixname);
864 		return (IDMAP_ERR_MEMORY);
865 	}
866 	(void) snprintf(filter, len, "(&(objectclass=%s)(%s=%.*s))",
867 	    is_wuser ? "user" : "group", attrname, ulen, s_unixname);
868 	if (s_unixname != unixname)
869 		free(s_unixname);
870 
871 	retcode = idmap_batch_add1(state, filter, NULL, NULL,
872 	    _IDMAP_T_UNDEF, dn, NULL, NULL, name, dname, sid, rid, sid_type,
873 	    NULL, rc);
874 
875 	if (retcode == IDMAP_SUCCESS && attr != NULL) {
876 		if ((*attr = strdup(attrname)) == NULL)
877 			retcode = IDMAP_ERR_MEMORY;
878 	}
879 
880 	if (retcode == IDMAP_SUCCESS && value != NULL) {
881 		if (ulen > 0) {
882 			if ((*value = strdup(unixname)) == NULL)
883 				retcode = IDMAP_ERR_MEMORY;
884 		}
885 		else
886 			*value = NULL;
887 	}
888 
889 	free(filter);
890 
891 	return (retcode);
892 }
893