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 */
52struct cbinfo {
53	const char * const *attrs;
54	int nattrs;
55	directory_entry_rpc *entry;
56	const char *domain;
57};
58
59static void directory_provider_ad_cb(LDAP *ld, LDAPMessage **ldapres, int rc,
60    int qid, void *argp);
61static void directory_provider_ad_cb1(LDAP *ld, LDAPMessage *msg,
62    struct cbinfo *cbinfo);
63static directory_error_t bv_list_dav(directory_values_rpc *lvals,
64    struct berval **bv);
65static 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);
68static directory_error_t get_domain(LDAP *ld, LDAPMessage *ldapres,
69    char **domain);
70static directory_error_t directory_provider_ad_utils_error(char *func, int rc);
71
72#if	defined(DUMP_VALUES)
73static 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 */
83static
84void
85maybe_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 */
100static
101const char **
102copy_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 */
134static
135directory_error_t
136directory_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
267nomem:
268	de = directory_error("ENOMEM.AD",
269	    "Out of memory during AD lookup", NULL);
270out:
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 */
280static
281directory_error_t
282directory_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 */
344static
345void
346directory_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 */
372static
373void
374directory_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
457nomem:
458	de = directory_error("ENOMEM.users",
459	    "No memory allocating return value for user lookup", NULL);
460
461err:
462	directory_entry_set_error(pent, de);
463	de = NULL;
464
465out:
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 */
476static
477directory_error_t
478bv_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
504nomem:
505	return (directory_error("ENOMEM.bv_list_dav",
506	    "Insufficient memory copying values"));
507}
508
509#if	defined(DUMP_VALUES)
510static
511void
512dump_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 */
530static
531directory_error_t
532get_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 */
575static
576directory_error_t
577directory_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
588struct directory_provider_static directory_provider_ad = {
589	"AD",
590	directory_provider_ad_get,
591};
592