/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Processes name2sid & sid2name batched lookups for a given user or
 * computer from an AD Directory server using GSSAPI authentication
 */

#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <string.h>
#include <strings.h>
#include <lber.h>
#include <ldap.h>
#include <sasl/sasl.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <synch.h>
#include <atomic.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include <time.h>
#include <sys/u8_textprep.h>
#include "libadutils.h"
#include "nldaputils.h"
#include "idmapd.h"

/* Attribute names and filter format strings */
#define	SAN		"sAMAccountName"
#define	OBJSID		"objectSid"
#define	OBJCLASS	"objectClass"
#define	SANFILTER	"(sAMAccountName=%.*s)"
#define	OBJSIDFILTER	"(objectSid=%s)"

void	idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc,
		int qid, void *argp);

/*
 * A place to put the results of a batched (async) query
 *
 * There is one of these for every query added to a batch object
 * (idmap_query_state, see below).
 */
typedef struct idmap_q {
	/*
	 * data used for validating search result entries for name->SID
	 * lookups
	 */
	char			*ecanonname;	/* expected canon name */
	char			*edomain;	/* expected domain name */
	int			eunixtype;	/* expected unix type */
	/* results */
	char			**canonname;	/* actual canon name */
	char			**domain;	/* name of domain of object */
	char			**sid;		/* stringified SID */
	rid_t			*rid;		/* RID */
	int			*sid_type;	/* user or group SID? */
	char			**unixname;	/* unixname for name mapping */
	char			**dn;		/* DN of entry */
	char			**attr;		/* Attr for name mapping */
	char			**value;	/* value for name mapping */
	idmap_retcode		*rc;
	adutils_rc		ad_rc;
	adutils_result_t	*result;

	/*
	 * The LDAP search entry result is placed here to be processed
	 * when the search done result is received.
	 */
	LDAPMessage		*search_res;	/* The LDAP search result */
} idmap_q_t;

/* Batch context structure; typedef is in header file */
struct idmap_query_state {
	adutils_query_state_t	*qs;
	int			qsize;		/* Queue size */
	uint32_t		qcount;		/* Number of queued requests */
	const char		*ad_unixuser_attr;
	const char		*ad_unixgroup_attr;
	idmap_q_t		queries[1];	/* array of query results */
};

static pthread_t	reaperid = 0;

/*
 * Keep connection management simple for now, extend or replace later
 * with updated libsldap code.
 */
#define	ADREAPERSLEEP	60

/*
 * Idle connection reaping side of connection management
 *
 * Every minute wake up and look for connections that have been idle for
 * five minutes or more and close them.
 */
/*ARGSUSED*/
static
void
adreaper(void *arg)
{
	timespec_t	ts;

	ts.tv_sec = ADREAPERSLEEP;
	ts.tv_nsec = 0;

	for (;;) {
		/*
		 * nanosleep(3RT) is thead-safe (no SIGALRM) and more
		 * portable than usleep(3C)
		 */
		(void) nanosleep(&ts, NULL);
		adutils_reap_idle_connections();
	}
}

/*
 * Take ad_host_config_t information, create a ad_host_t,
 * populate it and add it to the list of hosts.
 */

int
idmap_add_ds(adutils_ad_t *ad, const char *host, int port)
{
	int	ret = -1;

	if (adutils_add_ds(ad, host, port) == ADUTILS_SUCCESS)
		ret = 0;

	/* Start reaper if it doesn't exist */
	if (ret == 0 && reaperid == 0)
		(void) pthread_create(&reaperid, NULL,
		    (void *(*)(void *))adreaper, (void *)NULL);
	return (ret);
}

