Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Apache/modules/ldap/   (Apache Software Stiftung Version 2.4.65©)  Datei vom 7.0.2025 mit Größe 113 kB image not shown  

Impressum util_ldap.c   Sprache: C

 
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/*
 * util_ldap.c: LDAP things
 *
 * Original code from auth_ldap module for Apache v1.3:
 * Copyright 1998, 1999 Enbridge Pipelines Inc.
 * Copyright 1999-2001 Dave Carrigan
 */


#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_mutex.h"
#include "util_ldap.h"
#include "util_ldap_cache.h"

#include <apr_strings.h>

#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif

#if !APR_HAS_LDAP
#error mod_ldap requires APR-util to have LDAP support built in
#endif

/* Default define for ldap functions that need a SIZELIMIT but
 * do not have the define
 * XXX This should be removed once a supporting #define is
 *  released through APR-Util.
 */

#ifndef APR_LDAP_SIZELIMIT
#define APR_LDAP_SIZELIMIT -1
#endif

#ifdef LDAP_OPT_DEBUG_LEVEL
#define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG_LEVEL
#else
#ifdef LDAP_OPT_DEBUG
#define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG
#endif
#endif

#define AP_LDAP_HOPLIMIT_UNSET -1
#define AP_LDAP_CHASEREFERRALS_SDKDEFAULT -1
#define AP_LDAP_CHASEREFERRALS_OFF 0
#define AP_LDAP_CHASEREFERRALS_ON 1

#define AP_LDAP_CONNPOOL_DEFAULT -1
#define AP_LDAP_CONNPOOL_INFINITE -2

#if !defined(LDAP_OPT_NETWORK_TIMEOUT) && defined(LDAP_OPT_CONNECT_TIMEOUT)
#define LDAP_OPT_NETWORK_TIMEOUT LDAP_OPT_CONNECT_TIMEOUT
#endif

module AP_MODULE_DECLARE_DATA ldap_module;
static const char *ldap_cache_mutex_type = "ldap-cache";
static apr_status_t uldap_connection_unbind(void *param);

/* For OpenLDAP with the 3-arg version of ldap_set_rebind_proc(), use
 * a simpler rebind callback than the implementation in APR-util.
 * Testing for API version >= 3001 appears safe although OpenLDAP
 * 2.1.x (API version = 2004) also has the 3-arg API. */

#if APR_HAS_OPENLDAP_LDAPSDK && defined(LDAP_API_VERSION) && LDAP_API_VERSION >= 3001

#define uldap_rebind_init(p) APR_SUCCESS /* noop */

static int uldap_rebind_proc(LDAP *ld, const char *url, ber_tag_t request,
                             ber_int_t msgid, void *params)
{
    util_ldap_connection_t *ldc = params;

    return ldap_bind_s(ld, ldc->binddn, ldc->bindpw, LDAP_AUTH_SIMPLE);
}

static apr_status_t uldap_rebind_add(util_ldap_connection_t *ldc)
{
    ldap_set_rebind_proc(ldc->ldap, uldap_rebind_proc, ldc);
    return APR_SUCCESS;
}

#else /* !APR_HAS_OPENLDAP_LDAPSDK */

#define USE_APR_LDAP_REBIND
#include <apr_ldap_rebind.h>

#define uldap_rebind_init(p) apr_ldap_rebind_init(p)
#define uldap_rebind_add(ldc) apr_ldap_rebind_add((ldc)->rebind_pool, \
                                                  (ldc)->ldap, (ldc)->binddn, \
                                                  (ldc)->bindpw)
#endif

static APR_INLINE apr_status_t ldap_cache_lock(util_ldap_state_t *st, request_rec *r) { 
    apr_status_t rv = APR_SUCCESS;
    if (st->util_ldap_cache_lock) { 
        apr_status_t rv = apr_global_mutex_lock(st->util_ldap_cache_lock);
        if (rv != APR_SUCCESS) { 
            ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, APLOGNO(10134) "LDAP cache lock failed");
            ap_assert(0);
        }
    }
    return rv; 
}
static APR_INLINE apr_status_t ldap_cache_unlock(util_ldap_state_t *st, request_rec *r) { 
    apr_status_t rv = APR_SUCCESS;
    if (st->util_ldap_cache_lock) { 
        apr_status_t rv = apr_global_mutex_unlock(st->util_ldap_cache_lock);
        if (rv != APR_SUCCESS) { 
            ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, APLOGNO(10135) "LDAP cache lock failed");
            ap_assert(0);
        }
    }
    return rv; 
}

static void util_ldap_strdup (char **str, const char *newstr)
{
    if (*str) {
        free(*str);
        *str = NULL;
    }

    if (newstr) {
        *str = strdup(newstr);
    }
}

/*
 * Status Handler
 * --------------
 *
 * This handler generates a status page about the current performance of
 * the LDAP cache. It is enabled as follows:
 *
 * <Location /ldap-status>
 *   SetHandler ldap-status
 * </Location>
 *
 */

static int util_ldap_handler(request_rec *r)
{
    util_ldap_state_t *st;

    r->allowed |= (1 << M_GET);
    if (r->method_number != M_GET) {
        return DECLINED;
    }

    if (strcmp(r->handler, "ldap-status")) {
        return DECLINED;
    }

    st = (util_ldap_state_t *) ap_get_module_config(r->server->module_config,
            &ldap_module);

    ap_set_content_type_ex(r, "text/html; charset=ISO-8859-1", 1);

    if (r->header_only)
        return OK;

    ap_rputs(DOCTYPE_HTML_3_2
             "LDAP Cache Information\n", r);
    ap_rputs("

LDAP Cache Information"
             "

\n"
, r);

    util_ald_cache_display(r, st);

    return OK;
}



/* ------------------------------------------------------------------ */
/*
 * Closes an LDAP connection by unlocking it. The next time
 * uldap_connection_find() is called this connection will be
 * available for reuse.
 */

static void uldap_connection_close(util_ldap_connection_t *ldc)
{

     /* We leave bound LDAP connections floating around in our pool,
      * but always check/fix the binddn/bindpw when we take them out
      * of the pool
      */

     if (!ldc->keep) {
         uldap_connection_unbind(ldc);
         ldc->r = NULL;
     }
     else {
         /* mark our connection as available for reuse */
         ldc->freed = apr_time_now();
         ldc->r = NULL;
     }

#if APR_HAS_THREADS
     apr_thread_mutex_unlock(ldc->lock);
#endif
}


/*
 * Destroys an LDAP connection by unbinding and closing the connection to
 * the LDAP server. It is used to bring the connection back to a known
 * state after an error.
 */

