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.
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.
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]
23 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
28 * Retrieve directory information for Active Directory users.
38 #include <libadutils.h>
42 #include "directory.h"
43 #include "directory_private.h"
45 #include <rpcsvc/idmap_prot.h>
46 #include "directory_server_impl.h"
49 * Information required by the function that handles the callback from LDAP
50 * when responses are received.
53 const char * const *attrs
;
55 directory_entry_rpc
*entry
;
59 static void directory_provider_ad_cb(LDAP
*ld
, LDAPMessage
**ldapres
, int rc
,
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
,
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
,
70 static directory_error_t
directory_provider_ad_utils_error(char *func
, int rc
);
72 #if defined(DUMP_VALUES)
73 static void dump_bv_list(const char *attr
, struct berval
**bv
);
76 #define MAX_EXTRA_ATTRS 1 /* sAMAccountName */
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.
85 maybe_add_to_list(const char **list
, const char *s
)
87 for (; *list
!= NULL
; list
++) {
88 if (uu_strcaseeq(*list
, s
))
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
102 copy_and_augment_attr_list(char **req_list
, int req_list_len
)
104 const char **new_list
;
108 calloc(req_list_len
+ MAX_EXTRA_ATTRS
+ 1, sizeof (*new_list
));
109 if (new_list
== NULL
)
112 (void) memcpy(new_list
, req_list
, req_list_len
* sizeof (char *));
114 for (i
= 0; i
< req_list_len
; i
++) {
115 const char *a
= req_list
[i
];
117 * Note that you must update MAX_EXTRA_ATTRS above if you
120 if (uu_strcaseeq(a
, "x-sun-canonicalName")) {
121 maybe_add_to_list(new_list
, "sAMAccountName");
124 /* None needed for x-sun-provider */
131 * Retrieve information by name.
132 * Called indirectly through the Directory_provider_static structure.
136 directory_provider_ad_get(
137 directory_entry_rpc
*del
,
138 idmap_utf8str_list
*ids
,
140 idmap_utf8str_list
*attrs
)
144 directory_error_t de
= NULL
;
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.
150 if (_idmapdstate
.num_gcs
< 1) {
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
);
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.
168 attrs2
= copy_and_augment_attr_list(attrs
->idmap_utf8str_list_val
,
169 attrs
->idmap_utf8str_list_len
);
173 for (i
= 0; i
< ids
->idmap_utf8str_list_len
; i
++) {
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.
183 if (*(types
+1) != '\0')
187 * If this entry has already been handled, one way or another,
190 if (del
[i
].status
!= DIRECTORY_NOT_FOUND
)
193 char *id
= ids
->idmap_utf8str_list_val
[i
];
196 * Allow for expanding every character to \xx, plus some
197 * space for the query syntax.
199 int id_len
= strlen(id
);
200 char filter
[1000 + id_len
*3];
202 if (type
== DIRECTORY_ID_SID
[0]) {
204 * Mildly surprisingly, AD appears to allow searching
205 * based on text SIDs. Must be a special case on the
208 ldap_build_filter(filter
, sizeof (filter
),
209 "(objectSid=%v)", NULL
, NULL
, NULL
, id
, NULL
);
211 de
= directory_provider_ad_lookup(&del
[i
], attrs2
,
212 attrs
->idmap_utf8str_list_len
, NULL
, filter
);
214 directory_entry_set_error(&del
[i
], de
);
218 int id_len
= strlen(id
);
219 char name
[id_len
+ 1];
220 char domain
[id_len
+ 1];
222 split_name(name
, domain
, id
);
226 if (uu_streq(domain
, "")) {
227 vw
[1] = default_domain
;
232 if (type
== DIRECTORY_ID_USER
[0])
234 else if (type
== DIRECTORY_ID_GROUP
[0])
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
247 * Eventually we should perhaps also try
250 ldap_build_filter(filter
, sizeof (filter
),
251 "(&(samAccountName=%v1)(objectClass=%v3))",
252 NULL
, NULL
, NULL
, NULL
, vw
);
254 de
= directory_provider_ad_lookup(&del
[i
], attrs2
,
255 attrs
->idmap_utf8str_list_len
, vw
[1], filter
);
257 directory_entry_set_error(&del
[i
], de
);
268 de
= directory_error("ENOMEM.AD",
269 "Out of memory during AD lookup", NULL
);
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.
282 directory_provider_ad_lookup(
283 directory_entry_rpc
*pent
,
284 const char * const * attrs
,
291 struct cbinfo cbinfo
;
292 adutils_query_state_t
*qs
;
296 * NEEDSWORK: Should eventually handle other forests.
297 * NEEDSWORK: Should eventually handle non-GC attributes.
299 ad
= _idmapdstate
.gcs
[0];
301 /* Stash away information for the callback function. */
302 cbinfo
.attrs
= attrs
;
303 cbinfo
.nattrs
= nattrs
;
305 cbinfo
.domain
= domain
;
307 rc
= adutils_lookup_batch_start(ad
, 1, directory_provider_ad_cb
,
309 if (rc
!= ADUTILS_SUCCESS
) {
310 return (directory_provider_ad_utils_error(
311 "adutils_lookup_batch_start", rc
));
314 rc
= adutils_lookup_batch_add(qs
, filter
, attrs
, domain
,
316 if (rc
!= ADUTILS_SUCCESS
) {
317 adutils_lookup_batch_release(&qs
);
318 return (directory_provider_ad_utils_error(
319 "adutils_lookup_batch_add", rc
));
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
));
328 if (batchrc
!= ADUTILS_SUCCESS
) {
330 * NEEDSWORK: We're consistently getting -9997 here.
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.
346 directory_provider_ad_cb(
348 LDAPMessage
**ldapres
,
353 NOTE(ARGUNUSED(rc
, qid
))
354 struct cbinfo
*cbinfo
= (struct cbinfo
*)argp
;
355 LDAPMessage
*msg
= *ldapres
;
357 for (msg
= ldap_first_entry(ld
, msg
);
359 msg
= ldap_next_entry(ld
, msg
)) {
360 directory_provider_ad_cb1(ld
, msg
, cbinfo
);
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.
374 directory_provider_ad_cb1(
377 struct cbinfo
*cbinfo
)
379 int nattrs
= cbinfo
->nattrs
;
380 const char * const *attrs
= cbinfo
->attrs
;
381 directory_entry_rpc
*pent
= cbinfo
->entry
;
384 directory_values_rpc
*llvals
;
385 directory_error_t de
;
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.
393 de
= get_domain(ld
, msg
, &domain
);
397 if (cbinfo
->domain
!= NULL
&& !domain_eq(cbinfo
->domain
, domain
))
401 * If we've already found a match, error.
403 if (pent
->status
!= DIRECTORY_NOT_FOUND
) {
404 de
= directory_error("Duplicate.AD",
405 "Multiple matching entries found", NULL
);
409 llvals
= calloc(nattrs
, sizeof (directory_values_rpc
));
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
;
417 for (i
= 0; i
< nattrs
; i
++) {
419 const char *a
= attrs
[i
];
420 directory_values_rpc
*val
= &llvals
[i
];
422 bv
= ldap_get_values_len(ld
, msg
, a
);
423 #if defined(DUMP_VALUES)
424 dump_bv_list(attrs
[i
], bv
);
427 de
= bv_list_dav(val
, bv
);
428 ldap_value_free_len(bv
);
431 } else if (uu_strcaseeq(a
, "x-sun-canonicalName")) {
432 bv
= ldap_get_values_len(ld
, msg
, "sAMAccountName");
434 int n
= ldap_count_values_len(bv
);
437 (void) asprintf(&tmp
, "%.*s@%s",
438 bv
[0]->bv_len
, bv
[0]->bv_val
,
442 const char *ctmp
= tmp
;
443 de
= str_list_dav(val
, &ctmp
, 1);
449 } else if (uu_strcaseeq(a
, "x-sun-provider")) {
450 const char *provider
= "LDAP-AD";
451 de
= str_list_dav(val
, &provider
, 1);
458 de
= directory_error("ENOMEM.users",
459 "No memory allocating return value for user lookup", NULL
);
462 directory_entry_set_error(pent
, de
);
470 * Given a struct berval, populate a directory attribute value (which is a
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
478 bv_list_dav(directory_values_rpc
*lvals
, struct berval
**bv
)
480 directory_value_rpc
*dav
;
484 n
= ldap_count_values_len(bv
);
486 dav
= calloc(n
, sizeof (directory_value_rpc
));
490 lvals
->directory_values_rpc_u
.values
.values_val
= dav
;
491 lvals
->directory_values_rpc_u
.values
.values_len
= n
;
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
)
499 dav
[i
].directory_value_rpc_len
= bv
[i
]->bv_len
;
505 return (directory_error("ENOMEM.bv_list_dav",
506 "Insufficient memory copying values"));
509 #if defined(DUMP_VALUES)
512 dump_bv_list(const char *attr
, struct berval
**bv
)
517 (void) fprintf(stderr
, "%s: (empty)\n", attr
);
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
);
525 #endif /* DUMP_VALUES */
528 * Return the domain associated with the specified entry.
539 char *dn
= ldap_get_dn(ld
, msg
);
541 char buf
[100]; /* big enough for any int */
544 int err
= ldap_get_lderrno(ld
, &m
, &s
);
545 (void) snprintf(buf
, sizeof (buf
), "%d", err
);
547 return directory_error("AD.get_domain.ldap_get_dn",
548 "ldap_get_dn: %1 (%2)\n"
551 ldap_err2string(err
), buf
,
552 m
== NULL
? "(null)" : m
,
553 s
== NULL
? "(null)" : s
,
557 *domain
= adutils_dn2dns(dn
);
558 if (*domain
== NULL
) {
559 directory_error_t de
;
561 de
= directory_error("Unknown.get_domain.adutils_dn2dns",
562 "get_domain: Unexpected error from adutils_dn2dns(%1)",
573 * Given an error report from libadutils, generate a directory_error_t.
577 directory_provider_ad_utils_error(char *func
, int rc
)
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
);
584 return (directory_error(code
,
585 "Error %2 from adutils function %1", func
, rcstr
, NULL
));
588 struct directory_provider_static directory_provider_ad
= {
590 directory_provider_ad_get
,