static
idmap_retcode
map_adrc2idmaprc(adutils_rc adrc)
{
	switch (adrc) {
	case ADUTILS_SUCCESS:
		return (IDMAP_SUCCESS);
	case ADUTILS_ERR_NOTFOUND:
		return (IDMAP_ERR_NOTFOUND);
	case ADUTILS_ERR_MEMORY:
		return (IDMAP_ERR_MEMORY);
	case ADUTILS_ERR_DOMAIN:
		return (IDMAP_ERR_DOMAIN);
	case ADUTILS_ERR_OTHER:
		return (IDMAP_ERR_OTHER);
	case ADUTILS_ERR_RETRIABLE_NET_ERR:
		return (IDMAP_ERR_RETRIABLE_NET_ERR);
	default:
		return (IDMAP_ERR_INTERNAL);
	}
	/* NOTREACHED */
}

idmap_retcode
idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries,
	idmap_query_state_t **state)
{
	idmap_query_state_t	*new_state;
	adutils_rc		rc;

	*state = NULL;

	assert(ad != NULL);

	new_state = calloc(1, sizeof (idmap_query_state_t) +
	    (nqueries - 1) * sizeof (idmap_q_t));
	if (new_state == NULL)
		return (IDMAP_ERR_MEMORY);

	if ((rc = adutils_lookup_batch_start(ad, nqueries,
	    idmap_ldap_res_search_cb, new_state, &new_state->qs))
	    != ADUTILS_SUCCESS) {
		free(new_state);
		return (map_adrc2idmaprc(rc));
	}

	new_state->qsize = nqueries;
	*state = new_state;
	return (IDMAP_SUCCESS);
}

/*
 * Set unixuser_attr and unixgroup_attr for AD-based name mapping
 */
void
idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
		const char *unixuser_attr, const char *unixgroup_attr)
{
	state->ad_unixuser_attr = unixuser_attr;
	state->ad_unixgroup_attr = unixgroup_attr;
}

/*
 * Take parsed attribute values from a search result entry and check if
 * it is the result that was desired and, if so, set the result fields
 * of the given idmap_q_t.
 *
 * Frees the unused char * values.
 */
