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