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 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * Processes name2sid & sid2name batched lookups for a given user or 31 * computer from an AD Directory server using GSSAPI authentication 32 */ 33 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <alloca.h> 37 #include <string.h> 38 #include <strings.h> 39 #include <lber.h> 40 #include <ldap.h> 41 #include <sasl/sasl.h> 42 #include <string.h> 43 #include <ctype.h> 44 #include <pthread.h> 45 #include <synch.h> 46 #include <atomic.h> 47 #include <errno.h> 48 #include <assert.h> 49 #include <limits.h> 50 #include <sys/u8_textprep.h> 51 #include "idmapd.h" 52 53 /* 54 * Internal data structures for this code 55 */ 56 57 /* Attribute names and filter format strings */ 58 #define SAN "sAMAccountName" 59 #define OBJSID "objectSid" 60 #define OBJCLASS "objectClass" 61 #define SANFILTER "(sAMAccountName=%.*s)" 62 #define OBJSIDFILTER "(objectSid=%s)" 63 64 /* 65 * This should really be in some <sys/sid.h> file or so; we have a 66 * private version of sid_t, and so must other components of ON until we 67 * rationalize this. 68 */ 69 typedef struct sid { 70 uchar_t version; 71 uchar_t sub_authority_count; 72 uint64_t authority; /* really, 48-bits */ 73 rid_t sub_authorities[SID_MAX_SUB_AUTHORITIES]; 74 } sid_t; 75 76 /* A single DS */ 77 typedef struct ad_host { 78 struct ad_host *next; 79 ad_t *owner; /* ad_t to which this belongs */ 80 pthread_mutex_t lock; 81 LDAP *ld; /* LDAP connection */ 82 uint32_t ref; /* ref count */ 83 time_t idletime; /* time since last activity */ 84 int dead; /* error on LDAP connection */ 85 /* 86 * Used to distinguish between different instances of LDAP 87 * connections to this same DS. We need this so we never mix up 88 * results for a given msgID from one connection with those of 89 * another earlier connection where two batch state structures 90 * share this ad_host object but used different LDAP connections 91 * to send their LDAP searches. 92 */ 93 uint64_t generation; 94 95 /* LDAP DS info */ 96 char *host; 97 int port; 98 99 /* hardwired to SASL GSSAPI only for now */ 100 char *saslmech; 101 unsigned saslflags; 102 } ad_host_t; 103 104 /* A set of DSs for a given AD partition; ad_t typedef comes from adutil.h */ 105 struct ad { 106 char *dflt_w2k_dom; /* used to qualify bare names */ 107 pthread_mutex_t lock; 108 uint32_t ref; 109 ad_host_t *last_adh; 110 idmap_ad_partition_t partition; /* Data or global catalog? */ 111 }; 112 113 /* 114 * A place to put the results of a batched (async) query 115 * 116 * There is one of these for every query added to a batch object 117 * (idmap_query_state, see below). 118 */ 119 typedef struct idmap_q { 120 /* 121 * data used for validating search result entries for name->SID 122 * lookups 123 */ 124 char *ecanonname; /* expected canon name */ 125 char *edomain; /* expected domain name */ 126 int eunixtype; /* expected unix type */ 127 /* results */ 128 char **canonname; /* actual canon name */ 129 char **domain; /* name of domain of object */ 130 char **sid; /* stringified SID */ 131 rid_t *rid; /* RID */ 132 int *sid_type; /* user or group SID? */ 133 char **unixname; /* unixname for name mapping */ 134 idmap_retcode *rc; 135 136 /* lookup state */ 137 int msgid; /* LDAP message ID */ 138 } idmap_q_t; 139 140 /* Batch context structure; typedef is in header file */ 141 struct idmap_query_state { 142 idmap_query_state_t *next; 143 int qcount; /* how many queries */ 144 int ref_cnt; /* reference count */ 145 pthread_cond_t cv; /* Condition wait variable */ 146 uint32_t qlastsent; 147 uint32_t qinflight; /* how many queries in flight */ 148 uint16_t qdead; /* oops, lost LDAP connection */ 149 ad_host_t *qadh; /* LDAP connection */ 150 uint64_t qadh_gen; /* same as qadh->generation */ 151 const char *ad_unixuser_attr; 152 const char *ad_unixgroup_attr; 153 idmap_q_t queries[1]; /* array of query results */ 154 }; 155 156 /* 157 * List of query state structs -- needed so we can "route" LDAP results 158 * to the right context if multiple threads should be using the same 159 * connection concurrently 160 */ 161 static idmap_query_state_t *qstatehead = NULL; 162 static pthread_mutex_t qstatelock = PTHREAD_MUTEX_INITIALIZER; 163 164 /* 165 * List of DSs, needed by the idle connection reaper thread 166 */ 167 static ad_host_t *host_head = NULL; 168 static pthread_t reaperid = 0; 169 static pthread_mutex_t adhostlock = PTHREAD_MUTEX_INITIALIZER; 170 171 172 static void 173 idmap_lookup_unlock_batch(idmap_query_state_t **state); 174 175 static void 176 delete_ds(ad_t *ad, const char *host, int port); 177 178 /*ARGSUSED*/ 179 static int 180 idmap_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts) { 181 sasl_interact_t *interact; 182 183 if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE) 184 return (LDAP_PARAM_ERROR); 185 186 /* There should be no extra arguemnts for SASL/GSSAPI authentication */ 187 for (interact = prompts; interact->id != SASL_CB_LIST_END; 188 interact++) { 189 interact->result = NULL; 190 interact->len = 0; 191 } 192 return (LDAP_SUCCESS); 193 } 194 195 196 /* 197 * Turn "dc=foo,dc=bar,dc=com" into "foo.bar.com"; ignores any other 198 * attributes (CN, etc...). We don't need the reverse, for now. 199 */ 200 static 201 char * 202 dn2dns(const char *dn) 203 { 204 char **rdns = NULL; 205 char **attrs = NULL; 206 char **labels = NULL; 207 char *dns = NULL; 208 char **rdn, **attr, **label; 209 int maxlabels = 5; 210 int nlabels = 0; 211 int dnslen; 212 213 /* 214 * There is no reverse of ldap_dns_to_dn() in our libldap, so we 215 * have to do the hard work here for now. 216 */ 217 218 /* 219 * This code is much too liberal: it looks for "dc" attributes 220 * in all RDNs of the DN. In theory this could cause problems 221 * if people were to use "dc" in nodes other than the root of 222 * the tree, but in practice noone, least of all Active 223 * Directory, does that. 224 * 225 * On the other hand, this code is much too conservative: it 226 * does not make assumptions about ldap_explode_dn(), and _that_ 227 * is the true for looking at every attr of every RDN. 228 * 229 * Since we only ever look at dc and those must be DNS labels, 230 * at least until we get around to supporting IDN here we 231 * shouldn't see escaped labels from AD nor from libldap, though 232 * the spec (RFC2253) does allow libldap to escape things that 233 * don't need escaping -- if that should ever happen then 234 * libldap will need a spanking, and we can take care of that. 235 */ 236 237 /* Explode a DN into RDNs */ 238 if ((rdns = ldap_explode_dn(dn, 0)) == NULL) 239 return (NULL); 240 241 labels = calloc(maxlabels + 1, sizeof (char *)); 242 label = labels; 243 244 for (rdn = rdns; *rdn != NULL; rdn++) { 245 if (attrs != NULL) 246 ldap_value_free(attrs); 247 248 /* Explode each RDN, look for DC attr, save val as DNS label */ 249 if ((attrs = ldap_explode_rdn(rdn[0], 0)) == NULL) 250 goto done; 251 252 for (attr = attrs; *attr != NULL; attr++) { 253 if (strncasecmp(*attr, "dc=", 3) != 0) 254 continue; 255 256 /* Found a DNS label */ 257 labels[nlabels++] = strdup((*attr) + 3); 258 259 if (nlabels == maxlabels) { 260 char **tmp; 261 tmp = realloc(labels, 262 sizeof (char *) * (maxlabels + 1)); 263 264 if (tmp == NULL) 265 goto done; 266 267 labels = tmp; 268 labels[nlabels] = NULL; 269 } 270 271 /* There should be just one DC= attr per-RDN */ 272 break; 273 } 274 } 275 276 /* 277 * Got all the labels, now join with '.' 278 * 279 * We need room for nlabels - 1 periods ('.'), one nul 280 * terminator, and the strlen() of each label. 281 */ 282 dnslen = nlabels; 283 for (label = labels; *label != NULL; label++) 284 dnslen += strlen(*label); 285 286 if ((dns = malloc(dnslen)) == NULL) 287 goto done; 288 289 *dns = '\0'; 290 291 for (label = labels; *label != NULL; label++) { 292 (void) strlcat(dns, *label, dnslen); 293 /* 294 * NOTE: the last '.' won't be appended -- there's no room 295 * for it! 296 */ 297 (void) strlcat(dns, ".", dnslen); 298 } 299 300 done: 301 if (labels != NULL) { 302 for (label = labels; *label != NULL; label++) 303 free(*label); 304 free(labels); 305 } 306 if (attrs != NULL) 307 ldap_value_free(attrs); 308 if (rdns != NULL) 309 ldap_value_free(rdns); 310 311 return (dns); 312 } 313 314 /* 315 * Keep connection management simple for now, extend or replace later 316 * with updated libsldap code. 317 */ 318 #define ADREAPERSLEEP 60 319 #define ADCONN_TIME 300 320 321 /* 322 * Idle connection reaping side of connection management 323 * 324 * Every minute wake up and look for connections that have been idle for 325 * five minutes or more and close them. 326 */ 327 /*ARGSUSED*/ 328 static 329 void 330 adreaper(void *arg) 331 { 332 ad_host_t *adh; 333 time_t now; 334 timespec_t ts; 335 336 ts.tv_sec = ADREAPERSLEEP; 337 ts.tv_nsec = 0; 338 339 for (;;) { 340 /* 341 * nanosleep(3RT) is thead-safe (no SIGALRM) and more 342 * portable than usleep(3C) 343 */ 344 (void) nanosleep(&ts, NULL); 345 (void) pthread_mutex_lock(&adhostlock); 346 now = time(NULL); 347 for (adh = host_head; adh != NULL; adh = adh->next) { 348 (void) pthread_mutex_lock(&adh->lock); 349 if (adh->ref == 0 && adh->idletime != 0 && 350 adh->idletime + ADCONN_TIME < now) { 351 if (adh->ld) { 352 (void) ldap_unbind(adh->ld); 353 adh->ld = NULL; 354 adh->idletime = 0; 355 adh->ref = 0; 356 } 357 } 358 (void) pthread_mutex_unlock(&adh->lock); 359 } 360 (void) pthread_mutex_unlock(&adhostlock); 361 } 362 } 363 364 int 365 idmap_ad_alloc(ad_t **new_ad, const char *default_domain, 366 idmap_ad_partition_t part) 367 { 368 ad_t *ad; 369 370 *new_ad = NULL; 371 372 if ((default_domain == NULL || *default_domain == '\0') && 373 part != IDMAP_AD_GLOBAL_CATALOG) 374 return (-1); 375 376 if ((ad = calloc(1, sizeof (ad_t))) == NULL) 377 return (-1); 378 379 ad->ref = 1; 380 ad->partition = part; 381 382 /* 383 * If default_domain is NULL, deal, deferring errors until 384 * idmap_lookup_batch_start() -- this makes it easier on the 385 * caller, who can simply observe lookups failing as opposed to 386 * having to conditionalize calls to lookups according to 387 * whether it has a non-NULL ad_t *. 388 */ 389 if (default_domain == NULL) 390 default_domain = ""; 391 392 if ((ad->dflt_w2k_dom = strdup(default_domain)) == NULL) 393 goto err; 394 395 if (pthread_mutex_init(&ad->lock, NULL) != 0) 396 goto err; 397 398 *new_ad = ad; 399 400 return (0); 401 err: 402 if (ad->dflt_w2k_dom != NULL) 403 free(ad->dflt_w2k_dom); 404 free(ad); 405 return (-1); 406 } 407 408 409 void 410 idmap_ad_free(ad_t **ad) 411 { 412 ad_host_t *p; 413 ad_host_t *prev; 414 415 if (ad == NULL || *ad == NULL) 416 return; 417 418 (void) pthread_mutex_lock(&(*ad)->lock); 419 420 if (atomic_dec_32_nv(&(*ad)->ref) > 0) { 421 (void) pthread_mutex_unlock(&(*ad)->lock); 422 *ad = NULL; 423 return; 424 } 425 426 (void) pthread_mutex_lock(&adhostlock); 427 prev = NULL; 428 p = host_head; 429 while (p != NULL) { 430 if (p->owner != (*ad)) { 431 prev = p; 432 p = p->next; 433 continue; 434 } else { 435 delete_ds((*ad), p->host, p->port); 436 if (prev == NULL) 437 p = host_head; 438 else 439 p = prev->next; 440 } 441 } 442 (void) pthread_mutex_unlock(&adhostlock); 443 444 (void) pthread_mutex_unlock(&(*ad)->lock); 445 (void) pthread_mutex_destroy(&(*ad)->lock); 446 447 free((*ad)->dflt_w2k_dom); 448 free(*ad); 449 450 *ad = NULL; 451 } 452 453 454 static 455 int 456 idmap_open_conn(ad_host_t *adh) 457 { 458 int zero = 0; 459 int timeoutms = 30 * 1000; 460 int ldversion, rc; 461 462 if (adh == NULL) 463 return (0); 464 465 (void) pthread_mutex_lock(&adh->lock); 466 467 if (!adh->dead && adh->ld != NULL) 468 /* done! */ 469 goto out; 470 471 if (adh->ld != NULL) { 472 (void) ldap_unbind(adh->ld); 473 adh->ld = NULL; 474 } 475 476 atomic_inc_64(&adh->generation); 477 478 /* Open and bind an LDAP connection */ 479 adh->ld = ldap_init(adh->host, adh->port); 480 if (adh->ld == NULL) { 481 idmapdlog(LOG_INFO, "ldap_init() to server " 482 "%s port %d failed. (%s)", adh->host, 483 adh->port, strerror(errno)); 484 goto out; 485 } 486 ldversion = LDAP_VERSION3; 487 (void) ldap_set_option(adh->ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion); 488 (void) ldap_set_option(adh->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); 489 (void) ldap_set_option(adh->ld, LDAP_OPT_TIMELIMIT, &zero); 490 (void) ldap_set_option(adh->ld, LDAP_OPT_SIZELIMIT, &zero); 491 (void) ldap_set_option(adh->ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms); 492 (void) ldap_set_option(adh->ld, LDAP_OPT_RESTART, LDAP_OPT_ON); 493 rc = ldap_sasl_interactive_bind_s(adh->ld, "" /* binddn */, 494 adh->saslmech, NULL, NULL, adh->saslflags, &idmap_saslcallback, 495 NULL); 496 497 if (rc != LDAP_SUCCESS) { 498 (void) ldap_unbind(adh->ld); 499 adh->ld = NULL; 500 idmapdlog(LOG_INFO, "ldap_sasl_interactive_bind_s() to server " 501 "%s port %d failed. (%s)", adh->host, adh->port, 502 ldap_err2string(rc)); 503 } 504 505 idmapdlog(LOG_DEBUG, "Using global catalog server %s:%d", 506 adh->host, adh->port); 507 508 out: 509 if (adh->ld != NULL) { 510 atomic_inc_32(&adh->ref); 511 adh->idletime = time(NULL); 512 adh->dead = 0; 513 (void) pthread_mutex_unlock(&adh->lock); 514 return (1); 515 } 516 517 (void) pthread_mutex_unlock(&adh->lock); 518 return (0); 519 } 520 521 522 /* 523 * Connection management: find an open connection or open one 524 */ 525 static 526 ad_host_t * 527 idmap_get_conn(ad_t *ad) 528 { 529 ad_host_t *adh = NULL; 530 ad_host_t *first_adh, *next_adh; 531 int seen_last; 532 int tries = -1; 533 534 retry: 535 (void) pthread_mutex_lock(&adhostlock); 536 537 if (host_head == NULL) 538 goto out; 539 540 /* Try as many DSs as we have, once each; count them once */ 541 if (tries < 0) { 542 for (adh = host_head, tries = 0; adh != NULL; adh = adh->next) 543 tries++; 544 } 545 546 /* 547 * Find a suitable ad_host_t (one associated with this ad_t, 548 * preferably one that's already connected and not dead), 549 * possibly round-robining through the ad_host_t list. 550 * 551 * If we can't find a non-dead ad_host_t and we've had one 552 * before (ad->last_adh != NULL) then pick the next one or, if 553 * there is no next one, the first one (i.e., round-robin). 554 * 555 * If we've never had one then (ad->last_adh == NULL) then pick 556 * the first one. 557 * 558 * If we ever want to be more clever, such as preferring DSes 559 * with better average response times, adjusting for weights 560 * from SRV RRs, and so on, then this is the place to do it. 561 */ 562 563 for (adh = host_head, first_adh = NULL, next_adh = NULL, seen_last = 0; 564 adh != NULL; adh = adh->next) { 565 if (adh->owner != ad) 566 continue; 567 if (first_adh == NULL) 568 first_adh = adh; 569 if (adh == ad->last_adh) 570 seen_last++; 571 else if (seen_last) 572 next_adh = adh; 573 574 /* First time or current adh is live -> done */ 575 if (ad->last_adh == NULL || (!adh->dead && adh->ld != NULL)) 576 break; 577 } 578 579 /* Round-robin */ 580 if (adh == NULL) 581 adh = (next_adh != NULL) ? next_adh : first_adh; 582 583 if (adh != NULL) 584 ad->last_adh = adh; 585 586 out: 587 (void) pthread_mutex_unlock(&adhostlock); 588 589 /* Found suitable DS, open it if not already opened */ 590 if (idmap_open_conn(adh)) 591 return (adh); 592 593 if (tries-- > 0) 594 goto retry; 595 596 idmapdlog(LOG_ERR, "Couldn't open an LDAP connection to any global " 597 "catalog server!"); 598 599 return (NULL); 600 } 601 602 static 603 void 604 idmap_release_conn(ad_host_t *adh) 605 { 606 (void) pthread_mutex_lock(&adh->lock); 607 if (atomic_dec_32_nv(&adh->ref) == 0) 608 adh->idletime = time(NULL); 609 (void) pthread_mutex_unlock(&adh->lock); 610 } 611 612 /* 613 * Take ad_host_config_t information, create a ad_host_t, 614 * populate it and add it to the list of hosts. 615 */ 616 617 int 618 idmap_add_ds(ad_t *ad, const char *host, int port) 619 { 620 ad_host_t *p; 621 ad_host_t *new = NULL; 622 int ret = -1; 623 624 if (port == 0) 625 port = (int)ad->partition; 626 627 (void) pthread_mutex_lock(&adhostlock); 628 for (p = host_head; p != NULL; p = p->next) { 629 if (p->owner != ad) 630 continue; 631 632 if (strcmp(host, p->host) == 0 && p->port == port) { 633 /* already added */ 634 ret = 0; 635 goto err; 636 } 637 } 638 639 /* add new entry */ 640 new = (ad_host_t *)calloc(1, sizeof (ad_host_t)); 641 if (new == NULL) 642 goto err; 643 new->owner = ad; 644 new->port = port; 645 new->dead = 0; 646 if ((new->host = strdup(host)) == NULL) 647 goto err; 648 649 /* default to SASL GSSAPI only for now */ 650 new->saslflags = LDAP_SASL_INTERACTIVE; 651 new->saslmech = "GSSAPI"; 652 653 if ((ret = pthread_mutex_init(&new->lock, NULL)) != 0) { 654 free(new->host); 655 new->host = NULL; 656 errno = ret; 657 ret = -1; 658 goto err; 659 } 660 661 /* link in */ 662 new->next = host_head; 663 host_head = new; 664 665 /* Start reaper if it doesn't exist */ 666 if (reaperid == 0) 667 (void) pthread_create(&reaperid, NULL, 668 (void *(*)(void *))adreaper, (void *)NULL); 669 670 err: 671 (void) pthread_mutex_unlock(&adhostlock); 672 673 if (ret != 0 && new != NULL) { 674 if (new->host != NULL) { 675 (void) pthread_mutex_destroy(&new->lock); 676 free(new->host); 677 } 678 free(new); 679 } 680 681 return (ret); 682 } 683 684 /* 685 * Free a DS configuration. 686 * Caller must lock the adhostlock mutex 687 */ 688 static void 689 delete_ds(ad_t *ad, const char *host, int port) 690 { 691 ad_host_t **p, *q; 692 693 for (p = &host_head; *p != NULL; p = &((*p)->next)) { 694 if ((*p)->owner != ad || strcmp(host, (*p)->host) != 0 || 695 (*p)->port != port) 696 continue; 697 /* found */ 698 if ((*p)->ref > 0) 699 break; /* still in use */ 700 701 q = *p; 702 *p = (*p)->next; 703 704 (void) pthread_mutex_destroy(&q->lock); 705 706 if (q->ld) 707 (void) ldap_unbind(q->ld); 708 if (q->host) 709 free(q->host); 710 free(q); 711 break; 712 } 713 714 } 715 716 717 /* 718 * Convert a binary SID in a BerValue to a sid_t 719 */ 720 static 721 int 722 idmap_getsid(BerValue *bval, sid_t *sidp) 723 { 724 int i, j; 725 uchar_t *v; 726 uint32_t a; 727 728 /* 729 * The binary format of a SID is as follows: 730 * 731 * byte #0: version, always 0x01 732 * byte #1: RID count, always <= 0x0f 733 * bytes #2-#7: SID authority, big-endian 48-bit unsigned int 734 * 735 * followed by RID count RIDs, each a little-endian, unsigned 736 * 32-bit int. 737 */ 738 /* 739 * Sanity checks: must have at least one RID, version must be 740 * 0x01, and the length must be 8 + rid count * 4 741 */ 742 if (bval->bv_len > 8 && bval->bv_val[0] == 0x01 && 743 bval->bv_len == 1 + 1 + 6 + bval->bv_val[1] * 4) { 744 v = (uchar_t *)bval->bv_val; 745 sidp->version = v[0]; 746 sidp->sub_authority_count = v[1]; 747 sidp->authority = 748 /* big endian -- so start from the left */ 749 ((u_longlong_t)v[2] << 40) | 750 ((u_longlong_t)v[3] << 32) | 751 ((u_longlong_t)v[4] << 24) | 752 ((u_longlong_t)v[5] << 16) | 753 ((u_longlong_t)v[6] << 8) | 754 (u_longlong_t)v[7]; 755 for (i = 0; i < sidp->sub_authority_count; i++) { 756 j = 8 + (i * 4); 757 /* little endian -- so start from the right */ 758 a = (v[j + 3] << 24) | (v[j + 2] << 16) | 759 (v[j + 1] << 8) | (v[j]); 760 sidp->sub_authorities[i] = a; 761 } 762 return (0); 763 } 764 return (-1); 765 } 766 767 /* 768 * Convert a sid_t to S-1-... 769 */ 770 static 771 char * 772 idmap_sid2txt(sid_t *sidp) 773 { 774 int rlen, i, len; 775 char *str, *cp; 776 777 if (sidp->version != 1) 778 return (NULL); 779 780 len = sizeof ("S-1-") - 1; 781 782 /* 783 * We could optimize like so, but, why? 784 * if (sidp->authority < 10) 785 * len += 2; 786 * else if (sidp->authority < 100) 787 * len += 3; 788 * else 789 * len += snprintf(NULL, 0"%llu", sidp->authority); 790 */ 791 len += snprintf(NULL, 0, "%llu", sidp->authority); 792 793 /* Max length of a uint32_t printed out in ASCII is 10 bytes */ 794 len += 1 + (sidp->sub_authority_count + 1) * 10; 795 796 if ((cp = str = malloc(len)) == NULL) 797 return (NULL); 798 799 rlen = snprintf(str, len, "S-1-%llu", sidp->authority); 800 801 cp += rlen; 802 len -= rlen; 803 804 for (i = 0; i < sidp->sub_authority_count; i++) { 805 assert(len > 0); 806 rlen = snprintf(cp, len, "-%u", sidp->sub_authorities[i]); 807 cp += rlen; 808 len -= rlen; 809 assert(len >= 0); 810 } 811 812 return (str); 813 } 814 815 /* 816 * Convert a sid_t to on-the-wire encoding 817 */ 818 static 819 int 820 idmap_sid2binsid(sid_t *sid, uchar_t *binsid, int binsidlen) 821 { 822 uchar_t *p; 823 int i; 824 uint64_t a; 825 uint32_t r; 826 827 if (sid->version != 1 || 828 binsidlen != (1 + 1 + 6 + sid->sub_authority_count * 4)) 829 return (-1); 830 831 p = binsid; 832 *p++ = 0x01; /* version */ 833 /* sub authority count */ 834 *p++ = sid->sub_authority_count; 835 /* Authority */ 836 a = sid->authority; 837 /* big-endian -- start from left */ 838 *p++ = (a >> 40) & 0xFF; 839 *p++ = (a >> 32) & 0xFF; 840 *p++ = (a >> 24) & 0xFF; 841 *p++ = (a >> 16) & 0xFF; 842 *p++ = (a >> 8) & 0xFF; 843 *p++ = a & 0xFF; 844 845 /* sub-authorities */ 846 for (i = 0; i < sid->sub_authority_count; i++) { 847 r = sid->sub_authorities[i]; 848 /* little-endian -- start from right */ 849 *p++ = (r & 0x000000FF); 850 *p++ = (r & 0x0000FF00) >> 8; 851 *p++ = (r & 0x00FF0000) >> 16; 852 *p++ = (r & 0xFF000000) >> 24; 853 } 854 855 return (0); 856 } 857 858 /* 859 * Convert a stringified SID (S-1-...) into a hex-encoded version of the 860 * on-the-wire encoding, but with each pair of hex digits pre-pended 861 * with a '\', so we can pass this to libldap. 862 */ 863 static 864 int 865 idmap_txtsid2hexbinsid(const char *txt, const rid_t *rid, 866 char *hexbinsid, int hexbinsidlen) 867 { 868 sid_t sid = { 0 }; 869 int i, j; 870 const char *cp; 871 char *ecp; 872 u_longlong_t a; 873 unsigned long r; 874 uchar_t *binsid, b, hb; 875 876 /* Only version 1 SIDs please */ 877 if (strncmp(txt, "S-1-", strlen("S-1-")) != 0) 878 return (-1); 879 880 if (strlen(txt) < (strlen("S-1-") + 1)) 881 return (-1); 882 883 /* count '-'s */ 884 for (j = 0, cp = strchr(txt, '-'); 885 cp != NULL && *cp != '\0'; 886 j++, cp = strchr(cp + 1, '-')) { 887 /* can't end on a '-' */ 888 if (*(cp + 1) == '\0') 889 return (-1); 890 } 891 892 /* Adjust count for version and authority */ 893 j -= 2; 894 895 /* we know the version number and RID count */ 896 sid.version = 1; 897 sid.sub_authority_count = (rid != NULL) ? j + 1 : j; 898 899 /* must have at least one RID, but not too many */ 900 if (sid.sub_authority_count < 1 || 901 sid.sub_authority_count > SID_MAX_SUB_AUTHORITIES) 902 return (-1); 903 904 /* check that we only have digits and '-' */ 905 if (strspn(txt + 1, "0123456789-") < (strlen(txt) - 1)) 906 return (-1); 907 908 cp = txt + strlen("S-1-"); 909 910 /* 64-bit safe parsing of unsigned 48-bit authority value */ 911 errno = 0; 912 a = strtoull(cp, &ecp, 10); 913 914 /* errors parsing the authority or too many bits */ 915 if (cp == ecp || (a == 0 && errno == EINVAL) || 916 (a == ULLONG_MAX && errno == ERANGE) || 917 (a & 0x0000ffffffffffffULL) != a) 918 return (-1); 919 920 cp = ecp; 921 922 sid.authority = (uint64_t)a; 923 924 for (i = 0; i < j; i++) { 925 if (*cp++ != '-') 926 return (-1); 927 /* 64-bit safe parsing of unsigned 32-bit RID */ 928 errno = 0; 929 r = strtoul(cp, &ecp, 10); 930 /* errors parsing the RID or too many bits */ 931 if (cp == ecp || (r == 0 && errno == EINVAL) || 932 (r == ULONG_MAX && errno == ERANGE) || 933 (r & 0xffffffffUL) != r) 934 return (-1); 935 sid.sub_authorities[i] = (uint32_t)r; 936 cp = ecp; 937 } 938 939 /* check that all of the string SID has been consumed */ 940 if (*cp != '\0') 941 return (-1); 942 943 if (rid != NULL) 944 sid.sub_authorities[j] = *rid; 945 946 j = 1 + 1 + 6 + sid.sub_authority_count * 4; 947 948 if (hexbinsidlen < (j * 3)) 949 return (-2); 950 951 /* binary encode the SID */ 952 binsid = (uchar_t *)alloca(j); 953 (void) idmap_sid2binsid(&sid, binsid, j); 954 955 /* hex encode, with a backslash before each byte */ 956 for (ecp = hexbinsid, i = 0; i < j; i++) { 957 b = binsid[i]; 958 *ecp++ = '\\'; 959 hb = (b >> 4) & 0xF; 960 *ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A'); 961 hb = b & 0xF; 962 *ecp++ = (hb <= 0x9 ? hb + '0' : hb - 10 + 'A'); 963 } 964 *ecp = '\0'; 965 966 return (0); 967 } 968 969 static 970 char * 971 convert_bval2sid(BerValue *bval, rid_t *rid) 972 { 973 sid_t sid; 974 975 if (idmap_getsid(bval, &sid) < 0) 976 return (NULL); 977 978 /* 979 * If desired and if the SID is what should be a domain/computer 980 * user or group SID (i.e., S-1-5-w-x-y-z-<user/group RID>) then 981 * save the last RID and truncate the SID 982 */ 983 if (rid != NULL && sid.authority == 5 && sid.sub_authority_count == 5) 984 *rid = sid.sub_authorities[--sid.sub_authority_count]; 985 return (idmap_sid2txt(&sid)); 986 } 987 988 989 idmap_retcode 990 idmap_lookup_batch_start(ad_t *ad, int nqueries, idmap_query_state_t **state) 991 { 992 idmap_query_state_t *new_state; 993 ad_host_t *adh = NULL; 994 995 *state = NULL; 996 997 /* Note: ad->dflt_w2k_dom cannot be NULL - see idmap_ad_alloc() */ 998 if (ad == NULL || *ad->dflt_w2k_dom == '\0') 999 return (IDMAP_ERR_INTERNAL); 1000 1001 adh = idmap_get_conn(ad); 1002 if (adh == NULL) 1003 return (IDMAP_ERR_OTHER); 1004 1005 new_state = calloc(1, sizeof (idmap_query_state_t) + 1006 (nqueries - 1) * sizeof (idmap_q_t)); 1007 1008 if (new_state == NULL) 1009 return (IDMAP_ERR_MEMORY); 1010 1011 new_state->ref_cnt = 1; 1012 new_state->qadh = adh; 1013 new_state->qcount = nqueries; 1014 new_state->qadh_gen = adh->generation; 1015 /* should be -1, but the atomic routines want unsigned */ 1016 new_state->qlastsent = 0; 1017 (void) pthread_cond_init(&new_state->cv, NULL); 1018 1019 (void) pthread_mutex_lock(&qstatelock); 1020 new_state->next = qstatehead; 1021 qstatehead = new_state; 1022 (void) pthread_mutex_unlock(&qstatelock); 1023 1024 *state = new_state; 1025 1026 return (IDMAP_SUCCESS); 1027 } 1028 1029 /* 1030 * Set unixuser_attr and unixgroup_attr for AD-based name mapping 1031 */ 1032 void 1033 idmap_lookup_batch_set_unixattr(idmap_query_state_t *state, 1034 const char *unixuser_attr, const char *unixgroup_attr) { 1035 state->ad_unixuser_attr = unixuser_attr; 1036 state->ad_unixgroup_attr = unixgroup_attr; 1037 } 1038 1039 /* 1040 * Find the idmap_query_state_t to which a given LDAP result msgid on a 1041 * given connection belongs. This routine increaments the reference count 1042 * so that the object can not be freed. idmap_lookup_unlock_batch() 1043 * must be called to decreament the reference count. 1044 */ 1045 static 1046 int 1047 idmap_msgid2query(ad_host_t *adh, int msgid, 1048 idmap_query_state_t **state, int *qid) 1049 { 1050 idmap_query_state_t *p; 1051 int i; 1052 1053 (void) pthread_mutex_lock(&qstatelock); 1054 for (p = qstatehead; p != NULL; p = p->next) { 1055 if (p->qadh != adh || adh->generation != p->qadh_gen) 1056 continue; 1057 for (i = 0; i < p->qcount; i++) { 1058 if ((p->queries[i]).msgid == msgid) { 1059 p->ref_cnt++; 1060 *state = p; 1061 *qid = i; 1062 (void) pthread_mutex_unlock(&qstatelock); 1063 return (1); 1064 } 1065 } 1066 } 1067 (void) pthread_mutex_unlock(&qstatelock); 1068 return (0); 1069 } 1070 1071 /* 1072 * Take parsed attribute values from a search result entry and check if 1073 * it is the result that was desired and, if so, set the result fields 1074 * of the given idmap_q_t. 1075 * 1076 * Frees the unused char * values. 1077 */ 1078 static 1079 void 1080 idmap_setqresults(idmap_q_t *q, char *san, char *dn, char *sid, 1081 rid_t rid, int sid_type, char *unixname) 1082 { 1083 char *domain; 1084 int err1, err2; 1085 1086 assert(dn != NULL); 1087 1088 if ((domain = dn2dns(dn)) == NULL) 1089 goto out; 1090 1091 if (q->ecanonname != NULL && san != NULL) { 1092 /* Check that this is the canonname that we were looking for */ 1093 if (u8_strcmp(q->ecanonname, san, 0, 1094 U8_STRCMP_CI_LOWER, /* no normalization, for now */ 1095 U8_UNICODE_LATEST, &err1) != 0 || err1 != 0) 1096 goto out; 1097 } 1098 1099 if (q->edomain != NULL) { 1100 /* Check that this is the domain that we were looking for */ 1101 if (u8_strcmp(q->edomain, domain, 0, U8_STRCMP_CI_LOWER, 1102 U8_UNICODE_LATEST, &err2) != 0 || err2 != 0) 1103 goto out; 1104 } 1105 1106 /* Set results */ 1107 if (q->sid) { 1108 *q->sid = sid; 1109 sid = NULL; 1110 } 1111 if (q->rid) 1112 *q->rid = rid; 1113 if (q->sid_type) 1114 *q->sid_type = sid_type; 1115 if (q->unixname) { 1116 *q->unixname = unixname; 1117 unixname = NULL; 1118 } 1119 if (q->domain != NULL) { 1120 *q->domain = domain; 1121 domain = NULL; 1122 } 1123 if (q->canonname != NULL) { 1124 *q->canonname = san; 1125 san = NULL; 1126 } 1127 1128 /* Always have q->rc; idmap_extract_object() asserts this */ 1129 *q->rc = IDMAP_SUCCESS; 1130 1131 out: 1132 /* Free unused attribute values */ 1133 free(san); 1134 free(sid); 1135 free(domain); 1136 free(unixname); 1137 } 1138 1139 /* 1140 * The following three functions extract objectSid, sAMAccountName and 1141 * objectClass attribute values and, in the case of objectSid and 1142 * objectClass, parse them. 1143 * 1144 * idmap_setqresults() takes care of dealing with the result entry's DN. 1145 */ 1146 1147 /* 1148 * Return a NUL-terminated stringified SID from the value of an 1149 * objectSid attribute and put the last RID in *rid. 1150 */ 1151 static 1152 char * 1153 idmap_bv_objsid2sidstr(BerValue **bvalues, rid_t *rid) 1154 { 1155 char *sid; 1156 1157 if (bvalues == NULL) 1158 return (NULL); 1159 /* objectSid is single valued */ 1160 if ((sid = convert_bval2sid(bvalues[0], rid)) == NULL) 1161 return (NULL); 1162 return (sid); 1163 } 1164 1165 /* 1166 * Return a NUL-terminated string from the value of a sAMAccountName 1167 * or unixname attribute. 1168 */ 1169 static 1170 char * 1171 idmap_bv_name2str(BerValue **bvalues) 1172 { 1173 char *s; 1174 1175 if (bvalues == NULL || bvalues[0] == NULL || 1176 bvalues[0]->bv_val == NULL) 1177 return (NULL); 1178 1179 if ((s = malloc(bvalues[0]->bv_len + 1)) == NULL) 1180 return (NULL); 1181 1182 (void) snprintf(s, bvalues[0]->bv_len + 1, "%.*s", bvalues[0]->bv_len, 1183 bvalues[0]->bv_val); 1184 1185 return (s); 1186 } 1187 1188 1189 #define BVAL_CASEEQ(bv, str) \ 1190 (((*(bv))->bv_len == (sizeof (str) - 1)) && \ 1191 strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0) 1192 1193 /* 1194 * Extract the class of the result entry. Returns 1 on success, 0 on 1195 * failure. 1196 */ 1197 static 1198 int 1199 idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type) 1200 { 1201 BerValue **cbval; 1202 1203 *sid_type = _IDMAP_T_OTHER; 1204 if (bvalues == NULL) 1205 return (0); 1206 1207 /* 1208 * We iterate over all the values because computer is a 1209 * sub-class of user. 1210 */ 1211 for (cbval = bvalues; *cbval != NULL; cbval++) { 1212 if (BVAL_CASEEQ(cbval, "Computer")) { 1213 *sid_type = _IDMAP_T_COMPUTER; 1214 break; 1215 } else if (BVAL_CASEEQ(cbval, "Group")) { 1216 *sid_type = _IDMAP_T_GROUP; 1217 break; 1218 } else if (BVAL_CASEEQ(cbval, "USER")) { 1219 *sid_type = _IDMAP_T_USER; 1220 /* Continue looping -- this may be a computer yet */ 1221 } 1222 /* 1223 * "else if (*sid_type = _IDMAP_T_USER)" then this is a 1224 * new sub-class of user -- what to do with it?? 1225 */ 1226 } 1227 1228 return (1); 1229 } 1230 1231 /* 1232 * Handle a given search result entry 1233 */ 1234 static 1235 void 1236 idmap_extract_object(idmap_query_state_t *state, int qid, LDAPMessage *res) 1237 { 1238 BerElement *ber = NULL; 1239 BerValue **bvalues; 1240 ad_host_t *adh; 1241 idmap_q_t *q; 1242 char *attr; 1243 const char *unixuser_attr = NULL; 1244 const char *unixgroup_attr = NULL; 1245 char *unixuser = NULL; 1246 char *unixgroup = NULL; 1247 char *dn = NULL; 1248 char *san = NULL; 1249 char *sid = NULL; 1250 rid_t rid = 0; 1251 int sid_type = _IDMAP_T_UNDEF; 1252 int has_class, has_san, has_sid; 1253 int has_unixuser, has_unixgroup; 1254 1255 adh = state->qadh; 1256 1257 (void) pthread_mutex_lock(&adh->lock); 1258 1259 q = &(state->queries[qid]); 1260 1261 assert(q->rc != NULL); 1262 1263 if (*q->rc == IDMAP_SUCCESS || adh->dead || 1264 (dn = ldap_get_dn(adh->ld, res)) == NULL) { 1265 (void) pthread_mutex_unlock(&adh->lock); 1266 return; 1267 } 1268 1269 assert(q->domain == NULL || *q->domain == NULL); 1270 1271 /* 1272 * If the caller has requested unixname then determine the 1273 * AD attribute name that will have the unixname. 1274 */ 1275 if (q->unixname != NULL) { 1276 if (q->eunixtype == _IDMAP_T_USER) 1277 unixuser_attr = state->ad_unixuser_attr; 1278 else if (q->eunixtype == _IDMAP_T_GROUP) 1279 unixgroup_attr = state->ad_unixgroup_attr; 1280 else if (q->eunixtype == _IDMAP_T_UNDEF) { 1281 /* 1282 * This is the case where we don't know 1283 * before hand whether we need unixuser 1284 * or unixgroup. This will be determined 1285 * by the "sid_type" (i.e whether the given 1286 * winname is user or group). If sid_type 1287 * turns out to be user we will return 1288 * unixuser (if found) and if it is a group 1289 * we will return unixgroup (if found). We 1290 * lookup for both ad_unixuser_attr and 1291 * ad_unixgroup_attr and discard one of them 1292 * after we know the "sidtype". This 1293 * supports the following type of lookups. 1294 * 1295 * Example: 1296 * $idmap show -c winname:foo 1297 * In the above example, idmap will 1298 * return uid if winname is winuser 1299 * and gid if winname is wingroup. 1300 */ 1301 unixuser_attr = state->ad_unixuser_attr; 1302 unixgroup_attr = state->ad_unixgroup_attr; 1303 } 1304 } 1305 1306 has_class = has_san = has_sid = has_unixuser = has_unixgroup = 0; 1307 for (attr = ldap_first_attribute(adh->ld, res, &ber); attr != NULL; 1308 attr = ldap_next_attribute(adh->ld, res, ber)) { 1309 bvalues = NULL; /* for memory management below */ 1310 1311 /* 1312 * If this is an attribute we are looking for and 1313 * haven't seen it yet, parse it 1314 */ 1315 if (q->sid != NULL && !has_sid && 1316 strcasecmp(attr, OBJSID) == 0) { 1317 bvalues = ldap_get_values_len(adh->ld, res, attr); 1318 sid = idmap_bv_objsid2sidstr(bvalues, &rid); 1319 has_sid = (sid != NULL); 1320 } else if (!has_san && strcasecmp(attr, SAN) == 0) { 1321 bvalues = ldap_get_values_len(adh->ld, res, attr); 1322 san = idmap_bv_name2str(bvalues); 1323 has_san = (san != NULL); 1324 } else if (!has_class && strcasecmp(attr, OBJCLASS) == 0) { 1325 bvalues = ldap_get_values_len(adh->ld, res, attr); 1326 has_class = idmap_bv_objclass2sidtype(bvalues, 1327 &sid_type); 1328 if (has_class && q->unixname != NULL && 1329 q->eunixtype == _IDMAP_T_UNDEF) { 1330 /* 1331 * This is the case where we didn't 1332 * know whether we wanted unixuser or 1333 * unixgroup as described above. 1334 * Now since we know the "sid_type" 1335 * we discard the unwanted value 1336 * if it was retrieved before we 1337 * got here. 1338 */ 1339 if (sid_type == _IDMAP_T_USER) { 1340 free(unixgroup); 1341 unixgroup_attr = unixgroup = NULL; 1342 } else if (sid_type == _IDMAP_T_GROUP) { 1343 free(unixuser); 1344 unixuser_attr = unixuser = NULL; 1345 } else { 1346 free(unixuser); 1347 free(unixgroup); 1348 unixuser_attr = unixuser = NULL; 1349 unixgroup_attr = unixgroup = NULL; 1350 } 1351 } 1352 } else if (!has_unixuser && unixuser_attr != NULL && 1353 strcasecmp(attr, unixuser_attr) == 0) { 1354 bvalues = ldap_get_values_len(adh->ld, res, attr); 1355 unixuser = idmap_bv_name2str(bvalues); 1356 has_unixuser = (unixuser != NULL); 1357 } else if (!has_unixgroup && unixgroup_attr != NULL && 1358 strcasecmp(attr, unixgroup_attr) == 0) { 1359 bvalues = ldap_get_values_len(adh->ld, res, attr); 1360 unixgroup = idmap_bv_name2str(bvalues); 1361 has_unixgroup = (unixgroup != NULL); 1362 } 1363 1364 if (bvalues != NULL) 1365 ldap_value_free_len(bvalues); 1366 ldap_memfree(attr); 1367 1368 if (has_class && has_san && 1369 (q->sid == NULL || has_sid) && 1370 (unixuser_attr == NULL || has_unixuser) && 1371 (unixgroup_attr == NULL || has_unixgroup)) { 1372 /* Got what we need */ 1373 break; 1374 } 1375 } 1376 1377 if (!has_class) { 1378 /* 1379 * Didn't find objectclass. Something's wrong with our 1380 * AD data. 1381 */ 1382 free(san); 1383 free(sid); 1384 free(unixuser); 1385 free(unixgroup); 1386 } else { 1387 /* 1388 * Either we got what we needed and came out of the loop 1389 * early OR we completed the loop in which case we didn't 1390 * find some attributes that we were looking for. In either 1391 * case set the result with what we got. 1392 */ 1393 idmap_setqresults(q, san, dn, sid, rid, sid_type, 1394 (unixuser != NULL) ? unixuser : unixgroup); 1395 } 1396 1397 (void) pthread_mutex_unlock(&adh->lock); 1398 1399 if (ber != NULL) 1400 ber_free(ber, 0); 1401 1402 ldap_memfree(dn); 1403 } 1404 1405 /* 1406 * Try to get a result; if there is one, find the corresponding 1407 * idmap_q_t and process the result. 1408 */ 1409 static 1410 int 1411 idmap_get_adobject_batch(ad_host_t *adh, struct timeval *timeout) 1412 { 1413 idmap_query_state_t *query_state; 1414 LDAPMessage *res = NULL; 1415 int rc, ret, msgid, qid; 1416 1417 (void) pthread_mutex_lock(&adh->lock); 1418 if (adh->dead) { 1419 (void) pthread_mutex_unlock(&adh->lock); 1420 return (-1); 1421 } 1422 1423 /* Get one result */ 1424 rc = ldap_result(adh->ld, LDAP_RES_ANY, 0, 1425 timeout, &res); 1426 if (rc == LDAP_UNAVAILABLE || rc == LDAP_UNWILLING_TO_PERFORM || 1427 rc == LDAP_CONNECT_ERROR || rc == LDAP_SERVER_DOWN || 1428 rc == LDAP_BUSY) 1429 adh->dead = 1; 1430 (void) pthread_mutex_unlock(&adh->lock); 1431 1432 if (adh->dead) 1433 return (-1); 1434 1435 switch (rc) { 1436 case LDAP_RES_SEARCH_RESULT: 1437 /* We have all the LDAP replies for some search... */ 1438 msgid = ldap_msgid(res); 1439 if (idmap_msgid2query(adh, msgid, 1440 &query_state, &qid)) { 1441 /* ...so we can decrement qinflight */ 1442 atomic_dec_32(&query_state->qinflight); 1443 /* We've seen all the result entries we'll see */ 1444 if (*query_state->queries[qid].rc != IDMAP_SUCCESS) 1445 *query_state->queries[qid].rc = 1446 IDMAP_ERR_NOTFOUND; 1447 idmap_lookup_unlock_batch(&query_state); 1448 } 1449 (void) ldap_msgfree(res); 1450 ret = 0; 1451 break; 1452 case LDAP_RES_SEARCH_REFERENCE: 1453 /* 1454 * We have no need for these at the moment. Eventually, 1455 * when we query things that we can't expect to find in 1456 * the Global Catalog then we'll need to learn to follow 1457 * references. 1458 */ 1459 (void) ldap_msgfree(res); 1460 ret = 0; 1461 break; 1462 case LDAP_RES_SEARCH_ENTRY: 1463 /* Got a result */ 1464 msgid = ldap_msgid(res); 1465 if (idmap_msgid2query(adh, msgid, 1466 &query_state, &qid)) { 1467 idmap_extract_object(query_state, qid, res); 1468 /* we saw at least one result */ 1469 idmap_lookup_unlock_batch(&query_state); 1470 } 1471 (void) ldap_msgfree(res); 1472 ret = 0; 1473 break; 1474 default: 1475 /* timeout or error; treat the same */ 1476 ret = -1; 1477 break; 1478 } 1479 1480 return (ret); 1481 } 1482 1483 /* 1484 * This routine decreament the reference count of the 1485 * idmap_query_state_t 1486 */ 1487 static void 1488 idmap_lookup_unlock_batch(idmap_query_state_t **state) 1489 { 1490 /* 1491 * Decrement reference count with qstatelock locked 1492 */ 1493 (void) pthread_mutex_lock(&qstatelock); 1494 (*state)->ref_cnt--; 1495 /* 1496 * If there are no references wakup the allocating thread 1497 */ 1498 if ((*state)->ref_cnt == 0) 1499 (void) pthread_cond_signal(&(*state)->cv); 1500 (void) pthread_mutex_unlock(&qstatelock); 1501 *state = NULL; 1502 } 1503 1504 static 1505 void 1506 idmap_cleanup_batch(idmap_query_state_t *batch) 1507 { 1508 int i; 1509 1510 for (i = 0; i < batch->qcount; i++) { 1511 if (batch->queries[i].ecanonname != NULL) 1512 free(batch->queries[i].ecanonname); 1513 batch->queries[i].ecanonname = NULL; 1514 if (batch->queries[i].edomain != NULL) 1515 free(batch->queries[i].edomain); 1516 batch->queries[i].edomain = NULL; 1517 } 1518 } 1519 1520 /* 1521 * This routine frees the idmap_query_state_t structure 1522 * If the reference count is greater than 1 it waits 1523 * for the other threads to finish using it. 1524 */ 1525 void 1526 idmap_lookup_release_batch(idmap_query_state_t **state) 1527 { 1528 idmap_query_state_t **p; 1529 1530 /* 1531 * Decrement reference count with qstatelock locked 1532 * and wait for reference count to get to zero 1533 */ 1534 (void) pthread_mutex_lock(&qstatelock); 1535 (*state)->ref_cnt--; 1536 while ((*state)->ref_cnt > 0) { 1537 (void) pthread_cond_wait(&(*state)->cv, &qstatelock); 1538 } 1539 1540 /* Remove this state struct from the list of state structs */ 1541 for (p = &qstatehead; *p != NULL; p = &(*p)->next) { 1542 if (*p == (*state)) { 1543 *p = (*state)->next; 1544 break; 1545 } 1546 } 1547 1548 idmap_cleanup_batch(*state); 1549 1550 (void) pthread_mutex_unlock(&qstatelock); 1551 1552 (void) pthread_cond_destroy(&(*state)->cv); 1553 1554 idmap_release_conn((*state)->qadh); 1555 1556 free(*state); 1557 *state = NULL; 1558 } 1559 1560 idmap_retcode 1561 idmap_lookup_batch_end(idmap_query_state_t **state, 1562 struct timeval *timeout) 1563 { 1564 int rc = LDAP_SUCCESS; 1565 idmap_retcode retcode = IDMAP_SUCCESS; 1566 1567 (*state)->qdead = 1; 1568 1569 /* Process results until done or until timeout, if given */ 1570 while ((*state)->qinflight > 0) { 1571 if ((rc = idmap_get_adobject_batch((*state)->qadh, 1572 timeout)) != 0) 1573 break; 1574 } 1575 1576 if (rc == LDAP_UNAVAILABLE || rc == LDAP_UNWILLING_TO_PERFORM || 1577 rc == LDAP_CONNECT_ERROR || rc == LDAP_SERVER_DOWN || 1578 rc == LDAP_BUSY) { 1579 retcode = IDMAP_ERR_RETRIABLE_NET_ERR; 1580 (*state)->qadh->dead = 1; 1581 } 1582 1583 idmap_lookup_release_batch(state); 1584 1585 return (retcode); 1586 } 1587 1588 /* 1589 * Send one prepared search, queue up msgid, process what results are 1590 * available 1591 */ 1592 static 1593 idmap_retcode 1594 idmap_batch_add1(idmap_query_state_t *state, 1595 const char *filter, char *ecanonname, char *edomain, int eunixtype, 1596 char **canonname, char **dname, char **sid, rid_t *rid, 1597 int *sid_type, char **unixname, idmap_retcode *rc) 1598 { 1599 idmap_retcode retcode = IDMAP_SUCCESS; 1600 int lrc, qid, i; 1601 struct timeval tv; 1602 idmap_q_t *q; 1603 static char *attrs[] = { 1604 SAN, 1605 OBJSID, 1606 OBJCLASS, 1607 NULL, /* placeholder for unixname attr */ 1608 NULL, /* placeholder for unixname attr */ 1609 NULL 1610 }; 1611 1612 qid = atomic_inc_32_nv(&state->qlastsent) - 1; 1613 1614 q = &(state->queries[qid]); 1615 1616 /* 1617 * Remember the expected canonname so we can check the results 1618 * agains it 1619 */ 1620 q->ecanonname = ecanonname; 1621 q->edomain = edomain; 1622 q->eunixtype = eunixtype; 1623 1624 /* Remember where to put the results */ 1625 q->canonname = canonname; 1626 q->sid = sid; 1627 q->domain = dname; 1628 q->rid = rid; 1629 q->sid_type = sid_type; 1630 q->rc = rc; 1631 q->unixname = unixname; 1632 1633 /* Add unixuser/unixgroup attribute names to the attrs list */ 1634 if (unixname != NULL) { 1635 i = 3; 1636 if (eunixtype != _IDMAP_T_GROUP && 1637 state->ad_unixuser_attr != NULL) 1638 attrs[i++] = (char *)state->ad_unixuser_attr; 1639 if (eunixtype != _IDMAP_T_USER && 1640 state->ad_unixgroup_attr != NULL) 1641 attrs[i] = (char *)state->ad_unixgroup_attr; 1642 } 1643 1644 /* 1645 * Provide sane defaults for the results in case we never hear 1646 * back from the DS before closing the connection. 1647 * 1648 * In particular we default the result to indicate a retriable 1649 * error. The first complete matching result entry will cause 1650 * this to be set to IDMAP_SUCCESS, and the end of the results 1651 * for this search will cause this to indicate "not found" if no 1652 * result entries arrived or no complete ones matched the lookup 1653 * we were doing. 1654 */ 1655 *rc = IDMAP_ERR_RETRIABLE_NET_ERR; 1656 if (sid_type != NULL) 1657 *sid_type = _IDMAP_T_OTHER; 1658 if (sid != NULL) 1659 *sid = NULL; 1660 if (canonname != NULL) 1661 *canonname = NULL; 1662 if (dname != NULL) 1663 *dname = NULL; 1664 if (rid != NULL) 1665 *rid = 0; 1666 1667 /* Send this lookup, don't wait for a result here */ 1668 (void) pthread_mutex_lock(&state->qadh->lock); 1669 1670 if (!state->qadh->dead) { 1671 state->qadh->idletime = time(NULL); 1672 lrc = ldap_search_ext(state->qadh->ld, "", 1673 LDAP_SCOPE_SUBTREE, filter, attrs, 0, NULL, NULL, 1674 NULL, -1, &q->msgid); 1675 if (lrc == LDAP_BUSY || lrc == LDAP_UNAVAILABLE || 1676 lrc == LDAP_CONNECT_ERROR || lrc == LDAP_SERVER_DOWN || 1677 lrc == LDAP_UNWILLING_TO_PERFORM) { 1678 retcode = IDMAP_ERR_RETRIABLE_NET_ERR; 1679 state->qadh->dead = 1; 1680 } else if (lrc != LDAP_SUCCESS) { 1681 retcode = IDMAP_ERR_OTHER; 1682 state->qadh->dead = 1; 1683 } 1684 } 1685 (void) pthread_mutex_unlock(&state->qadh->lock); 1686 1687 if (state->qadh->dead) 1688 return (retcode); 1689 1690 atomic_inc_32(&state->qinflight); 1691 1692 /* 1693 * Reap as many requests as we can _without_ waiting 1694 * 1695 * We do this to prevent any possible TCP socket buffer 1696 * starvation deadlocks. 1697 */ 1698 (void) memset(&tv, 0, sizeof (tv)); 1699 while (idmap_get_adobject_batch(state->qadh, &tv) == 0) 1700 ; 1701 1702 return (IDMAP_SUCCESS); 1703 } 1704 1705 idmap_retcode 1706 idmap_name2sid_batch_add1(idmap_query_state_t *state, 1707 const char *name, const char *dname, int eunixtype, 1708 char **canonname, char **sid, rid_t *rid, int *sid_type, 1709 char **unixname, idmap_retcode *rc) 1710 { 1711 idmap_retcode retcode; 1712 int len, samAcctNameLen; 1713 char *filter = NULL; 1714 char *ecanonname, *edomain; /* expected canonname */ 1715 1716 /* 1717 * Strategy: search the global catalog for user/group by 1718 * sAMAccountName = user/groupname with "" as the base DN and by 1719 * userPrincipalName = user/groupname@domain. The result 1720 * entries will be checked to conform to the name and domain 1721 * name given here. The DN, sAMAccountName, userPrincipalName, 1722 * objectSid and objectClass of the result entries are all we 1723 * need to figure out which entries match the lookup, the SID of 1724 * the user/group and whether it is a user or a group. 1725 */ 1726 1727 /* 1728 * We need the name and the domain name separately and as 1729 * name@domain. We also allow the domain to be provided 1730 * separately. 1731 */ 1732 samAcctNameLen = strlen(name); 1733 1734 if ((ecanonname = strdup(name)) == NULL) 1735 return (IDMAP_ERR_MEMORY); 1736 1737 if (dname == NULL || *dname == '\0') { 1738 if ((dname = strchr(name, '@')) != NULL) { 1739 /* 'name' is qualified with a domain name */ 1740 if ((edomain = strdup(dname + 1)) == NULL) { 1741 free(ecanonname); 1742 return (IDMAP_ERR_MEMORY); 1743 } 1744 *strchr(ecanonname, '@') = '\0'; 1745 } else { 1746 /* 'name' not qualified and dname not given */ 1747 if (state->qadh->owner->dflt_w2k_dom == NULL || 1748 *state->qadh->owner->dflt_w2k_dom == '\0') { 1749 free(ecanonname); 1750 return (IDMAP_ERR_DOMAIN); 1751 } 1752 edomain = strdup(state->qadh->owner->dflt_w2k_dom); 1753 if (edomain == NULL) { 1754 free(ecanonname); 1755 return (IDMAP_ERR_MEMORY); 1756 } 1757 } 1758 } else { 1759 if ((edomain = strdup(dname)) == NULL) { 1760 free(ecanonname); 1761 return (IDMAP_ERR_MEMORY); 1762 } 1763 } 1764 1765 /* Assemble filter */ 1766 len = snprintf(NULL, 0, SANFILTER, samAcctNameLen, name) + 1; 1767 if ((filter = (char *)malloc(len)) == NULL) { 1768 free(ecanonname); 1769 return (IDMAP_ERR_MEMORY); 1770 } 1771 (void) snprintf(filter, len, SANFILTER, samAcctNameLen, name); 1772 1773 retcode = idmap_batch_add1(state, filter, ecanonname, edomain, 1774 eunixtype, canonname, NULL, sid, rid, sid_type, unixname, rc); 1775 1776 free(filter); 1777 1778 return (retcode); 1779 } 1780 1781 idmap_retcode 1782 idmap_sid2name_batch_add1(idmap_query_state_t *state, 1783 const char *sid, const rid_t *rid, int eunixtype, 1784 char **name, char **dname, int *sid_type, char **unixname, 1785 idmap_retcode *rc) 1786 { 1787 idmap_retcode retcode; 1788 int flen, ret; 1789 char *filter = NULL; 1790 char cbinsid[MAXHEXBINSID + 1]; 1791 1792 /* 1793 * Strategy: search [the global catalog] for user/group by 1794 * objectSid = SID with empty base DN. The DN, sAMAccountName 1795 * and objectClass of the result are all we need to figure out 1796 * the name of the SID and whether it is a user, a group or a 1797 * computer. 1798 */ 1799 1800 ret = idmap_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid)); 1801 if (ret != 0) 1802 return (IDMAP_ERR_SID); 1803 1804 /* Assemble filter */ 1805 flen = snprintf(NULL, 0, OBJSIDFILTER, cbinsid) + 1; 1806 if ((filter = (char *)malloc(flen)) == NULL) 1807 return (IDMAP_ERR_MEMORY); 1808 (void) snprintf(filter, flen, OBJSIDFILTER, cbinsid); 1809 1810 retcode = idmap_batch_add1(state, filter, NULL, NULL, eunixtype, 1811 name, dname, NULL, NULL, sid_type, unixname, rc); 1812 1813 free(filter); 1814 1815 return (retcode); 1816 } 1817 1818 idmap_retcode 1819 idmap_unixname2sid_batch_add1(idmap_query_state_t *state, 1820 const char *unixname, int is_user, int is_wuser, 1821 char **sid, rid_t *rid, char **name, char **dname, int *sid_type, 1822 idmap_retcode *rc) 1823 { 1824 idmap_retcode retcode; 1825 int len, ulen; 1826 char *filter = NULL; 1827 const char *attrname = NULL; 1828 1829 /* Get unixuser or unixgroup AD attribute name */ 1830 attrname = (is_user) ? 1831 state->ad_unixuser_attr : state->ad_unixgroup_attr; 1832 if (attrname == NULL) 1833 return (IDMAP_ERR_NOTFOUND); 1834 1835 /* Assemble filter */ 1836 ulen = strlen(unixname); 1837 len = snprintf(NULL, 0, "(&(objectclass=%s)(%s=%.*s))", 1838 is_wuser ? "user" : "group", attrname, ulen, unixname) + 1; 1839 if ((filter = (char *)malloc(len)) == NULL) 1840 return (IDMAP_ERR_MEMORY); 1841 (void) snprintf(filter, len, "(&(objectclass=%s)(%s=%.*s))", 1842 is_wuser ? "user" : "group", attrname, ulen, unixname); 1843 1844 retcode = idmap_batch_add1(state, filter, NULL, NULL, 1845 _IDMAP_T_UNDEF, name, dname, sid, rid, sid_type, NULL, rc); 1846 1847 free(filter); 1848 1849 return (retcode); 1850 } 1851