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 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 ** 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 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 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 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 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 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 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 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 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