From: Graham Leggett Date: Sat, 18 Aug 2001 16:43:27 +0000 (+0000) Subject: Landing of mod_ldap - the LDAP cache and connection pooling module. X-Git-Tag: 2.0.25~302 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=011142a2afe78de0f27ea9de34fab8143c0d5271;p=apache Landing of mod_ldap - the LDAP cache and connection pooling module. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@90321 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/ldap/Makefile.in b/modules/ldap/Makefile.in new file mode 100644 index 0000000000..167b343d0d --- /dev/null +++ b/modules/ldap/Makefile.in @@ -0,0 +1,3 @@ + +include $(top_srcdir)/build/special.mk + diff --git a/modules/ldap/config2.m4 b/modules/ldap/config2.m4 new file mode 100644 index 0000000000..0cc65bf2e8 --- /dev/null +++ b/modules/ldap/config2.m4 @@ -0,0 +1,8 @@ +dnl modules enabled in this directory by default + +APACHE_MODPATH_INIT(ldap) + +ldap_objects="util_ldap.lo util_ldap_cache.lo util_ldap_cache_mgr.lo" +APACHE_MODULE(ldap, LDAP caching and connection pooling services, $ldap_objects, , no) + +APACHE_MODPATH_FINISH diff --git a/modules/ldap/util_ldap.c b/modules/ldap/util_ldap.c new file mode 100644 index 0000000000..02689bfa12 --- /dev/null +++ b/modules/ldap/util_ldap.c @@ -0,0 +1,1045 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +/* + * 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 + */ + +/* + * FIXME: + * + * - The compare cache presently does not have the ability to + * cache negatively. This has the negative effect of requiring + * a connect/bind/compare/unbind/disconnect when two or more + * atrributes are optional for group membership, and performance + * sucks as a result. + */ + +#include + +#ifdef APU_HAS_LDAP + +#include +#include + +#include "ap_config.h" +#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_ldap.h" +#include "util_ldap_cache.h" + +#include + +module AP_MODULE_DECLARE_DATA ldap_module; + +int util_ldap_handler(request_rec *r); +void *util_ldap_create_config(apr_pool_t *p, server_rec *s); + + +/* + * Some definitions to help between various versions of apache. + */ + +#ifndef DOCTYPE_HTML_2_0 +#define DOCTYPE_HTML_2_0 "\n" +#endif + +#ifndef DOCTYPE_HTML_3_2 +#define DOCTYPE_HTML_3_2 "\n" +#endif + +#ifndef DOCTYPE_HTML_4_0S +#define DOCTYPE_HTML_4_0S "\n" +#endif + +#ifndef DOCTYPE_HTML_4_0T +#define DOCTYPE_HTML_4_0T "\n" +#endif + +#ifndef DOCTYPE_HTML_4_0F +#define DOCTYPE_HTML_4_0F "\n" +#endif + +/* + * Status Handler + * -------------- + * + * This handler generates a status page about the current performance of + * the LDAP cache. It is enabled as follows: + * + * + * SetHandler ldap-status + * + * + */ +int util_ldap_handler(request_rec *r) +{ + + r->allowed |= (1 << M_GET); + if (r->method_number != M_GET) + return DECLINED; + + if (strcmp(r->handler, "ldap-status")) { + return DECLINED; + } + + r->content_type = "text/html"; + if (r->header_only) + return OK; + + ap_rputs(DOCTYPE_HTML_3_2 + "LDAP Cache Information\n", r); + ap_rputs("

LDAP Cache Information

\n", r); + + ap_rputs("

\n" + "\n" + "\n" + "" + "" + "" + "" + "" + "" + "" + "\n", r + ); + + ap_rputs(util_ald_cache_display(r->pool), r); + + ap_rputs("
Cache NameEntriesAvg. Chain Len.HitsIns/RemPurgesAvg Purge Time
\n

