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