static apr_status_t uldap_connection_unbind(void *param)
{
    util_ldap_connection_t *ldc = param;

    if (ldc) {
#ifdef USE_APR_LDAP_REBIND
        /* forget the rebind info for this conn */
        if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
            apr_pool_clear(ldc->rebind_pool);
        }
#endif

        if (ldc->ldap) {
            if (ldc->r) { 
                ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, ldc->r, "LDC %pp unbind", ldc); 
            }
            ldap_unbind_s(ldc->ldap);
            ldc->ldap = NULL;
        }
        ldc->bound = 0;
    }

    return APR_SUCCESS;
}

/* not presently used, not part of the API */
#if 0
/*
 * util_ldap_connection_remove frees all storage associated with the LDAP
 * connection and removes it completely from the per-virtualhost list of
 * connections
 *
 * The caller should hold the lock for this connection
 */

static apr_status_t util_ldap_connection_remove (void *param)
{
    util_ldap_connection_t *ldc = param, *l = NULL, *prev = NULL;
    util_ldap_state_t *st;

    if (!ldc) return APR_SUCCESS;

    st = ldc->st;

    uldap_connection_unbind(ldc);

#if APR_HAS_THREADS
    apr_thread_mutex_lock(st->mutex);
#endif

    /* Remove ldc from the list */
    for (l=st->connections; l; l=l->next) {
        if (l == ldc) {
            if (prev) {
                prev->next = l->next;
            }
            else {
                st->connections = l->next;
            }
            break;
        }
        prev = l;
    }

    if (ldc->bindpw) {
        free((void*)ldc->bindpw);
    }
    if (ldc->binddn) {
        free((void*)ldc->binddn);
    }

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(ldc->lock);
    apr_thread_mutex_unlock(st->mutex);
#endif

    /* Destroy the pool associated with this connection */

    apr_pool_destroy(ldc->pool);

    return APR_SUCCESS;
}
#endif

static int uldap_connection_init(request_rec *r,
                                 util_ldap_connection_t *ldc)
{
    int rc = 0, ldap_option = 0;
    int version  = LDAP_VERSION3;
    apr_ldap_err_t *result = NULL;
#ifdef LDAP_OPT_NETWORK_TIMEOUT
    struct timeval connectionTimeout = {0};
#endif
    util_ldap_state_t *st =
        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
        &ldap_module);
    int have_client_certs = !apr_is_empty_array(ldc->client_certs);
#if !APR_HAS_SOLARIS_LDAPSDK
    /*
     * Normally we enable SSL/TLS with apr_ldap_set_option(), except
     * with Solaris LDAP, where this is broken.
     */

    int secure = APR_LDAP_NONE;
#else
    /*
     * With Solaris LDAP, we enable TSL via the secure argument
     * to apr_ldap_init(). This requires a fix from apr-util >= 1.4.0.
     *
     * Just in case client certificates ever get supported, we
     * handle those as with the other LDAP SDKs.
     */

    int secure = have_client_certs ? APR_LDAP_NONE : ldc->secure;
#endif

    /* Since the host will include a port if the default port is not used,
     * always specify the default ports for the port parameter.  This will
     * allow a host string that contains multiple hosts the ability to mix
     * some hosts with ports and some without. All hosts which do not
     * specify a port will use the default port.
     */

    apr_ldap_init(r->pool, &(ldc->ldap),
                  ldc->host,
                  APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
                  secure, &(result));

    if (NULL == result) {
        /* something really bad happened */
        ldc->bound = 0;
        if (NULL == ldc->reason) {
            ldc->reason = "LDAP: ldap initialization failed. Make sure the apr_ldap module is installed.";
        }
        return(APR_EGENERAL);
    }

    if (result->rc) {
        ldc->reason = result->reason;
        ldc->bound = 0;
        return result->rc;
    }

    if (NULL == ldc->ldap)
    {
        ldc->bound = 0;
        if (NULL == ldc->reason) {
            ldc->reason = "LDAP: ldap initialization failed";
        }
        else {
            ldc->reason = result->reason;
        }
        return(result->rc);
    }

    ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "LDC %pp init", ldc);

    if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
        /* Now that we have an ldap struct, add it to the referral list for rebinds. */
        rc = uldap_rebind_add(ldc);
        if (rc != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, APLOGNO(01277)
                    "LDAP: Unable to add rebind cross reference entry. Out of memory?");
            uldap_connection_unbind(ldc);
            ldc->reason = "LDAP: Unable to add rebind cross reference entry.";
            return(rc);
        }
    }

    /* always default to LDAP V3 */
    ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);

    /* set client certificates */
    if (have_client_certs) {
        apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
                            ldc->client_certs, &(result));
        if (LDAP_SUCCESS != result->rc) {
            uldap_connection_unbind( ldc );
            ldc->reason = result->reason;
            return(result->rc);
        }
    }

    /* switch on SSL/TLS */
    if (APR_LDAP_NONE != ldc->secure
#if APR_HAS_SOLARIS_LDAPSDK
        /* See comments near apr_ldap_init() above */
        && have_client_certs
#endif
       ) {
        apr_ldap_set_option(r->pool, ldc->ldap,
                            APR_LDAP_OPT_TLS, &ldc->secure, &(result));
        if (LDAP_SUCCESS != result->rc) {
            uldap_connection_unbind( ldc );
            ldc->reason = result->reason;
            return(result->rc);
        }
    }

    /* Set the alias dereferencing option */
    ldap_option = ldc->deref;
    ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);

    if (ldc->ChaseReferrals != AP_LDAP_CHASEREFERRALS_SDKDEFAULT) {
        /* Set options for rebind and referrals. */
        ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, r->server,
                "LDAP: Setting referrals to %s.",
                ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"));
        apr_ldap_set_option(r->pool, ldc->ldap,
                APR_LDAP_OPT_REFERRALS,
                (void *)((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ?
                    LDAP_OPT_ON : LDAP_OPT_OFF),
                &(result));
        if (result->rc != LDAP_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01279)
                    "Unable to set LDAP_OPT_REFERRALS option to %s: %d.",
                    ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"),
                    result->rc);
            result->reason = "Unable to set LDAP_OPT_REFERRALS.";
            ldc->reason = result->reason;
            uldap_connection_unbind(ldc);
            return(result->rc);
        }
    }

    if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
        if ((ldc->ReferralHopLimit != AP_LDAP_HOPLIMIT_UNSET) && ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
            /* Referral hop limit - only if referrals are enabled and a hop limit is explicitly requested */
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01280)
                    "Setting referral hop limit to %d.",
                    ldc->ReferralHopLimit);
            apr_ldap_set_option(r->pool, ldc->ldap,
                    APR_LDAP_OPT_REFHOPLIMIT,
                    (void *)&ldc->ReferralHopLimit,
                    &(result));
            if (result->rc != LDAP_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01281)
                        "Unable to set LDAP_OPT_REFHOPLIMIT option to %d: %d.",
                        ldc->ReferralHopLimit,
                        result->rc);
                result->reason = "Unable to set LDAP_OPT_REFHOPLIMIT.";
                ldc->reason = result->reason;
                uldap_connection_unbind(ldc);
                return(result->rc);
            }
        }
    }

