1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * util_ldap.c: LDAP things
20 * Original code from auth_ldap module for Apache v1.3:
21 * Copyright 1998, 1999 Enbridge Pipelines Inc.
22 * Copyright 1999-2001 Dave Carrigan
26 #include "http_config.h"
27 #include "http_core.h"
29 #include "http_protocol.h"
30 #include "http_request.h"
31 #include "util_ldap.h"
32 #include "util_ldap_cache.h"
34 #include <apr_strings.h>
41 #error mod_ldap requires APR-util to have LDAP support built in
44 #ifdef AP_NEED_SET_MUTEX_PERMS
48 /* Default define for ldap functions that need a SIZELIMIT but
49 * do not have the define
50 * XXX This should be removed once a supporting #define is
51 * released through APR-Util.
53 #ifndef APR_LDAP_SIZELIMIT
54 #define APR_LDAP_SIZELIMIT -1
57 module AP_MODULE_DECLARE_DATA ldap_module;
59 #define LDAP_CACHE_LOCK() do { \
60 if (st->util_ldap_cache_lock) \
61 apr_global_mutex_lock(st->util_ldap_cache_lock); \
64 #define LDAP_CACHE_UNLOCK() do { \
65 if (st->util_ldap_cache_lock) \
66 apr_global_mutex_unlock(st->util_ldap_cache_lock); \
69 static void util_ldap_strdup (char **str, const char *newstr)
77 *str = strdup(newstr);
85 * This handler generates a status page about the current performance of
86 * the LDAP cache. It is enabled as follows:
88 * <Location /ldap-status>
89 * SetHandler ldap-status
93 static int util_ldap_handler(request_rec *r)
95 util_ldap_state_t *st = (util_ldap_state_t *)
96 ap_get_module_config(r->server->module_config,
99 r->allowed |= (1 << M_GET);
100 if (r->method_number != M_GET)
103 if (strcmp(r->handler, "ldap-status")) {
107 ap_set_content_type(r, "text/html");
112 ap_rputs(DOCTYPE_HTML_3_2
113 "<html><head><title>LDAP Cache Information</title></head>\n", r);
114 ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information"
117 util_ald_cache_display(r, st);
122 /* ------------------------------------------------------------------ */
126 * Closes an LDAP connection by unlocking it. The next time
127 * uldap_connection_find() is called this connection will be
128 * available for reuse.
130 static void uldap_connection_close(util_ldap_connection_t *ldc)
136 * Is it safe leaving bound connections floating around between the
137 * different modules? Keeping the user bound is a performance boost,
138 * but it is also a potential security problem - maybe.
140 * For now we unbind the user when we finish with a connection, but
141 * we don't have to...
144 /* mark our connection as available for reuse */
147 apr_thread_mutex_unlock(ldc->lock);
153 * Destroys an LDAP connection by unbinding and closing the connection to
154 * the LDAP server. It is used to bring the connection back to a known
155 * state after an error, and during pool cleanup.
157 static apr_status_t uldap_connection_unbind(void *param)
159 util_ldap_connection_t *ldc = param;
163 ldap_unbind_s(ldc->ldap);
174 * Clean up an LDAP connection by unbinding and unlocking the connection.
176 static apr_status_t uldap_connection_cleanup(void *param)
178 util_ldap_connection_t *ldc = param;
182 /* unbind and disconnect from the LDAP server */
183 uldap_connection_unbind(ldc);
185 /* free the username and password */
187 free((void*)ldc->bindpw);
190 free((void*)ldc->binddn);
193 /* unlock this entry */
194 uldap_connection_close(ldc);
201 static int uldap_connection_init(request_rec *r,
202 util_ldap_connection_t *ldc )
204 int rc = 0, ldap_option = 0;
205 int version = LDAP_VERSION3;
206 apr_ldap_err_t *result = NULL;
207 struct timeval timeOut = {10,0}; /* 10 second connection timeout */
208 util_ldap_state_t *st =
209 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
212 /* Since the host will include a port if the default port is not used,
213 * always specify the default ports for the port parameter. This will
214 * allow a host string that contains multiple hosts the ability to mix
215 * some hosts with ports and some without. All hosts which do not
216 * specify a port will use the default port.
218 apr_ldap_init(r->pool, &(ldc->ldap),
220 APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
225 if (result != NULL && result->rc) {
226 ldc->reason = result->reason;
229 if (NULL == ldc->ldap)
232 if (NULL == ldc->reason) {
233 ldc->reason = "LDAP: ldap initialization failed";
236 ldc->reason = result->reason;
241 /* always default to LDAP V3 */
242 ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
244 /* set client certificates */
245 if (!apr_is_empty_array(ldc->client_certs)) {
246 apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
247 ldc->client_certs, &(result));
248 if (LDAP_SUCCESS != result->rc) {
249 uldap_connection_unbind( ldc );
250 ldc->reason = result->reason;
255 /* switch on SSL/TLS */
256 if (APR_LDAP_NONE != ldc->secure) {
257 apr_ldap_set_option(r->pool, ldc->ldap,
258 APR_LDAP_OPT_TLS, &ldc->secure, &(result));
259 if (LDAP_SUCCESS != result->rc) {
260 uldap_connection_unbind( ldc );
261 ldc->reason = result->reason;
266 /* Set the alias dereferencing option */
267 ldap_option = ldc->deref;
268 ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);
270 /*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
271 #ifdef APR_LDAP_OPT_VERIFY_CERT
272 apr_ldap_set_option(r->pool, ldc->ldap,
273 APR_LDAP_OPT_VERIFY_CERT, &(st->verify_svr_cert), &(result));
275 #if defined(LDAPSSL_VERIFY_SERVER)
276 if (st->verify_svr_cert) {
277 result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
280 result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
282 #elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
283 /* This is not a per-connection setting so just pass NULL for the
284 Ldap connection handle */
285 if (st->verify_svr_cert) {
286 int i = LDAP_OPT_X_TLS_DEMAND;
287 result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
290 int i = LDAP_OPT_X_TLS_NEVER;
291 result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
296 #ifdef LDAP_OPT_NETWORK_TIMEOUT
297 if (st->connectionTimeout > 0) {
298 timeOut.tv_sec = st->connectionTimeout;
301 if (st->connectionTimeout >= 0) {
302 rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
303 (void *)&timeOut, &(result));
304 if (APR_SUCCESS != rc) {
305 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
306 "LDAP: Could not set the connection timeout");
315 * Connect to the LDAP server and binds. Does not connect if already
316 * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
318 * Returns LDAP_SUCCESS on success; and an error code on failure
320 static int uldap_connection_open(request_rec *r,
321 util_ldap_connection_t *ldc)
326 /* sanity check for NULL */
331 /* If the connection is already bound, return
335 ldc->reason = "LDAP: connection open successful (already bound)";
339 /* create the ldap session handle
341 if (NULL == ldc->ldap)
343 rc = uldap_connection_init( r, ldc );
344 if (LDAP_SUCCESS != rc)
351 /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is
352 * returned. Break out of the loop on Success or any other error.
354 * NOTE: Looping is probably not a great idea. If the server isn't
355 * responding the chances it will respond after a few tries are poor.
356 * However, the original code looped and it only happens on
357 * the error condition.
359 for (failures=0; failures<10; failures++)
361 rc = ldap_simple_bind_s(ldc->ldap,
363 (char *)ldc->bindpw);
364 if (LDAP_SERVER_DOWN != rc) {
366 } else if (failures == 5) {
367 /* attempt to init the connection once again */
368 uldap_connection_unbind( ldc );
369 rc = uldap_connection_init( r, ldc );
370 if (LDAP_SUCCESS != rc)
377 /* free the handle if there was an error
379 if (LDAP_SUCCESS != rc)
381 uldap_connection_unbind(ldc);
382 ldc->reason = "LDAP: ldap_simple_bind_s() failed";
386 ldc->reason = "LDAP: connection open successful";
394 * Compare client certificate arrays.
396 * Returns 1 on compare failure, 0 otherwise.
398 static int compare_client_certs(apr_array_header_t *srcs,
399 apr_array_header_t *dests)
402 struct apr_ldap_opt_tls_cert_t *src, *dest;
404 /* arrays both NULL? if so, then equal */
405 if (srcs == NULL && dests == NULL) {
409 /* arrays different length or either NULL? If so, then not equal */
410 if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
414 /* run an actual comparison */
415 src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
416 dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
417 for (i = 0; i < srcs->nelts; i++) {
418 if (strcmp(src[i].path, dest[i].path) ||
419 strcmp(src[i].password, dest[i].password) ||
420 src[i].type != dest[i].type) {
425 /* if we got here, the cert arrays were identical */
432 * Find an existing ldap connection struct that matches the
433 * provided ldap connection parameters.
435 * If not found in the cache, a new ldc structure will be allocated
436 * from st->pool and returned to the caller. If found in the cache,
437 * a pointer to the existing ldc structure will be returned.
439 static util_ldap_connection_t *
440 uldap_connection_find(request_rec *r,
441 const char *host, int port,
442 const char *binddn, const char *bindpw,
443 deref_options deref, int secure)
445 struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
446 int secureflag = secure;
448 util_ldap_state_t *st =
449 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
454 /* mutex lock this function */
455 apr_thread_mutex_lock(st->mutex);
458 if (secure < APR_LDAP_NONE) {
459 secureflag = st->secure;
462 /* Search for an exact connection match in the list that is not
465 for (l=st->connections,p=NULL; l; l=l->next) {
467 if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
469 if ( (l->port == port) && (strcmp(l->host, host) == 0)
470 && ((!l->binddn && !binddn) || (l->binddn && binddn
471 && !strcmp(l->binddn, binddn)))
472 && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
473 && !strcmp(l->bindpw, bindpw)))
474 && (l->deref == deref) && (l->secure == secureflag)
475 && !compare_client_certs(st->client_certs, l->client_certs))
480 /* If this connection didn't match the criteria, then we
481 * need to unlock the mutex so it is available to be reused.
483 apr_thread_mutex_unlock(l->lock);
489 /* If nothing found, search again, but we don't care about the
490 * binddn and bindpw this time.
493 for (l=st->connections,p=NULL; l; l=l->next) {
495 if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
498 if ((l->port == port) && (strcmp(l->host, host) == 0) &&
499 (l->deref == deref) && (l->secure == secureflag) &&
500 !compare_client_certs(st->client_certs, l->client_certs))
502 /* the bind credentials have changed */
504 util_ldap_strdup((char**)&(l->binddn), binddn);
505 util_ldap_strdup((char**)&(l->bindpw), bindpw);
509 /* If this connection didn't match the criteria, then we
510 * need to unlock the mutex so it is available to be reused.
512 apr_thread_mutex_unlock(l->lock);
519 /* artificially disable cache */
522 /* If no connection what found after the second search, we
528 * Add the new connection entry to the linked list. Note that we
529 * don't actually establish an LDAP connection yet; that happens
530 * the first time authentication is requested.
532 /* create the details to the pool in st */
533 l = apr_pcalloc(st->pool, sizeof(util_ldap_connection_t));
534 if (apr_pool_create(&l->pool, st->pool) != APR_SUCCESS) {
535 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
536 "util_ldap: Failed to create memory pool");
538 apr_thread_mutex_unlock(st->mutex);
544 apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, st->pool);
545 apr_thread_mutex_lock(l->lock);
548 l->host = apr_pstrdup(st->pool, host);
551 util_ldap_strdup((char**)&(l->binddn), binddn);
552 util_ldap_strdup((char**)&(l->bindpw), bindpw);
554 /* The security mode after parsing the URL will always be either
555 * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
556 * If the security setting is NONE, override it to the security
557 * setting optionally supplied by the admin using LDAPTrustedMode
559 l->secure = secureflag;
561 /* save away a copy of the client cert list that is presently valid */
562 l->client_certs = apr_array_copy_hdr(l->pool, st->client_certs);
573 apr_thread_mutex_unlock(st->mutex);
578 /* ------------------------------------------------------------------ */
581 * Compares two DNs to see if they're equal. The only way to do this correctly
582 * is to search for the dn and then do ldap_get_dn() on the result. This should
583 * match the initial dn, since it would have been also retrieved with
584 * ldap_get_dn(). This is expensive, so if the configuration value
585 * compare_dn_on_server is false, just does an ordinary strcmp.
587 * The lock for the ldap cache should already be acquired.
589 static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
590 const char *url, const char *dn,
591 const char *reqdn, int compare_dn_on_server)
594 util_url_node_t *curl;
595 util_url_node_t curnode;
596 util_dn_compare_node_t *node;
597 util_dn_compare_node_t newnode;
599 LDAPMessage *res, *entry;
602 util_ldap_state_t *st = (util_ldap_state_t *)
603 ap_get_module_config(r->server->module_config,
606 /* get cache entry (or create one) */
610 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
612 curl = util_ald_create_caches(st, url);
616 /* a simple compare? */
617 if (!compare_dn_on_server) {
618 /* unlock this read lock */
619 if (strcmp(dn, reqdn)) {
620 ldc->reason = "DN Comparison FALSE (direct strcmp())";
621 return LDAP_COMPARE_FALSE;
624 ldc->reason = "DN Comparison TRUE (direct strcmp())";
625 return LDAP_COMPARE_TRUE;
630 /* no - it's a server side compare */
633 /* is it in the compare cache? */
634 newnode.reqdn = (char *)reqdn;
635 node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
637 /* If it's in the cache, it's good */
638 /* unlock this read lock */
640 ldc->reason = "DN Comparison TRUE (cached)";
641 return LDAP_COMPARE_TRUE;
644 /* unlock this read lock */
649 if (failures++ > 10) {
650 /* too many failures */
654 /* make a server connection */
655 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
656 /* connect to server failed */
660 /* search for reqdn */
661 if ((result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
662 "(objectclass=*)", NULL, 1,
663 NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &res))
666 ldc->reason = "DN Comparison ldap_search_ext_s() "
667 "failed with server down";
668 uldap_connection_unbind(ldc);
671 if (result != LDAP_SUCCESS) {
672 /* search for reqdn failed - no match */
673 ldc->reason = "DN Comparison ldap_search_ext_s() failed";
677 entry = ldap_first_entry(ldc->ldap, res);
678 searchdn = ldap_get_dn(ldc->ldap, entry);
681 if (strcmp(dn, searchdn) != 0) {
682 /* compare unsuccessful */
683 ldc->reason = "DN Comparison FALSE (checked on server)";
684 result = LDAP_COMPARE_FALSE;
688 /* compare successful - add to the compare cache */
690 newnode.reqdn = (char *)reqdn;
691 newnode.dn = (char *)dn;
693 node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
695 || (strcmp(reqdn, node->reqdn) != 0)
696 || (strcmp(dn, node->dn) != 0))
698 util_ald_cache_insert(curl->dn_compare_cache, &newnode);
702 ldc->reason = "DN Comparison TRUE (checked on server)";
703 result = LDAP_COMPARE_TRUE;
705 ldap_memfree(searchdn);
711 * Does an generic ldap_compare operation. It accepts a cache that it will use
712 * to lookup the compare in the cache. We cache two kinds of compares
713 * (require group compares) and (require user compares). Each compare has a different
714 * cache node: require group includes the DN; require user does not because the
715 * require user cache is owned by the
718 static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
719 const char *url, const char *dn,
720 const char *attrib, const char *value)
723 util_url_node_t *curl;
724 util_url_node_t curnode;
725 util_compare_node_t *compare_nodep;
726 util_compare_node_t the_compare_node;
727 apr_time_t curtime = 0; /* silence gcc -Wall */
730 util_ldap_state_t *st = (util_ldap_state_t *)
731 ap_get_module_config(r->server->module_config,
734 /* get cache entry (or create one) */
737 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
739 curl = util_ald_create_caches(st, url);
744 /* make a comparison to the cache */
746 curtime = apr_time_now();
748 the_compare_node.dn = (char *)dn;
749 the_compare_node.attrib = (char *)attrib;
750 the_compare_node.value = (char *)value;
751 the_compare_node.result = 0;
752 the_compare_node.sgl_processed = 0;
753 the_compare_node.subgroupList = NULL;
755 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
758 if (compare_nodep != NULL) {
760 if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
761 /* ...but it is too old */
762 util_ald_cache_remove(curl->compare_cache, compare_nodep);
765 /* ...and it is good */
766 if (LDAP_COMPARE_TRUE == compare_nodep->result) {
767 ldc->reason = "Comparison true (cached)";
769 else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
770 ldc->reason = "Comparison false (cached)";
772 else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
773 ldc->reason = "Comparison no such attribute (cached)";
776 ldc->reason = "Comparison undefined (cached)";
779 /* record the result code to return with the reason... */
780 result = compare_nodep->result;
781 /* and unlock this read lock */
786 /* unlock this read lock */
791 if (failures++ > 10) {
792 /* too many failures */
796 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
801 if ((result = ldap_compare_s(ldc->ldap,
805 == LDAP_SERVER_DOWN) {
806 /* connection failed - try again */
807 ldc->reason = "ldap_compare_s() failed with server down";
808 uldap_connection_unbind(ldc);
812 ldc->reason = "Comparison complete";
813 if ((LDAP_COMPARE_TRUE == result) ||
814 (LDAP_COMPARE_FALSE == result) ||
815 (LDAP_NO_SUCH_ATTRIBUTE == result)) {
817 /* compare completed; caching result */
819 the_compare_node.lastcompare = curtime;
820 the_compare_node.result = result;
821 the_compare_node.sgl_processed = 0;
822 the_compare_node.subgroupList = NULL;
824 /* If the node doesn't exist then insert it, otherwise just update
825 * it with the last results
827 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
829 if ( (compare_nodep == NULL)
830 || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
831 || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
832 || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
836 junk = util_ald_cache_insert(curl->compare_cache, &the_compare_node);
838 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] cache_compare: Cache insertion failure.", getpid());
842 compare_nodep->lastcompare = curtime;
843 compare_nodep->result = result;
847 if (LDAP_COMPARE_TRUE == result) {
848 ldc->reason = "Comparison true (adding to cache)";
849 return LDAP_COMPARE_TRUE;
851 else if (LDAP_COMPARE_FALSE == result) {
852 ldc->reason = "Comparison false (adding to cache)";
853 return LDAP_COMPARE_FALSE;
856 ldc->reason = "Comparison no such attribute (adding to cache)";
857 return LDAP_NO_SUCH_ATTRIBUTE;
864 * Does a recursive lookup operation to try to find a user within (cached) nested groups.
866 * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
868 static int uldap_cache_check_subgroups(request_rec *r, util_ldap_connection_t *ldc,
869 const char *url, const char *dn,
870 const char *attrib, const char *value,
871 char **subgroupAttrs, apr_array_header_t *subgroupclasses,
872 int cur_subgroup_depth, int max_subgroup_depth)
874 int result = LDAP_COMPARE_FALSE;
875 util_url_node_t *curl;
876 util_url_node_t curnode;
877 util_compare_node_t *compare_nodep;
878 util_compare_node_t the_compare_node;
879 util_compare_subgroup_t *tmp_local_sgl = NULL;
881 LDAPMessage *sga_res, *entry;
882 apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
884 util_ldap_state_t *st = (util_ldap_state_t *)
885 ap_get_module_config(r->server->module_config,
889 * 1. Call uldap_cache_compare for each subgroupclass value to check the generic,
890 * user-agnostic, cached group entry. This will create a new generic cache entry if there
891 * wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we have no groups.
892 * 2. Lock The cache and get the generic cache entry.
893 * 3. Check if there is already a subgrouplist in this generic group's cache entry.
894 * A. If there is, go to step 4.
896 * i) Use ldap_search to get the full list
897 * of subgroup "members" (which may include non-group "members").
898 * ii) Use uldap_cache_compare to strip the list down to just groups.
899 * iii) Lock and add this stripped down list to the cache of the generic group.
900 * 4. Loop through the sgl and call uldap_cache_compare (using the user info) for each
901 * subgroup to see if the subgroup contains the user and to get the subgroups added to the
902 * cache (with user-afinity, if they aren't already there).
903 * A. If the user is in the subgroup, then we'll be returning LDAP_COMPARE_TRUE.
904 * B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via uldap_cache_compare) then
905 * recursively call this function to get the sub-subgroups added...
906 * 5. Cleanup local allocations.
907 * 6. Return the final result.
910 /* Stop looking at deeper levels of nested groups if we have reached the max.
911 * Since we already checked the top-level group in uldap_cache_compare, we don't
912 * need to check it again here - so if max_subgroup_depth is set to 0, we won't
913 * check it (i.e. that is why we check < rather than <=).
914 * We'll be calling uldap_cache_compare from here to check if the user is in the
915 * next level before we recurse into that next level looking for more subgroups.
917 if (cur_subgroup_depth < max_subgroup_depth) {
918 int base_sgcIndex = 0;
919 int lcl_sgl_processedFlag = 0;
920 struct mod_auth_ldap_groupattr_entry_t *sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
922 /* 1. Check the "groupiness" of the specified basedn. Stopping at the first TRUE return. */
923 while ((base_sgcIndex < subgroupclasses->nelts) && (result != LDAP_COMPARE_TRUE)) {
924 result = uldap_cache_compare(r, ldc, url, dn, "objectClass", sgc_ents[base_sgcIndex].name);
925 if (result != LDAP_COMPARE_TRUE) {
930 if (result != LDAP_COMPARE_TRUE) {
931 /* The dn we were handed doesn't seem to be a group, how can we check for SUB-groups if this
932 * isn't a group?!?!?!
934 ldc->reason = "DN failed group verification.";
938 /* 2. Find previously created cache entry and check if there is already a subgrouplist. */
941 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
944 if (curl && curl->compare_cache) {
945 /* make a comparison to the cache */
948 the_compare_node.dn = (char *)dn;
949 the_compare_node.attrib = (char *)"objectClass";
950 the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
951 the_compare_node.result = 0;
952 the_compare_node.sgl_processed = 0;
953 the_compare_node.subgroupList = NULL;
955 compare_nodep = util_ald_cache_fetch(curl->compare_cache, &the_compare_node);
957 if (compare_nodep == NULL) {
958 /* Didn't find it. This shouldn't happen since we just called uldap_cache_compare. */
960 ldc->reason = "check_subgroups failed to find cached element.";
961 return LDAP_COMPARE_FALSE;
964 /* Found the generic group entry... but the user isn't in this group or we wouldn't be here. */
965 lcl_sgl_processedFlag = compare_nodep->sgl_processed;
966 if(compare_nodep->sgl_processed && compare_nodep->subgroupList) {
967 /* Make a local copy of the subgroup list */
969 tmp_local_sgl = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
970 tmp_local_sgl->len = compare_nodep->subgroupList->len;
971 tmp_local_sgl->subgroupDNs = apr_pcalloc(r->pool, sizeof(char *) * compare_nodep->subgroupList->len);
972 for (i = 0; i < compare_nodep->subgroupList->len; i++) {
973 tmp_local_sgl->subgroupDNs[i] = apr_pstrdup(r->pool, compare_nodep->subgroupList->subgroupDNs[i]);
977 /* unlock this read lock */
981 /* If we get here, something is wrong. Caches should have been created and
982 this group entry should be found in the cache. */
983 ldc->reason = "check_subgroups failed to find any caches.";
984 return LDAP_COMPARE_FALSE;
987 result = LDAP_COMPARE_FALSE;
989 /* No cache entry had a processed SGL. Retrieve from LDAP server */
990 if ((lcl_sgl_processedFlag == 0) && (!tmp_local_sgl)) {
992 /* 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups. */
993 if (failures++ > 10) {
994 /* too many failures */
998 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1003 /* try to do the search */
1004 result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
1005 (char *)"cn=*", subgroupAttrs, 0,
1006 NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
1007 if (result == LDAP_SERVER_DOWN) {
1008 ldc->reason = "ldap_search_ext_s() for subgroups failed with server down";
1009 uldap_connection_unbind(ldc);
1013 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1014 if (result != LDAP_SUCCESS) {
1015 ldc->reason = "ldap_search_ext_s() for subgroups failed";
1019 entry = ldap_first_entry(ldc->ldap, sga_res);
1022 * Get values for the provided sub-group attributes.
1024 if (subgroupAttrs) {
1025 int indx = 0, tmp_sgcIndex;
1027 while (subgroupAttrs[indx]) {
1031 /* Get *all* matching "member" values from this group. */
1032 values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
1037 * Now we are going to pare the subgroup members of this group to *just*
1038 * the subgroups, add them to the compare_nodep, and then proceed to check
1039 * the new level of subgroups.
1041 while (values[val_index]) {
1042 /* Check if this entry really is a group. */
1044 result = LDAP_COMPARE_FALSE;
1045 while ((tmp_sgcIndex < subgroupclasses->nelts) && (result != LDAP_COMPARE_TRUE)) {
1046 result = uldap_cache_compare(r, ldc, url, values[val_index], "objectClass",
1047 sgc_ents[tmp_sgcIndex].name);
1049 if (result != LDAP_COMPARE_TRUE) {
1053 /* It's a group, so add it to the array. */
1054 if (result == LDAP_COMPARE_TRUE) {
1055 char **newgrp = (char **) apr_array_push(subgroups);
1056 *newgrp = apr_pstrdup(r->pool, values[val_index]);
1060 ldap_value_free(values);
1066 ldap_msgfree(sga_res);
1068 if (subgroups->nelts > 0) {
1069 /* We need to fill in tmp_local_subgroups using the data from LDAP */
1072 tmp_local_sgl = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
1073 tmp_local_sgl->subgroupDNs = apr_pcalloc(r->pool, sizeof(char *) * (subgroups->nelts));
1074 for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
1075 tmp_local_sgl->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
1077 tmp_local_sgl->len = sgindex;
1080 /* Find the generic group cache entry and add the sgl. */
1083 the_compare_node.dn = (char *)dn;
1084 the_compare_node.attrib = (char *)"objectClass";
1085 the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1086 the_compare_node.result = 0;
1087 the_compare_node.sgl_processed = 0;
1088 the_compare_node.subgroupList = NULL;
1090 compare_nodep = util_ald_cache_fetch(curl->compare_cache, &the_compare_node);
1092 if (compare_nodep == NULL) {
1093 /* Didn't find it. This shouldn't happen since we just called uldap_cache_compare. */
1094 LDAP_CACHE_UNLOCK();
1095 ldc->reason = "check_subgroups failed to find the cache entry to add sub-group list to.";
1096 return LDAP_COMPARE_FALSE;
1099 /* overwrite SGL if it was previously updated between the last
1100 ** two times we looked at the cache
1102 compare_nodep->sgl_processed = 1;
1103 if (tmp_local_sgl) {
1104 compare_nodep->subgroupList = util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
1107 /* We didn't find a single subgroup, next time save us from looking */
1108 compare_nodep->subgroupList = NULL;
1111 /* unlock this read lock */
1112 LDAP_CACHE_UNLOCK();
1115 /* tmp_local_sgl has either been created, or copied out of the cache */
1116 /* If tmp_local_sgl is NULL, there are no subgroups to process and we'll return false */
1117 result = LDAP_COMPARE_FALSE;
1118 if (tmp_local_sgl) {
1120 const char *group = NULL;
1121 while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
1122 group = tmp_local_sgl->subgroupDNs[sgindex];
1123 /* 4. Now loop through the subgroupList and call uldap_cache_compare to check for the user. */
1124 result = uldap_cache_compare(r, ldc, url, group, attrib, value);
1125 if (result == LDAP_COMPARE_TRUE) {
1126 /* 4.A. We found the user in the subgroup. Return LDAP_COMPARE_TRUE. */
1127 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] util_ldap:"
1128 " Found the user in a subgroup (%s) at level %d of %d. (7).",
1129 getpid(), group, cur_subgroup_depth+1, max_subgroup_depth);
1132 /* 4.B. We didn't find the user in this subgroup, so recurse into it and keep looking. */
1133 result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
1134 value, subgroupAttrs, subgroupclasses,
1135 cur_subgroup_depth+1, max_subgroup_depth);
1146 static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
1147 const char *url, const char *basedn,
1148 int scope, char **attrs, const char *filter,
1149 const char *bindpw, const char **binddn,
1150 const char ***retvals)
1152 const char **vals = NULL;
1155 LDAPMessage *res, *entry;
1159 util_url_node_t *curl; /* Cached URL node */
1160 util_url_node_t curnode;
1161 util_search_node_t *search_nodep; /* Cached search node */
1162 util_search_node_t the_search_node;
1165 util_ldap_state_t *st =
1166 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1169 /* Get the cache node for this url */
1172 curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1175 curl = util_ald_create_caches(st, url);
1177 LDAP_CACHE_UNLOCK();
1181 the_search_node.username = filter;
1182 search_nodep = util_ald_cache_fetch(curl->search_cache,
1184 if (search_nodep != NULL) {
1186 /* found entry in search cache... */
1187 curtime = apr_time_now();
1190 * Remove this item from the cache if its expired. If the sent
1191 * password doesn't match the storepassword, the entry will
1192 * be removed and readded later if the credentials pass
1195 if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1196 /* ...but entry is too old */
1197 util_ald_cache_remove(curl->search_cache, search_nodep);
1199 else if ( (search_nodep->bindpw)
1200 && (search_nodep->bindpw[0] != '\0')
1201 && (strcmp(search_nodep->bindpw, bindpw) == 0))
1203 /* ...and entry is valid */
1204 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1208 *retvals = apr_pcalloc(r->pool, sizeof(char *) * k);
1209 while (search_nodep->vals[i]) {
1210 *retvals[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1214 LDAP_CACHE_UNLOCK();
1215 ldc->reason = "Authentication successful (cached)";
1216 return LDAP_SUCCESS;
1219 /* unlock this read lock */
1220 LDAP_CACHE_UNLOCK();
1224 * At this point, there is no valid cached search, so lets do the search.
1228 * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1231 if (failures++ > 10) {
1234 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1238 /* try do the search */
1239 if ((result = ldap_search_ext_s(ldc->ldap,
1240 (char *)basedn, scope,
1241 (char *)filter, attrs, 0,
1242 NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &res))
1243 == LDAP_SERVER_DOWN)
1245 ldc->reason = "ldap_search_ext_s() for user failed with server down";
1246 uldap_connection_unbind(ldc);
1250 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1251 if (result != LDAP_SUCCESS) {
1252 ldc->reason = "ldap_search_ext_s() for user failed";
1257 * We should have found exactly one entry; to find a different
1258 * number is an error.
1260 count = ldap_count_entries(ldc->ldap, res);
1264 ldc->reason = "User not found";
1266 ldc->reason = "User is not unique (search found two "
1269 return LDAP_NO_SUCH_OBJECT;
1272 entry = ldap_first_entry(ldc->ldap, res);
1274 /* Grab the dn, copy it into the pool, and free it again */
1275 dn = ldap_get_dn(ldc->ldap, entry);
1276 *binddn = apr_pstrdup(r->pool, dn);
1280 * A bind to the server with an empty password always succeeds, so
1281 * we check to ensure that the password is not empty. This implies
1282 * that users who actually do have empty passwords will never be
1283 * able to authenticate with this module. I don't see this as a big
1286 if (!bindpw || strlen(bindpw) <= 0) {
1288 ldc->reason = "Empty password not allowed";
1289 return LDAP_INVALID_CREDENTIALS;
1293 * Attempt to bind with the retrieved dn and the password. If the bind
1294 * fails, it means that the password is wrong (the dn obviously
1295 * exists, since we just retrieved it)
1297 if ((result = ldap_simple_bind_s(ldc->ldap,
1299 (char *)bindpw)) == LDAP_SERVER_DOWN) {
1300 ldc->reason = "ldap_simple_bind_s() to check user credentials "
1301 "failed with server down";
1303 uldap_connection_unbind(ldc);
1307 /* failure? if so - return */
1308 if (result != LDAP_SUCCESS) {
1309 ldc->reason = "ldap_simple_bind_s() to check user credentials failed";
1311 uldap_connection_unbind(ldc);
1316 * We have just bound the connection to a different user and password
1317 * combination, which might be reused unintentionally next time this
1318 * connection is used from the connection pool. To ensure no confusion,
1319 * we mark the connection as unbound.
1325 * Get values for the provided attributes.
1331 vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1338 values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1339 while (values && values[j]) {
1340 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1341 : apr_pstrdup(r->pool, values[j]);
1344 ldap_value_free(values);
1352 * Add the new username to the search cache.
1356 the_search_node.username = filter;
1357 the_search_node.dn = *binddn;
1358 the_search_node.bindpw = bindpw;
1359 the_search_node.lastbind = apr_time_now();
1360 the_search_node.vals = vals;
1361 the_search_node.numvals = numvals;
1363 /* Search again to make sure that another thread didn't ready insert
1364 * this node into the cache before we got here. If it does exist then
1365 * update the lastbind
1367 search_nodep = util_ald_cache_fetch(curl->search_cache,
1369 if ((search_nodep == NULL) ||
1370 (strcmp(*binddn, search_nodep->dn) != 0)) {
1372 /* Nothing in cache, insert new entry */
1373 util_ald_cache_insert(curl->search_cache, &the_search_node);
1375 else if ((!search_nodep->bindpw) ||
1376 (strcmp(bindpw, search_nodep->bindpw) != 0)) {
1378 /* Entry in cache is invalid, remove it and insert new one */
1379 util_ald_cache_remove(curl->search_cache, search_nodep);
1380 util_ald_cache_insert(curl->search_cache, &the_search_node);
1383 /* Cache entry is valid, update lastbind */
1384 search_nodep->lastbind = the_search_node.lastbind;
1386 LDAP_CACHE_UNLOCK();
1390 ldc->reason = "Authentication successful";
1391 return LDAP_SUCCESS;
1395 * This function will return the DN of the entry matching userid.
1396 * It is used to get the DN in case some other module than mod_auth_ldap
1397 * has authenticated the user.
1398 * The function is basically a copy of uldap_cache_checkuserid
1399 * with password checking removed.
1401 static int uldap_cache_getuserdn(request_rec *r, util_ldap_connection_t *ldc,
1402 const char *url, const char *basedn,
1403 int scope, char **attrs, const char *filter,
1404 const char **binddn, const char ***retvals)
1406 const char **vals = NULL;
1409 LDAPMessage *res, *entry;
1413 util_url_node_t *curl; /* Cached URL node */
1414 util_url_node_t curnode;
1415 util_search_node_t *search_nodep; /* Cached search node */
1416 util_search_node_t the_search_node;
1419 util_ldap_state_t *st =
1420 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1423 /* Get the cache node for this url */
1426 curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1429 curl = util_ald_create_caches(st, url);
1431 LDAP_CACHE_UNLOCK();
1435 the_search_node.username = filter;
1436 search_nodep = util_ald_cache_fetch(curl->search_cache,
1438 if (search_nodep != NULL) {
1440 /* found entry in search cache... */
1441 curtime = apr_time_now();
1444 * Remove this item from the cache if its expired.
1446 if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1447 /* ...but entry is too old */
1448 util_ald_cache_remove(curl->search_cache, search_nodep);
1451 /* ...and entry is valid */
1452 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1456 *retvals = apr_pcalloc(r->pool, sizeof(char *) * k);
1457 while (search_nodep->vals[i]) {
1458 *retvals[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1462 LDAP_CACHE_UNLOCK();
1463 ldc->reason = "Search successful (cached)";
1464 return LDAP_SUCCESS;
1467 /* unlock this read lock */
1468 LDAP_CACHE_UNLOCK();
1472 * At this point, there is no valid cached search, so lets do the search.
1476 * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1479 if (failures++ > 10) {
1482 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1486 /* try do the search */
1487 if ((result = ldap_search_ext_s(ldc->ldap,
1488 (char *)basedn, scope,
1489 (char *)filter, attrs, 0,
1490 NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &res))
1491 == LDAP_SERVER_DOWN)
1493 ldc->reason = "ldap_search_ext_s() for user failed with server down";
1494 uldap_connection_unbind(ldc);
1498 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1499 if (result != LDAP_SUCCESS) {
1500 ldc->reason = "ldap_search_ext_s() for user failed";
1505 * We should have found exactly one entry; to find a different
1506 * number is an error.
1508 count = ldap_count_entries(ldc->ldap, res);
1512 ldc->reason = "User not found";
1514 ldc->reason = "User is not unique (search found two "
1517 return LDAP_NO_SUCH_OBJECT;
1520 entry = ldap_first_entry(ldc->ldap, res);
1522 /* Grab the dn, copy it into the pool, and free it again */
1523 dn = ldap_get_dn(ldc->ldap, entry);
1524 *binddn = apr_pstrdup(r->pool, dn);
1528 * Get values for the provided attributes.
1534 vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1541 values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1542 while (values && values[j]) {
1543 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1544 : apr_pstrdup(r->pool, values[j]);
1547 ldap_value_free(values);
1555 * Add the new username to the search cache.
1559 the_search_node.username = filter;
1560 the_search_node.dn = *binddn;
1561 the_search_node.bindpw = NULL;
1562 the_search_node.lastbind = apr_time_now();
1563 the_search_node.vals = vals;
1564 the_search_node.numvals = numvals;
1566 /* Search again to make sure that another thread didn't ready insert
1567 * this node into the cache before we got here. If it does exist then
1568 * update the lastbind
1570 search_nodep = util_ald_cache_fetch(curl->search_cache,
1572 if ((search_nodep == NULL) ||
1573 (strcmp(*binddn, search_nodep->dn) != 0)) {
1575 /* Nothing in cache, insert new entry */
1576 util_ald_cache_insert(curl->search_cache, &the_search_node);
1579 * Don't update lastbind on entries with bindpw because
1580 * we haven't verified that password. It's OK to update
1581 * the entry if there is no password in it.
1583 else if (!search_nodep->bindpw) {
1584 /* Cache entry is valid, update lastbind */
1585 search_nodep->lastbind = the_search_node.lastbind;
1587 LDAP_CACHE_UNLOCK();
1592 ldc->reason = "Search successful";
1593 return LDAP_SUCCESS;
1597 * Reports if ssl support is enabled
1599 * 1 = enabled, 0 = not enabled
1601 static int uldap_ssl_supported(request_rec *r)
1603 util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
1604 r->server->module_config, &ldap_module);
1606 return(st->ssl_supported);
1610 /* ---------------------------------------- */
1611 /* config directives */
1614 static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy,
1617 util_ldap_state_t *st =
1618 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1620 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1626 st->cache_bytes = atol(bytes);
1628 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1629 "[%" APR_PID_T_FMT "] ldap cache: Setting shared memory "
1630 " cache size to %" APR_SIZE_T_FMT " bytes.",
1631 getpid(), st->cache_bytes);
1636 static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy,
1639 util_ldap_state_t *st =
1640 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1642 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1649 st->cache_file = ap_server_root_relative(st->pool, file);
1652 st->cache_file = NULL;
1655 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1656 "LDAP cache: Setting shared memory cache file to %s bytes.",
1662 static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy,
1665 util_ldap_state_t *st =
1666 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1668 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1674 st->search_cache_ttl = atol(ttl) * 1000000;
1676 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1677 "[%" APR_PID_T_FMT "] ldap cache: Setting cache TTL to %ld microseconds.",
1678 getpid(), st->search_cache_ttl);
1683 static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy,
1686 util_ldap_state_t *st =
1687 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1689 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1695 st->search_cache_size = atol(size);
1696 if (st->search_cache_size < 0) {
1697 st->search_cache_size = 0;
1700 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1701 "[%" APR_PID_T_FMT "] ldap cache: Setting search cache size to %ld entries.",
1702 getpid(), st->search_cache_size);
1707 static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy,
1710 util_ldap_state_t *st =
1711 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1713 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1719 st->compare_cache_ttl = atol(ttl) * 1000000;
1721 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1722 "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache TTL to %ld microseconds.",
1723 getpid(), st->compare_cache_ttl);
1728 static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy,
1731 util_ldap_state_t *st =
1732 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1734 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1740 st->compare_cache_size = atol(size);
1741 if (st->compare_cache_size < 0) {
1742 st->compare_cache_size = 0;
1745 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1746 "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache size to %ld "
1747 "entries.", getpid(), st->compare_cache_size);
1754 * Parse the certificate type.
1756 * The type can be one of the following:
1757 * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64,
1758 * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64
1760 * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned.
1762 static int util_ldap_parse_cert_type(const char *type)
1764 /* Authority file in binary DER format */
1765 if (0 == strcasecmp("CA_DER", type)) {
1766 return APR_LDAP_CA_TYPE_DER;
1769 /* Authority file in Base64 format */
1770 else if (0 == strcasecmp("CA_BASE64", type)) {
1771 return APR_LDAP_CA_TYPE_BASE64;
1774 /* Netscape certificate database file/directory */
1775 else if (0 == strcasecmp("CA_CERT7_DB", type)) {
1776 return APR_LDAP_CA_TYPE_CERT7_DB;
1779 /* Netscape secmod file/directory */
1780 else if (0 == strcasecmp("CA_SECMOD", type)) {
1781 return APR_LDAP_CA_TYPE_SECMOD;
1784 /* Client cert file in DER format */
1785 else if (0 == strcasecmp("CERT_DER", type)) {
1786 return APR_LDAP_CERT_TYPE_DER;
1789 /* Client cert file in Base64 format */
1790 else if (0 == strcasecmp("CERT_BASE64", type)) {
1791 return APR_LDAP_CERT_TYPE_BASE64;
1794 /* Client cert file in PKCS#12 format */
1795 else if (0 == strcasecmp("CERT_PFX", type)) {
1796 return APR_LDAP_CERT_TYPE_PFX;
1799 /* Netscape client cert database file/directory */
1800 else if (0 == strcasecmp("CERT_KEY3_DB", type)) {
1801 return APR_LDAP_CERT_TYPE_KEY3_DB;
1804 /* Netscape client cert nickname */
1805 else if (0 == strcasecmp("CERT_NICKNAME", type)) {
1806 return APR_LDAP_CERT_TYPE_NICKNAME;
1809 /* Client cert key file in DER format */
1810 else if (0 == strcasecmp("KEY_DER", type)) {
1811 return APR_LDAP_KEY_TYPE_DER;
1814 /* Client cert key file in Base64 format */
1815 else if (0 == strcasecmp("KEY_BASE64", type)) {
1816 return APR_LDAP_KEY_TYPE_BASE64;
1819 /* Client cert key file in PKCS#12 format */
1820 else if (0 == strcasecmp("KEY_PFX", type)) {
1821 return APR_LDAP_KEY_TYPE_PFX;
1825 return APR_LDAP_CA_TYPE_UNKNOWN;
1832 * Set LDAPTrustedGlobalCert.
1834 * This directive takes either two or three arguments:
1835 * - certificate type
1836 * - certificate file / directory / nickname
1837 * - certificate password (optional)
1839 * This directive may only be used globally.
1841 static const char *util_ldap_set_trusted_global_cert(cmd_parms *cmd,
1845 const char *password)
1847 util_ldap_state_t *st =
1848 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1850 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1854 apr_ldap_opt_tls_cert_t *cert;
1860 /* handle the certificate type */
1862 cert_type = util_ldap_parse_cert_type(type);
1863 if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
1864 return apr_psprintf(cmd->pool, "The certificate type %s is "
1865 "not recognised. It should be one "
1866 "of CA_DER, CA_BASE64, CA_CERT7_DB, "
1867 "CA_SECMOD, CERT_DER, CERT_BASE64, "
1868 "CERT_KEY3_DB, CERT_NICKNAME, "
1869 "KEY_DER, KEY_BASE64", type);
1873 return "Certificate type was not specified.";
1876 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1877 "LDAP: SSL trusted global cert - %s (type %s)",
1880 /* add the certificate to the global array */
1881 cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
1882 cert->type = cert_type;
1884 cert->password = password;
1886 /* if file is a file or path, fix the path */
1887 if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
1888 cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
1890 cert->path = ap_server_root_relative(cmd->pool, file);
1892 ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
1895 ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
1896 "LDAP: Could not open SSL trusted certificate "
1897 "authority file - %s",
1898 cert->path == NULL ? file : cert->path);
1899 return "Invalid global certificate file path";
1908 * Set LDAPTrustedClientCert.
1910 * This directive takes either two or three arguments:
1911 * - certificate type
1912 * - certificate file / directory / nickname
1913 * - certificate password (optional)
1915 static const char *util_ldap_set_trusted_client_cert(cmd_parms *cmd,
1919 const char *password)
1921 util_ldap_state_t *st =
1922 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1927 apr_ldap_opt_tls_cert_t *cert;
1929 /* handle the certificate type */
1931 cert_type = util_ldap_parse_cert_type(type);
1932 if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
1933 return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
1934 "not recognised. It should be one "
1935 "of CERT_DER, CERT_BASE64, "
1936 "CERT_NICKNAME, CERT_PFX,"
1937 "KEY_DER, KEY_BASE64, KEY_PFX",
1940 else if (APR_LDAP_CA_TYPE_DER == cert_type ||
1941 APR_LDAP_CA_TYPE_BASE64 == cert_type ||
1942 APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
1943 APR_LDAP_CA_TYPE_SECMOD == cert_type ||
1944 APR_LDAP_CERT_TYPE_PFX == cert_type ||
1945 APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
1946 return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
1947 "only valid within a "
1948 "LDAPTrustedGlobalCert directive. "
1949 "Only CERT_DER, CERT_BASE64, "
1950 "CERT_NICKNAME, KEY_DER, and "
1951 "KEY_BASE64 may be used.", type);
1955 return "Certificate type was not specified.";
1958 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1959 "LDAP: SSL trusted client cert - %s (type %s)",
1962 /* add the certificate to the global array */
1963 cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
1964 cert->type = cert_type;
1966 cert->password = password;
1968 /* if file is a file or path, fix the path */
1969 if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
1970 cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
1972 cert->path = ap_server_root_relative(cmd->pool, file);
1974 ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
1977 ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
1978 "LDAP: Could not open SSL client certificate "
1980 cert->path == NULL ? file : cert->path);
1981 return "Invalid client certificate file path";
1991 * Set LDAPTrustedMode.
1993 * This directive sets what encryption mode to use on a connection:
1994 * - None (No encryption)
1995 * - SSL (SSL encryption)
1996 * - STARTTLS (TLS encryption)
1998 static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
2001 util_ldap_state_t *st =
2002 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2005 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2006 "LDAP: SSL trusted mode - %s",
2009 if (0 == strcasecmp("NONE", mode)) {
2010 st->secure = APR_LDAP_NONE;
2012 else if (0 == strcasecmp("SSL", mode)) {
2013 st->secure = APR_LDAP_SSL;
2015 else if ( (0 == strcasecmp("TLS", mode))
2016 || (0 == strcasecmp("STARTTLS", mode))) {
2017 st->secure = APR_LDAP_STARTTLS;
2020 return "Invalid LDAPTrustedMode setting: must be one of NONE, "
2021 "SSL, or TLS/STARTTLS";
2028 static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
2032 util_ldap_state_t *st =
2033 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2035 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2041 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2042 "LDAP: SSL verify server certificate - %s",
2043 mode?"TRUE":"FALSE");
2045 st->verify_svr_cert = mode;
2051 static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
2055 util_ldap_state_t *st =
2056 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2058 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2064 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2065 st->connectionTimeout = atol(ttl);
2067 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2068 "[%" APR_PID_T_FMT "] ldap connection: Setting connection timeout to "
2069 "%ld seconds.", getpid(), st->connectionTimeout);
2071 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
2072 "LDAP: Connection timout option not supported by the "
2073 "LDAP SDK in use." );
2080 static void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
2082 util_ldap_state_t *st =
2083 (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
2085 /* Create a per vhost pool for mod_ldap to use, serialized with
2086 * st->mutex (also one per vhost). both are replicated by fork(),
2087 * no shared memory managed by either.
2089 apr_pool_create(&st->pool, p);
2091 apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
2094 st->cache_bytes = 100000;
2095 st->search_cache_ttl = 600000000;
2096 st->search_cache_size = 1024;
2097 st->compare_cache_ttl = 600000000;
2098 st->compare_cache_size = 1024;
2099 st->connections = NULL;
2100 st->ssl_supported = 0;
2101 st->global_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2102 st->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2103 st->secure = APR_LDAP_NONE;
2105 st->connectionTimeout = 10;
2106 st->verify_svr_cert = 1;
2111 static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
2114 util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
2115 util_ldap_state_t *base = (util_ldap_state_t *) basev;
2116 util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
2118 st->pool = overrides->pool;
2120 st->mutex = overrides->mutex;
2123 /* The cache settings can not be modified in a
2124 virtual host since all server use the same
2125 shared memory cache. */
2126 st->cache_bytes = base->cache_bytes;
2127 st->search_cache_ttl = base->search_cache_ttl;
2128 st->search_cache_size = base->search_cache_size;
2129 st->compare_cache_ttl = base->compare_cache_ttl;
2130 st->compare_cache_size = base->compare_cache_size;
2131 st->util_ldap_cache_lock = base->util_ldap_cache_lock;
2133 st->connections = NULL;
2134 st->ssl_supported = 0;
2135 st->global_certs = apr_array_append(p, base->global_certs,
2136 overrides->global_certs);
2137 st->client_certs = apr_array_append(p, base->client_certs,
2138 overrides->client_certs);
2139 st->secure = (overrides->secure_set == 0) ? base->secure
2140 : overrides->secure;
2142 /* These LDAP connection settings can not be overwritten in
2143 a virtual host. Once set in the base server, they must
2144 remain the same. None of the LDAP SDKs seem to be able
2145 to handle setting the verify_svr_cert flag on a
2146 per-connection basis. The OpenLDAP client appears to be
2147 able to handle the connection timeout per-connection
2148 but the Novell SDK cannot. Allowing the timeout to
2149 be set by each vhost is of little value so rather than
2150 trying to make special expections for one LDAP SDK, GLOBAL_ONLY
2151 is being enforced on this setting as well. */
2152 st->connectionTimeout = base->connectionTimeout;
2153 st->verify_svr_cert = base->verify_svr_cert;
2158 static apr_status_t util_ldap_cleanup_module(void *data)
2161 server_rec *s = data;
2162 util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
2163 s->module_config, &ldap_module);
2165 if (st->ssl_supported) {
2166 apr_ldap_ssl_deinit();
2173 static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog,
2174 apr_pool_t *ptemp, server_rec *s)
2176 apr_status_t result;
2177 server_rec *s_vhost;
2178 util_ldap_state_t *st_vhost;
2180 util_ldap_state_t *st = (util_ldap_state_t *)
2181 ap_get_module_config(s->module_config,
2185 const char *userdata_key = "util_ldap_init";
2186 apr_ldap_err_t *result_err = NULL;
2189 /* util_ldap_post_config() will be called twice. Don't bother
2190 * going through all of the initialization on the first call
2191 * because it will just be thrown away.*/
2192 apr_pool_userdata_get(&data, userdata_key, s->process->pool);
2194 apr_pool_userdata_set((const void *)1, userdata_key,
2195 apr_pool_cleanup_null, s->process->pool);
2197 #if APR_HAS_SHARED_MEMORY
2198 /* If the cache file already exists then delete it. Otherwise we are
2199 * going to run into problems creating the shared memory. */
2200 if (st->cache_file) {
2201 char *lck_file = apr_pstrcat(ptemp, st->cache_file, ".lck",
2203 apr_file_remove(lck_file, ptemp);
2209 #if APR_HAS_SHARED_MEMORY
2210 /* initializing cache if shared memory size is not zero and we already
2211 * don't have shm address
2213 if (!st->cache_shm && st->cache_bytes > 0) {
2215 result = util_ldap_cache_init(p, st);
2216 if (result != APR_SUCCESS) {
2217 ap_log_error(APLOG_MARK, APLOG_ERR, result, s,
2218 "LDAP cache: could not create shared memory segment");
2223 #if APR_HAS_SHARED_MEMORY
2224 if (st->cache_file) {
2225 st->lock_file = apr_pstrcat(st->pool, st->cache_file, ".lck",
2230 result = apr_global_mutex_create(&st->util_ldap_cache_lock,
2231 st->lock_file, APR_LOCK_DEFAULT,
2233 if (result != APR_SUCCESS) {
2237 #ifdef AP_NEED_SET_MUTEX_PERMS
2238 result = unixd_set_global_mutex_perms(st->util_ldap_cache_lock);
2239 if (result != APR_SUCCESS) {
2240 ap_log_error(APLOG_MARK, APLOG_CRIT, result, s,
2241 "LDAP cache: failed to set mutex permissions");
2246 /* merge config in all vhost */
2249 st_vhost = (util_ldap_state_t *)
2250 ap_get_module_config(s_vhost->module_config,
2253 #if APR_HAS_SHARED_MEMORY
2254 st_vhost->cache_shm = st->cache_shm;
2255 st_vhost->cache_rmm = st->cache_rmm;
2256 st_vhost->cache_file = st->cache_file;
2257 ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s,
2258 "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
2259 "for VHOST: %s", st->cache_shm, st->cache_rmm,
2260 s_vhost->server_hostname);
2262 st_vhost->lock_file = st->lock_file;
2263 s_vhost = s_vhost->next;
2265 #if APR_HAS_SHARED_MEMORY
2268 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
2269 "LDAP cache: LDAPSharedCacheSize is zero, disabling "
2270 "shared memory cache");
2274 /* log the LDAP SDK used
2277 apr_ldap_err_t *result = NULL;
2278 apr_ldap_info(p, &(result));
2279 if (result != NULL) {
2280 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s", result->reason);
2284 apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
2285 util_ldap_cleanup_module);
2288 * Initialize SSL support, and log the result for the benefit of the admin.
2290 * If SSL is not supported it is not necessarily an error, as the
2291 * application may not want to use it.
2293 rc = apr_ldap_ssl_init(p,
2297 if (APR_SUCCESS == rc) {
2298 rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
2299 (void *)st->global_certs, &(result_err));
2302 if (APR_SUCCESS == rc) {
2303 st->ssl_supported = 1;
2304 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2305 "LDAP: SSL support available" );
2308 st->ssl_supported = 0;
2309 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2310 "LDAP: SSL support unavailable%s%s",
2311 result_err ? ": " : "",
2312 result_err ? result_err->reason : "");
2318 static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
2321 util_ldap_state_t *st = ap_get_module_config(s->module_config,
2324 if (!st->util_ldap_cache_lock) return;
2326 sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
2328 if (sts != APR_SUCCESS) {
2329 ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s,
2330 "Failed to initialise global mutex %s in child process %"
2332 st->lock_file, getpid());
2336 static const command_rec util_ldap_cmds[] = {
2337 AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes,
2339 "Set the size of the shared memory cache (in bytes). Use "
2340 "0 to disable the shared memory cache. (default: 100000)"),
2342 AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file,
2344 "Set the file name for the shared memory cache."),
2346 AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries,
2348 "Set the maximum number of entries that are possible in the "
2349 "LDAP search cache. Use 0 for no limit. "
2350 "-1 disables the cache. (default: 1024)"),
2352 AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl,
2354 "Set the maximum time (in seconds) that an item can be "
2355 "cached in the LDAP search cache. Use 0 for no limit. "
2358 AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries,
2360 "Set the maximum number of entries that are possible "
2361 "in the LDAP compare cache. Use 0 for no limit. "
2362 "Use -1 to disable the cache. (default: 1024)"),
2364 AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl,
2366 "Set the maximum time (in seconds) that an item is cached "
2367 "in the LDAP operation cache. Use 0 for no limit. "
2370 AP_INIT_TAKE23("LDAPTrustedGlobalCert", util_ldap_set_trusted_global_cert,
2372 "Takes three args; the file and/or directory containing "
2373 "the trusted CA certificates (and global client certs "
2374 "for Netware) used to validate the LDAP server. Second "
2375 "arg is the cert type for the first arg, one of CA_DER, "
2376 "CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, "
2377 "CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, or KEY_BASE64. "
2378 "Third arg is an optional passphrase if applicable."),
2380 AP_INIT_TAKE23("LDAPTrustedClientCert", util_ldap_set_trusted_client_cert,
2382 "Takes three args; the file and/or directory containing "
2383 "the client certificate, or certificate ID used to "
2384 "validate this LDAP client. Second arg is the cert type "
2385 "for the first arg, one of CA_DER, CA_BASE64, CA_CERT7_DB, "
2386 "CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
2387 "CERT_NICKNAME, KEY_DER, or KEY_BASE64. Third arg is an "
2388 "optional passphrase if applicable."),
2390 AP_INIT_TAKE1("LDAPTrustedMode", util_ldap_set_trusted_mode,
2392 "Specify the type of security that should be applied to "
2393 "an LDAP connection. One of; NONE, SSL or STARTTLS."),
2395 AP_INIT_FLAG("LDAPVerifyServerCert", util_ldap_set_verify_srv_cert,
2397 "Set to 'ON' requires that the server certificate be verified "
2398 "before a secure LDAP connection can be establish. Default 'ON'"),
2400 AP_INIT_TAKE1("LDAPConnectionTimeout", util_ldap_set_connection_timeout,
2402 "Specify the LDAP socket connection timeout in seconds "
2408 static void util_ldap_register_hooks(apr_pool_t *p)
2410 APR_REGISTER_OPTIONAL_FN(uldap_connection_open);
2411 APR_REGISTER_OPTIONAL_FN(uldap_connection_close);
2412 APR_REGISTER_OPTIONAL_FN(uldap_connection_unbind);
2413 APR_REGISTER_OPTIONAL_FN(uldap_connection_cleanup);
2414 APR_REGISTER_OPTIONAL_FN(uldap_connection_find);
2415 APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn);
2416 APR_REGISTER_OPTIONAL_FN(uldap_cache_compare);
2417 APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid);
2418 APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn);
2419 APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported);
2420 APR_REGISTER_OPTIONAL_FN(uldap_cache_check_subgroups);
2422 ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
2423 ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
2424 ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
2427 module AP_MODULE_DECLARE_DATA ldap_module = {
2428 STANDARD20_MODULE_STUFF,
2429 NULL, /* create dir config */
2430 NULL, /* merge dir config */
2431 util_ldap_create_config, /* create server config */
2432 util_ldap_merge_config, /* merge server config */
2433 util_ldap_cmds, /* command table */
2434 util_ldap_register_hooks, /* set up request processing hooks */