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