OpenSolaris_b135/cmd/idmap/idmapd/directory_provider_ad.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Retrieve directory information for Active Directory users.
 */

#include <ldap.h>
#include <lber.h>
#include <pwd.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <libadutils.h>
#include <note.h>
#include <assert.h>
#include "directory.h"
#include "directory_private.h"
#include "idmapd.h"
#include <rpcsvc/idmap_prot.h>
#include "directory_server_impl.h"
#include "miscutils.h"

/*
 * Information required by the function that handles the callback from LDAP
 * when responses are received.
 */
struct cbinfo {
	const char * const *attrs;
	int nattrs;
	directory_entry_rpc *entry;
	const char *domain;
};

static void directory_provider_ad_cb(LDAP *ld, LDAPMessage **ldapres, int rc,
    int qid, void *argp);
static void directory_provider_ad_cb1(LDAP *ld, LDAPMessage *msg,
    struct cbinfo *cbinfo);
static directory_error_t bv_list_dav(directory_values_rpc *lvals,
    struct berval **bv);
static directory_error_t directory_provider_ad_lookup(
    directory_entry_rpc *pent, const char * const * attrs, int nattrs,
    const char *domain, const char *filter);
static directory_error_t get_domain(LDAP *ld, LDAPMessage *ldapres,
    char **domain);
static directory_error_t directory_provider_ad_utils_error(char *func, int rc);

#if	defined(DUMP_VALUES)
static void dump_bv_list(const char *attr, struct berval **bv);
#endif

#define	MAX_EXTRA_ATTRS	1	/* sAMAccountName */

/*
 * Add an entry to a NULL-terminated list, if it's not already there.
 * Assumes that the list has been allocated large enough for all additions,
 * and prefilled with NULL.
 */
static
void
maybe_add_to_list(const char **list, const char *s)
{
	for (; *list != NULL; list++) {
		if (strcaseeq(*list, s))
			return;
	}
	*list = s;
}

/*
 * Copy a counted attribute list to a NULL-terminated one.
 * In the process, examine the requested attributes and augment
 * the list as required to support any synthesized attributes
 * requested.
 */
static
const char **
copy_and_augment_attr_list(char **req_list, int req_list_len)
{
	const char **new_list;
	int i;

	new_list =
	    calloc(req_list_len + MAX_EXTRA_ATTRS + 1, sizeof (*new_list));
	if (new_list == NULL)
		return (NULL);

	(void) memcpy(new_list, req_list, req_list_len * sizeof (char *));

	for (i = 0; i < req_list_len; i++) {
		const char *a = req_list[i];
		/*
		 * Note that you must update MAX_EXTRA_ATTRS above if you
		 * add to this list.
		 */
		if (strcaseeq(a, "x-sun-canonicalName")) {
			maybe_add_to_list(new_list, "sAMAccountName");
			continue;
		}
		/* None needed for x-sun-provider */
	}

	return (new_list);
}

/*
 * Retrieve information by name.
 * Called indirectly through the Directory_provider_static structure.
 */