/*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
#ifdef APR_LDAP_OPT_VERIFY_CERT
    apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_VERIFY_CERT,
                        &(st->verify_svr_cert), &(result));
#else
#if defined(LDAPSSL_VERIFY_SERVER)
    if (st->verify_svr_cert) {
        result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
    }
    else {
        result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
    }
#elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
    /* This is not a per-connection setting so just pass NULL for the
       Ldap connection handle */

    if (st->verify_svr_cert) {
        int i = LDAP_OPT_X_TLS_DEMAND;
        result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
    }
    else {
        int i = LDAP_OPT_X_TLS_NEVER;
        result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
    }
#endif
#endif

#ifdef LDAP_OPT_NETWORK_TIMEOUT
    if (st->connectionTimeout > 0) {
        connectionTimeout.tv_sec = st->connectionTimeout;
    }

    if (connectionTimeout.tv_sec > 0) {
        rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
                                 (void *)&connectionTimeout, &(result));
        if (APR_SUCCESS != rc) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01282)
                             "LDAP: Could not set the connection timeout");
        }
    }
#endif

#ifdef LDAP_OPT_TIMEOUT
    /*
     * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap
     * function calls and not just ldap_search_ext_s(), which accepts a timeout
     * parameter.
     * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all
     * XXX: synchronous ldap function calls with asynchronous calls and using
     * XXX: ldap_result() with a timeout.
     */

    if (st->opTimeout) {
        rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_TIMEOUT,
                                 st->opTimeout, &(result));
        if (APR_SUCCESS != rc) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01283)
                             "LDAP: Could not set LDAP_OPT_TIMEOUT");
        }
    }
#endif

    return(rc);
}

static int uldap_ld_errno(util_ldap_connection_t *ldc)
{
    int ldaprc;
#ifdef LDAP_OPT_ERROR_NUMBER
    if (LDAP_SUCCESS == ldap_get_option(ldc->ldap, LDAP_OPT_ERROR_NUMBER, &ldaprc)) return ldaprc;
#endif
#ifdef LDAP_OPT_RESULT_CODE
    if (LDAP_SUCCESS == ldap_get_option(ldc->ldap, LDAP_OPT_RESULT_CODE, &ldaprc)) return ldaprc;
#endif
    return LDAP_OTHER;
}

/*
 * Replacement function for ldap_simple_bind_s() with a timeout.
 * To do this in a portable way, we have to use ldap_simple_bind() and
 * ldap_result().
 *
 * Returns LDAP_SUCCESS on success; and an error code on failure
 */

static int uldap_simple_bind(util_ldap_connection_t *ldc, char *binddn,
                             char* bindpw, struct timeval *timeout)
{
    LDAPMessage *result;
    int rc;
    int msgid = ldap_simple_bind(ldc->ldap, binddn, bindpw);
    if (msgid == -1) {
        ldc->reason = "LDAP: ldap_simple_bind() failed";
        return uldap_ld_errno(ldc);
    }
    rc = ldap_result(ldc->ldap, msgid, 0, timeout, &result);
    if (rc == -1) {
        ldc->reason = "LDAP: ldap_simple_bind() result retrieval failed";
        /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
        return uldap_ld_errno(ldc);
    }
    else if (rc == 0) {
        ldc->reason = "LDAP: ldap_simple_bind() timed out";
        rc = LDAP_TIMEOUT;
    } else if (ldap_parse_result(ldc->ldap, result, &rc, NULL, NULL, NULL,
                                 NULL, 1) == -1) {
        ldc->reason = "LDAP: ldap_simple_bind() parse result failed";
        return uldap_ld_errno(ldc);
    }
    else { 
        ldc->last_backend_conn = ldc->r->request_time;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, ldc->r, "LDC %pp bind", ldc);
    }
    return rc;
}

/*
 * Connect to the LDAP server and binds. Does not connect if already
 * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
 *
 * Returns LDAP_SUCCESS on success; and an error code on failure
 */

static int uldap_connection_open(request_rec *r,
                                 util_ldap_connection_t *ldc)
{
    int rc = 0;
    int failures = 0;
    int new_connection = 0;
    util_ldap_state_t *st;

    /* sanity check for NULL */
    if (!ldc) {
        return -1;
    }

    /* If the connection is already bound, return
    */

    if (ldc->bound && !ldc->must_rebind)
    {
        ldc->reason = "LDAP: connection open successful (already bound)";
        return LDAP_SUCCESS;
    }

    /* create the ldap session handle
    */

    if (NULL == ldc->ldap)
    {
       new_connection = 1;
       rc = uldap_connection_init( r, ldc );
       if (LDAP_SUCCESS != rc)
       {
           return rc;
       }
    }


    st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
                                                   &ldap_module);

    /* loop trying to bind up to st->retries times if LDAP_SERVER_DOWN or LDAP_TIMEOUT
     * are returned.  Close the connection before the first retry, and then on every
     * other retry.
     *
     * On Success or any other error, break out of the loop.
     *
     * NOTE: Looping is probably not a great idea. If the server isn't
     * responding the chances it will respond after a few tries are poor.
     * However, the original code looped and it only happens on
     * the error condition.
     */


    while (failures <= st->retries) {
        if (failures > 0 && st->retry_delay > 0) {
            apr_sleep(st->retry_delay);
        }
        rc = uldap_simple_bind(ldc, (char *)ldc->binddn, (char *)ldc->bindpw,
                               st->opTimeout);

        if (rc == LDAP_SUCCESS) break;

        failures++;

        if (AP_LDAP_IS_SERVER_DOWN(rc)) {
             ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                          "ldap_simple_bind() failed with server down "
                          "(try %d)", failures);
        }
        else if (rc == LDAP_TIMEOUT) {
            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01284)
                          "ldap_simple_bind() timed out on %s "
                          "connection, dropped by firewall?",
                          new_connection ? "new" : "reused");
        }
        else {
            /* Other errors not retryable */
            break;
        }

        if (!(failures % 2)) {
            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                          "attempt to re-init the connection");
            uldap_connection_unbind(ldc);
            if (LDAP_SUCCESS != uldap_connection_init(r, ldc)) {
                /* leave rc as the initial bind return code */
                break;
            }
        }
    }

    /* free the handle if there was an error
    */

    if (LDAP_SUCCESS != rc)
    {
        uldap_connection_unbind(ldc);
        ldc->reason = "LDAP: ldap_simple_bind() failed";
    }
    else {
        ldc->bound = 1;
        ldc->must_rebind = 0;
        ldc->reason = "LDAP: connection open successful";
    }

    return(rc);
}


/*
 * Compare client certificate arrays.
 *
 * Returns 1 on compare failure, 0 otherwise.
 */

