/* Copyright © 1998, 1999 Enbridge Pipelines Inc. Copyright © 1999-2006 Dave Carrigan All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Apache itself. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The copyright holder of this module can not be held liable for any general, special, incidental or consequential damages arising out of the use of the module. $Id: auth_ldap_cache.c,v 1.10 2001/02/16 23:06:20 dave Exp $ */ #include "auth_ldap.h" extern module MODULE_VAR_EXPORT auth_ldap_module; /* ------------------------------------------------------------------ */ unsigned long auth_ldap_url_node_hash(void *n) { url_node *node = (url_node *)n; return ald_hash_string(1, node->url); } int auth_ldap_url_node_compare(void *a, void *b) { url_node *na = (url_node *)a; url_node *nb = (url_node *)b; return(strcmp(na->url, nb->url) == 0); } void * auth_ldap_url_node_copy(void *c) { url_node *n = (url_node *)c; url_node *node = (url_node *)ald_alloc(sizeof(url_node)); node->url = ald_strdup(n->url); node->search_cache = n->search_cache; node->compare_cache = n->compare_cache; node->dn_compare_cache = n->dn_compare_cache; return node; } void auth_ldap_url_node_free(void *n) { url_node *node = (url_node *)n; ald_free(node->url); ald_destroy_cache(node->search_cache); ald_destroy_cache(node->compare_cache); ald_destroy_cache(node->dn_compare_cache); ald_free(node); } /* ------------------------------------------------------------------ */ /* Cache functions for search nodes */ unsigned long auth_ldap_search_node_hash(void *n) { search_node *node = (search_node *)n; return ald_hash_string(1, ((search_node *)(node))->username); } int auth_ldap_search_node_compare(void *a, void *b) { return(strcmp(((search_node *)a)->username, ((search_node *)b)->username) == 0); } void * auth_ldap_search_node_copy(void *c) { search_node *node = (search_node *)c; search_node *newnode = ald_alloc(sizeof(search_node)); newnode->username = ald_strdup(node->username); newnode->dn = ald_strdup(node->dn); newnode->bindpw = ald_strdup(node->bindpw); newnode->lastbind = node->lastbind; return (void *)newnode; } void auth_ldap_search_node_free(void *n) { search_node *node = (search_node *)n; ald_free(node->username); ald_free(node->dn); ald_free(node->bindpw); ald_free(node); } /* ------------------------------------------------------------------ */ unsigned long auth_ldap_compare_node_hash(void *n) { compare_node *node = (compare_node *)n; return ald_hash_string(3, node->dn, node->attrib, node->value); } int auth_ldap_compare_node_compare(void *a, void *b) { compare_node *na = (compare_node *)a; compare_node *nb = (compare_node *)b; return (strcmp(na->dn, nb->dn) == 0 && strcmp(na->attrib, nb->attrib) == 0 && strcmp(na->value, nb->value) == 0); } void * auth_ldap_compare_node_copy(void *c) { compare_node *n = (compare_node *)c; compare_node *node = (compare_node *)ald_alloc(sizeof(compare_node)); node->dn = ald_strdup(n->dn); node->attrib = ald_strdup(n->attrib); node->value = ald_strdup(n->value); node->lastcompare = n->lastcompare; return node; } void auth_ldap_compare_node_free(void *n) { compare_node *node = (compare_node *)n; ald_free(node->dn); ald_free(node->attrib); ald_free(node->value); ald_free(node); } /* ------------------------------------------------------------------ */ unsigned long auth_ldap_dn_compare_node_hash(void *n) { return ald_hash_string(1, ((dn_compare_node *)n)->reqdn); } int auth_ldap_dn_compare_node_compare(void *a, void *b) { return (strcmp(((dn_compare_node *)a)->reqdn, ((dn_compare_node *)b)->reqdn) == 0); } void * auth_ldap_dn_compare_node_copy(void *c) { dn_compare_node *n = (dn_compare_node *)c; dn_compare_node *node = (dn_compare_node *)ald_alloc(sizeof(dn_compare_node)); node->reqdn = ald_strdup(n->reqdn); node->dn = ald_strdup(n->dn); return node; } void auth_ldap_dn_compare_node_free(void *n) { dn_compare_node *node = (dn_compare_node *)n; ald_free(node->reqdn); ald_free(node->dn); ald_free(node); } /* ------------------------------------------------------------------ */ /* * 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 AuthLDAPCompareDNOnServer is * false, just does an ordinary strcmp. * * The mutex for the ldap cache should already be acquired. */ int auth_ldap_comparedn(const char *dn, const char *reqdn, request_rec *r, url_node *curl) { int result; dn_compare_node *node; dn_compare_node newnode; auth_ldap_config_rec *sec; auth_ldap_server_conf *conf; int failures = 0; LDAPMessage *res, *entry; char *searchdn; int ret; conf = (auth_ldap_server_conf *)ap_get_module_config(r->server->module_config, &auth_ldap_module); sec = (auth_ldap_config_rec *) ap_get_module_config(r->per_dir_config, &auth_ldap_module); if (!sec->compare_dn_on_server) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Comparing the two DNs (doing local compare)", (int)getpid()); return strcmp(dn, reqdn) == 0; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Comparing the two DNs (using server-side compare)", (int)getpid()); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Searching for `%s'/`%s' in the dn compare cache", (int)getpid(), dn, reqdn); newnode.reqdn = (char *)reqdn; node = ald_cache_fetch(curl->dn_compare_cache, &newnode); GETMUTEX(sec->ldc->mtx); if (node != NULL) { /* If it's in the cache, it's good */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Found one", (int)getpid()); RELMUTEX(sec->ldc->mtx); return 1; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} No match in the dn compare cache", (int)getpid()); start_over: if (failures++ > 10) { auth_ldap_log_reason(r, "Too many failures connecting to LDAP server"); RELMUTEX(sec->ldc->mtx); return 0; } if (!auth_ldap_connect_to_server(r)) { RELMUTEX(sec->ldc->mtx); return 0; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Doing LDAP compare of uncached %s=%s", (int)getpid(), reqdn, dn); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} LDAP OP: search", (int)getpid()); if ((result = ldap_search_ext_s(sec->ldc->ldap, const_cast(reqdn), LDAP_SCOPE_BASE, "(objectclass=*)", NULL, 1, NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Server is down; reconnecting and starting over", (int)getpid()); auth_ldap_free_connection(r, 1); goto start_over; } if (result != LDAP_SUCCESS) { auth_ldap_log_reason(r, "LDAP search for %s failed: LDAP error: %s", reqdn, ldap_err2string(result)); RELMUTEX(sec->ldc->mtx); return 0; } entry = ldap_first_entry(sec->ldc->ldap, res); searchdn = ldap_get_dn(sec->ldc->ldap, entry); ldap_msgfree(res); if (strcmp(dn, searchdn) != 0) { RELMUTEX(sec->ldc->mtx); ret = 0; } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Adding `%s'/`%s' to dn compare cache", (int)getpid(), dn, reqdn); newnode.reqdn = (char *)reqdn; newnode.dn = (char *)dn; ald_cache_insert(curl->dn_compare_cache, &newnode); RELMUTEX(sec->ldc->mtx); ret = 1; } ldap_memfree(searchdn); return ret; } /* * 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 * * The mutex for the ldap cache should already be acquired. */ int auth_ldap_compare(const char *dn, const char *attrib, const char *value, request_rec *r, ald_cache *cache) { int result; compare_node *compare_nodep; compare_node the_compare_node; auth_ldap_config_rec *sec; auth_ldap_server_conf *conf; time_t curtime; int failures = 0; conf = (auth_ldap_server_conf *)ap_get_module_config(r->server->module_config, &auth_ldap_module); sec = (auth_ldap_config_rec *) ap_get_module_config(r->per_dir_config, &auth_ldap_module); time(&curtime); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Searching cache for `%s'/`%s' and dn `%s'", (int)getpid(), attrib, value, dn); the_compare_node.dn = (char *)dn; the_compare_node.attrib = (char *)attrib; the_compare_node.value = (char *)value; compare_nodep = ald_cache_fetch(cache, &the_compare_node); if (compare_nodep != NULL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Found it...", (int)getpid()); if (curtime - compare_nodep->lastcompare > conf->compare_cache_ttl) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} ...but it's too old.", (int)getpid()); ald_cache_remove(cache, compare_nodep); } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} ...and it's good.", (int)getpid()); RELMUTEX(sec->ldc->mtx); return 1; } } GETMUTEX(sec->ldc->mtx); start_over: if (failures++ > 10) { auth_ldap_log_reason(r, "Too many failures connecting to LDAP server"); RELMUTEX(sec->ldc->mtx); return 0; } if (!auth_ldap_connect_to_server(r)) { RELMUTEX(sec->ldc->mtx); return 0; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Doing LDAP compare of %s=%s in entry %s", (int)getpid(), attrib, value, dn); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} LDAP OP: compare", (int)getpid()); if ((result = ldap_compare_s(sec->ldc->ldap, const_cast(dn), const_cast(attrib), const_cast(value))) == LDAP_SERVER_DOWN) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Server is down; reconnecting and starting over", (int)getpid()); auth_ldap_free_connection(r, 1); goto start_over; } if (result == LDAP_COMPARE_TRUE) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Compare succeeded; caching result", (int)getpid()); the_compare_node.lastcompare = curtime; ald_cache_insert(cache, &the_compare_node); RELMUTEX(sec->ldc->mtx); return 1; } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Compare failed", (int)getpid()); RELMUTEX(sec->ldc->mtx); return 0; } } /* * 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 int auth_ldap_display_info(request_rec *r) { int i; char buf[MAX_STRING_LEN]; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Entering auth_ldap_display_info", (int)getpid()); r->allowed |= (1 << M_GET); if (r->method_number != M_GET) return DECLINED; r->content_type = "text/html"; ap_send_http_header(r); if (r->header_only) return 0; ap_hard_timeout("send auth_ldap info", r); ap_rputs(DOCTYPE_HTML_3_2 "Auth_LDAP Information\n", r); ap_rputs("

Auth_LDAP Information

\n", r); if (auth_ldap_cache == NULL) { ap_rputs("URL cache is NULL", r); ap_kill_timeout(r); return 0; } ap_rputs("

\n" "\n" "\n" "" "" "" "" "" "" "" "\n", r ); ald_cache_display_stats(auth_ldap_cache, r, "LDAP URL Cache"); for (i=0; i < auth_ldap_cache->size; ++i) { cache_node *p; for (p = auth_ldap_cache->nodes[i]; p != NULL; p = p->next) { url_node *n; n = (url_node *)p->payload; ap_snprintf(buf, sizeof(buf), "%s (Searches)", n->url); ald_cache_display_stats(n->search_cache, r, buf); ap_snprintf(buf, sizeof(buf), "%s (Compares)", n->url); ald_cache_display_stats(n->compare_cache, r, buf); ap_snprintf(buf, sizeof(buf), "%s (DNCompares)", n->url); ald_cache_display_stats(n->dn_compare_cache, r, buf); } } ap_rputs("
Cache NameEntriesAvg. Chain Len.HitsIns/RemPurgesAvg Purge Time
\n

\n", r); ap_kill_timeout(r); return 0; }