static
directory_error_t
directory_provider_ad_get(
    directory_entry_rpc *del,
    idmap_utf8str_list *ids,
    char *types,
    idmap_utf8str_list *attrs)
{
	int i;
	const char **attrs2;
	directory_error_t de = NULL;

	/*
	 * If we don't have any AD servers handy, we can't find anything.
	 */
	if (_idmapdstate.num_gcs < 1) {
		return (NULL);
	}

	RDLOCK_CONFIG()

	/* 6835280 spurious lint error if the strlen is in the declaration */
	int len = strlen(_idmapdstate.cfg->pgcfg.default_domain);
	char default_domain[len + 1];
	(void) strcpy(default_domain, _idmapdstate.cfg->pgcfg.default_domain);

	UNLOCK_CONFIG();

	/*
	 * Turn our counted-array argument into a NULL-terminated array.
	 * At the same time, add in any attributes that we need to support
	 * any requested synthesized attributes.
	 */
	attrs2 = copy_and_augment_attr_list(attrs->idmap_utf8str_list_val,
	    attrs->idmap_utf8str_list_len);
	if (attrs2 == NULL)
		goto nomem;

	for (i = 0; i < ids->idmap_utf8str_list_len; i++) {
		char *vw[3];
		int type;

		/*
		 * Extract the type for this particular ID.
		 * Advance to the next type, if it's there, else keep
		 * using this type until we run out of IDs.
		 */
		type = *types;
		if (*(types+1) != '\0')
			types++;

		/*
		 * If this entry has already been handled, one way or another,
		 * skip it.
		 */
		if (del[i].status != DIRECTORY_NOT_FOUND)
			continue;

		char *id = ids->idmap_utf8str_list_val[i];

		/*
		 * Allow for expanding every character to \xx, plus some
		 * space for the query syntax.
		 */
		int id_len = strlen(id);
		char filter[1000 + id_len*3];

		if (type == DIRECTORY_ID_SID[0]) {
			/*
			 * Mildly surprisingly, AD appears to allow searching
			 * based on text SIDs.  Must be a special case on the
			 * server end.
			 */
			ldap_build_filter(filter, sizeof (filter),
			    "(objectSid=%v)", NULL, NULL, NULL, id, NULL);

			de = directory_provider_ad_lookup(&del[i], attrs2,
			    attrs->idmap_utf8str_list_len, NULL, filter);
			if (de != NULL) {
				directory_entry_set_error(&del[i], de);
				de = NULL;
			}
		} else {
			int id_len = strlen(id);
			char name[id_len + 1];
			char domain[id_len + 1];

			split_name(name, domain, id);

			vw[0] = name;

			if (streq(domain, "")) {
				vw[1] = default_domain;
			} else {
				vw[1] = domain;
			}

			if (type == DIRECTORY_ID_USER[0])
				vw[2] = "user";
			else if (type == DIRECTORY_ID_GROUP[0])
				vw[2] = "group";
			else
				vw[2] = "*";

			/*
			 * Try samAccountName.
			 * Note that here we rely on checking the returned
			 * distinguishedName to make sure that we found an
			 * entry from the right domain, because there's no
			 * attribute we can straightforwardly filter for to
			 * match domain.
			 *
			 * Eventually we should perhaps also try
			 * userPrincipalName.
			 */
			ldap_build_filter(filter, sizeof (filter),
			    "(&(samAccountName=%v1)(objectClass=%v3))",
			    NULL, NULL, NULL, NULL, vw);

			de = directory_provider_ad_lookup(&del[i], attrs2,
			    attrs->idmap_utf8str_list_len, vw[1], filter);
			if (de != NULL) {
				directory_entry_set_error(&del[i], de);
				de = NULL;
			}
		}
	}

	de = NULL;

	goto out;

nomem:
	de = directory_error("ENOMEM.AD",
	    "Out of memory during AD lookup", NULL);
out:
	free(attrs2);
	return (de);
}

/*
 * Note that attrs is NULL terminated, and that nattrs is the number
 * of attributes requested by the user... which might be fewer than are
 * in attrs because of attributes that we need for our own processing.
 */
static
directory_error_t
directory_provider_ad_lookup(
    directory_entry_rpc *pent,
    const char * const * attrs,
    int nattrs,
    const char *domain,
    const char *filter)
{
	adutils_ad_t *ad;
	adutils_rc batchrc;
	struct cbinfo cbinfo;
	adutils_query_state_t *qs;
	int rc;

	/*
	 * NEEDSWORK:  Should eventually handle other forests.
	 * NEEDSWORK:  Should eventually handle non-GC attributes.
	 */
	ad = _idmapdstate.gcs[0];

	/* Stash away information for the callback function. */
	cbinfo.attrs = attrs;
	cbinfo.nattrs = nattrs;
	cbinfo.entry = pent;
	cbinfo.domain = domain;

	rc = adutils_lookup_batch_start(ad, 1, directory_provider_ad_cb,
	    &cbinfo, &qs);
	if (rc != ADUTILS_SUCCESS) {
		return (directory_provider_ad_utils_error(
		    "adutils_lookup_batch_start", rc));
	}

	rc = adutils_lookup_batch_add(qs, filter, attrs, domain,
	    NULL, &batchrc);
	if (rc != ADUTILS_SUCCESS) {
		adutils_lookup_batch_release(&qs);
		return (directory_provider_ad_utils_error(
		    "adutils_lookup_batch_add", rc));
	}

	rc = adutils_lookup_batch_end(&qs);
	if (rc != ADUTILS_SUCCESS) {
		return (directory_provider_ad_utils_error(
		    "adutils_lookup_batch_end", rc));
	}

	if (batchrc != ADUTILS_SUCCESS) {
		/*
		 * NEEDSWORK:  We're consistently getting -9997 here.
		 * What does it mean?
		 */
		return (NULL);
	}

	return (NULL);
}