static
void
idmap_setqresults(idmap_q_t *q, char *san, char *dn, const char *attr,
	char *sid, rid_t rid, int sid_type, char *unixname)
{
	char *domain;
	int err1, err2;

	assert(dn != NULL);

	if ((domain = adutils_dn2dns(dn)) == NULL)
		goto out;

	if (q->ecanonname != NULL && san != NULL) {
		/* Check that this is the canonname that we were looking for */
		if (u8_strcmp(q->ecanonname, san, 0,
		    U8_STRCMP_CI_LOWER, /* no normalization, for now */
		    U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
			goto out;
	}

	if (q->edomain != NULL) {
		/* Check that this is the domain that we were looking for */
		if (u8_strcmp(q->edomain, domain, 0, U8_STRCMP_CI_LOWER,
		    U8_UNICODE_LATEST, &err2) != 0 || err2 != 0)
			goto out;
	}

	/* Copy the DN and attr and value */
	if (q->dn != NULL)
		*q->dn = strdup(dn);

	if (q->attr != NULL && attr != NULL)
		*q->attr = strdup(attr);

	if (q->value != NULL && unixname != NULL)
		*q->value = strdup(unixname);

	/* Set results */
	if (q->sid) {
		*q->sid = sid;
		sid = NULL;
	}
	if (q->rid)
		*q->rid = rid;
	if (q->sid_type)
		*q->sid_type = sid_type;
	if (q->unixname) {
		*q->unixname = unixname;
		unixname = NULL;
	}
	if (q->domain != NULL) {
		*q->domain = domain;
		domain = NULL;
	}
	if (q->canonname != NULL) {
		/*
		 * The caller may be replacing the given winname by its
		 * canonical name and therefore free any old name before
		 * overwriting the field by the canonical name.
		 */
		free(*q->canonname);
		*q->canonname = san;
		san = NULL;
	}

	q->ad_rc = ADUTILS_SUCCESS;

out:
	/* Free unused attribute values */
	free(san);
	free(sid);
	free(domain);
	free(unixname);
}

#define	BVAL_CASEEQ(bv, str) \
		(((*(bv))->bv_len == (sizeof (str) - 1)) && \
		    strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)

/*
 * Extract the class of the result entry.  Returns 1 on success, 0 on
 * failure.
 */
static
int
idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
{
	BerValue	**cbval;

	*sid_type = _IDMAP_T_OTHER;
	if (bvalues == NULL)
		return (0);

	/*
	 * We iterate over all the values because computer is a
	 * sub-class of user.
	 */
	for (cbval = bvalues; *cbval != NULL; cbval++) {
		if (BVAL_CASEEQ(cbval, "Computer")) {
			*sid_type = _IDMAP_T_COMPUTER;
			break;
		} else if (BVAL_CASEEQ(cbval, "Group")) {
			*sid_type = _IDMAP_T_GROUP;
			break;
		} else if (BVAL_CASEEQ(cbval, "USER")) {
			*sid_type = _IDMAP_T_USER;
			/* Continue looping -- this may be a computer yet */
		}
		/*
		 * "else if (*sid_type = _IDMAP_T_USER)" then this is a
		 * new sub-class of user -- what to do with it??
		 */
	}

	return (1);
}

/*
 * Handle a given search result entry
 */
static
void
idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q,
	LDAPMessage *res, LDAP *ld)
{
	BerElement		*ber = NULL;
	BerValue		**bvalues;
	char			*attr;
	const char		*unixuser_attr = NULL;
	const char		*unixgroup_attr = NULL;
	char			*unixuser = NULL;
	char			*unixgroup = NULL;
	char			*dn = NULL;
	char			*san = NULL;
	char			*sid = NULL;
	rid_t			rid = 0;
	int			sid_type = _IDMAP_T_UNDEF;
	int			has_class, has_san, has_sid;
	int			has_unixuser, has_unixgroup;

	assert(q->rc != NULL);

	if ((dn = ldap_get_dn(ld, res)) == NULL)
		return;

	assert(q->domain == NULL || *q->domain == NULL);

	/*
	 * If the caller has requested unixname then determine the
	 * AD attribute name that will have the unixname.
	 */
	if (q->unixname != NULL) {
		if (q->eunixtype == _IDMAP_T_USER)
			unixuser_attr = state->ad_unixuser_attr;
		else if (q->eunixtype == _IDMAP_T_GROUP)
			unixgroup_attr = state->ad_unixgroup_attr;
		else if (q->eunixtype == _IDMAP_T_UNDEF) {
			/*
			 * This is the case where we don't know
			 * before hand whether we need unixuser
			 * or unixgroup. This will be determined
			 * by the "sid_type" (i.e whether the given
			 * winname is user or group). If sid_type
			 * turns out to be user we will return
			 * unixuser (if found) and if it is a group
			 * we will return unixgroup (if found). We
			 * lookup for both ad_unixuser_attr and
			 * ad_unixgroup_attr and discard one of them
			 * after we know the "sidtype". This
			 * supports the following type of lookups.
			 *
			 * Example:
			 *   $idmap show -c winname:foo
			 * In the above example, idmap will
			 * return uid if winname is winuser
			 * and gid if winname is wingroup.
			 */
			unixuser_attr = state->ad_unixuser_attr;
			unixgroup_attr = state->ad_unixgroup_attr;
		}
	}

	has_class = has_san = has_sid = has_unixuser = has_unixgroup = 0;
	for (attr = ldap_first_attribute(ld, res, &ber); attr != NULL;
	    attr = ldap_next_attribute(ld, res, ber)) {
		bvalues = NULL;	/* for memory management below */

		/*
		 * If this is an attribute we are looking for and
		 * haven't seen it yet, parse it
		 */
		if (q->sid != NULL && !has_sid &&
		    strcasecmp(attr, OBJSID) == 0) {
			bvalues = ldap_get_values_len(ld, res, attr);
			if (bvalues != NULL) {
				sid = adutils_bv_objsid2sidstr(
				    bvalues[0], &rid);
				has_sid = (sid != NULL);
			}
		} else if (!has_san && strcasecmp(attr, SAN) == 0) {
			bvalues = ldap_get_values_len(ld, res, attr);
			if (bvalues != NULL) {
				san = adutils_bv_name2str(bvalues[0]);
				has_san = (san != NULL);
			}
		} else if (!has_class && strcasecmp(attr, OBJCLASS) == 0) {
			bvalues = ldap_get_values_len(ld, res, attr);
			has_class = idmap_bv_objclass2sidtype(bvalues,
			    &sid_type);
			if (has_class && q->unixname != NULL &&
			    q->eunixtype == _IDMAP_T_UNDEF) {
				/*
				 * This is the case where we didn't
				 * know whether we wanted unixuser or
				 * unixgroup as described above.
				 * Now since we know the "sid_type"
				 * we discard the unwanted value
				 * if it was retrieved before we
				 * got here.
				 */
				if (sid_type == _IDMAP_T_USER) {
					free(unixgroup);
					unixgroup_attr = unixgroup = NULL;
				} else if (sid_type == _IDMAP_T_GROUP) {
					free(unixuser);
					unixuser_attr = unixuser = NULL;
				} else {
					free(unixuser);
					free(unixgroup);
					unixuser_attr = unixuser = NULL;
					unixgroup_attr = unixgroup = NULL;
				}
			}
		} else if (!has_unixuser && unixuser_attr != NULL &&
		    strcasecmp(attr, unixuser_attr) == 0) {
			bvalues = ldap_get_values_len(ld, res, attr);
			if (bvalues != NULL) {
				unixuser = adutils_bv_name2str(bvalues[0]);
				has_unixuser = (unixuser != NULL);
			}

		} else if (!has_unixgroup && unixgroup_attr != NULL &&
		    strcasecmp(attr, unixgroup_attr) == 0) {
			bvalues = ldap_get_values_len(ld, res, attr);
			if (bvalues != NULL) {
				unixgroup = adutils_bv_name2str(bvalues[0]);
				has_unixgroup = (unixgroup != NULL);
			}
		}

		if (bvalues != NULL)
			ldap_value_free_len(bvalues);
		ldap_memfree(attr);

		if (has_class && has_san &&
		    (q->sid == NULL || has_sid) &&
		    (unixuser_attr == NULL || has_unixuser) &&
		    (unixgroup_attr == NULL || has_unixgroup)) {
			/* Got what we need */
			break;
		}
	}

	if (!has_class) {
		/*
		 * Didn't find objectclass. Something's wrong with our
		 * AD data.
		 */
		free(san);
		free(sid);
		free(unixuser);
		free(unixgroup);
	} else {
		/*
		 * Either we got what we needed and came out of the loop
		 * early OR we completed the loop in which case we didn't
		 * find some attributes that we were looking for. In either
		 * case set the result with what we got.
		 */
		idmap_setqresults(q, san, dn,
		    (unixuser != NULL) ? unixuser_attr : unixgroup_attr,
		    sid, rid, sid_type,
		    (unixuser != NULL) ? unixuser : unixgroup);
	}

	if (ber != NULL)
		ber_free(ber, 0);

	ldap_memfree(dn);
}

void
idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid,
		void *argp)
{
	idmap_query_state_t	*state = (idmap_query_state_t *)argp;
	idmap_q_t		*q = &(state->queries[qid]);

	switch (rc) {
	case LDAP_RES_SEARCH_RESULT:
		if (q->search_res != NULL) {
			idmap_extract_object(state, q, q->search_res, ld);
			(void) ldap_msgfree(q->search_res);
			q->search_res = NULL;
		} else
			q->ad_rc = ADUTILS_ERR_NOTFOUND;
		break;
	case LDAP_RES_SEARCH_ENTRY:
		if (q->search_res == NULL) {
			q->search_res = *res;
			*res = NULL;
		}
		break;
	default:
		break;
	}
}