static int compare_client_certs(apr_array_header_t *srcs,
                                apr_array_header_t *dests)
{
    int i = 0;
    struct apr_ldap_opt_tls_cert_t *src, *dest;

    /* arrays both NULL? if so, then equal */
    if (srcs == NULL && dests == NULL) {
        return 0;
    }

    /* arrays different length or either NULL? If so, then not equal */
    if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
        return 1;
    }

    /* run an actual comparison */
    src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
    dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
    for (i = 0; i < srcs->nelts; i++) {
        if ((strcmp(src[i].path, dest[i].path)) ||
            (src[i].type != dest[i].type) ||
            /* One is passwordless? If so, then not equal */
            ((src[i].password == NULL) ^ (dest[i].password == NULL)) ||
            (src[i].password != NULL && dest[i].password != NULL &&
             strcmp(src[i].password, dest[i].password))) {
            return 1;
        }
    }

    /* if we got here, the cert arrays were identical */
    return 0;

}


/*
 * Find an existing ldap connection struct that matches the
 * provided ldap connection parameters.
 *
 * If not found in the cache, a new ldc structure will be allocated
 * from st->pool and returned to the caller.  If found in the cache,
 * a pointer to the existing ldc structure will be returned.
 */

static util_ldap_connection_t *
            uldap_connection_find(request_rec *r,
                                  const char *host, int port,
                                  const char *binddn, const char *bindpw,
                                  deref_options deref, int secure)
{
    struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
    int secureflag = secure;
    apr_time_t now = apr_time_now();

    util_ldap_state_t *st =
        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
        &ldap_module);
    util_ldap_config_t *dc =
        (util_ldap_config_t *) ap_get_module_config(r->per_dir_config, &ldap_module);

#if APR_HAS_THREADS
    /* mutex lock this function */
    apr_thread_mutex_lock(st->mutex);
#endif

    if (secure < APR_LDAP_NONE) {
        secureflag = st->secure;
    }

    /* Search for an exact connection match in the list that is not
     * being used.
     */

    for (l=st->connections,p=NULL; l; l=l->next) {
#if APR_HAS_THREADS
        if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
#endif
        if (   (l->port == port) && (strcmp(l->host, host) == 0)
            && ((!l->binddn && !binddn) || (l->binddn && binddn
                                             && !strcmp(l->binddn, binddn)))
            && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
                                             && !strcmp(l->bindpw, bindpw)))
            && (l->deref == deref) && (l->secure == secureflag)
            && !compare_client_certs(dc->client_certs, l->client_certs))
        {
            if (st->connection_pool_ttl > 0) {
                if (l->bound && (now - l->last_backend_conn) > st->connection_pool_ttl) {
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
                                  "Removing LDAP connection last used %" APR_TIME_T_FMT " seconds ago",
                                  (now - l->last_backend_conn) / APR_USEC_PER_SEC);
                    l->r = r;
                    uldap_connection_unbind(l);
                    /* Go ahead (by falling through) and use it, so we don't create more just to unbind some other old ones */
                }
                ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, 
                              "Reuse %s LDC %pp"
                              l->bound ? "bound" : "unbound", l);
            }
            break;
        }
#if APR_HAS_THREADS
            /* If this connection didn't match the criteria, then we
             * need to unlock the mutex so it is available to be reused.
             */

            apr_thread_mutex_unlock(l->lock);
        }
#endif
        p = l;
    }

    /* If nothing found, search again, but we don't care about the
     * binddn and bindpw this time.
     */

    if (!l) {
        for (l=st->connections,p=NULL; l; l=l->next) {
#if APR_HAS_THREADS
            if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {

#endif
            if ((l->port == port) && (strcmp(l->host, host) == 0) &&
                (l->deref == deref) && (l->secure == secureflag) &&
                !compare_client_certs(dc->client_certs, l->client_certs))
            {
                if (st->connection_pool_ttl > 0) {
                    if (l->bound && (now - l->last_backend_conn) > st->connection_pool_ttl) {
                        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
                                "Removing LDAP connection last used %" APR_TIME_T_FMT " seconds ago",
                                (now - l->last_backend_conn) / APR_USEC_PER_SEC);
                        l->r = r;
                        uldap_connection_unbind(l);
                        /* Go ahead (by falling through) and use it, so we don't create more just to unbind some other old ones */
                    }
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, 
                                  "Reuse %s LDC %pp (will rebind)"
                                   l->bound ? "bound" : "unbound", l);
                }

                /* the bind credentials have changed */
                l->must_rebind = 1;
                util_ldap_strdup((char**)&(l->binddn), binddn);
                util_ldap_strdup((char**)&(l->bindpw), bindpw);

                break;
            }
#if APR_HAS_THREADS
                /* If this connection didn't match the criteria, then we
                 * need to unlock the mutex so it is available to be reused.
                 */

                apr_thread_mutex_unlock(l->lock);
            }
#endif
            p = l;
        }
    }

/* artificially disable cache */
/* l = NULL; */

    /* If no connection was found after the second search, we
     * must create one.
     */

    if (!l) {
        apr_pool_t *newpool;
        if (apr_pool_create(&newpool, NULL) != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01285)
                          "util_ldap: Failed to create memory pool");
#if APR_HAS_THREADS
            apr_thread_mutex_unlock(st->mutex);
#endif
            return NULL;
        }
        apr_pool_tag(newpool, "util_ldap_connection");

        /*
         * Add the new connection entry to the linked list. Note that we
         * don't actually establish an LDAP connection yet; that happens
         * the first time authentication is requested.
         */


        /* create the details of this connection in the new pool */
        l = apr_pcalloc(newpool, sizeof(util_ldap_connection_t));
        l->pool = newpool;
        l->st = st;

#if APR_HAS_THREADS
        apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, l->pool);
        apr_thread_mutex_lock(l->lock);
#endif
        l->bound = 0;
        l->host = apr_pstrdup(l->pool, host);
        l->port = port;
        l->deref = deref;
        util_ldap_strdup((char**)&(l->binddn), binddn);
        util_ldap_strdup((char**)&(l->bindpw), bindpw);
        l->ChaseReferrals = dc->ChaseReferrals;
        l->ReferralHopLimit = dc->ReferralHopLimit;

        /* The security mode after parsing the URL will always be either
         * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
         * If the security setting is NONE, override it to the security
         * setting optionally supplied by the admin using LDAPTrustedMode
         */

        l->secure = secureflag;

        /* save away a copy of the client cert list that is presently valid */
        l->client_certs = apr_array_copy_hdr(l->pool, dc->client_certs);

        /* whether or not to keep this connection in the pool when it's returned */
        l->keep = (st->connection_pool_ttl == 0) ? 0 : 1;

#ifdef USE_APR_LDAP_REBIND
        if (l->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
            if (apr_pool_create(&(l->rebind_pool), l->pool) != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01286)
                              "util_ldap: Failed to create memory pool");