/*
 * Callback from the LDAP functions when they get responses.
 * We don't really need (nor want) asynchronous handling, but it's
 * what libadutils gives us.
 */
static
void
directory_provider_ad_cb(
    LDAP *ld,
    LDAPMessage **ldapres,
    int rc,
    int qid,
    void *argp)
{
	NOTE(ARGUNUSED(rc, qid))
	struct cbinfo *cbinfo = (struct cbinfo *)argp;
	LDAPMessage *msg = *ldapres;

	for (msg = ldap_first_entry(ld, msg);
	    msg != NULL;
	    msg = ldap_next_entry(ld, msg)) {
		directory_provider_ad_cb1(ld, msg, cbinfo);
	}
}

/*
 * Process a single entry returned by an LDAP callback.
 * Note that this performs a function roughly equivalent to the
 * directory*Populate() functions in the other providers.
 * Given an LDAP response, populate the directory entry for return to
 * the caller.  This one differs primarily in that we're working directly
 * with LDAP, so we don't have to do any attribute translation.
 */
static
void
directory_provider_ad_cb1(
    LDAP *ld,
    LDAPMessage *msg,
    struct cbinfo *cbinfo)
{
	int nattrs = cbinfo->nattrs;
	const char * const *attrs = cbinfo->attrs;
	directory_entry_rpc *pent = cbinfo->entry;

	int i;
	directory_values_rpc *llvals;
	directory_error_t de;
	char *domain = NULL;

	/*
	 * We don't have a way to filter for entries from the right domain
	 * in the LDAP query, so we check for it here.  Searches based on
	 * samAccountName might yield results from the wrong domain.
	 */
	de = get_domain(ld, msg, &domain);
	if (de != NULL)
		goto err;

	if (cbinfo->domain != NULL && !domain_eq(cbinfo->domain, domain))
		goto out;

	/*
	 * If we've already found a match, error.
	 */
	if (pent->status != DIRECTORY_NOT_FOUND) {
		de = directory_error("Duplicate.AD",
		    "Multiple matching entries found", NULL);
		goto err;
	}

	llvals = calloc(nattrs, sizeof (directory_values_rpc));
	if (llvals == NULL)
		goto nomem;

	pent->directory_entry_rpc_u.attrs.attrs_val = llvals;
	pent->directory_entry_rpc_u.attrs.attrs_len = nattrs;
	pent->status = DIRECTORY_FOUND;

	for (i = 0; i < nattrs; i++) {
		struct berval **bv;
		const char *a = attrs[i];
		directory_values_rpc *val = &llvals[i];

		bv = ldap_get_values_len(ld, msg, a);
#if	defined(DUMP_VALUES)
		dump_bv_list(attrs[i], bv);
#endif
		if (bv != NULL) {
			de = bv_list_dav(val, bv);
			ldap_value_free_len(bv);
			if (de != NULL)
				goto err;
		} else if (strcaseeq(a, "x-sun-canonicalName")) {
			bv = ldap_get_values_len(ld, msg, "sAMAccountName");
			if (bv != NULL) {
				int n = ldap_count_values_len(bv);
				if (n > 0) {
					char *tmp;
					(void) asprintf(&tmp, "%.*s@%s",
					    bv[0]->bv_len, bv[0]->bv_val,
					    domain);
					if (tmp == NULL)
						goto nomem;
					const char *ctmp = tmp;
					de = str_list_dav(val, &ctmp, 1);
					free(tmp);
					if (de != NULL)
						goto err;
				}
			}
		} else if (strcaseeq(a, "x-sun-provider")) {
			const char *provider = "LDAP-AD";
			de = str_list_dav(val, &provider, 1);
		}
	}