static
void
idmap_cleanup_batch(idmap_query_state_t *batch)
{
	int i;

	for (i = 0; i < batch->qcount; i++) {
		if (batch->queries[i].ecanonname != NULL)
			free(batch->queries[i].ecanonname);
		batch->queries[i].ecanonname = NULL;
		if (batch->queries[i].edomain != NULL)
			free(batch->queries[i].edomain);
		batch->queries[i].edomain = NULL;
	}
}

/*
 * This routine frees the idmap_query_state_t structure
 */
void
idmap_lookup_release_batch(idmap_query_state_t **state)
{
	if (state == NULL || *state == NULL)
		return;
	adutils_lookup_batch_release(&(*state)->qs);
	idmap_cleanup_batch(*state);
	free(*state);
	*state = NULL;
}

idmap_retcode
idmap_lookup_batch_end(idmap_query_state_t **state)
{
	adutils_rc		ad_rc;
	int			i;
	idmap_query_state_t	*id_qs = *state;

	ad_rc = adutils_lookup_batch_end(&id_qs->qs);

	/*
	 * Map adutils rc to idmap_retcode in each
	 * query because consumers in dbutils.c
	 * expects idmap_retcode.
	 */
	for (i = 0; i < id_qs->qcount; i++) {
		*id_qs->queries[i].rc =
		    map_adrc2idmaprc(id_qs->queries[i].ad_rc);
	}
	idmap_lookup_release_batch(state);
	return (map_adrc2idmaprc(ad_rc));
}

