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 */
51struct cbinfo {
52	const char * const *attrs;
53	int nattrs;
54	directory_entry_rpc *entry;
55	const char *domain;
56};
57
58static void directory_provider_ad_cb(LDAP *ld, LDAPMessage **ldapres, int rc,
59    int qid, void *argp);
60static void directory_provider_ad_cb1(LDAP *ld, LDAPMessage *msg,
61    struct cbinfo *cbinfo);
62static directory_error_t bv_list_dav(directory_values_rpc *lvals,
63    struct berval **bv);
64static 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);
67static directory_error_t get_domain(LDAP *ld, LDAPMessage *ldapres,
68    char **domain);
69static directory_error_t directory_provider_ad_utils_error(char *func, int rc);
70
71#if	defined(DUMP_VALUES)
72static 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 */
82static
83void
84maybe_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 */
99static
100const char **
101copy_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 */
133static
134directory_error_t
135directory_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
265nomem:
266	de = directory_error("ENOMEM.AD",
267	    "Out of memory during AD lookup", NULL);
268out:
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 */
278static
279directory_error_t
280directory_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 */
342static
343void
344directory_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 */
370static
371void
372directory_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
455nomem:
456	de = directory_error("ENOMEM.users",
457	    "No memory allocating return value for user lookup", NULL);
458
459err:
460	directory_entry_set_error(pent, de);
461	de = NULL;
462
463out:
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 */
474static
475directory_error_t
476bv_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
502nomem:
503	return (directory_error("ENOMEM.bv_list_dav",
504	    "Insufficient memory copying values"));
505}
506
507#if	defined(DUMP_VALUES)
508static
509void
510dump_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 */
528static
529directory_error_t
530get_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 */
573static
574directory_error_t
575directory_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
586struct directory_provider_static directory_provider_ad = {
587	"AD",
588	directory_provider_ad_get,
589};
590