xref: /titanic_50/usr/src/cmd/idmap/idmapd/adutils.c (revision 5c51f1241dbbdf2656d0e10011981411ed0c9673)
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			qsize;		/* Queue size */
103 	uint32_t		qcount;		/* Number of queued requests */
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 	assert(ad != NULL);
196 
197 	new_state = calloc(1, sizeof (idmap_query_state_t) +
198 	    (nqueries - 1) * sizeof (idmap_q_t));
199 	if (new_state == NULL)
200 		return (IDMAP_ERR_MEMORY);
201 
202 	if ((rc = adutils_lookup_batch_start(ad, nqueries,
203 	    idmap_ldap_res_search_cb, new_state, &new_state->qs))
204 	    != ADUTILS_SUCCESS) {
205 		free(new_state);
206 		return (map_adrc2idmaprc(rc));
207 	}
208 
209 	new_state->qsize = nqueries;
210 	*state = new_state;
211 	return (IDMAP_SUCCESS);
212 }
213 
214 /*
215  * Set unixuser_attr and unixgroup_attr for AD-based name mapping
216  */
217 void
218 idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
219 		const char *unixuser_attr, const char *unixgroup_attr)
220 {
221 	state->ad_unixuser_attr = unixuser_attr;
222 	state->ad_unixgroup_attr = unixgroup_attr;
223 }
224 
225 /*
226  * Take parsed attribute values from a search result entry and check if
227  * it is the result that was desired and, if so, set the result fields
228  * of the given idmap_q_t.
229  *
230  * Frees the unused char * values.
231  */
232 static
233 void
234 idmap_setqresults(idmap_q_t *q, char *san, char *dn, const char *attr,
235 	char *sid, rid_t rid, int sid_type, char *unixname)
236 {
237 	char *domain;
238 	int err1, err2;
239 
240 	assert(dn != NULL);
241 
242 	if ((domain = adutils_dn2dns(dn)) == NULL)
243 		goto out;
244 
245 	if (q->ecanonname != NULL && san != NULL) {
246 		/* Check that this is the canonname that we were looking for */
247 		if (u8_strcmp(q->ecanonname, san, 0,
248 		    U8_STRCMP_CI_LOWER, /* no normalization, for now */
249 		    U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
250 			goto out;
251 	}
252 
253 	if (q->edomain != NULL) {
254 		/* Check that this is the domain that we were looking for */
255 		if (u8_strcmp(q->edomain, domain, 0, U8_STRCMP_CI_LOWER,
256 		    U8_UNICODE_LATEST, &err2) != 0 || err2 != 0)
257 			goto out;
258 	}
259 
260 	/* Copy the DN and attr and value */
261 	if (q->dn != NULL)
262 		*q->dn = strdup(dn);
263 
264 	if (q->attr != NULL && attr != NULL)
265 		*q->attr = strdup(attr);
266 
267 	if (q->value != NULL && unixname != NULL)
268 		*q->value = strdup(unixname);
269 
270 	/* Set results */
271 	if (q->sid) {
272 		*q->sid = sid;
273 		sid = NULL;
274 	}
275 	if (q->rid)
276 		*q->rid = rid;
277 	if (q->sid_type)
278 		*q->sid_type = sid_type;
279 	if (q->unixname) {
280 		*q->unixname = unixname;
281 		unixname = NULL;
282 	}
283 	if (q->domain != NULL) {
284 		*q->domain = domain;
285 		domain = NULL;
286 	}
287 	if (q->canonname != NULL) {
288 		/*
289 		 * The caller may be replacing the given winname by its
290 		 * canonical name and therefore free any old name before
291 		 * overwriting the field by the canonical name.
292 		 */
293 		free(*q->canonname);
294 		*q->canonname = san;
295 		san = NULL;
296 	}
297 
298 	q->ad_rc = ADUTILS_SUCCESS;
299 
300 out:
301 	/* Free unused attribute values */
302 	free(san);
303 	free(sid);
304 	free(domain);
305 	free(unixname);
306 }
307 
308 #define	BVAL_CASEEQ(bv, str) \
309 		(((*(bv))->bv_len == (sizeof (str) - 1)) && \
310 		    strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
311 
312 /*
313  * Extract the class of the result entry.  Returns 1 on success, 0 on
314  * failure.
315  */
316 static
317 int
318 idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
319 {
320 	BerValue	**cbval;
321 
322 	*sid_type = _IDMAP_T_OTHER;
323 	if (bvalues == NULL)
324 		return (0);
325 
326 	/*
327 	 * We iterate over all the values because computer is a
328 	 * sub-class of user.
329 	 */
330 	for (cbval = bvalues; *cbval != NULL; cbval++) {
331 		if (BVAL_CASEEQ(cbval, "Computer")) {
332 			*sid_type = _IDMAP_T_COMPUTER;
333 			break;
334 		} else if (BVAL_CASEEQ(cbval, "Group")) {
335 			*sid_type = _IDMAP_T_GROUP;
336 			break;
337 		} else if (BVAL_CASEEQ(cbval, "USER")) {
338 			*sid_type = _IDMAP_T_USER;
339 			/* Continue looping -- this may be a computer yet */
340 		}
341 		/*
342 		 * "else if (*sid_type = _IDMAP_T_USER)" then this is a
343 		 * new sub-class of user -- what to do with it??
344 		 */
345 	}
346 
347 	return (1);
348 }
349 
350 /*
351  * Handle a given search result entry
352  */
353 static
354 void
355 idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q,
356 	LDAPMessage *res, LDAP *ld)
357 {
358 	BerElement		*ber = NULL;
359 	BerValue		**bvalues;
360 	char			*attr;
361 	const char		*unixuser_attr = NULL;
362 	const char		*unixgroup_attr = NULL;
363 	char			*unixuser = NULL;
364 	char			*unixgroup = NULL;
365 	char			*dn = NULL;
366 	char			*san = NULL;
367 	char			*sid = NULL;
368 	rid_t			rid = 0;
369 	int			sid_type = _IDMAP_T_UNDEF;
370 	int			has_class, has_san, has_sid;
371 	int			has_unixuser, has_unixgroup;
372 
373 	assert(q->rc != NULL);
374 
375 	if ((dn = ldap_get_dn(ld, res)) == NULL)
376 		return;
377 
378 	assert(q->domain == NULL || *q->domain == NULL);
379 
380 	/*
381 	 * If the caller has requested unixname then determine the
382 	 * AD attribute name that will have the unixname.
383 	 */
384 	if (q->unixname != NULL) {
385 		if (q->eunixtype == _IDMAP_T_USER)
386 			unixuser_attr = state->ad_unixuser_attr;
387 		else if (q->eunixtype == _IDMAP_T_GROUP)
388 			unixgroup_attr = state->ad_unixgroup_attr;
389 		else if (q->eunixtype == _IDMAP_T_UNDEF) {
390 			/*
391 			 * This is the case where we don't know
392 			 * before hand whether we need unixuser
393 			 * or unixgroup. This will be determined
394 			 * by the "sid_type" (i.e whether the given
395 			 * winname is user or group). If sid_type
396 			 * turns out to be user we will return
397 			 * unixuser (if found) and if it is a group
398 			 * we will return unixgroup (if found). We
399 			 * lookup for both ad_unixuser_attr and
400 			 * ad_unixgroup_attr and discard one of them
401 			 * after we know the "sidtype". This
402 			 * supports the following type of lookups.
403 			 *
404 			 * Example:
405 			 *   $idmap show -c winname:foo
406 			 * In the above example, idmap will
407 			 * return uid if winname is winuser
408 			 * and gid if winname is wingroup.
409 			 */
410 			unixuser_attr = state->ad_unixuser_attr;
411 			unixgroup_attr = state->ad_unixgroup_attr;
412 		}
413 	}
414 
415 	has_class = has_san = has_sid = has_unixuser = has_unixgroup = 0;
416 	for (attr = ldap_first_attribute(ld, res, &ber); attr != NULL;
417 	    attr = ldap_next_attribute(ld, res, ber)) {
418 		bvalues = NULL;	/* for memory management below */
419 
420 		/*
421 		 * If this is an attribute we are looking for and
422 		 * haven't seen it yet, parse it
423 		 */
424 		if (q->sid != NULL && !has_sid &&
425 		    strcasecmp(attr, OBJSID) == 0) {
426 			bvalues = ldap_get_values_len(ld, res, attr);
427 			if (bvalues != NULL) {
428 				sid = adutils_bv_objsid2sidstr(
429 				    bvalues[0], &rid);
430 				has_sid = (sid != NULL);
431 			}
432 		} else if (!has_san && strcasecmp(attr, SAN) == 0) {
433 			bvalues = ldap_get_values_len(ld, res, attr);
434 			if (bvalues != NULL) {
435 				san = adutils_bv_name2str(bvalues[0]);
436 				has_san = (san != NULL);
437 			}
438 		} else if (!has_class && strcasecmp(attr, OBJCLASS) == 0) {
439 			bvalues = ldap_get_values_len(ld, res, attr);
440 			has_class = idmap_bv_objclass2sidtype(bvalues,
441 			    &sid_type);
442 			if (has_class && q->unixname != NULL &&
443 			    q->eunixtype == _IDMAP_T_UNDEF) {
444 				/*
445 				 * This is the case where we didn't
446 				 * know whether we wanted unixuser or
447 				 * unixgroup as described above.
448 				 * Now since we know the "sid_type"
449 				 * we discard the unwanted value
450 				 * if it was retrieved before we
451 				 * got here.
452 				 */
453 				if (sid_type == _IDMAP_T_USER) {
454 					free(unixgroup);
455 					unixgroup_attr = unixgroup = NULL;
456 				} else if (sid_type == _IDMAP_T_GROUP) {
457 					free(unixuser);
458 					unixuser_attr = unixuser = NULL;
459 				} else {
460 					free(unixuser);
461 					free(unixgroup);
462 					unixuser_attr = unixuser = NULL;
463 					unixgroup_attr = unixgroup = NULL;
464 				}
465 			}
466 		} else if (!has_unixuser && unixuser_attr != NULL &&
467 		    strcasecmp(attr, unixuser_attr) == 0) {
468 			bvalues = ldap_get_values_len(ld, res, attr);
469 			if (bvalues != NULL) {
470 				unixuser = adutils_bv_name2str(bvalues[0]);
471 				has_unixuser = (unixuser != NULL);
472 			}
473 
474 		} else if (!has_unixgroup && unixgroup_attr != NULL &&
475 		    strcasecmp(attr, unixgroup_attr) == 0) {
476 			bvalues = ldap_get_values_len(ld, res, attr);
477 			if (bvalues != NULL) {
478 				unixgroup = adutils_bv_name2str(bvalues[0]);
479 				has_unixgroup = (unixgroup != NULL);
480 			}
481 		}
482 
483 		if (bvalues != NULL)
484 			ldap_value_free_len(bvalues);
485 		ldap_memfree(attr);
486 
487 		if (has_class && has_san &&
488 		    (q->sid == NULL || has_sid) &&
489 		    (unixuser_attr == NULL || has_unixuser) &&
490 		    (unixgroup_attr == NULL || has_unixgroup)) {
491 			/* Got what we need */
492 			break;
493 		}
494 	}
495 
496 	if (!has_class) {
497 		/*
498 		 * Didn't find objectclass. Something's wrong with our
499 		 * AD data.
500 		 */
501 		free(san);
502 		free(sid);
503 		free(unixuser);
504 		free(unixgroup);
505 	} else {
506 		/*
507 		 * Either we got what we needed and came out of the loop
508 		 * early OR we completed the loop in which case we didn't
509 		 * find some attributes that we were looking for. In either
510 		 * case set the result with what we got.
511 		 */
512 		idmap_setqresults(q, san, dn,
513 		    (unixuser != NULL) ? unixuser_attr : unixgroup_attr,
514 		    sid, rid, sid_type,
515 		    (unixuser != NULL) ? unixuser : unixgroup);
516 	}
517 
518 	if (ber != NULL)
519 		ber_free(ber, 0);
520 
521 	ldap_memfree(dn);
522 }
523 
524 void
525 idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid,
526 		void *argp)
527 {
528 	idmap_query_state_t	*state = (idmap_query_state_t *)argp;
529 	idmap_q_t		*q = &(state->queries[qid]);
530 
531 	switch (rc) {
532 	case LDAP_RES_SEARCH_RESULT:
533 		if (q->search_res != NULL) {
534 			idmap_extract_object(state, q, q->search_res, ld);
535 			(void) ldap_msgfree(q->search_res);
536 			q->search_res = NULL;
537 		} else
538 			q->ad_rc = ADUTILS_ERR_NOTFOUND;
539 		break;
540 	case LDAP_RES_SEARCH_ENTRY:
541 		if (q->search_res == NULL) {
542 			q->search_res = *res;
543 			*res = NULL;
544 		}
545 		break;
546 	default:
547 		break;
548 	}
549 }
550 
551 static
552 void
553 idmap_cleanup_batch(idmap_query_state_t *batch)
554 {
555 	int i;
556 
557 	for (i = 0; i < batch->qcount; i++) {
558 		if (batch->queries[i].ecanonname != NULL)
559 			free(batch->queries[i].ecanonname);
560 		batch->queries[i].ecanonname = NULL;
561 		if (batch->queries[i].edomain != NULL)
562 			free(batch->queries[i].edomain);
563 		batch->queries[i].edomain = NULL;
564 	}
565 }
566 
567 /*
568  * This routine frees the idmap_query_state_t structure
569  */
570 void
571 idmap_lookup_release_batch(idmap_query_state_t **state)
572 {
573 	if (state == NULL || *state == NULL)
574 		return;
575 	adutils_lookup_batch_release(&(*state)->qs);
576 	idmap_cleanup_batch(*state);
577 	free(*state);
578 	*state = NULL;
579 }
580 
581 idmap_retcode
582 idmap_lookup_batch_end(idmap_query_state_t **state)
583 {
584 	adutils_rc		ad_rc;
585 	int			i;
586 	idmap_query_state_t	*id_qs = *state;
587 
588 	ad_rc = adutils_lookup_batch_end(&id_qs->qs);
589 
590 	/*
591 	 * Map adutils rc to idmap_retcode in each
592 	 * query because consumers in dbutils.c
593 	 * expects idmap_retcode.
594 	 */
595 	for (i = 0; i < id_qs->qcount; i++) {
596 		*id_qs->queries[i].rc =
597 		    map_adrc2idmaprc(id_qs->queries[i].ad_rc);
598 	}
599 	idmap_lookup_release_batch(state);
600 	return (map_adrc2idmaprc(ad_rc));
601 }
602 
603 /*
604  * Send one prepared search, queue up msgid, process what results are
605  * available
606  */
607 static
608 idmap_retcode
609 idmap_batch_add1(idmap_query_state_t *state, const char *filter,
610 	char *ecanonname, char *edomain, int eunixtype,
611 	char **dn, char **attr, char **value,
612 	char **canonname, char **dname,
613 	char **sid, rid_t *rid, int *sid_type, char **unixname,
614 	idmap_retcode *rc)
615 {
616 	adutils_rc	ad_rc;
617 	int		qid, i;
618 	idmap_q_t	*q;
619 	static char	*attrs[] = {
620 		SAN,
621 		OBJSID,
622 		OBJCLASS,
623 		NULL,	/* placeholder for unixname attr */
624 		NULL,	/* placeholder for unixname attr */
625 		NULL
626 	};
627 
628 	qid = atomic_inc_32_nv(&state->qcount) - 1;
629 	q = &(state->queries[qid]);
630 
631 	assert(qid < state->qsize);
632 
633 	/*
634 	 * Remember the expected canonname, domainname and unix type
635 	 * so we can check the results * against it
636 	 */
637 	q->ecanonname = ecanonname;
638 	q->edomain = edomain;
639 	q->eunixtype = eunixtype;
640 
641 	/* Remember where to put the results */
642 	q->canonname = canonname;
643 	q->sid = sid;
644 	q->domain = dname;
645 	q->rid = rid;
646 	q->sid_type = sid_type;
647 	q->rc = rc;
648 	q->unixname = unixname;
649 	q->dn = dn;
650 	q->attr = attr;
651 	q->value = value;
652 
653 	/* Add unixuser/unixgroup attribute names to the attrs list */
654 	if (unixname != NULL) {
655 		i = 3;
656 		if (eunixtype != _IDMAP_T_GROUP &&
657 		    state->ad_unixuser_attr != NULL)
658 			attrs[i++] = (char *)state->ad_unixuser_attr;
659 		if (eunixtype != _IDMAP_T_USER &&
660 		    state->ad_unixgroup_attr != NULL)
661 			attrs[i] = (char *)state->ad_unixgroup_attr;
662 	}
663 
664 	/*
665 	 * Provide sane defaults for the results in case we never hear
666 	 * back from the DS before closing the connection.
667 	 *
668 	 * In particular we default the result to indicate a retriable
669 	 * error.  The first complete matching result entry will cause
670 	 * this to be set to IDMAP_SUCCESS, and the end of the results
671 	 * for this search will cause this to indicate "not found" if no
672 	 * result entries arrived or no complete ones matched the lookup
673 	 * we were doing.
674 	 */
675 	*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
676 	if (sid_type != NULL)
677 		*sid_type = _IDMAP_T_OTHER;
678 	if (sid != NULL)
679 		*sid = NULL;
680 	if (dname != NULL)
681 		*dname = NULL;
682 	if (rid != NULL)
683 		*rid = 0;
684 	if (dn != NULL)
685 		*dn = NULL;
686 	if (attr != NULL)
687 		*attr = NULL;
688 	if (value != NULL)
689 		*value = NULL;
690 
691 	/*
692 	 * Don't set *canonname to NULL because it may be pointing to the
693 	 * given winname. Later on if we get a canonical name from AD the
694 	 * old name if any will be freed before assigning the new name.
695 	 */
696 
697 	/*
698 	 * Invoke the mother of all APIs i.e. the adutils API
699 	 */
700 	ad_rc = adutils_lookup_batch_add(state->qs, filter,
701 	    (const char **)attrs,
702 	    edomain, &q->result, &q->ad_rc);
703 	return (map_adrc2idmaprc(ad_rc));
704 }
705 
706 idmap_retcode
707 idmap_name2sid_batch_add1(idmap_query_state_t *state,
708 	const char *name, const char *dname, int eunixtype,
709 	char **dn, char **attr, char **value,
710 	char **canonname, char **sid, rid_t *rid,
711 	int *sid_type, char **unixname, idmap_retcode *rc)
712 {
713 	idmap_retcode	retcode;
714 	int		len, samAcctNameLen;
715 	char		*filter = NULL, *s_name;
716 	char		*ecanonname, *edomain; /* expected canonname */
717 
718 	/*
719 	 * Strategy: search the global catalog for user/group by
720 	 * sAMAccountName = user/groupname with "" as the base DN and by
721 	 * userPrincipalName = user/groupname@domain.  The result
722 	 * entries will be checked to conform to the name and domain
723 	 * name given here.  The DN, sAMAccountName, userPrincipalName,
724 	 * objectSid and objectClass of the result entries are all we
725 	 * need to figure out which entries match the lookup, the SID of
726 	 * the user/group and whether it is a user or a group.
727 	 */
728 
729 	/*
730 	 * We need the name and the domain name separately and as
731 	 * name@domain.  We also allow the domain to be provided
732 	 * separately.
733 	 */
734 	samAcctNameLen = strlen(name);
735 
736 	if ((ecanonname = strdup(name)) == NULL)
737 		return (IDMAP_ERR_MEMORY);
738 
739 	if (dname == NULL || *dname == '\0') {
740 		if ((dname = strchr(name, '@')) != NULL) {
741 			/* 'name' is qualified with a domain name */
742 			if ((edomain = strdup(dname + 1)) == NULL) {
743 				free(ecanonname);
744 				return (IDMAP_ERR_MEMORY);
745 			}
746 			*strchr(ecanonname, '@') = '\0';
747 		} else {
748 			/* 'name' not qualified and dname not given */
749 			dname = adutils_lookup_batch_getdefdomain(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 	if (!adutils_lookup_check_domain(state->qs, dname)) {
769 		free(ecanonname);
770 		free(edomain);
771 		return (IDMAP_ERR_DOMAIN_NOTFOUND);
772 	}
773 
774 	s_name = sanitize_for_ldap_filter(name);
775 	if (s_name == NULL) {
776 		free(ecanonname);
777 		free(edomain);
778 		return (IDMAP_ERR_MEMORY);
779 	}
780 
781 	/* Assemble filter */
782 	len = snprintf(NULL, 0, SANFILTER, samAcctNameLen, s_name) + 1;
783 	if ((filter = (char *)malloc(len)) == NULL) {
784 		free(ecanonname);
785 		free(edomain);
786 		if (s_name != name)
787 			free(s_name);
788 		return (IDMAP_ERR_MEMORY);
789 	}
790 	(void) snprintf(filter, len, SANFILTER, samAcctNameLen, s_name);
791 	if (s_name != name)
792 		free(s_name);
793 
794 	retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
795 	    eunixtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
796 	    unixname, rc);
797 
798 	free(filter);
799 
800 	return (retcode);
801 }
802 
803 idmap_retcode
804 idmap_sid2name_batch_add1(idmap_query_state_t *state,
805 	const char *sid, const rid_t *rid, int eunixtype,
806 	char **dn, char **attr, char **value,
807 	char **name, char **dname, int *sid_type,
808 	char **unixname, idmap_retcode *rc)
809 {
810 	idmap_retcode	retcode;
811 	int		flen, ret;
812 	char		*filter = NULL;
813 	char		cbinsid[ADUTILS_MAXHEXBINSID + 1];
814 
815 	/*
816 	 * Strategy: search [the global catalog] for user/group by
817 	 * objectSid = SID with empty base DN.  The DN, sAMAccountName
818 	 * and objectClass of the result are all we need to figure out
819 	 * the name of the SID and whether it is a user, a group or a
820 	 * computer.
821 	 */
822 
823 	if (!adutils_lookup_check_sid_prefix(state->qs, sid))
824 		return (IDMAP_ERR_DOMAIN_NOTFOUND);
825 
826 	ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
827 	if (ret != 0)
828 		return (IDMAP_ERR_SID);
829 
830 	/* Assemble filter */
831 	flen = snprintf(NULL, 0, OBJSIDFILTER, cbinsid) + 1;
832 	if ((filter = (char *)malloc(flen)) == NULL)
833 		return (IDMAP_ERR_MEMORY);
834 	(void) snprintf(filter, flen, OBJSIDFILTER, cbinsid);
835 
836 	retcode = idmap_batch_add1(state, filter, NULL, NULL, eunixtype,
837 	    dn, attr, value, name, dname, NULL, NULL, sid_type, unixname, rc);
838 
839 	free(filter);
840 
841 	return (retcode);
842 }
843 
844 idmap_retcode
845 idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
846 	const char *unixname, int is_user, int is_wuser,
847 	char **dn, char **attr, char **value,
848 	char **sid, rid_t *rid, char **name,
849 	char **dname, int *sid_type, idmap_retcode *rc)
850 {
851 	idmap_retcode	retcode;
852 	int		len, ulen;
853 	char		*filter = NULL, *s_unixname;
854 	const char	*attrname = NULL;
855 
856 	/* Get unixuser or unixgroup AD attribute name */
857 	attrname = (is_user) ?
858 	    state->ad_unixuser_attr : state->ad_unixgroup_attr;
859 	if (attrname == NULL)
860 		return (IDMAP_ERR_NOTFOUND);
861 
862 	s_unixname = sanitize_for_ldap_filter(unixname);
863 	if (s_unixname == NULL)
864 		return (IDMAP_ERR_MEMORY);
865 
866 	/*  Assemble filter */
867 	ulen = strlen(unixname);
868 	len = snprintf(NULL, 0, "(&(objectclass=%s)(%s=%.*s))",
869 	    is_wuser ? "user" : "group", attrname, ulen, s_unixname) + 1;
870 	if ((filter = (char *)malloc(len)) == NULL) {
871 		if (s_unixname != unixname)
872 			free(s_unixname);
873 		return (IDMAP_ERR_MEMORY);
874 	}
875 	(void) snprintf(filter, len, "(&(objectclass=%s)(%s=%.*s))",
876 	    is_wuser ? "user" : "group", attrname, ulen, s_unixname);
877 	if (s_unixname != unixname)
878 		free(s_unixname);
879 
880 	retcode = idmap_batch_add1(state, filter, NULL, NULL,
881 	    _IDMAP_T_UNDEF, dn, NULL, NULL, name, dname, sid, rid, sid_type,
882 	    NULL, rc);
883 
884 	if (retcode == IDMAP_SUCCESS && attr != NULL) {
885 		if ((*attr = strdup(attrname)) == NULL)
886 			retcode = IDMAP_ERR_MEMORY;
887 	}
888 
889 	if (retcode == IDMAP_SUCCESS && value != NULL) {
890 		if (ulen > 0) {
891 			if ((*value = strdup(unixname)) == NULL)
892 				retcode = IDMAP_ERR_MEMORY;
893 		}
894 		else
895 			*value = NULL;
896 	}
897 
898 	free(filter);
899 
900 	return (retcode);
901 }
902