adutils.c revision cd37da7426f0c49c14ad9a8a07638ca971477566
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Processes name2sid & sid2name batched lookups for a given user or
* computer from an AD Directory server using GSSAPI authentication
*/
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <string.h>
#include <strings.h>
#include <lber.h>
#include <ldap.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <synch.h>
#include <atomic.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include <sys/u8_textprep.h>
#include "idmapd.h"
/*
* Internal data structures for this code
*/
/* Attribute names and filter format strings */
#define SAN "sAMAccountName"
#define OBJSID "objectSid"
#define OBJCLASS "objectClass"
#define SANFILTER "(sAMAccountName=%.*s)"
#define OBJSIDFILTER "(objectSid=%s)"
/*
* private version of sid_t, and so must other components of ON until we
* rationalize this.
*/
typedef struct sid {
} sid_t;
/* A single DS */
typedef struct ad_host {
int dead; /* error on LDAP connection */
/*
* Used to distinguish between different instances of LDAP
* connections to this same DS. We need this so we never mix up
* results for a given msgID from one connection with those of
* another earlier connection where two batch state structures
* share this ad_host object but used different LDAP connections
* to send their LDAP searches.
*/
/* LDAP DS info */
char *host;
int port;
/* hardwired to SASL GSSAPI only for now */
char *saslmech;
unsigned saslflags;
} ad_host_t;
/* A set of DSs for a given AD partition; ad_t typedef comes from adutil.h */
struct ad {
char *dflt_w2k_dom; /* used to qualify bare names */
};
/*
* A place to put the results of a batched (async) query
*
* There is one of these for every query added to a batch object
* (idmap_query_state, see below).
*/
typedef struct idmap_q {
/*
* data used for validating search result entries for name->SID
* loopups
*/
char *ecanonname; /* expected canon name */
char *edomain; /* expected domain name */
/* results */
char **canonname; /* actual canon name */
char **result; /* name or stringified SID */
char **domain; /* name of domain of object */
int *sid_type; /* if not NULL */
/* lookup state */
int msgid; /* LDAP message ID */
} idmap_q_t;
/* Batch context structure; typedef is in header file */
struct idmap_query_state {
int qcount; /* how many queries */
int ref_cnt; /* reference count */
};
/*
* List of query state structs -- needed so we can "route" LDAP results
* to the right context if multiple threads should be using the same
* connection concurrently
*/
/*
* List of DSs, needed by the idle connection reaper thread
*/
static void
static void
/*ARGSUSED*/
static int
return (LDAP_PARAM_ERROR);
interact++) {
}
return (LDAP_SUCCESS);
}
/*
* Turn "dc=foo,dc=bar,dc=com" into "foo.bar.com"; ignores any other
* attributes (CN, etc...). We don't need the reverse, for now.
*/
static
char *
{
int maxlabels = 5;
int nlabels = 0;
int dnslen;
/*
* There is no reverse of ldap_dns_to_dn() in our libldap, so we
* have to do the hard work here for now.
*/
/*
* This code is much too liberal: it looks for "dc" attributes
* in all RDNs of the DN. In theory this could cause problems
* if people were to use "dc" in nodes other than the root of
* the tree, but in practice noone, least of all Active
* Directory, does that.
*
* On the other hand, this code is much too conservative: it
* does not make assumptions about ldap_explode_dn(), and _that_
* is the true for looking at every attr of every RDN.
*
* Since we only ever look at dc and those must be DNS labels,
* at least until we get around to supporting IDN here we
* shouldn't see escaped labels from AD nor from libldap, though
* the spec (RFC2253) does allow libldap to escape things that
* don't need escaping -- if that should ever happen then
* libldap will need a spanking, and we can take care of that.
*/
/* Explode a DN into RDNs */
return (NULL);
/* Explode each RDN, look for DC attr, save val as DNS label */
goto done;
continue;
/* Found a DNS label */
char **tmp;
sizeof (char *) * (maxlabels + 1));
goto done;
}
/* There should be just one DC= attr per-RDN */
break;
}
}
/*
* Got all the labels, now join with '.'
*
* We need room for nlabels - 1 periods ('.'), one nul
* terminator, and the strlen() of each label.
*/
goto done;
*dns = '\0';
/*
* NOTE: the last '.' won't be appended -- there's no room
* for it!
*/
}
done:
}
return (dns);
}
/*
* Keep connection management simple for now, extend or replace later
* with updated libsldap code.
*/
#define ADREAPERSLEEP 60
#define ADCONN_TIME 300
/*
* Idle connection reaping side of connection management
*
* Every minute wake up and look for connections that have been idle for
* five minutes or more and close them.
*/
/*ARGSUSED*/
static
void
{
for (;;) {
/*
* nanosleep(3RT) is thead-safe (no SIGALRM) and more
* portable than usleep(3C)
*/
(void) pthread_mutex_lock(&adhostlock);
}
}
}
(void) pthread_mutex_unlock(&adhostlock);
}
}
int
{
return (-1);
return (-1);
/*
* If default_domain is NULL, deal, deferring errors until
* idmap_lookup_batch_start() -- this makes it easier on the
* caller, who can simply observe lookups failing as opposed to
* having to conditionalize calls to lookups according to
* whether it has a non-NULL ad_t *.
*/
if (default_domain == NULL)
default_domain = "";
goto err;
goto err;
return (0);
err:
return (-1);
}
void
{
ad_host_t *p;
return;
return;
}
(void) pthread_mutex_lock(&adhostlock);
p = host_head;
while (p != NULL) {
prev = p;
p = p->next;
continue;
} else {
p = host_head;
else
}
}
(void) pthread_mutex_unlock(&adhostlock);
}
static
int
{
int zero = 0;
return (0);
/* done! */
goto out;
}
/* Open and bind an LDAP connection */
goto out;
}
NULL);
if (rc != LDAP_SUCCESS) {
}
out:
return (1);
}
return (0);
}
/*
* Connection management: find an open connection or open one
*/
static
{
int seen_last;
int tries = -1;
(void) pthread_mutex_lock(&adhostlock);
goto out;
/* Try as many DSs as we have, once each; count them once */
if (tries < 0) {
tries++;
}
/*
* Find a suitable ad_host_t (one associated with this ad_t,
* preferably one that's already connected and not dead),
* possibly round-robining through the ad_host_t list.
*
* If we can't find a non-dead ad_host_t and we've had one
* before (ad->last_adh != NULL) then pick the next one or, if
* there is no next one, the first one (i.e., round-robin).
*
* If we've never had one then (ad->last_adh == NULL) then pick
* the first one.
*
* If we ever want to be more clever, such as preferring DSes
* with better average response times, adjusting for weights
* from SRV RRs, and so on, then this is the place to do it.
*/
continue;
seen_last++;
else if (seen_last)
/* First time or current adh is live -> done */
break;
}
/* Round-robin */
out:
(void) pthread_mutex_unlock(&adhostlock);
/* Found suitable DS, open it if not already opened */
if (idmap_open_conn(adh))
return (adh);
if (tries-- > 0)
goto retry;
"catalog server!");
return (NULL);
}
static
void
{
}
/*
* Take ad_host_config_t information, create a ad_host_t,
* populate it and add it to the list of hosts.
*/
int
{
ad_host_t *p;
int ret = -1;
if (port == 0)
(void) pthread_mutex_lock(&adhostlock);
continue;
/* already added */
ret = 0;
goto err;
}
}
/* add new entry */
goto err;
goto err;
/* default to SASL GSSAPI only for now */
ret = -1;
goto err;
}
/* link in */
/* Start reaper if it doesn't exist */
if (reaperid == 0)
err:
(void) pthread_mutex_unlock(&adhostlock);
}
}
return (ret);
}
/*
* Free a DS configuration.
* Caller must lock the adhostlock mutex
*/
static void
{
ad_host_t **p, *q;
continue;
/* found */
if ((*p)->ref > 0)
break; /* still in use */
q = *p;
*p = (*p)->next;
(void) pthread_mutex_destroy(&q->lock);
if (q->ld)
(void) ldap_unbind(q->ld);
if (q->host)
free(q);
break;
}
}
/*
* Convert a binary SID in a BerValue to a sid_t
*/
static
int
{
int i, j;
uchar_t *v;
uint32_t a;
/*
* The binary format of a SID is as follows:
*
* byte #0: version, always 0x01
* byte #1: RID count, always <= 0x0f
* bytes #2-#7: SID authority, big-endian 48-bit unsigned int
*
* followed by RID count RIDs, each a little-endian, unsigned
* 32-bit int.
*/
/*
* Sanity checks: must have at least one RID, version must be
* 0x01, and the length must be 8 + rid count * 4
*/
/* big endian -- so start from the left */
(u_longlong_t)v[7];
for (i = 0; i < sidp->sub_authority_count; i++) {
j = 8 + (i * 4);
/* little endian -- so start from the right */
a = (v[j + 3] << 24) | (v[j + 2] << 16) |
(v[j + 1] << 8) | (v[j]);
sidp->sub_authorities[i] = a;
}
return (0);
}
return (-1);
}
/*
* Convert a sid_t to S-1-...
*/
static
char *
{
return (NULL);
/*
* We could optimize like so, but, why?
* if (sidp->authority < 10)
* len += 2;
* else if (sidp->authority < 100)
* len += 3;
* else
* len += snprintf(NULL, 0"%llu", sidp->authority);
*/
/* Max length of a uint32_t printed out in ASCII is 10 bytes */
return (NULL);
for (i = 0; i < sidp->sub_authority_count; i++) {
}
return (str);
}
/*
* Convert a sid_t to on-the-wire encoding
*/
static
int
{
uchar_t *p;
int i;
uint64_t a;
uint32_t r;
return (-1);
p = binsid;
*p++ = 0x01; /* version */
/* sub authority count */
*p++ = sid->sub_authority_count;
/* Authority */
/* big-endian -- start from left */
*p++ = (a >> 40) & 0xFF;
*p++ = (a >> 32) & 0xFF;
*p++ = (a >> 24) & 0xFF;
*p++ = (a >> 16) & 0xFF;
*p++ = (a >> 8) & 0xFF;
*p++ = a & 0xFF;
/* sub-authorities */
for (i = 0; i < sid->sub_authority_count; i++) {
r = sid->sub_authorities[i];
/* little-endian -- start from right */
*p++ = (r & 0x000000FF);
*p++ = (r & 0x0000FF00) >> 8;
*p++ = (r & 0x00FF0000) >> 16;
*p++ = (r & 0xFF000000) >> 24;
}
return (0);
}
/*
* Convert a stringified SID (S-1-...) into a hex-encoded version of the
* on-the-wire encoding, but with each pair of hex digits pre-pended
* with a '\', so we can pass this to libldap.
*/
static
int
char *hexbinsid, int hexbinsidlen)
{
int i, j;
const char *cp;
char *ecp;
u_longlong_t a;
unsigned long r;
/* Only version 1 SIDs please */
return (-1);
return (-1);
/* count '-'s */
/* can't end on a '-' */
return (-1);
}
/* Adjust count for version and authority */
j -= 2;
/* we know the version number and RID count */
/* must have at least one RID, but not too many */
return (-1);
/* check that we only have digits and '-' */
return (-1);
/* 64-bit safe parsing of unsigned 48-bit authority value */
errno = 0;
/* errors parsing the authority or too many bits */
(a & 0x0000ffffffffffffULL) != a)
return (-1);
for (i = 0; i < j; i++) {
if (*cp++ != '-')
return (-1);
/* 64-bit safe parsing of unsigned 32-bit RID */
errno = 0;
/* errors parsing the RID or too many bits */
(r & 0xffffffffUL) != r)
return (-1);
}
/* check that all of the string SID has been consumed */
if (*cp != '\0')
return (-1);
if (hexbinsidlen < (j * 3))
return (-2);
/* binary encode the SID */
/* hex encode, with a backslash before each byte */
b = binsid[i];
*ecp++ = '\\';
hb = b & 0xF;
}
*ecp = '\0';
return (0);
}
static
char *
{
return (NULL);
/*
* save the last RID and truncate the SID
*/
return (idmap_sid2txt(&sid));
}
{
/* Note: ad->dflt_w2k_dom cannot be NULL - see idmap_ad_alloc() */
return (-1);
return (IDMAP_ERR_OTHER);
return (IDMAP_ERR_MEMORY);
/* should be -1, but the atomic routines want unsigned */
(void) pthread_mutex_lock(&qstatelock);
(void) pthread_mutex_unlock(&qstatelock);
return (IDMAP_SUCCESS);
}
/*
* Find the idmap_query_state_t to which a given LDAP result msgid on a
* given connection belongs. This routine increaments the reference count
* so that the object can not be freed. idmap_lookup_unlock_batch()
* must be called to decreament the reference count.
*/
static
int
{
int i;
(void) pthread_mutex_lock(&qstatelock);
continue;
for (i = 0; i < p->qcount; i++) {
p->ref_cnt++;
*state = p;
*qid = i;
(void) pthread_mutex_unlock(&qstatelock);
return (1);
}
}
}
(void) pthread_mutex_unlock(&qstatelock);
return (0);
}
/*
* Take parsed attribute values from a search result entry and check if
* it is the result that was desired and, if so, set the result fields
* of the given idmap_q_t.
*
* Frees the unused char * values, and returns 1 on success, 0 if the
* result entry was not applicable or if ENOMEM.
*/
static
void
{
char *domain;
/*
* Name->SID lookups need a canonname output in addition to the
* SID, whereas SID->name lookups always put the canonical name
* in the result field. So if q->canonname != NULL then this
* must be a name->SID lookup.
*/
goto out;
if (n2s) {
/* Check that this is the entry that we were looking for */
U8_STRCMP_CI_LOWER, /* no normalization, for now */
goto out;
/* Set name->SID results */
/* We need the canonical SAN for case-insensitivity */
/* Don't free these now that q references them */
} else {
/* SID->name search result entry */
/* name and domain in separate slots */
/* Don't free these now that q references them */
} else {
char *s;
int len;
/* name@domain in one slot */
goto out;
}
*q->result = s;
}
}
*q->rc = IDMAP_SUCCESS;
out:
/* Free unused attribute values */
}
/*
* The following three functions extract objectSid, sAMAccountName and
* objectClass attribute values and, in the case of objectSid and
* objectClass, parse them.
*
* idmap_setqresults() takes care of dealing with the result entry's DN.
*/
/*
* Return a NUL-terminated stringified SID from the value of an
* objectSid attribute and put the last RID in *rid.
*/
static
char *
{
char *sid;
return (NULL);
/* objectSid is single valued */
return (NULL);
return (sid);
}
/*
* Return a NUL-terminated string from the value of a sAMAccountName
* attribute.
*/
static
char *
{
char *s;
return (NULL);
return (NULL);
return (s);
}
/*
* Extract the class of the result entry. Returns 1 on success, 0 on
* failure.
*/
static
int
{
return (0);
/*
* We iterate over all the values because computer is a
* sub-class of user.
*/
break;
break;
/* Continue looping -- this may be a computer yet */
}
/*
* "else if (*sid_type = _IDMAP_T_USER)" then this is a
* new sub-class of user -- what to do with it??
*/
}
return (1);
}
/*
* Handle a given search result entry
*/
static
void
{
idmap_q_t *q;
char *attr;
int sid_type;
return;
}
/*
* If this is an attribute we are looking for and
* haven't seen it yet, parse it
*/
&sid_type);
}
/* Got what we need, set results if relevant */
break;
}
}
}
/*
* Try to get a result; if there is one, find the corresponding
* idmap_q_t and process the result.
*/
static
int
{
return (-1);
}
/* Get one result */
return (-1);
switch (rc) {
case LDAP_RES_SEARCH_RESULT:
/* We have all the LDAP replies for some search... */
&query_state, &qid)) {
/* ...so we can decrement qinflight */
/* We've seen all the result entries we'll see */
}
(void) ldap_msgfree(res);
ret = 0;
break;
/*
* We have no need for these at the moment. Eventually,
* when we query things that we can't expect to find in
* the Global Catalog then we'll need to learn to follow
* references.
*/
(void) ldap_msgfree(res);
ret = 0;
break;
case LDAP_RES_SEARCH_ENTRY:
/* Got a result */
&query_state, &qid)) {
/* we saw at least one result */
}
(void) ldap_msgfree(res);
ret = 0;
break;
default:
/* timeout or error; treat the same */
ret = -1;
break;
}
return (ret);
}
/*
* This routine decreament the reference count of the
* idmap_query_state_t
*/
static void
{
/*
* Decrement reference count with qstatelock locked
*/
(void) pthread_mutex_lock(&qstatelock);
/*
* If there are no references wakup the allocating thread
*/
(void) pthread_mutex_unlock(&qstatelock);
}
static
void
{
int i;
}
}
/*
* This routine frees the idmap_query_state_t structure
* If the reference count is greater than 1 it waits
* for the other threads to finish using it.
*/
void
{
idmap_query_state_t **p;
/*
* Decrement reference count with qstatelock locked
* and wait for reference count to get to zero
*/
(void) pthread_mutex_lock(&qstatelock);
}
/* Remove this state struct from the list of state structs */
if (*p == (*state)) {
break;
}
}
(void) pthread_mutex_unlock(&qstatelock);
}
{
int rc = LDAP_SUCCESS;
/* Process results until done or until timeout, if given */
timeout)) != 0)
break;
}
}
return (retcode);
}
/*
* Send one prepared search, queue up msgid, process what results are
* available
*/
static
{
idmap_q_t *q;
static char *attrs[] = {
SAN,
};
/*
* Remember the expected canonname so we can check the results
* agains it
*/
q->ecanonname = ecanonname;
/* Remember where to put the results */
/*
* Provide sane defaults for the results in case we never hear
* back from the DS before closing the connection.
*
* In particular we default the result to indicate a retriable
* error. The first complete matching result entry will cause
* this to be set to IDMAP_SUCCESS, and the end of the results
* for this search will cause this to indicate "not found" if no
* result entries arrived or no complete ones matched the lookup
* we were doing.
*/
*rid = 0;
/* Send this lookup, don't wait for a result here */
lrc == LDAP_UNWILLING_TO_PERFORM) {
} else if (lrc != LDAP_SUCCESS) {
}
}
return (retcode);
/*
* Reap as many requests as we can _without_ waiting
*
* We do this to prevent any possible TCP socket buffer
* starvation deadlocks.
*/
;
return (IDMAP_SUCCESS);
}
{
int len, samAcctNameLen;
/*
* entries will be checked to conform to the name and domain
* name given here. The DN, sAMAccountName, userPrincipalName,
* objectSid and objectClass of the result entries are all we
* need to figure out which entries match the lookup, the SID of
*/
/*
* We need the name and the domain name separately and as
* name@domain. We also allow the domain to be provided
* separately.
*/
return (IDMAP_ERR_MEMORY);
/* 'name' is qualified with a domain name */
return (IDMAP_ERR_MEMORY);
}
} else {
/* 'name' not qualified and dname not given */
return (IDMAP_ERR_DOMAIN);
}
return (IDMAP_ERR_MEMORY);
}
}
} else {
return (IDMAP_ERR_MEMORY);
}
}
/* Assemble filter */
return (IDMAP_ERR_MEMORY);
}
return (retcode);
}
{
/*
* objectSid = SID with empty base DN. The DN, sAMAccountName
* and objectClass of the result are all we need to figure out
* the name of the SID and whether it is a user, a group or a
* computer.
*/
if (ret != 0)
return (IDMAP_ERR_SID);
/* Assemble filter */
return (IDMAP_ERR_MEMORY);
return (retcode);
}