#if APR_HAS_THREADS
                apr_thread_mutex_unlock(st->mutex);
#endif
                return NULL;
            }
            apr_pool_tag(l->rebind_pool, "util_ldap_rebind");
        }
#endif

        if (p) {
            p->next = l;
        }
        else {
            st->connections = l;
        }
    }

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(st->mutex);
#endif
    l->r = r;
    return l;
}

/* ------------------------------------------------------------------ */

/*
 * Compares two DNs to see if they're equal. The only way to do this correctly
 * is to search for the dn and then do ldap_get_dn() on the result. This should
 * match the initial dn, since it would have been also retrieved with
 * ldap_get_dn(). This is expensive, so if the configuration value
 * compare_dn_on_server is false, just does an ordinary strcmp.
 *
 * The lock for the ldap cache should already be acquired.
 */

static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
                                 const char *url, const char *dn,
                                 const char *reqdn, int compare_dn_on_server)
{
    int result = 0;
    util_url_node_t *curl;
    util_url_node_t curnode;
    util_dn_compare_node_t *node;
    util_dn_compare_node_t newnode;
    int failures = 0;
    LDAPMessage *res, *entry;
    char *searchdn;

    util_ldap_state_t *st = (util_ldap_state_t *)
                            ap_get_module_config(r->server->module_config,
                                                 &ldap_module);

    /* get cache entry (or create one) */
    ldap_cache_lock(st, r);

    curnode.url = url;
    curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
    if (curl == NULL) {
        curl = util_ald_create_caches(st, url);
    }
    ldap_cache_unlock(st, r);

    /* a simple compare? */
    if (!compare_dn_on_server) {
        /* unlock this read lock */
        if (strcmp(dn, reqdn)) {
            ldc->reason = "DN Comparison FALSE (direct strcmp())";
            return LDAP_COMPARE_FALSE;
        }
        else {
            ldc->reason = "DN Comparison TRUE (direct strcmp())";
            return LDAP_COMPARE_TRUE;
        }
    }

    if (curl) {
        /* no - it's a server side compare */
        ldap_cache_lock(st, r);

        /* is it in the compare cache? */
        newnode.reqdn = (char *)reqdn;
        node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
        if (node != NULL) {
            /* If it's in the cache, it's good */
            /* unlock this read lock */
            ldap_cache_unlock(st, r);
            ldc->reason = "DN Comparison TRUE (cached)";
            return LDAP_COMPARE_TRUE;
        }

        /* unlock this read lock */
        ldap_cache_unlock(st, r);
    }

start_over:
    if (failures > st->retries) {
        return result;
    }

    if (failures > 0 && st->retry_delay > 0) {
        apr_sleep(st->retry_delay);
    }

    /* make a server connection */
    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
        /* connect to server failed */
        return result;
    }

    /* search for reqdn */
    result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
                               "(objectclass=*)", NULL, 1,
                               NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
    if (AP_LDAP_IS_SERVER_DOWN(result))
    {
        ldc->reason = "DN Comparison ldap_search_ext_s() "
                      "failed with server down";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }
    if (result == LDAP_TIMEOUT && failures == 0) {
        /*
         * we are reusing a connection that doesn't seem to be active anymore
         * (firewall state drop?), let's try a new connection.
         */

        ldc->reason = "DN Comparison ldap_search_ext_s() "
                      "failed with timeout";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }
    if (result != LDAP_SUCCESS) {
        /* search for reqdn failed - no match */
        ldc->reason = "DN Comparison ldap_search_ext_s() failed";
        return result;
    }

    ldc->last_backend_conn = r->request_time;
    entry = ldap_first_entry(ldc->ldap, res);
    searchdn = ldap_get_dn(ldc->ldap, entry);

    ldap_msgfree(res);
    if (strcmp(dn, searchdn) != 0) {
        /* compare unsuccessful */
        ldc->reason = "DN Comparison FALSE (checked on server)";
        result = LDAP_COMPARE_FALSE;
    }
    else {
        if (curl) {
            /* compare successful - add to the compare cache */
            ldap_cache_lock(st, r);
            newnode.reqdn = (char *)reqdn;
            newnode.dn = (char *)dn;

            node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
            if (   (node == NULL)
                || (strcmp(reqdn, node->reqdn) != 0)
                || (strcmp(dn, node->dn) != 0))
            {
                util_ald_cache_insert(curl->dn_compare_cache, &newnode);
            }
            ldap_cache_unlock(st, r);
        }
        ldc->reason = "DN Comparison TRUE (checked on server)";
        result = LDAP_COMPARE_TRUE;
    }
    ldap_memfree(searchdn);
    return result;

}

/*
 * Does an generic ldap_compare operation. It accepts a cache that it will use
 * to lookup the compare in the cache. We cache two kinds of compares
 * (require group compares) and (require user compares). Each compare has a
 * different cache node: require group includes the DN; require user does not
 * because the require user cache is owned by the
 *
 */

static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
                               const char *url, const char *dn,
                               const char *attrib, const char *value)
{
    int result = 0;
    util_url_node_t *curl;
    util_url_node_t curnode;
    util_compare_node_t *compare_nodep;
    util_compare_node_t the_compare_node;
    apr_time_t curtime = 0; /* silence gcc -Wall */
    int failures = 0;

    util_ldap_state_t *st = (util_ldap_state_t *)
                            ap_get_module_config(r->server->module_config,
                                                 &ldap_module);

    /* get cache entry (or create one) */
    ldap_cache_lock(st, r);
    curnode.url = url;
    curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
    if (curl == NULL) {
        curl = util_ald_create_caches(st, url);
    }
    ldap_cache_unlock(st, r);

    if (curl) {
        /* make a comparison to the cache */
        ldap_cache_lock(st, r);
        curtime = apr_time_now();

        the_compare_node.dn = (char *)dn;
        the_compare_node.attrib = (char *)attrib;
        the_compare_node.value = (char *)value;
        the_compare_node.result = 0;
        the_compare_node.sgl_processed = 0;
        the_compare_node.subgroupList = NULL;

        compare_nodep = util_ald_cache_fetch(curl->compare_cache,
                                             &the_compare_node);

        if (compare_nodep != NULL) {
            /* found it... */
            if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
                /* ...but it is too old */
                util_ald_cache_remove(curl->compare_cache, compare_nodep);
            }
            else {
                /* ...and it is good */
                if (LDAP_COMPARE_TRUE == compare_nodep->result) {
                    ldc->reason = "Comparison true (cached)";
                }
                else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
                    ldc->reason = "Comparison false (cached)";
                }
                else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
                    ldc->reason = "Comparison no such attribute (cached)";
                }
                else {
                    ldc->reason = apr_psprintf(r->pool, 
                                              "Comparison undefined: (%d): %s (adding to cache)"
                                              result, ldap_err2string(result));
                }

                /* record the result code to return with the reason... */
                result = compare_nodep->result;
                /* and unlock this read lock */
                ldap_cache_unlock(st, r);

                ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, 
                              "ldap_compare_s(%pp, %s, %s, %s) = %s (cached)"
                              ldc->ldap, dn, attrib, value, ldap_err2string(result));
                return result;
            }
        }
        /* unlock this read lock */
        ldap_cache_unlock(st, r);
    }

