Home | History | Annotate | Download | only in idmapd
      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