/*
 * Send one prepared search, queue up msgid, process what results are
 * available
 */
static
idmap_retcode
idmap_batch_add1(idmap_query_state_t *state, const char *filter,
	char *ecanonname, char *edomain, int eunixtype,
	char **dn, char **attr, char **value,
	char **canonname, char **dname,
	char **sid, rid_t *rid, int *sid_type, char **unixname,
	idmap_retcode *rc)
{
	adutils_rc	ad_rc;
	int		qid, i;
	idmap_q_t	*q;
	static char	*attrs[] = {
		SAN,
		OBJSID,
		OBJCLASS,
		NULL,	/* placeholder for unixname attr */
		NULL,	/* placeholder for unixname attr */
		NULL
	};

	qid = atomic_inc_32_nv(&state->qcount) - 1;
	q = &(state->queries[qid]);

	assert(qid < state->qsize);

	/*
	 * Remember the expected canonname, domainname and unix type
	 * so we can check the results * against it
	 */
	q->ecanonname = ecanonname;
	q->edomain = edomain;
	q->eunixtype = eunixtype;

	/* Remember where to put the results */
	q->canonname = canonname;
	q->sid = sid;
	q->domain = dname;
	q->rid = rid;
	q->sid_type = sid_type;
	q->rc = rc;
	q->unixname = unixname;
	q->dn = dn;
	q->attr = attr;
	q->value = value;

	/* Add unixuser/unixgroup attribute names to the attrs list */
	if (unixname != NULL) {
		i = 3;
		if (eunixtype != _IDMAP_T_GROUP &&
		    state->ad_unixuser_attr != NULL)
			attrs[i++] = (char *)state->ad_unixuser_attr;
		if (eunixtype != _IDMAP_T_USER &&
		    state->ad_unixgroup_attr != NULL)
			attrs[i] = (char *)state->ad_unixgroup_attr;
	}

	/*
	 * Provide sane defaults for the results in case we never hear
	 * back from the DS before closing the connection.
	 *
	 * In particular we default the result to indicate a retriable
	 * error.  The first complete matching result entry will cause
	 * this to be set to IDMAP_SUCCESS, and the end of the results
	 * for this search will cause this to indicate "not found" if no
	 * result entries arrived or no complete ones matched the lookup
	 * we were doing.
	 */
	*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
	if (sid_type != NULL)
		*sid_type = _IDMAP_T_OTHER;
	if (sid != NULL)
		*sid = NULL;
	if (dname != NULL)
		*dname = NULL;
	if (rid != NULL)
		*rid = 0;
	if (dn != NULL)
		*dn = NULL;
	if (attr != NULL)
		*attr = NULL;
	if (value != NULL)
		*value = NULL;

	/*
	 * Don't set *canonname to NULL because it may be pointing to the
	 * given winname. Later on if we get a canonical name from AD the
	 * old name if any will be freed before assigning the new name.
	 */

	/*
	 * Invoke the mother of all APIs i.e. the adutils API
	 */
	ad_rc = adutils_lookup_batch_add(state->qs, filter,
	    (const char **)attrs,
	    edomain, &q->result, &q->ad_rc);
	return (map_adrc2idmaprc(ad_rc));
}

