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