From: Bradley Nicholes Date: Wed, 18 Aug 2004 22:18:39 +0000 (+0000) Subject: Move util_ldap out of experimental and into ldap. X-Git-Tag: STRIKER_2_1_0_RC1~84 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=871dcc3f67fef7398b0d4da139aa504b8009abc9;p=apache Move util_ldap out of experimental and into ldap. 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 --- diff --git a/modules/ldap/NWGNUmakefile b/modules/ldap/NWGNUmakefile new file mode 100644 index 0000000000..aa15476e4e --- /dev/null +++ b/modules/ldap/NWGNUmakefile @@ -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 index 0000000000..c9445b8153 --- /dev/null +++ b/modules/ldap/README.ldap @@ -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 index 0000000000..7b58cb94f9 --- /dev/null +++ b/modules/ldap/util_ldap.c @@ -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 +#include + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "util_ldap.h" +#include "util_ldap_cache.h" + +#if APR_HAVE_UNISTD_H +#include +#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 "\n" +#endif + +#ifndef DOCTYPE_HTML_3_2 +#define DOCTYPE_HTML_3_2 "\n" +#endif + +#ifndef DOCTYPE_HTML_4_0S +#define DOCTYPE_HTML_4_0S "\n" +#endif + +#ifndef DOCTYPE_HTML_4_0T +#define DOCTYPE_HTML_4_0T "\n" +#endif + +#ifndef DOCTYPE_HTML_4_0F +#define DOCTYPE_HTML_4_0F "\n" +#endif + +#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: + * + * + * SetHandler ldap-status + * + * + */ +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 + "LDAP Cache Information\n", r); + ap_rputs("

LDAP Cache Information

\n", r); + + util_ald_cache_display(r, st); + + return OK; +} + +/* ------------------------------------------------------------------ */ + + +/* + * Closes an LDAP connection by unlocking it. The next time + * 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 index 0000000000..318ce5476c --- /dev/null +++ b/modules/ldap/util_ldap.dsp @@ -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 index 0000000000..47547d623a --- /dev/null +++ b/modules/ldap/util_ldap_cache.c @@ -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 +#include +#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, + "" + "%s (%s)" + "%ld" + "%ld" + "%ld" + "%ld" + "%s" + "", + 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, + "" + "%s" + "%s" + "%s" + "", + 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, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + 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, + "" + "%s" + "%s" + "", + 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 index 0000000000..4fc9f3463e --- /dev/null +++ b/modules/ldap/util_ldap_cache.h @@ -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 +#include /* 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 index 0000000000..31b297e552 --- /dev/null +++ b/modules/ldap/util_ldap_cache_mgr.c @@ -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 +#include "util_ldap.h" +#include "util_ldap_cache.h" +#include + +#if APR_HAS_LDAP + +/* only here until strdup is gone */ +#include + +/* here till malloc is gone */ +#include + +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, + "%s", + r->uri, + id, + name); + } + else { + buf2 = name; + } + + buf = apr_psprintf(p, + "" + "%s" + "%lu (%.0f%% full)" + "%.1f" + "%lu/%lu" + "%.0f%%" + "%lu/%lu", + 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" + "%lu\n" + "%s\n", + buf, + cache->numpurges, + str_ctime); + } + else { + buf = apr_psprintf(p, + "%s(none)\n", + buf); + } + + buf = apr_psprintf(p, "%s%.2g\n", buf, cache->avg_purgetime); + + return buf; +} + +char *util_ald_cache_display(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 "Cache has not been enabled/initialised."; + } + + 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, + "

\n" + "\n" + "\n" + "" + "" + "\n" + "
Cache Name:%s (%s)
\n

\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, + "

\n" + "\n" + "\n" + "" + "" + "\n" + "\n" + "" + "" + "\n" + "\n" + "" + "" + "\n" + "\n" + "" + "" + "\n" + "\n" + "" + "" + "\n" + "
Size:%ld
Max Entries:%ld
# Entries:%ld
Full Mark:%ld
Full Mark Time:%s
\n

\n", + util_ldap_cache->size, + util_ldap_cache->maxentries, + util_ldap_cache->numentries, + util_ldap_cache->fullmark, + date_str), r); + + ap_rputs("

\n" + "\n" + "\n" + "" + "" + "" + "" + "" + "" + "\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("
LDAP URLSizeMax Entries# EntriesFull MarkFull Mark Time
\n

\n", r); + + + break; + case 's': + ap_rputs("

\n" + "\n" + "\n" + "" + "" + "" + "\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("
LDAP FilterUser NameLast Bind
\n

\n", r); + break; + case 'c': + ap_rputs("

\n" + "\n" + "\n" + "" + "" + "" + "" + "" + "\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("
DNAttributeValueLast CompareResult
\n

\n", r); + break; + case 'd': + ap_rputs("

\n" + "\n" + "\n" + "" + "" + "\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("
Require DNActual DN
\n

\n", r); + break; + default: + break; + } + + } + } + else { + ap_rputs("

\n" + "\n" + "\n" + "" + "" + "" + "" + "" + "" + "" + "\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("
Cache NameEntriesAvg. Chain Len.HitsIns/RemPurgesAvg Purge Time
\n

\n", r); + } + + return buf; +} + +#endif /* APR_HAS_LDAP */