start_over:
    if (failures > st->retries) {
        return result;
    }

    if (failures > 0 && st->retry_delay > 0) {
        apr_sleep(st->retry_delay);
    }

    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
        /* connect failed */
        return result;
    }

    result = ldap_compare_s(ldc->ldap,
                            (char *)dn,
                            (char *)attrib,
                            (char *)value);
    if (AP_LDAP_IS_SERVER_DOWN(result)) {
        /* connection failed - try again */
        ldc->reason = "ldap_compare_s() failed with server down";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }
    if (result == LDAP_TIMEOUT && failures == 0) {
        /*
         * we are reusing a connection that doesn't seem to be active anymore
         * (firewall state drop?), let's try a new connection.
         */

        ldc->reason = "ldap_compare_s() failed with timeout";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }

    ldc->last_backend_conn = r->request_time;
    ldc->reason = "Comparison complete";
    if ((LDAP_COMPARE_TRUE == result) ||
        (LDAP_COMPARE_FALSE == result) ||
        (LDAP_NO_SUCH_ATTRIBUTE == result)) {
        if (curl) {
            /* compare completed; caching result */
            ldap_cache_lock(st, r);
            the_compare_node.lastcompare = curtime;
            the_compare_node.result = result;
            the_compare_node.sgl_processed = 0;
            the_compare_node.subgroupList = NULL;

            /* If the node doesn't exist then insert it, otherwise just update
             * it with the last results
             */

            compare_nodep = util_ald_cache_fetch(curl->compare_cache,
                                                 &the_compare_node);
            if (   (compare_nodep == NULL)
                || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
                || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
                || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
            {
                void *junk;

                junk = util_ald_cache_insert(curl->compare_cache,
                                             &the_compare_node);
                if (junk == NULL) {
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01287)
                                  "cache_compare: Cache insertion failure.");
                }
            }
            else {
                compare_nodep->lastcompare = curtime;
                compare_nodep->result = result;
            }
            ldap_cache_unlock(st, r);
        }

        if (LDAP_COMPARE_TRUE == result) {
            ldc->reason = "Comparison true (adding to cache)";
        }
        else if (LDAP_COMPARE_FALSE == result) {
            ldc->reason = "Comparison false (adding to cache)";
        }
        else if (LDAP_NO_SUCH_ATTRIBUTE == result) {
            ldc->reason = "Comparison no such attribute (adding to cache)";
        }
        else {
            ldc->reason = apr_psprintf(r->pool, 
                                       "Comparison undefined: (%d): %s (adding to cache)"
                                        result, ldap_err2string(result));
        }
    }

    ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, 
                  "ldap_compare_s(%pp, %s, %s, %s) = %s"
                  ldc->ldap, dn, attrib, value, ldap_err2string(result));
    return result;
}


static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r,
                                                    util_ldap_connection_t *ldc,
                                                    const char *url,
                                                    const char *dn,
                                                    char **subgroupAttrs,
                                                    apr_array_header_t *subgroupclasses)
{
    int failures = 0;
    int result = LDAP_COMPARE_FALSE;
    util_compare_subgroup_t *res = NULL;
    LDAPMessage *sga_res, *entry;
    struct mod_auth_ldap_groupattr_entry_t *sgc_ents;
    apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
    util_ldap_state_t *st = (util_ldap_state_t *)
                            ap_get_module_config(r->server->module_config,
                                                 &ldap_module);

    sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;

    if (!subgroupAttrs) {
        return res;
    }

start_over:
    /*
     * 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups.
     */

    if (failures > st->retries) {
        return res;
    }

    if (failures > 0 && st->retry_delay > 0) {
        apr_sleep(st->retry_delay);
    }


    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
        /* connect failed */
        return res;
    }

    /* try to do the search */
    result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
                               NULL, subgroupAttrs, 0,
                               NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
    if (AP_LDAP_IS_SERVER_DOWN(result)) {
        ldc->reason = "ldap_search_ext_s() for subgroups failed with server"
                      " down";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }
    if (result == LDAP_TIMEOUT && failures == 0) {
        /*
         * we are reusing a connection that doesn't seem to be active anymore
         * (firewall state drop?), let's try a new connection.
         */

        ldc->reason = "ldap_search_ext_s() for subgroups failed with timeout";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }

    /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
    if (result != LDAP_SUCCESS) {
        ldc->reason = "ldap_search_ext_s() for subgroups failed";
        return res;
    }

    ldc->last_backend_conn = r->request_time;
    entry = ldap_first_entry(ldc->ldap, sga_res);

    /*
     * Get values for the provided sub-group attributes.
     */

    if (subgroupAttrs) {
        int indx = 0, tmp_sgcIndex;

        while (subgroupAttrs[indx]) {
            char **values;
            int val_index = 0;

            /* Get *all* matching "member" values from this group. */
            values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);

            if (values) {
                val_index = 0;
                /*
                 * Now we are going to pare the subgroup members of this group
                 * to *just* the subgroups, add them to the compare_nodep, and
                 * then proceed to check the new level of subgroups.
                 */

                while (values[val_index]) {
                    /* Check if this entry really is a group. */
                    tmp_sgcIndex = 0;
                    result = LDAP_COMPARE_FALSE;
                    while ((tmp_sgcIndex < subgroupclasses->nelts)
                           && (result != LDAP_COMPARE_TRUE)) {
                        result = uldap_cache_compare(r, ldc, url,
                                                     values[val_index],
                                                     "objectClass",
                                                     sgc_ents[tmp_sgcIndex].name
                                                     );

                        if (result != LDAP_COMPARE_TRUE) {
                            tmp_sgcIndex++;
                        }
                    }
                    /* It's a group, so add it to the array.  */
                    if (result == LDAP_COMPARE_TRUE) {
                        char **newgrp = (char **) apr_array_push(subgroups);
                        *newgrp = apr_pstrdup(r->pool, values[val_index]);
                    }
                    val_index++;
                }
                ldap_value_free(values);
            }
            indx++;
        }
    }

    ldap_msgfree(sga_res);

    if (subgroups->nelts > 0) {
        /* We need to fill in tmp_local_subgroups using the data from LDAP */
        int sgindex;
        char **group;
        res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
        res->subgroupDNs  = apr_palloc(r->pool,
                                       sizeof(char *) * (subgroups->nelts));
        for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
            res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
        }
        res->len = sgindex;
    }

    return res;
}


