1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  24  */
  25 
  26 /*
  27  * Processes name2sid & sid2name batched lookups for a given user or
  28  * computer from an AD Directory server using GSSAPI authentication
  29  */
  30 
  31 #include <stdio.h>
  32 #include <stdlib.h>
  33 #include <alloca.h>
  34 #include <string.h>
  35 #include <strings.h>
  36 #include <lber.h>
  37 #include <ldap.h>
  38 #include <sasl/sasl.h>
  39 #include <string.h>
  40 #include <ctype.h>
  41 #include <pthread.h>
  42 #include <synch.h>
  43 #include <atomic.h>
  44 #include <errno.h>
  45 #include <assert.h>
  46 #include <limits.h>
  47 #include <time.h>
  48 #include <sys/u8_textprep.h>
  49 #include "libadutils.h"
  50 #include "nldaputils.h"
  51 #include "idmapd.h"
  52 
  53 /* Attribute names and filter format strings */
  54 #define SAN             "sAMAccountName"
  55 #define OBJSID          "objectSid"
  56 #define OBJCLASS        "objectClass"
  57 #define UIDNUMBER       "uidNumber"
  58 #define GIDNUMBER       "gidNumber"
  59 #define UIDNUMBERFILTER "(&(objectclass=user)(uidNumber=%u))"
  60 #define GIDNUMBERFILTER "(&(objectclass=group)(gidNumber=%u))"
  61 #define SANFILTER       "(sAMAccountName=%s)"
  62 #define OBJSIDFILTER    "(objectSid=%s)"
  63 
  64 void    idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc,
  65                 int qid, void *argp);
  66 
  67 /*
  68  * A place to put the results of a batched (async) query
  69  *
  70  * There is one of these for every query added to a batch object
  71  * (idmap_query_state, see below).
  72  */
  73 typedef struct idmap_q {
  74         /*
  75          * data used for validating search result entries for name->SID
  76          * lookups
  77          */
  78         char                    *ecanonname;    /* expected canon name */
  79         char                    *edomain;       /* expected domain name */
  80         idmap_id_type           esidtype;       /* expected SID type */
  81         /* results */
  82         char                    **canonname;    /* actual canon name */
  83         char                    **domain;       /* name of domain of object */
  84         char                    **sid;          /* stringified SID */
  85         rid_t                   *rid;           /* RID */
  86         idmap_id_type           *sid_type;      /* user or group SID? */
  87         char                    **unixname;     /* unixname for name mapping */
  88         char                    **dn;           /* DN of entry */
  89         char                    **attr;         /* Attr for name mapping */
  90         char                    **value;        /* value for name mapping */
  91         posix_id_t              *pid;           /* Posix ID found via IDMU */
  92         idmap_retcode           *rc;
  93         adutils_rc              ad_rc;
  94         adutils_result_t        *result;
  95 
  96         /*
  97          * The LDAP search entry result is placed here to be processed
  98          * when the search done result is received.
  99          */
 100         LDAPMessage             *search_res;    /* The LDAP search result */
 101 } idmap_q_t;
 102 
 103 /* Batch context structure; typedef is in header file */
 104 struct idmap_query_state {
 105         adutils_query_state_t   *qs;
 106         int                     qsize;          /* Queue size */
 107         uint32_t                qcount;         /* Number of queued requests */
 108         const char              *ad_unixuser_attr;
 109         const char              *ad_unixgroup_attr;
 110         int                     directory_based_mapping;        /* enum */
 111         char                    *default_domain;
 112         idmap_q_t               queries[1];     /* array of query results */
 113 };
 114 
 115 static pthread_t        reaperid = 0;
 116 
 117 /*
 118  * Keep connection management simple for now, extend or replace later
 119  * with updated libsldap code.
 120  */
 121 #define ADREAPERSLEEP   60
 122 
 123 /*
 124  * Idle connection reaping side of connection management
 125  *
 126  * Every minute wake up and look for connections that have been idle for
 127  * five minutes or more and close them.
 128  */
 129 /*ARGSUSED*/
 130 static
 131 void
 132 adreaper(void *arg)
 133 {
 134         timespec_t      ts;
 135 
 136         ts.tv_sec = ADREAPERSLEEP;
 137         ts.tv_nsec = 0;
 138 
 139         for (;;) {
 140                 /*
 141                  * nanosleep(3RT) is thead-safe (no SIGALRM) and more
 142                  * portable than usleep(3C)
 143                  */
 144                 (void) nanosleep(&ts, NULL);
 145                 adutils_reap_idle_connections();
 146         }
 147 }
 148 
 149 /*
 150  * Take ad_host_config_t information, create a ad_host_t,
 151  * populate it and add it to the list of hosts.
 152  */
 153 
 154 int
 155 idmap_add_ds(adutils_ad_t *ad, const char *host, int port)
 156 {
 157         int     ret = -1;
 158 
 159         if (adutils_add_ds(ad, host, port) == ADUTILS_SUCCESS)
 160                 ret = 0;
 161 
 162         /* Start reaper if it doesn't exist */
 163         if (ret == 0 && reaperid == 0)
 164                 (void) pthread_create(&reaperid, NULL,
 165                     (void *(*)(void *))adreaper, (void *)NULL);
 166         return (ret);
 167 }
 168 
 169 static
 170 idmap_retcode
 171 map_adrc2idmaprc(adutils_rc adrc)
 172 {
 173         switch (adrc) {
 174         case ADUTILS_SUCCESS:
 175                 return (IDMAP_SUCCESS);
 176         case ADUTILS_ERR_NOTFOUND:
 177                 return (IDMAP_ERR_NOTFOUND);
 178         case ADUTILS_ERR_MEMORY:
 179                 return (IDMAP_ERR_MEMORY);
 180         case ADUTILS_ERR_DOMAIN:
 181                 return (IDMAP_ERR_DOMAIN);
 182         case ADUTILS_ERR_OTHER:
 183                 return (IDMAP_ERR_OTHER);
 184         case ADUTILS_ERR_RETRIABLE_NET_ERR:
 185                 return (IDMAP_ERR_RETRIABLE_NET_ERR);
 186         default:
 187                 return (IDMAP_ERR_INTERNAL);
 188         }
 189         /* NOTREACHED */
 190 }
 191 
 192 idmap_retcode
 193 idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries,
 194         int directory_based_mapping, const char *default_domain,
 195         idmap_query_state_t **state)
 196 {
 197         idmap_query_state_t     *new_state;
 198         adutils_rc              rc;
 199 
 200         *state = NULL;
 201 
 202         assert(ad != NULL);
 203 
 204         new_state = calloc(1, sizeof (idmap_query_state_t) +
 205             (nqueries - 1) * sizeof (idmap_q_t));
 206         if (new_state == NULL)
 207                 return (IDMAP_ERR_MEMORY);
 208 
 209         if ((rc = adutils_lookup_batch_start(ad, nqueries,
 210             idmap_ldap_res_search_cb, new_state, &new_state->qs))
 211             != ADUTILS_SUCCESS) {
 212                 idmap_lookup_release_batch(&new_state);
 213                 return (map_adrc2idmaprc(rc));
 214         }
 215 
 216         new_state->default_domain = strdup(default_domain);
 217         if (new_state->default_domain == NULL) {
 218                 idmap_lookup_release_batch(&new_state);
 219                 return (IDMAP_ERR_MEMORY);
 220         }
 221 
 222         new_state->directory_based_mapping = directory_based_mapping;
 223         new_state->qsize = nqueries;
 224         *state = new_state;
 225         return (IDMAP_SUCCESS);
 226 }
 227 
 228 /*
 229  * Set unixuser_attr and unixgroup_attr for AD-based name mapping
 230  */
 231 void
 232 idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
 233                 const char *unixuser_attr, const char *unixgroup_attr)
 234 {
 235         state->ad_unixuser_attr = unixuser_attr;
 236         state->ad_unixgroup_attr = unixgroup_attr;
 237 }
 238 
 239 /*
 240  * Take parsed attribute values from a search result entry and check if
 241  * it is the result that was desired and, if so, set the result fields
 242  * of the given idmap_q_t.
 243  *
 244  * Except for dn and attr, all strings are consumed, either by transferring
 245  * them over into the request results (where the caller will eventually free
 246  * them) or by freeing them here.  Note that this aligns with the "const"
 247  * declarations below.
 248  */
 249 static
 250 void
 251 idmap_setqresults(
 252     idmap_q_t *q,
 253     char *san,
 254     const char *dn,
 255     const char *attr,
 256     char *value,
 257     char *sid,
 258     rid_t rid,
 259     int sid_type,
 260     char *unixname,
 261     posix_id_t pid)
 262 {
 263         char *domain;
 264         int err1;
 265 
 266         assert(dn != NULL);
 267 
 268         if ((domain = adutils_dn2dns(dn)) == NULL)
 269                 goto out;
 270 
 271         if (q->ecanonname != NULL && san != NULL) {
 272                 /* Check that this is the canonname that we were looking for */
 273                 if (u8_strcmp(q->ecanonname, san, 0,
 274                     U8_STRCMP_CI_LOWER, /* no normalization, for now */
 275                     U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
 276                         goto out;
 277         }
 278 
 279         if (q->edomain != NULL) {
 280                 /* Check that this is the domain that we were looking for */
 281                 if (!domain_eq(q->edomain, domain))
 282                         goto out;
 283         }
 284 
 285         /* Copy the DN and attr and value */
 286         if (q->dn != NULL)
 287                 *q->dn = strdup(dn);
 288 
 289         if (q->attr != NULL && attr != NULL)
 290                 *q->attr = strdup(attr);
 291 
 292         if (q->value != NULL && value != NULL) {
 293                 *q->value = value;
 294                 value = NULL;
 295         }
 296 
 297         /* Set results */
 298         if (q->sid) {
 299                 *q->sid = sid;
 300                 sid = NULL;
 301         }
 302         if (q->rid)
 303                 *q->rid = rid;
 304         if (q->sid_type)
 305                 *q->sid_type = sid_type;
 306         if (q->unixname) {
 307                 *q->unixname = unixname;
 308                 unixname = NULL;
 309         }
 310         if (q->domain != NULL) {
 311                 *q->domain = domain;
 312                 domain = NULL;
 313         }
 314         if (q->canonname != NULL) {
 315                 /*
 316                  * The caller may be replacing the given winname by its
 317                  * canonical name and therefore free any old name before
 318                  * overwriting the field by the canonical name.
 319                  */
 320                 free(*q->canonname);
 321                 *q->canonname = san;
 322                 san = NULL;
 323         }
 324 
 325         if (q->pid != NULL && pid != IDMAP_SENTINEL_PID) {
 326                 *q->pid = pid;
 327         }
 328 
 329         q->ad_rc = ADUTILS_SUCCESS;
 330 
 331 out:
 332         /* Free unused attribute values */
 333         free(san);
 334         free(sid);
 335         free(domain);
 336         free(unixname);
 337         free(value);
 338 }
 339 
 340 #define BVAL_CASEEQ(bv, str) \
 341                 (((*(bv))->bv_len == (sizeof (str) - 1)) && \
 342                     strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
 343 
 344 /*
 345  * Extract the class of the result entry.  Returns 1 on success, 0 on
 346  * failure.
 347  */
 348 static
 349 int
 350 idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
 351 {
 352         BerValue        **cbval;
 353 
 354         *sid_type = IDMAP_SID;
 355         if (bvalues == NULL)
 356                 return (0);
 357 
 358         /*
 359          * We consider Computer to be a subclass of User, so we can just
 360          * ignore Computer entries and pay attention to the accompanying
 361          * User entries.
 362          */
 363         for (cbval = bvalues; *cbval != NULL; cbval++) {
 364                 if (BVAL_CASEEQ(cbval, "group")) {
 365                         *sid_type = IDMAP_GSID;
 366                         break;
 367                 } else if (BVAL_CASEEQ(cbval, "user")) {
 368                         *sid_type = IDMAP_USID;
 369                         break;
 370                 }
 371                 /*
 372                  * "else if (*sid_type = IDMAP_USID)" then this is a
 373                  * new sub-class of user -- what to do with it??
 374                  */
 375         }
 376 
 377         return (1);
 378 }
 379 
 380 /*
 381  * Handle a given search result entry
 382  */
 383 static
 384 void
 385 idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q,
 386         LDAPMessage *res, LDAP *ld)
 387 {
 388         BerValue                **bvalues;
 389         const char              *attr = NULL;
 390         char                    *value = NULL;
 391         char                    *unix_name = NULL;
 392         char                    *dn;
 393         char                    *san = NULL;
 394         char                    *sid = NULL;
 395         rid_t                   rid = 0;
 396         int                     sid_type;
 397         int                     ok;
 398         posix_id_t              pid = IDMAP_SENTINEL_PID;
 399 
 400         assert(q->rc != NULL);
 401         assert(q->domain == NULL || *q->domain == NULL);
 402 
 403         if ((dn = ldap_get_dn(ld, res)) == NULL)
 404                 return;
 405 
 406         bvalues = ldap_get_values_len(ld, res, OBJCLASS);
 407         if (bvalues == NULL) {
 408                 /*
 409                  * Didn't find objectclass. Something's wrong with our
 410                  * AD data.
 411                  */
 412                 idmapdlog(LOG_ERR, "%s has no %s", dn, OBJCLASS);
 413                 goto out;
 414         }
 415         ok = idmap_bv_objclass2sidtype(bvalues, &sid_type);
 416         ldap_value_free_len(bvalues);
 417         if (!ok) {
 418                 /*
 419                  * Didn't understand objectclass. Something's wrong with our
 420                  * AD data.
 421                  */
 422                 idmapdlog(LOG_ERR, "%s has unexpected %s", dn, OBJCLASS);
 423                 goto out;
 424         }
 425 
 426         if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU &&
 427             q->pid != NULL) {
 428                 if (sid_type == IDMAP_USID)
 429                         attr = UIDNUMBER;
 430                 else if (sid_type == IDMAP_GSID)
 431                         attr = GIDNUMBER;
 432                 if (attr != NULL) {
 433                         bvalues = ldap_get_values_len(ld, res, attr);
 434                         if (bvalues != NULL) {
 435                                 value = adutils_bv_str(bvalues[0]);
 436                                 if (!adutils_bv_uint(bvalues[0], &pid)) {
 437                                         idmapdlog(LOG_ERR,
 438                                             "%s has Invalid %s value \"%s\"",
 439                                             dn, attr, value);
 440                                 }
 441                                 ldap_value_free_len(bvalues);
 442                         }
 443                 }
 444         }
 445 
 446         if (state->directory_based_mapping == DIRECTORY_MAPPING_NAME &&
 447             q->unixname != NULL) {
 448                 /*
 449                  * If the caller has requested unixname then determine the
 450                  * AD attribute name that will have the unixname, and retrieve
 451                  * its value.
 452                  */
 453                 idmap_id_type esidtype;
 454                 /*
 455                  * Determine the target type.
 456                  *
 457                  * If the caller specified one, use that.  Otherwise, give the
 458                  * same type that as we found for the Windows user.
 459                  */
 460                 esidtype = q->esidtype;
 461                 if (esidtype == IDMAP_SID)
 462                         esidtype = sid_type;
 463 
 464                 if (esidtype == IDMAP_USID)
 465                         attr = state->ad_unixuser_attr;
 466                 else if (esidtype == IDMAP_GSID)
 467                         attr = state->ad_unixgroup_attr;
 468 
 469                 if (attr != NULL) {
 470                         bvalues = ldap_get_values_len(ld, res, attr);
 471                         if (bvalues != NULL) {
 472                                 unix_name = adutils_bv_str(bvalues[0]);
 473                                 ldap_value_free_len(bvalues);
 474                                 value = strdup(unix_name);
 475                         }
 476                 }
 477         }
 478 
 479         bvalues = ldap_get_values_len(ld, res, SAN);
 480         if (bvalues != NULL) {
 481                 san = adutils_bv_str(bvalues[0]);
 482                 ldap_value_free_len(bvalues);
 483         }
 484 
 485         if (q->sid != NULL) {
 486                 bvalues = ldap_get_values_len(ld, res, OBJSID);
 487                 if (bvalues != NULL) {
 488                         sid = adutils_bv_objsid2sidstr(bvalues[0], &rid);
 489                         ldap_value_free_len(bvalues);
 490                 }
 491         }
 492 
 493         idmap_setqresults(q, san, dn,
 494             attr, value,
 495             sid, rid, sid_type,
 496             unix_name, pid);
 497 
 498 out:
 499         ldap_memfree(dn);
 500 }
 501 
 502 void
 503 idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid,
 504                 void *argp)
 505 {
 506         idmap_query_state_t     *state = (idmap_query_state_t *)argp;
 507         idmap_q_t               *q = &(state->queries[qid]);
 508 
 509         switch (rc) {
 510         case LDAP_RES_SEARCH_RESULT:
 511                 if (q->search_res != NULL) {
 512                         idmap_extract_object(state, q, q->search_res, ld);
 513                         (void) ldap_msgfree(q->search_res);
 514                         q->search_res = NULL;
 515                 } else
 516                         q->ad_rc = ADUTILS_ERR_NOTFOUND;
 517                 break;
 518         case LDAP_RES_SEARCH_ENTRY:
 519                 if (q->search_res == NULL) {
 520                         q->search_res = *res;
 521                         *res = NULL;
 522                 }
 523                 break;
 524         default:
 525                 break;
 526         }
 527 }
 528 
 529 static
 530 void
 531 idmap_cleanup_batch(idmap_query_state_t *batch)
 532 {
 533         int i;
 534 
 535         for (i = 0; i < batch->qcount; i++) {
 536                 if (batch->queries[i].ecanonname != NULL)
 537                         free(batch->queries[i].ecanonname);
 538                 batch->queries[i].ecanonname = NULL;
 539                 if (batch->queries[i].edomain != NULL)
 540                         free(batch->queries[i].edomain);
 541                 batch->queries[i].edomain = NULL;
 542         }
 543 }
 544 
 545 /*
 546  * This routine frees the idmap_query_state_t structure
 547  */
 548 void
 549 idmap_lookup_release_batch(idmap_query_state_t **state)
 550 {
 551         if (state == NULL || *state == NULL)
 552                 return;
 553         adutils_lookup_batch_release(&(*state)->qs);
 554         idmap_cleanup_batch(*state);
 555         free((*state)->default_domain);
 556         free(*state);
 557         *state = NULL;
 558 }
 559 
 560 idmap_retcode
 561 idmap_lookup_batch_end(idmap_query_state_t **state)
 562 {
 563         adutils_rc              ad_rc;
 564         int                     i;
 565         idmap_query_state_t     *id_qs = *state;
 566 
 567         ad_rc = adutils_lookup_batch_end(&id_qs->qs);
 568 
 569         /*
 570          * Map adutils rc to idmap_retcode in each
 571          * query because consumers in dbutils.c
 572          * expects idmap_retcode.
 573          */
 574         for (i = 0; i < id_qs->qcount; i++) {
 575                 *id_qs->queries[i].rc =
 576                     map_adrc2idmaprc(id_qs->queries[i].ad_rc);
 577         }
 578         idmap_lookup_release_batch(state);
 579         return (map_adrc2idmaprc(ad_rc));
 580 }
 581 
 582 /*
 583  * Send one prepared search, queue up msgid, process what results are
 584  * available
 585  */
 586 static
 587 idmap_retcode
 588 idmap_batch_add1(idmap_query_state_t *state, const char *filter,
 589         char *ecanonname, char *edomain, idmap_id_type esidtype,
 590         char **dn, char **attr, char **value,
 591         char **canonname, char **dname,
 592         char **sid, rid_t *rid, idmap_id_type *sid_type, char **unixname,
 593         posix_id_t *pid,
 594         idmap_retcode *rc)
 595 {
 596         adutils_rc      ad_rc;
 597         int             qid, i;
 598         idmap_q_t       *q;
 599         char    *attrs[20];     /* Plenty */
 600 
 601         qid = atomic_inc_32_nv(&state->qcount) - 1;
 602         q = &(state->queries[qid]);
 603 
 604         assert(qid < state->qsize);
 605 
 606         /*
 607          * Remember the expected canonname, domainname and unix type
 608          * so we can check the results * against it
 609          */
 610         q->ecanonname = ecanonname;
 611         q->edomain = edomain;
 612         q->esidtype = esidtype;
 613 
 614         /* Remember where to put the results */
 615         q->canonname = canonname;
 616         q->sid = sid;
 617         q->domain = dname;
 618         q->rid = rid;
 619         q->sid_type = sid_type;
 620         q->rc = rc;
 621         q->unixname = unixname;
 622         q->dn = dn;
 623         q->attr = attr;
 624         q->value = value;
 625         q->pid = pid;
 626 
 627         /* Add attributes that are not always needed */
 628         i = 0;
 629         attrs[i++] = SAN;
 630         attrs[i++] = OBJSID;
 631         attrs[i++] = OBJCLASS;
 632 
 633         if (unixname != NULL) {
 634                 /* Add unixuser/unixgroup attribute names to the attrs list */
 635                 if (esidtype != IDMAP_GSID &&
 636                     state->ad_unixuser_attr != NULL)
 637                         attrs[i++] = (char *)state->ad_unixuser_attr;
 638                 if (esidtype != IDMAP_USID &&
 639                     state->ad_unixgroup_attr != NULL)
 640                         attrs[i++] = (char *)state->ad_unixgroup_attr;
 641         }
 642 
 643         if (pid != NULL) {
 644                 if (esidtype != IDMAP_GSID)
 645                         attrs[i++] = UIDNUMBER;
 646                 if (esidtype != IDMAP_USID)
 647                         attrs[i++] = GIDNUMBER;
 648         }
 649 
 650         attrs[i] = NULL;
 651 
 652         /*
 653          * Provide sane defaults for the results in case we never hear
 654          * back from the DS before closing the connection.
 655          *
 656          * In particular we default the result to indicate a retriable
 657          * error.  The first complete matching result entry will cause
 658          * this to be set to IDMAP_SUCCESS, and the end of the results
 659          * for this search will cause this to indicate "not found" if no
 660          * result entries arrived or no complete ones matched the lookup
 661          * we were doing.
 662          */
 663         *rc = IDMAP_ERR_RETRIABLE_NET_ERR;
 664         if (sid_type != NULL)
 665                 *sid_type = IDMAP_SID;
 666         if (sid != NULL)
 667                 *sid = NULL;
 668         if (dname != NULL)
 669                 *dname = NULL;
 670         if (rid != NULL)
 671                 *rid = 0;
 672         if (dn != NULL)
 673                 *dn = NULL;
 674         if (attr != NULL)
 675                 *attr = NULL;
 676         if (value != NULL)
 677                 *value = NULL;
 678 
 679         /*
 680          * Don't set *canonname to NULL because it may be pointing to the
 681          * given winname. Later on if we get a canonical name from AD the
 682          * old name if any will be freed before assigning the new name.
 683          */
 684 
 685         /*
 686          * Invoke the mother of all APIs i.e. the adutils API
 687          */
 688         ad_rc = adutils_lookup_batch_add(state->qs, filter,
 689             (const char **)attrs,
 690             edomain, &q->result, &q->ad_rc);
 691         return (map_adrc2idmaprc(ad_rc));
 692 }
 693 
 694 idmap_retcode
 695 idmap_name2sid_batch_add1(idmap_query_state_t *state,
 696         const char *name, const char *dname, idmap_id_type esidtype,
 697         char **dn, char **attr, char **value,
 698         char **canonname, char **sid, rid_t *rid,
 699         idmap_id_type *sid_type, char **unixname,
 700         posix_id_t *pid, idmap_retcode *rc)
 701 {
 702         idmap_retcode   retcode;
 703         char            *filter, *s_name;
 704         char            *ecanonname, *edomain; /* expected canonname */
 705 
 706         /*
 707          * Strategy: search the global catalog for user/group by
 708          * sAMAccountName = user/groupname with "" as the base DN and by
 709          * userPrincipalName = user/groupname@domain.  The result
 710          * entries will be checked to conform to the name and domain
 711          * name given here.  The DN, sAMAccountName, userPrincipalName,
 712          * objectSid and objectClass of the result entries are all we
 713          * need to figure out which entries match the lookup, the SID of
 714          * the user/group and whether it is a user or a group.
 715          */
 716 
 717         if ((ecanonname = strdup(name)) == NULL)
 718                 return (IDMAP_ERR_MEMORY);
 719 
 720         if (dname == NULL || *dname == '\0') {
 721                 /* 'name' not qualified and dname not given */
 722                 dname = state->default_domain;
 723                 edomain = strdup(dname);
 724                 if (edomain == NULL) {
 725                         free(ecanonname);
 726                         return (IDMAP_ERR_MEMORY);
 727                 }
 728         } else {
 729                 if ((edomain = strdup(dname)) == NULL) {
 730                         free(ecanonname);
 731                         return (IDMAP_ERR_MEMORY);
 732                 }
 733         }
 734 
 735         if (!adutils_lookup_check_domain(state->qs, dname)) {
 736                 free(ecanonname);
 737                 free(edomain);
 738                 return (IDMAP_ERR_DOMAIN_NOTFOUND);
 739         }
 740 
 741         s_name = sanitize_for_ldap_filter(name);
 742         if (s_name == NULL) {
 743                 free(ecanonname);
 744                 free(edomain);
 745                 return (IDMAP_ERR_MEMORY);
 746         }
 747 
 748         /* Assemble filter */
 749         (void) asprintf(&filter, SANFILTER, s_name);
 750         if (s_name != name)
 751                 free(s_name);
 752         if (filter == NULL) {
 753                 free(ecanonname);
 754                 free(edomain);
 755                 return (IDMAP_ERR_MEMORY);
 756         }
 757 
 758         retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
 759             esidtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
 760             unixname, pid, rc);
 761 
 762         free(filter);
 763 
 764         return (retcode);
 765 }
 766 
 767 idmap_retcode
 768 idmap_sid2name_batch_add1(idmap_query_state_t *state,
 769         const char *sid, const rid_t *rid, idmap_id_type esidtype,
 770         char **dn, char **attr, char **value,
 771         char **name, char **dname, idmap_id_type *sid_type,
 772         char **unixname, posix_id_t *pid, idmap_retcode *rc)
 773 {
 774         idmap_retcode   retcode;
 775         int             ret;
 776         char            *filter;
 777         char            cbinsid[ADUTILS_MAXHEXBINSID + 1];
 778 
 779         /*
 780          * Strategy: search [the global catalog] for user/group by
 781          * objectSid = SID with empty base DN.  The DN, sAMAccountName
 782          * and objectClass of the result are all we need to figure out
 783          * the name of the SID and whether it is a user, a group or a
 784          * computer.
 785          */
 786 
 787         if (!adutils_lookup_check_sid_prefix(state->qs, sid))
 788                 return (IDMAP_ERR_DOMAIN_NOTFOUND);
 789 
 790         ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
 791         if (ret != 0)
 792                 return (IDMAP_ERR_SID);
 793 
 794         /* Assemble filter */
 795         (void) asprintf(&filter, OBJSIDFILTER, cbinsid);
 796         if (filter == NULL)
 797                 return (IDMAP_ERR_MEMORY);
 798 
 799         retcode = idmap_batch_add1(state, filter, NULL, NULL, esidtype,
 800             dn, attr, value, name, dname, NULL, NULL, sid_type, unixname,
 801             pid, rc);
 802 
 803         free(filter);
 804 
 805         return (retcode);
 806 }
 807 
 808 idmap_retcode
 809 idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
 810         const char *unixname, int is_user, int is_wuser,
 811         char **dn, char **attr, char **value,
 812         char **sid, rid_t *rid, char **name,
 813         char **dname, idmap_id_type *sid_type, idmap_retcode *rc)
 814 {
 815         idmap_retcode   retcode;
 816         char            *filter, *s_unixname;
 817         const char      *attrname;
 818 
 819         /* Get unixuser or unixgroup AD attribute name */
 820         attrname = (is_user) ?
 821             state->ad_unixuser_attr : state->ad_unixgroup_attr;
 822         if (attrname == NULL)
 823                 return (IDMAP_ERR_NOTFOUND);
 824 
 825         s_unixname = sanitize_for_ldap_filter(unixname);
 826         if (s_unixname == NULL)
 827                 return (IDMAP_ERR_MEMORY);
 828 
 829         /*  Assemble filter */
 830         (void) asprintf(&filter, "(&(objectclass=%s)(%s=%s))",
 831             is_wuser ? "user" : "group", attrname, s_unixname);
 832         if (s_unixname != unixname)
 833                 free(s_unixname);
 834         if (filter == NULL) {
 835                 return (IDMAP_ERR_MEMORY);
 836         }
 837 
 838         retcode = idmap_batch_add1(state, filter, NULL, NULL,
 839             IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type,
 840             NULL, NULL, rc);
 841 
 842         if (retcode == IDMAP_SUCCESS && attr != NULL) {
 843                 if ((*attr = strdup(attrname)) == NULL)
 844                         retcode = IDMAP_ERR_MEMORY;
 845         }
 846 
 847         if (retcode == IDMAP_SUCCESS && value != NULL) {
 848                 if ((*value = strdup(unixname)) == NULL)
 849                         retcode = IDMAP_ERR_MEMORY;
 850         }
 851 
 852         free(filter);
 853 
 854         return (retcode);
 855 }
 856 
 857 idmap_retcode
 858 idmap_pid2sid_batch_add1(idmap_query_state_t *state,
 859         posix_id_t pid, int is_user,
 860         char **dn, char **attr, char **value,
 861         char **sid, rid_t *rid, char **name,
 862         char **dname, idmap_id_type *sid_type, idmap_retcode *rc)
 863 {
 864         idmap_retcode   retcode;
 865         char            *filter;
 866         const char      *attrname;
 867 
 868         /*  Assemble filter */
 869         if (is_user) {
 870                 (void) asprintf(&filter, UIDNUMBERFILTER, pid);
 871                 attrname = UIDNUMBER;
 872         } else {
 873                 (void) asprintf(&filter, GIDNUMBERFILTER, pid);
 874                 attrname = GIDNUMBER;
 875         }
 876         if (filter == NULL)
 877                 return (IDMAP_ERR_MEMORY);
 878 
 879         retcode = idmap_batch_add1(state, filter, NULL, NULL,
 880             IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type,
 881             NULL, NULL, rc);
 882 
 883         if (retcode == IDMAP_SUCCESS && attr != NULL) {
 884                 if ((*attr = strdup(attrname)) == NULL)
 885                         retcode = IDMAP_ERR_MEMORY;
 886         }
 887 
 888         if (retcode == IDMAP_SUCCESS && value != NULL) {
 889                 (void) asprintf(value, "%u", pid);
 890                 if (*value == NULL)
 891                         retcode = IDMAP_ERR_MEMORY;
 892         }
 893 
 894         free(filter);
 895 
 896         return (retcode);
 897 }