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