/*
 * Does a recursive lookup operation to try to find a user within (cached)
 * nested groups. It accepts a cache that it will use to lookup previous
 * compare attempts. We cache two kinds of compares (require group compares)
 * and (require user compares). Each compare has a different cache node:
 * require group includes the DN; require user does not because the require
 * user cache is owned by the
 *
 * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
 *
 *
 * 1. Call uldap_cache_compare for each subgroupclass value to check the
 *    generic, user-agnostic, cached group entry. This will create a new generic
 *    cache entry if there
 *    wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we
 *    have no groups.
 * 2. Lock The cache and get the generic cache entry.
 * 3. Check if there is already a subgrouplist in this generic group's cache
 *    entry.
 *    A. If there is, go to step 4.
 *    B. If there isn't:
 *       i)   Use ldap_search to get the full list
 *            of subgroup "members" (which may include non-group "members").
 *       ii)  Use uldap_cache_compare to strip the list down to just groups.
 *       iii) Lock and add this stripped down list to the cache of the generic
 *            group.
 * 4. Loop through the sgl and call uldap_cache_compare (using the user info)
 *    for each
 *    subgroup to see if the subgroup contains the user and to get the subgroups
 *    added to the
 *    cache (with user-afinity, if they aren't already there).
 *    A. If the user is in the subgroup, then we'll be returning
 *       LDAP_COMPARE_TRUE.
 *    B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via
 *       uldap_cache_compare) then recursively call this function to get the
 *       sub-subgroups added...
 * 5. Cleanup local allocations.
 * 6. Return the final result.
 */


static int uldap_cache_check_subgroups(request_rec *r,
                                       util_ldap_connection_t *ldc,
                                       const char *url, const char *dn,
                                       const char *attrib, const char *value,
                                       char **subgroupAttrs,
                                       apr_array_header_t *subgroupclasses,
                                       int cur_subgroup_depth,
                                       int max_subgroup_depth)
{
    int result = LDAP_COMPARE_FALSE;
    util_url_node_t *curl;
    util_url_node_t curnode;
    util_compare_node_t *compare_nodep;
    util_compare_node_t the_compare_node;
    util_compare_subgroup_t *tmp_local_sgl = NULL;
    int sgl_cached_empty = 0, sgindex = 0, base_sgcIndex = 0;
    struct mod_auth_ldap_groupattr_entry_t *sgc_ents =
            (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
    util_ldap_state_t *st = (util_ldap_state_t *)
                            ap_get_module_config(r->server->module_config,
                                                 &ldap_module);

    /*
     * Stop looking at deeper levels of nested groups if we have reached the
     * max. Since we already checked the top-level group in uldap_cache_compare,
     * we don't need to check it again here - so if max_subgroup_depth is set
     * to 0, we won't check it (i.e. that is why we check < rather than <=).
     * We'll be calling uldap_cache_compare from here to check if the user is
     * in the next level before we recurse into that next level looking for
     * more subgroups.
     */

    if (cur_subgroup_depth >= max_subgroup_depth) {
        return LDAP_COMPARE_FALSE;
    }

    /*
     * 1. Check the "groupiness" of the specified basedn. Stopping at the first
     *    TRUE return.
     */

    while ((base_sgcIndex < subgroupclasses->nelts)
           && (result != LDAP_COMPARE_TRUE)) {
        result = uldap_cache_compare(r, ldc, url, dn, "objectClass",
                                     sgc_ents[base_sgcIndex].name);
        if (result != LDAP_COMPARE_TRUE) {
            base_sgcIndex++;
        }
    }

    if (result != LDAP_COMPARE_TRUE) {
        ldc->reason = "DN failed group verification.";
        return result;
    }

    /*
     * 2. Find previously created cache entry and check if there is already a
     *    subgrouplist.
     */

    ldap_cache_lock(st, r);
    curnode.url = url;
    curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
    ldap_cache_unlock(st, r);

    if (curl && curl->compare_cache) {
        /* make a comparison to the cache */
        ldap_cache_lock(st, r);

        the_compare_node.dn = (char *)dn;
        the_compare_node.attrib = (char *)"objectClass";
        the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
        the_compare_node.result = 0;
        the_compare_node.sgl_processed = 0;
        the_compare_node.subgroupList = NULL;

        compare_nodep = util_ald_cache_fetch(curl->compare_cache,
                                             &the_compare_node);

        if (compare_nodep != NULL) {
            /*
             * Found the generic group entry... but the user isn't in this
             * group or we wouldn't be here.
             */

            if (compare_nodep->sgl_processed) {
                if (compare_nodep->subgroupList) {
                    /* Make a local copy of the subgroup list */
                    int i;
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01288)
                                  "Making local copy of SGL for "
                                  "group (%s)(objectClass=%s) ",
                                  dn, (char *)sgc_ents[base_sgcIndex].name);
                    tmp_local_sgl = apr_pcalloc(r->pool,
                                                sizeof(util_compare_subgroup_t));
                    tmp_local_sgl->len = compare_nodep->subgroupList->len;
                    tmp_local_sgl->subgroupDNs =
                        apr_palloc(r->pool,
                                   sizeof(char *) * compare_nodep->subgroupList->len);
                    for (i = 0; i < compare_nodep->subgroupList->len; i++) {
                        tmp_local_sgl->subgroupDNs[i] =
                            apr_pstrdup(r->pool,
                                        compare_nodep->subgroupList->subgroupDNs[i]);
                    }
                }
                else {
                    sgl_cached_empty = 1;
                }
            }
        }
        ldap_cache_unlock(st, r);
    }

    if (!tmp_local_sgl && !sgl_cached_empty) {
        /* No Cached SGL, retrieve from LDAP */
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01289)
                      "no cached SGL for %s, retrieving from LDAP", dn);
        tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs,
                                            subgroupclasses);
        if (!tmp_local_sgl) {
            /* No SGL aailable via LDAP either */
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01290) "no subgroups for %s",
                          dn);
        }

      if (curl && curl->compare_cache) {
        /*
         * Find the generic group cache entry and add the sgl we just retrieved.
         */

        ldap_cache_lock(st, r);

        the_compare_node.dn = (char *)dn;
        the_compare_node.attrib = (char *)"objectClass";
        the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
        the_compare_node.result = 0;
        the_compare_node.sgl_processed = 0;
        the_compare_node.subgroupList = NULL;

        compare_nodep = util_ald_cache_fetch(curl->compare_cache,
                                             &the_compare_node);

        if (compare_nodep == NULL) {
            /*
             * The group entry we want to attach our SGL to doesn't exist.
             * We only got here if we verified this DN was actually a group
             * based on the objectClass, but we can't call the compare function
             * while we already hold the cache lock -- only the insert.
             */

            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01291)
                          "Cache entry for %s doesn't exist", dn);
            the_compare_node.result = LDAP_COMPARE_TRUE;
            util_ald_cache_insert(curl->compare_cache, &the_compare_node);
            compare_nodep = util_ald_cache_fetch(curl->compare_cache,
                                                 &the_compare_node);
            if (compare_nodep == NULL) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01292)
                              "util_ldap: Couldn't retrieve group entry "
                              "for %s from cache",
                              dn);
            }
        }

        /*
         * We have a valid cache entry and a locally generated SGL.
         * Attach the SGL to the cache entry
         */

        if (compare_nodep && !compare_nodep->sgl_processed) {
            if (!tmp_local_sgl) {
                /* We looked up an SGL for a group and found it to be empty */
                if (compare_nodep->subgroupList == NULL) {
                    compare_nodep->sgl_processed = 1;
                }
            }
            else {
                util_compare_subgroup_t *sgl_copy =
                    util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01293)
                             "Copying local SGL of len %d for group %s into cache",
                             tmp_local_sgl->len, dn);
                if (sgl_copy) {
                    if (compare_nodep->subgroupList) {
                        util_ald_sgl_free(curl->compare_cache,
                                          &(compare_nodep->subgroupList));
                    }
                    compare_nodep->subgroupList = sgl_copy;
                    compare_nodep->sgl_processed = 1;
                }
                else {
                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01294)
                                 "Copy of SGL failed to obtain shared memory, "
                                 "couldn't update cache");
                }
            }
        }
        ldap_cache_unlock(st, r);
      }
    }

    /*
     * tmp_local_sgl has either been created, or copied out of the cache
     * If tmp_local_sgl is NULL, there are no subgroups to process and we'll
     * return false
     */

    result = LDAP_COMPARE_FALSE;
    if (!tmp_local_sgl) {
        return result;
    }

    while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
        const char *group = NULL;
        group = tmp_local_sgl->subgroupDNs[sgindex];
        /*
         * 4. Now loop through the subgroupList and call uldap_cache_compare
         * to check for the user.
         */

        result = uldap_cache_compare(r, ldc, url, group, attrib, value);
        if (result == LDAP_COMPARE_TRUE) {
            /*
             * 4.A. We found the user in the subgroup. Return
             * LDAP_COMPARE_TRUE.
             */

            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01295)
                          "Found user %s in a subgroup (%s) at level %d of %d.",
                          r->user, group, cur_subgroup_depth+1,
                          max_subgroup_depth);
        }
        else {
            /*
             * 4.B. We didn't find the user in this subgroup, so recurse into
             * it and keep looking.
             */

            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01296)
                          "User %s not found in subgroup (%s) at level %d of "
                          "%d.", r->user, group, cur_subgroup_depth+1,
                          max_subgroup_depth);
            result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
                                                 value, subgroupAttrs,
                                                 subgroupclasses,
                                                 cur_subgroup_depth+1,
                                                 max_subgroup_depth);
        }
        sgindex++;
    }

    return result;
}


