]> granicus.if.org Git - apache/commitdiff
Move util_ldap out of experimental and into ldap.
authorBradley Nicholes <bnicholes@apache.org>
Wed, 18 Aug 2004 22:18:39 +0000 (22:18 +0000)
committerBradley Nicholes <bnicholes@apache.org>
Wed, 18 Aug 2004 22:18:39 +0000 (22:18 +0000)
See Attic in experimental directory for previous change history.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@104718 13f79535-47bb-0310-9956-ffa450edef68

modules/ldap/NWGNUmakefile [new file with mode: 0644]
modules/ldap/README.ldap [new file with mode: 0644]
modules/ldap/util_ldap.c [new file with mode: 0644]
modules/ldap/util_ldap.dsp [new file with mode: 0644]
modules/ldap/util_ldap_cache.c [new file with mode: 0644]
modules/ldap/util_ldap_cache.h [new file with mode: 0644]
modules/ldap/util_ldap_cache_mgr.c [new file with mode: 0644]

diff --git a/modules/ldap/NWGNUmakefile b/modules/ldap/NWGNUmakefile
new file mode 100644 (file)
index 0000000..aa15476
--- /dev/null
@@ -0,0 +1,266 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary.  This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS       += \
+                       $(AP_WORK)/include \
+                       $(NWOS) \
+                       $(AP_WORK)/srclib/apr/include \
+                       $(AP_WORK)/srclib/apr-util/include \
+                       $(AP_WORK)/srclib/apr \
+                       $(LDAPSDK)/inc \
+                       $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS                += \
+                       -prefix pre_nw.h \
+                       $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES       += \
+                       $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS                += \
+                       $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS       += \
+                       $(EOLIST)
+
+XCFLAGS                += \
+                       $(EOLIST)
+
+XDEFINES       += \
+                       $(EOLIST)
+
+XLFLAGS                += \
+                       $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS       += \
+                       $(EOLIST)
+
+XCFLAGS                += \
+                       $(EOLIST)
+
+XDEFINES       += \
+                       $(EOLIST)
+
+XLFLAGS                += \
+                       $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS       += \
+                       $(EOLIST)
+
+XCFLAGS                += \
+                       $(EOLIST)
+
+XDEFINES       += \
+                       $(EOLIST)
+
+XLFLAGS                += \
+                       $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm.  If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME               = utilldap
+
+#
+# This is used by the link '-desc ' directive. 
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION        = Apache $(VERSION_STR) LDAP Authentication Module
+
+#
+# This is used by the '-threadname' directive.  If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME        = UtilLDAP Module
+
+#
+# If this is specified, it will override VERSION value in 
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION            =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM  = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM   = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM  =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS              =  AUTOUNLOAD, PSEUDOPREEMPTION
+
+#
+# If this is specified it will be linked in with the XDCData option in the def 
+# file instead of the default of $(NWOS)/apache.xdc.  XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA         = 
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+       $(OBJDIR)/utilldap.nlm \
+       $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+       $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+       $(OBJDIR)/util_ldap.o \
+       $(OBJDIR)/util_ldap_cache.o \
+       $(OBJDIR)/util_ldap_cache_mgr.o \
+       $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+       libcpre.o \
+       $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+       aprlib \
+       libc \
+       lldapsdk \
+       lldapssl \
+       lldapx \
+       $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+       @$(APR)/aprlib.imp \
+       @$(NWOS)/httpd.imp \
+       @libc.imp \
+       @$(LDAPSDK)/imports/lldapsdk.imp \
+       @$(LDAPSDK)/imports/lldapssl.imp \
+       $(EOLIST)
+#   
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+       ldap_module \
+       util_ldap_connection_find \
+       util_ldap_connection_close \
+       util_ldap_connection_unbind \
+       util_ldap_connection_cleanup \
+       util_ldap_cache_checkuserid \
+       util_ldap_cache_compare \
+       util_ldap_cache_comparedn \
+       util_ldap_ssl_supported \
+       $(EOLIST)
+       
+#   
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+               $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the 
+# correct place.  (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+       copy $(OBJDIR)\*.nlm $(INSTALL)\Apache2\modules\*.*
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/ldap/README.ldap b/modules/ldap/README.ldap
new file mode 100644 (file)
index 0000000..c9445b8
--- /dev/null
@@ -0,0 +1,47 @@
+Quick installation instructions (UNIX):
+
+- Building on generic Unix:
+
+  Add generic ldap support and the TWO ldap modules to the build, like this:
+
+  ./configure --with-ldap --enable-ldap --enable-auth-ldap
+
+  The --with-ldap switches on LDAP library linking in apr-util. Make
+  sure that you have an LDAP client library available such as those
+  from Netscape/iPlanet/Sun One or the OpenLDAP project.
+
+  The --enable-ldap option switches on the LDAP caching module. This
+  module is a support module for other LDAP modules, and is not useful
+  on its own.  This module is required, but caching can be disabled
+  via the configuration directive LDAPCacheEntries.
+
+  The --enable-auth-ldap option switches on the LDAP authentication
+  module.
+
+- Building on AIX:
+
+  The following ./configure line is reported to work for AIX:
+
+    CC=cc_r; export CC
+    CPPFLAGS=-qcpluscmt;export CPPFLAGS
+    ./configure --with-mpm=worker --prefix=/usr/local/apache \
+      --enable-dav=static --enable-dav_fs=static --enable-ssl=static
+      --with-ldap=yes --with-ldap-include=/usr/local/include
+      --with-ldap-lib=/usr/local/lib --enable-ldap=static
+      --enable-auth_ldap=static
+
+
+Quick installation instructions (win32):
+
+1. copy the file srclib\apr-util\include\apr_ldap.hw to apr_ldap.h
+2. the netscape/iplanet ldap libraries are installed in srclib\ldap
+3. Compile the two modules util_ldap and mod_auth_ldap using the dsp files
+4. You get a mod_auth_ldap.so and a util_ldap.so module
+5. Put them in the modules directory, don't forget to copy the
+   nsldap32v50.dll somewhere where apache.exe will find it
+6. Load the two modules in your httpd.conf, like below:
+   LoadModule ldap_module modules/util_ldap.so
+   LoadModule auth_ldap_module modules/mod_auth_ldap.so
+7. Configure the directories as described in the docus.
+
+
diff --git a/modules/ldap/util_ldap.c b/modules/ldap/util_ldap.c
new file mode 100644 (file)
index 0000000..7b58cb9
--- /dev/null
@@ -0,0 +1,1336 @@
+/* Copyright 2001-2004 The Apache Software Foundation
+ *
+ * Licensed 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 <apr_ldap.h>
+#include <apr_strings.h>
+
+#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"
+
+#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
+
+    /* defines for certificate file types
+    */
+#define LDAP_CA_TYPE_UNKNOWN            0
+#define LDAP_CA_TYPE_DER                1
+#define LDAP_CA_TYPE_BASE64             2
+#define LDAP_CA_TYPE_CERT7_DB           3
+
+
+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  "<!DOCTYPE HTML PUBLIC \"-//IETF//" \
+                          "DTD HTML 2.0//EN\">\n"
+#endif
+
+#ifndef DOCTYPE_HTML_3_2
+#define DOCTYPE_HTML_3_2  "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
+                          "DTD HTML 3.2 Final//EN\">\n"
+#endif
+
+#ifndef DOCTYPE_HTML_4_0S
+#define DOCTYPE_HTML_4_0S "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
+                          "DTD HTML 4.0//EN\"\n" \
+                          "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
+#endif
+
+#ifndef DOCTYPE_HTML_4_0T
+#define DOCTYPE_HTML_4_0T "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
+                          "DTD HTML 4.0 Transitional//EN\"\n" \
+                          "\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n"
+#endif
+
+#ifndef DOCTYPE_HTML_4_0F
+#define DOCTYPE_HTML_4_0F "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
+                          "DTD HTML 4.0 Frameset//EN\"\n" \
+                          "\"http://www.w3.org/TR/REC-html40/frameset.dtd\">\n"
+#endif
+
+#define LDAP_CACHE_LOCK() \
+    apr_global_mutex_lock(st->util_ldap_cache_lock)
+#define LDAP_CACHE_UNLOCK() \
+    apr_global_mutex_unlock(st->util_ldap_cache_lock)
+
+
+static void util_ldap_strdup (char **str, const char *newstr)
+{
+    if (*str) {
+        free(*str);
+        *str = NULL;
+    }
+
+    if (newstr) {
+        *str = calloc(1, strlen(newstr)+1);
+        strcpy (*str, 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>
+ *
+ */
+int util_ldap_handler(request_rec *r)
+{
+    util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config, &ldap_module);
+
+    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
+             "<html><head><title>LDAP Cache Information</title></head>\n", r);
+    ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information</h1>\n", r);
+
+    util_ald_cache_display(r, st);
+
+    return OK;
+}
+
+/* ------------------------------------------------------------------ */
+
+
+/*
+ * Closes an LDAP connection by unlocking it. The next time
+ * util_ldap_connection_find() is called this connection will be
+ * available for reuse.
+ */
+LDAP_DECLARE(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...
+     */
+
+    /* mark our connection as available for reuse */
+
+#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, and during pool cleanup.
+ */
+LDAP_DECLARE_NONSTD(apr_status_t) util_ldap_connection_unbind(void *param)
+{
+    util_ldap_connection_t *ldc = param;
+
+    if (ldc) {
+        if (ldc->ldap) {
+            ldap_unbind_s(ldc->ldap);
+            ldc->ldap = NULL;
+        }
+        ldc->bound = 0;
+    }
+
+    return APR_SUCCESS;
+}
+
+
+/*
+ * Clean up an LDAP connection by unbinding and unlocking the connection.
+ * This function is registered with the pool cleanup function - causing
+ * the LDAP connections to be shut down cleanly on graceful restart.
+ */
+LDAP_DECLARE_NONSTD(apr_status_t) util_ldap_connection_cleanup(void *param)
+{
+    util_ldap_connection_t *ldc = param;
+
+    if (ldc) {
+
+        /* unbind and disconnect from the LDAP server */
+        util_ldap_connection_unbind(ldc);
+
+        /* free the username and password */
+        if (ldc->bindpw) {
+            free((void*)ldc->bindpw);
+        }
+        if (ldc->binddn) {
+            free((void*)ldc->binddn);
+        }
+
+        /* unlock this entry */
+        util_ldap_connection_close(ldc);
+    
+    }
+
+    return APR_SUCCESS;
+}
+
+
+/*
+ * 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
+ */
+LDAP_DECLARE(int) util_ldap_connection_open(request_rec *r, 
+                                            util_ldap_connection_t *ldc)
+{
+    int result = 0;
+    int failures = 0;
+    int version  = LDAP_VERSION3;
+
+    util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
+                                r->server->module_config, &ldap_module);
+
+    /* If the connection is already bound, return
+    */
+    if (ldc->bound)
+    {
+        ldc->reason = "LDAP: connection open successful (already bound)";
+        return LDAP_SUCCESS;
+    }
+
+    /* create the ldap session handle
+    */
+    if (NULL == ldc->ldap)
+    {
+        apr_ldap_err_t *result = NULL;
+        int rc = apr_ldap_init(r->pool,
+                               &(ldc->ldap),
+                               ldc->host,
+                               ldc->port,
+                               ldc->secure,
+                               &(result));
+
+        if (result != NULL) {
+            ldc->reason = result->reason;
+        }
+
+        if (NULL == ldc->ldap)
+        {
+            ldc->bound = 0;
+            if (NULL == ldc->reason)
+                ldc->reason = "LDAP: ldap initialization failed";
+            return(-1);
+        }
+
+        /* Set the alias dereferencing option */
+        ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &(ldc->deref));
+
+        /* always default to LDAP V3 */
+        ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
+
+    }
+
+
+    /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is
+     * returned.  Break out of the loop on Success or any other error.
+     *
+     * 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.
+      */
+    for (failures=0; failures<10; failures++)
+    {
+        result = ldap_simple_bind_s(ldc->ldap, ldc->binddn, ldc->bindpw);
+        if (LDAP_SERVER_DOWN != result)
+            break;
+    }
+
+    /* free the handle if there was an error
+    */
+    if (LDAP_SUCCESS != result)
+    {
+        ldap_unbind_s(ldc->ldap);
+        ldc->ldap = NULL;
+        ldc->bound = 0;
+        ldc->reason = "LDAP: ldap_simple_bind_s() failed";
+    }
+    else {
+        ldc->bound = 1;
+        ldc->reason = "LDAP: connection open successful";
+    }
+
+    return(result);
+}
+
+
+/*
+ * 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.
+ */
+LDAP_DECLARE(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 secure )
+{
+    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);
+
+
+#if APR_HAS_THREADS
+    /* mutex lock this function */
+    if (!st->mutex) {
+        apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
+    }
+    apr_thread_mutex_lock(st->mutex);
+#endif
+
+    /* 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 == secure)) {
+
+            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 == secure)) {
+
+                /* the bind credentials have changed */
+                l->bound = 0;
+                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 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));
+#if APR_HAS_THREADS
+        apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, st->pool);
+        apr_thread_mutex_lock(l->lock);
+#endif
+        l->pool = st->pool;
+        l->bound = 0;
+        l->host = apr_pstrdup(st->pool, host);
+        l->port = port;
+        l->deref = deref;
+        util_ldap_strdup((char**)&(l->binddn), binddn);
+        util_ldap_strdup((char**)&(l->bindpw), bindpw);
+        l->secure = secure;
+
+        /* add the cleanup to the pool */
+        apr_pool_cleanup_register(l->pool, l,
+                                  util_ldap_connection_cleanup,
+                                  apr_pool_cleanup_null);
+
+        if (p) {
+            p->next = l;
+        }
+        else {
+            st->connections = l;
+        }
+    }
+
+#if APR_HAS_THREADS
+    apr_thread_mutex_unlock(st->mutex);
+#endif
+    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.
+ */
+LDAP_DECLARE(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);
+
+    /* get cache entry (or create one) */
+    LDAP_CACHE_LOCK();
+
+    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();
+
+    /* 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();
+    
+        /* 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();
+            ldc->reason = "DN Comparison TRUE (cached)";
+            return LDAP_COMPARE_TRUE;
+        }
+    
+        /* unlock this read lock */
+        LDAP_CACHE_UNLOCK();
+    }
+
+start_over:
+    if (failures++ > 10) {
+       /* too many failures */
+        return result;
+    }
+
+    /* make a server connection */
+    if (LDAP_SUCCESS != (result = util_ldap_connection_open(r, ldc))) {
+       /* connect to server failed */
+        return result;
+    }
+
+    /* search for reqdn */
+    if ((result = ldap_search_ext_s(ldc->ldap, reqdn, LDAP_SCOPE_BASE, 
+                                   "(objectclass=*)", NULL, 1, 
+                                   NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) {
+        ldc->reason = "DN Comparison ldap_search_ext_s() failed with server down";
+        util_ldap_connection_unbind(ldc);
+        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 {
+        if (curl) {
+            /* compare successful - add to the compare cache */
+            LDAP_CACHE_LOCK();
+            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();
+        }
+        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 
+ *
+ */
+LDAP_DECLARE(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 = 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();
+    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();
+
+    if (curl) {
+        /* make a comparison to the cache */
+        LDAP_CACHE_LOCK();
+        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;
+    
+        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 */
+                LDAP_CACHE_UNLOCK();
+                if (LDAP_COMPARE_TRUE == compare_nodep->result) {
+                    ldc->reason = "Comparison true (cached)";
+                    return compare_nodep->result;
+                }
+                else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
+                    ldc->reason = "Comparison false (cached)";
+                    return compare_nodep->result;
+                }
+                else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
+                    ldc->reason = "Comparison no such attribute (cached)";
+                    return compare_nodep->result;
+                }
+                else {
+                    ldc->reason = "Comparison undefined (cached)";
+                    return compare_nodep->result;
+                }
+            }
+        }
+        /* unlock this read lock */
+        LDAP_CACHE_UNLOCK();
+    }
+
+start_over:
+    if (failures++ > 10) {
+        /* too many failures */
+        return result;
+    }
+    if (LDAP_SUCCESS != (result = util_ldap_connection_open(r, ldc))) {
+        /* connect failed */
+        return result;
+    }
+
+    if ((result = ldap_compare_s(ldc->ldap, dn, attrib, value))
+        == LDAP_SERVER_DOWN) { 
+        /* connection failed - try again */
+        ldc->reason = "ldap_compare_s() failed with server down";
+        util_ldap_connection_unbind(ldc);
+        goto start_over;
+    }
+
+    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();
+            the_compare_node.lastcompare = curtime;
+            the_compare_node.result = result;
+
+            /* 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)) {
+
+                util_ald_cache_insert(curl->compare_cache, &the_compare_node);
+            }
+            else {
+                compare_nodep->lastcompare = curtime;
+                compare_nodep->result = result;
+            }
+            LDAP_CACHE_UNLOCK();
+        }
+        if (LDAP_COMPARE_TRUE == result) {
+            ldc->reason = "Comparison true (adding to cache)";
+            return LDAP_COMPARE_TRUE;
+        }
+        else if (LDAP_COMPARE_FALSE == result) {
+            ldc->reason = "Comparison false (adding to cache)";
+            return LDAP_COMPARE_FALSE;
+        }
+        else {
+            ldc->reason = "Comparison no such attribute (adding to cache)";
+            return LDAP_NO_SUCH_ATTRIBUTE;
+        }
+    }
+    return result;
+}
+
+LDAP_DECLARE(int) util_ldap_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 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();
+    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();
+
+    if (curl) {
+        LDAP_CACHE_LOCK();
+        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;
+                *retvals = search_nodep->vals;
+                LDAP_CACHE_UNLOCK();
+                ldc->reason = "Authentication successful (cached)";
+                return LDAP_SUCCESS;
+            }
+        }
+        /* unlock this read lock */
+        LDAP_CACHE_UNLOCK();
+    }
+
+    /* 
+     * 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(r, ldc))) {
+        return result;
+    }
+
+    /* try do the search */
+    if ((result = ldap_search_ext_s(ldc->ldap,
+                                   basedn, scope, 
+                                   filter, attrs, 0, 
+                                   NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) {
+        ldc->reason = "ldap_search_ext_s() for user failed with server down";
+        util_ldap_connection_unbind(ldc);
+        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) 
+    {
+        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;
+    }
+
+    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(r->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";
+        ldap_msgfree(res);
+        util_ldap_connection_unbind(ldc);
+        goto start_over;
+    }
+
+    /* failure? if so - return */
+    if (result != LDAP_SUCCESS) {
+        ldc->reason = "ldap_simple_bind_s() to check user credentials failed";
+        ldap_msgfree(res);
+        util_ldap_connection_unbind(ldc);
+        return result;
+    }
+    else {
+        /*
+         * We have just bound the connection to a different user and password
+         * combination, which might be reused unintentionally next time this
+         * connection is used from the connection pool. To ensure no confusion,
+         * we mark the connection as unbound.
+         */
+        ldc->bound = 0;
+    }
+
+    /*
+     * Get values for the provided attributes.
+     */
+    if (attrs) {
+        int k = 0;
+        int i = 0;
+        while (attrs[k++]);
+        vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
+        while (attrs[i]) {
+            char **values;
+            int j = 0;
+            char *str = NULL;
+            /* get values */
+            values = ldap_get_values(ldc->ldap, entry, attrs[i]);
+            while (values && values[j]) {
+                str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL) : apr_pstrdup(r->pool, values[j]);
+                j++;
+            }
+            ldap_value_free(values);
+            vals[i] = str;
+            i++;
+        }
+        *retvals = vals;
+    }
+
+    /*                 
+     * Add the new username to the search cache.
+     */
+    if (curl) {
+        LDAP_CACHE_LOCK();
+        the_search_node.username = filter;
+        the_search_node.dn = *binddn;
+        the_search_node.bindpw = bindpw;
+        the_search_node.lastbind = apr_time_now();
+        the_search_node.vals = vals;
+
+        /* Search again to make sure that another thread didn't ready insert this node
+           into the cache before we got here. If it does exist then update the lastbind */
+        search_nodep = util_ald_cache_fetch(curl->search_cache, &the_search_node);
+        if ((search_nodep == NULL) || 
+            (strcmp(*binddn, search_nodep->dn) != 0) || (strcmp(bindpw, search_nodep->bindpw) != 0)) {
+
+            util_ald_cache_insert(curl->search_cache, &the_search_node);
+        }
+        else {
+            search_nodep->lastbind = the_search_node.lastbind;
+        }
+        LDAP_CACHE_UNLOCK();
+    }
+    ldap_msgfree(res);
+
+    ldc->reason = "Authentication successful";
+    return LDAP_SUCCESS;
+}
+
+
+/*
+ * Reports if ssl support is enabled 
+ *
+ * 1 = enabled, 0 = not enabled
+ */
+LDAP_DECLARE(int) util_ldap_ssl_supported(request_rec *r)
+{
+   util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
+                                r->server->module_config, &ldap_module);
+
+   return(st->ssl_support);
+}
+
+
+/* ---------------------------------------- */
+/* 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, 
+                 "[%" APR_PID_T_FMT "] ldap cache: Setting shared memory "
+                 " cache size to %" APR_SIZE_T_FMT " bytes.", 
+                 getpid(), st->cache_bytes);
+
+    return NULL;
+}
+
+static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy, const char *file)
+{
+    util_ldap_state_t *st = 
+        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
+                                                  &ldap_module);
+
+    if (file) {
+        st->cache_file = ap_server_root_relative(st->pool, file);
+    }
+    else {
+        st->cache_file = NULL;
+    }
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
+                 "LDAP cache: Setting shared memory cache file to %s bytes.", 
+                 st->cache_file);
+
+    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) * 1000000;
+
+    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) * 1000000;
+
+    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;
+}
+
+static const char *util_ldap_set_cert_auth(cmd_parms *cmd, void *dummy, const char *file)
+{
+    util_ldap_state_t *st = 
+        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
+                                                 &ldap_module);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+    if (err != NULL) {
+        return err;
+    }
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
+                      "LDAP: SSL trusted certificate authority file - %s", 
+                       file);
+
+    st->cert_auth_file = ap_server_root_relative(cmd->pool, file);
+
+    return(NULL);
+}
+
+
+static const char *util_ldap_set_cert_type(cmd_parms *cmd, void *dummy, const char *Type)
+{
+    util_ldap_state_t *st = 
+    (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
+                                              &ldap_module);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+    if (err != NULL) {
+        return err;
+    }
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
+                      "LDAP: SSL trusted certificate authority file type - %s", 
+                       Type);
+
+    if (0 == strcmp("DER_FILE", Type))
+        st->cert_file_type = LDAP_CA_TYPE_DER;
+
+    else if (0 == strcmp("BASE64_FILE", Type))
+        st->cert_file_type = LDAP_CA_TYPE_BASE64;
+
+    else if (0 == strcmp("CERT7_DB_PATH", Type))
+        st->cert_file_type = LDAP_CA_TYPE_CERT7_DB;
+
+    else
+        st->cert_file_type = LDAP_CA_TYPE_UNKNOWN;
+
+    return(NULL);
+}
+
+
+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 = 600000000;
+    st->search_cache_size = 1024;
+    st->compare_cache_ttl = 600000000;
+    st->compare_cache_size = 1024;
+    st->connections = NULL;
+    st->cert_auth_file = NULL;
+    st->cert_file_type = LDAP_CA_TYPE_UNKNOWN;
+    st->ssl_support = 0;
+
+    return st;
+}
+
+static apr_status_t util_ldap_cleanup_module(void *data)
+{
+
+    server_rec *s = data;
+    util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
+        s->module_config, &ldap_module);
+    
+    if (st->ssl_support) {
+        apr_ldap_ssl_deinit();
+    }
+
+    return APR_SUCCESS;
+
+}
+
+static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, 
+                                 apr_pool_t *ptemp, server_rec *s)
+{
+    int rc = LDAP_SUCCESS;
+    apr_status_t result;
+    char buf[MAX_STRING_LEN];
+    server_rec *s_vhost;
+    util_ldap_state_t *st_vhost;
+
+    util_ldap_state_t *st =
+        (util_ldap_state_t *)ap_get_module_config(s->module_config, &ldap_module);
+
+    void *data;
+    const char *userdata_key = "util_ldap_init";
+
+    /* util_ldap_post_config() will be called twice. Don't bother
+     * going through all of the initialization on the first call
+     * because it will just be thrown away.*/
+    apr_pool_userdata_get(&data, userdata_key, s->process->pool);
+    if (!data) {
+        apr_pool_userdata_set((const void *)1, userdata_key,
+                               apr_pool_cleanup_null, s->process->pool);
+
+#if APR_HAS_SHARED_MEMORY
+        /* If the cache file already exists then delete it.  Otherwise we are
+         * going to run into problems creating the shared memory. */
+        if (st->cache_file) {
+            char *lck_file = apr_pstrcat (st->pool, st->cache_file, ".lck", NULL);
+            apr_file_remove(st->cache_file, ptemp);
+            apr_file_remove(lck_file, ptemp);
+        }
+#endif
+        return OK;
+    }
+
+#if APR_HAS_SHARED_MEMORY
+    /* initializing cache if shared memory size is not zero and we already don't have shm address */
+    if (!st->cache_shm && st->cache_bytes > 0) {
+#endif
+        result = util_ldap_cache_init(p, st);
+        if (result != APR_SUCCESS) {
+            apr_strerror(result, buf, sizeof(buf));
+            ap_log_error(APLOG_MARK, APLOG_ERR, result, s,
+                         "LDAP cache: error while creating a shared memory segment: %s", buf);
+        }
+
+
+#if APR_HAS_SHARED_MEMORY
+        if (st->cache_file) {
+            st->lock_file = apr_pstrcat (st->pool, st->cache_file, ".lck", NULL);
+        }
+        else
+#endif
+            st->lock_file = ap_server_root_relative(st->pool, tmpnam(NULL));
+
+        result = apr_global_mutex_create(&st->util_ldap_cache_lock, st->lock_file, APR_LOCK_DEFAULT, st->pool);
+        if (result != APR_SUCCESS) {
+            return result;
+        }
+
+        /* merge config in all vhost */
+        s_vhost = s->next;
+        while (s_vhost) {
+            st_vhost = (util_ldap_state_t *)ap_get_module_config(s_vhost->module_config, &ldap_module);
+
+#if APR_HAS_SHARED_MEMORY
+            st_vhost->cache_shm = st->cache_shm;
+            st_vhost->cache_rmm = st->cache_rmm;
+            st_vhost->cache_file = st->cache_file;
+            ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, result, s, 
+                         "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp for VHOST: %s",
+                         st->cache_shm, st->cache_rmm, s_vhost->server_hostname);
+#endif
+            st_vhost->lock_file = st->lock_file;
+            s_vhost = s_vhost->next;
+        }
+#if APR_HAS_SHARED_MEMORY
+    }
+    else {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "LDAP cache: LDAPSharedCacheSize is zero, disabling shared memory cache");
+    }
+#endif
+    
+    /* log the LDAP SDK used 
+     */
+    {
+        apr_ldap_err_t *result = NULL;
+        apr_ldap_info(p, &(result));
+        if (result != NULL) {
+            ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, result->reason);
+        }
+    }
+
+    apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
+                              util_ldap_cleanup_module); 
+
+    /* initialize SSL support if requested
+    */
+    if (st->cert_auth_file) {
+
+        apr_ldap_err_t *result = NULL;
+        int rc = apr_ldap_ssl_init(p,
+                                   st->cert_auth_file,
+                                   st->cert_file_type,
+                                   &(result));
+
+        if (LDAP_SUCCESS == rc) {
+            st->ssl_support = 1;
+        }
+        else if (NULL != result) {
+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, result->reason);
+            st->ssl_support = 0;
+        }
+
+    }
+      
+    /* log SSL status - If SSL isn't available it isn't necessarily
+     * an error because the modules asking for LDAP connections 
+     * may not ask for SSL support
+     */
+    if (st->ssl_support) {
+       ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, 
+                         "LDAP: SSL support available" );
+    }
+    else {
+       ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, 
+                         "LDAP: SSL support unavailable" );
+    }
+    
+    return(OK);
+}
+
+static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
+{
+    apr_status_t sts;
+    util_ldap_state_t *st =
+        (util_ldap_state_t *)ap_get_module_config(s->module_config, &ldap_module);
+
+    sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock, st->lock_file, p);
+    if (sts != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s, "failed to init caching lock in child process");
+        return;
+    }
+    else {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, s, 
+                     "INIT global mutex %s in child %d ", st->lock_file, getpid());
+    }
+}
+
+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("LDAPSharedCacheFile", util_ldap_set_cache_file, NULL, RSRC_CONF,
+                  "Sets the file of the shared memory cache."
+                  "Nothing means disable the shared memory cache."),
+
+    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)."),
+
+    AP_INIT_TAKE1("LDAPTrustedCA", util_ldap_set_cert_auth, NULL, RSRC_CONF,
+                  "Sets the file containing the trusted Certificate Authority certificate. "
+                  "Used to validate the LDAP server certificate for SSL connections."),
+
+    AP_INIT_TAKE1("LDAPTrustedCAType", util_ldap_set_cert_type, NULL, RSRC_CONF,
+                 "Specifies the type of the Certificate Authority file.  "
+                 "The following types are supported:  "
+                 "    DER_FILE      - file in binary DER format "
+                 "    BASE64_FILE   - file in Base64 format "
+                 "    CERT7_DB_PATH - Netscape certificate database file "),
+    {NULL}
+};
+
+static void util_ldap_register_hooks(apr_pool_t *p)
+{
+    ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+    ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_child_init(util_ldap_child_init, 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.dsp b/modules/ldap/util_ldap.dsp
new file mode 100644 (file)
index 0000000..318ce54
--- /dev/null
@@ -0,0 +1,140 @@
+# Microsoft Developer Studio Project File - Name="util_ldap" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=util_ldap - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE 
+!MESSAGE NMAKE /f "util_ldap.mak".
+!MESSAGE 
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "util_ldap.mak" CFG="util_ldap - Win32 Release"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "util_ldap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "util_ldap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "util_ldap - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /Zi /O2 /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fd"Release\util_ldap_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /machine:I386 /out:"Release/util_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,util_ldap.so
+# ADD LINK32 kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /debug /machine:I386 /out:"Release/util_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,util_ldap.so /opt:ref
+
+!ELSEIF  "$(CFG)" == "util_ldap - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /GX /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fd"Debug\util_ldap_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /machine:I386 /out:"Debug/util_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,util_ldap.so
+# ADD LINK32 kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /debug /machine:I386 /out:"Debug/util_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,util_ldap.so
+
+!ENDIF 
+
+# Begin Target
+
+# Name "util_ldap - Win32 Release"
+# Name "util_ldap - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\util_ldap.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap.rc
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap_cache_mgr.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\win32ver.awk
+
+!IF  "$(CFG)" == "util_ldap - Win32 Release"
+
+# PROP Ignore_Default_Tool 1
+# Begin Custom Build - Creating Version Resource
+InputPath=..\..\build\win32\win32ver.awk
+
+".\util_ldap.rc" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+       awk -f ../../build/win32/win32ver.awk util_ldap.so "LDAP Utility Module for Apache" ../../include/ap_release.h > .\util_ldap.rc
+
+# End Custom Build
+
+!ELSEIF  "$(CFG)" == "util_ldap - Win32 Debug"
+
+# PROP Ignore_Default_Tool 1
+# Begin Custom Build - Creating Version Resource
+InputPath=..\..\build\win32\win32ver.awk
+
+".\util_ldap.rc" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+       awk -f ../../build/win32/win32ver.awk util_ldap.so "LDAP Utility Module for Apache" ../../include/ap_release.h > .\util_ldap.rc
+
+# End Custom Build
+
+!ENDIF 
+
+# End Source File
+# End Target
+# End Project
diff --git a/modules/ldap/util_ldap_cache.c b/modules/ldap/util_ldap_cache.c
new file mode 100644 (file)
index 0000000..47547d6
--- /dev/null
@@ -0,0 +1,429 @@
+/* Copyright 2001-2004 The Apache Software Foundation
+ *
+ * Licensed 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_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 <apr_ldap.h>
+#include <apr_strings.h>
+#include "util_ldap.h"
+#include "util_ldap_cache.h"
+
+#if APR_HAS_LDAP
+
+#if APR_HAS_SHARED_MEMORY
+#define MODLDAP_SHMEM_CACHE "/tmp/mod_ldap_cache"
+#endif
+
+/* ------------------------------------------------------------------ */
+
+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(util_ald_cache_t *cache, void *c)
+{
+    util_url_node_t *n = (util_url_node_t *)c;
+    util_url_node_t *node = (util_url_node_t *)util_ald_alloc(cache, sizeof(util_url_node_t));
+
+    if (node) {
+        if (!(node->url = util_ald_strdup(cache, n->url))) {
+            util_ald_free(cache, node->url);
+            return NULL;
+        }
+        node->search_cache = n->search_cache;
+        node->compare_cache = n->compare_cache;
+        node->dn_compare_cache = n->dn_compare_cache;
+        return node;
+    }
+    else {
+        return NULL;
+    }
+}
+
+void util_ldap_url_node_free(util_ald_cache_t *cache, void *n)
+{
+    util_url_node_t *node = (util_url_node_t *)n;
+
+    util_ald_free(cache, 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(cache, node);
+}
+
+void util_ldap_url_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+    util_url_node_t *node = (util_url_node_t *)n;
+    char date_str[APR_CTIME_LEN+1];
+    char *buf;
+    const char *type_str;
+    util_ald_cache_t *cache_node;
+    int x;
+
+    for (x=0;x<3;x++) {
+        switch (x) {
+            case 0:
+                cache_node = node->search_cache;
+                type_str = "Searches";
+                break;
+            case 1:
+                cache_node = node->compare_cache;
+                type_str = "Compares";
+                break;
+            case 2:
+                cache_node = node->dn_compare_cache;
+                type_str = "DN Compares";
+                break;
+        }
+        
+        if (cache_node->marktime) {
+            apr_ctime(date_str, cache_node->marktime);
+        }
+        else 
+            date_str[0] = 0;
+
+        buf = apr_psprintf(r->pool, 
+                 "<tr valign='top'>"
+                 "<td nowrap>%s (%s)</td>"
+                 "<td nowrap>%ld</td>"
+                 "<td nowrap>%ld</td>"
+                 "<td nowrap>%ld</td>"
+                 "<td nowrap>%ld</td>"
+                 "<td nowrap>%s</td>"
+                 "<tr>",
+             node->url,
+             type_str,
+             cache_node->size,
+             cache_node->maxentries,
+             cache_node->numentries,
+             cache_node->fullmark,
+             date_str);
+    
+        ap_rputs(buf, r);
+    }
+
+}
+
+/* ------------------------------------------------------------------ */
+
+/* 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(util_ald_cache_t *cache, void *c)
+{
+    util_search_node_t *node = (util_search_node_t *)c;
+    util_search_node_t *newnode = util_ald_alloc(cache, sizeof(util_search_node_t));
+
+    /* safety check */
+    if (newnode) {
+
+        /* copy vals */
+        if (node->vals) {
+            int k = 0;
+            int i = 0;
+            while (node->vals[k++]);
+            if (!(newnode->vals = util_ald_alloc(cache, sizeof(char *) * (k+1)))) {
+                util_ldap_search_node_free(cache, newnode);
+                return NULL;
+            }
+            while (node->vals[i]) {
+                if (!(newnode->vals[i] = util_ald_strdup(cache, node->vals[i]))) {
+                    util_ldap_search_node_free(cache, newnode);
+                    return NULL;
+                }
+                i++;
+            }
+        }
+        else {
+            newnode->vals = NULL;
+        }
+        if (!(newnode->username = util_ald_strdup(cache, node->username)) ||
+            !(newnode->dn = util_ald_strdup(cache, node->dn)) ) {
+            util_ldap_search_node_free(cache, newnode);
+            return NULL;
+        }
+        if(node->bindpw) {
+            if(!(newnode->bindpw = util_ald_strdup(cache, node->bindpw))) {
+                util_ldap_search_node_free(cache, newnode);
+                return NULL;
+            }
+        } else {
+            newnode->bindpw = NULL;
+        }
+        newnode->lastbind = node->lastbind;
+
+    }
+    return (void *)newnode;
+}
+
+void util_ldap_search_node_free(util_ald_cache_t *cache, void *n)
+{
+    int i = 0;
+    util_search_node_t *node = (util_search_node_t *)n;
+    if (node->vals) {
+        while (node->vals[i]) {
+            util_ald_free(cache, node->vals[i++]);
+        }
+        util_ald_free(cache, node->vals);
+    }
+    util_ald_free(cache, node->username);
+    util_ald_free(cache, node->dn);
+    util_ald_free(cache, node->bindpw);
+    util_ald_free(cache, node);
+}
+
+void util_ldap_search_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+    util_search_node_t *node = (util_search_node_t *)n;
+    char date_str[APR_CTIME_LEN+1];
+    char *buf;
+
+    apr_ctime(date_str, node->lastbind);
+
+    buf = apr_psprintf(r->pool, 
+             "<tr valign='top'>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<tr>",
+         node->username,
+         node->dn,
+         date_str);
+
+    ap_rputs(buf, r);
+}
+
+/* ------------------------------------------------------------------ */
+
+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(util_ald_cache_t *cache, void *c)
+{
+    util_compare_node_t *n = (util_compare_node_t *)c;
+    util_compare_node_t *node = (util_compare_node_t *)util_ald_alloc(cache, sizeof(util_compare_node_t));
+
+    if (node) {
+        if (!(node->dn = util_ald_strdup(cache, n->dn)) ||
+            !(node->attrib = util_ald_strdup(cache, n->attrib)) ||
+            !(node->value = util_ald_strdup(cache, n->value))) {
+            util_ldap_compare_node_free(cache, node);
+            return NULL;
+        }
+        node->lastcompare = n->lastcompare;
+        node->result = n->result;
+        return node;
+    }
+    else {
+        return NULL;
+    }
+}
+
+void util_ldap_compare_node_free(util_ald_cache_t *cache, void *n)
+{
+    util_compare_node_t *node = (util_compare_node_t *)n;
+    util_ald_free(cache, node->dn);
+    util_ald_free(cache, node->attrib);
+    util_ald_free(cache, node->value);
+    util_ald_free(cache, node);
+}
+
+void util_ldap_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+    util_compare_node_t *node = (util_compare_node_t *)n;
+    char date_str[APR_CTIME_LEN+1];
+    char *buf, *cmp_result;
+
+    apr_ctime(date_str, node->lastcompare);
+
+    if (node->result == LDAP_COMPARE_TRUE) {
+        cmp_result = "LDAP_COMPARE_TRUE";
+    }
+    else if (node->result == LDAP_COMPARE_FALSE) {
+        cmp_result = "LDAP_COMPARE_FALSE";
+    }
+    else {
+        cmp_result = apr_itoa(r->pool, node->result);
+    }
+
+    buf = apr_psprintf(r->pool, 
+             "<tr valign='top'>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<tr>",
+         node->dn,
+         node->attrib,
+         node->value,
+         date_str,
+         cmp_result);
+
+    ap_rputs(buf, r);
+}
+
+/* ------------------------------------------------------------------ */
+
+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(util_ald_cache_t *cache, 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(cache, sizeof(util_dn_compare_node_t));
+    if (node) {
+        if (!(node->reqdn = util_ald_strdup(cache, n->reqdn)) ||
+            !(node->dn = util_ald_strdup(cache, n->dn))) {
+            util_ldap_dn_compare_node_free(cache, node);
+            return NULL;
+        }
+        return node;
+    }
+    else {
+        return NULL;
+    }
+}
+
+void util_ldap_dn_compare_node_free(util_ald_cache_t *cache, void *n)
+{
+    util_dn_compare_node_t *node = (util_dn_compare_node_t *)n;
+    util_ald_free(cache, node->reqdn);
+    util_ald_free(cache, node->dn);
+    util_ald_free(cache, node);
+}
+
+void util_ldap_dn_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+    util_dn_compare_node_t *node = (util_dn_compare_node_t *)n;
+    char *buf;
+
+    buf = apr_psprintf(r->pool, 
+             "<tr valign='top'>"
+             "<td nowrap>%s</td>"
+             "<td nowrap>%s</td>"
+             "<tr>",
+         node->reqdn,
+         node->dn);
+
+    ap_rputs(buf, r);
+}
+
+
+/* ------------------------------------------------------------------ */
+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)
+{
+    util_ldap_state_t *st = (util_ldap_state_t *)data;
+
+    util_ald_destroy_cache(st->util_ldap_cache);
+#if APR_HAS_SHARED_MEMORY
+    if (st->cache_rmm != NULL) {
+        apr_rmm_destroy (st->cache_rmm);
+        st->cache_rmm = NULL;
+    }
+    if (st->cache_shm != NULL) {
+        apr_status_t result = apr_shm_destroy(st->cache_shm);
+        st->cache_shm = NULL;
+        apr_file_remove(st->cache_file, st->pool);
+        return result;
+    }
+#endif
+    return APR_SUCCESS;
+}
+
+apr_status_t util_ldap_cache_init(apr_pool_t *pool, util_ldap_state_t *st)
+{
+#if APR_HAS_SHARED_MEMORY
+    apr_status_t result;
+
+    result = apr_shm_create(&st->cache_shm, st->cache_bytes, st->cache_file, st->pool);
+    if (result == APR_EEXIST) {
+        /*
+         * The cache could have already been created (i.e. we may be a child process).  See
+         * if we can attach to the existing shared memory
+         */
+        result = apr_shm_attach(&st->cache_shm, st->cache_file, st->pool);
+    } 
+    if (result != APR_SUCCESS) {
+        return result;
+    }
+
+    /* This will create a rmm "handler" to get into the shared memory area */
+    apr_rmm_init(&st->cache_rmm, NULL, (void *)apr_shm_baseaddr_get(st->cache_shm), st->cache_bytes, st->pool);
+#endif
+
+    apr_pool_cleanup_register(st->pool, st , util_ldap_cache_module_kill, apr_pool_cleanup_null);
+
+    st->util_ldap_cache =
+        util_ald_create_cache(st,
+                              util_ldap_url_node_hash,
+                              util_ldap_url_node_compare,
+                              util_ldap_url_node_copy,
+                              util_ldap_url_node_free,
+                              util_ldap_url_node_display);
+    return APR_SUCCESS;
+}
+
+
+#endif /* APR_HAS_LDAP */
diff --git a/modules/ldap/util_ldap_cache.h b/modules/ldap/util_ldap_cache.h
new file mode 100644 (file)
index 0000000..4fc9f34
--- /dev/null
@@ -0,0 +1,191 @@
+/* Copyright 2001-2004 The Apache Software Foundation
+ *
+ * Licensed 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.
+ */
+
+#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 */
+#if APR_HAS_LDAP
+
+
+/*
+ * LDAP Cache Manager
+ */
+
+#if APR_HAS_SHARED_MEMORY
+#include <apr_shm.h>
+#include <apr_rmm.h> /* EDD */
+#endif
+
+typedef struct util_cache_node_t {
+    void *payload;             /* Pointer to the payload */
+    apr_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 util_ald_cache_t;
+
+struct util_ald_cache {
+    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 */
+    apr_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)(util_ald_cache_t *cache, void *); /* Func to alloc mem and copy payload to new mem */
+    void (*free)(util_ald_cache_t *cache, void *); /* Func to free mem used by the payload */
+    void (*display)(request_rec *r, util_ald_cache_t *cache, void *); /* Func to display the payload contents */
+    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 */
+    apr_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 */
+
+#if APR_HAS_SHARED_MEMORY
+    apr_shm_t *shm_addr;
+    apr_rmm_t *rmm_addr;
+#endif
+
+};
+
+#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 */
+    const char **vals;                 /* Values of queried attributes */
+} 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;
+    int result;
+} 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(util_ald_cache_t *cache, void *c);
+void util_ldap_url_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_url_node_display(request_rec *r, util_ald_cache_t *cache, 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(util_ald_cache_t *cache, void *c);
+void util_ldap_search_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_search_node_display(request_rec *r, util_ald_cache_t *cache, 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(util_ald_cache_t *cache, void *c);
+void util_ldap_compare_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_compare_node_display(request_rec *r, util_ald_cache_t *cache, 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(util_ald_cache_t *cache, void *c);
+void util_ldap_dn_compare_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_dn_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n);
+
+
+/* util_ldap_cache_mgr.c */
+
+/* Cache alloc and free function, dealing or not with shm */
+void util_ald_free(util_ald_cache_t *cache, const void *ptr);
+void *util_ald_alloc(util_ald_cache_t *cache, unsigned long size);
+const char *util_ald_strdup(util_ald_cache_t *cache, const char *s);
+
+/* Cache managing function */
+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(util_ldap_state_t *st,
+                                unsigned long (*hashfunc)(void *), 
+                                int (*comparefunc)(void *, void *),
+                                void * (*copyfunc)(util_ald_cache_t *cache, void *),
+                                void (*freefunc)(util_ald_cache_t *cache, void *),
+                                void (*displayfunc)(request_rec *r, util_ald_cache_t *cache, 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(request_rec *r, util_ald_cache_t *cache, char *name, char *id);
+
+#endif /* APR_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 (file)
index 0000000..31b297e
--- /dev/null
@@ -0,0 +1,740 @@
+/* Copyright 2001-2004 The Apache Software Foundation
+ *
+ * Licensed 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_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 <apr_ldap.h>
+#include "util_ldap.h"
+#include "util_ldap_cache.h"
+#include <apr_strings.h>
+
+#if APR_HAS_LDAP
+
+/* only here until strdup is gone */
+#include <string.h>
+
+/* here till malloc is gone */
+#include <stdlib.h>
+
+static const unsigned long 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(util_ald_cache_t *cache, const void *ptr)
+{
+#if APR_HAS_SHARED_MEMORY
+    if (cache->rmm_addr) {
+        if (ptr)
+            /* Free in shared memory */
+            apr_rmm_free(cache->rmm_addr, apr_rmm_offset_get(cache->rmm_addr, (void *)ptr));
+    }
+    else {
+        if (ptr)
+            /* Cache shm is not used */
+            free((void *)ptr);
+    }
+#else
+    if (ptr)
+        free((void *)ptr);
+#endif
+}
+
+void *util_ald_alloc(util_ald_cache_t *cache, unsigned long size)
+{
+    if (0 == size)
+        return NULL;
+#if APR_HAS_SHARED_MEMORY
+    if (cache->rmm_addr) {
+        /* allocate from shared memory */
+        apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, size);
+        return block ? (void *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
+    }
+    else {
+        /* Cache shm is not used */
+        return (void *)calloc(sizeof(char), size);
+    }
+#else
+    return (void *)calloc(sizeof(char), size);
+#endif
+}
+
+const char *util_ald_strdup(util_ald_cache_t *cache, const char *s)
+{
+#if APR_HAS_SHARED_MEMORY
+    if (cache->rmm_addr) {
+        /* allocate from shared memory */
+        apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, strlen(s)+1);
+        char *buf = block ? (char *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
+        if (buf) {
+            strcpy(buf, s);
+            return buf;
+        }
+        else {
+            return NULL;
+        }
+    } else {
+        /* Cache shm is not used */
+        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)
+{
+    unsigned long i;
+    util_cache_node_t *p, *q;
+    apr_time_t t;
+
+    if (!cache)
+        return;
+  
+    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)(cache, p->payload);
+                util_ald_free(cache, 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, *newcurl;
+    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,
+                      util_ldap_search_node_hash,
+                      util_ldap_search_node_compare,
+                      util_ldap_search_node_copy,
+                      util_ldap_search_node_free,
+                      util_ldap_search_node_display);
+    compare_cache = util_ald_create_cache(st,
+                      util_ldap_compare_node_hash,
+                      util_ldap_compare_node_compare,
+                      util_ldap_compare_node_copy,
+                      util_ldap_compare_node_free,
+                      util_ldap_compare_node_display);
+    dn_compare_cache = util_ald_create_cache(st,
+                      util_ldap_dn_compare_node_hash,
+                      util_ldap_dn_compare_node_compare,
+                      util_ldap_dn_compare_node_copy,
+                      util_ldap_dn_compare_node_free,
+                      util_ldap_dn_compare_node_display);
+
+    /* check that all the caches initialised successfully */
+    if (search_cache && compare_cache && dn_compare_cache) {
+
+        /* The contents of this structure will be duplicated in shared
+           memory during the insert.  So use stack memory rather than
+           pool memory to avoid a memory leak. */
+        memset (&curl, 0, 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;
+
+        newcurl = util_ald_cache_insert(st->util_ldap_cache, &curl);
+
+    }
+
+    return newcurl;
+}
+
+
+util_ald_cache_t *util_ald_create_cache(util_ldap_state_t *st,
+                                unsigned long (*hashfunc)(void *), 
+                                int (*comparefunc)(void *, void *),
+                                void * (*copyfunc)(util_ald_cache_t *cache, void *),
+                                void (*freefunc)(util_ald_cache_t *cache, void *),
+                                void (*displayfunc)(request_rec *r, util_ald_cache_t *cache, void *))
+{
+    util_ald_cache_t *cache;
+    unsigned long i;
+
+    if (st->search_cache_size <= 0)
+        return NULL;
+
+#if APR_HAS_SHARED_MEMORY
+    if (!st->cache_rmm) {
+        return NULL;
+    }
+    else {
+        apr_rmm_off_t block = apr_rmm_calloc(st->cache_rmm, sizeof(util_ald_cache_t));
+        cache = block ? (util_ald_cache_t *)apr_rmm_addr_get(st->cache_rmm, block) : NULL;
+    }
+#else
+    cache = (util_ald_cache_t *)calloc(sizeof(util_ald_cache_t), 1);
+#endif
+    if (!cache)
+        return NULL;
+
+#if APR_HAS_SHARED_MEMORY
+    cache->rmm_addr = st->cache_rmm;
+    cache->shm_addr = st->cache_shm;
+#endif
+    cache->maxentries = st->search_cache_size;
+    cache->numentries = 0;
+    cache->size = st->search_cache_size / 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, cache->size * sizeof(util_cache_node_t *));
+    if (!cache->nodes) {
+        util_ald_free(cache, 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->display = displayfunc;
+
+    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)
+{
+    unsigned long 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)(cache, p->payload);
+           util_ald_free(cache, p);
+           p = q;
+        }
+    }
+    util_ald_free(cache, cache->nodes);
+    util_ald_free(cache, cache);
+}
+
+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;
+
+    /* sanity check */
+    if (cache == NULL || payload == NULL) {
+        return NULL;
+    }
+
+    /* check if we are full - if so, try purge */
+    if (cache->numentries >= cache->maxentries) {
+        util_ald_cache_purge(cache);
+        if (cache->numentries >= cache->maxentries) {
+            /* if the purge was not effective, we leave now to avoid an overflow */
+            return NULL;
+        }
+    }
+
+    /* should be safe to add an entry */
+    if ((node = (util_cache_node_t *)util_ald_alloc(cache, sizeof(util_cache_node_t))) == NULL) {
+        return NULL;
+    }
+
+    /* populate the entry */
+    cache->inserts++;
+    hashval = (*cache->hash)(payload) % cache->size;
+    node->add_time = apr_time_now();
+    node->payload = (*cache->copy)(cache, payload);
+    node->next = cache->nodes[hashval];
+    cache->nodes[hashval] = node;
+
+    /* if we reach the full mark, note the time we did so
+     * for the benefit of the purge function
+     */
+    if (++cache->numentries == cache->fullmark) {
+        cache->marktime=apr_time_now();
+    }
+
+    return node->payload;
+}
+
+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)(cache, p->payload);
+    util_ald_free(cache, p);
+    cache->numentries--;
+}
+
+char *util_ald_cache_display_stats(request_rec *r, util_ald_cache_t *cache, char *name, char *id)
+{
+    unsigned long i;
+    int totchainlen = 0;
+    int nchains = 0;
+    double chainlen;
+    util_cache_node_t *n;
+    char *buf, *buf2;
+    apr_pool_t *p = r->pool;
+
+    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;
+
+    if (id) {
+        buf2 = apr_psprintf(p, 
+                 "<a href=\"%s?%s\">%s</a>",
+             r->uri,
+             id,
+             name);
+    }
+    else {
+        buf2 = name;
+    }
+
+    buf = apr_psprintf(p, 
+             "<tr valign='top'>"
+             "<td nowrap>%s</td>"
+             "<td align='right' nowrap>%lu (%.0f%% full)</td>"
+             "<td align='right'>%.1f</td>"
+             "<td align='right'>%lu/%lu</td>"
+             "<td align='right'>%.0f%%</td>"
+             "<td align='right'>%lu/%lu</td>",
+         buf2,
+         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"
+                 "<td align='right'>%lu</td>\n"
+                 "<td align='right' nowrap>%s</td>\n", 
+             buf,
+             cache->numpurges,
+             str_ctime);
+    }
+    else {
+        buf = apr_psprintf(p, 
+                 "%s<td colspan='2' align='center'>(none)</td>\n",
+             buf);
+    }
+
+    buf = apr_psprintf(p, "%s<td align='right'>%.2g</td>\n</tr>", buf, cache->avg_purgetime);
+
+    return buf;
+}
+
+char *util_ald_cache_display(request_rec *r, util_ldap_state_t *st)
+{
+    unsigned long i,j;
+    char *buf, *t1, *t2, *t3;
+    char *id1, *id2, *id3;
+    char *argfmt = "cache=%s&id=%d&off=%d";
+    char *scanfmt = "cache=%4s&id=%u&off=%u%1s";
+    apr_pool_t *pool = r->pool;
+    util_cache_node_t *p = NULL;
+    util_url_node_t *n = NULL;
+
+    util_ald_cache_t *util_ldap_cache = st->util_ldap_cache;
+
+
+    if (!util_ldap_cache) {
+        return "<tr valign='top'><td nowrap colspan=7>Cache has not been enabled/initialised.</td></tr>";
+    }
+
+    if (r->args && strlen(r->args)) {
+        char cachetype[5], lint[2];
+        unsigned int id, off;
+        char date_str[APR_CTIME_LEN+1];
+
+        if ((3 == sscanf(r->args, scanfmt, cachetype, &id, &off, lint)) &&
+            (id < util_ldap_cache->size)) {
+
+            if ((p = util_ldap_cache->nodes[id]) != NULL) {
+                n = (util_url_node_t *)p->payload;
+                buf = (char*)n->url;
+            }
+            else {
+                buf = "";
+            }
+
+            ap_rputs(apr_psprintf(r->pool, 
+                     "<p>\n"
+                     "<table border='0'>\n"
+                     "<tr>\n"
+                     "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name:</b></font></td>"
+                     "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s (%s)</b></font></td>"
+                     "</tr>\n"
+                     "</table>\n</p>\n",
+                 buf,
+                 cachetype[0] == 'm'? "Main" : 
+                                  (cachetype[0] == 's' ? "Search" : 
+                                   (cachetype[0] == 'c' ? "Compares" : "DNCompares"))), r);
+            
+            switch (cachetype[0]) {
+                case 'm':
+                    if (util_ldap_cache->marktime) {
+                        apr_ctime(date_str, util_ldap_cache->marktime);
+                    }
+                    else
+                        date_str[0] = 0;
+
+                    ap_rputs(apr_psprintf(r->pool, 
+                            "<p>\n"
+                            "<table border='0'>\n"
+                            "<tr>\n"
+                            "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size:</b></font></td>"
+                            "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+                            "</tr>\n"
+                            "<tr>\n"
+                            "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries:</b></font></td>"
+                            "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+                            "</tr>\n"
+                            "<tr>\n"
+                            "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries:</b></font></td>"
+                            "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+                            "</tr>\n"
+                            "<tr>\n"
+                            "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark:</b></font></td>"
+                            "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+                            "</tr>\n"
+                            "<tr>\n"
+                            "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time:</b></font></td>"
+                            "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s</b></font></td>"
+                            "</tr>\n"
+                            "</table>\n</p>\n",
+                        util_ldap_cache->size,
+                        util_ldap_cache->maxentries,
+                        util_ldap_cache->numentries,
+                        util_ldap_cache->fullmark,
+                        date_str), r);
+
+                    ap_rputs("<p>\n"
+                             "<table border='0'>\n"
+                             "<tr bgcolor='#000000'>\n"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP URL</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time</b></font></td>"
+                             "</tr>\n", r
+                            );
+                    for (i=0; i < util_ldap_cache->size; ++i) {
+                        for (p = util_ldap_cache->nodes[i]; p != NULL; p = p->next) {
+
+                            (*util_ldap_cache->display)(r, util_ldap_cache, p->payload);
+                        }
+                    }
+                    ap_rputs("</table>\n</p>\n", r);
+                    
+
+                    break;
+                case 's':
+                    ap_rputs("<p>\n"
+                             "<table border='0'>\n"
+                             "<tr bgcolor='#000000'>\n"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP Filter</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>User Name</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Bind</b></font></td>"
+                             "</tr>\n", r
+                            );
+                    for (i=0; i < n->search_cache->size; ++i) {
+                        for (p = n->search_cache->nodes[i]; p != NULL; p = p->next) {
+
+                            (*n->search_cache->display)(r, n->search_cache, p->payload);
+                        }
+                    }
+                    ap_rputs("</table>\n</p>\n", r);
+                    break;
+                case 'c':
+                    ap_rputs("<p>\n"
+                             "<table border='0'>\n"
+                             "<tr bgcolor='#000000'>\n"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>DN</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Attribute</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Value</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Compare</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Result</b></font></td>"
+                             "</tr>\n", r
+                            );
+                    for (i=0; i < n->compare_cache->size; ++i) {
+                        for (p = n->compare_cache->nodes[i]; p != NULL; p = p->next) {
+
+                            (*n->compare_cache->display)(r, n->compare_cache, p->payload);
+                        }
+                    }
+                    ap_rputs("</table>\n</p>\n", r);
+                    break;
+                case 'd':
+                    ap_rputs("<p>\n"
+                             "<table border='0'>\n"
+                             "<tr bgcolor='#000000'>\n"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Require DN</b></font></td>"
+                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Actual DN</b></font></td>"
+                             "</tr>\n", r
+                            );
+                    for (i=0; i < n->dn_compare_cache->size; ++i) {
+                        for (p = n->dn_compare_cache->nodes[i]; p != NULL; p = p->next) {
+
+                            (*n->dn_compare_cache->display)(r, n->dn_compare_cache, p->payload);
+                        }
+                    }
+                    ap_rputs("</table>\n</p>\n", r);
+                    break;
+                default:
+                    break;
+            }
+
+        }
+    }
+    else {
+        ap_rputs("<p>\n"
+                 "<table border='0'>\n"
+                 "<tr bgcolor='#000000'>\n"
+                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name</b></font></td>"
+                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Entries</b></font></td>"
+                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg. Chain Len.</b></font></td>"
+                 "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Hits</b></font></td>"
+                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Ins/Rem</b></font></td>"
+                 "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Purges</b></font></td>"
+                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg Purge Time</b></font></td>"
+                 "</tr>\n", r
+                );
+
+
+        id1 = apr_psprintf(pool, argfmt, "main", 0, 0);
+        buf = util_ald_cache_display_stats(r, st->util_ldap_cache, "LDAP URL Cache", id1);
+    
+        for (i=0; i < util_ldap_cache->size; ++i) {
+            for (p = util_ldap_cache->nodes[i],j=0; p != NULL; p = p->next,j++) {
+    
+                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);
+                id1 = apr_psprintf(pool, argfmt, "srch", i, j);
+                id2 = apr_psprintf(pool, argfmt, "cmpr", i, j);
+                id3 = apr_psprintf(pool, argfmt, "dncp", i, j);
+    
+                buf = apr_psprintf(pool, "%s\n\n"
+                                         "%s\n\n"
+                                         "%s\n\n"
+                                         "%s\n\n",
+                                         buf,
+                                         util_ald_cache_display_stats(r, n->search_cache, t1, id1),
+                                         util_ald_cache_display_stats(r, n->compare_cache, t2, id2),
+                                         util_ald_cache_display_stats(r, n->dn_compare_cache, t3, id3)
+                                  );
+            }
+        }
+        ap_rputs(buf, r);
+        ap_rputs("</table>\n</p>\n", r);
+    }
+
+    return buf;
+}
+
+#endif /* APR_HAS_LDAP */