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