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  * Retrieve directory information for Active Directory users.
     29  */
     30 
     31 #include <ldap.h>
     32 #include <lber.h>
     33 #include <pwd.h>
     34 #include <malloc.h>
     35 #include <string.h>
     36 #include <stdlib.h>
     37 #include <netdb.h>
     38 #include <libadutils.h>
     39 #include <note.h>
     40 #include <assert.h>
     41 #include "directory.h"
     42 #include "directory_private.h"
     43 #include "idmapd.h"
     44 #include <rpcsvc/idmap_prot.h>
     45 #include "directory_server_impl.h"
     46 #include "miscutils.h"
     47 
     48 /*
     49  * Information required by the function that handles the callback from LDAP
     50  * when responses are received.
     51  */
     52 struct cbinfo {
     53 	const char * const *attrs;
     54 	int nattrs;
     55 	directory_entry_rpc *entry;
     56 	const char *domain;
     57 };
     58 
     59 static void directory_provider_ad_cb(LDAP *ld, LDAPMessage **ldapres, int rc,
     60     int qid, void *argp);
     61 static void directory_provider_ad_cb1(LDAP *ld, LDAPMessage *msg,
     62     struct cbinfo *cbinfo);
     63 static directory_error_t bv_list_dav(directory_values_rpc *lvals,
     64     struct berval **bv);
     65 static directory_error_t directory_provider_ad_lookup(
     66     directory_entry_rpc *pent, const char * const * attrs, int nattrs,
     67     const char *domain, const char *filter);
     68 static directory_error_t get_domain(LDAP *ld, LDAPMessage *ldapres,
     69     char **domain);
     70 static directory_error_t directory_provider_ad_utils_error(char *func, int rc);
     71 
     72 #if	defined(DUMP_VALUES)
     73 static void dump_bv_list(const char *attr, struct berval **bv);
     74 #endif
     75 
     76 #define	MAX_EXTRA_ATTRS	1	/* sAMAccountName */
     77 
     78 /*
     79  * Add an entry to a NULL-terminated list, if it's not already there.
     80  * Assumes that the list has been allocated large enough for all additions,
     81  * and prefilled with NULL.
     82  */
     83 static
     84 void
     85 maybe_add_to_list(const char **list, const char *s)
     86 {
     87 	for (; *list != NULL; list++) {
     88 		if (strcaseeq(*list, s))
     89 			return;
     90 	}
     91 	*list = s;
     92 }
     93 
     94 /*
     95  * Copy a counted attribute list to a NULL-terminated one.
     96  * In the process, examine the requested attributes and augment
     97  * the list as required to support any synthesized attributes
     98  * requested.
     99  */
    100 static
    101 const char **
    102 copy_and_augment_attr_list(char **req_list, int req_list_len)
    103 {
    104 	const char **new_list;
    105 	int i;
    106 
    107 	new_list =
    108 	    calloc(req_list_len + MAX_EXTRA_ATTRS + 1, sizeof (*new_list));
    109 	if (new_list == NULL)
    110 		return (NULL);
    111 
    112 	(void) memcpy(new_list, req_list, req_list_len * sizeof (char *));
    113 
    114 	for (i = 0; i < req_list_len; i++) {
    115 		const char *a = req_list[i];
    116 		/*
    117 		 * Note that you must update MAX_EXTRA_ATTRS above if you
    118 		 * add to this list.
    119 		 */
    120 		if (strcaseeq(a, "x-sun-canonicalName")) {
    121 			maybe_add_to_list(new_list, "sAMAccountName");
    122 			continue;
    123 		}
    124 		/* None needed for x-sun-provider */
    125 	}
    126 
    127 	return (new_list);
    128 }
    129 
    130 /*
    131  * Retrieve information by name.
    132  * Called indirectly through the Directory_provider_static structure.
    133  */
    134 static
    135 directory_error_t
    136 directory_provider_ad_get(
    137     directory_entry_rpc *del,
    138     idmap_utf8str_list *ids,
    139     char *types,
    140     idmap_utf8str_list *attrs)
    141 {
    142 	int i;
    143 	const char **attrs2;
    144 	directory_error_t de = NULL;
    145 
    146 	/*
    147 	 * If we don't have any AD servers handy, we can't find anything.
    148 	 */
    149 	if (_idmapdstate.num_gcs < 1) {
    150 		return (NULL);
    151 	}
    152 
    153 	RDLOCK_CONFIG()
    154 
    155 	/* 6835280 spurious lint error if the strlen is in the declaration */
    156 	int len = strlen(_idmapdstate.cfg->pgcfg.default_domain);
    157 	char default_domain[len + 1];
    158 	(void) strcpy(default_domain, _idmapdstate.cfg->pgcfg.default_domain);
    159 
    160 	UNLOCK_CONFIG();
    161 
    162 	/*
    163 	 * Turn our counted-array argument into a NULL-terminated array.
    164 	 * At the same time, add in any attributes that we need to support
    165 	 * any requested synthesized attributes.
    166 	 */
    167 	attrs2 = copy_and_augment_attr_list(attrs->idmap_utf8str_list_val,
    168 	    attrs->idmap_utf8str_list_len);
    169 	if (attrs2 == NULL)
    170 		goto nomem;
    171 
    172 	for (i = 0; i < ids->idmap_utf8str_list_len; i++) {
    173 		char *vw[3];
    174 		int type;
    175 
    176 		/*
    177 		 * Extract the type for this particular ID.
    178 		 * Advance to the next type, if it's there, else keep
    179 		 * using this type until we run out of IDs.
    180 		 */
    181 		type = *types;
    182 		if (*(types+1) != '\0')
    183 			types++;
    184 
    185 		/*
    186 		 * If this entry has already been handled, one way or another,
    187 		 * skip it.
    188 		 */
    189 		if (del[i].status != DIRECTORY_NOT_FOUND)
    190 			continue;
    191 
    192 		char *id = ids->idmap_utf8str_list_val[i];
    193 
    194 		/*
    195 		 * Allow for expanding every character to \xx, plus some
    196 		 * space for the query syntax.
    197 		 */
    198 		int id_len = strlen(id);
    199 		char filter[1000 + id_len*3];
    200 
    201 		if (type == DIRECTORY_ID_SID[0]) {
    202 			/*
    203 			 * Mildly surprisingly, AD appears to allow searching
    204 			 * based on text SIDs.  Must be a special case on the
    205 			 * server end.
    206 			 */
    207 			ldap_build_filter(filter, sizeof (filter),
    208 			    "(objectSid=%v)", NULL, NULL, NULL, id, NULL);
    209 
    210 			de = directory_provider_ad_lookup(&del[i], attrs2,
    211 			    attrs->idmap_utf8str_list_len, NULL, filter);
    212 			if (de != NULL) {
    213 				directory_entry_set_error(&del[i], de);
    214 				de = NULL;
    215 			}
    216 		} else {
    217 			int id_len = strlen(id);
    218 			char name[id_len + 1];
    219 			char domain[id_len + 1];
    220 
    221 			split_name(name, domain, id);
    222 
    223 			vw[0] = name;
    224 
    225 			if (streq(domain, "")) {
    226 				vw[1] = default_domain;
    227 			} else {
    228 				vw[1] = domain;
    229 			}
    230 
    231 			if (type == DIRECTORY_ID_USER[0])
    232 				vw[2] = "user";
    233 			else if (type == DIRECTORY_ID_GROUP[0])
    234 				vw[2] = "group";
    235 			else
    236 				vw[2] = "*";
    237 
    238 			/*
    239 			 * Try samAccountName.
    240 			 * Note that here we rely on checking the returned
    241 			 * distinguishedName to make sure that we found an
    242 			 * entry from the right domain, because there's no
    243 			 * attribute we can straightforwardly filter for to
    244 			 * match domain.
    245 			 *
    246 			 * Eventually we should perhaps also try
    247 			 * userPrincipalName.
    248 			 */
    249 			ldap_build_filter(filter, sizeof (filter),
    250 			    "(&(samAccountName=%v1)(objectClass=%v3))",
    251 			    NULL, NULL, NULL, NULL, vw);
    252 
    253 			de = directory_provider_ad_lookup(&del[i], attrs2,
    254 			    attrs->idmap_utf8str_list_len, vw[1], filter);
    255 			if (de != NULL) {
    256 				directory_entry_set_error(&del[i], de);
    257 				de = NULL;
    258 			}
    259 		}
    260 	}
    261 
    262 	de = NULL;
    263 
    264 	goto out;
    265 
    266 nomem:
    267 	de = directory_error("ENOMEM.AD",
    268 	    "Out of memory during AD lookup", NULL);
    269 out:
    270 	free(attrs2);
    271 	return (de);
    272 }
    273 
    274 /*
    275  * Note that attrs is NULL terminated, and that nattrs is the number
    276  * of attributes requested by the user... which might be fewer than are
    277  * in attrs because of attributes that we need for our own processing.
    278  */
    279 static
    280 directory_error_t
    281 directory_provider_ad_lookup(
    282     directory_entry_rpc *pent,
    283     const char * const * attrs,
    284     int nattrs,
    285     const char *domain,
    286     const char *filter)
    287 {
    288 	adutils_ad_t *ad;
    289 	adutils_rc batchrc;
    290 	struct cbinfo cbinfo;
    291 	adutils_query_state_t *qs;
    292 	int rc;
    293 
    294 	/*
    295 	 * NEEDSWORK:  Should eventually handle other forests.
    296 	 * NEEDSWORK:  Should eventually handle non-GC attributes.
    297 	 */
    298 	ad = _idmapdstate.gcs[0];
    299 
    300 	/* Stash away information for the callback function. */
    301 	cbinfo.attrs = attrs;
    302 	cbinfo.nattrs = nattrs;
    303 	cbinfo.entry = pent;
    304 	cbinfo.domain = domain;
    305 
    306 	rc = adutils_lookup_batch_start(ad, 1, directory_provider_ad_cb,
    307 	    &cbinfo, &qs);
    308 	if (rc != ADUTILS_SUCCESS) {
    309 		return (directory_provider_ad_utils_error(
    310 		    "adutils_lookup_batch_start", rc));
    311 	}
    312 
    313 	rc = adutils_lookup_batch_add(qs, filter, attrs, domain,
    314 	    NULL, &batchrc);
    315 	if (rc != ADUTILS_SUCCESS) {
    316 		adutils_lookup_batch_release(&qs);
    317 		return (directory_provider_ad_utils_error(
    318 		    "adutils_lookup_batch_add", rc));
    319 	}
    320 
    321 	rc = adutils_lookup_batch_end(&qs);
    322 	if (rc != ADUTILS_SUCCESS) {
    323 		return (directory_provider_ad_utils_error(
    324 		    "adutils_lookup_batch_end", rc));
    325 	}
    326 
    327 	if (batchrc != ADUTILS_SUCCESS) {
    328 		/*
    329 		 * NEEDSWORK:  We're consistently getting -9997 here.
    330 		 * What does it mean?
    331 		 */
    332 		return (NULL);
    333 	}
    334 
    335 	return (NULL);
    336 }
    337 
    338 /*
    339  * Callback from the LDAP functions when they get responses.
    340  * We don't really need (nor want) asynchronous handling, but it's
    341  * what libadutils gives us.
    342  */
    343 static
    344 void
    345 directory_provider_ad_cb(
    346     LDAP *ld,
    347     LDAPMessage **ldapres,
    348     int rc,
    349     int qid,
    350     void *argp)
    351 {
    352 	NOTE(ARGUNUSED(rc, qid))
    353 	struct cbinfo *cbinfo = (struct cbinfo *)argp;
    354 	LDAPMessage *msg = *ldapres;
    355 
    356 	for (msg = ldap_first_entry(ld, msg);
    357 	    msg != NULL;
    358 	    msg = ldap_next_entry(ld, msg)) {
    359 		directory_provider_ad_cb1(ld, msg, cbinfo);
    360 	}
    361 }
    362 
    363 /*
    364  * Process a single entry returned by an LDAP callback.
    365  * Note that this performs a function roughly equivalent to the
    366  * directory*Populate() functions in the other providers.
    367  * Given an LDAP response, populate the directory entry for return to
    368  * the caller.  This one differs primarily in that we're working directly
    369  * with LDAP, so we don't have to do any attribute translation.
    370  */
    371 static
    372 void
    373 directory_provider_ad_cb1(
    374     LDAP *ld,
    375     LDAPMessage *msg,
    376     struct cbinfo *cbinfo)
    377 {
    378 	int nattrs = cbinfo->nattrs;
    379 	const char * const *attrs = cbinfo->attrs;
    380 	directory_entry_rpc *pent = cbinfo->entry;
    381 
    382 	int i;
    383 	directory_values_rpc *llvals;
    384 	directory_error_t de;
    385 	char *domain = NULL;
    386 
    387 	/*
    388 	 * We don't have a way to filter for entries from the right domain
    389 	 * in the LDAP query, so we check for it here.  Searches based on
    390 	 * samAccountName might yield results from the wrong domain.
    391 	 */
    392 	de = get_domain(ld, msg, &domain);
    393 	if (de != NULL)
    394 		goto err;
    395 
    396 	if (cbinfo->domain != NULL && !domain_eq(cbinfo->domain, domain))
    397 		goto out;
    398 
    399 	/*
    400 	 * If we've already found a match, error.
    401 	 */
    402 	if (pent->status != DIRECTORY_NOT_FOUND) {
    403 		de = directory_error("Duplicate.AD",
    404 		    "Multiple matching entries found", NULL);
    405 		goto err;
    406 	}
    407 
    408 	llvals = calloc(nattrs, sizeof (directory_values_rpc));
    409 	if (llvals == NULL)
    410 		goto nomem;
    411 
    412 	pent->directory_entry_rpc_u.attrs.attrs_val = llvals;
    413 	pent->directory_entry_rpc_u.attrs.attrs_len = nattrs;
    414 	pent->status = DIRECTORY_FOUND;
    415 
    416 	for (i = 0; i < nattrs; i++) {
    417 		struct berval **bv;
    418 		const char *a = attrs[i];
    419 		directory_values_rpc *val = &llvals[i];
    420 
    421 		bv = ldap_get_values_len(ld, msg, a);
    422 #if	defined(DUMP_VALUES)
    423 		dump_bv_list(attrs[i], bv);
    424 #endif
    425 		if (bv != NULL) {
    426 			de = bv_list_dav(val, bv);
    427 			ldap_value_free_len(bv);
    428 			if (de != NULL)
    429 				goto err;
    430 		} else if (strcaseeq(a, "x-sun-canonicalName")) {
    431 			bv = ldap_get_values_len(ld, msg, "sAMAccountName");
    432 			if (bv != NULL) {
    433 				int n = ldap_count_values_len(bv);
    434 				if (n > 0) {
    435 					char *tmp;
    436 					(void) asprintf(&tmp, "%.*s@%s",
    437 					    bv[0]->bv_len, bv[0]->bv_val,
    438 					    domain);
    439 					if (tmp == NULL)
    440 						goto nomem;
    441 					const char *ctmp = tmp;
    442 					de = str_list_dav(val, &ctmp, 1);
    443 					free(tmp);
    444 					if (de != NULL)
    445 						goto err;
    446 				}
    447 			}
    448 		} else if (strcaseeq(a, "x-sun-provider")) {
    449 			const char *provider = "LDAP-AD";
    450 			de = str_list_dav(val, &provider, 1);
    451 		}
    452 	}
    453 
    454 	goto out;
    455 
    456 nomem:
    457 	de = directory_error("ENOMEM.users",
    458 	    "No memory allocating return value for user lookup", NULL);
    459 
    460 err:
    461 	directory_entry_set_error(pent, de);
    462 	de = NULL;
    463 
    464 out:
    465 	free(domain);
    466 }
    467 
    468 /*
    469  * Given a struct berval, populate a directory attribute value (which is a
    470  * list of values).
    471  * Note that here we populate the DAV with the exact bytes that LDAP returns.
    472  * Back over in the client it appends a \0 so that strings are null
    473  * terminated.
    474  */
    475 static
    476 directory_error_t
    477 bv_list_dav(directory_values_rpc *lvals, struct berval **bv)
    478 {
    479 	directory_value_rpc *dav;
    480 	int n;
    481 	int i;
    482 
    483 	n = ldap_count_values_len(bv);
    484 
    485 	dav = calloc(n, sizeof (directory_value_rpc));
    486 	if (dav == NULL)
    487 		goto nomem;
    488 
    489 	lvals->directory_values_rpc_u.values.values_val = dav;
    490 	lvals->directory_values_rpc_u.values.values_len = n;
    491 	lvals->found = TRUE;
    492 
    493 	for (i = 0; i < n; i++) {
    494 		dav[i].directory_value_rpc_val =
    495 		    memdup(bv[i]->bv_val, bv[i]->bv_len);
    496 		if (dav[i].directory_value_rpc_val == NULL)
    497 			goto nomem;
    498 		dav[i].directory_value_rpc_len = bv[i]->bv_len;
    499 	}
    500 
    501 	return (NULL);
    502 
    503 nomem:
    504 	return (directory_error("ENOMEM.bv_list_dav",
    505 	    "Insufficient memory copying values"));
    506 }
    507 
    508 #if	defined(DUMP_VALUES)
    509 static
    510 void
    511 dump_bv_list(const char *attr, struct berval **bv)
    512 {
    513 	int i;
    514 
    515 	if (bv == NULL) {
    516 		(void) fprintf(stderr, "%s:  (empty)\n", attr);
    517 		return;
    518 	}
    519 	for (i = 0; bv[i] != NULL; i++) {
    520 		(void) fprintf(stderr, "%s[%d] =\n", attr, i);
    521 		dump(stderr, "    ", bv[i]->bv_val, bv[i]->bv_len);
    522 	}
    523 }
    524 #endif	/* DUMP_VALUES */
    525 
    526 /*
    527  * Return the domain associated with the specified entry.
    528  */
    529 static
    530 directory_error_t
    531 get_domain(
    532     LDAP *ld,
    533     LDAPMessage *msg,
    534     char **domain)
    535 {
    536 	*domain = NULL;
    537 
    538 	char *dn = ldap_get_dn(ld, msg);
    539 	if (dn == NULL) {
    540 		char buf[100];	/* big enough for any int */
    541 		char *m;
    542 		char *s;
    543 		int err = ldap_get_lderrno(ld, &m, &s);
    544 		(void) snprintf(buf, sizeof (buf), "%d", err);
    545 
    546 		return directory_error("AD.get_domain.ldap_get_dn",
    547 		    "ldap_get_dn: %1 (%2)\n"
    548 		    "matched: %3\n"
    549 		    "error:   %4",
    550 		    ldap_err2string(err), buf,
    551 		    m == NULL ? "(null)" : m,
    552 		    s == NULL ? "(null)" : s,
    553 		    NULL);
    554 	}
    555 
    556 	*domain = adutils_dn2dns(dn);
    557 	if (*domain == NULL) {
    558 		directory_error_t de;
    559 
    560 		de = directory_error("Unknown.get_domain.adutils_dn2dns",
    561 		    "get_domain:  Unexpected error from adutils_dn2dns(%1)",
    562 		    dn, NULL);
    563 		free(dn);
    564 		return (de);
    565 	}
    566 	free(dn);
    567 
    568 	return (NULL);
    569 }
    570 
    571 /*
    572  * Given an error report from libadutils, generate a directory_error_t.
    573  */
    574 static
    575 directory_error_t
    576 directory_provider_ad_utils_error(char *func, int rc)
    577 {
    578 	char rcstr[100];	/* plenty for any int */
    579 	char code[100];		/* plenty for any int */
    580 	(void) snprintf(rcstr, sizeof (rcstr), "%d", rc);
    581 	(void) snprintf(code, sizeof (code), "ADUTILS.%d", rc);
    582 
    583 	return (directory_error(code,
    584 	    "Error %2 from adutils function %1", func, rcstr, NULL));
    585 }
    586 
    587 struct directory_provider_static directory_provider_ad = {
    588 	"AD",
    589 	directory_provider_ad_get,
    590 };
    591