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