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
maybe_add_to_list(const char ** list,const char * s)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 **
copy_and_augment_attr_list(char ** req_list,int req_list_len)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
directory_provider_ad_get(directory_entry_rpc * del,idmap_utf8str_list * ids,char * types,idmap_utf8str_list * attrs)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
directory_provider_ad_lookup(directory_entry_rpc * pent,const char * const * attrs,int nattrs,const char * domain,const char * filter)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
directory_provider_ad_cb(LDAP * ld,LDAPMessage ** ldapres,int rc,int qid,void * argp)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
directory_provider_ad_cb1(LDAP * ld,LDAPMessage * msg,struct cbinfo * cbinfo)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
bv_list_dav(directory_values_rpc * lvals,struct berval ** bv)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
dump_bv_list(const char * attr,struct berval ** bv)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
get_domain(LDAP * ld,LDAPMessage * msg,char ** domain)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
directory_provider_ad_utils_error(char * func,int rc)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