\n", r); + + return OK; +} + +/* ------------------------------------------------------------------ */ + + +/* + * Closes an LDAP connection by unbinding. Sets the boundas value for the + * http connection config record and clears the bound dn string in the + * global connection record. The next time util_ldap_connection_open() is + * called, the connection will be recreated. + * + * If the log parameter is set, adds a debug entry to the log that the + * server was down and it's reconnecting. + * + */ +void util_ldap_connection_close(util_ldap_connection_t *ldc) +{ + + /* + * QUESTION: + * + * Is it safe leaving bound connections floating around between the + * different modules? Keeping the user bound is a performance boost, + * but it is also a potential security problem - maybe. + * + * For now we unbind the user when we finish with a connection, but + * we don't have to... + */ + + /* unbinding from the LDAP server */ +/* FIXME: add this to pool cleanup instead */ +/* + if (ldc->ldap) { + ldap_unbind_s(ldc->ldap); + ldc->bound = 0; + ldc->ldap = NULL; + } +*/ + + /* mark our connection as available for reuse */ + apr_lock_release(ldc->lock); + +} + + +/* + * 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 + * XXX FIXME: Make these APR error codes, not LDAP error codes + * + */ +int util_ldap_connection_open(util_ldap_connection_t *ldc) +{ + int result = 0; + int failures = 0; + + +start_over: + if (failures++ > 10) { + /* too many failures - leave */ + return result; + } + + if (!ldc->ldap) { + ldc->bound = 0; + + /* opening connection to LDAP server */ + if ((ldc->ldap = ldap_init(ldc->host, ldc->port)) == NULL) { + /* couldn't connect */ + ldc->reason = "ldap_init() failed"; + return -1; + } + + /* Set the alias dereferencing option */ +#if LDAP_VERSION_MAX == 2 + ldc->ldap->ld_deref = ldc->deref; +#else + result = ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &(ldc->deref)); + if (result != LDAP_SUCCESS) { + /* setting LDAP dereference option failed */ + /* we ignore this error */ + } +#endif /* LDAP_VERSION_MAX */ + +#ifdef APU_HAS_LDAP_NETSCAPE_SSL + if (ldc->netscapessl) { + if (!ldc->certdb) { + /* secure LDAP requested, but no CA cert defined */ + ldc->reason = "secure LDAP requested, but no CA cert defined"; + return -1; + } else { + result = ldapssl_install_routines(ldc->ldap); + if (result != LDAP_SUCCESS) { + /* SSL initialisation failed */ + ldc->reason = "ldapssl_install_routines() failed"; + return result; + } + result = ldap_set_option(ldc->ldap, LDAP_OPT_SSL, LDAP_OPT_ON); + if (result != LDAP_SUCCESS) { + /* SSL option failed */ + ldc->reason = "ldap_set_option() failed trying to set LDAP_OPT_SSL"; + return result; + } + } + } +#endif /* APU_HAS_LDAP_NETSCAPE_SSL */ + +#ifdef APU_HAS_LDAP_STARTTLS + if (ldc->starttls) { + int version = LDAP_VERSION3; + + /* Also we have to set the connection to use protocol version 3, + * since we're using TLS. */ + if ((result = ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, + &version)) != LDAP_SUCCESS) { + /* setting LDAP version failed - ignore error */ + } + + /* + * In util_ldap_connection_find, we compare ldc->withtls to + * sec->starttls to see if we have a cache match. On the off + * chance that apache's config processing rotines set starttls to + * some other true value besides 1, we set it to 1 here to ensure + * that the comparison succeeds. + */ + ldc->starttls = 1; + + result = ldap_start_tls_s(ldc->ldap, NULL, NULL); + if (result != LDAP_SUCCESS) { + /* start TLS failed */ + ldc->withtls = 0; + ldc->reason = "ldap_start_tls_s() failed"; + return result; + } + ldc->withtls = 1; + } else { + ldc->withtls = 0; + } +#endif /* APU_HAS_LDAP_STARTTLS */ + } + + /* + * At this point the LDAP connection is guaranteed alive. If bound says + * that we're bound already, we can just return. + */ + if (ldc->bound) { + ldc->reason = "LDAP connection open successful (already bound)"; + return LDAP_SUCCESS; + } + + /* + * Now bind with the username/password provided by the + * configuration. It will be an anonymous bind if no u/p was + * provided. + */ + if ((result = ldap_simple_bind_s(ldc->ldap, ldc->binddn, ldc->bindpw)) + == LDAP_SERVER_DOWN) { + /* couldn't connect - try again */ + ldc->reason = "ldap_simple_bind_s() failed with server down"; + goto start_over; + } + + if (result != LDAP_SUCCESS) { + /* LDAP fatal error occured */ + ldc->reason = "ldap_simple_bind_s() failed"; + return result; + } + + /* note how we are bound */ + ldc->bound = 1; + + ldc->reason = "LDAP connection open successful"; + return LDAP_SUCCESS; +} + + +/* + * 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. + */ +util_ldap_connection_t *util_ldap_connection_find(request_rec *r, const char *host, int port, + const char *binddn, const char *bindpw, deref_options deref, + int netscapessl, int starttls) +{ + struct util_ldap_connection_t *l, *p; /* To traverse the linked list */ + + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(r->server->module_config, + &ldap_module); + + + /* mutex lock this function */ + if (!st->mutex) { + apr_lock_create(&st->mutex, APR_MUTEX, APR_INTRAPROCESS, NULL, st->pool); + } + apr_lock_acquire(st->mutex); + + /* 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_SUCCESS == apr_lock_tryacquire(l->lock)) + && 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 +#ifdef APU_HAS_LDAP_NETSCAPE_SSL + && l->netscapessl == netscapessl +#endif +#ifdef APU_HAS_LDAP_STARTTLS + && l->withtls == starttls +#endif + ) + break; + 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_SUCCESS == apr_lock_tryacquire(l->lock)) + && l->port == port + && strcmp(l->host, host) == 0 + && l->deref == deref +#ifdef APU_HAS_LDAP_NETSCAPE_SSL + && l->netscapessl == netscapessl +#endif +#ifdef APU_HAS_LDAP_STARTTLS + && l->withtls == starttls +#endif + ) { + /* the bind credentials have changed */ + l->bound = 0; + l->binddn = apr_pstrdup(st->pool, binddn); + l->bindpw = apr_pstrdup(st->pool, bindpw); + break; + } + p = l; + } + } + +/* artificially disable cache */ +//l = NULL; + + /* If no connection what found after the second search, we + * must create one. + */ + if (!l) { + + /* + * 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 to the pool in st */ + l = apr_pcalloc(st->pool, sizeof(util_ldap_connection_t)); + apr_lock_create(&l->lock, APR_MUTEX, APR_INTRAPROCESS, NULL, st->pool); + apr_lock_acquire(l->lock); + l->bound = 0; + l->host = apr_pstrdup(st->pool, host); + l->port = port; + l->deref = deref; + l->binddn = apr_pstrdup(st->pool, binddn); + l->bindpw = apr_pstrdup(st->pool, bindpw); + l->netscapessl = netscapessl; + l->starttls = starttls; + l->withtls = 0; + + if (p) { + p->next = l; + } + else { + st->connections = l; + } + } + + apr_lock_release(st->mutex); + 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. + */ +int util_ldap_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); + + + /* read lock this function */ + if (!util_ldap_cache_lock) { + apr_lock_create(&util_ldap_cache_lock, APR_READWRITE, APR_INTRAPROCESS, NULL, st->pool); + } + + /* get cache entry (or create one) */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_WRITER); + curnode.url = url; + curl = util_ald_cache_fetch(util_ldap_cache, &curnode); + if (curl == NULL) { + curl = util_ald_create_caches(st, url); + } + apr_lock_release(util_ldap_cache_lock); + + /* 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; + } + } + + /* no - it's a server side compare */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_READER); + + /* 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 */ + apr_lock_release(util_ldap_cache_lock); + ldc->reason = "DN Comparison TRUE (cached)"; + return LDAP_COMPARE_TRUE; + } + + /* unlock this read lock */ + apr_lock_release(util_ldap_cache_lock); + +start_over: + if (failures++ > 10) { + /* too many failures */ + return result; + } + + /* make a server connection */ + if (LDAP_SUCCESS != (result = util_ldap_connection_open(ldc))) { + /* connect to server failed */ + return result; + } + + /* search for reqdn */ + if ((result = ldap_search_ext_s(ldc->ldap, const_cast(reqdn), LDAP_SCOPE_BASE, + "(objectclass=*)", NULL, 1, + NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) { + util_ldap_connection_close(ldc); + ldc->reason = "DN Comparison ldap_search_ext_s() failed with server down"; + goto start_over; + } + if (result != LDAP_SUCCESS) { + /* search for reqdn failed - no match */ + ldc->reason = "DN Comparison ldap_search_ext_s() failed"; + return result; + } + + 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 { + /* compare successful - add to the compare cache */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_READER); + newnode.reqdn = (char *)reqdn; + newnode.dn = (char *)dn; + util_ald_cache_insert(curl->dn_compare_cache, &newnode); + apr_lock_release(util_ldap_cache_lock); + 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 + * + */ +int util_ldap_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; + int failures = 0; + + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(r->server->module_config, + &ldap_module); + + + /* read lock this function */ + if (!util_ldap_cache_lock) { + apr_lock_create(&util_ldap_cache_lock, APR_READWRITE, APR_INTRAPROCESS, NULL, st->pool); + } + + /* get cache entry (or create one) */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_WRITER); + curnode.url = url; + curl = util_ald_cache_fetch(util_ldap_cache, &curnode); + if (curl == NULL) { + curl = util_ald_create_caches(st, url); + } + apr_lock_release(util_ldap_cache_lock); + + /* make a comparison to the cache */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_READER); + curtime = apr_time_now(); + + the_compare_node.dn = (char *)dn; + the_compare_node.attrib = (char *)attrib; + the_compare_node.value = (char *)value; + + 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 */ + /* unlock this read lock */ + apr_lock_release(util_ldap_cache_lock); + ldc->reason = "Comparison successful (cached)"; + return LDAP_COMPARE_TRUE; + } + } + /* unlock this read lock */ + apr_lock_release(util_ldap_cache_lock); + + +start_over: + if (failures++ > 10) { + /* too many failures */ + return result; + } + if (LDAP_SUCCESS != (result = util_ldap_connection_open(ldc))) { + /* connect failed */ + return result; + } + + if ((result = ldap_compare_s(ldc->ldap, const_cast(dn), + const_cast(attrib), const_cast(value))) + == LDAP_SERVER_DOWN) { + /* connection failed - try again */ + util_ldap_connection_close(ldc); + ldc->reason = "ldap_compare_s() failed with server down"; + goto start_over; + } + + if (result == LDAP_COMPARE_TRUE) { + /* compare succeeded; caching result */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_WRITER); + the_compare_node.lastcompare = curtime; + util_ald_cache_insert(curl->compare_cache, &the_compare_node); + apr_lock_release(util_ldap_cache_lock); + } + ldc->reason = "Comparison complete"; + return result; +} + +int util_ldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc, + const char *url, const char *basedn, int scope, + const char *filter, const char *bindpw, const char **binddn) +{ + 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); + + + /* read lock this function */ + if (!util_ldap_cache_lock) { + apr_lock_create(&util_ldap_cache_lock, APR_READWRITE, APR_INTRAPROCESS, NULL, st->pool); + } + + /* Get the cache node for this url */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_WRITER); + curnode.url = url; + curl = (util_url_node_t *)util_ald_cache_fetch(util_ldap_cache, &curnode); + if (curl == NULL) { + curl = util_ald_create_caches(st, url); + } + apr_lock_release(util_ldap_cache_lock); + + apr_lock_acquire_rw(util_ldap_cache_lock, APR_READER); + the_search_node.username = filter; + search_nodep = util_ald_cache_fetch(curl->search_cache, &the_search_node); + if (search_nodep != NULL && search_nodep->bindpw) { + + /* found entry in search cache... */ + curtime = apr_time_now(); + + /* + * Remove this item from the cache if its expired, or if the + * sent password doesn't match the storepassword. + */ + 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 (strcmp(search_nodep->bindpw, bindpw) != 0) { + /* ...but cached password doesn't match sent password */ + util_ald_cache_remove(curl->search_cache, search_nodep); + } + else { + /* ...and entry is valid */ + *binddn = search_nodep->dn; + apr_lock_release(util_ldap_cache_lock); + ldc->reason = "Authentication successful (cached)"; + return LDAP_SUCCESS; + } + } + /* unlock this read lock */ + apr_lock_release(util_ldap_cache_lock); + + + /* + * At this point, there is no valid cached search, so lets do the search. + */ + + /* + * If any LDAP operation fails due to LDAP_SERVER_DOWN, control returns here. + */ +start_over: + if (failures++ > 10) { + return result; + } + if (LDAP_SUCCESS != (result = util_ldap_connection_open(ldc))) { + return result; + } + + /* try do the search */ + if ((result = ldap_search_ext_s(ldc->ldap, + basedn, scope, + filter, NULL, 1, + NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) { + ldc->reason = "ldap_search_ext_s() for user failed with server down"; + 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. + */ + count = ldap_count_entries(ldc->ldap, res); + if (count != 1) { + ldap_msgfree(res); + ldc->reason = "User is not unique (search found two or more matches)"; + return LDAP_NO_SUCH_OBJECT; + } + + entry = ldap_first_entry(ldc->ldap, res); + + /* Grab the dn, copy it into the pool, and free it again */ + dn = ldap_get_dn(ldc->ldap, entry); + *binddn = apr_pstrdup(st->pool, dn); + ldap_memfree(dn); + + /* + * A bind to the server with an empty password always succeeds, so + * we check to ensure that the password is not empty. This implies + * that users who actually do have empty passwords will never be + * able to authenticate with this module. I don't see this as a big + * problem. + */ + if (strlen(bindpw) <= 0) { + ldap_msgfree(res); + ldc->reason = "Empty password not allowed"; + return LDAP_INVALID_CREDENTIALS; + } + + /* + * Attempt to bind with the retrieved dn and the password. If the bind + * fails, it means that the password is wrong (the dn obviously + * exists, since we just retrieved it) + */ + if ((result = + ldap_simple_bind_s(ldc->ldap, *binddn, bindpw)) == + LDAP_SERVER_DOWN) { + ldc->reason = "ldap_simple_bind_s() to check user credentials failed with server down"; + goto start_over; + } + + /* failure? if so - return */ + if (result != LDAP_SUCCESS) { + ldc->reason = "ldap_simple_bind_s() to check user credentials failed"; + return result; + } + + ldap_msgfree(res); + + /* + * Add the new username to the search cache. + */ + apr_lock_acquire_rw(util_ldap_cache_lock, APR_WRITER); + the_search_node.username = filter; + the_search_node.dn = *binddn; + the_search_node.bindpw = bindpw; + the_search_node.lastbind = apr_time_now(); + util_ald_cache_insert(curl->search_cache, &the_search_node); + apr_lock_release(util_ldap_cache_lock); + + ldc->reason = "Authentication successful"; + return LDAP_SUCCESS; +} + +#endif /* APU_HAS_LDAP */ + + + +/* ---------------------------------------- */ +/* config directives */ + + +static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy, const char *bytes) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, + &ldap_module); + + st->cache_bytes = atol(bytes); + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, + "[%d] ldap cache: Setting shared memory cache size to %d bytes.", + getpid(), st->cache_bytes); + + return NULL; +} + +static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy, const char *ttl) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, + &ldap_module); + + st->search_cache_ttl = atol(ttl) * 1000; + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, + "[%d] ldap cache: Setting cache TTL to %ld microseconds.", + getpid(), st->search_cache_ttl); + + return NULL; +} + +static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy, const char *size) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, + &ldap_module); + + + st->search_cache_size = atol(size); + if (st->search_cache_size < 0) { + st->search_cache_size = 0; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, + "[%d] ldap cache: Setting search cache size to %ld entries.", + getpid(), st->search_cache_size); + + return NULL; +} + +static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy, const char *ttl) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, + &ldap_module); + + st->compare_cache_ttl = atol(ttl) * 1000; + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, + "[%d] ldap cache: Setting operation cache TTL to %ld microseconds.", + getpid(), st->compare_cache_ttl); + + return NULL; +} + +static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy, const char *size) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, + &ldap_module); + + st->compare_cache_size = atol(size); + if (st->compare_cache_size < 0) { + st->compare_cache_size = 0; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, + "[%d] ldap cache: Setting operation cache size to %ld entries.", + getpid(), st->compare_cache_size); + + return NULL; +} + +#ifdef APU_HAS_LDAPSSL_CLIENT_INIT +static const char *util_ldap_set_certdbpath(cmd_parms *cmd, void *dummy, const char *path) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, + &ldap_module); + + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, + "[%d] ldap cache: Setting LDAP SSL client certificate dbpath to %s.", + getpid(), path); + + st->have_certdb = 1; + if (ldapssl_client_init(path, NULL) != 0) { + return "Could not initialize SSL client"; + } + else { + return NULL; + } +} +#endif + +void *util_ldap_create_config(apr_pool_t *p, server_rec *s) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t)); + + st->pool = p; + + st->cache_bytes = 100000; + st->search_cache_ttl = 600000; + st->search_cache_size = 1024; + st->compare_cache_ttl = 600000; + st->compare_cache_size = 1024; + + st->connections = NULL; +#ifdef APU_HAS_LDAP_NETSCAPE_SSL + st->have_certdb = 0; +#endif + + return st; +} + +static void util_ldap_init_module(apr_pool_t *pool, server_rec *s) +{ + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(s->module_config, + &ldap_module); + + apr_status_t result = util_ldap_cache_init(pool, st->cache_bytes); + char buf[MAX_STRING_LEN]; + + apr_strerror(result, buf, sizeof(buf)); + ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, result, s, + "[%d] ldap cache init: %s", + getpid(), buf); +} + + +command_rec util_ldap_cmds[] = { + AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes, NULL, RSRC_CONF, + "Sets the size of the shared memory cache in bytes. " + "Zero means disable the shared memory cache. Defaults to 100KB."), + + AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries, NULL, RSRC_CONF, + "Sets the maximum number of entries that are possible in the LDAP " + "search cache. " + "Zero means no limit; -1 disables the cache. Defaults to 1024 entries."), + + AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl, NULL, RSRC_CONF, + "Sets the maximum time (in seconds) that an item can be cached in the LDAP " + "search cache. Zero means no limit. Defaults to 600 seconds (10 minutes)."), + + AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries, NULL, RSRC_CONF, + "Sets the maximum number of entries that are possible in the LDAP " + "compare cache. " + "Zero means no limit; -1 disables the cache. Defaults to 1024 entries."), + + AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl, NULL, RSRC_CONF, + "Sets the maximum time (in seconds) that an item is cached in the LDAP " + "operation cache. Zero means no limit. Defaults to 600 seconds (10 minutes)."), + +#ifdef APU_HAS_LDAPSSL_CLIENT_INIT + AP_INIT_TAKE1("LDAPCertDBPath", util_ldap_set_certdbpath, NULL, RSRC_CONF, + "Specifies the file containing Certificate Authority certificates " + "for validating secure LDAP server certificates. This file must be the " + "cert7.db database used by Netscape Communicator"), +#endif + + {NULL} +}; + +static void util_ldap_register_hooks(apr_pool_t *p) +{ + ap_hook_child_init(util_ldap_init_module, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +module ldap_module = { + STANDARD20_MODULE_STUFF, + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + util_ldap_create_config, /* server config */ + NULL, /* merge server config */ + util_ldap_cmds, /* command table */ + util_ldap_register_hooks, /* set up request processing hooks */ +}; diff --git a/modules/ldap/util_ldap_cache.c b/modules/ldap/util_ldap_cache.c new file mode 100644 index 0000000000..363122adcb --- /dev/null +++ b/modules/ldap/util_ldap_cache.c @@ -0,0 +1,253 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +/* + * util_ldap_cache.c: LDAP cache things + * + * Original code from auth_ldap module for Apache v1.3: + * Copyright 1998, 1999 Enbridge Pipelines Inc. + * Copyright 1999-2001 Dave Carrigan + */ + +#include +#include "util_ldap.h" +#include "util_ldap_cache.h" + +#ifdef APU_HAS_LDAP + + + + +/* ------------------------------------------------------------------ */ + +unsigned long util_ldap_url_node_hash(void *n) +{ + util_url_node_t *node = (util_url_node_t *)n; + return util_ald_hash_string(1, node->url); +} + +int util_ldap_url_node_compare(void *a, void *b) +{ + util_url_node_t *na = (util_url_node_t *)a; + util_url_node_t *nb = (util_url_node_t *)b; + + return(strcmp(na->url, nb->url) == 0); +} + +void *util_ldap_url_node_copy(void *c) +{ + util_url_node_t *n = (util_url_node_t *)c; + util_url_node_t *node = (util_url_node_t *)util_ald_alloc(sizeof(util_url_node_t)); + + node->url = util_ald_strdup(n->url); + node->search_cache = n->search_cache; + node->compare_cache = n->compare_cache; + node->dn_compare_cache = n->dn_compare_cache; + return node; +} + +void util_ldap_url_node_free(void *n) +{ + util_url_node_t *node = (util_url_node_t *)n; + + util_ald_free(node->url); + util_ald_destroy_cache(node->search_cache); + util_ald_destroy_cache(node->compare_cache); + util_ald_destroy_cache(node->dn_compare_cache); + util_ald_free(node); +} + +/* ------------------------------------------------------------------ */ + +/* Cache functions for search nodes */ +unsigned long util_ldap_search_node_hash(void *n) +{ + util_search_node_t *node = (util_search_node_t *)n; + return util_ald_hash_string(1, ((util_search_node_t *)(node))->username); +} + +int util_ldap_search_node_compare(void *a, void *b) +{ + return(strcmp(((util_search_node_t *)a)->username, + ((util_search_node_t *)b)->username) == 0); +} + +void *util_ldap_search_node_copy(void *c) +{ + util_search_node_t *node = (util_search_node_t *)c; + util_search_node_t *newnode = util_ald_alloc(sizeof(util_search_node_t)); + newnode->username = util_ald_strdup(node->username); + newnode->dn = util_ald_strdup(node->dn); + newnode->bindpw = util_ald_strdup(node->bindpw); + newnode->lastbind = node->lastbind; + return (void *)newnode; +} + +void util_ldap_search_node_free(void *n) +{ + util_search_node_t *node = (util_search_node_t *)n; + util_ald_free(node->username); + util_ald_free(node->dn); + util_ald_free(node->bindpw); + util_ald_free(node); +} + +/* ------------------------------------------------------------------ */ + +unsigned long util_ldap_compare_node_hash(void *n) +{ + util_compare_node_t *node = (util_compare_node_t *)n; + return util_ald_hash_string(3, node->dn, node->attrib, node->value); +} + +int util_ldap_compare_node_compare(void *a, void *b) +{ + util_compare_node_t *na = (util_compare_node_t *)a; + util_compare_node_t *nb = (util_compare_node_t *)b; + return (strcmp(na->dn, nb->dn) == 0 && + strcmp(na->attrib, nb->attrib) == 0 && + strcmp(na->value, nb->value) == 0); +} + +void *util_ldap_compare_node_copy(void *c) +{ + util_compare_node_t *n = (util_compare_node_t *)c; + util_compare_node_t *node = (util_compare_node_t *)util_ald_alloc(sizeof(util_compare_node_t)); + node->dn = util_ald_strdup(n->dn); + node->attrib = util_ald_strdup(n->attrib); + node->value = util_ald_strdup(n->value); + node->lastcompare = n->lastcompare; + return node; +} + +void util_ldap_compare_node_free(void *n) +{ + util_compare_node_t *node = (util_compare_node_t *)n; + util_ald_free(node->dn); + util_ald_free(node->attrib); + util_ald_free(node->value); + util_ald_free(node); +} + +/* ------------------------------------------------------------------ */ + +unsigned long util_ldap_dn_compare_node_hash(void *n) +{ + return util_ald_hash_string(1, ((util_dn_compare_node_t *)n)->reqdn); +} + +int util_ldap_dn_compare_node_compare(void *a, void *b) +{ + return (strcmp(((util_dn_compare_node_t *)a)->reqdn, + ((util_dn_compare_node_t *)b)->reqdn) == 0); +} + +void *util_ldap_dn_compare_node_copy(void *c) +{ + util_dn_compare_node_t *n = (util_dn_compare_node_t *)c; + util_dn_compare_node_t *node = (util_dn_compare_node_t *)util_ald_alloc(sizeof(util_dn_compare_node_t)); + node->reqdn = util_ald_strdup(n->reqdn); + node->dn = util_ald_strdup(n->dn); + return node; +} + +void util_ldap_dn_compare_node_free(void *n) +{ + util_dn_compare_node_t *node = (util_dn_compare_node_t *)n; + util_ald_free(node->reqdn); + util_ald_free(node->dn); + util_ald_free(node); +} + + +/* ------------------------------------------------------------------ */ +apr_status_t util_ldap_cache_child_kill(void *data); +apr_status_t util_ldap_cache_module_kill(void *data); + +apr_status_t util_ldap_cache_module_kill(void *data) +{ +#ifdef APU_HAS_LDAP_SHARED_CACHE + if (util_ldap_shm != NULL) { + apr_status_t result = apr_shm_destroy(util_ldap_shm); + util_ldap_shm = NULL; + return result; + } +#endif + return APR_SUCCESS; +} + +apr_status_t util_ldap_cache_child_kill(void *data) +{ + /* Nothing to do */ + return APR_SUCCESS; +} + +apr_status_t util_ldap_cache_init(apr_pool_t *pool, apr_size_t reqsize) +{ + apr_status_t result = APR_SUCCESS; + apr_pool_cleanup_register(pool, NULL, util_ldap_cache_module_kill, util_ldap_cache_child_kill); + +#ifdef APU_HAS_LDAP_SHARED_CACHE + result = apr_shm_init(&util_ldap_shm, reqsize, "/tmp/ldap_cache", pool); +#endif + util_ldap_cache = util_ald_create_cache(50, + util_ldap_url_node_hash, + util_ldap_url_node_compare, + util_ldap_url_node_copy, + util_ldap_url_node_free); + return result; +} + + +#endif /* APU_HAS_LDAP */ diff --git a/modules/ldap/util_ldap_cache.h b/modules/ldap/util_ldap_cache.h new file mode 100644 index 0000000000..38a5568aa5 --- /dev/null +++ b/modules/ldap/util_ldap_cache.h @@ -0,0 +1,216 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + + +#ifndef APU_LDAP_CACHE_H +#define APU_LDAP_CACHE_H + +/* + * This switches LDAP support on or off. + */ + +/* this whole thing disappears if LDAP is not enabled */ +#ifdef APU_HAS_LDAP + +/* FIXME */ +#define APU_HAS_LDAP_SHARED_CACHE + +/* + * LDAP Cache Manager + */ + +#ifdef APU_HAS_LDAP_SHARED_CACHE +#include +#endif + +typedef struct util_cache_node_t { + void *payload; /* Pointer to the payload */ + time_t add_time; /* Time node was added to cache */ + struct util_cache_node_t *next; +} util_cache_node_t; + +typedef struct util_ald_cache_t { + unsigned long size; /* Size of cache array */ + unsigned long maxentries; /* Maximum number of cache entries */ + unsigned long numentries; /* Current number of cache entries */ + unsigned long fullmark; /* Used to keep track of when cache becomes 3/4 full */ + time_t marktime; /* Time that the cache became 3/4 full */ + unsigned long (*hash)(void *); /* Func to hash the payload */ + int (*compare)(void *, void *); /* Func to compare two payloads */ + void * (*copy)(void *); /* Func to alloc mem and copy payload to new mem */ + void (*free)(void *); /* Func to free mem used by the payload */ + util_cache_node_t **nodes; + + unsigned long numpurges; /* No. of times the cache has been purged */ + double avg_purgetime; /* Average time to purge the cache */ + time_t last_purge; /* Time of the last purge */ + unsigned long npurged; /* Number of elements purged in last purge. This is not + obvious: it won't be 3/4 the size of the cache if + there were a lot of expired entries. */ + + unsigned long fetches; /* Number of fetches */ + unsigned long hits; /* Number of cache hits */ + unsigned long inserts; /* Number of inserts */ + unsigned long removes; /* Number of removes */ +} util_ald_cache_t; + +#ifdef APU_HAS_LDAP_SHARED_CACHE +apr_shmem_t *util_ldap_shm; +#endif +util_ald_cache_t *util_ldap_cache; +apr_lock_t *util_ldap_cache_lock; + +#ifndef WIN32 +#define ALD_MM_FILE_MODE ( S_IRUSR|S_IWUSR ) +#else +#define ALD_MM_FILE_MODE ( _S_IREAD|_S_IWRITE ) +#endif + + +/* + * LDAP Cache + */ + +/* + * Maintain a cache of LDAP URLs that the server handles. Each node in + * the cache contains the search cache for that URL, and a compare cache + * for the URL. The compare cash is populated when doing require group + * compares. + */ +typedef struct util_url_node_t { + const char *url; + util_ald_cache_t *search_cache; + util_ald_cache_t *compare_cache; + util_ald_cache_t *dn_compare_cache; +} util_url_node_t; + +/* + * We cache every successful search and bind operation, using the username + * as the key. Each node in the cache contains the returned DN, plus the + * password used to bind. + */ +typedef struct util_search_node_t { + const char *username; /* Cache key */ + const char *dn; /* DN returned from search */ + const char *bindpw; /* The most recently used bind password; + NULL if the bind failed */ + apr_time_t lastbind; /* Time of last successful bind */ +} util_search_node_t; + +/* + * We cache every successful compare operation, using the DN, attrib, and + * value as the key. + */ +typedef struct util_compare_node_t { + const char *dn; /* DN, attrib and value combine to be the key */ + const char *attrib; + const char *value; + apr_time_t lastcompare; +} util_compare_node_t; + +/* + * We cache every successful compare dn operation, using the dn in the require + * statement and the dn fetched based on the client-provided username. + */ +typedef struct util_dn_compare_node_t { + const char *reqdn; /* The DN in the require dn statement */ + const char *dn; /* The DN found in the search */ +} util_dn_compare_node_t; + + +/* + * Function prototypes for LDAP cache + */ + +/* util_ldap_cache.c */ +unsigned long util_ldap_url_node_hash(void *n); +int util_ldap_url_node_compare(void *a, void *b); +void *util_ldap_url_node_copy(void *c); +void util_ldap_url_node_free(void *n); +unsigned long util_ldap_search_node_hash(void *n); +int util_ldap_search_node_compare(void *a, void *b); +void *util_ldap_search_node_copy(void *c); +void util_ldap_search_node_free(void *n); +unsigned long util_ldap_compare_node_hash(void *n); +int util_ldap_compare_node_compare(void *a, void *b); +void *util_ldap_compare_node_copy(void *c); +void util_ldap_compare_node_free(void *n); +unsigned long util_ldap_dn_compare_node_hash(void *n); +int util_ldap_dn_compare_node_compare(void *a, void *b); +void *util_ldap_dn_compare_node_copy(void *c); +void util_ldap_dn_compare_node_free(void *n); + + +/* util_ldap_cache_mgr.c */ + +void util_ald_free(const void *ptr); +void *util_ald_alloc(int size); +const char *util_ald_strdup(const char *s); +unsigned long util_ald_hash_string(int nstr, ...); +void util_ald_cache_purge(util_ald_cache_t *cache); +util_url_node_t *util_ald_create_caches(util_ldap_state_t *s, const char *url); +util_ald_cache_t *util_ald_create_cache(unsigned long maxentries, + unsigned long (*hashfunc)(void *), + int (*comparefunc)(void *, void *), + void * (*copyfunc)(void *), + void (*freefunc)(void *)); +void util_ald_destroy_cache(util_ald_cache_t *cache); +void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload); +void util_ald_cache_insert(util_ald_cache_t *cache, void *payload); +void util_ald_cache_remove(util_ald_cache_t *cache, void *payload); +char *util_ald_cache_display_stats(apr_pool_t *p, util_ald_cache_t *cache, + char *name); + +#endif /* APU_HAS_LDAP */ +#endif /* APU_LDAP_CACHE_H */ diff --git a/modules/ldap/util_ldap_cache_mgr.c b/modules/ldap/util_ldap_cache_mgr.c new file mode 100644 index 0000000000..9a63b53b75 --- /dev/null +++ b/modules/ldap/util_ldap_cache_mgr.c @@ -0,0 +1,529 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +/* + * util_ldap_cache_mgr.c: LDAP cache manager things + * + * Original code from auth_ldap module for Apache v1.3: + * Copyright 1998, 1999 Enbridge Pipelines Inc. + * Copyright 1999-2001 Dave Carrigan + */ + +#include +#include "util_ldap.h" +#include "util_ldap_cache.h" +#include + +#ifdef APU_HAS_LDAP + +/* only here until strdup is gone */ +#include + +/* here till malloc is gone */ +#include + +static const int primes[] = +{ + 11, + 19, + 37, + 73, + 109, + 163, + 251, + 367, + 557, + 823, + 1237, + 1861, + 2777, + 4177, + 6247, + 9371, + 14057, + 21089, + 31627, + 47431, + 71143, + 106721, + 160073, + 240101, + 360163, + 540217, + 810343, + 1215497, + 1823231, + 2734867, + 4102283, + 6153409, + 9230113, + 13845163, + 0 +}; + +void util_ald_free(const void *ptr) +{ +#ifdef APU_HAS_LDAP_SHARED_CACHE + if (util_ldap_shm) { + apr_shm_free(util_ldap_shm, (void *)ptr); + } else { + free((void *)ptr); + } +#else + free((void *)ptr); +#endif +} + +void *util_ald_alloc(int size) +{ +#ifdef APU_HAS_LDAP_SHARED_CACHE + if (util_ldap_shm) { + return (void *)apr_shm_malloc(util_ldap_shm, size); + } else { + return (void *)malloc(size); + } +#else + return (void *)malloc(size); +#endif +} + +const char *util_ald_strdup(const char *s) +{ +#ifdef APU_HAS_LDAP_SHARED_CACHE + if (util_ldap_shm) { + char *buf = apr_shm_malloc(util_ldap_shm, strlen(s)+1); + if (buf) { + strcpy(buf, s); + return buf; + } + else { + return NULL; + } + } else { + return strdup(s); + } +#else + return strdup(s); +#endif +} + + +/* + * Computes the hash on a set of strings. The first argument is the number + * of strings to hash, the rest of the args are strings. + * Algorithm taken from glibc. + */ +unsigned long util_ald_hash_string(int nstr, ...) +{ + int i; + va_list args; + unsigned long h=0, g; + char *str, *p; + + va_start(args, nstr); + for (i=0; i < nstr; ++i) { + str = va_arg(args, char *); + for (p = str; *p; ++p) { + h = ( h << 4 ) + *p; + if ( ( g = h & 0xf0000000 ) ) { + h = h ^ (g >> 24); + h = h ^ g; + } + } + } + va_end(args); + + return h; +} + + +/* + Purges a cache that has gotten full. We keep track of the time that we + added the entry that made the cache 3/4 full, then delete all entries + that were added before that time. It's pretty simplistic, but time to + purge is only O(n), which is more important. +*/ +void util_ald_cache_purge(util_ald_cache_t *cache) +{ + int i; + util_cache_node_t *p, *q; + apr_time_t t; + + cache->last_purge = apr_time_now(); + cache->npurged = 0; + cache->numpurges++; + + for (i=0; i < cache->size; ++i) { + p = cache->nodes[i]; + while (p != NULL) { + if (p->add_time < cache->marktime) { + q = p->next; + (*cache->free)(p->payload); + util_ald_free(p); + cache->numentries--; + cache->npurged++; + p = q; + } + else { + p = p->next; + } + } + } + + t = apr_time_now(); + cache->avg_purgetime = + ((t - cache->last_purge) + (cache->avg_purgetime * (cache->numpurges-1))) / + cache->numpurges; +} + + +/* + * create caches + */ +util_url_node_t *util_ald_create_caches(util_ldap_state_t *st, const char *url) +{ + util_url_node_t *curl = NULL; + util_ald_cache_t *search_cache; + util_ald_cache_t *compare_cache; + util_ald_cache_t *dn_compare_cache; + + /* create the three caches */ + search_cache = util_ald_create_cache(st->search_cache_size, + util_ldap_search_node_hash, + util_ldap_search_node_compare, + util_ldap_search_node_copy, + util_ldap_search_node_free); + compare_cache = util_ald_create_cache(st->compare_cache_size, + util_ldap_compare_node_hash, + util_ldap_compare_node_compare, + util_ldap_compare_node_copy, + util_ldap_compare_node_free); + dn_compare_cache = util_ald_create_cache(st->compare_cache_size, + util_ldap_dn_compare_node_hash, + util_ldap_dn_compare_node_compare, + util_ldap_dn_compare_node_copy, + util_ldap_dn_compare_node_free); + + /* check that all the caches initialised successfully */ + if (search_cache && compare_cache && dn_compare_cache) { + + curl = (util_url_node_t *)apr_pcalloc(st->pool, sizeof(util_url_node_t)); + curl->url = url; + curl->search_cache = search_cache; + curl->compare_cache = compare_cache; + curl->dn_compare_cache = dn_compare_cache; + + util_ald_cache_insert(util_ldap_cache, curl); + + } + + return curl; +} + + +util_ald_cache_t *util_ald_create_cache(unsigned long maxentries, + unsigned long (*hashfunc)(void *), + int (*comparefunc)(void *, void *), + void * (*copyfunc)(void *), + void (*freefunc)(void *)) +{ + util_ald_cache_t *cache; + int i; + + if (maxentries <= 0) + return NULL; + + cache = (util_ald_cache_t *)util_ald_alloc(sizeof(util_ald_cache_t)); + if (!cache) + return NULL; + + cache->maxentries = maxentries; + cache->numentries = 0; + cache->size = maxentries / 3; + if (cache->size < 64) cache->size = 64; + for (i = 0; primes[i] && primes[i] < cache->size; ++i) ; + cache->size = primes[i]? primes[i] : primes[i-1]; + + cache->nodes = (util_cache_node_t **)util_ald_alloc(cache->size * sizeof(util_cache_node_t *)); + if (!cache->nodes) { + util_ald_free(cache); + return NULL; + } + + for (i=0; i < cache->size; ++i) + cache->nodes[i] = NULL; + + cache->hash = hashfunc; + cache->compare = comparefunc; + cache->copy = copyfunc; + cache->free = freefunc; + + cache->fullmark = cache->maxentries / 4 * 3; + cache->marktime = 0; + cache->avg_purgetime = 0.0; + cache->numpurges = 0; + cache->last_purge = 0; + cache->npurged = 0; + + cache->fetches = 0; + cache->hits = 0; + cache->inserts = 0; + cache->removes = 0; + + return cache; +} + +void util_ald_destroy_cache(util_ald_cache_t *cache) +{ + int i; + util_cache_node_t *p, *q; + + if (cache == NULL) + return; + + for (i = 0; i < cache->size; ++i) { + p = cache->nodes[i]; + q = NULL; + while (p != NULL) { + q = p->next; + (*cache->free)(p->payload); + util_ald_free(p); + p = q; + } + } + util_ald_free(cache->nodes); +} + +void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload) +{ + int hashval; + util_cache_node_t *p; + + if (cache == NULL) + return NULL; + + cache->fetches++; + + hashval = (*cache->hash)(payload) % cache->size; + for (p = cache->nodes[hashval]; + p && !(*cache->compare)(p->payload, payload); + p = p->next) ; + + if (p != NULL) { + cache->hits++; + return p->payload; + } + else { + return NULL; + } +} + +/* + * Insert an item into the cache. + * *** Does not catch duplicates!!! *** + */ +void util_ald_cache_insert(util_ald_cache_t *cache, void *payload) +{ + int hashval; + util_cache_node_t *node; + + if (cache == NULL || payload == NULL) + return; + + cache->inserts++; + hashval = (*cache->hash)(payload) % cache->size; + node = (util_cache_node_t *)util_ald_alloc(sizeof(util_cache_node_t)); + node->add_time = apr_time_now(); + node->payload = (*cache->copy)(payload); + node->next = cache->nodes[hashval]; + cache->nodes[hashval] = node; + if (++cache->numentries == cache->fullmark) + cache->marktime=apr_time_now(); + if (cache->numentries >= cache->maxentries) + util_ald_cache_purge(cache); +} + +void util_ald_cache_remove(util_ald_cache_t *cache, void *payload) +{ + int hashval; + util_cache_node_t *p, *q; + + if (cache == NULL) + return; + + cache->removes++; + hashval = (*cache->hash)(payload) % cache->size; + for (p = cache->nodes[hashval], q=NULL; + p && !(*cache->compare)(p->payload, payload); + p = p->next) { + q = p; + } + + /* If p is null, it means that we couldn't find the node, so just return */ + if (p == NULL) + return; + + if (q == NULL) { + /* We found the node, and it's the first in the list */ + cache->nodes[hashval] = p->next; + } + else { + /* We found the node and it's not the first in the list */ + q->next = p->next; + } + (*cache->free)(p->payload); + util_ald_free(p); + cache->numentries--; +} + +char *util_ald_cache_display_stats(apr_pool_t *p, util_ald_cache_t *cache, char *name) +{ + int i; + int totchainlen = 0; + int nchains = 0; + double chainlen; + util_cache_node_t *n; + char *buf; + + if (cache == NULL) { + return ""; + } + + for (i=0; i < cache->size; ++i) { + if (cache->nodes[i] != NULL) { + nchains++; + for (n = cache->nodes[i]; n != NULL; n = n->next) + totchainlen++; + } + } + chainlen = nchains? (double)totchainlen / (double)nchains : 0; + + buf = apr_psprintf(p, + "" + "%s" + "%lu (%.0f%% full)" + "%.1f" + "%lu/%lu" + "%.0f%%" + "%lu/%lu", + name, + cache->numentries, + (double)cache->numentries / (double)cache->maxentries * 100.0, + chainlen, + cache->hits, + cache->fetches, + (cache->fetches > 0 ? (double)(cache->hits) / (double)(cache->fetches) * 100.0 : 100.0), + cache->inserts, + cache->removes); + + if (cache->numpurges) { + char str_ctime[APR_CTIME_LEN]; + + apr_ctime(str_ctime, cache->last_purge); + buf = apr_psprintf(p, + "%s" + "%lu\n" + "%s\n", + buf, + cache->numpurges, + str_ctime); + } + else { + buf = apr_psprintf(p, + "%s(none)\n", + buf); + } + + buf = apr_psprintf(p, "%s%.2g\n", buf, cache->avg_purgetime); + + return buf; +} + +char *util_ald_cache_display(apr_pool_t *pool) +{ + int i; + char *buf, *t1, *t2, *t3; + + if (!util_ldap_cache) { + return "Cache has not been enabled/initialised."; + } + + buf = util_ald_cache_display_stats(pool, util_ldap_cache, "LDAP URL Cache"); + + for (i=0; i < util_ldap_cache->size; ++i) { + util_cache_node_t *p; + for (p = util_ldap_cache->nodes[i]; p != NULL; p = p->next) { + util_url_node_t *n; + + n = (util_url_node_t *)p->payload; + + t1 = apr_psprintf(pool, "%s (Searches)", n->url); + t2 = apr_psprintf(pool, "%s (Compares)", n->url); + t3 = apr_psprintf(pool, "%s (DNCompares)", n->url); + + buf = apr_psprintf(pool, "%s\n\n" + "%s\n\n" + "%s\n\n" + "%s\n\n", + buf, + util_ald_cache_display_stats(pool, n->search_cache, t1), + util_ald_cache_display_stats(pool, n->compare_cache, t2), + util_ald_cache_display_stats(pool, n->dn_compare_cache, t3) + ); + } + } + return buf; +} + +#endif /* APU_HAS_LDAP */