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