idmap_retcode
idmap_name2sid_batch_add1(idmap_query_state_t *state,
	const char *name, const char *dname, int eunixtype,
	char **dn, char **attr, char **value,
	char **canonname, char **sid, rid_t *rid,
	int *sid_type, char **unixname, idmap_retcode *rc)
{
	idmap_retcode	retcode;
	int		len, samAcctNameLen;
	char		*filter = NULL, *s_name;
	char		*ecanonname, *edomain; /* expected canonname */

	/*
	 * Strategy: search the global catalog for user/group by
	 * sAMAccountName = user/groupname with "" as the base DN and by
	 * userPrincipalName = user/groupname@domain.  The result
	 * entries will be checked to conform to the name and domain
	 * name given here.  The DN, sAMAccountName, userPrincipalName,
	 * objectSid and objectClass of the result entries are all we
	 * need to figure out which entries match the lookup, the SID of
	 * the user/group and whether it is a user or a group.
	 */

	/*
	 * We need the name and the domain name separately and as
	 * name@domain.  We also allow the domain to be provided
	 * separately.
	 */
	samAcctNameLen = strlen(name);

	if ((ecanonname = strdup(name)) == NULL)
		return (IDMAP_ERR_MEMORY);

	if (dname == NULL || *dname == '\0') {
		if ((dname = strchr(name, '@')) != NULL) {
			/* 'name' is qualified with a domain name */
			if ((edomain = strdup(dname + 1)) == NULL) {
				free(ecanonname);
				return (IDMAP_ERR_MEMORY);
			}
			*strchr(ecanonname, '@') = '\0';
		} else {
			/* 'name' not qualified and dname not given */
			dname = adutils_lookup_batch_getdefdomain(state->qs);
			assert(dname != NULL);
			if (*dname == '\0') {
				free(ecanonname);
				return (IDMAP_ERR_DOMAIN);
			}
			edomain = strdup(dname);
			if (edomain == NULL) {
				free(ecanonname);
				return (IDMAP_ERR_MEMORY);
			}
		}
	} else {
		if ((edomain = strdup(dname)) == NULL) {
			free(ecanonname);
			return (IDMAP_ERR_MEMORY);
		}
	}

	if (!adutils_lookup_check_domain(state->qs, dname)) {
		free(ecanonname);
		free(edomain);
		return (IDMAP_ERR_DOMAIN_NOTFOUND);
	}

	s_name = sanitize_for_ldap_filter(name);
	if (s_name == NULL) {
		free(ecanonname);
		free(edomain);
		return (IDMAP_ERR_MEMORY);
	}

	/* Assemble filter */
	len = snprintf(NULL, 0, SANFILTER, samAcctNameLen, s_name) + 1;
	if ((filter = (char *)malloc(len)) == NULL) {
		free(ecanonname);
		free(edomain);
		if (s_name != name)
			free(s_name);
		return (IDMAP_ERR_MEMORY);
	}
	(void) snprintf(filter, len, SANFILTER, samAcctNameLen, s_name);
	if (s_name != name)
		free(s_name);

	retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
	    eunixtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
	    unixname, rc);

	free(filter);

	return (retcode);
}