static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
                                   const char *url, const char *basedn,
                                   int scope, char **attrs, const char *filter,
                                   const char *bindpw, const char **binddn,
                                   const char ***retvals)
{
    const char **vals = NULL;
    int numvals = 0;
    int result = 0;
    LDAPMessage *res, *entry;
    char *dn;
    int count;
    int failures = 0;
    util_url_node_t *curl;              /* Cached URL node */
    util_url_node_t curnode;
    util_search_node_t *search_nodep;   /* Cached search node */
    util_search_node_t the_search_node;
    apr_time_t curtime;

    util_ldap_state_t *st =
        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
        &ldap_module);

    /* Get the cache node for this url */
    ldap_cache_lock(st, r);
    curnode.url = url;
    curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
                                                   &curnode);
    if (curl == NULL) {
        curl = util_ald_create_caches(st, url);
    }
    ldap_cache_unlock(st, r);

    if (curl) {
        ldap_cache_lock(st, r);
        the_search_node.username = filter;
        search_nodep = util_ald_cache_fetch(curl->search_cache,
                                            &the_search_node);
        if (search_nodep != NULL) {

            /* found entry in search cache... */
            curtime = apr_time_now();

            /*
             * Remove this item from the cache if its expired. If the sent
             * password doesn't match the storepassword, the entry will
             * be removed and readded later if the credentials pass
             * authentication.
             */

            if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
                /* ...but entry is too old */
                util_ald_cache_remove(curl->search_cache, search_nodep);
            }
            else if (   (search_nodep->bindpw)
                     && (search_nodep->bindpw[0] != '\0')
                     && (strcmp(search_nodep->bindpw, bindpw) == 0))
            {
                /* ...and entry is valid */
                *binddn = apr_pstrdup(r->pool, search_nodep->dn);
                if (attrs) {
                    int i;
                    *retvals = apr_palloc(r->pool, sizeof(char *) * search_nodep->numvals);
                    for (i = 0; i < search_nodep->numvals; i++) {
                        (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
                    }
                }
                ldap_cache_unlock(st, r);
                ldc->reason = "Authentication successful (cached)";
                return LDAP_SUCCESS;
            }
        }
        /* unlock this read lock */
        ldap_cache_unlock(st, r);
    }

    /*
     * At this point, there is no valid cached search, so lets do the search.
     */


    /*
     * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
     */

start_over:
    if (failures > st->retries) {
        return result;
    }

    if (failures > 0 && st->retry_delay > 0) {
        apr_sleep(st->retry_delay);
    }

    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
        return result;
    }

    /* try do the search */
    result = ldap_search_ext_s(ldc->ldap,
                               (char *)basedn, scope,
                               (char *)filter, attrs, 0,
                               NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
    if (AP_LDAP_IS_SERVER_DOWN(result))
    {
        ldc->reason = "ldap_search_ext_s() for user failed with server down";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }

    if (result == LDAP_TIMEOUT) {
        ldc->reason = "ldap_search_ext_s() for user failed with timeout";
        uldap_connection_unbind(ldc);
        failures++;
        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
        goto start_over;
    }


    /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
    if (result != LDAP_SUCCESS) {
        ldc->reason = "ldap_search_ext_s() for user failed";
        return result;
    }

    /*
     * We should have found exactly one entry; to find a different
     * number is an error.
     */

    ldc->last_backend_conn = r->request_time;
    count = ldap_count_entries(ldc->ldap, res);
    if (count != 1)
    {
        if (count == 0 )
            ldc->reason = "User not found";
        else
            ldc->reason = "User is not unique (search found two "
                          "or more matches)";
        ldap_msgfree(res);
        return LDAP_NO_SUCH_OBJECT;
    }

--> --------------------

--> maximum size reached

--> --------------------

92%


¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.25Bemerkung:  (vorverarbeitet)  ¤

*Bot Zugriff






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Ergonomie der
Schnittstellen

Diese beiden folgenden Angebotsgruppen bietet das Unternehmen

Angebot

Hier finden Sie eine Liste der Produkte des Unternehmens