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;
236 if (!ldc) return APR_SUCCESS;
240 uldap_connection_unbind(ldc);
243 apr_thread_mutex_lock(st->mutex);
246 /* Remove ldc from the list */
247 for (l=st->connections; l; l=l->next) {
250 prev->next = l->next;
253 st->connections = l->next;
260 /* Some unfortunate duplication between this method
261 * and uldap_connection_cleanup()
264 free((void*)ldc->bindpw);
267 free((void*)ldc->binddn);
271 apr_thread_mutex_unlock(ldc->lock);
272 apr_thread_mutex_unlock(st->mutex);
275 /* Destory the pool associated with this connection */
277 apr_pool_destroy(ldc->pool);
282 static int uldap_connection_init(request_rec *r,
283 util_ldap_connection_t *ldc)
285 int rc = 0, ldap_option = 0;
286 int version = LDAP_VERSION3;
287 apr_ldap_err_t *result = NULL;
288 #ifdef LDAP_OPT_NETWORK_TIMEOUT
289 struct timeval connectionTimeout = {10,0}; /* 10 second connection timeout */
291 util_ldap_state_t *st =
292 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
295 /* Since the host will include a port if the default port is not used,
296 * always specify the default ports for the port parameter. This will
297 * allow a host string that contains multiple hosts the ability to mix
298 * some hosts with ports and some without. All hosts which do not
299 * specify a port will use the default port.
301 apr_ldap_init(r->pool, &(ldc->ldap),
303 APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
307 if (NULL == result) {
308 /* something really bad happened */
310 if (NULL == ldc->reason) {
311 ldc->reason = "LDAP: ldap initialization failed";
313 return(APR_EGENERAL);
317 ldc->reason = result->reason;
320 if (NULL == ldc->ldap)
323 if (NULL == ldc->reason) {
324 ldc->reason = "LDAP: ldap initialization failed";
327 ldc->reason = result->reason;
332 /* Now that we have an ldap struct, add it to the referral list for rebinds. */
333 rc = apr_ldap_rebind_add(ldc->pool, ldc->ldap, ldc->binddn, ldc->bindpw);
334 if (rc != APR_SUCCESS) {
335 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
336 "LDAP: Unable to add rebind cross reference entry. Out of memory?");
337 uldap_connection_unbind(ldc);
338 ldc->reason = "LDAP: Unable to add rebind cross reference entry.";
342 /* always default to LDAP V3 */
343 ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
345 /* set client certificates */
346 if (!apr_is_empty_array(ldc->client_certs)) {
347 apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
348 ldc->client_certs, &(result));
349 if (LDAP_SUCCESS != result->rc) {
350 uldap_connection_unbind( ldc );
351 ldc->reason = result->reason;
356 /* switch on SSL/TLS */
357 if (APR_LDAP_NONE != ldc->secure) {
358 apr_ldap_set_option(r->pool, ldc->ldap,
359 APR_LDAP_OPT_TLS, &ldc->secure, &(result));
360 if (LDAP_SUCCESS != result->rc) {
361 uldap_connection_unbind( ldc );
362 ldc->reason = result->reason;
367 /* Set the alias dereferencing option */
368 ldap_option = ldc->deref;
369 ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);
371 /* Set options for rebind and referrals. */
372 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
373 "LDAP: Setting referrals to %s.",
374 ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"));
375 apr_ldap_set_option(r->pool, ldc->ldap,
376 APR_LDAP_OPT_REFERRALS,
377 (void *)((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ?
378 LDAP_OPT_ON : LDAP_OPT_OFF),
380 if (result->rc != LDAP_SUCCESS) {
381 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
382 "Unable to set LDAP_OPT_REFERRALS option to %s: %d.",
383 ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"),
385 result->reason = "Unable to set LDAP_OPT_REFERRALS.";
386 ldc->reason = result->reason;
387 uldap_connection_unbind(ldc);
391 if ((ldc->ReferralHopLimit != AP_LDAP_HOPLIMIT_UNSET) && ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
392 /* Referral hop limit - only if referrals are enabled and a hop limit is explicitly requested */
393 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
394 "Setting referral hop limit to %d.",
395 ldc->ReferralHopLimit);
396 apr_ldap_set_option(r->pool, ldc->ldap,
397 APR_LDAP_OPT_REFHOPLIMIT,
398 (void *)&ldc->ReferralHopLimit,
400 if (result->rc != LDAP_SUCCESS) {
401 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
402 "Unable to set LDAP_OPT_REFHOPLIMIT option to %d: %d.",
403 ldc->ReferralHopLimit,
405 result->reason = "Unable to set LDAP_OPT_REFHOPLIMIT.";
406 ldc->reason = result->reason;
407 uldap_connection_unbind(ldc);
412 /*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
413 #ifdef APR_LDAP_OPT_VERIFY_CERT
414 apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_VERIFY_CERT,
415 &(st->verify_svr_cert), &(result));
417 #if defined(LDAPSSL_VERIFY_SERVER)
418 if (st->verify_svr_cert) {
419 result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
422 result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
424 #elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
425 /* This is not a per-connection setting so just pass NULL for the
426 Ldap connection handle */
427 if (st->verify_svr_cert) {
428 int i = LDAP_OPT_X_TLS_DEMAND;
429 result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
432 int i = LDAP_OPT_X_TLS_NEVER;
433 result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
438 #ifdef LDAP_OPT_NETWORK_TIMEOUT
439 if (st->connectionTimeout > 0) {
440 connectionTimeout.tv_sec = st->connectionTimeout;
443 if (st->connectionTimeout >= 0) {
444 rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
445 (void *)&connectionTimeout, &(result));
446 if (APR_SUCCESS != rc) {
447 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
448 "LDAP: Could not set the connection timeout");
453 #ifdef LDAP_OPT_TIMEOUT
455 * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap
456 * function calls and not just ldap_search_ext_s(), which accepts a timeout
458 * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all
459 * XXX: synchronous ldap function calls with asynchronous calls and using
460 * XXX: ldap_result() with a timeout.
463 rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_TIMEOUT,
464 st->opTimeout, &(result));
465 if (APR_SUCCESS != rc) {
466 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
467 "LDAP: Could not set LDAP_OPT_TIMEOUT");
476 * Replacement function for ldap_simple_bind_s() with a timeout.
477 * To do this in a portable way, we have to use ldap_simple_bind() and
480 * Returns LDAP_SUCCESS on success; and an error code on failure
482 static int uldap_simple_bind(util_ldap_connection_t *ldc, char *binddn,
483 char* bindpw, struct timeval *timeout)
487 int msgid = ldap_simple_bind(ldc->ldap, binddn, bindpw);
489 ldc->reason = "LDAP: ldap_simple_bind() failed";
490 /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
493 rc = ldap_result(ldc->ldap, msgid, 0, timeout, &result);
495 ldc->reason = "LDAP: ldap_simple_bind() result retrieval failed";
496 /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
500 ldc->reason = "LDAP: ldap_simple_bind() timed out";
502 } else if (ldap_parse_result(ldc->ldap, result, &rc, NULL, NULL, NULL,
504 ldc->reason = "LDAP: ldap_simple_bind() parse result failed";
510 * Connect to the LDAP server and binds. Does not connect if already
511 * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
513 * Returns LDAP_SUCCESS on success; and an error code on failure
515 static int uldap_connection_open(request_rec *r,
516 util_ldap_connection_t *ldc)
520 int new_connection = 0;
521 util_ldap_state_t *st;
523 /* sanity check for NULL */
528 /* If the connection is already bound, return
532 ldc->reason = "LDAP: connection open successful (already bound)";
536 /* create the ldap session handle
538 if (NULL == ldc->ldap)
541 rc = uldap_connection_init( r, ldc );
542 if (LDAP_SUCCESS != rc)
549 st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
552 /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is
553 * returned. If LDAP_TIMEOUT is returned on the first try, maybe the
554 * connection was idle for a long time and has been dropped by a firewall.
555 * In this case close the connection immediately and try again.
557 * On Success or any other error, break out of the loop.
559 * NOTE: Looping is probably not a great idea. If the server isn't
560 * responding the chances it will respond after a few tries are poor.
561 * However, the original code looped and it only happens on
562 * the error condition.
564 for (failures=0; failures<10; failures++)
566 rc = uldap_simple_bind(ldc, (char *)ldc->binddn, (char *)ldc->bindpw,
568 if ((AP_LDAP_IS_SERVER_DOWN(rc) && failures == 5) ||
569 (rc == LDAP_TIMEOUT && failures == 0))
571 if (rc == LDAP_TIMEOUT && !new_connection) {
572 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
573 "ldap_simple_bind() timed out on reused "
574 "connection, dropped by firewall?");
576 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
577 "attempt to re-init the connection");
578 /* attempt to init the connection once again */
579 uldap_connection_unbind( ldc );
580 rc = uldap_connection_init( r, ldc );
581 if (LDAP_SUCCESS != rc)
586 else if (!AP_LDAP_IS_SERVER_DOWN(rc)) {
589 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
590 "ldap_simple_bind() failed with server down "
591 "(try %d)", failures + 1);
594 /* free the handle if there was an error
596 if (LDAP_SUCCESS != rc)
598 uldap_connection_unbind(ldc);
599 ldc->reason = "LDAP: ldap_simple_bind() failed";
603 ldc->reason = "LDAP: connection open successful";
611 * Compare client certificate arrays.
613 * Returns 1 on compare failure, 0 otherwise.
615 static int compare_client_certs(apr_array_header_t *srcs,
616 apr_array_header_t *dests)
619 struct apr_ldap_opt_tls_cert_t *src, *dest;
621 /* arrays both NULL? if so, then equal */
622 if (srcs == NULL && dests == NULL) {
626 /* arrays different length or either NULL? If so, then not equal */
627 if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
631 /* run an actual comparison */
632 src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
633 dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
634 for (i = 0; i < srcs->nelts; i++) {
635 if ((strcmp(src[i].path, dest[i].path)) ||
636 (src[i].type != dest[i].type) ||
637 /* One is passwordless? If so, then not equal */
638 ((src[i].password == NULL) ^ (dest[i].password == NULL)) ||
639 (src[i].password != NULL && dest[i].password != NULL &&
640 strcmp(src[i].password, dest[i].password))) {
645 /* if we got here, the cert arrays were identical */
652 * Find an existing ldap connection struct that matches the
653 * provided ldap connection parameters.
655 * If not found in the cache, a new ldc structure will be allocated
656 * from st->pool and returned to the caller. If found in the cache,
657 * a pointer to the existing ldc structure will be returned.
659 static util_ldap_connection_t *
660 uldap_connection_find(request_rec *r,
661 const char *host, int port,
662 const char *binddn, const char *bindpw,
663 deref_options deref, int secure)
665 struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
666 int secureflag = secure;
668 util_ldap_state_t *st =
669 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
671 util_ldap_config_t *dc =
672 (util_ldap_config_t *) ap_get_module_config(r->per_dir_config, &ldap_module);
675 /* mutex lock this function */
676 apr_thread_mutex_lock(st->mutex);
679 if (secure < APR_LDAP_NONE) {
680 secureflag = st->secure;
683 /* Search for an exact connection match in the list that is not
686 for (l=st->connections,p=NULL; l; l=l->next) {
688 if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
690 if ( (l->port == port) && (strcmp(l->host, host) == 0)
691 && ((!l->binddn && !binddn) || (l->binddn && binddn
692 && !strcmp(l->binddn, binddn)))
693 && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
694 && !strcmp(l->bindpw, bindpw)))
695 && (l->deref == deref) && (l->secure == secureflag)
696 && !compare_client_certs(dc->client_certs, l->client_certs))
701 /* If this connection didn't match the criteria, then we
702 * need to unlock the mutex so it is available to be reused.
704 apr_thread_mutex_unlock(l->lock);
710 /* If nothing found, search again, but we don't care about the
711 * binddn and bindpw this time.
714 for (l=st->connections,p=NULL; l; l=l->next) {
716 if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
719 if ((l->port == port) && (strcmp(l->host, host) == 0) &&
720 (l->deref == deref) && (l->secure == secureflag) &&
721 !compare_client_certs(dc->client_certs, l->client_certs))
723 /* the bind credentials have changed */
725 util_ldap_strdup((char**)&(l->binddn), binddn);
726 util_ldap_strdup((char**)&(l->bindpw), bindpw);
730 /* If this connection didn't match the criteria, then we
731 * need to unlock the mutex so it is available to be reused.
733 apr_thread_mutex_unlock(l->lock);
740 /* artificially disable cache */
743 /* If no connection was found after the second search, we
748 if (apr_pool_create(&newpool, NULL) != APR_SUCCESS) {
749 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
750 "util_ldap: Failed to create memory pool");
752 apr_thread_mutex_unlock(st->mutex);
758 * Add the new connection entry to the linked list. Note that we
759 * don't actually establish an LDAP connection yet; that happens
760 * the first time authentication is requested.
763 /* create the details of this connection in the new pool */
764 l = apr_pcalloc(newpool, sizeof(util_ldap_connection_t));
769 apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, l->pool);
770 apr_thread_mutex_lock(l->lock);
773 l->host = apr_pstrdup(l->pool, host);
776 util_ldap_strdup((char**)&(l->binddn), binddn);
777 util_ldap_strdup((char**)&(l->bindpw), bindpw);
778 l->ChaseReferrals = dc->ChaseReferrals;
779 l->ReferralHopLimit = dc->ReferralHopLimit;
781 /* The security mode after parsing the URL will always be either
782 * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
783 * If the security setting is NONE, override it to the security
784 * setting optionally supplied by the admin using LDAPTrustedMode
786 l->secure = secureflag;
788 /* save away a copy of the client cert list that is presently valid */
789 l->client_certs = apr_array_copy_hdr(l->pool, dc->client_certs);
802 apr_thread_mutex_unlock(st->mutex);
807 /* ------------------------------------------------------------------ */
810 * Compares two DNs to see if they're equal. The only way to do this correctly
811 * is to search for the dn and then do ldap_get_dn() on the result. This should
812 * match the initial dn, since it would have been also retrieved with
813 * ldap_get_dn(). This is expensive, so if the configuration value
814 * compare_dn_on_server is false, just does an ordinary strcmp.
816 * The lock for the ldap cache should already be acquired.
818 static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
819 const char *url, const char *dn,
820 const char *reqdn, int compare_dn_on_server)
823 util_url_node_t *curl;
824 util_url_node_t curnode;
825 util_dn_compare_node_t *node;
826 util_dn_compare_node_t newnode;
828 LDAPMessage *res, *entry;
831 util_ldap_state_t *st = (util_ldap_state_t *)
832 ap_get_module_config(r->server->module_config,
835 /* get cache entry (or create one) */
839 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
841 curl = util_ald_create_caches(st, url);
845 /* a simple compare? */
846 if (!compare_dn_on_server) {
847 /* unlock this read lock */
848 if (strcmp(dn, reqdn)) {
849 ldc->reason = "DN Comparison FALSE (direct strcmp())";
850 return LDAP_COMPARE_FALSE;
853 ldc->reason = "DN Comparison TRUE (direct strcmp())";
854 return LDAP_COMPARE_TRUE;
859 /* no - it's a server side compare */
862 /* is it in the compare cache? */
863 newnode.reqdn = (char *)reqdn;
864 node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
866 /* If it's in the cache, it's good */
867 /* unlock this read lock */
869 ldc->reason = "DN Comparison TRUE (cached)";
870 return LDAP_COMPARE_TRUE;
873 /* unlock this read lock */
878 if (failures++ > 10) {
879 /* too many failures */
883 /* make a server connection */
884 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
885 /* connect to server failed */
889 /* search for reqdn */
890 result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
891 "(objectclass=*)", NULL, 1,
892 NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
893 if (AP_LDAP_IS_SERVER_DOWN(result))
895 ldc->reason = "DN Comparison ldap_search_ext_s() "
896 "failed with server down";
897 uldap_connection_unbind(ldc);
900 if (result == LDAP_TIMEOUT && failures == 0) {
902 * we are reusing a connection that doesn't seem to be active anymore
903 * (firewall state drop?), let's try a new connection.
905 ldc->reason = "DN Comparison ldap_search_ext_s() "
906 "failed with timeout";
907 uldap_connection_unbind(ldc);
910 if (result != LDAP_SUCCESS) {
911 /* search for reqdn failed - no match */
912 ldc->reason = "DN Comparison ldap_search_ext_s() failed";
916 entry = ldap_first_entry(ldc->ldap, res);
917 searchdn = ldap_get_dn(ldc->ldap, entry);
920 if (strcmp(dn, searchdn) != 0) {
921 /* compare unsuccessful */
922 ldc->reason = "DN Comparison FALSE (checked on server)";
923 result = LDAP_COMPARE_FALSE;
927 /* compare successful - add to the compare cache */
929 newnode.reqdn = (char *)reqdn;
930 newnode.dn = (char *)dn;
932 node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
934 || (strcmp(reqdn, node->reqdn) != 0)
935 || (strcmp(dn, node->dn) != 0))
937 util_ald_cache_insert(curl->dn_compare_cache, &newnode);
941 ldc->reason = "DN Comparison TRUE (checked on server)";
942 result = LDAP_COMPARE_TRUE;
944 ldap_memfree(searchdn);
950 * Does an generic ldap_compare operation. It accepts a cache that it will use
951 * to lookup the compare in the cache. We cache two kinds of compares
952 * (require group compares) and (require user compares). Each compare has a
953 * different cache node: require group includes the DN; require user does not
954 * because the require user cache is owned by the
957 static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
958 const char *url, const char *dn,
959 const char *attrib, const char *value)
962 util_url_node_t *curl;
963 util_url_node_t curnode;
964 util_compare_node_t *compare_nodep;
965 util_compare_node_t the_compare_node;
966 apr_time_t curtime = 0; /* silence gcc -Wall */
969 util_ldap_state_t *st = (util_ldap_state_t *)
970 ap_get_module_config(r->server->module_config,
973 /* get cache entry (or create one) */
976 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
978 curl = util_ald_create_caches(st, url);
983 /* make a comparison to the cache */
985 curtime = apr_time_now();
987 the_compare_node.dn = (char *)dn;
988 the_compare_node.attrib = (char *)attrib;
989 the_compare_node.value = (char *)value;
990 the_compare_node.result = 0;
991 the_compare_node.sgl_processed = 0;
992 the_compare_node.subgroupList = NULL;
994 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
997 if (compare_nodep != NULL) {
999 if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
1000 /* ...but it is too old */
1001 util_ald_cache_remove(curl->compare_cache, compare_nodep);
1004 /* ...and it is good */
1005 if (LDAP_COMPARE_TRUE == compare_nodep->result) {
1006 ldc->reason = "Comparison true (cached)";
1008 else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
1009 ldc->reason = "Comparison false (cached)";
1011 else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
1012 ldc->reason = "Comparison no such attribute (cached)";
1015 ldc->reason = "Comparison undefined (cached)";
1018 /* record the result code to return with the reason... */
1019 result = compare_nodep->result;
1020 /* and unlock this read lock */
1021 LDAP_CACHE_UNLOCK();
1025 /* unlock this read lock */
1026 LDAP_CACHE_UNLOCK();
1030 if (failures++ > 10) {
1031 /* too many failures */
1035 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1036 /* connect failed */
1040 result = ldap_compare_s(ldc->ldap,
1044 if (AP_LDAP_IS_SERVER_DOWN(result)) {
1045 /* connection failed - try again */
1046 ldc->reason = "ldap_compare_s() failed with server down";
1047 uldap_connection_unbind(ldc);
1050 if (result == LDAP_TIMEOUT && failures == 0) {
1052 * we are reusing a connection that doesn't seem to be active anymore
1053 * (firewall state drop?), let's try a new connection.
1055 ldc->reason = "ldap_compare_s() failed with timeout";
1056 uldap_connection_unbind(ldc);
1060 ldc->reason = "Comparison complete";
1061 if ((LDAP_COMPARE_TRUE == result) ||
1062 (LDAP_COMPARE_FALSE == result) ||
1063 (LDAP_NO_SUCH_ATTRIBUTE == result)) {
1065 /* compare completed; caching result */
1067 the_compare_node.lastcompare = curtime;
1068 the_compare_node.result = result;
1069 the_compare_node.sgl_processed = 0;
1070 the_compare_node.subgroupList = NULL;
1072 /* If the node doesn't exist then insert it, otherwise just update
1073 * it with the last results
1075 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1077 if ( (compare_nodep == NULL)
1078 || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
1079 || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
1080 || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
1084 junk = util_ald_cache_insert(curl->compare_cache,
1087 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1088 "cache_compare: Cache insertion failure.");
1092 compare_nodep->lastcompare = curtime;
1093 compare_nodep->result = result;
1095 LDAP_CACHE_UNLOCK();
1097 if (LDAP_COMPARE_TRUE == result) {
1098 ldc->reason = "Comparison true (adding to cache)";
1099 return LDAP_COMPARE_TRUE;
1101 else if (LDAP_COMPARE_FALSE == result) {
1102 ldc->reason = "Comparison false (adding to cache)";
1103 return LDAP_COMPARE_FALSE;
1106 ldc->reason = "Comparison no such attribute (adding to cache)";
1107 return LDAP_NO_SUCH_ATTRIBUTE;
1114 static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r,
1115 util_ldap_connection_t *ldc,
1118 char **subgroupAttrs,
1119 apr_array_header_t *subgroupclasses)
1122 int result = LDAP_COMPARE_FALSE;
1123 util_compare_subgroup_t *res = NULL;
1124 LDAPMessage *sga_res, *entry;
1125 struct mod_auth_ldap_groupattr_entry_t *sgc_ents;
1126 apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
1128 sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1130 if (!subgroupAttrs) {
1136 * 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups.
1138 if (failures++ > 10) {
1139 /* too many failures */
1143 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1144 /* connect failed */
1148 /* try to do the search */
1149 result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
1150 (char *)"cn=*", subgroupAttrs, 0,
1151 NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
1152 if (AP_LDAP_IS_SERVER_DOWN(result)) {
1153 ldc->reason = "ldap_search_ext_s() for subgroups failed with server"
1155 uldap_connection_unbind(ldc);
1158 if (result == LDAP_TIMEOUT && failures == 0) {
1160 * we are reusing a connection that doesn't seem to be active anymore
1161 * (firewall state drop?), let's try a new connection.
1163 ldc->reason = "ldap_search_ext_s() for subgroups failed with timeout";
1164 uldap_connection_unbind(ldc);
1168 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1169 if (result != LDAP_SUCCESS) {
1170 ldc->reason = "ldap_search_ext_s() for subgroups failed";
1174 entry = ldap_first_entry(ldc->ldap, sga_res);
1177 * Get values for the provided sub-group attributes.
1179 if (subgroupAttrs) {
1180 int indx = 0, tmp_sgcIndex;
1182 while (subgroupAttrs[indx]) {
1186 /* Get *all* matching "member" values from this group. */
1187 values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
1192 * Now we are going to pare the subgroup members of this group
1193 * to *just* the subgroups, add them to the compare_nodep, and
1194 * then proceed to check the new level of subgroups.
1196 while (values[val_index]) {
1197 /* Check if this entry really is a group. */
1199 result = LDAP_COMPARE_FALSE;
1200 while ((tmp_sgcIndex < subgroupclasses->nelts)
1201 && (result != LDAP_COMPARE_TRUE)) {
1202 result = uldap_cache_compare(r, ldc, url,
1205 sgc_ents[tmp_sgcIndex].name
1208 if (result != LDAP_COMPARE_TRUE) {
1212 /* It's a group, so add it to the array. */
1213 if (result == LDAP_COMPARE_TRUE) {
1214 char **newgrp = (char **) apr_array_push(subgroups);
1215 *newgrp = apr_pstrdup(r->pool, values[val_index]);
1219 ldap_value_free(values);
1225 ldap_msgfree(sga_res);
1227 if (subgroups->nelts > 0) {
1228 /* We need to fill in tmp_local_subgroups using the data from LDAP */
1231 res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
1232 res->subgroupDNs = apr_pcalloc(r->pool,
1233 sizeof(char *) * (subgroups->nelts));
1234 for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
1235 res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
1245 * Does a recursive lookup operation to try to find a user within (cached)
1246 * nested groups. It accepts a cache that it will use to lookup previous
1247 * compare attempts. We cache two kinds of compares (require group compares)
1248 * and (require user compares). Each compare has a different cache node:
1249 * require group includes the DN; require user does not because the require
1250 * user cache is owned by the
1252 * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
1255 * 1. Call uldap_cache_compare for each subgroupclass value to check the
1256 * generic, user-agnostic, cached group entry. This will create a new generic
1257 * cache entry if there
1258 * wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we
1260 * 2. Lock The cache and get the generic cache entry.
1261 * 3. Check if there is already a subgrouplist in this generic group's cache
1263 * A. If there is, go to step 4.
1264 * B. If there isn't:
1265 * i) Use ldap_search to get the full list
1266 * of subgroup "members" (which may include non-group "members").
1267 * ii) Use uldap_cache_compare to strip the list down to just groups.
1268 * iii) Lock and add this stripped down list to the cache of the generic
1270 * 4. Loop through the sgl and call uldap_cache_compare (using the user info)
1272 * subgroup to see if the subgroup contains the user and to get the subgroups
1274 * cache (with user-afinity, if they aren't already there).
1275 * A. If the user is in the subgroup, then we'll be returning
1276 * LDAP_COMPARE_TRUE.
1277 * B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via
1278 * uldap_cache_compare) then recursively call this function to get the
1279 * sub-subgroups added...
1280 * 5. Cleanup local allocations.
1281 * 6. Return the final result.
1284 static int uldap_cache_check_subgroups(request_rec *r,
1285 util_ldap_connection_t *ldc,
1286 const char *url, const char *dn,
1287 const char *attrib, const char *value,
1288 char **subgroupAttrs,
1289 apr_array_header_t *subgroupclasses,
1290 int cur_subgroup_depth,
1291 int max_subgroup_depth)
1293 int result = LDAP_COMPARE_FALSE;
1294 util_url_node_t *curl;
1295 util_url_node_t curnode;
1296 util_compare_node_t *compare_nodep;
1297 util_compare_node_t the_compare_node;
1298 util_compare_subgroup_t *tmp_local_sgl = NULL;
1299 int sgl_cached_empty = 0, sgindex = 0, base_sgcIndex = 0;
1300 struct mod_auth_ldap_groupattr_entry_t *sgc_ents =
1301 (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1302 util_ldap_state_t *st = (util_ldap_state_t *)
1303 ap_get_module_config(r->server->module_config,
1307 * Stop looking at deeper levels of nested groups if we have reached the
1308 * max. Since we already checked the top-level group in uldap_cache_compare,
1309 * we don't need to check it again here - so if max_subgroup_depth is set
1310 * to 0, we won't check it (i.e. that is why we check < rather than <=).
1311 * We'll be calling uldap_cache_compare from here to check if the user is
1312 * in the next level before we recurse into that next level looking for
1315 if (cur_subgroup_depth >= max_subgroup_depth) {
1316 return LDAP_COMPARE_FALSE;
1320 * 1. Check the "groupiness" of the specified basedn. Stopping at the first
1323 while ((base_sgcIndex < subgroupclasses->nelts)
1324 && (result != LDAP_COMPARE_TRUE)) {
1325 result = uldap_cache_compare(r, ldc, url, dn, "objectClass",
1326 sgc_ents[base_sgcIndex].name);
1327 if (result != LDAP_COMPARE_TRUE) {
1332 if (result != LDAP_COMPARE_TRUE) {
1333 ldc->reason = "DN failed group verification.";
1338 * 2. Find previously created cache entry and check if there is already a
1343 curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
1344 LDAP_CACHE_UNLOCK();
1346 if (curl && curl->compare_cache) {
1347 /* make a comparison to the cache */
1350 the_compare_node.dn = (char *)dn;
1351 the_compare_node.attrib = (char *)"objectClass";
1352 the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1353 the_compare_node.result = 0;
1354 the_compare_node.sgl_processed = 0;
1355 the_compare_node.subgroupList = NULL;
1357 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1360 if (compare_nodep != NULL) {
1362 * Found the generic group entry... but the user isn't in this
1363 * group or we wouldn't be here.
1365 if (compare_nodep->sgl_processed) {
1366 if (compare_nodep->subgroupList) {
1367 /* Make a local copy of the subgroup list */
1369 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1370 "Making local copy of SGL for "
1371 "group (%s)(objectClass=%s) ",
1372 dn, (char *)sgc_ents[base_sgcIndex].name);
1373 tmp_local_sgl = apr_pcalloc(r->pool,
1374 sizeof(util_compare_subgroup_t));
1375 tmp_local_sgl->len = compare_nodep->subgroupList->len;
1376 tmp_local_sgl->subgroupDNs =
1377 apr_pcalloc(r->pool,
1378 sizeof(char *) * compare_nodep->subgroupList->len);
1379 for (i = 0; i < compare_nodep->subgroupList->len; i++) {
1380 tmp_local_sgl->subgroupDNs[i] =
1381 apr_pstrdup(r->pool,
1382 compare_nodep->subgroupList->subgroupDNs[i]);
1386 sgl_cached_empty = 1;
1390 LDAP_CACHE_UNLOCK();
1393 if (!tmp_local_sgl && !sgl_cached_empty) {
1394 /* No Cached SGL, retrieve from LDAP */
1395 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1396 "no cached SGL for %s, retrieving from LDAP", dn);
1397 tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs,
1399 if (!tmp_local_sgl) {
1400 /* No SGL aailable via LDAP either */
1401 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "no subgroups for %s",
1405 if (curl && curl->compare_cache) {
1407 * Find the generic group cache entry and add the sgl we just retrieved.
1411 the_compare_node.dn = (char *)dn;
1412 the_compare_node.attrib = (char *)"objectClass";
1413 the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1414 the_compare_node.result = 0;
1415 the_compare_node.sgl_processed = 0;
1416 the_compare_node.subgroupList = NULL;
1418 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1421 if (compare_nodep == NULL) {
1423 * The group entry we want to attach our SGL to doesn't exist.
1424 * We only got here if we verified this DN was actually a group
1425 * based on the objectClass, but we can't call the compare function
1426 * while we already hold the cache lock -- only the insert.
1428 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1429 "Cache entry for %s doesn't exist", dn);
1430 the_compare_node.result = LDAP_COMPARE_TRUE;
1431 util_ald_cache_insert(curl->compare_cache, &the_compare_node);
1432 compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1434 if (compare_nodep == NULL) {
1435 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1436 "util_ldap: Couldn't retrieve group entry "
1437 "for %s from cache",
1443 * We have a valid cache entry and a locally generated SGL.
1444 * Attach the SGL to the cache entry
1446 if (compare_nodep && !compare_nodep->sgl_processed) {
1447 if (!tmp_local_sgl) {
1448 /* We looked up an SGL for a group and found it to be empty */
1449 if (compare_nodep->subgroupList == NULL) {
1450 compare_nodep->sgl_processed = 1;
1454 util_compare_subgroup_t *sgl_copy =
1455 util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
1456 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1457 "Copying local SGL of len %d for group %s into cache",
1458 tmp_local_sgl->len, dn);
1460 if (compare_nodep->subgroupList) {
1461 util_ald_sgl_free(curl->compare_cache,
1462 &(compare_nodep->subgroupList));
1464 compare_nodep->subgroupList = sgl_copy;
1465 compare_nodep->sgl_processed = 1;
1468 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1469 "Copy of SGL failed to obtain shared memory, "
1470 "couldn't update cache");
1474 LDAP_CACHE_UNLOCK();
1479 * tmp_local_sgl has either been created, or copied out of the cache
1480 * If tmp_local_sgl is NULL, there are no subgroups to process and we'll
1483 result = LDAP_COMPARE_FALSE;
1484 if (!tmp_local_sgl) {
1488 while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
1489 const char *group = NULL;
1490 group = tmp_local_sgl->subgroupDNs[sgindex];
1492 * 4. Now loop through the subgroupList and call uldap_cache_compare
1493 * to check for the user.
1495 result = uldap_cache_compare(r, ldc, url, group, attrib, value);
1496 if (result == LDAP_COMPARE_TRUE) {
1498 * 4.A. We found the user in the subgroup. Return
1499 * LDAP_COMPARE_TRUE.
1501 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1502 "Found user %s in a subgroup (%s) at level %d of %d.",
1503 r->user, group, cur_subgroup_depth+1,
1504 max_subgroup_depth);
1508 * 4.B. We didn't find the user in this subgroup, so recurse into
1509 * it and keep looking.
1511 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1512 "User %s not found in subgroup (%s) at level %d of "
1513 "%d.", r->user, group, cur_subgroup_depth+1,
1514 max_subgroup_depth);
1515 result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
1516 value, subgroupAttrs,
1518 cur_subgroup_depth+1,
1519 max_subgroup_depth);
1528 static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
1529 const char *url, const char *basedn,
1530 int scope, char **attrs, const char *filter,
1531 const char *bindpw, const char **binddn,
1532 const char ***retvals)
1534 const char **vals = NULL;
1537 LDAPMessage *res, *entry;
1541 util_url_node_t *curl; /* Cached URL node */
1542 util_url_node_t curnode;
1543 util_search_node_t *search_nodep; /* Cached search node */
1544 util_search_node_t the_search_node;
1547 util_ldap_state_t *st =
1548 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1551 /* Get the cache node for this url */
1554 curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1557 curl = util_ald_create_caches(st, url);
1559 LDAP_CACHE_UNLOCK();
1563 the_search_node.username = filter;
1564 search_nodep = util_ald_cache_fetch(curl->search_cache,
1566 if (search_nodep != NULL) {
1568 /* found entry in search cache... */
1569 curtime = apr_time_now();
1572 * Remove this item from the cache if its expired. If the sent
1573 * password doesn't match the storepassword, the entry will
1574 * be removed and readded later if the credentials pass
1577 if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1578 /* ...but entry is too old */
1579 util_ald_cache_remove(curl->search_cache, search_nodep);
1581 else if ( (search_nodep->bindpw)
1582 && (search_nodep->bindpw[0] != '\0')
1583 && (strcmp(search_nodep->bindpw, bindpw) == 0))
1585 /* ...and entry is valid */
1586 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1589 *retvals = apr_pcalloc(r->pool, sizeof(char *) * search_nodep->numvals);
1590 for (i = 0; i < search_nodep->numvals; i++) {
1591 (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1594 LDAP_CACHE_UNLOCK();
1595 ldc->reason = "Authentication successful (cached)";
1596 return LDAP_SUCCESS;
1599 /* unlock this read lock */
1600 LDAP_CACHE_UNLOCK();
1604 * At this point, there is no valid cached search, so lets do the search.
1608 * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1611 if (failures++ > 10) {
1614 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1618 /* try do the search */
1619 result = ldap_search_ext_s(ldc->ldap,
1620 (char *)basedn, scope,
1621 (char *)filter, attrs, 0,
1622 NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
1623 if (AP_LDAP_IS_SERVER_DOWN(result))
1625 ldc->reason = "ldap_search_ext_s() for user failed with server down";
1626 uldap_connection_unbind(ldc);
1630 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1631 if (result != LDAP_SUCCESS) {
1632 ldc->reason = "ldap_search_ext_s() for user failed";
1637 * We should have found exactly one entry; to find a different
1638 * number is an error.
1640 count = ldap_count_entries(ldc->ldap, res);
1644 ldc->reason = "User not found";
1646 ldc->reason = "User is not unique (search found two "
1649 return LDAP_NO_SUCH_OBJECT;
1652 entry = ldap_first_entry(ldc->ldap, res);
1654 /* Grab the dn, copy it into the pool, and free it again */
1655 dn = ldap_get_dn(ldc->ldap, entry);
1656 *binddn = apr_pstrdup(r->pool, dn);
1660 * A bind to the server with an empty password always succeeds, so
1661 * we check to ensure that the password is not empty. This implies
1662 * that users who actually do have empty passwords will never be
1663 * able to authenticate with this module. I don't see this as a big
1666 if (!bindpw || strlen(bindpw) <= 0) {
1668 ldc->reason = "Empty password not allowed";
1669 return LDAP_INVALID_CREDENTIALS;
1673 * Attempt to bind with the retrieved dn and the password. If the bind
1674 * fails, it means that the password is wrong (the dn obviously
1675 * exists, since we just retrieved it)
1677 result = uldap_simple_bind(ldc, (char *)*binddn, (char *)bindpw,
1679 if (AP_LDAP_IS_SERVER_DOWN(result) ||
1680 (result == LDAP_TIMEOUT && failures == 0)) {
1681 if (AP_LDAP_IS_SERVER_DOWN(result))
1682 ldc->reason = "ldap_simple_bind() to check user credentials "
1683 "failed with server down";
1685 ldc->reason = "ldap_simple_bind() to check user credentials "
1688 uldap_connection_unbind(ldc);
1692 /* failure? if so - return */
1693 if (result != LDAP_SUCCESS) {
1694 ldc->reason = "ldap_simple_bind() to check user credentials failed";
1696 uldap_connection_unbind(ldc);
1701 * We have just bound the connection to a different user and password
1702 * combination, which might be reused unintentionally next time this
1703 * connection is used from the connection pool. To ensure no confusion,
1704 * we mark the connection as unbound.
1710 * Get values for the provided attributes.
1716 vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1723 values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1724 while (values && values[j]) {
1725 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1726 : apr_pstrdup(r->pool, values[j]);
1729 ldap_value_free(values);
1737 * Add the new username to the search cache.
1741 the_search_node.username = filter;
1742 the_search_node.dn = *binddn;
1743 the_search_node.bindpw = bindpw;
1744 the_search_node.lastbind = apr_time_now();
1745 the_search_node.vals = vals;
1746 the_search_node.numvals = numvals;
1748 /* Search again to make sure that another thread didn't ready insert
1749 * this node into the cache before we got here. If it does exist then
1750 * update the lastbind
1752 search_nodep = util_ald_cache_fetch(curl->search_cache,
1754 if ((search_nodep == NULL) ||
1755 (strcmp(*binddn, search_nodep->dn) != 0)) {
1757 /* Nothing in cache, insert new entry */
1758 util_ald_cache_insert(curl->search_cache, &the_search_node);
1760 else if ((!search_nodep->bindpw) ||
1761 (strcmp(bindpw, search_nodep->bindpw) != 0)) {
1763 /* Entry in cache is invalid, remove it and insert new one */
1764 util_ald_cache_remove(curl->search_cache, search_nodep);
1765 util_ald_cache_insert(curl->search_cache, &the_search_node);
1768 /* Cache entry is valid, update lastbind */
1769 search_nodep->lastbind = the_search_node.lastbind;
1771 LDAP_CACHE_UNLOCK();
1775 ldc->reason = "Authentication successful";
1776 return LDAP_SUCCESS;
1780 * This function will return the DN of the entry matching userid.
1781 * It is used to get the DN in case some other module than mod_auth_ldap
1782 * has authenticated the user.
1783 * The function is basically a copy of uldap_cache_checkuserid
1784 * with password checking removed.
1786 static int uldap_cache_getuserdn(request_rec *r, util_ldap_connection_t *ldc,
1787 const char *url, const char *basedn,
1788 int scope, char **attrs, const char *filter,
1789 const char **binddn, const char ***retvals)
1791 const char **vals = NULL;
1794 LDAPMessage *res, *entry;
1798 util_url_node_t *curl; /* Cached URL node */
1799 util_url_node_t curnode;
1800 util_search_node_t *search_nodep; /* Cached search node */
1801 util_search_node_t the_search_node;
1804 util_ldap_state_t *st =
1805 (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1808 /* Get the cache node for this url */
1811 curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1814 curl = util_ald_create_caches(st, url);
1816 LDAP_CACHE_UNLOCK();
1820 the_search_node.username = filter;
1821 search_nodep = util_ald_cache_fetch(curl->search_cache,
1823 if (search_nodep != NULL) {
1825 /* found entry in search cache... */
1826 curtime = apr_time_now();
1829 * Remove this item from the cache if its expired.
1831 if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1832 /* ...but entry is too old */
1833 util_ald_cache_remove(curl->search_cache, search_nodep);
1836 /* ...and entry is valid */
1837 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1840 *retvals = apr_pcalloc(r->pool, sizeof(char *) * search_nodep->numvals);
1841 for (i = 0; i < search_nodep->numvals; i++) {
1842 (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1845 LDAP_CACHE_UNLOCK();
1846 ldc->reason = "Search successful (cached)";
1847 return LDAP_SUCCESS;
1850 /* unlock this read lock */
1851 LDAP_CACHE_UNLOCK();
1855 * At this point, there is no valid cached search, so lets do the search.
1859 * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1862 if (failures++ > 10) {
1865 if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1869 /* try do the search */
1870 result = ldap_search_ext_s(ldc->ldap,
1871 (char *)basedn, scope,
1872 (char *)filter, attrs, 0,
1873 NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
1874 if (AP_LDAP_IS_SERVER_DOWN(result))
1876 ldc->reason = "ldap_search_ext_s() for user failed with server down";
1877 uldap_connection_unbind(ldc);
1881 /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1882 if (result != LDAP_SUCCESS) {
1883 ldc->reason = "ldap_search_ext_s() for user failed";
1888 * We should have found exactly one entry; to find a different
1889 * number is an error.
1891 count = ldap_count_entries(ldc->ldap, res);
1895 ldc->reason = "User not found";
1897 ldc->reason = "User is not unique (search found two "
1900 return LDAP_NO_SUCH_OBJECT;
1903 entry = ldap_first_entry(ldc->ldap, res);
1905 /* Grab the dn, copy it into the pool, and free it again */
1906 dn = ldap_get_dn(ldc->ldap, entry);
1907 *binddn = apr_pstrdup(r->pool, dn);
1911 * Get values for the provided attributes.
1917 vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1924 values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1925 while (values && values[j]) {
1926 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1927 : apr_pstrdup(r->pool, values[j]);
1930 ldap_value_free(values);
1938 * Add the new username to the search cache.
1942 the_search_node.username = filter;
1943 the_search_node.dn = *binddn;
1944 the_search_node.bindpw = NULL;
1945 the_search_node.lastbind = apr_time_now();
1946 the_search_node.vals = vals;
1947 the_search_node.numvals = numvals;
1949 /* Search again to make sure that another thread didn't ready insert
1950 * this node into the cache before we got here. If it does exist then
1951 * update the lastbind
1953 search_nodep = util_ald_cache_fetch(curl->search_cache,
1955 if ((search_nodep == NULL) ||
1956 (strcmp(*binddn, search_nodep->dn) != 0)) {
1958 /* Nothing in cache, insert new entry */
1959 util_ald_cache_insert(curl->search_cache, &the_search_node);
1962 * Don't update lastbind on entries with bindpw because
1963 * we haven't verified that password. It's OK to update
1964 * the entry if there is no password in it.
1966 else if (!search_nodep->bindpw) {
1967 /* Cache entry is valid, update lastbind */
1968 search_nodep->lastbind = the_search_node.lastbind;
1970 LDAP_CACHE_UNLOCK();
1975 ldc->reason = "Search successful";
1976 return LDAP_SUCCESS;
1980 * Reports if ssl support is enabled
1982 * 1 = enabled, 0 = not enabled
1984 static int uldap_ssl_supported(request_rec *r)
1986 util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
1987 r->server->module_config, &ldap_module);
1989 return(st->ssl_supported);
1993 /* ---------------------------------------- */
1994 /* config directives */
1997 static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy,
2000 util_ldap_state_t *st =
2001 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2003 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2009 st->cache_bytes = atol(bytes);
2011 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2012 "ldap cache: Setting shared memory cache size to "
2013 "%" APR_SIZE_T_FMT " bytes.",
2019 static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy,
2022 util_ldap_state_t *st =
2023 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2025 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2032 st->cache_file = ap_server_root_relative(st->pool, file);
2035 st->cache_file = NULL;
2038 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2039 "LDAP cache: Setting shared memory cache file to %s bytes.",
2045 static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy,
2048 util_ldap_state_t *st =
2049 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2051 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2057 st->search_cache_ttl = atol(ttl) * 1000000;
2059 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2060 "ldap cache: Setting cache TTL to %ld microseconds.",
2061 st->search_cache_ttl);
2066 static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy,
2069 util_ldap_state_t *st =
2070 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2072 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2078 st->search_cache_size = atol(size);
2079 if (st->search_cache_size < 0) {
2080 st->search_cache_size = 0;
2083 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2084 "ldap cache: Setting search cache size to %ld entries.",
2085 st->search_cache_size);
2090 static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy,
2093 util_ldap_state_t *st =
2094 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2096 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2102 st->compare_cache_ttl = atol(ttl) * 1000000;
2104 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2105 "ldap cache: Setting operation cache TTL to %ld microseconds.",
2106 st->compare_cache_ttl);
2111 static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy,
2114 util_ldap_state_t *st =
2115 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2117 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2123 st->compare_cache_size = atol(size);
2124 if (st->compare_cache_size < 0) {
2125 st->compare_cache_size = 0;
2128 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2129 "ldap cache: Setting operation cache size to %ld entries.",
2130 st->compare_cache_size);
2137 * Parse the certificate type.
2139 * The type can be one of the following:
2140 * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64,
2141 * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64
2143 * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned.
2145 static int util_ldap_parse_cert_type(const char *type)
2147 /* Authority file in binary DER format */
2148 if (0 == strcasecmp("CA_DER", type)) {
2149 return APR_LDAP_CA_TYPE_DER;
2152 /* Authority file in Base64 format */
2153 else if (0 == strcasecmp("CA_BASE64", type)) {
2154 return APR_LDAP_CA_TYPE_BASE64;
2157 /* Netscape certificate database file/directory */
2158 else if (0 == strcasecmp("CA_CERT7_DB", type)) {
2159 return APR_LDAP_CA_TYPE_CERT7_DB;
2162 /* Netscape secmod file/directory */
2163 else if (0 == strcasecmp("CA_SECMOD", type)) {
2164 return APR_LDAP_CA_TYPE_SECMOD;
2167 /* Client cert file in DER format */
2168 else if (0 == strcasecmp("CERT_DER", type)) {
2169 return APR_LDAP_CERT_TYPE_DER;
2172 /* Client cert file in Base64 format */
2173 else if (0 == strcasecmp("CERT_BASE64", type)) {
2174 return APR_LDAP_CERT_TYPE_BASE64;
2177 /* Client cert file in PKCS#12 format */
2178 else if (0 == strcasecmp("CERT_PFX", type)) {
2179 return APR_LDAP_CERT_TYPE_PFX;
2182 /* Netscape client cert database file/directory */
2183 else if (0 == strcasecmp("CERT_KEY3_DB", type)) {
2184 return APR_LDAP_CERT_TYPE_KEY3_DB;
2187 /* Netscape client cert nickname */
2188 else if (0 == strcasecmp("CERT_NICKNAME", type)) {
2189 return APR_LDAP_CERT_TYPE_NICKNAME;
2192 /* Client cert key file in DER format */
2193 else if (0 == strcasecmp("KEY_DER", type)) {
2194 return APR_LDAP_KEY_TYPE_DER;
2197 /* Client cert key file in Base64 format */
2198 else if (0 == strcasecmp("KEY_BASE64", type)) {
2199 return APR_LDAP_KEY_TYPE_BASE64;
2202 /* Client cert key file in PKCS#12 format */
2203 else if (0 == strcasecmp("KEY_PFX", type)) {
2204 return APR_LDAP_KEY_TYPE_PFX;
2208 return APR_LDAP_CA_TYPE_UNKNOWN;
2215 * Set LDAPTrustedGlobalCert.
2217 * This directive takes either two or three arguments:
2218 * - certificate type
2219 * - certificate file / directory / nickname
2220 * - certificate password (optional)
2222 * This directive may only be used globally.
2224 static const char *util_ldap_set_trusted_global_cert(cmd_parms *cmd,
2228 const char *password)
2230 util_ldap_state_t *st =
2231 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2233 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2237 apr_ldap_opt_tls_cert_t *cert;
2243 /* handle the certificate type */
2245 cert_type = util_ldap_parse_cert_type(type);
2246 if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2247 return apr_psprintf(cmd->pool, "The certificate type %s is "
2248 "not recognised. It should be one "
2249 "of CA_DER, CA_BASE64, CA_CERT7_DB, "
2250 "CA_SECMOD, CERT_DER, CERT_BASE64, "
2251 "CERT_KEY3_DB, CERT_NICKNAME, "
2252 "KEY_DER, KEY_BASE64", type);
2256 return "Certificate type was not specified.";
2259 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2260 "LDAP: SSL trusted global cert - %s (type %s)",
2263 /* add the certificate to the global array */
2264 cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
2265 cert->type = cert_type;
2267 cert->password = password;
2269 /* if file is a file or path, fix the path */
2270 if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2271 cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2273 cert->path = ap_server_root_relative(cmd->pool, file);
2275 ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2278 ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
2279 "LDAP: Could not open SSL trusted certificate "
2280 "authority file - %s",
2281 cert->path == NULL ? file : cert->path);
2282 return "Invalid global certificate file path";
2291 * Set LDAPTrustedClientCert.
2293 * This directive takes either two or three arguments:
2294 * - certificate type
2295 * - certificate file / directory / nickname
2296 * - certificate password (optional)
2298 static const char *util_ldap_set_trusted_client_cert(cmd_parms *cmd,
2302 const char *password)
2304 util_ldap_config_t *dc = config;
2308 apr_ldap_opt_tls_cert_t *cert;
2310 /* handle the certificate type */
2312 cert_type = util_ldap_parse_cert_type(type);
2313 if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2314 return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2315 "not recognised. It should be one "
2316 "of CA_DER, CA_BASE64, "
2317 "CERT_DER, CERT_BASE64, "
2318 "CERT_NICKNAME, CERT_PFX, "
2319 "KEY_DER, KEY_BASE64, KEY_PFX",
2322 else if ( APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
2323 APR_LDAP_CA_TYPE_SECMOD == cert_type ||
2324 APR_LDAP_CERT_TYPE_PFX == cert_type ||
2325 APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
2326 return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2327 "only valid within a "
2328 "LDAPTrustedGlobalCert directive. "
2329 "Only CA_DER, CA_BASE64, "
2330 "CERT_DER, CERT_BASE64, "
2331 "CERT_NICKNAME, KEY_DER, and "
2332 "KEY_BASE64 may be used.", type);
2336 return "Certificate type was not specified.";
2339 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2340 "LDAP: SSL trusted client cert - %s (type %s)",
2343 /* add the certificate to the client array */
2344 cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(dc->client_certs);
2345 cert->type = cert_type;
2347 cert->password = password;
2349 /* if file is a file or path, fix the path */
2350 if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2351 cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2353 cert->path = ap_server_root_relative(cmd->pool, file);
2355 ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2358 ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
2359 "LDAP: Could not open SSL client certificate "
2361 cert->path == NULL ? file : cert->path);
2362 return "Invalid client certificate file path";
2372 * Set LDAPTrustedMode.
2374 * This directive sets what encryption mode to use on a connection:
2375 * - None (No encryption)
2376 * - SSL (SSL encryption)
2377 * - STARTTLS (TLS encryption)
2379 static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
2382 util_ldap_state_t *st =
2383 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2386 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2387 "LDAP: SSL trusted mode - %s",
2390 if (0 == strcasecmp("NONE", mode)) {
2391 st->secure = APR_LDAP_NONE;
2393 else if (0 == strcasecmp("SSL", mode)) {
2394 st->secure = APR_LDAP_SSL;
2396 else if ( (0 == strcasecmp("TLS", mode))
2397 || (0 == strcasecmp("STARTTLS", mode))) {
2398 st->secure = APR_LDAP_STARTTLS;
2401 return "Invalid LDAPTrustedMode setting: must be one of NONE, "
2402 "SSL, or TLS/STARTTLS";
2409 static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
2413 util_ldap_state_t *st =
2414 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2416 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2422 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2423 "LDAP: SSL verify server certificate - %s",
2424 mode?"TRUE":"FALSE");
2426 st->verify_svr_cert = mode;
2432 static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
2436 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2437 util_ldap_state_t *st =
2438 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2441 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2447 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2448 st->connectionTimeout = atol(ttl);
2450 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2451 "ldap connection: Setting connection timeout to %ld seconds.",
2452 st->connectionTimeout);
2454 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
2455 "LDAP: Connection timeout option not supported by the "
2456 "LDAP SDK in use." );
2463 static const char *util_ldap_set_chase_referrals(cmd_parms *cmd,
2467 util_ldap_config_t *dc = config;
2469 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2470 "LDAP: Setting referral chasing %s",
2471 (mode == AP_LDAP_CHASEREFERRALS_ON) ? "ON" : "OFF");
2473 dc->ChaseReferrals = mode;
2478 static const char *util_ldap_set_debug_level(cmd_parms *cmd,
2481 util_ldap_state_t *st =
2482 (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2485 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2490 #ifndef AP_LDAP_OPT_DEBUG
2491 return "This directive is not supported with the currently linked LDAP library";
2494 st->debug_level = atoi(arg);
2498 static const char *util_ldap_set_referral_hop_limit(cmd_parms *cmd,
2500 const char *hop_limit)
2502 util_ldap_config_t *dc = config;
2504 dc->ReferralHopLimit = atol(hop_limit);
2506 if (dc->ReferralHopLimit <= 0) {
2507 return "LDAPReferralHopLimit must be greater than zero (Use 'LDAPReferrals Off' to disable referral chasing)";
2510 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2511 "LDAP: Limit chased referrals to maximum of %d hops.",
2512 dc->ReferralHopLimit);
2517 static void *util_ldap_create_dir_config(apr_pool_t *p, char *d) {
2518 util_ldap_config_t *dc =
2519 (util_ldap_config_t *) apr_pcalloc(p,sizeof(util_ldap_config_t));
2521 /* defaults are AP_LDAP_CHASEREFERRALS_ON and AP_LDAP_DEFAULT_HOPLIMIT */
2522 dc->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
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 "ldap connection: Setting op timeout to %ld seconds.",
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->secure = APR_LDAP_NONE;
2603 st->connectionTimeout = 10;
2604 st->opTimeout = apr_pcalloc(p, sizeof(struct timeval));
2605 st->opTimeout->tv_sec = 60;
2606 st->verify_svr_cert = 1;
2611 /* cache-related settings are not merged here, but in the post_config hook,
2612 * since the cache has not yet sprung to life
2614 static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
2617 util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
2618 util_ldap_state_t *base = (util_ldap_state_t *) basev;
2619 util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
2621 st->pool = overrides->pool;
2623 st->mutex = overrides->mutex;
2626 /* The cache settings can not be modified in a
2627 virtual host since all server use the same
2628 shared memory cache. */
2629 st->cache_bytes = base->cache_bytes;
2630 st->search_cache_ttl = base->search_cache_ttl;
2631 st->search_cache_size = base->search_cache_size;
2632 st->compare_cache_ttl = base->compare_cache_ttl;
2633 st->compare_cache_size = base->compare_cache_size;
2634 st->util_ldap_cache_lock = base->util_ldap_cache_lock;
2636 st->connections = NULL;
2637 st->ssl_supported = 0;
2638 st->global_certs = apr_array_append(p, base->global_certs,
2639 overrides->global_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, NULL,
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 st_vhost->util_ldap_cache = st->util_ldap_cache;
2757 ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s,
2758 "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
2759 "for VHOST: %s", st->cache_shm, st->cache_rmm,
2760 s_vhost->server_hostname);
2762 s_vhost = s_vhost->next;
2764 #if APR_HAS_SHARED_MEMORY
2767 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
2768 "LDAP cache: LDAPSharedCacheSize is zero, disabling "
2769 "shared memory cache");
2773 /* log the LDAP SDK used
2776 apr_ldap_err_t *result = NULL;
2777 apr_ldap_info(p, &(result));
2778 if (result != NULL) {
2779 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s", result->reason);
2783 apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
2784 util_ldap_cleanup_module);
2787 * Initialize SSL support, and log the result for the benefit of the admin.
2789 * If SSL is not supported it is not necessarily an error, as the
2790 * application may not want to use it.
2792 rc = apr_ldap_ssl_init(p,
2796 if (APR_SUCCESS == rc) {
2797 rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
2798 (void *)st->global_certs, &(result_err));
2801 if (APR_SUCCESS == rc) {
2802 st->ssl_supported = 1;
2803 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2804 "LDAP: SSL support available" );
2807 st->ssl_supported = 0;
2808 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2809 "LDAP: SSL support unavailable%s%s",
2810 result_err ? ": " : "",
2811 result_err ? result_err->reason : "");
2814 /* Initialize the rebind callback's cross reference list. */
2815 apr_ldap_rebind_init (p);
2817 #ifdef AP_LDAP_OPT_DEBUG
2818 if (st->debug_level > 0) {
2819 result = ldap_set_option(NULL, AP_LDAP_OPT_DEBUG, &st->debug_level);
2820 if (result != LDAP_SUCCESS) {
2821 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
2822 "LDAP: Could not set the LDAP library debug level to %d:(%d) %s",
2823 st->debug_level, result, ldap_err2string(result));
2831 static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
2834 util_ldap_state_t *st = ap_get_module_config(s->module_config,
2837 if (!st->util_ldap_cache_lock) return;
2839 sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
2840 apr_global_mutex_lockfile(st->util_ldap_cache_lock), p);
2841 if (sts != APR_SUCCESS) {
2842 ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s,
2843 "Failed to initialise global mutex %s in child process",
2844 ldap_cache_mutex_type);
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 AP_DECLARE_MODULE(ldap) = {
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 */