idmap_retcode
idmap_sid2name_batch_add1(idmap_query_state_t *state,
	const char *sid, const rid_t *rid, int eunixtype,
	char **dn, char **attr, char **value,
	char **name, char **dname, int *sid_type,
	char **unixname, idmap_retcode *rc)
{
	idmap_retcode	retcode;
	int		flen, ret;
	char		*filter = NULL;
	char		cbinsid[ADUTILS_MAXHEXBINSID + 1];

	/*
	 * Strategy: search [the global catalog] for user/group by
	 * objectSid = SID with empty base DN.  The DN, sAMAccountName
	 * and objectClass of the result are all we need to figure out
	 * the name of the SID and whether it is a user, a group or a
	 * computer.
	 */

	if (!adutils_lookup_check_sid_prefix(state->qs, sid))
		return (IDMAP_ERR_DOMAIN_NOTFOUND);

	ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
	if (ret != 0)
		return (IDMAP_ERR_SID);

	/* Assemble filter */
	flen = snprintf(NULL, 0, OBJSIDFILTER, cbinsid) + 1;
	if ((filter = (char *)malloc(flen)) == NULL)
		return (IDMAP_ERR_MEMORY);
	(void) snprintf(filter, flen, OBJSIDFILTER, cbinsid);

	retcode = idmap_batch_add1(state, filter, NULL, NULL, eunixtype,
	    dn, attr, value, name, dname, NULL, NULL, sid_type, unixname, rc);

	free(filter);

	return (retcode);
}

idmap_retcode
idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
	const char *unixname, int is_user, int is_wuser,
	char **dn, char **attr, char **value,
	char **sid, rid_t *rid, char **name,
	char **dname, int *sid_type, idmap_retcode *rc)
{
	idmap_retcode	retcode;
	int		len, ulen;
	char		*filter = NULL, *s_unixname;
	const char	*attrname = NULL;

	/* Get unixuser or unixgroup AD attribute name */
	attrname = (is_user) ?
	    state->ad_unixuser_attr : state->ad_unixgroup_attr;
	if (attrname == NULL)
		return (IDMAP_ERR_NOTFOUND);

	s_unixname = sanitize_for_ldap_filter(unixname);
	if (s_unixname == NULL)
		return (IDMAP_ERR_MEMORY);

	/*  Assemble filter */
	ulen = strlen(unixname);
	len = snprintf(NULL, 0, "(&(objectclass=%s)(%s=%.*s))",
	    is_wuser ? "user" : "group", attrname, ulen, s_unixname) + 1;
	if ((filter = (char *)malloc(len)) == NULL) {
		if (s_unixname != unixname)
			free(s_unixname);
		return (IDMAP_ERR_MEMORY);
	}
	(void) snprintf(filter, len, "(&(objectclass=%s)(%s=%.*s))",
	    is_wuser ? "user" : "group", attrname, ulen, s_unixname);
	if (s_unixname != unixname)
		free(s_unixname);

	retcode = idmap_batch_add1(state, filter, NULL, NULL,
	    _IDMAP_T_UNDEF, dn, NULL, NULL, name, dname, sid, rid, sid_type,
	    NULL, rc);

	if (retcode == IDMAP_SUCCESS && attr != NULL) {
		if ((*attr = strdup(attrname)) == NULL)
			retcode = IDMAP_ERR_MEMORY;
	}

	if (retcode == IDMAP_SUCCESS && value != NULL) {
		if (ulen > 0) {
			if ((*value = strdup(unixname)) == NULL)
				retcode = IDMAP_ERR_MEMORY;
		}
		else
			*value = NULL;
	}

	free(filter);

	return (retcode);
}