	goto out;

nomem:
	de = directory_error("ENOMEM.users",
	    "No memory allocating return value for user lookup", NULL);

err:
	directory_entry_set_error(pent, de);
	de = NULL;

out:
	free(domain);
}

/*
 * Given a struct berval, populate a directory attribute value (which is a
 * list of values).
 * Note that here we populate the DAV with the exact bytes that LDAP returns.
 * Back over in the client it appends a \0 so that strings are null
 * terminated.
 */
static
directory_error_t
bv_list_dav(directory_values_rpc *lvals, struct berval **bv)
{
	directory_value_rpc *dav;
	int n;
	int i;

	n = ldap_count_values_len(bv);

	dav = calloc(n, sizeof (directory_value_rpc));
	if (dav == NULL)
		goto nomem;

	lvals->directory_values_rpc_u.values.values_val = dav;
	lvals->directory_values_rpc_u.values.values_len = n;
	lvals->found = TRUE;

	for (i = 0; i < n; i++) {
		dav[i].directory_value_rpc_val =
		    memdup(bv[i]->bv_val, bv[i]->bv_len);
		if (dav[i].directory_value_rpc_val == NULL)
			goto nomem;
		dav[i].directory_value_rpc_len = bv[i]->bv_len;
	}

	return (NULL);

nomem:
	return (directory_error("ENOMEM.bv_list_dav",
	    "Insufficient memory copying values"));
}

#if	defined(DUMP_VALUES)
static
void
dump_bv_list(const char *attr, struct berval **bv)
{
	int i;

	if (bv == NULL) {
		(void) fprintf(stderr, "%s:  (empty)\n", attr);
		return;
	}
	for (i = 0; bv[i] != NULL; i++) {
		(void) fprintf(stderr, "%s[%d] =\n", attr, i);
		dump(stderr, "    ", bv[i]->bv_val, bv[i]->bv_len);
	}
}
#endif	/* DUMP_VALUES */

/*
 * Return the domain associated with the specified entry.
 */
static
directory_error_t
get_domain(
    LDAP *ld,
    LDAPMessage *msg,
    char **domain)
{
	*domain = NULL;

	char *dn = ldap_get_dn(ld, msg);
	if (dn == NULL) {
		char buf[100];	/* big enough for any int */
		char *m;
		char *s;
		int err = ldap_get_lderrno(ld, &m, &s);
		(void) snprintf(buf, sizeof (buf), "%d", err);

		return directory_error("AD.get_domain.ldap_get_dn",
		    "ldap_get_dn: %1 (%2)\n"
		    "matched: %3\n"
		    "error:   %4",
		    ldap_err2string(err), buf,
		    m == NULL ? "(null)" : m,
		    s == NULL ? "(null)" : s,
		    NULL);
	}

	*domain = adutils_dn2dns(dn);
	if (*domain == NULL) {
		directory_error_t de;

		de = directory_error("Unknown.get_domain.adutils_dn2dns",
		    "get_domain:  Unexpected error from adutils_dn2dns(%1)",
		    dn, NULL);
		free(dn);
		return (de);
	}
	free(dn);

	return (NULL);
}

/*
 * Given an error report from libadutils, generate a directory_error_t.
 */
static
directory_error_t
directory_provider_ad_utils_error(char *func, int rc)
{
	char rcstr[100];	/* plenty for any int */
	char code[100];		/* plenty for any int */
	(void) snprintf(rcstr, sizeof (rcstr), "%d", rc);
	(void) snprintf(code, sizeof (code), "ADUTILS.%d", rc);

	return (directory_error(code,
	    "Error %2 from adutils function %1", func, rcstr, NULL));
}

struct directory_provider_static directory_provider_ad = {
	"AD",
	directory_provider_ad_get,
};