/* * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* * Retrieve directory information for Active Directory users. */ #include #include #include #include #include #include #include #include #include #include #include #include "directory.h" #include "directory_private.h" #include "idmapd.h" #include #include "directory_server_impl.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 (uu_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 (uu_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. * XXX: this should be using our DC, not the GC. */ 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 (uu_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 (uu_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 (uu_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 = uu_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, };