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