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_mutex.h"
32 #include "util_ldap.h"
33 #include "util_ldap_cache.h"
35 #include <apr_strings.h>
42 #error mod_ldap requires APR-util to have LDAP support built in
45 /* Default define for ldap functions that need a SIZELIMIT but
46 * do not have the define
47 * XXX This should be removed once a supporting #define is
48 * released through APR-Util.
50 #ifndef APR_LDAP_SIZELIMIT
51 #define APR_LDAP_SIZELIMIT -1
54 #ifdef LDAP_OPT_DEBUG_LEVEL
55 #define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG_LEVEL
58 #define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG
62 #define AP_LDAP_HOPLIMIT_UNSET -1
63 #define AP_LDAP_CHASEREFERRALS_OFF 0
64 #define AP_LDAP_CHASEREFERRALS_ON 1
66 module AP_MODULE_DECLARE_DATA ldap_module;
67 static const char *ldap_cache_mutex_type = "ldap-cache";
69 #define LDAP_CACHE_LOCK() do { \
70 if (st->util_ldap_cache_lock) \
71 apr_global_mutex_lock(st->util_ldap_cache_lock); \
74 #define LDAP_CACHE_UNLOCK() do { \
75 if (st->util_ldap_cache_lock) \
76 apr_global_mutex_unlock(st->util_ldap_cache_lock); \
79 static apr_status_t util_ldap_connection_remove (void *param);
81 static void util_ldap_strdup (char **str, const char *newstr)
89 *str = strdup(newstr);
97 * This handler generates a status page about the current performance of
98 * the LDAP cache. It is enabled as follows:
100 * <Location /ldap-status>
101 * SetHandler ldap-status
105 static int util_ldap_handler(request_rec *r)
107 util_ldap_state_t *st = (util_ldap_state_t *)
108 ap_get_module_config(r->server->module_config,
111 r->allowed |= (1 << M_GET);
112 if (r->method_number != M_GET)
115 if (strcmp(r->handler, "ldap-status")) {
119 ap_set_content_type(r, "text/html; charset=ISO-8859-1");
124 ap_rputs(DOCTYPE_HTML_3_2
125 "<html><head><title>LDAP Cache Information</title></head>\n", r);
126 ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information"
129 util_ald_cache_display(r, st);
136 /* ------------------------------------------------------------------ */
138 * Closes an LDAP connection by unlocking it. The next time
139 * uldap_connection_find() is called this connection will be
140 * available for reuse.
142 static void uldap_connection_close(util_ldap_connection_t *ldc)
148 * Is it safe leaving bound connections floating around between the
149 * different modules? Keeping the user bound is a performance boost,
150 * but it is also a potential security problem - maybe.
152 * For now we unbind the user when we finish with a connection, but
153 * we don't have to...
157 util_ldap_connection_remove(ldc);
160 /* mark our connection as available for reuse */
162 apr_thread_mutex_unlock(ldc->lock);
169 * Destroys an LDAP connection by unbinding and closing the connection to
170 * the LDAP server. It is used to bring the connection back to a known
171 * state after an error.
173 static apr_status_t uldap_connection_unbind(void *param)
175 util_ldap_connection_t *ldc = param;
179 ldap_unbind_s(ldc->ldap);
190 * Clean up an LDAP connection by unbinding and unlocking the connection.
191 * This cleanup does not remove the util_ldap_connection_t from the
192 * per-virtualhost list of connections, does not remove the storage
193 * for the util_ldap_connection_t or it's data, and is NOT run automatically.
195 static apr_status_t uldap_connection_cleanup(void *param)
197 util_ldap_connection_t *ldc = param;
200 /* Release the rebind info for this connection. No more referral rebinds required. */
201 apr_ldap_rebind_remove(ldc->ldap);
203 /* unbind and disconnect from the LDAP server */
204 uldap_connection_unbind(ldc);
206 /* free the username and password */
208 free((void*)ldc->bindpw);
211 free((void*)ldc->binddn);
213 /* ldc->reason is allocated from r->pool */
217 /* unlock this entry */
218 uldap_connection_close(ldc);
226 * util_ldap_connection_remove frees all storage associated with the LDAP
227 * connection and removes it completely from the per-virtualhost list of
230 * The caller should hold the lock for this connection
232 static apr_status_t util_ldap_connection_remove (void *param) {
233 util_ldap_connection_t *ldc = param, *l = NULL, *prev = NULL;
234 util_ldap_state_t *st = ldc->st;
236 if (!ldc) return APR_SUCCESS;
238 uldap_connection_unbind(ldc);
241 apr_thread_mutex_lock(st->mutex);
244 /* Remove ldc from the list */
245 for (l=st->connections; l; l=l->next) {
248 prev->next = l->next;
251 st->connections = l->next;
258 /* Some unfortunate duplication between this method
259 * and uldap_connection_cleanup()
262 free((void*)ldc->bindpw);
265 free((void*)ldc->binddn);
269 apr_thread_mutex_unlock(ldc->lock);
270 apr_thread_mutex_unlock(st->mutex);
273 /* Destory the pool associated with this connection */
275 apr_pool_destroy(ldc->pool);
280 static int uldap_connection_init(request_rec *r,
281 util_ldap_connection_t *ldc)
283 int rc = 0, ldap_option = 0;
284 int version = LDAP_VERSION3;
285 apr_ldap_err_t *result = NULL;
286 #ifdef LDAP_OPT_NETWORK_TIMEOUT
287 struct timeval connectionTimeout = {10,0}; /* 10 second connection timeout */
289 util_ldap_state_t *st =
290 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
293 /* Since the host will include a port if the default port is not used,
294 * always specify the default ports for the port parameter. This will
295 * allow a host string that contains multiple hosts the ability to mix
296 * some hosts with ports and some without. All hosts which do not
297 * specify a port will use the default port.
299 apr_ldap_init(r->pool, &(ldc->ldap),
301 APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
305 if (NULL == result) {
306 /* something really bad happened */
308 if (NULL == ldc->reason) {
309 ldc->reason = "LDAP: ldap initialization failed";
311 return(APR_EGENERAL);
315 ldc->reason = result->reason;
318 if (NULL == ldc->ldap)
321 if (NULL == ldc->reason) {
322 ldc->reason = "LDAP: ldap initialization failed";
325 ldc->reason = result->reason;
330 /* Now that we have an ldap struct, add it to the referral list for rebinds. */
331 rc = apr_ldap_rebind_add(ldc->pool, ldc->ldap, ldc->binddn, ldc->bindpw);
332 if (rc != APR_SUCCESS) {
333 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
334 "LDAP: Unable to add rebind cross reference entry. Out of memory?");
335 uldap_connection_unbind(ldc);
336 ldc->reason = "LDAP: Unable to add rebind cross reference entry.";
340 /* always default to LDAP V3 */
341 ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
343 /* set client certificates */
344 if (!apr_is_empty_array(ldc->client_certs)) {
345 apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
346 ldc->client_certs, &(result));
347 if (LDAP_SUCCESS != result->rc) {
348 uldap_connection_unbind( ldc );
349 ldc->reason = result->reason;
354 /* switch on SSL/TLS */
355 if (APR_LDAP_NONE != ldc->secure) {
356 apr_ldap_set_option(r->pool, ldc->ldap,
357 APR_LDAP_OPT_TLS, &ldc->secure, &(result));
358 if (LDAP_SUCCESS != result->rc) {
359 uldap_connection_unbind( ldc );
360 ldc->reason = result->reason;
365 /* Set the alias dereferencing option */
366 ldap_option = ldc->deref;
367 ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);
369 /* Set options for rebind and referrals. */
370 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
371 "LDAP: Setting referrals to %s.",
372 ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"));
373 apr_ldap_set_option(r->pool, ldc->ldap,
374 APR_LDAP_OPT_REFERRALS,
375 (void *)((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ?
376 LDAP_OPT_ON : LDAP_OPT_OFF),
378 if (result->rc != LDAP_SUCCESS) {
379 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
380 "Unable to set LDAP_OPT_REFERRALS option to %s: %d.",
381 ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"),
383 result->reason = "Unable to set LDAP_OPT_REFERRALS.";
384 ldc->reason = result->reason;
385 uldap_connection_unbind(ldc);
389 if ((ldc->ReferralHopLimit != AP_LDAP_HOPLIMIT_UNSET) && ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
390 /* Referral hop limit - only if referrals are enabled and a hop limit is explicitly requested */
391 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
392 "Setting referral hop limit to %d.",
393 ldc->ReferralHopLimit);
394 apr_ldap_set_option(r->pool, ldc->ldap,
395 APR_LDAP_OPT_REFHOPLIMIT,
396 (void *)&ldc->ReferralHopLimit,
398 if (result->rc != LDAP_SUCCESS) {
399 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
400 "Unable to set LDAP_OPT_REFHOPLIMIT option to %d: %d.",
401 ldc->ReferralHopLimit,
403 result->reason = "Unable to set LDAP_OPT_REFHOPLIMIT.";
404 ldc->reason = result->reason;
405 uldap_connection_unbind(ldc);
410 /*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
411 #ifdef APR_LDAP_OPT_VERIFY_CERT
412 apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_VERIFY_CERT,
413 &(st->verify_svr_cert), &(result));
415 #if defined(LDAPSSL_VERIFY_SERVER)
416 if (st->verify_svr_cert) {
417 result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
420 result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
422 #elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
423 /* This is not a per-connection setting so just pass NULL for the
424 Ldap connection handle */
425 if (st->verify_svr_cert) {
426 int i = LDAP_OPT_X_TLS_DEMAND;
427 result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
430 int i = LDAP_OPT_X_TLS_NEVER;
431 result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
436 #ifdef LDAP_OPT_NETWORK_TIMEOUT
437 if (st->connectionTimeout > 0) {
438 connectionTimeout.tv_sec = st->connectionTimeout;
441 if (st->connectionTimeout >= 0) {
442 rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
443 (void *)&connectionTimeout, &(result));
444 if (APR_SUCCESS != rc) {
445 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
446 "LDAP: Could not set the connection timeout");
451 #ifdef LDAP_OPT_TIMEOUT
453 * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap
454 * function calls and not just ldap_search_ext_s(), which accepts a timeout
456 * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all
457 * XXX: synchronous ldap function calls with asynchronous calls and using
458 * XXX: ldap_result() with a timeout.
461 rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_TIMEOUT,
462 st->opTimeout, &(result));
463 if (APR_SUCCESS != rc) {
464 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
465 "LDAP: Could not set LDAP_OPT_TIMEOUT");
474 * Replacement function for ldap_simple_bind_s() with a timeout.
475 * To do this in a portable way, we have to use ldap_simple_bind() and
478 * Returns LDAP_SUCCESS on success; and an error code on failure
480 static int uldap_simple_bind(util_ldap_connection_t *ldc, char *binddn,
481 char* bindpw, struct timeval *timeout)
485 int msgid = ldap_simple_bind(ldc->ldap, binddn, bindpw);
487 ldc->reason = "LDAP: ldap_simple_bind() failed";
488 /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
491 rc = ldap_result(ldc->ldap, msgid, 0, timeout, &result);
493 ldc->reason = "LDAP: ldap_simple_bind() result retrieval failed";
494 /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
498 ldc->reason = "LDAP: ldap_simple_bind() timed out";
500 } else if (ldap_parse_result(ldc->ldap, result, &rc, NULL, NULL, NULL,
502 ldc->reason = "LDAP: ldap_simple_bind() parse result failed";
508 * Connect to the LDAP server and binds. Does not connect if already
509 * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
511 * Returns LDAP_SUCCESS on success; and an error code on failure
513 static int uldap_connection_open(request_rec *r,
514 util_ldap_connection_t *ldc)
518 int new_connection = 0;
519 util_ldap_state_t *st;
521 /* sanity check for NULL */
526 /* If the connection is already bound, return
530 ldc->reason = "LDAP: connection open successful (already bound)";
534 /* create the ldap session handle
536 if (NULL == ldc->ldap)
539 rc = uldap_connection_init( r, ldc );
540 if (LDAP_SUCCESS != rc)
547 st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
550 /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is
551 * returned. If LDAP_TIMEOUT is returned on the first try, maybe the
552 * connection was idle for a long time and has been dropped by a firewall.
553 * In this case close the connection immediately and try again.
555 * On Success or any other error, break out of the loop.
557 * NOTE: Looping is probably not a great idea. If the server isn't
558 * responding the chances it will respond after a few tries are poor.
559 * However, the original code looped and it only happens on
560 * the error condition.
562 for (failures=0; failures<10; failures++)
564 rc = uldap_simple_bind(ldc, (char *)ldc->binddn, (char *)ldc->bindpw,
566 if ((AP_LDAP_IS_SERVER_DOWN(rc) && failures == 5) ||
567 (rc == LDAP_TIMEOUT && failures == 0))
569 if (rc == LDAP_TIMEOUT && !new_connection) {
570 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
571 "ldap_simple_bind() timed out on reused "
572 "connection, dropped by firewall?");
574 /* attempt to init the connection once again */
575 uldap_connection_unbind( ldc );
576 rc = uldap_connection_init( r, ldc );
577 if (LDAP_SUCCESS != rc)
582 else if (!AP_LDAP_IS_SERVER_DOWN(rc)) {
587 /* free the handle if there was an error
589 if (LDAP_SUCCESS != rc)
591 uldap_connection_unbind(ldc);
592 ldc->reason = "LDAP: ldap_simple_bind() failed";
596 ldc->reason = "LDAP: connection open successful";
604 * Compare client certificate arrays.
606 * Returns 1 on compare failure, 0 otherwise.
608 static int compare_client_certs(apr_array_header_t *srcs,
609 apr_array_header_t *dests)
612 struct apr_ldap_opt_tls_cert_t *src, *dest;
614 /* arrays both NULL? if so, then equal */
615 if (srcs == NULL && dests == NULL) {
619 /* arrays different length or either NULL? If so, then not equal */
620 if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
624 /* run an actual comparison */
625 src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
626 dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
627 for (i = 0; i < srcs->nelts; i++) {
628 if ((strcmp(src[i].path, dest[i].path)) ||
629 (src[i].type != dest[i].type) ||
630 /* One is passwordless? If so, then not equal */
631 ((src[i].password == NULL) ^ (dest[i].password == NULL)) ||
632 (src[i].password != NULL && dest[i].password != NULL &&
633 strcmp(src[i].password, dest[i].password))) {
638 /* if we got here, the cert arrays were identical */
645 * Find an existing ldap connection struct that matches the
646 * provided ldap connection parameters.
648 * If not found in the cache, a new ldc structure will be allocated
649 * from st->pool and returned to the caller. If found in the cache,
650 * a pointer to the existing ldc structure will be returned.
652 static util_ldap_connection_t *
653 uldap_connection_find(request_rec *r,
654 const char *host, int port,
655 const char *binddn, const char *bindpw,
656 deref_options deref, int secure)
658 struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
659 int secureflag = secure;
661 util_ldap_state_t *st =
662 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
664 util_ldap_config_t *dc =
665 (util_ldap_config_t *) ap_get_module_config(r->per_dir_config, &ldap_module);
668 /* mutex lock this function */
669 apr_thread_mutex_lock(st->mutex);
672 if (secure < APR_LDAP_NONE) {
673 secureflag = st->secure;
676 /* Search for an exact connection match in the list that is not
679 for (l=st->connections,p=NULL; l; l=l->next) {
681 if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
683 if ( (l->port == port) && (strcmp(l->host, host) == 0)
684 && ((!l->binddn && !binddn) || (l->binddn && binddn
685 && !strcmp(l->binddn, binddn)))
686 && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
687 && !strcmp(l->bindpw, bindpw)))
688 && (l->deref == deref) && (l->secure == secureflag)
689 && !compare_client_certs(st->client_certs, l->client_certs))
694 /* If this connection didn't match the criteria, then we
695 * need to unlock the mutex so it is available to be reused.
697 apr_thread_mutex_unlock(l->lock);
703 /* If nothing found, search again, but we don't care about the
704 * binddn and bindpw this time.
707 for (l=st->connections,p=NULL; l; l=l->next) {
709 if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
712 if ((l->port == port) && (strcmp(l->host, host) == 0) &&
713 (l->deref == deref) && (l->secure == secureflag) &&
714 !compare_client_certs(st->client_certs, l->client_certs))
716 /* the bind credentials have changed */
718 util_ldap_strdup((char**)&(l->binddn), binddn);
719 util_ldap_strdup((char**)&(l->bindpw), bindpw);
723 /* If this connection didn't match the criteria, then we
724 * need to unlock the mutex so it is available to be reused.
726 apr_thread_mutex_unlock(l->lock);
733 /* artificially disable cache */
736 /* If no connection was found after the second search, we
741 if (apr_pool_create(&newpool, NULL) != APR_SUCCESS) {
742 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
743 "util_ldap: Failed to create memory pool");
745 apr_thread_mutex_unlock(st->mutex);
751 * Add the new connection entry to the linked list. Note that we
752 * don't actually establish an LDAP connection yet; that happens
753 * the first time authentication is requested.
756 /* create the details of this connection in the new pool */
757 l = apr_pcalloc(newpool, sizeof(util_ldap_connection_t));
762 apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, l->pool);
763 apr_thread_mutex_lock(l->lock);
766 l->host = apr_pstrdup(l->pool, host);
769 util_ldap_strdup((char**)&(l->binddn), binddn);
770 util_ldap_strdup((char**)&(l->bindpw), bindpw);
771 l->ChaseReferrals = dc->ChaseReferrals;
772 l->ReferralHopLimit = dc->ReferralHopLimit;
774 /* The security mode after parsing the URL will always be either
775 * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
776 * If the security setting is NONE, override it to the security
777 * setting optionally supplied by the admin using LDAPTrustedMode
779 l->secure = secureflag;
781 /* save away a copy of the client cert list that is presently valid */
782 l->client_certs = apr_array_copy_hdr(l->pool, st->client_certs);
795 apr_thread_mutex_unlock(st->mutex);
800 /* ------------------------------------------------------------------ */
803 * Compares two DNs to see if they're equal. The only way to do this correctly
804 * is to search for the dn and then do ldap_get_dn() on the result. This should
805 * match the initial dn, since it would have been also retrieved with
806 * ldap_get_dn(). This is expensive, so if the configuration value
807 * compare_dn_on_server is false, just does an ordinary strcmp.
809 * The lock for the ldap cache should already be acquired.
811 static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
812 const char *url, const char *dn,
813 const char *reqdn, int compare_dn_on_server)
816 util_url_node_t *curl;
817 util_url_node_t curnode;
818 util_dn_compare_node_t *node;
819 util_dn_compare_node_t newnode;
821 LDAPMessage *res, *entry;
824 util_ldap_state_t *st = (util_ldap_state_t *)
825 ap_get_module_config(r->server->module_config,
828 /* get cache entry (or create one) */
832 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
834 curl = util_ald_create_caches(st, url);
838 /* a simple compare? */
839 if (!compare_dn_on_server) {
840 /* unlock this read lock */
841 if (strcmp(dn, reqdn)) {
842 ldc->reason = "DN Comparison FALSE (direct strcmp())";
843 return LDAP_COMPARE_FALSE;
846 ldc->reason = "DN Comparison TRUE (direct strcmp())";
847 return LDAP_COMPARE_TRUE;
852 /* no - it's a server side compare */
855 /* is it in the compare cache? */
856 newnode.reqdn = (char *)reqdn;
857 node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
859 /* If it's in the cache, it's good */
860 /* unlock this read lock */
862 ldc->reason = "DN Comparison TRUE (cached)";
863 return LDAP_COMPARE_TRUE;
866 /* unlock this read lock */
871 if (failures++ > 10) {
872 /* too many failures */
876 /* make a server connection */
877 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
878 /* connect to server failed */
882 /* search for reqdn */
883 result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
884 "(objectclass=*)", NULL, 1,
885 NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
886 if (AP_LDAP_IS_SERVER_DOWN(result))
888 ldc->reason = "DN Comparison ldap_search_ext_s() "
889 "failed with server down";
890 uldap_connection_unbind(ldc);
893 if (result == LDAP_TIMEOUT && failures == 0) {
895 * we are reusing a connection that doesn't seem to be active anymore
896 * (firewall state drop?), let's try a new connection.
898 ldc->reason = "DN Comparison ldap_search_ext_s() "
899 "failed with timeout";
900 uldap_connection_unbind(ldc);
903 if (result != LDAP_SUCCESS) {
904 /* search for reqdn failed - no match */
905 ldc->reason = "DN Comparison ldap_search_ext_s() failed";
909 entry = ldap_first_entry(ldc->ldap, res);
910 searchdn = ldap_get_dn(ldc->ldap, entry);
913 if (strcmp(dn, searchdn) != 0) {
914 /* compare unsuccessful */
915 ldc->reason = "DN Comparison FALSE (checked on server)";
916 result = LDAP_COMPARE_FALSE;
920 /* compare successful - add to the compare cache */
922 newnode.reqdn = (char *)reqdn;
923 newnode.dn = (char *)dn;
925 node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
927 || (strcmp(reqdn, node->reqdn) != 0)
928 || (strcmp(dn, node->dn) != 0))
930 util_ald_cache_insert(curl->dn_compare_cache, &newnode);
934 ldc->reason = "DN Comparison TRUE (checked on server)";
935 result = LDAP_COMPARE_TRUE;
937 ldap_memfree(searchdn);
943 * Does an generic ldap_compare operation. It accepts a cache that it will use
944 * to lookup the compare in the cache. We cache two kinds of compares
945 * (require group compares) and (require user compares). Each compare has a
946 * different cache node: require group includes the DN; require user does not
947 * because the require user cache is owned by the
950 static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
951 const char *url, const char *dn,
952 const char *attrib, const char *value)
955 util_url_node_t *curl;
956 util_url_node_t curnode;
957 util_compare_node_t *compare_nodep;
958 util_compare_node_t the_compare_node;
959 apr_time_t curtime = 0; /* silence gcc -Wall */
962 util_ldap_state_t *st = (util_ldap_state_t *)
963 ap_get_module_config(r->server->module_config,
966 /* get cache entry (or create one) */
969 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
971 curl = util_ald_create_caches(st, url);
976 /* make a comparison to the cache */
978 curtime = apr_time_now();
980 the_compare_node.dn = (char *)dn;
981 the_compare_node.attrib = (char *)attrib;
982 the_compare_node.value = (char *)value;
983 the_compare_node.result = 0;
984 the_compare_node.sgl_processed = 0;
985 the_compare_node.subgroupList = NULL;
987 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
990 if (compare_nodep != NULL) {
992 if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
993 /* ...but it is too old */
994 util_ald_cache_remove(curl->compare_cache, compare_nodep);
997 /* ...and it is good */
998 if (LDAP_COMPARE_TRUE == compare_nodep->result) {
999 ldc->reason = "Comparison true (cached)";
1001 else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
1002 ldc->reason = "Comparison false (cached)";
1004 else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
1005 ldc->reason = "Comparison no such attribute (cached)";
1008 ldc->reason = "Comparison undefined (cached)";
1011 /* record the result code to return with the reason... */
1012 result = compare_nodep->result;
1013 /* and unlock this read lock */
1014 LDAP_CACHE_UNLOCK();
1018 /* unlock this read lock */
1019 LDAP_CACHE_UNLOCK();
1023 if (failures++ > 10) {
1024 /* too many failures */
1028 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1029 /* connect failed */
1033 result = ldap_compare_s(ldc->ldap,
1037 if (AP_LDAP_IS_SERVER_DOWN(result)) {
1038 /* connection failed - try again */
1039 ldc->reason = "ldap_compare_s() failed with server down";
1040 uldap_connection_unbind(ldc);
1043 if (result == LDAP_TIMEOUT && failures == 0) {
1045 * we are reusing a connection that doesn't seem to be active anymore
1046 * (firewall state drop?), let's try a new connection.
1048 ldc->reason = "ldap_compare_s() failed with timeout";
1049 uldap_connection_unbind(ldc);
1053 ldc->reason = "Comparison complete";
1054 if ((LDAP_COMPARE_TRUE == result) ||
1055 (LDAP_COMPARE_FALSE == result) ||
1056 (LDAP_NO_SUCH_ATTRIBUTE == result)) {
1058 /* compare completed; caching result */
1060 the_compare_node.lastcompare = curtime;
1061 the_compare_node.result = result;
1062 the_compare_node.sgl_processed = 0;
1063 the_compare_node.subgroupList = NULL;
1065 /* If the node doesn't exist then insert it, otherwise just update
1066 * it with the last results
1068 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1070 if ( (compare_nodep == NULL)
1071 || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
1072 || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
1073 || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
1077 junk = util_ald_cache_insert(curl->compare_cache,
1080 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1081 "[%" APR_PID_T_FMT "] cache_compare: Cache"
1082 " insertion failure.", getpid());
1086 compare_nodep->lastcompare = curtime;
1087 compare_nodep->result = result;
1089 LDAP_CACHE_UNLOCK();
1091 if (LDAP_COMPARE_TRUE == result) {
1092 ldc->reason = "Comparison true (adding to cache)";
1093 return LDAP_COMPARE_TRUE;
1095 else if (LDAP_COMPARE_FALSE == result) {
1096 ldc->reason = "Comparison false (adding to cache)";
1097 return LDAP_COMPARE_FALSE;
1100 ldc->reason = "Comparison no such attribute (adding to cache)";
1101 return LDAP_NO_SUCH_ATTRIBUTE;
1108 static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r,
1109 util_ldap_connection_t *ldc,
1112 char **subgroupAttrs,
1113 apr_array_header_t *subgroupclasses)
1116 int result = LDAP_COMPARE_FALSE;
1117 util_compare_subgroup_t *res = NULL;
1118 LDAPMessage *sga_res, *entry;
1119 struct mod_auth_ldap_groupattr_entry_t *sgc_ents;
1120 apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
1122 sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1124 if (!subgroupAttrs) {
1130 * 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups.
1132 if (failures++ > 10) {
1133 /* too many failures */
1137 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1138 /* connect failed */
1142 /* try to do the search */
1143 result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
1144 (char *)"cn=*", subgroupAttrs, 0,
1145 NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
1146 if (AP_LDAP_IS_SERVER_DOWN(result)) {
1147 ldc->reason = "ldap_search_ext_s() for subgroups failed with server"
1149 uldap_connection_unbind(ldc);
1152 if (result == LDAP_TIMEOUT && failures == 0) {
1154 * we are reusing a connection that doesn't seem to be active anymore
1155 * (firewall state drop?), let's try a new connection.
1157 ldc->reason = "ldap_search_ext_s() for subgroups failed with timeout";
1158 uldap_connection_unbind(ldc);
1162 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1163 if (result != LDAP_SUCCESS) {
1164 ldc->reason = "ldap_search_ext_s() for subgroups failed";
1168 entry = ldap_first_entry(ldc->ldap, sga_res);
1171 * Get values for the provided sub-group attributes.
1173 if (subgroupAttrs) {
1174 int indx = 0, tmp_sgcIndex;
1176 while (subgroupAttrs[indx]) {
1180 /* Get *all* matching "member" values from this group. */
1181 values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
1186 * Now we are going to pare the subgroup members of this group
1187 * to *just* the subgroups, add them to the compare_nodep, and
1188 * then proceed to check the new level of subgroups.
1190 while (values[val_index]) {
1191 /* Check if this entry really is a group. */
1193 result = LDAP_COMPARE_FALSE;
1194 while ((tmp_sgcIndex < subgroupclasses->nelts)
1195 && (result != LDAP_COMPARE_TRUE)) {
1196 result = uldap_cache_compare(r, ldc, url,
1199 sgc_ents[tmp_sgcIndex].name
1202 if (result != LDAP_COMPARE_TRUE) {
1206 /* It's a group, so add it to the array. */
1207 if (result == LDAP_COMPARE_TRUE) {
1208 char **newgrp = (char **) apr_array_push(subgroups);
1209 *newgrp = apr_pstrdup(r->pool, values[val_index]);
1213 ldap_value_free(values);
1219 ldap_msgfree(sga_res);
1221 if (subgroups->nelts > 0) {
1222 /* We need to fill in tmp_local_subgroups using the data from LDAP */
1225 res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
1226 res->subgroupDNs = apr_pcalloc(r->pool,
1227 sizeof(char *) * (subgroups->nelts));
1228 for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
1229 res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
1239 * Does a recursive lookup operation to try to find a user within (cached)
1240 * nested groups. It accepts a cache that it will use to lookup previous
1241 * compare attempts. We cache two kinds of compares (require group compares)
1242 * and (require user compares). Each compare has a different cache node:
1243 * require group includes the DN; require user does not because the require
1244 * user cache is owned by the
1246 * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
1249 * 1. Call uldap_cache_compare for each subgroupclass value to check the
1250 * generic, user-agnostic, cached group entry. This will create a new generic
1251 * cache entry if there
1252 * wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we
1254 * 2. Lock The cache and get the generic cache entry.
1255 * 3. Check if there is already a subgrouplist in this generic group's cache
1257 * A. If there is, go to step 4.
1258 * B. If there isn't:
1259 * i) Use ldap_search to get the full list
1260 * of subgroup "members" (which may include non-group "members").
1261 * ii) Use uldap_cache_compare to strip the list down to just groups.
1262 * iii) Lock and add this stripped down list to the cache of the generic
1264 * 4. Loop through the sgl and call uldap_cache_compare (using the user info)
1266 * subgroup to see if the subgroup contains the user and to get the subgroups
1268 * cache (with user-afinity, if they aren't already there).
1269 * A. If the user is in the subgroup, then we'll be returning
1270 * LDAP_COMPARE_TRUE.
1271 * B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via
1272 * uldap_cache_compare) then recursively call this function to get the
1273 * sub-subgroups added...
1274 * 5. Cleanup local allocations.
1275 * 6. Return the final result.
1278 static int uldap_cache_check_subgroups(request_rec *r,
1279 util_ldap_connection_t *ldc,
1280 const char *url, const char *dn,
1281 const char *attrib, const char *value,
1282 char **subgroupAttrs,
1283 apr_array_header_t *subgroupclasses,
1284 int cur_subgroup_depth,
1285 int max_subgroup_depth)
1287 int result = LDAP_COMPARE_FALSE;
1288 util_url_node_t *curl;
1289 util_url_node_t curnode;
1290 util_compare_node_t *compare_nodep;
1291 util_compare_node_t the_compare_node;
1292 util_compare_subgroup_t *tmp_local_sgl = NULL;
1293 int sgl_cached_empty = 0, sgindex = 0, base_sgcIndex = 0;
1294 struct mod_auth_ldap_groupattr_entry_t *sgc_ents =
1295 (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1296 util_ldap_state_t *st = (util_ldap_state_t *)
1297 ap_get_module_config(r->server->module_config,
1301 * Stop looking at deeper levels of nested groups if we have reached the
1302 * max. Since we already checked the top-level group in uldap_cache_compare,
1303 * we don't need to check it again here - so if max_subgroup_depth is set
1304 * to 0, we won't check it (i.e. that is why we check < rather than <=).
1305 * We'll be calling uldap_cache_compare from here to check if the user is
1306 * in the next level before we recurse into that next level looking for
1309 if (cur_subgroup_depth >= max_subgroup_depth) {
1310 return LDAP_COMPARE_FALSE;
1314 * 1. Check the "groupiness" of the specified basedn. Stopping at the first
1317 while ((base_sgcIndex < subgroupclasses->nelts)
1318 && (result != LDAP_COMPARE_TRUE)) {
1319 result = uldap_cache_compare(r, ldc, url, dn, "objectClass",
1320 sgc_ents[base_sgcIndex].name);
1321 if (result != LDAP_COMPARE_TRUE) {
1326 if (result != LDAP_COMPARE_TRUE) {
1327 ldc->reason = "DN failed group verification.";
1332 * 2. Find previously created cache entry and check if there is already a
1337 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
1338 LDAP_CACHE_UNLOCK();
1340 if (curl && curl->compare_cache) {
1341 /* make a comparison to the cache */
1344 the_compare_node.dn = (char *)dn;
1345 the_compare_node.attrib = (char *)"objectClass";
1346 the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1347 the_compare_node.result = 0;
1348 the_compare_node.sgl_processed = 0;
1349 the_compare_node.subgroupList = NULL;
1351 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1354 if (compare_nodep != NULL) {
1356 * Found the generic group entry... but the user isn't in this
1357 * group or we wouldn't be here.
1359 if (compare_nodep->sgl_processed) {
1360 if (compare_nodep->subgroupList) {
1361 /* Make a local copy of the subgroup list */
1363 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1364 "[%" APR_PID_T_FMT "] util_ldap:"
1365 " Making local copy of SGL for "
1366 "group (%s)(objectClass=%s) ",
1368 (char *)sgc_ents[base_sgcIndex].name);
1369 tmp_local_sgl = apr_pcalloc(r->pool,
1370 sizeof(util_compare_subgroup_t));
1371 tmp_local_sgl->len = compare_nodep->subgroupList->len;
1372 tmp_local_sgl->subgroupDNs =
1373 apr_pcalloc(r->pool,
1374 sizeof(char *) * compare_nodep->subgroupList->len);
1375 for (i = 0; i < compare_nodep->subgroupList->len; i++) {
1376 tmp_local_sgl->subgroupDNs[i] =
1377 apr_pstrdup(r->pool,
1378 compare_nodep->subgroupList->subgroupDNs[i]);
1382 sgl_cached_empty = 1;
1386 LDAP_CACHE_UNLOCK();
1389 if (!tmp_local_sgl && !sgl_cached_empty) {
1390 /* No Cached SGL, retrieve from LDAP */
1391 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1392 "[%" APR_PID_T_FMT "] util_ldap: no cached SGL for %s,"
1393 " retrieving from LDAP" , getpid(), dn);
1394 tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs,
1396 if (!tmp_local_sgl) {
1397 /* No SGL aailable via LDAP either */
1398 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "]"
1399 " util_ldap: no subgroups for %s" , getpid(), dn);
1402 if (curl && curl->compare_cache) {
1404 * Find the generic group cache entry and add the sgl we just retrieved.
1408 the_compare_node.dn = (char *)dn;
1409 the_compare_node.attrib = (char *)"objectClass";
1410 the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1411 the_compare_node.result = 0;
1412 the_compare_node.sgl_processed = 0;
1413 the_compare_node.subgroupList = NULL;
1415 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1418 if (compare_nodep == NULL) {
1420 * The group entry we want to attach our SGL to doesn't exist.
1421 * We only got here if we verified this DN was actually a group
1422 * based on the objectClass, but we can't call the compare function
1423 * while we already hold the cache lock -- only the insert.
1425 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1426 "[%" APR_PID_T_FMT "] util_ldap: Cache entry "
1427 "for %s doesn't exist",
1429 the_compare_node.result = LDAP_COMPARE_TRUE;
1430 util_ald_cache_insert(curl->compare_cache, &the_compare_node);
1431 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1433 if (compare_nodep == NULL) {
1434 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1435 "[%" APR_PID_T_FMT "] util_ldap: Couldn't "
1436 "retrieve group entry for %s from cache",
1442 * We have a valid cache entry and a locally generated SGL.
1443 * Attach the SGL to the cache entry
1445 if (compare_nodep && !compare_nodep->sgl_processed) {
1446 if (!tmp_local_sgl) {
1447 /* We looked up an SGL for a group and found it to be empty */
1448 if (compare_nodep->subgroupList == NULL) {
1449 compare_nodep->sgl_processed = 1;
1453 util_compare_subgroup_t *sgl_copy =
1454 util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
1455 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1456 "Copying local SGL of len %d for group %s into cache",
1457 tmp_local_sgl->len, dn);
1459 if (compare_nodep->subgroupList) {
1460 util_ald_sgl_free(curl->compare_cache,
1461 &(compare_nodep->subgroupList));
1463 compare_nodep->subgroupList = sgl_copy;
1464 compare_nodep->sgl_processed = 1;
1467 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1468 "Copy of SGL failed to obtain shared memory, "
1469 "couldn't update cache");
1473 LDAP_CACHE_UNLOCK();
1478 * tmp_local_sgl has either been created, or copied out of the cache
1479 * If tmp_local_sgl is NULL, there are no subgroups to process and we'll
1482 result = LDAP_COMPARE_FALSE;
1483 if (!tmp_local_sgl) {
1487 while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
1488 const char *group = NULL;
1489 group = tmp_local_sgl->subgroupDNs[sgindex];
1491 * 4. Now loop through the subgroupList and call uldap_cache_compare
1492 * to check for the user.
1494 result = uldap_cache_compare(r, ldc, url, group, attrib, value);
1495 if (result == LDAP_COMPARE_TRUE) {
1497 * 4.A. We found the user in the subgroup. Return
1498 * LDAP_COMPARE_TRUE.
1500 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "]"
1501 " util_ldap: Found user %s in a subgroup (%s) at"
1502 " level %d of %d.", getpid(), r->user, group,
1503 cur_subgroup_depth+1, max_subgroup_depth);
1507 * 4.B. We didn't find the user in this subgroup, so recurse into
1508 * it and keep looking.
1510 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "]"
1511 " util_ldap: user %s not found in subgroup (%s) at"
1512 " level %d of %d.", getpid(), r->user, group,
1513 cur_subgroup_depth+1, max_subgroup_depth);
1514 result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
1515 value, subgroupAttrs,
1517 cur_subgroup_depth+1,
1518 max_subgroup_depth);
1527 static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
1528 const char *url, const char *basedn,
1529 int scope, char **attrs, const char *filter,
1530 const char *bindpw, const char **binddn,
1531 const char ***retvals)
1533 const char **vals = NULL;
1536 LDAPMessage *res, *entry;
1540 util_url_node_t *curl; /* Cached URL node */
1541 util_url_node_t curnode;
1542 util_search_node_t *search_nodep; /* Cached search node */
1543 util_search_node_t the_search_node;
1546 util_ldap_state_t *st =
1547 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1550 /* Get the cache node for this url */
1553 curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1556 curl = util_ald_create_caches(st, url);
1558 LDAP_CACHE_UNLOCK();
1562 the_search_node.username = filter;
1563 search_nodep = util_ald_cache_fetch(curl->search_cache,
1565 if (search_nodep != NULL) {
1567 /* found entry in search cache... */
1568 curtime = apr_time_now();
1571 * Remove this item from the cache if its expired. If the sent
1572 * password doesn't match the storepassword, the entry will
1573 * be removed and readded later if the credentials pass
1576 if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1577 /* ...but entry is too old */
1578 util_ald_cache_remove(curl->search_cache, search_nodep);
1580 else if ( (search_nodep->bindpw)
1581 && (search_nodep->bindpw[0] != '\0')
1582 && (strcmp(search_nodep->bindpw, bindpw) == 0))
1584 /* ...and entry is valid */
1585 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1588 *retvals = apr_pcalloc(r->pool, sizeof(char *) * search_nodep->numvals);
1589 for (i = 0; i < search_nodep->numvals; i++) {
1590 (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1593 LDAP_CACHE_UNLOCK();
1594 ldc->reason = "Authentication successful (cached)";
1595 return LDAP_SUCCESS;
1598 /* unlock this read lock */
1599 LDAP_CACHE_UNLOCK();
1603 * At this point, there is no valid cached search, so lets do the search.
1607 * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1610 if (failures++ > 10) {
1613 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1617 /* try do the search */
1618 result = ldap_search_ext_s(ldc->ldap,
1619 (char *)basedn, scope,
1620 (char *)filter, attrs, 0,
1621 NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
1622 if (AP_LDAP_IS_SERVER_DOWN(result))
1624 ldc->reason = "ldap_search_ext_s() for user failed with server down";
1625 uldap_connection_unbind(ldc);
1629 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1630 if (result != LDAP_SUCCESS) {
1631 ldc->reason = "ldap_search_ext_s() for user failed";
1636 * We should have found exactly one entry; to find a different
1637 * number is an error.
1639 count = ldap_count_entries(ldc->ldap, res);
1643 ldc->reason = "User not found";
1645 ldc->reason = "User is not unique (search found two "
1648 return LDAP_NO_SUCH_OBJECT;
1651 entry = ldap_first_entry(ldc->ldap, res);
1653 /* Grab the dn, copy it into the pool, and free it again */
1654 dn = ldap_get_dn(ldc->ldap, entry);
1655 *binddn = apr_pstrdup(r->pool, dn);
1659 * A bind to the server with an empty password always succeeds, so
1660 * we check to ensure that the password is not empty. This implies
1661 * that users who actually do have empty passwords will never be
1662 * able to authenticate with this module. I don't see this as a big
1665 if (!bindpw || strlen(bindpw) <= 0) {
1667 ldc->reason = "Empty password not allowed";
1668 return LDAP_INVALID_CREDENTIALS;
1672 * Attempt to bind with the retrieved dn and the password. If the bind
1673 * fails, it means that the password is wrong (the dn obviously
1674 * exists, since we just retrieved it)
1676 result = uldap_simple_bind(ldc, (char *)*binddn, (char *)bindpw,
1678 if (AP_LDAP_IS_SERVER_DOWN(result) ||
1679 (result == LDAP_TIMEOUT && failures == 0)) {
1680 if (AP_LDAP_IS_SERVER_DOWN(result))
1681 ldc->reason = "ldap_simple_bind() to check user credentials "
1682 "failed with server down";
1684 ldc->reason = "ldap_simple_bind() to check user credentials "
1687 uldap_connection_unbind(ldc);
1691 /* failure? if so - return */
1692 if (result != LDAP_SUCCESS) {
1693 ldc->reason = "ldap_simple_bind() to check user credentials failed";
1695 uldap_connection_unbind(ldc);
1700 * We have just bound the connection to a different user and password
1701 * combination, which might be reused unintentionally next time this
1702 * connection is used from the connection pool. To ensure no confusion,
1703 * we mark the connection as unbound.
1709 * Get values for the provided attributes.
1715 vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1722 values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1723 while (values && values[j]) {
1724 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1725 : apr_pstrdup(r->pool, values[j]);
1728 ldap_value_free(values);
1736 * Add the new username to the search cache.
1740 the_search_node.username = filter;
1741 the_search_node.dn = *binddn;
1742 the_search_node.bindpw = bindpw;
1743 the_search_node.lastbind = apr_time_now();
1744 the_search_node.vals = vals;
1745 the_search_node.numvals = numvals;
1747 /* Search again to make sure that another thread didn't ready insert
1748 * this node into the cache before we got here. If it does exist then
1749 * update the lastbind
1751 search_nodep = util_ald_cache_fetch(curl->search_cache,
1753 if ((search_nodep == NULL) ||
1754 (strcmp(*binddn, search_nodep->dn) != 0)) {
1756 /* Nothing in cache, insert new entry */
1757 util_ald_cache_insert(curl->search_cache, &the_search_node);
1759 else if ((!search_nodep->bindpw) ||
1760 (strcmp(bindpw, search_nodep->bindpw) != 0)) {
1762 /* Entry in cache is invalid, remove it and insert new one */
1763 util_ald_cache_remove(curl->search_cache, search_nodep);
1764 util_ald_cache_insert(curl->search_cache, &the_search_node);
1767 /* Cache entry is valid, update lastbind */
1768 search_nodep->lastbind = the_search_node.lastbind;
1770 LDAP_CACHE_UNLOCK();
1774 ldc->reason = "Authentication successful";
1775 return LDAP_SUCCESS;
1779 * This function will return the DN of the entry matching userid.
1780 * It is used to get the DN in case some other module than mod_auth_ldap
1781 * has authenticated the user.
1782 * The function is basically a copy of uldap_cache_checkuserid
1783 * with password checking removed.
1785 static int uldap_cache_getuserdn(request_rec *r, util_ldap_connection_t *ldc,
1786 const char *url, const char *basedn,
1787 int scope, char **attrs, const char *filter,
1788 const char **binddn, const char ***retvals)
1790 const char **vals = NULL;
1793 LDAPMessage *res, *entry;
1797 util_url_node_t *curl; /* Cached URL node */
1798 util_url_node_t curnode;
1799 util_search_node_t *search_nodep; /* Cached search node */
1800 util_search_node_t the_search_node;
1803 util_ldap_state_t *st =
1804 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1807 /* Get the cache node for this url */
1810 curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1813 curl = util_ald_create_caches(st, url);
1815 LDAP_CACHE_UNLOCK();
1819 the_search_node.username = filter;
1820 search_nodep = util_ald_cache_fetch(curl->search_cache,
1822 if (search_nodep != NULL) {
1824 /* found entry in search cache... */
1825 curtime = apr_time_now();
1828 * Remove this item from the cache if its expired.
1830 if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1831 /* ...but entry is too old */
1832 util_ald_cache_remove(curl->search_cache, search_nodep);
1835 /* ...and entry is valid */
1836 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1839 *retvals = apr_pcalloc(r->pool, sizeof(char *) * search_nodep->numvals);
1840 for (i = 0; i < search_nodep->numvals; i++) {
1841 (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1844 LDAP_CACHE_UNLOCK();
1845 ldc->reason = "Search successful (cached)";
1846 return LDAP_SUCCESS;
1849 /* unlock this read lock */
1850 LDAP_CACHE_UNLOCK();
1854 * At this point, there is no valid cached search, so lets do the search.
1858 * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1861 if (failures++ > 10) {
1864 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1868 /* try do the search */
1869 result = ldap_search_ext_s(ldc->ldap,
1870 (char *)basedn, scope,
1871 (char *)filter, attrs, 0,
1872 NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
1873 if (AP_LDAP_IS_SERVER_DOWN(result))
1875 ldc->reason = "ldap_search_ext_s() for user failed with server down";
1876 uldap_connection_unbind(ldc);
1880 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1881 if (result != LDAP_SUCCESS) {
1882 ldc->reason = "ldap_search_ext_s() for user failed";
1887 * We should have found exactly one entry; to find a different
1888 * number is an error.
1890 count = ldap_count_entries(ldc->ldap, res);
1894 ldc->reason = "User not found";
1896 ldc->reason = "User is not unique (search found two "
1899 return LDAP_NO_SUCH_OBJECT;
1902 entry = ldap_first_entry(ldc->ldap, res);
1904 /* Grab the dn, copy it into the pool, and free it again */
1905 dn = ldap_get_dn(ldc->ldap, entry);
1906 *binddn = apr_pstrdup(r->pool, dn);
1910 * Get values for the provided attributes.
1916 vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1923 values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1924 while (values && values[j]) {
1925 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1926 : apr_pstrdup(r->pool, values[j]);
1929 ldap_value_free(values);
1937 * Add the new username to the search cache.
1941 the_search_node.username = filter;
1942 the_search_node.dn = *binddn;
1943 the_search_node.bindpw = NULL;
1944 the_search_node.lastbind = apr_time_now();
1945 the_search_node.vals = vals;
1946 the_search_node.numvals = numvals;
1948 /* Search again to make sure that another thread didn't ready insert
1949 * this node into the cache before we got here. If it does exist then
1950 * update the lastbind
1952 search_nodep = util_ald_cache_fetch(curl->search_cache,
1954 if ((search_nodep == NULL) ||
1955 (strcmp(*binddn, search_nodep->dn) != 0)) {
1957 /* Nothing in cache, insert new entry */
1958 util_ald_cache_insert(curl->search_cache, &the_search_node);
1961 * Don't update lastbind on entries with bindpw because
1962 * we haven't verified that password. It's OK to update
1963 * the entry if there is no password in it.
1965 else if (!search_nodep->bindpw) {
1966 /* Cache entry is valid, update lastbind */
1967 search_nodep->lastbind = the_search_node.lastbind;
1969 LDAP_CACHE_UNLOCK();
1974 ldc->reason = "Search successful";
1975 return LDAP_SUCCESS;
1979 * Reports if ssl support is enabled
1981 * 1 = enabled, 0 = not enabled
1983 static int uldap_ssl_supported(request_rec *r)
1985 util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
1986 r->server->module_config, &ldap_module);
1988 return(st->ssl_supported);
1992 /* ---------------------------------------- */
1993 /* config directives */
1996 static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy,
1999 util_ldap_state_t *st =
2000 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2002 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2008 st->cache_bytes = atol(bytes);
2010 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2011 "[%" APR_PID_T_FMT "] ldap cache: Setting shared memory "
2012 " cache size to %" APR_SIZE_T_FMT " bytes.",
2013 getpid(), st->cache_bytes);
2018 static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy,
2021 util_ldap_state_t *st =
2022 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2024 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2031 st->cache_file = ap_server_root_relative(st->pool, file);
2034 st->cache_file = NULL;
2037 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2038 "LDAP cache: Setting shared memory cache file to %s bytes.",
2044 static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy,
2047 util_ldap_state_t *st =
2048 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2050 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2056 st->search_cache_ttl = atol(ttl) * 1000000;
2058 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2059 "[%" APR_PID_T_FMT "] ldap cache: Setting cache TTL to %ld"
2060 " microseconds.", getpid(), st->search_cache_ttl);
2065 static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy,
2068 util_ldap_state_t *st =
2069 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2071 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2077 st->search_cache_size = atol(size);
2078 if (st->search_cache_size < 0) {
2079 st->search_cache_size = 0;
2082 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2083 "[%" APR_PID_T_FMT "] ldap cache: Setting search cache size"
2084 " to %ld entries.", getpid(), st->search_cache_size);
2089 static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy,
2092 util_ldap_state_t *st =
2093 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2095 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2101 st->compare_cache_ttl = atol(ttl) * 1000000;
2103 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2104 "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache TTL"
2105 " to %ld microseconds.", getpid(), st->compare_cache_ttl);
2110 static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy,
2113 util_ldap_state_t *st =
2114 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2116 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2122 st->compare_cache_size = atol(size);
2123 if (st->compare_cache_size < 0) {
2124 st->compare_cache_size = 0;
2127 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2128 "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache size"
2129 " to %ld entries.", getpid(), st->compare_cache_size);
2136 * Parse the certificate type.
2138 * The type can be one of the following:
2139 * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64,
2140 * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64
2142 * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned.
2144 static int util_ldap_parse_cert_type(const char *type)
2146 /* Authority file in binary DER format */
2147 if (0 == strcasecmp("CA_DER", type)) {
2148 return APR_LDAP_CA_TYPE_DER;
2151 /* Authority file in Base64 format */
2152 else if (0 == strcasecmp("CA_BASE64", type)) {
2153 return APR_LDAP_CA_TYPE_BASE64;
2156 /* Netscape certificate database file/directory */
2157 else if (0 == strcasecmp("CA_CERT7_DB", type)) {
2158 return APR_LDAP_CA_TYPE_CERT7_DB;
2161 /* Netscape secmod file/directory */
2162 else if (0 == strcasecmp("CA_SECMOD", type)) {
2163 return APR_LDAP_CA_TYPE_SECMOD;
2166 /* Client cert file in DER format */
2167 else if (0 == strcasecmp("CERT_DER", type)) {
2168 return APR_LDAP_CERT_TYPE_DER;
2171 /* Client cert file in Base64 format */
2172 else if (0 == strcasecmp("CERT_BASE64", type)) {
2173 return APR_LDAP_CERT_TYPE_BASE64;
2176 /* Client cert file in PKCS#12 format */
2177 else if (0 == strcasecmp("CERT_PFX", type)) {
2178 return APR_LDAP_CERT_TYPE_PFX;
2181 /* Netscape client cert database file/directory */
2182 else if (0 == strcasecmp("CERT_KEY3_DB", type)) {
2183 return APR_LDAP_CERT_TYPE_KEY3_DB;
2186 /* Netscape client cert nickname */
2187 else if (0 == strcasecmp("CERT_NICKNAME", type)) {
2188 return APR_LDAP_CERT_TYPE_NICKNAME;
2191 /* Client cert key file in DER format */
2192 else if (0 == strcasecmp("KEY_DER", type)) {
2193 return APR_LDAP_KEY_TYPE_DER;
2196 /* Client cert key file in Base64 format */
2197 else if (0 == strcasecmp("KEY_BASE64", type)) {
2198 return APR_LDAP_KEY_TYPE_BASE64;
2201 /* Client cert key file in PKCS#12 format */
2202 else if (0 == strcasecmp("KEY_PFX", type)) {
2203 return APR_LDAP_KEY_TYPE_PFX;
2207 return APR_LDAP_CA_TYPE_UNKNOWN;
2214 * Set LDAPTrustedGlobalCert.
2216 * This directive takes either two or three arguments:
2217 * - certificate type
2218 * - certificate file / directory / nickname
2219 * - certificate password (optional)
2221 * This directive may only be used globally.
2223 static const char *util_ldap_set_trusted_global_cert(cmd_parms *cmd,
2227 const char *password)
2229 util_ldap_state_t *st =
2230 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2232 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2236 apr_ldap_opt_tls_cert_t *cert;
2242 /* handle the certificate type */
2244 cert_type = util_ldap_parse_cert_type(type);
2245 if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2246 return apr_psprintf(cmd->pool, "The certificate type %s is "
2247 "not recognised. It should be one "
2248 "of CA_DER, CA_BASE64, CA_CERT7_DB, "
2249 "CA_SECMOD, CERT_DER, CERT_BASE64, "
2250 "CERT_KEY3_DB, CERT_NICKNAME, "
2251 "KEY_DER, KEY_BASE64", type);
2255 return "Certificate type was not specified.";
2258 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2259 "LDAP: SSL trusted global cert - %s (type %s)",
2262 /* add the certificate to the global array */
2263 cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
2264 cert->type = cert_type;
2266 cert->password = password;
2268 /* if file is a file or path, fix the path */
2269 if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2270 cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2272 cert->path = ap_server_root_relative(cmd->pool, file);
2274 ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2277 ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
2278 "LDAP: Could not open SSL trusted certificate "
2279 "authority file - %s",
2280 cert->path == NULL ? file : cert->path);
2281 return "Invalid global certificate file path";
2290 * Set LDAPTrustedClientCert.
2292 * This directive takes either two or three arguments:
2293 * - certificate type
2294 * - certificate file / directory / nickname
2295 * - certificate password (optional)
2297 static const char *util_ldap_set_trusted_client_cert(cmd_parms *cmd,
2301 const char *password)
2303 util_ldap_state_t *st =
2304 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2309 apr_ldap_opt_tls_cert_t *cert;
2311 /* handle the certificate type */
2313 cert_type = util_ldap_parse_cert_type(type);
2314 if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2315 return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2316 "not recognised. It should be one "
2317 "of CERT_DER, CERT_BASE64, "
2318 "CERT_NICKNAME, CERT_PFX,"
2319 "KEY_DER, KEY_BASE64, KEY_PFX",
2322 else if (APR_LDAP_CA_TYPE_DER == cert_type ||
2323 APR_LDAP_CA_TYPE_BASE64 == cert_type ||
2324 APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
2325 APR_LDAP_CA_TYPE_SECMOD == cert_type ||
2326 APR_LDAP_CERT_TYPE_PFX == cert_type ||
2327 APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
2328 return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2329 "only valid within a "
2330 "LDAPTrustedGlobalCert directive. "
2331 "Only CERT_DER, CERT_BASE64, "
2332 "CERT_NICKNAME, KEY_DER, and "
2333 "KEY_BASE64 may be used.", type);
2337 return "Certificate type was not specified.";
2340 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2341 "LDAP: SSL trusted client cert - %s (type %s)",
2344 /* add the certificate to the global array */
2345 cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
2346 cert->type = cert_type;
2348 cert->password = password;
2350 /* if file is a file or path, fix the path */
2351 if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2352 cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2354 cert->path = ap_server_root_relative(cmd->pool, file);
2356 ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2359 ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
2360 "LDAP: Could not open SSL client certificate "
2362 cert->path == NULL ? file : cert->path);
2363 return "Invalid client certificate file path";
2373 * Set LDAPTrustedMode.
2375 * This directive sets what encryption mode to use on a connection:
2376 * - None (No encryption)
2377 * - SSL (SSL encryption)
2378 * - STARTTLS (TLS encryption)
2380 static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
2383 util_ldap_state_t *st =
2384 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2387 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2388 "LDAP: SSL trusted mode - %s",
2391 if (0 == strcasecmp("NONE", mode)) {
2392 st->secure = APR_LDAP_NONE;
2394 else if (0 == strcasecmp("SSL", mode)) {
2395 st->secure = APR_LDAP_SSL;
2397 else if ( (0 == strcasecmp("TLS", mode))
2398 || (0 == strcasecmp("STARTTLS", mode))) {
2399 st->secure = APR_LDAP_STARTTLS;
2402 return "Invalid LDAPTrustedMode setting: must be one of NONE, "
2403 "SSL, or TLS/STARTTLS";
2410 static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
2414 util_ldap_state_t *st =
2415 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2417 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2423 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2424 "LDAP: SSL verify server certificate - %s",
2425 mode?"TRUE":"FALSE");
2427 st->verify_svr_cert = mode;
2433 static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
2437 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2438 util_ldap_state_t *st =
2439 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2442 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2448 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2449 st->connectionTimeout = atol(ttl);
2451 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2452 "[%" APR_PID_T_FMT "] ldap connection: Setting connection"
2453 " timeout to %ld seconds.", getpid(), st->connectionTimeout);
2455 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
2456 "LDAP: Connection timeout option not supported by the "
2457 "LDAP SDK in use." );
2464 static const char *util_ldap_set_chase_referrals(cmd_parms *cmd,
2468 util_ldap_config_t *dc = config;
2470 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2471 "LDAP: Setting referral chasing %s",
2472 (mode == AP_LDAP_CHASEREFERRALS_ON) ? "ON" : "OFF");
2474 dc->ChaseReferrals = mode;
2479 static const char *util_ldap_set_debug_level(cmd_parms *cmd,
2482 util_ldap_state_t *st =
2483 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2486 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2491 #ifndef AP_LDAP_OPT_DEBUG
2492 return "This directive is not supported with the currently linked LDAP library";
2495 st->debug_level = atoi(arg);
2499 static const char *util_ldap_set_referral_hop_limit(cmd_parms *cmd,
2501 const char *hop_limit)
2503 util_ldap_config_t *dc = config;
2505 dc->ReferralHopLimit = atol(hop_limit);
2507 if (dc->ReferralHopLimit <= 0) {
2508 return "LDAPReferralHopLimit must be greater than zero (Use 'LDAPReferrals Off' to disable referral chasing)";
2511 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2512 "LDAP: Limit chased referrals to maximum of %d hops.",
2513 dc->ReferralHopLimit);
2518 static void *util_ldap_create_dir_config(apr_pool_t *p, char *d) {
2519 util_ldap_config_t *dc =
2520 (util_ldap_config_t *) apr_pcalloc(p,sizeof(util_ldap_config_t));
2522 /* defaults are AP_LDAP_CHASEREFERRALS_ON and AP_LDAP_DEFAULT_HOPLIMIT */
2523 dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_ON;
2524 dc->ReferralHopLimit = AP_LDAP_HOPLIMIT_UNSET;
2529 static const char *util_ldap_set_op_timeout(cmd_parms *cmd,
2535 util_ldap_state_t *st =
2536 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2538 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2544 timeout = strtol(val, &endptr, 10);
2545 if ((val == endptr) || (*endptr != '\0')) {
2546 return "Timeout not numerical";
2549 return "Timeout must be non-negative";
2553 if (!st->opTimeout) {
2554 st->opTimeout = apr_pcalloc(cmd->pool, sizeof(struct timeval));
2556 st->opTimeout->tv_sec = timeout;
2559 st->opTimeout = NULL;
2562 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2563 "[%" APR_PID_T_FMT "] ldap connection: Setting op timeout "
2564 "to %ld seconds.", getpid(), timeout);
2566 #ifndef LDAP_OPT_TIMEOUT
2568 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2569 "LDAP: LDAP_OPT_TIMEOUT option not supported by the "
2570 "LDAP library in use. Using LDAPTimeout value as search "
2579 static void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
2581 util_ldap_state_t *st =
2582 (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
2584 /* Create a per vhost pool for mod_ldap to use, serialized with
2585 * st->mutex (also one per vhost). both are replicated by fork(),
2586 * no shared memory managed by either.
2588 apr_pool_create(&st->pool, p);
2590 apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
2593 st->cache_bytes = 500000;
2594 st->search_cache_ttl = 600000000;
2595 st->search_cache_size = 1024;
2596 st->compare_cache_ttl = 600000000;
2597 st->compare_cache_size = 1024;
2598 st->connections = NULL;
2599 st->ssl_supported = 0;
2600 st->global_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2601 st->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2602 st->secure = APR_LDAP_NONE;
2604 st->connectionTimeout = 10;
2605 st->opTimeout = apr_pcalloc(p, sizeof(struct timeval));
2606 st->opTimeout->tv_sec = 60;
2607 st->verify_svr_cert = 1;
2612 static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
2615 util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
2616 util_ldap_state_t *base = (util_ldap_state_t *) basev;
2617 util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
2619 st->pool = overrides->pool;
2621 st->mutex = overrides->mutex;
2624 /* The cache settings can not be modified in a
2625 virtual host since all server use the same
2626 shared memory cache. */
2627 st->cache_bytes = base->cache_bytes;
2628 st->search_cache_ttl = base->search_cache_ttl;
2629 st->search_cache_size = base->search_cache_size;
2630 st->compare_cache_ttl = base->compare_cache_ttl;
2631 st->compare_cache_size = base->compare_cache_size;
2632 st->util_ldap_cache_lock = base->util_ldap_cache_lock;
2634 st->connections = NULL;
2635 st->ssl_supported = 0;
2636 st->global_certs = apr_array_append(p, base->global_certs,
2637 overrides->global_certs);
2638 st->client_certs = apr_array_append(p, base->client_certs,
2639 overrides->client_certs);
2640 st->secure = (overrides->secure_set == 0) ? base->secure
2641 : overrides->secure;
2643 /* These LDAP connection settings can not be overwritten in
2644 a virtual host. Once set in the base server, they must
2645 remain the same. None of the LDAP SDKs seem to be able
2646 to handle setting the verify_svr_cert flag on a
2647 per-connection basis. The OpenLDAP client appears to be
2648 able to handle the connection timeout per-connection
2649 but the Novell SDK cannot. Allowing the timeout to
2650 be set by each vhost is of little value so rather than
2651 trying to make special expections for one LDAP SDK, GLOBAL_ONLY
2652 is being enforced on this setting as well. */
2653 st->connectionTimeout = base->connectionTimeout;
2654 st->opTimeout = base->opTimeout;
2655 st->verify_svr_cert = base->verify_svr_cert;
2656 st->debug_level = base->debug_level;
2661 static apr_status_t util_ldap_cleanup_module(void *data)
2664 server_rec *s = data;
2665 util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
2666 s->module_config, &ldap_module);
2668 if (st->ssl_supported) {
2669 apr_ldap_ssl_deinit();
2676 static int util_ldap_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
2679 apr_status_t result;
2681 result = ap_mutex_register(pconf, ldap_cache_mutex_type, NULL,
2682 APR_LOCK_DEFAULT, 0);
2683 if (result != APR_SUCCESS) {
2690 static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog,
2691 apr_pool_t *ptemp, server_rec *s)
2693 apr_status_t result;
2694 server_rec *s_vhost;
2695 util_ldap_state_t *st_vhost;
2697 util_ldap_state_t *st = (util_ldap_state_t *)
2698 ap_get_module_config(s->module_config,
2702 const char *userdata_key = "util_ldap_init";
2703 apr_ldap_err_t *result_err = NULL;
2706 /* util_ldap_post_config() will be called twice. Don't bother
2707 * going through all of the initialization on the first call
2708 * because it will just be thrown away.*/
2709 apr_pool_userdata_get(&data, userdata_key, s->process->pool);
2711 apr_pool_userdata_set((const void *)1, userdata_key,
2712 apr_pool_cleanup_null, s->process->pool);
2714 #if APR_HAS_SHARED_MEMORY
2715 /* If the cache file already exists then delete it. Otherwise we are
2716 * going to run into problems creating the shared memory. */
2717 if (st->cache_file) {
2718 char *lck_file = apr_pstrcat(ptemp, st->cache_file, ".lck",
2720 apr_file_remove(lck_file, ptemp);
2726 #if APR_HAS_SHARED_MEMORY
2727 /* initializing cache if shared memory size is not zero and we already
2728 * don't have shm address
2730 if (!st->cache_shm && st->cache_bytes > 0) {
2732 result = util_ldap_cache_init(p, st);
2733 if (result != APR_SUCCESS) {
2734 ap_log_error(APLOG_MARK, APLOG_ERR, result, s,
2735 "LDAP cache: could not create shared memory segment");
2739 result = ap_global_mutex_create(&st->util_ldap_cache_lock,
2740 ldap_cache_mutex_type, NULL, s, p, 0);
2741 if (result != APR_SUCCESS) {
2745 /* merge config in all vhost */
2748 st_vhost = (util_ldap_state_t *)
2749 ap_get_module_config(s_vhost->module_config,
2752 #if APR_HAS_SHARED_MEMORY
2753 st_vhost->cache_shm = st->cache_shm;
2754 st_vhost->cache_rmm = st->cache_rmm;
2755 st_vhost->cache_file = st->cache_file;
2756 ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s,
2757 "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
2758 "for VHOST: %s", st->cache_shm, st->cache_rmm,
2759 s_vhost->server_hostname);
2761 s_vhost = s_vhost->next;
2763 #if APR_HAS_SHARED_MEMORY
2766 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
2767 "LDAP cache: LDAPSharedCacheSize is zero, disabling "
2768 "shared memory cache");
2772 /* log the LDAP SDK used
2775 apr_ldap_err_t *result = NULL;
2776 apr_ldap_info(p, &(result));
2777 if (result != NULL) {
2778 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s", result->reason);
2782 apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
2783 util_ldap_cleanup_module);
2786 * Initialize SSL support, and log the result for the benefit of the admin.
2788 * If SSL is not supported it is not necessarily an error, as the
2789 * application may not want to use it.
2791 rc = apr_ldap_ssl_init(p,
2795 if (APR_SUCCESS == rc) {
2796 rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
2797 (void *)st->global_certs, &(result_err));
2800 if (APR_SUCCESS == rc) {
2801 st->ssl_supported = 1;
2802 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2803 "LDAP: SSL support available" );
2806 st->ssl_supported = 0;
2807 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2808 "LDAP: SSL support unavailable%s%s",
2809 result_err ? ": " : "",
2810 result_err ? result_err->reason : "");
2813 /* Initialize the rebind callback's cross reference list. */
2814 apr_ldap_rebind_init (p);
2816 #ifdef AP_LDAP_OPT_DEBUG
2817 if (st->debug_level > 0) {
2818 result = ldap_set_option(NULL, AP_LDAP_OPT_DEBUG, &st->debug_level);
2819 if (result != LDAP_SUCCESS) {
2820 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
2821 "LDAP: Could not set the LDAP library debug level to %d:(%d) %s",
2822 st->debug_level, result, ldap_err2string(result));
2830 static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
2833 util_ldap_state_t *st = ap_get_module_config(s->module_config,
2836 if (!st->util_ldap_cache_lock) return;
2838 sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
2839 apr_global_mutex_lockfile(st->util_ldap_cache_lock), p);
2840 if (sts != APR_SUCCESS) {
2841 ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s,
2842 "Failed to initialise global mutex %s in child process %"
2844 ldap_cache_mutex_type, getpid());
2848 static const command_rec util_ldap_cmds[] = {
2849 AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes,
2851 "Set the size of the shared memory cache (in bytes). Use "
2852 "0 to disable the shared memory cache. (default: 100000)"),
2854 AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file,
2856 "Set the file name for the shared memory cache."),
2858 AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries,
2860 "Set the maximum number of entries that are possible in the "
2861 "LDAP search cache. Use 0 or -1 to disable the search cache "
2864 AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl,
2866 "Set the maximum time (in seconds) that an item can be "
2867 "cached in the LDAP search cache. Use 0 for no limit. "
2870 AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries,
2872 "Set the maximum number of entries that are possible "
2873 "in the LDAP compare cache. Use 0 or -1 to disable the compare cache "
2876 AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl,
2878 "Set the maximum time (in seconds) that an item is cached "
2879 "in the LDAP operation cache. Use 0 for no limit. "
2882 AP_INIT_TAKE23("LDAPTrustedGlobalCert", util_ldap_set_trusted_global_cert,
2884 "Takes three arguments; the first argument is the cert "
2885 "type of the second argument, one of CA_DER, CA_BASE64, "
2886 "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
2887 "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
2888 "specifes the file and/or directory containing the trusted CA "
2889 "certificates (and global client certs for Netware) used to "
2890 "validate the LDAP server. The third argument is an optional "
2891 "passphrase if applicable."),
2893 AP_INIT_TAKE23("LDAPTrustedClientCert", util_ldap_set_trusted_client_cert,
2895 "Takes three arguments: the first argument is the certificate "
2896 "type of the second argument, one of CA_DER, CA_BASE64, "
2897 "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
2898 "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
2899 "specifies the file and/or directory containing the client "
2900 "certificate, or certificate ID used to validate this LDAP "
2901 "client. The third argument is an optional passphrase if "
2904 AP_INIT_TAKE1("LDAPTrustedMode", util_ldap_set_trusted_mode,
2906 "Specify the type of security that should be applied to "
2907 "an LDAP connection. One of; NONE, SSL or STARTTLS."),
2909 AP_INIT_FLAG("LDAPVerifyServerCert", util_ldap_set_verify_srv_cert,
2911 "Set to 'ON' requires that the server certificate be verified"
2912 " before a secure LDAP connection can be establish. Default"
2915 AP_INIT_TAKE1("LDAPConnectionTimeout", util_ldap_set_connection_timeout,
2917 "Specify the LDAP socket connection timeout in seconds "
2920 AP_INIT_FLAG("LDAPReferrals", util_ldap_set_chase_referrals,
2922 "Choose whether referrals are chased ['ON'|'OFF']. Default 'ON'"),
2924 AP_INIT_TAKE1("LDAPReferralHopLimit", util_ldap_set_referral_hop_limit,
2926 "Limit the number of referral hops that LDAP can follow. "
2927 "(Integer value, Consult LDAP SDK documentation for applicability and defaults"),
2929 AP_INIT_TAKE1("LDAPLibraryDebug", util_ldap_set_debug_level,
2931 "Enable debugging in LDAP SDK (Default: off, values: SDK specific"),
2933 AP_INIT_TAKE1("LDAPTimeout", util_ldap_set_op_timeout,
2935 "Specify the LDAP bind/search timeout in seconds "
2936 "(0 = no limit). Default: 60"),
2941 static void util_ldap_register_hooks(apr_pool_t *p)
2943 APR_REGISTER_OPTIONAL_FN(uldap_connection_open);
2944 APR_REGISTER_OPTIONAL_FN(uldap_connection_close);
2945 APR_REGISTER_OPTIONAL_FN(uldap_connection_unbind);
2946 APR_REGISTER_OPTIONAL_FN(uldap_connection_cleanup);
2947 APR_REGISTER_OPTIONAL_FN(uldap_connection_find);
2948 APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn);
2949 APR_REGISTER_OPTIONAL_FN(uldap_cache_compare);
2950 APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid);
2951 APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn);
2952 APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported);
2953 APR_REGISTER_OPTIONAL_FN(uldap_cache_check_subgroups);
2955 ap_hook_pre_config(util_ldap_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
2956 ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
2957 ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
2958 ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
2961 module AP_MODULE_DECLARE_DATA ldap_module = {
2962 STANDARD20_MODULE_STUFF,
2963 util_ldap_create_dir_config, /* create dir config */
2964 NULL, /* merge dir config */
2965 util_ldap_create_config, /* create server config */
2966 util_ldap_merge_config, /* merge server config */
2967 util_ldap_cmds, /* command table */
2968 util_ldap_register_hooks, /* set up request processing hooks */