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
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17/*
18 * util_ldap.c: LDAP things
19 *
20 * Original code from auth_ldap module for Apache v1.3:
21 * Copyright 1998, 1999 Enbridge Pipelines Inc.
22 * Copyright 1999-2001 Dave Carrigan
23 */
24
25#include "httpd.h"
26#include "http_config.h"
27#include "http_core.h"
28#include "http_log.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"
34
35#include <apr_strings.h>
36
37#if APR_HAVE_UNISTD_H
38#include <unistd.h>
39#endif
40
41#if !APR_HAS_LDAP
42#error mod_ldap requires APR-util to have LDAP support built in
43#endif
44
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.
49 */
50#ifndef APR_LDAP_SIZELIMIT
51#define APR_LDAP_SIZELIMIT -1
52#endif
53
54#ifdef LDAP_OPT_DEBUG_LEVEL
55#define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG_LEVEL
56#else
57#ifdef LDAP_OPT_DEBUG
58#define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG
59#endif
60#endif
61
62#define AP_LDAP_HOPLIMIT_UNSET -1
63#define AP_LDAP_CHASEREFERRALS_SDKDEFAULT -1
64#define AP_LDAP_CHASEREFERRALS_OFF 0
65#define AP_LDAP_CHASEREFERRALS_ON 1
66
67#define AP_LDAP_CONNPOOL_DEFAULT -1
68#define AP_LDAP_CONNPOOL_INFINITE -2
69
70#if !defined(LDAP_OPT_NETWORK_TIMEOUT) && defined(LDAP_OPT_CONNECT_TIMEOUT)
71#define LDAP_OPT_NETWORK_TIMEOUT LDAP_OPT_CONNECT_TIMEOUT
72#endif
73
74module AP_MODULE_DECLARE_DATA ldap_module;
75static const char *ldap_cache_mutex_type = "ldap-cache";
76static apr_status_t uldap_connection_unbind(void *param);
77
78#define LDAP_CACHE_LOCK() do {                                  \
79    if (st->util_ldap_cache_lock)                               \
80        apr_global_mutex_lock(st->util_ldap_cache_lock);        \
81} while (0)
82
83#define LDAP_CACHE_UNLOCK() do {                                \
84    if (st->util_ldap_cache_lock)                               \
85        apr_global_mutex_unlock(st->util_ldap_cache_lock);      \
86} while (0)
87
88static void util_ldap_strdup (char **str, const char *newstr)
89{
90    if (*str) {
91        free(*str);
92        *str = NULL;
93    }
94
95    if (newstr) {
96        *str = strdup(newstr);
97    }
98}
99
100/*
101 * Status Handler
102 * --------------
103 *
104 * This handler generates a status page about the current performance of
105 * the LDAP cache. It is enabled as follows:
106 *
107 * <Location /ldap-status>
108 *   SetHandler ldap-status
109 * </Location>
110 *
111 */
112static int util_ldap_handler(request_rec *r)
113{
114    util_ldap_state_t *st;
115
116    r->allowed |= (1 << M_GET);
117    if (r->method_number != M_GET) {
118        return DECLINED;
119    }
120
121    if (strcmp(r->handler, "ldap-status")) {
122        return DECLINED;
123    }
124
125    st = (util_ldap_state_t *) ap_get_module_config(r->server->module_config,
126            &ldap_module);
127
128    ap_set_content_type(r, "text/html; charset=ISO-8859-1");
129
130    if (r->header_only)
131        return OK;
132
133    ap_rputs(DOCTYPE_HTML_3_2
134             "<html><head><title>LDAP Cache Information</title></head>\n", r);
135    ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information"
136             "</h1>\n", r);
137
138    util_ald_cache_display(r, st);
139
140    return OK;
141}
142
143
144
145/* ------------------------------------------------------------------ */
146/*
147 * Closes an LDAP connection by unlocking it. The next time
148 * uldap_connection_find() is called this connection will be
149 * available for reuse.
150 */
151static void uldap_connection_close(util_ldap_connection_t *ldc)
152{
153
154     /* We leave bound LDAP connections floating around in our pool,
155      * but always check/fix the binddn/bindpw when we take them out
156      * of the pool
157      */
158     if (!ldc->keep) {
159         uldap_connection_unbind(ldc);
160     }
161     else {
162         /* mark our connection as available for reuse */
163         ldc->freed = apr_time_now();
164#if APR_HAS_THREADS
165         apr_thread_mutex_unlock(ldc->lock);
166#endif
167     }
168}
169
170
171/*
172 * Destroys an LDAP connection by unbinding and closing the connection to
173 * the LDAP server. It is used to bring the connection back to a known
174 * state after an error.
175 */
176static apr_status_t uldap_connection_unbind(void *param)
177{
178    util_ldap_connection_t *ldc = param;
179
180    if (ldc) {
181        if (ldc->ldap) {
182            ldap_unbind_s(ldc->ldap);
183            ldc->ldap = NULL;
184        }
185        ldc->bound = 0;
186
187        /* forget the rebind info for this conn */
188        if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
189            apr_ldap_rebind_remove(ldc->ldap);
190            apr_pool_clear(ldc->rebind_pool);
191        }
192    }
193
194    return APR_SUCCESS;
195}
196
197/* not presently used, not part of the API */
198#if 0
199/*
200 * util_ldap_connection_remove frees all storage associated with the LDAP
201 * connection and removes it completely from the per-virtualhost list of
202 * connections
203 *
204 * The caller should hold the lock for this connection
205 */
206static apr_status_t util_ldap_connection_remove (void *param) {
207    util_ldap_connection_t *ldc = param, *l  = NULL, *prev = NULL;
208    util_ldap_state_t *st;
209
210    if (!ldc) return APR_SUCCESS;
211
212    st = ldc->st;
213
214    uldap_connection_unbind(ldc);
215
216#if APR_HAS_THREADS
217    apr_thread_mutex_lock(st->mutex);
218#endif
219
220    /* Remove ldc from the list */
221    for (l=st->connections; l; l=l->next) {
222        if (l == ldc) {
223            if (prev) {
224                prev->next = l->next;
225            }
226            else {
227                st->connections = l->next;
228            }
229            break;
230        }
231        prev = l;
232    }
233
234    if (ldc->bindpw) {
235        free((void*)ldc->bindpw);
236    }
237    if (ldc->binddn) {
238        free((void*)ldc->binddn);
239    }
240
241#if APR_HAS_THREADS
242    apr_thread_mutex_unlock(ldc->lock);
243    apr_thread_mutex_unlock(st->mutex);
244#endif
245
246    /* Destory the pool associated with this connection */
247
248    apr_pool_destroy(ldc->pool);
249
250    return APR_SUCCESS;
251}
252#endif
253
254static int uldap_connection_init(request_rec *r,
255                                 util_ldap_connection_t *ldc)
256{
257    int rc = 0, ldap_option = 0;
258    int version  = LDAP_VERSION3;
259    apr_ldap_err_t *result = NULL;
260#ifdef LDAP_OPT_NETWORK_TIMEOUT
261    struct timeval connectionTimeout = {0};
262#endif
263    util_ldap_state_t *st =
264        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
265        &ldap_module);
266    int have_client_certs = !apr_is_empty_array(ldc->client_certs);
267#if !APR_HAS_SOLARIS_LDAPSDK
268    /*
269     * Normally we enable SSL/TLS with apr_ldap_set_option(), except
270     * with Solaris LDAP, where this is broken.
271     */
272    int secure = APR_LDAP_NONE;
273#else
274    /*
275     * With Solaris LDAP, we enable TSL via the secure argument
276     * to apr_ldap_init(). This requires a fix from apr-util >= 1.4.0.
277     *
278     * Just in case client certificates ever get supported, we
279     * handle those as with the other LDAP SDKs.
280     */
281    int secure = have_client_certs ? APR_LDAP_NONE : ldc->secure;
282#endif
283
284    /* Since the host will include a port if the default port is not used,
285     * always specify the default ports for the port parameter.  This will
286     * allow a host string that contains multiple hosts the ability to mix
287     * some hosts with ports and some without. All hosts which do not
288     * specify a port will use the default port.
289     */
290    apr_ldap_init(r->pool, &(ldc->ldap),
291                  ldc->host,
292                  APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
293                  secure, &(result));
294
295    if (NULL == result) {
296        /* something really bad happened */
297        ldc->bound = 0;
298        if (NULL == ldc->reason) {
299            ldc->reason = "LDAP: ldap initialization failed";
300        }
301        return(APR_EGENERAL);
302    }
303
304    if (result->rc) {
305        ldc->reason = result->reason;
306        ldc->bound = 0;
307        return result->rc;
308    }
309
310    if (NULL == ldc->ldap)
311    {
312        ldc->bound = 0;
313        if (NULL == ldc->reason) {
314            ldc->reason = "LDAP: ldap initialization failed";
315        }
316        else {
317            ldc->reason = result->reason;
318        }
319        return(result->rc);
320    }
321
322    if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
323        /* Now that we have an ldap struct, add it to the referral list for rebinds. */
324        rc = apr_ldap_rebind_add(ldc->rebind_pool, ldc->ldap, ldc->binddn, ldc->bindpw);
325        if (rc != APR_SUCCESS) {
326            ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, APLOGNO(01277)
327                    "LDAP: Unable to add rebind cross reference entry. Out of memory?");
328            uldap_connection_unbind(ldc);
329            ldc->reason = "LDAP: Unable to add rebind cross reference entry.";
330            return(rc);
331        }
332    }
333
334    /* always default to LDAP V3 */
335    ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
336
337    /* set client certificates */
338    if (have_client_certs) {
339        apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
340                            ldc->client_certs, &(result));
341        if (LDAP_SUCCESS != result->rc) {
342            uldap_connection_unbind( ldc );
343            ldc->reason = result->reason;
344            return(result->rc);
345        }
346    }
347
348    /* switch on SSL/TLS */
349    if (APR_LDAP_NONE != ldc->secure
350#if APR_HAS_SOLARIS_LDAPSDK
351        /* See comments near apr_ldap_init() above */
352        && have_client_certs
353#endif
354       ) {
355        apr_ldap_set_option(r->pool, ldc->ldap,
356                            APR_LDAP_OPT_TLS, &ldc->secure, &(result));
357        if (LDAP_SUCCESS != result->rc) {
358            uldap_connection_unbind( ldc );
359            ldc->reason = result->reason;
360            return(result->rc);
361        }
362    }
363
364    /* Set the alias dereferencing option */
365    ldap_option = ldc->deref;
366    ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);
367
368    if (ldc->ChaseReferrals != AP_LDAP_CHASEREFERRALS_SDKDEFAULT) {
369        /* Set options for rebind and referrals. */
370        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01278)
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),
377                &(result));
378        if (result->rc != LDAP_SUCCESS) {
379            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01279)
380                    "Unable to set LDAP_OPT_REFERRALS option to %s: %d.",
381                    ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"),
382                    result->rc);
383            result->reason = "Unable to set LDAP_OPT_REFERRALS.";
384            ldc->reason = result->reason;
385            uldap_connection_unbind(ldc);
386            return(result->rc);
387        }
388    }
389
390    if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
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, APLOGNO(01280)
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,
399                    &(result));
400            if (result->rc != LDAP_SUCCESS) {
401                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01281)
402                        "Unable to set LDAP_OPT_REFHOPLIMIT option to %d: %d.",
403                        ldc->ReferralHopLimit,
404                        result->rc);
405                result->reason = "Unable to set LDAP_OPT_REFHOPLIMIT.";
406                ldc->reason = result->reason;
407                uldap_connection_unbind(ldc);
408                return(result->rc);
409            }
410        }
411    }
412
413/*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
414#ifdef APR_LDAP_OPT_VERIFY_CERT
415    apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_VERIFY_CERT,
416                        &(st->verify_svr_cert), &(result));
417#else
418#if defined(LDAPSSL_VERIFY_SERVER)
419    if (st->verify_svr_cert) {
420        result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
421    }
422    else {
423        result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
424    }
425#elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
426    /* This is not a per-connection setting so just pass NULL for the
427       Ldap connection handle */
428    if (st->verify_svr_cert) {
429        int i = LDAP_OPT_X_TLS_DEMAND;
430        result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
431    }
432    else {
433        int i = LDAP_OPT_X_TLS_NEVER;
434        result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
435    }
436#endif
437#endif
438
439#ifdef LDAP_OPT_NETWORK_TIMEOUT
440    if (st->connectionTimeout > 0) {
441        connectionTimeout.tv_sec = st->connectionTimeout;
442    }
443
444    if (connectionTimeout.tv_sec > 0) {
445        rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
446                                 (void *)&connectionTimeout, &(result));
447        if (APR_SUCCESS != rc) {
448            ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01282)
449                             "LDAP: Could not set the connection timeout");
450        }
451    }
452#endif
453
454#ifdef LDAP_OPT_TIMEOUT
455    /*
456     * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap
457     * function calls and not just ldap_search_ext_s(), which accepts a timeout
458     * parameter.
459     * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all
460     * XXX: synchronous ldap function calls with asynchronous calls and using
461     * XXX: ldap_result() with a timeout.
462     */
463    if (st->opTimeout) {
464        rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_TIMEOUT,
465                                 st->opTimeout, &(result));
466        if (APR_SUCCESS != rc) {
467            ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01283)
468                             "LDAP: Could not set LDAP_OPT_TIMEOUT");
469        }
470    }
471#endif
472
473    return(rc);
474}
475
476static int uldap_ld_errno(util_ldap_connection_t *ldc)
477{
478    int ldaprc;
479#ifdef LDAP_OPT_ERROR_NUMBER
480    if (LDAP_SUCCESS == ldap_get_option(ldc->ldap, LDAP_OPT_ERROR_NUMBER, &ldaprc)) return ldaprc;
481#endif
482#ifdef LDAP_OPT_RESULT_CODE
483    if (LDAP_SUCCESS == ldap_get_option(ldc->ldap, LDAP_OPT_RESULT_CODE, &ldaprc)) return ldaprc;
484#endif
485    return LDAP_OTHER;
486}
487
488/*
489 * Replacement function for ldap_simple_bind_s() with a timeout.
490 * To do this in a portable way, we have to use ldap_simple_bind() and
491 * ldap_result().
492 *
493 * Returns LDAP_SUCCESS on success; and an error code on failure
494 */
495static int uldap_simple_bind(util_ldap_connection_t *ldc, char *binddn,
496                             char* bindpw, struct timeval *timeout)
497{
498    LDAPMessage *result;
499    int rc;
500    int msgid = ldap_simple_bind(ldc->ldap, binddn, bindpw);
501    if (msgid == -1) {
502        ldc->reason = "LDAP: ldap_simple_bind() failed";
503        return uldap_ld_errno(ldc);
504    }
505    rc = ldap_result(ldc->ldap, msgid, 0, timeout, &result);
506    if (rc == -1) {
507        ldc->reason = "LDAP: ldap_simple_bind() result retrieval failed";
508        /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
509        return uldap_ld_errno(ldc);
510    }
511    else if (rc == 0) {
512        ldc->reason = "LDAP: ldap_simple_bind() timed out";
513        rc = LDAP_TIMEOUT;
514    } else if (ldap_parse_result(ldc->ldap, result, &rc, NULL, NULL, NULL,
515                                 NULL, 1) == -1) {
516        ldc->reason = "LDAP: ldap_simple_bind() parse result failed";
517        return uldap_ld_errno(ldc);
518    }
519    return rc;
520}
521
522/*
523 * Connect to the LDAP server and binds. Does not connect if already
524 * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
525 *
526 * Returns LDAP_SUCCESS on success; and an error code on failure
527 */
528static int uldap_connection_open(request_rec *r,
529                                 util_ldap_connection_t *ldc)
530{
531    int rc = 0;
532    int failures = 0;
533    int new_connection = 0;
534    util_ldap_state_t *st;
535
536    /* sanity check for NULL */
537    if (!ldc) {
538        return -1;
539    }
540
541    /* If the connection is already bound, return
542    */
543    if (ldc->bound)
544    {
545        ldc->reason = "LDAP: connection open successful (already bound)";
546        return LDAP_SUCCESS;
547    }
548
549    /* create the ldap session handle
550    */
551    if (NULL == ldc->ldap)
552    {
553       new_connection = 1;
554       rc = uldap_connection_init( r, ldc );
555       if (LDAP_SUCCESS != rc)
556       {
557           return rc;
558       }
559    }
560
561
562    st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
563                                                   &ldap_module);
564
565    /* loop trying to bind up to st->retries times if LDAP_SERVER_DOWN or LDAP_TIMEOUT
566     * are returned.  Close the connection before the first retry, and then on every
567     * other retry.
568     *
569     * On Success or any other error, break out of the loop.
570     *
571     * NOTE: Looping is probably not a great idea. If the server isn't
572     * responding the chances it will respond after a few tries are poor.
573     * However, the original code looped and it only happens on
574     * the error condition.
575     */
576
577    while (failures <= st->retries) {
578        if (failures > 0 && st->retry_delay > 0) {
579            apr_sleep(st->retry_delay);
580        }
581        rc = uldap_simple_bind(ldc, (char *)ldc->binddn, (char *)ldc->bindpw,
582                               st->opTimeout);
583
584        if (rc == LDAP_SUCCESS) break;
585
586        failures++;
587
588        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);
592        }
593        else if (rc == LDAP_TIMEOUT) {
594            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01284)
595                          "ldap_simple_bind() timed out on %s "
596                          "connection, dropped by firewall?",
597                          new_connection ? "new" : "reused");
598        }
599        else {
600            /* Other errors not retryable */
601            break;
602        }
603
604        if (!(failures % 2)) {
605            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
606                          "attempt to re-init the connection");
607            uldap_connection_unbind(ldc);
608            if (LDAP_SUCCESS != uldap_connection_init(r, ldc)) {
609                /* leave rc as the initial bind return code */
610                break;
611            }
612        }
613    }
614
615    /* free the handle if there was an error
616    */
617    if (LDAP_SUCCESS != rc)
618    {
619        uldap_connection_unbind(ldc);
620        ldc->reason = "LDAP: ldap_simple_bind() failed";
621    }
622    else {
623        ldc->bound = 1;
624        ldc->reason = "LDAP: connection open successful";
625    }
626
627    return(rc);
628}
629
630
631/*
632 * Compare client certificate arrays.
633 *
634 * Returns 1 on compare failure, 0 otherwise.
635 */
636static int compare_client_certs(apr_array_header_t *srcs,
637                                apr_array_header_t *dests)
638{
639    int i = 0;
640    struct apr_ldap_opt_tls_cert_t *src, *dest;
641
642    /* arrays both NULL? if so, then equal */
643    if (srcs == NULL && dests == NULL) {
644        return 0;
645    }
646
647    /* arrays different length or either NULL? If so, then not equal */
648    if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
649        return 1;
650    }
651
652    /* run an actual comparison */
653    src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
654    dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
655    for (i = 0; i < srcs->nelts; i++) {
656        if ((strcmp(src[i].path, dest[i].path)) ||
657            (src[i].type != dest[i].type) ||
658            /* One is passwordless? If so, then not equal */
659            ((src[i].password == NULL) ^ (dest[i].password == NULL)) ||
660            (src[i].password != NULL && dest[i].password != NULL &&
661             strcmp(src[i].password, dest[i].password))) {
662            return 1;
663        }
664    }
665
666    /* if we got here, the cert arrays were identical */
667    return 0;
668
669}
670
671
672/*
673 * Find an existing ldap connection struct that matches the
674 * provided ldap connection parameters.
675 *
676 * If not found in the cache, a new ldc structure will be allocated
677 * from st->pool and returned to the caller.  If found in the cache,
678 * a pointer to the existing ldc structure will be returned.
679 */
680static util_ldap_connection_t *
681            uldap_connection_find(request_rec *r,
682                                  const char *host, int port,
683                                  const char *binddn, const char *bindpw,
684                                  deref_options deref, int secure)
685{
686    struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
687    int secureflag = secure;
688    apr_time_t now = apr_time_now();
689
690    util_ldap_state_t *st =
691        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
692        &ldap_module);
693    util_ldap_config_t *dc =
694        (util_ldap_config_t *) ap_get_module_config(r->per_dir_config, &ldap_module);
695
696#if APR_HAS_THREADS
697    /* mutex lock this function */
698    apr_thread_mutex_lock(st->mutex);
699#endif
700
701    if (secure < APR_LDAP_NONE) {
702        secureflag = st->secure;
703    }
704
705    /* Search for an exact connection match in the list that is not
706     * being used.
707     */
708    for (l=st->connections,p=NULL; l; l=l->next) {
709#if APR_HAS_THREADS
710        if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
711#endif
712        if (   (l->port == port) && (strcmp(l->host, host) == 0)
713            && ((!l->binddn && !binddn) || (l->binddn && binddn
714                                             && !strcmp(l->binddn, binddn)))
715            && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
716                                             && !strcmp(l->bindpw, bindpw)))
717            && (l->deref == deref) && (l->secure == secureflag)
718            && !compare_client_certs(dc->client_certs, l->client_certs))
719        {
720            if (st->connection_pool_ttl > 0) {
721                if (l->bound && (now - l->freed) > st->connection_pool_ttl) {
722                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
723                                  "Removing LDAP connection last used %" APR_TIME_T_FMT " seconds ago",
724                                  (now - l->freed) / APR_USEC_PER_SEC);
725                    uldap_connection_unbind(l);
726                    /* Go ahead (by falling through) and use it, so we don't create more just to unbind some other old ones */
727                }
728            }
729            break;
730        }
731#if APR_HAS_THREADS
732            /* If this connection didn't match the criteria, then we
733             * need to unlock the mutex so it is available to be reused.
734             */
735            apr_thread_mutex_unlock(l->lock);
736        }
737#endif
738        p = l;
739    }
740
741    /* If nothing found, search again, but we don't care about the
742     * binddn and bindpw this time.
743     */
744    if (!l) {
745        for (l=st->connections,p=NULL; l; l=l->next) {
746#if APR_HAS_THREADS
747            if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
748
749#endif
750            if ((l->port == port) && (strcmp(l->host, host) == 0) &&
751                (l->deref == deref) && (l->secure == secureflag) &&
752                !compare_client_certs(dc->client_certs, l->client_certs))
753            {
754                /* the bind credentials have changed */
755                /* no check for connection_pool_ttl, since we are unbinding any way */
756                uldap_connection_unbind(l);
757
758                util_ldap_strdup((char**)&(l->binddn), binddn);
759                util_ldap_strdup((char**)&(l->bindpw), bindpw);
760                break;
761            }
762#if APR_HAS_THREADS
763                /* If this connection didn't match the criteria, then we
764                 * need to unlock the mutex so it is available to be reused.
765                 */
766                apr_thread_mutex_unlock(l->lock);
767            }
768#endif
769            p = l;
770        }
771    }
772
773/* artificially disable cache */
774/* l = NULL; */
775
776    /* If no connection was found after the second search, we
777     * must create one.
778     */
779    if (!l) {
780        apr_pool_t *newpool;
781        if (apr_pool_create(&newpool, NULL) != APR_SUCCESS) {
782            ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01285)
783                          "util_ldap: Failed to create memory pool");
784#if APR_HAS_THREADS
785            apr_thread_mutex_unlock(st->mutex);
786#endif
787            return NULL;
788        }
789
790        /*
791         * Add the new connection entry to the linked list. Note that we
792         * don't actually establish an LDAP connection yet; that happens
793         * the first time authentication is requested.
794         */
795
796        /* create the details of this connection in the new pool */
797        l = apr_pcalloc(newpool, sizeof(util_ldap_connection_t));
798        l->pool = newpool;
799        l->st = st;
800
801#if APR_HAS_THREADS
802        apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, l->pool);
803        apr_thread_mutex_lock(l->lock);
804#endif
805        l->bound = 0;
806        l->host = apr_pstrdup(l->pool, host);
807        l->port = port;
808        l->deref = deref;
809        util_ldap_strdup((char**)&(l->binddn), binddn);
810        util_ldap_strdup((char**)&(l->bindpw), bindpw);
811        l->ChaseReferrals = dc->ChaseReferrals;
812        l->ReferralHopLimit = dc->ReferralHopLimit;
813
814        /* The security mode after parsing the URL will always be either
815         * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
816         * If the security setting is NONE, override it to the security
817         * setting optionally supplied by the admin using LDAPTrustedMode
818         */
819        l->secure = secureflag;
820
821        /* save away a copy of the client cert list that is presently valid */
822        l->client_certs = apr_array_copy_hdr(l->pool, dc->client_certs);
823
824        /* whether or not to keep this connection in the pool when it's returned */
825        l->keep = (st->connection_pool_ttl == 0) ? 0 : 1;
826
827        if (l->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
828            if (apr_pool_create(&(l->rebind_pool), l->pool) != APR_SUCCESS) {
829                ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01286)
830                              "util_ldap: Failed to create memory pool");
831#if APR_HAS_THREADS
832                apr_thread_mutex_unlock(st->mutex);
833#endif
834                return NULL;
835            }
836        }
837
838        if (p) {
839            p->next = l;
840        }
841        else {
842            st->connections = l;
843        }
844    }
845
846#if APR_HAS_THREADS
847    apr_thread_mutex_unlock(st->mutex);
848#endif
849    return l;
850}
851
852/* ------------------------------------------------------------------ */
853
854/*
855 * Compares two DNs to see if they're equal. The only way to do this correctly
856 * is to search for the dn and then do ldap_get_dn() on the result. This should
857 * match the initial dn, since it would have been also retrieved with
858 * ldap_get_dn(). This is expensive, so if the configuration value
859 * compare_dn_on_server is false, just does an ordinary strcmp.
860 *
861 * The lock for the ldap cache should already be acquired.
862 */
863static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
864                                 const char *url, const char *dn,
865                                 const char *reqdn, int compare_dn_on_server)
866{
867    int result = 0;
868    util_url_node_t *curl;
869    util_url_node_t curnode;
870    util_dn_compare_node_t *node;
871    util_dn_compare_node_t newnode;
872    int failures = 0;
873    LDAPMessage *res, *entry;
874    char *searchdn;
875
876    util_ldap_state_t *st = (util_ldap_state_t *)
877                            ap_get_module_config(r->server->module_config,
878                                                 &ldap_module);
879
880    /* get cache entry (or create one) */
881    LDAP_CACHE_LOCK();
882
883    curnode.url = url;
884    curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
885    if (curl == NULL) {
886        curl = util_ald_create_caches(st, url);
887    }
888    LDAP_CACHE_UNLOCK();
889
890    /* a simple compare? */
891    if (!compare_dn_on_server) {
892        /* unlock this read lock */
893        if (strcmp(dn, reqdn)) {
894            ldc->reason = "DN Comparison FALSE (direct strcmp())";
895            return LDAP_COMPARE_FALSE;
896        }
897        else {
898            ldc->reason = "DN Comparison TRUE (direct strcmp())";
899            return LDAP_COMPARE_TRUE;
900        }
901    }
902
903    if (curl) {
904        /* no - it's a server side compare */
905        LDAP_CACHE_LOCK();
906
907        /* is it in the compare cache? */
908        newnode.reqdn = (char *)reqdn;
909        node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
910        if (node != NULL) {
911            /* If it's in the cache, it's good */
912            /* unlock this read lock */
913            LDAP_CACHE_UNLOCK();
914            ldc->reason = "DN Comparison TRUE (cached)";
915            return LDAP_COMPARE_TRUE;
916        }
917
918        /* unlock this read lock */
919        LDAP_CACHE_UNLOCK();
920    }
921
922start_over:
923    if (failures > st->retries) {
924        return result;
925    }
926
927    if (failures > 0 && st->retry_delay > 0) {
928        apr_sleep(st->retry_delay);
929    }
930
931    /* make a server connection */
932    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
933        /* connect to server failed */
934        return result;
935    }
936
937    /* search for reqdn */
938    result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
939                               "(objectclass=*)", NULL, 1,
940                               NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
941    if (AP_LDAP_IS_SERVER_DOWN(result))
942    {
943        ldc->reason = "DN Comparison ldap_search_ext_s() "
944                      "failed with server down";
945        uldap_connection_unbind(ldc);
946        failures++;
947        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
948        goto start_over;
949    }
950    if (result == LDAP_TIMEOUT && failures == 0) {
951        /*
952         * we are reusing a connection that doesn't seem to be active anymore
953         * (firewall state drop?), let's try a new connection.
954         */
955        ldc->reason = "DN Comparison ldap_search_ext_s() "
956                      "failed with timeout";
957        uldap_connection_unbind(ldc);
958        failures++;
959        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
960        goto start_over;
961    }
962    if (result != LDAP_SUCCESS) {
963        /* search for reqdn failed - no match */
964        ldc->reason = "DN Comparison ldap_search_ext_s() failed";
965        return result;
966    }
967
968    entry = ldap_first_entry(ldc->ldap, res);
969    searchdn = ldap_get_dn(ldc->ldap, entry);
970
971    ldap_msgfree(res);
972    if (strcmp(dn, searchdn) != 0) {
973        /* compare unsuccessful */
974        ldc->reason = "DN Comparison FALSE (checked on server)";
975        result = LDAP_COMPARE_FALSE;
976    }
977    else {
978        if (curl) {
979            /* compare successful - add to the compare cache */
980            LDAP_CACHE_LOCK();
981            newnode.reqdn = (char *)reqdn;
982            newnode.dn = (char *)dn;
983
984            node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
985            if (   (node == NULL)
986                || (strcmp(reqdn, node->reqdn) != 0)
987                || (strcmp(dn, node->dn) != 0))
988            {
989                util_ald_cache_insert(curl->dn_compare_cache, &newnode);
990            }
991            LDAP_CACHE_UNLOCK();
992        }
993        ldc->reason = "DN Comparison TRUE (checked on server)";
994        result = LDAP_COMPARE_TRUE;
995    }
996    ldap_memfree(searchdn);
997    return result;
998
999}
1000
1001/*
1002 * Does an generic ldap_compare operation. It accepts a cache that it will use
1003 * to lookup the compare in the cache. We cache two kinds of compares
1004 * (require group compares) and (require user compares). Each compare has a
1005 * different cache node: require group includes the DN; require user does not
1006 * because the require user cache is owned by the
1007 *
1008 */
1009static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
1010                               const char *url, const char *dn,
1011                               const char *attrib, const char *value)
1012{
1013    int result = 0;
1014    util_url_node_t *curl;
1015    util_url_node_t curnode;
1016    util_compare_node_t *compare_nodep;
1017    util_compare_node_t the_compare_node;
1018    apr_time_t curtime = 0; /* silence gcc -Wall */
1019    int failures = 0;
1020
1021    util_ldap_state_t *st = (util_ldap_state_t *)
1022                            ap_get_module_config(r->server->module_config,
1023                                                 &ldap_module);
1024
1025    /* get cache entry (or create one) */
1026    LDAP_CACHE_LOCK();
1027    curnode.url = url;
1028    curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
1029    if (curl == NULL) {
1030        curl = util_ald_create_caches(st, url);
1031    }
1032    LDAP_CACHE_UNLOCK();
1033
1034    if (curl) {
1035        /* make a comparison to the cache */
1036        LDAP_CACHE_LOCK();
1037        curtime = apr_time_now();
1038
1039        the_compare_node.dn = (char *)dn;
1040        the_compare_node.attrib = (char *)attrib;
1041        the_compare_node.value = (char *)value;
1042        the_compare_node.result = 0;
1043        the_compare_node.sgl_processed = 0;
1044        the_compare_node.subgroupList = NULL;
1045
1046        compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1047                                             &the_compare_node);
1048
1049        if (compare_nodep != NULL) {
1050            /* found it... */
1051            if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
1052                /* ...but it is too old */
1053                util_ald_cache_remove(curl->compare_cache, compare_nodep);
1054            }
1055            else {
1056                /* ...and it is good */
1057                if (LDAP_COMPARE_TRUE == compare_nodep->result) {
1058                    ldc->reason = "Comparison true (cached)";
1059                }
1060                else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
1061                    ldc->reason = "Comparison false (cached)";
1062                }
1063                else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
1064                    ldc->reason = "Comparison no such attribute (cached)";
1065                }
1066                else {
1067                    ldc->reason = "Comparison undefined (cached)";
1068                }
1069
1070                /* record the result code to return with the reason... */
1071                result = compare_nodep->result;
1072                /* and unlock this read lock */
1073                LDAP_CACHE_UNLOCK();
1074                return result;
1075            }
1076        }
1077        /* unlock this read lock */
1078        LDAP_CACHE_UNLOCK();
1079    }
1080
1081start_over:
1082    if (failures > st->retries) {
1083        return result;
1084    }
1085
1086    if (failures > 0 && st->retry_delay > 0) {
1087        apr_sleep(st->retry_delay);
1088    }
1089
1090    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1091        /* connect failed */
1092        return result;
1093    }
1094
1095    result = ldap_compare_s(ldc->ldap,
1096                            (char *)dn,
1097                            (char *)attrib,
1098                            (char *)value);
1099    if (AP_LDAP_IS_SERVER_DOWN(result)) {
1100        /* connection failed - try again */
1101        ldc->reason = "ldap_compare_s() failed with server down";
1102        uldap_connection_unbind(ldc);
1103        failures++;
1104        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1105        goto start_over;
1106    }
1107    if (result == LDAP_TIMEOUT && failures == 0) {
1108        /*
1109         * we are reusing a connection that doesn't seem to be active anymore
1110         * (firewall state drop?), let's try a new connection.
1111         */
1112        ldc->reason = "ldap_compare_s() failed with timeout";
1113        uldap_connection_unbind(ldc);
1114        failures++;
1115        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1116        goto start_over;
1117    }
1118
1119    ldc->reason = "Comparison complete";
1120    if ((LDAP_COMPARE_TRUE == result) ||
1121        (LDAP_COMPARE_FALSE == result) ||
1122        (LDAP_NO_SUCH_ATTRIBUTE == result)) {
1123        if (curl) {
1124            /* compare completed; caching result */
1125            LDAP_CACHE_LOCK();
1126            the_compare_node.lastcompare = curtime;
1127            the_compare_node.result = result;
1128            the_compare_node.sgl_processed = 0;
1129            the_compare_node.subgroupList = NULL;
1130
1131            /* If the node doesn't exist then insert it, otherwise just update
1132             * it with the last results
1133             */
1134            compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1135                                                 &the_compare_node);
1136            if (   (compare_nodep == NULL)
1137                || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
1138                || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
1139                || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
1140            {
1141                void *junk;
1142
1143                junk = util_ald_cache_insert(curl->compare_cache,
1144                                             &the_compare_node);
1145                if (junk == NULL) {
1146                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01287)
1147                                  "cache_compare: Cache insertion failure.");
1148                }
1149            }
1150            else {
1151                compare_nodep->lastcompare = curtime;
1152                compare_nodep->result = result;
1153            }
1154            LDAP_CACHE_UNLOCK();
1155        }
1156        if (LDAP_COMPARE_TRUE == result) {
1157            ldc->reason = "Comparison true (adding to cache)";
1158            return LDAP_COMPARE_TRUE;
1159        }
1160        else if (LDAP_COMPARE_FALSE == result) {
1161            ldc->reason = "Comparison false (adding to cache)";
1162            return LDAP_COMPARE_FALSE;
1163        }
1164        else {
1165            ldc->reason = "Comparison no such attribute (adding to cache)";
1166            return LDAP_NO_SUCH_ATTRIBUTE;
1167        }
1168    }
1169    return result;
1170}
1171
1172
1173static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r,
1174                                                    util_ldap_connection_t *ldc,
1175                                                    const char *url,
1176                                                    const char *dn,
1177                                                    char **subgroupAttrs,
1178                                                    apr_array_header_t *subgroupclasses)
1179{
1180    int failures = 0;
1181    int result = LDAP_COMPARE_FALSE;
1182    util_compare_subgroup_t *res = NULL;
1183    LDAPMessage *sga_res, *entry;
1184    struct mod_auth_ldap_groupattr_entry_t *sgc_ents;
1185    apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
1186    util_ldap_state_t *st = (util_ldap_state_t *)
1187                            ap_get_module_config(r->server->module_config,
1188                                                 &ldap_module);
1189
1190    sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1191
1192    if (!subgroupAttrs) {
1193        return res;
1194    }
1195
1196start_over:
1197    /*
1198     * 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups.
1199     */
1200    if (failures > st->retries) {
1201        return res;
1202    }
1203
1204    if (failures > 0 && st->retry_delay > 0) {
1205        apr_sleep(st->retry_delay);
1206    }
1207
1208
1209    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1210        /* connect failed */
1211        return res;
1212    }
1213
1214    /* try to do the search */
1215    result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
1216                               NULL, subgroupAttrs, 0,
1217                               NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
1218    if (AP_LDAP_IS_SERVER_DOWN(result)) {
1219        ldc->reason = "ldap_search_ext_s() for subgroups failed with server"
1220                      " down";
1221        uldap_connection_unbind(ldc);
1222        failures++;
1223        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1224        goto start_over;
1225    }
1226    if (result == LDAP_TIMEOUT && failures == 0) {
1227        /*
1228         * we are reusing a connection that doesn't seem to be active anymore
1229         * (firewall state drop?), let's try a new connection.
1230         */
1231        ldc->reason = "ldap_search_ext_s() for subgroups failed with timeout";
1232        uldap_connection_unbind(ldc);
1233        failures++;
1234        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1235        goto start_over;
1236    }
1237
1238    /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1239    if (result != LDAP_SUCCESS) {
1240        ldc->reason = "ldap_search_ext_s() for subgroups failed";
1241        return res;
1242    }
1243
1244    entry = ldap_first_entry(ldc->ldap, sga_res);
1245
1246    /*
1247     * Get values for the provided sub-group attributes.
1248     */
1249    if (subgroupAttrs) {
1250        int indx = 0, tmp_sgcIndex;
1251
1252        while (subgroupAttrs[indx]) {
1253            char **values;
1254            int val_index = 0;
1255
1256            /* Get *all* matching "member" values from this group. */
1257            values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
1258
1259            if (values) {
1260                val_index = 0;
1261                /*
1262                 * Now we are going to pare the subgroup members of this group
1263                 * to *just* the subgroups, add them to the compare_nodep, and
1264                 * then proceed to check the new level of subgroups.
1265                 */
1266                while (values[val_index]) {
1267                    /* Check if this entry really is a group. */
1268                    tmp_sgcIndex = 0;
1269                    result = LDAP_COMPARE_FALSE;
1270                    while ((tmp_sgcIndex < subgroupclasses->nelts)
1271                           && (result != LDAP_COMPARE_TRUE)) {
1272                        result = uldap_cache_compare(r, ldc, url,
1273                                                     values[val_index],
1274                                                     "objectClass",
1275                                                     sgc_ents[tmp_sgcIndex].name
1276                                                     );
1277
1278                        if (result != LDAP_COMPARE_TRUE) {
1279                            tmp_sgcIndex++;
1280                        }
1281                    }
1282                    /* It's a group, so add it to the array.  */
1283                    if (result == LDAP_COMPARE_TRUE) {
1284                        char **newgrp = (char **) apr_array_push(subgroups);
1285                        *newgrp = apr_pstrdup(r->pool, values[val_index]);
1286                    }
1287                    val_index++;
1288                }
1289                ldap_value_free(values);
1290            }
1291            indx++;
1292        }
1293    }
1294
1295    ldap_msgfree(sga_res);
1296
1297    if (subgroups->nelts > 0) {
1298        /* We need to fill in tmp_local_subgroups using the data from LDAP */
1299        int sgindex;
1300        char **group;
1301        res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
1302        res->subgroupDNs  = apr_palloc(r->pool,
1303                                       sizeof(char *) * (subgroups->nelts));
1304        for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
1305            res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
1306        }
1307        res->len = sgindex;
1308    }
1309
1310    return res;
1311}
1312
1313
1314/*
1315 * Does a recursive lookup operation to try to find a user within (cached)
1316 * nested groups. It accepts a cache that it will use to lookup previous
1317 * compare attempts. We cache two kinds of compares (require group compares)
1318 * and (require user compares). Each compare has a different cache node:
1319 * require group includes the DN; require user does not because the require
1320 * user cache is owned by the
1321 *
1322 * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
1323 *
1324 *
1325 * 1. Call uldap_cache_compare for each subgroupclass value to check the
1326 *    generic, user-agnostic, cached group entry. This will create a new generic
1327 *    cache entry if there
1328 *    wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we
1329 *    have no groups.
1330 * 2. Lock The cache and get the generic cache entry.
1331 * 3. Check if there is already a subgrouplist in this generic group's cache
1332 *    entry.
1333 *    A. If there is, go to step 4.
1334 *    B. If there isn't:
1335 *       i)   Use ldap_search to get the full list
1336 *            of subgroup "members" (which may include non-group "members").
1337 *       ii)  Use uldap_cache_compare to strip the list down to just groups.
1338 *       iii) Lock and add this stripped down list to the cache of the generic
1339 *            group.
1340 * 4. Loop through the sgl and call uldap_cache_compare (using the user info)
1341 *    for each
1342 *    subgroup to see if the subgroup contains the user and to get the subgroups
1343 *    added to the
1344 *    cache (with user-afinity, if they aren't already there).
1345 *    A. If the user is in the subgroup, then we'll be returning
1346 *       LDAP_COMPARE_TRUE.
1347 *    B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via
1348 *       uldap_cache_compare) then recursively call this function to get the
1349 *       sub-subgroups added...
1350 * 5. Cleanup local allocations.
1351 * 6. Return the final result.
1352 */
1353
1354static int uldap_cache_check_subgroups(request_rec *r,
1355                                       util_ldap_connection_t *ldc,
1356                                       const char *url, const char *dn,
1357                                       const char *attrib, const char *value,
1358                                       char **subgroupAttrs,
1359                                       apr_array_header_t *subgroupclasses,
1360                                       int cur_subgroup_depth,
1361                                       int max_subgroup_depth)
1362{
1363    int result = LDAP_COMPARE_FALSE;
1364    util_url_node_t *curl;
1365    util_url_node_t curnode;
1366    util_compare_node_t *compare_nodep;
1367    util_compare_node_t the_compare_node;
1368    util_compare_subgroup_t *tmp_local_sgl = NULL;
1369    int sgl_cached_empty = 0, sgindex = 0, base_sgcIndex = 0;
1370    struct mod_auth_ldap_groupattr_entry_t *sgc_ents =
1371            (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1372    util_ldap_state_t *st = (util_ldap_state_t *)
1373                            ap_get_module_config(r->server->module_config,
1374                                                 &ldap_module);
1375
1376    /*
1377     * Stop looking at deeper levels of nested groups if we have reached the
1378     * max. Since we already checked the top-level group in uldap_cache_compare,
1379     * we don't need to check it again here - so if max_subgroup_depth is set
1380     * to 0, we won't check it (i.e. that is why we check < rather than <=).
1381     * We'll be calling uldap_cache_compare from here to check if the user is
1382     * in the next level before we recurse into that next level looking for
1383     * more subgroups.
1384     */
1385    if (cur_subgroup_depth >= max_subgroup_depth) {
1386        return LDAP_COMPARE_FALSE;
1387    }
1388
1389    /*
1390     * 1. Check the "groupiness" of the specified basedn. Stopping at the first
1391     *    TRUE return.
1392     */
1393    while ((base_sgcIndex < subgroupclasses->nelts)
1394           && (result != LDAP_COMPARE_TRUE)) {
1395        result = uldap_cache_compare(r, ldc, url, dn, "objectClass",
1396                                     sgc_ents[base_sgcIndex].name);
1397        if (result != LDAP_COMPARE_TRUE) {
1398            base_sgcIndex++;
1399        }
1400    }
1401
1402    if (result != LDAP_COMPARE_TRUE) {
1403        ldc->reason = "DN failed group verification.";
1404        return result;
1405    }
1406
1407    /*
1408     * 2. Find previously created cache entry and check if there is already a
1409     *    subgrouplist.
1410     */
1411    LDAP_CACHE_LOCK();
1412    curnode.url = url;
1413    curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
1414    LDAP_CACHE_UNLOCK();
1415
1416    if (curl && curl->compare_cache) {
1417        /* make a comparison to the cache */
1418        LDAP_CACHE_LOCK();
1419
1420        the_compare_node.dn = (char *)dn;
1421        the_compare_node.attrib = (char *)"objectClass";
1422        the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1423        the_compare_node.result = 0;
1424        the_compare_node.sgl_processed = 0;
1425        the_compare_node.subgroupList = NULL;
1426
1427        compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1428                                             &the_compare_node);
1429
1430        if (compare_nodep != NULL) {
1431            /*
1432             * Found the generic group entry... but the user isn't in this
1433             * group or we wouldn't be here.
1434             */
1435            if (compare_nodep->sgl_processed) {
1436                if (compare_nodep->subgroupList) {
1437                    /* Make a local copy of the subgroup list */
1438                    int i;
1439                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01288)
1440                                  "Making local copy of SGL for "
1441                                  "group (%s)(objectClass=%s) ",
1442                                  dn, (char *)sgc_ents[base_sgcIndex].name);
1443                    tmp_local_sgl = apr_pcalloc(r->pool,
1444                                                sizeof(util_compare_subgroup_t));
1445                    tmp_local_sgl->len = compare_nodep->subgroupList->len;
1446                    tmp_local_sgl->subgroupDNs =
1447                        apr_palloc(r->pool,
1448                                   sizeof(char *) * compare_nodep->subgroupList->len);
1449                    for (i = 0; i < compare_nodep->subgroupList->len; i++) {
1450                        tmp_local_sgl->subgroupDNs[i] =
1451                            apr_pstrdup(r->pool,
1452                                        compare_nodep->subgroupList->subgroupDNs[i]);
1453                    }
1454                }
1455                else {
1456                    sgl_cached_empty = 1;
1457                }
1458            }
1459        }
1460        LDAP_CACHE_UNLOCK();
1461    }
1462
1463    if (!tmp_local_sgl && !sgl_cached_empty) {
1464        /* No Cached SGL, retrieve from LDAP */
1465        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01289)
1466                      "no cached SGL for %s, retrieving from LDAP", dn);
1467        tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs,
1468                                            subgroupclasses);
1469        if (!tmp_local_sgl) {
1470            /* No SGL aailable via LDAP either */
1471            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01290) "no subgroups for %s",
1472                          dn);
1473        }
1474
1475      if (curl && curl->compare_cache) {
1476        /*
1477         * Find the generic group cache entry and add the sgl we just retrieved.
1478         */
1479        LDAP_CACHE_LOCK();
1480
1481        the_compare_node.dn = (char *)dn;
1482        the_compare_node.attrib = (char *)"objectClass";
1483        the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1484        the_compare_node.result = 0;
1485        the_compare_node.sgl_processed = 0;
1486        the_compare_node.subgroupList = NULL;
1487
1488        compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1489                                             &the_compare_node);
1490
1491        if (compare_nodep == NULL) {
1492            /*
1493             * The group entry we want to attach our SGL to doesn't exist.
1494             * We only got here if we verified this DN was actually a group
1495             * based on the objectClass, but we can't call the compare function
1496             * while we already hold the cache lock -- only the insert.
1497             */
1498            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01291)
1499                          "Cache entry for %s doesn't exist", dn);
1500            the_compare_node.result = LDAP_COMPARE_TRUE;
1501            util_ald_cache_insert(curl->compare_cache, &the_compare_node);
1502            compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1503                                                 &the_compare_node);
1504            if (compare_nodep == NULL) {
1505                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01292)
1506                              "util_ldap: Couldn't retrieve group entry "
1507                              "for %s from cache",
1508                              dn);
1509            }
1510        }
1511
1512        /*
1513         * We have a valid cache entry and a locally generated SGL.
1514         * Attach the SGL to the cache entry
1515         */
1516        if (compare_nodep && !compare_nodep->sgl_processed) {
1517            if (!tmp_local_sgl) {
1518                /* We looked up an SGL for a group and found it to be empty */
1519                if (compare_nodep->subgroupList == NULL) {
1520                    compare_nodep->sgl_processed = 1;
1521                }
1522            }
1523            else {
1524                util_compare_subgroup_t *sgl_copy =
1525                    util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
1526                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01293)
1527                             "Copying local SGL of len %d for group %s into cache",
1528                             tmp_local_sgl->len, dn);
1529                if (sgl_copy) {
1530                    if (compare_nodep->subgroupList) {
1531                        util_ald_sgl_free(curl->compare_cache,
1532                                          &(compare_nodep->subgroupList));
1533                    }
1534                    compare_nodep->subgroupList = sgl_copy;
1535                    compare_nodep->sgl_processed = 1;
1536                }
1537                else {
1538                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01294)
1539                                 "Copy of SGL failed to obtain shared memory, "
1540                                 "couldn't update cache");
1541                }
1542            }
1543        }
1544        LDAP_CACHE_UNLOCK();
1545      }
1546    }
1547
1548    /*
1549     * tmp_local_sgl has either been created, or copied out of the cache
1550     * If tmp_local_sgl is NULL, there are no subgroups to process and we'll
1551     * return false
1552     */
1553    result = LDAP_COMPARE_FALSE;
1554    if (!tmp_local_sgl) {
1555        return result;
1556    }
1557
1558    while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
1559        const char *group = NULL;
1560        group = tmp_local_sgl->subgroupDNs[sgindex];
1561        /*
1562         * 4. Now loop through the subgroupList and call uldap_cache_compare
1563         * to check for the user.
1564         */
1565        result = uldap_cache_compare(r, ldc, url, group, attrib, value);
1566        if (result == LDAP_COMPARE_TRUE) {
1567            /*
1568             * 4.A. We found the user in the subgroup. Return
1569             * LDAP_COMPARE_TRUE.
1570             */
1571            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01295)
1572                          "Found user %s in a subgroup (%s) at level %d of %d.",
1573                          r->user, group, cur_subgroup_depth+1,
1574                          max_subgroup_depth);
1575        }
1576        else {
1577            /*
1578             * 4.B. We didn't find the user in this subgroup, so recurse into
1579             * it and keep looking.
1580             */
1581            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01296)
1582                          "User %s not found in subgroup (%s) at level %d of "
1583                          "%d.", r->user, group, cur_subgroup_depth+1,
1584                          max_subgroup_depth);
1585            result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
1586                                                 value, subgroupAttrs,
1587                                                 subgroupclasses,
1588                                                 cur_subgroup_depth+1,
1589                                                 max_subgroup_depth);
1590        }
1591        sgindex++;
1592    }
1593
1594    return result;
1595}
1596
1597
1598static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
1599                                   const char *url, const char *basedn,
1600                                   int scope, char **attrs, const char *filter,
1601                                   const char *bindpw, const char **binddn,
1602                                   const char ***retvals)
1603{
1604    const char **vals = NULL;
1605    int numvals = 0;
1606    int result = 0;
1607    LDAPMessage *res, *entry;
1608    char *dn;
1609    int count;
1610    int failures = 0;
1611    util_url_node_t *curl;              /* Cached URL node */
1612    util_url_node_t curnode;
1613    util_search_node_t *search_nodep;   /* Cached search node */
1614    util_search_node_t the_search_node;
1615    apr_time_t curtime;
1616
1617    util_ldap_state_t *st =
1618        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1619        &ldap_module);
1620
1621    /* Get the cache node for this url */
1622    LDAP_CACHE_LOCK();
1623    curnode.url = url;
1624    curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1625                                                   &curnode);
1626    if (curl == NULL) {
1627        curl = util_ald_create_caches(st, url);
1628    }
1629    LDAP_CACHE_UNLOCK();
1630
1631    if (curl) {
1632        LDAP_CACHE_LOCK();
1633        the_search_node.username = filter;
1634        search_nodep = util_ald_cache_fetch(curl->search_cache,
1635                                            &the_search_node);
1636        if (search_nodep != NULL) {
1637
1638            /* found entry in search cache... */
1639            curtime = apr_time_now();
1640
1641            /*
1642             * Remove this item from the cache if its expired. If the sent
1643             * password doesn't match the storepassword, the entry will
1644             * be removed and readded later if the credentials pass
1645             * authentication.
1646             */
1647            if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1648                /* ...but entry is too old */
1649                util_ald_cache_remove(curl->search_cache, search_nodep);
1650            }
1651            else if (   (search_nodep->bindpw)
1652                     && (search_nodep->bindpw[0] != '\0')
1653                     && (strcmp(search_nodep->bindpw, bindpw) == 0))
1654            {
1655                /* ...and entry is valid */
1656                *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1657                if (attrs) {
1658                    int i;
1659                    *retvals = apr_palloc(r->pool, sizeof(char *) * search_nodep->numvals);
1660                    for (i = 0; i < search_nodep->numvals; i++) {
1661                        (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1662                    }
1663                }
1664                LDAP_CACHE_UNLOCK();
1665                ldc->reason = "Authentication successful (cached)";
1666                return LDAP_SUCCESS;
1667            }
1668        }
1669        /* unlock this read lock */
1670        LDAP_CACHE_UNLOCK();
1671    }
1672
1673    /*
1674     * At this point, there is no valid cached search, so lets do the search.
1675     */
1676
1677    /*
1678     * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1679     */
1680start_over:
1681    if (failures > st->retries) {
1682        return result;
1683    }
1684
1685    if (failures > 0 && st->retry_delay > 0) {
1686        apr_sleep(st->retry_delay);
1687    }
1688
1689    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1690        return result;
1691    }
1692
1693    /* try do the search */
1694    result = ldap_search_ext_s(ldc->ldap,
1695                               (char *)basedn, scope,
1696                               (char *)filter, attrs, 0,
1697                               NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
1698    if (AP_LDAP_IS_SERVER_DOWN(result))
1699    {
1700        ldc->reason = "ldap_search_ext_s() for user failed with server down";
1701        uldap_connection_unbind(ldc);
1702        failures++;
1703        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1704        goto start_over;
1705    }
1706
1707    if (result == LDAP_TIMEOUT) {
1708        ldc->reason = "ldap_search_ext_s() for user failed with timeout";
1709        uldap_connection_unbind(ldc);
1710        failures++;
1711        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1712        goto start_over;
1713    }
1714
1715
1716    /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1717    if (result != LDAP_SUCCESS) {
1718        ldc->reason = "ldap_search_ext_s() for user failed";
1719        return result;
1720    }
1721
1722    /*
1723     * We should have found exactly one entry; to find a different
1724     * number is an error.
1725     */
1726    count = ldap_count_entries(ldc->ldap, res);
1727    if (count != 1)
1728    {
1729        if (count == 0 )
1730            ldc->reason = "User not found";
1731        else
1732            ldc->reason = "User is not unique (search found two "
1733                          "or more matches)";
1734        ldap_msgfree(res);
1735        return LDAP_NO_SUCH_OBJECT;
1736    }
1737
1738    entry = ldap_first_entry(ldc->ldap, res);
1739
1740    /* Grab the dn, copy it into the pool, and free it again */
1741    dn = ldap_get_dn(ldc->ldap, entry);
1742    *binddn = apr_pstrdup(r->pool, dn);
1743    ldap_memfree(dn);
1744
1745    /*
1746     * A bind to the server with an empty password always succeeds, so
1747     * we check to ensure that the password is not empty. This implies
1748     * that users who actually do have empty passwords will never be
1749     * able to authenticate with this module. I don't see this as a big
1750     * problem.
1751     */
1752    if (!bindpw || strlen(bindpw) <= 0) {
1753        ldap_msgfree(res);
1754        ldc->reason = "Empty password not allowed";
1755        return LDAP_INVALID_CREDENTIALS;
1756    }
1757
1758    /*
1759     * Attempt to bind with the retrieved dn and the password. If the bind
1760     * fails, it means that the password is wrong (the dn obviously
1761     * exists, since we just retrieved it)
1762     */
1763    result = uldap_simple_bind(ldc, (char *)*binddn, (char *)bindpw,
1764                               st->opTimeout);
1765    if (AP_LDAP_IS_SERVER_DOWN(result) ||
1766        (result == LDAP_TIMEOUT && failures == 0)) {
1767        if (AP_LDAP_IS_SERVER_DOWN(result))
1768            ldc->reason = "ldap_simple_bind() to check user credentials "
1769                          "failed with server down";
1770        else
1771            ldc->reason = "ldap_simple_bind() to check user credentials "
1772                          "timed out";
1773        ldap_msgfree(res);
1774        uldap_connection_unbind(ldc);
1775        failures++;
1776        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1777        goto start_over;
1778    }
1779
1780    /* failure? if so - return */
1781    if (result != LDAP_SUCCESS) {
1782        ldc->reason = "ldap_simple_bind() to check user credentials failed";
1783        ldap_msgfree(res);
1784        uldap_connection_unbind(ldc);
1785        return result;
1786    }
1787    else {
1788        /*
1789         * We have just bound the connection to a different user and password
1790         * combination, which might be reused unintentionally next time this
1791         * connection is used from the connection pool. To ensure no confusion,
1792         * we mark the connection as unbound.
1793         */
1794        ldc->bound = 0;
1795    }
1796
1797    /*
1798     * Get values for the provided attributes.
1799     */
1800    if (attrs) {
1801        int k = 0;
1802        int i = 0;
1803        while (attrs[k++]);
1804        vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1805        numvals = k;
1806        while (attrs[i]) {
1807            char **values;
1808            int j = 0;
1809            char *str = NULL;
1810            /* get values */
1811            values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1812            while (values && values[j]) {
1813                str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1814                          : apr_pstrdup(r->pool, values[j]);
1815                j++;
1816            }
1817            ldap_value_free(values);
1818            vals[i] = str;
1819            i++;
1820        }
1821        *retvals = vals;
1822    }
1823
1824    /*
1825     * Add the new username to the search cache.
1826     */
1827    if (curl) {
1828        LDAP_CACHE_LOCK();
1829        the_search_node.username = filter;
1830        the_search_node.dn = *binddn;
1831        the_search_node.bindpw = bindpw;
1832        the_search_node.lastbind = apr_time_now();
1833        the_search_node.vals = vals;
1834        the_search_node.numvals = numvals;
1835
1836        /* Search again to make sure that another thread didn't ready insert
1837         * this node into the cache before we got here. If it does exist then
1838         * update the lastbind
1839         */
1840        search_nodep = util_ald_cache_fetch(curl->search_cache,
1841                                            &the_search_node);
1842        if ((search_nodep == NULL) ||
1843            (strcmp(*binddn, search_nodep->dn) != 0)) {
1844
1845            /* Nothing in cache, insert new entry */
1846            util_ald_cache_insert(curl->search_cache, &the_search_node);
1847        }
1848        else if ((!search_nodep->bindpw) ||
1849            (strcmp(bindpw, search_nodep->bindpw) != 0)) {
1850
1851            /* Entry in cache is invalid, remove it and insert new one */
1852            util_ald_cache_remove(curl->search_cache, search_nodep);
1853            util_ald_cache_insert(curl->search_cache, &the_search_node);
1854        }
1855        else {
1856            /* Cache entry is valid, update lastbind */
1857            search_nodep->lastbind = the_search_node.lastbind;
1858        }
1859        LDAP_CACHE_UNLOCK();
1860    }
1861    ldap_msgfree(res);
1862
1863    ldc->reason = "Authentication successful";
1864    return LDAP_SUCCESS;
1865}
1866
1867/*
1868 * This function will return the DN of the entry matching userid.
1869 * It is used to get the DN in case some other module than mod_auth_ldap
1870 * has authenticated the user.
1871 * The function is basically a copy of uldap_cache_checkuserid
1872 * with password checking removed.
1873 */
1874static int uldap_cache_getuserdn(request_rec *r, util_ldap_connection_t *ldc,
1875                                 const char *url, const char *basedn,
1876                                 int scope, char **attrs, const char *filter,
1877                                 const char **binddn, const char ***retvals)
1878{
1879    const char **vals = NULL;
1880    int numvals = 0;
1881    int result = 0;
1882    LDAPMessage *res, *entry;
1883    char *dn;
1884    int count;
1885    int failures = 0;
1886    util_url_node_t *curl;              /* Cached URL node */
1887    util_url_node_t curnode;
1888    util_search_node_t *search_nodep;   /* Cached search node */
1889    util_search_node_t the_search_node;
1890    apr_time_t curtime;
1891
1892    util_ldap_state_t *st =
1893        (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1894        &ldap_module);
1895
1896    /* Get the cache node for this url */
1897    LDAP_CACHE_LOCK();
1898    curnode.url = url;
1899    curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1900                                                   &curnode);
1901    if (curl == NULL) {
1902        curl = util_ald_create_caches(st, url);
1903    }
1904    LDAP_CACHE_UNLOCK();
1905
1906    if (curl) {
1907        LDAP_CACHE_LOCK();
1908        the_search_node.username = filter;
1909        search_nodep = util_ald_cache_fetch(curl->search_cache,
1910                                            &the_search_node);
1911        if (search_nodep != NULL) {
1912
1913            /* found entry in search cache... */
1914            curtime = apr_time_now();
1915
1916            /*
1917             * Remove this item from the cache if its expired.
1918             */
1919            if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1920                /* ...but entry is too old */
1921                util_ald_cache_remove(curl->search_cache, search_nodep);
1922            }
1923            else {
1924                /* ...and entry is valid */
1925                *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1926                if (attrs) {
1927                    int i;
1928                    *retvals = apr_palloc(r->pool, sizeof(char *) * search_nodep->numvals);
1929                    for (i = 0; i < search_nodep->numvals; i++) {
1930                        (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1931                    }
1932                }
1933                LDAP_CACHE_UNLOCK();
1934                ldc->reason = "Search successful (cached)";
1935                return LDAP_SUCCESS;
1936            }
1937        }
1938        /* unlock this read lock */
1939        LDAP_CACHE_UNLOCK();
1940    }
1941
1942    /*
1943     * At this point, there is no valid cached search, so lets do the search.
1944     */
1945
1946    /*
1947     * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1948     */
1949start_over:
1950    if (failures > st->retries) {
1951        return result;
1952    }
1953
1954    if (failures > 0 && st->retry_delay > 0) {
1955        apr_sleep(st->retry_delay);
1956    }
1957
1958    if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1959        return result;
1960    }
1961
1962    /* try do the search */
1963    result = ldap_search_ext_s(ldc->ldap,
1964                               (char *)basedn, scope,
1965                               (char *)filter, attrs, 0,
1966                               NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
1967    if (AP_LDAP_IS_SERVER_DOWN(result))
1968    {
1969        ldc->reason = "ldap_search_ext_s() for user failed with server down";
1970        uldap_connection_unbind(ldc);
1971        failures++;
1972        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
1973        goto start_over;
1974    }
1975
1976    /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1977    if (result != LDAP_SUCCESS) {
1978        ldc->reason = "ldap_search_ext_s() for user failed";
1979        return result;
1980    }
1981
1982    /*
1983     * We should have found exactly one entry; to find a different
1984     * number is an error.
1985     */
1986    count = ldap_count_entries(ldc->ldap, res);
1987    if (count != 1)
1988    {
1989        if (count == 0 )
1990            ldc->reason = "User not found";
1991        else
1992            ldc->reason = "User is not unique (search found two "
1993                          "or more matches)";
1994        ldap_msgfree(res);
1995        return LDAP_NO_SUCH_OBJECT;
1996    }
1997
1998    entry = ldap_first_entry(ldc->ldap, res);
1999
2000    /* Grab the dn, copy it into the pool, and free it again */
2001    dn = ldap_get_dn(ldc->ldap, entry);
2002    *binddn = apr_pstrdup(r->pool, dn);
2003    ldap_memfree(dn);
2004
2005    /*
2006     * Get values for the provided attributes.
2007     */
2008    if (attrs) {
2009        int k = 0;
2010        int i = 0;
2011        while (attrs[k++]);
2012        vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
2013        numvals = k;
2014        while (attrs[i]) {
2015            char **values;
2016            int j = 0;
2017            char *str = NULL;
2018            /* get values */
2019            values = ldap_get_values(ldc->ldap, entry, attrs[i]);
2020            while (values && values[j]) {
2021                str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
2022                          : apr_pstrdup(r->pool, values[j]);
2023                j++;
2024            }
2025            ldap_value_free(values);
2026            vals[i] = str;
2027            i++;
2028        }
2029        *retvals = vals;
2030    }
2031
2032    /*
2033     * Add the new username to the search cache.
2034     */
2035    if (curl) {
2036        LDAP_CACHE_LOCK();
2037        the_search_node.username = filter;
2038        the_search_node.dn = *binddn;
2039        the_search_node.bindpw = NULL;
2040        the_search_node.lastbind = apr_time_now();
2041        the_search_node.vals = vals;
2042        the_search_node.numvals = numvals;
2043
2044        /* Search again to make sure that another thread didn't ready insert
2045         * this node into the cache before we got here. If it does exist then
2046         * update the lastbind
2047         */
2048        search_nodep = util_ald_cache_fetch(curl->search_cache,
2049                                            &the_search_node);
2050        if ((search_nodep == NULL) ||
2051            (strcmp(*binddn, search_nodep->dn) != 0)) {
2052
2053            /* Nothing in cache, insert new entry */
2054            util_ald_cache_insert(curl->search_cache, &the_search_node);
2055        }
2056        /*
2057         * Don't update lastbind on entries with bindpw because
2058         * we haven't verified that password. It's OK to update
2059         * the entry if there is no password in it.
2060         */
2061        else if (!search_nodep->bindpw) {
2062            /* Cache entry is valid, update lastbind */
2063            search_nodep->lastbind = the_search_node.lastbind;
2064        }
2065        LDAP_CACHE_UNLOCK();
2066    }
2067
2068    ldap_msgfree(res);
2069
2070    ldc->reason = "Search successful";
2071    return LDAP_SUCCESS;
2072}
2073
2074/*
2075 * Reports if ssl support is enabled
2076 *
2077 * 1 = enabled, 0 = not enabled
2078 */
2079static int uldap_ssl_supported(request_rec *r)
2080{
2081   util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
2082                                r->server->module_config, &ldap_module);
2083
2084   return(st->ssl_supported);
2085}
2086
2087
2088/* ---------------------------------------- */
2089/* config directives */
2090
2091
2092static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy,
2093                                             const char *bytes)
2094{
2095    util_ldap_state_t *st =
2096        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2097                                                  &ldap_module);
2098    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2099
2100    if (err != NULL) {
2101        return err;
2102    }
2103
2104    st->cache_bytes = atol(bytes);
2105
2106    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01297)
2107                 "ldap cache: Setting shared memory cache size to "
2108                 "%" APR_SIZE_T_FMT " bytes.",
2109                 st->cache_bytes);
2110
2111    return NULL;
2112}
2113
2114static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy,
2115                                            const char *file)
2116{
2117    util_ldap_state_t *st =
2118        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2119                                                  &ldap_module);
2120    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2121
2122    if (err != NULL) {
2123        return err;
2124    }
2125
2126    if (file) {
2127        st->cache_file = ap_server_root_relative(st->pool, file);
2128    }
2129    else {
2130        st->cache_file = NULL;
2131    }
2132
2133    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01298)
2134                 "LDAP cache: Setting shared memory cache file to %s.",
2135                 st->cache_file);
2136
2137    return NULL;
2138}
2139
2140static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy,
2141                                           const char *ttl)
2142{
2143    util_ldap_state_t *st =
2144        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2145                                                  &ldap_module);
2146    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2147
2148    if (err != NULL) {
2149        return err;
2150    }
2151
2152    st->search_cache_ttl = atol(ttl) * 1000000;
2153
2154    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01299)
2155                 "ldap cache: Setting cache TTL to %ld microseconds.",
2156                 st->search_cache_ttl);
2157
2158    return NULL;
2159}
2160
2161static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy,
2162                                               const char *size)
2163{
2164    util_ldap_state_t *st =
2165        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2166                                                  &ldap_module);
2167    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2168
2169    if (err != NULL) {
2170        return err;
2171    }
2172
2173    st->search_cache_size = atol(size);
2174    if (st->search_cache_size < 0) {
2175        st->search_cache_size = 0;
2176    }
2177
2178    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01300)
2179                 "ldap cache: Setting search cache size to %ld entries.",
2180                 st->search_cache_size);
2181
2182    return NULL;
2183}
2184
2185static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy,
2186                                             const char *ttl)
2187{
2188    util_ldap_state_t *st =
2189        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2190                                                  &ldap_module);
2191    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2192
2193    if (err != NULL) {
2194        return err;
2195    }
2196
2197    st->compare_cache_ttl = atol(ttl) * 1000000;
2198
2199    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01301)
2200                 "ldap cache: Setting operation cache TTL to %ld microseconds.",
2201                 st->compare_cache_ttl);
2202
2203    return NULL;
2204}
2205
2206static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy,
2207                                                 const char *size)
2208{
2209    util_ldap_state_t *st =
2210        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2211                                                  &ldap_module);
2212    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2213
2214    if (err != NULL) {
2215        return err;
2216    }
2217
2218    st->compare_cache_size = atol(size);
2219    if (st->compare_cache_size < 0) {
2220        st->compare_cache_size = 0;
2221    }
2222
2223    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01302)
2224                 "ldap cache: Setting operation cache size to %ld entries.",
2225                 st->compare_cache_size);
2226
2227    return NULL;
2228}
2229
2230
2231/**
2232 * Parse the certificate type.
2233 *
2234 * The type can be one of the following:
2235 * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64,
2236 * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64
2237 *
2238 * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned.
2239 */
2240static int util_ldap_parse_cert_type(const char *type)
2241{
2242    /* Authority file in binary DER format */
2243    if (0 == strcasecmp("CA_DER", type)) {
2244        return APR_LDAP_CA_TYPE_DER;
2245    }
2246
2247    /* Authority file in Base64 format */
2248    else if (0 == strcasecmp("CA_BASE64", type)) {
2249        return APR_LDAP_CA_TYPE_BASE64;
2250    }
2251
2252    /* Netscape certificate database file/directory */
2253    else if (0 == strcasecmp("CA_CERT7_DB", type)) {
2254        return APR_LDAP_CA_TYPE_CERT7_DB;
2255    }
2256
2257    /* Netscape secmod file/directory */
2258    else if (0 == strcasecmp("CA_SECMOD", type)) {
2259        return APR_LDAP_CA_TYPE_SECMOD;
2260    }
2261
2262    /* Client cert file in DER format */
2263    else if (0 == strcasecmp("CERT_DER", type)) {
2264        return APR_LDAP_CERT_TYPE_DER;
2265    }
2266
2267    /* Client cert file in Base64 format */
2268    else if (0 == strcasecmp("CERT_BASE64", type)) {
2269        return APR_LDAP_CERT_TYPE_BASE64;
2270    }
2271
2272    /* Client cert file in PKCS#12 format */
2273    else if (0 == strcasecmp("CERT_PFX", type)) {
2274        return APR_LDAP_CERT_TYPE_PFX;
2275    }
2276
2277    /* Netscape client cert database file/directory */
2278    else if (0 == strcasecmp("CERT_KEY3_DB", type)) {
2279        return APR_LDAP_CERT_TYPE_KEY3_DB;
2280    }
2281
2282    /* Netscape client cert nickname */
2283    else if (0 == strcasecmp("CERT_NICKNAME", type)) {
2284        return APR_LDAP_CERT_TYPE_NICKNAME;
2285    }
2286
2287    /* Client cert key file in DER format */
2288    else if (0 == strcasecmp("KEY_DER", type)) {
2289        return APR_LDAP_KEY_TYPE_DER;
2290    }
2291
2292    /* Client cert key file in Base64 format */
2293    else if (0 == strcasecmp("KEY_BASE64", type)) {
2294        return APR_LDAP_KEY_TYPE_BASE64;
2295    }
2296
2297    /* Client cert key file in PKCS#12 format */
2298    else if (0 == strcasecmp("KEY_PFX", type)) {
2299        return APR_LDAP_KEY_TYPE_PFX;
2300    }
2301
2302    else {
2303        return APR_LDAP_CA_TYPE_UNKNOWN;
2304    }
2305
2306}
2307
2308
2309/**
2310 * Set LDAPTrustedGlobalCert.
2311 *
2312 * This directive takes either two or three arguments:
2313 * - certificate type
2314 * - certificate file / directory / nickname
2315 * - certificate password (optional)
2316 *
2317 * This directive may only be used globally.
2318 */
2319static const char *util_ldap_set_trusted_global_cert(cmd_parms *cmd,
2320                                                     void *dummy,
2321                                                     const char *type,
2322                                                     const char *file,
2323                                                     const char *password)
2324{
2325    util_ldap_state_t *st =
2326        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2327                                                  &ldap_module);
2328    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2329    apr_finfo_t finfo;
2330    apr_status_t rv;
2331    int cert_type = 0;
2332    apr_ldap_opt_tls_cert_t *cert;
2333
2334    if (err != NULL) {
2335        return err;
2336    }
2337
2338    /* handle the certificate type */
2339    if (type) {
2340        cert_type = util_ldap_parse_cert_type(type);
2341        if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2342           return apr_psprintf(cmd->pool, "The certificate type %s is "
2343                                          "not recognised. It should be one "
2344                                          "of CA_DER, CA_BASE64, CA_CERT7_DB, "
2345                                          "CA_SECMOD, CERT_DER, CERT_BASE64, "
2346                                          "CERT_KEY3_DB, CERT_NICKNAME, "
2347                                          "KEY_DER, KEY_BASE64", type);
2348        }
2349    }
2350    else {
2351        return "Certificate type was not specified.";
2352    }
2353
2354    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01303)
2355                      "LDAP: SSL trusted global cert - %s (type %s)",
2356                       file, type);
2357
2358    /* add the certificate to the global array */
2359    cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
2360    cert->type = cert_type;
2361    cert->path = file;
2362    cert->password = password;
2363
2364    /* if file is a file or path, fix the path */
2365    if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2366        cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2367
2368        cert->path = ap_server_root_relative(cmd->pool, file);
2369        if (cert->path &&
2370            ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2371                != APR_SUCCESS))
2372        {
2373            ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server, APLOGNO(01304)
2374                         "LDAP: Could not open SSL trusted certificate "
2375                         "authority file - %s",
2376                         cert->path == NULL ? file : cert->path);
2377            return "Invalid global certificate file path";
2378        }
2379    }
2380
2381    return(NULL);
2382}
2383
2384
2385/**
2386 * Set LDAPTrustedClientCert.
2387 *
2388 * This directive takes either two or three arguments:
2389 * - certificate type
2390 * - certificate file / directory / nickname
2391 * - certificate password (optional)
2392 */
2393static const char *util_ldap_set_trusted_client_cert(cmd_parms *cmd,
2394                                                     void *config,
2395                                                     const char *type,
2396                                                     const char *file,
2397                                                     const char *password)
2398{
2399    util_ldap_config_t *dc =  config;
2400    apr_finfo_t finfo;
2401    apr_status_t rv;
2402    int cert_type = 0;
2403    apr_ldap_opt_tls_cert_t *cert;
2404
2405    /* handle the certificate type */
2406    if (type) {
2407        cert_type = util_ldap_parse_cert_type(type);
2408        if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2409            return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2410                                           "not recognised. It should be one "
2411                                           "of CA_DER, CA_BASE64, "
2412                                           "CERT_DER, CERT_BASE64, "
2413                                           "CERT_NICKNAME, CERT_PFX, "
2414                                           "KEY_DER, KEY_BASE64, KEY_PFX",
2415                                           type);
2416        }
2417        else if ( APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
2418                 APR_LDAP_CA_TYPE_SECMOD == cert_type ||
2419                 APR_LDAP_CERT_TYPE_PFX == cert_type ||
2420                 APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
2421            return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2422                                           "only valid within a "
2423                                           "LDAPTrustedGlobalCert directive. "
2424                                           "Only CA_DER, CA_BASE64, "
2425                                           "CERT_DER, CERT_BASE64, "
2426                                           "CERT_NICKNAME, KEY_DER, and "
2427                                           "KEY_BASE64 may be used.", type);
2428        }
2429    }
2430    else {
2431        return "Certificate type was not specified.";
2432    }
2433
2434    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01305)
2435                      "LDAP: SSL trusted client cert - %s (type %s)",
2436                       file, type);
2437
2438    /* add the certificate to the client array */
2439    cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(dc->client_certs);
2440    cert->type = cert_type;
2441    cert->path = file;
2442    cert->password = password;
2443
2444    /* if file is a file or path, fix the path */
2445    if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2446        cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2447
2448        cert->path = ap_server_root_relative(cmd->pool, file);
2449        if (cert->path &&
2450            ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2451                != APR_SUCCESS))
2452        {
2453            ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server, APLOGNO(01306)
2454                         "LDAP: Could not open SSL client certificate "
2455                         "file - %s",
2456                         cert->path == NULL ? file : cert->path);
2457            return "Invalid client certificate file path";
2458        }
2459
2460    }
2461
2462    return(NULL);
2463}
2464
2465
2466/**
2467 * Set LDAPTrustedMode.
2468 *
2469 * This directive sets what encryption mode to use on a connection:
2470 * - None (No encryption)
2471 * - SSL (SSL encryption)
2472 * - STARTTLS (TLS encryption)
2473 */
2474static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
2475                                              const char *mode)
2476{
2477    util_ldap_state_t *st =
2478    (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2479                                              &ldap_module);
2480
2481    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01307)
2482                      "LDAP: SSL trusted mode - %s",
2483                       mode);
2484
2485    if (0 == strcasecmp("NONE", mode)) {
2486        st->secure = APR_LDAP_NONE;
2487    }
2488    else if (0 == strcasecmp("SSL", mode)) {
2489        st->secure = APR_LDAP_SSL;
2490    }
2491    else if (   (0 == strcasecmp("TLS", mode))
2492             || (0 == strcasecmp("STARTTLS", mode))) {
2493        st->secure = APR_LDAP_STARTTLS;
2494    }
2495    else {
2496        return "Invalid LDAPTrustedMode setting: must be one of NONE, "
2497               "SSL, or TLS/STARTTLS";
2498    }
2499
2500    st->secure_set = 1;
2501    return(NULL);
2502}
2503
2504static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
2505                                                 void *dummy,
2506                                                 int mode)
2507{
2508    util_ldap_state_t *st =
2509    (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2510                                              &ldap_module);
2511    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2512
2513    if (err != NULL) {
2514        return err;
2515    }
2516
2517    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01308)
2518                      "LDAP: SSL verify server certificate - %s",
2519                      mode?"TRUE":"FALSE");
2520
2521    st->verify_svr_cert = mode;
2522
2523    return(NULL);
2524}
2525
2526
2527static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
2528                                                    void *dummy,
2529                                                    const char *ttl)
2530{
2531#ifdef LDAP_OPT_NETWORK_TIMEOUT
2532    util_ldap_state_t *st =
2533        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2534                                                  &ldap_module);
2535#endif
2536    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2537
2538    if (err != NULL) {
2539        return err;
2540    }
2541
2542#ifdef LDAP_OPT_NETWORK_TIMEOUT
2543    st->connectionTimeout = atol(ttl);
2544
2545    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01309)
2546                 "ldap connection: Setting connection timeout to %ld seconds.",
2547                 st->connectionTimeout);
2548#else
2549    ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server, APLOGNO(01310)
2550                 "LDAP: Connection timeout option not supported by the "
2551                 "LDAP SDK in use." );
2552#endif
2553
2554    return NULL;
2555}
2556
2557
2558static const char *util_ldap_set_chase_referrals(cmd_parms *cmd,
2559                                                 void *config,
2560                                                 const char *arg)
2561{
2562    util_ldap_config_t *dc =  config;
2563
2564    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01311)
2565                      "LDAP: Setting referral chasing %s", arg);
2566
2567    if (0 == strcasecmp(arg, "on")) {
2568        dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_ON;
2569    }
2570    else if (0 == strcasecmp(arg, "off")) {
2571        dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_OFF;
2572    }
2573    else if (0 == strcasecmp(arg, "default")) {
2574        dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_SDKDEFAULT;
2575    }
2576    else {
2577        return "LDAPReferrals must be 'on', 'off', or 'default'";
2578    }
2579
2580    return(NULL);
2581}
2582
2583static const char *util_ldap_set_debug_level(cmd_parms *cmd,
2584                                             void *config,
2585                                             const char *arg) {
2586#ifdef AP_LDAP_OPT_DEBUG
2587    util_ldap_state_t *st =
2588        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2589                                                  &ldap_module);
2590#endif
2591
2592    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2593    if (err != NULL) {
2594        return err;
2595    }
2596
2597#ifndef AP_LDAP_OPT_DEBUG
2598    return "This directive is not supported with the currently linked LDAP library";
2599#else
2600    st->debug_level = atoi(arg);
2601    return NULL;
2602#endif
2603}
2604
2605static const char *util_ldap_set_referral_hop_limit(cmd_parms *cmd,
2606                                                    void *config,
2607                                                    const char *hop_limit)
2608{
2609    util_ldap_config_t *dc =  config;
2610
2611    dc->ReferralHopLimit = atol(hop_limit);
2612
2613    if (dc->ReferralHopLimit <= 0) {
2614        return "LDAPReferralHopLimit must be greater than zero (Use 'LDAPReferrals Off' to disable referral chasing)";
2615    }
2616
2617    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01312)
2618                 "LDAP: Limit chased referrals to maximum of %d hops.",
2619                 dc->ReferralHopLimit);
2620
2621    return NULL;
2622}
2623
2624static void *util_ldap_create_dir_config(apr_pool_t *p, char *d) {
2625   util_ldap_config_t *dc =
2626       (util_ldap_config_t *) apr_pcalloc(p,sizeof(util_ldap_config_t));
2627
2628   /* defaults are AP_LDAP_CHASEREFERRALS_ON and AP_LDAP_DEFAULT_HOPLIMIT */
2629   dc->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2630   dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_ON;
2631   dc->ReferralHopLimit = AP_LDAP_HOPLIMIT_UNSET;
2632
2633   return dc;
2634}
2635
2636static const char *util_ldap_set_op_timeout(cmd_parms *cmd,
2637                                            void *dummy,
2638                                            const char *val)
2639{
2640    long timeout;
2641    char *endptr;
2642    util_ldap_state_t *st =
2643        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2644                                                  &ldap_module);
2645    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2646
2647    if (err != NULL) {
2648        return err;
2649    }
2650
2651    timeout = strtol(val, &endptr, 10);
2652    if ((val == endptr) || (*endptr != '\0')) {
2653        return "Timeout not numerical";
2654    }
2655    if (timeout < 0) {
2656        return "Timeout must be non-negative";
2657    }
2658
2659    if (timeout) {
2660        if (!st->opTimeout) {
2661            st->opTimeout = apr_pcalloc(cmd->pool, sizeof(struct timeval));
2662        }
2663        st->opTimeout->tv_sec = timeout;
2664    }
2665    else {
2666        st->opTimeout = NULL;
2667    }
2668
2669    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01313)
2670                 "ldap connection: Setting op timeout to %ld seconds.",
2671                 timeout);
2672
2673#ifndef LDAP_OPT_TIMEOUT
2674
2675    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01314)
2676                 "LDAP: LDAP_OPT_TIMEOUT option not supported by the "
2677                 "LDAP library in use. Using LDAPTimeout value as search "
2678                 "timeout only." );
2679#endif
2680
2681    return NULL;
2682}
2683
2684static const char *util_ldap_set_conn_ttl(cmd_parms *cmd,
2685                                            void *dummy,
2686                                            const char *val)
2687{
2688    apr_interval_time_t timeout;
2689    util_ldap_state_t *st =
2690        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2691                                                  &ldap_module);
2692
2693    if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS) {
2694        return "LDAPConnPoolTTL has wrong format";
2695    }
2696
2697    if (timeout < 0) {
2698        /* reserve -1 for default value */
2699        timeout =  AP_LDAP_CONNPOOL_INFINITE;
2700    }
2701    st->connection_pool_ttl = timeout;
2702    return NULL;
2703}
2704static const char *util_ldap_set_retry_delay(cmd_parms *cmd,
2705                                            void *dummy,
2706                                            const char *val)
2707{
2708    apr_interval_time_t timeout;
2709    util_ldap_state_t *st =
2710        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2711                                                  &ldap_module);
2712    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2713
2714    if (err != NULL) {
2715        return err;
2716    }
2717
2718    if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS) {
2719        return "LDAPRetryDelay has wrong format";
2720    }
2721
2722    if (timeout < 0) {
2723        return "LDAPRetryDelay must be >= 0";
2724    }
2725
2726    st->retry_delay = timeout;
2727    return NULL;
2728}
2729
2730static const char *util_ldap_set_retries(cmd_parms *cmd,
2731                                            void *dummy,
2732                                            const char *val)
2733{
2734    util_ldap_state_t *st =
2735        (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2736                                                  &ldap_module);
2737    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2738
2739    if (err != NULL) {
2740        return err;
2741    }
2742
2743    st->retries = atoi(val);
2744    if (st->retries < 0) {
2745        return  "LDAPRetries must be >= 0";
2746    }
2747
2748    return NULL;
2749}
2750
2751static void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
2752{
2753    util_ldap_state_t *st =
2754        (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
2755
2756    /* Create a per vhost pool for mod_ldap to use, serialized with
2757     * st->mutex (also one per vhost).  both are replicated by fork(),
2758     * no shared memory managed by either.
2759     */
2760    apr_pool_create(&st->pool, p);
2761#if APR_HAS_THREADS
2762    apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
2763#endif
2764
2765    st->cache_bytes = 500000;
2766    st->search_cache_ttl = 600000000;
2767    st->search_cache_size = 1024;
2768    st->compare_cache_ttl = 600000000;
2769    st->compare_cache_size = 1024;
2770    st->connections = NULL;
2771    st->ssl_supported = 0;
2772    st->global_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2773    st->secure = APR_LDAP_NONE;
2774    st->secure_set = 0;
2775    st->connectionTimeout = 10;
2776    st->opTimeout = apr_pcalloc(p, sizeof(struct timeval));
2777    st->opTimeout->tv_sec = 60;
2778    st->verify_svr_cert = 1;
2779    st->connection_pool_ttl = AP_LDAP_CONNPOOL_DEFAULT; /* no limit */
2780    st->retries = 3;
2781    st->retry_delay = 0; /* no delay */
2782
2783    return st;
2784}
2785
2786/* cache-related settings are not merged here, but in the post_config hook,
2787 * since the cache has not yet sprung to life
2788 */
2789static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
2790                                    void *overridesv)
2791{
2792    util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
2793    util_ldap_state_t *base = (util_ldap_state_t *) basev;
2794    util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
2795
2796    st->pool = overrides->pool;
2797#if APR_HAS_THREADS
2798    st->mutex = overrides->mutex;
2799#endif
2800
2801    /* The cache settings can not be modified in a
2802        virtual host since all server use the same
2803        shared memory cache. */
2804    st->cache_bytes = base->cache_bytes;
2805    st->search_cache_ttl = base->search_cache_ttl;
2806    st->search_cache_size = base->search_cache_size;
2807    st->compare_cache_ttl = base->compare_cache_ttl;
2808    st->compare_cache_size = base->compare_cache_size;
2809    st->util_ldap_cache_lock = base->util_ldap_cache_lock;
2810
2811    st->connections = NULL;
2812    st->ssl_supported = 0; /* not known until post-config and re-merged */
2813    st->global_certs = apr_array_append(p, base->global_certs,
2814                                           overrides->global_certs);
2815    st->secure = (overrides->secure_set == 0) ? base->secure
2816                                              : overrides->secure;
2817
2818    /* These LDAP connection settings can not be overwritten in
2819        a virtual host. Once set in the base server, they must
2820        remain the same. None of the LDAP SDKs seem to be able
2821        to handle setting the verify_svr_cert flag on a
2822        per-connection basis.  The OpenLDAP client appears to be
2823        able to handle the connection timeout per-connection
2824        but the Novell SDK cannot.  Allowing the timeout to
2825        be set by each vhost is of little value so rather than
2826        trying to make special expections for one LDAP SDK, GLOBAL_ONLY
2827        is being enforced on this setting as well. */
2828    st->connectionTimeout = base->connectionTimeout;
2829    st->opTimeout = base->opTimeout;
2830    st->verify_svr_cert = base->verify_svr_cert;
2831    st->debug_level = base->debug_level;
2832
2833    st->connection_pool_ttl = (overrides->connection_pool_ttl == AP_LDAP_CONNPOOL_DEFAULT) ?
2834                                base->connection_pool_ttl : overrides->connection_pool_ttl;
2835
2836    st->retries = base->retries;
2837    st->retry_delay = base->retry_delay;
2838
2839    return st;
2840}
2841
2842static apr_status_t util_ldap_cleanup_module(void *data)
2843{
2844
2845    server_rec *s = data;
2846    util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
2847        s->module_config, &ldap_module);
2848
2849    if (st->ssl_supported) {
2850        apr_ldap_ssl_deinit();
2851    }
2852
2853    return APR_SUCCESS;
2854
2855}
2856
2857static int util_ldap_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
2858                                apr_pool_t *ptemp)
2859{
2860    apr_status_t result;
2861
2862    result = ap_mutex_register(pconf, ldap_cache_mutex_type, NULL,
2863                               APR_LOCK_DEFAULT, 0);
2864    if (result != APR_SUCCESS) {
2865        return result;
2866    }
2867
2868    return OK;
2869}
2870
2871static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog,
2872                                 apr_pool_t *ptemp, server_rec *s)
2873{
2874    apr_status_t result;
2875    server_rec *s_vhost;
2876    util_ldap_state_t *st_vhost;
2877
2878    util_ldap_state_t *st = (util_ldap_state_t *)
2879                            ap_get_module_config(s->module_config,
2880                                                 &ldap_module);
2881
2882    apr_ldap_err_t *result_err = NULL;
2883    int rc;
2884
2885    /* util_ldap_post_config() will be called twice. Don't bother
2886     * going through all of the initialization on the first call
2887     * because it will just be thrown away.*/
2888    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
2889
2890#if APR_HAS_SHARED_MEMORY
2891        /*
2892         * If we are using shared memory caching and the cache file already
2893         * exists then delete it.  Otherwise we are going to run into problems
2894         * creating the shared memory.
2895         */
2896        if (st->cache_file && st->cache_bytes > 0) {
2897            char *lck_file = apr_pstrcat(ptemp, st->cache_file, ".lck",
2898                                         NULL);
2899            apr_file_remove(lck_file, ptemp);
2900        }
2901#endif
2902        return OK;
2903    }
2904
2905#if APR_HAS_SHARED_MEMORY
2906    /*
2907     * initializing cache if we don't already have a shm address
2908     */
2909    if (!st->cache_shm) {
2910#endif
2911        result = util_ldap_cache_init(p, st);
2912        if (result != APR_SUCCESS) {
2913            ap_log_error(APLOG_MARK, APLOG_ERR, result, s, APLOGNO(01315)
2914                         "LDAP cache: could not create shared memory segment");
2915            return DONE;
2916        }
2917
2918        result = ap_global_mutex_create(&st->util_ldap_cache_lock, NULL,
2919                                        ldap_cache_mutex_type, NULL, s, p, 0);
2920        if (result != APR_SUCCESS) {
2921            return result;
2922        }
2923
2924        /* merge config in all vhost */
2925        s_vhost = s->next;
2926        while (s_vhost) {
2927            st_vhost = (util_ldap_state_t *)
2928                       ap_get_module_config(s_vhost->module_config,
2929                                            &ldap_module);
2930
2931#if APR_HAS_SHARED_MEMORY
2932            st_vhost->cache_shm = st->cache_shm;
2933            st_vhost->cache_rmm = st->cache_rmm;
2934            st_vhost->cache_file = st->cache_file;
2935            st_vhost->util_ldap_cache = st->util_ldap_cache;
2936            ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s, APLOGNO(01316)
2937                         "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
2938                         "for VHOST: %s", st->cache_shm, st->cache_rmm,
2939                         s_vhost->server_hostname);
2940#endif
2941            s_vhost = s_vhost->next;
2942        }
2943#if APR_HAS_SHARED_MEMORY
2944    }
2945    else {
2946        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01317)
2947                     "LDAP cache: LDAPSharedCacheSize is zero, disabling "
2948                     "shared memory cache");
2949    }
2950#endif
2951
2952    /* log the LDAP SDK used
2953     */
2954    {
2955        apr_ldap_err_t *result = NULL;
2956        apr_ldap_info(p, &(result));
2957        if (result != NULL) {
2958            ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01318) "%s", result->reason);
2959        }
2960    }
2961
2962    apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
2963                              util_ldap_cleanup_module);
2964
2965    /*
2966     * Initialize SSL support, and log the result for the benefit of the admin.
2967     *
2968     * If SSL is not supported it is not necessarily an error, as the
2969     * application may not want to use it.
2970     */
2971    rc = apr_ldap_ssl_init(p,
2972                      NULL,
2973                      0,
2974                      &(result_err));
2975    if (APR_SUCCESS == rc) {
2976        rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
2977                                 (void *)st->global_certs, &(result_err));
2978    }
2979
2980    if (APR_SUCCESS == rc) {
2981        st->ssl_supported = 1;
2982        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01319)
2983                     "LDAP: SSL support available" );
2984    }
2985    else {
2986        st->ssl_supported = 0;
2987        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01320)
2988                     "LDAP: SSL support unavailable%s%s",
2989                     result_err ? ": " : "",
2990                     result_err ? result_err->reason : "");
2991    }
2992
2993    /* ssl_supported is really a global setting */
2994    s_vhost = s->next;
2995    while (s_vhost) {
2996        st_vhost = (util_ldap_state_t *)
2997                   ap_get_module_config(s_vhost->module_config,
2998                                        &ldap_module);
2999
3000        st_vhost->ssl_supported = st->ssl_supported;
3001        s_vhost = s_vhost->next;
3002    }
3003
3004    /* Initialize the rebind callback's cross reference list. */
3005    apr_ldap_rebind_init (p);
3006
3007#ifdef AP_LDAP_OPT_DEBUG
3008    if (st->debug_level > 0) {
3009        result = ldap_set_option(NULL, AP_LDAP_OPT_DEBUG, &st->debug_level);
3010        if (result != LDAP_SUCCESS) {
3011            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01321)
3012                    "LDAP: Could not set the LDAP library debug level to %d:(%d) %s",
3013                    st->debug_level, result, ldap_err2string(result));
3014        }
3015    }
3016#endif
3017
3018    return(OK);
3019}
3020
3021static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
3022{
3023    apr_status_t sts;
3024    util_ldap_state_t *st = ap_get_module_config(s->module_config,
3025                                                 &ldap_module);
3026
3027    if (!st->util_ldap_cache_lock) return;
3028
3029    sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
3030              apr_global_mutex_lockfile(st->util_ldap_cache_lock), p);
3031    if (sts != APR_SUCCESS) {
3032        ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s, APLOGNO(01322)
3033                     "Failed to initialise global mutex %s in child process",
3034                     ldap_cache_mutex_type);
3035    }
3036}
3037
3038static const command_rec util_ldap_cmds[] = {
3039    AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes,
3040                  NULL, RSRC_CONF,
3041                  "Set the size of the shared memory cache (in bytes). Use "
3042                  "0 to disable the shared memory cache. (default: 500000)"),
3043
3044    AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file,
3045                  NULL, RSRC_CONF,
3046                  "Set the file name for the shared memory cache."),
3047
3048    AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries,
3049                  NULL, RSRC_CONF,
3050                  "Set the maximum number of entries that are possible in the "
3051                  "LDAP search cache. Use 0 or -1 to disable the search cache "
3052                  "(default: 1024)"),
3053
3054    AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl,
3055                  NULL, RSRC_CONF,
3056                  "Set the maximum time (in seconds) that an item can be "
3057                  "cached in the LDAP search cache. Use 0 for no limit. "
3058                  "(default 600)"),
3059
3060    AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries,
3061                  NULL, RSRC_CONF,
3062                  "Set the maximum number of entries that are possible "
3063                  "in the LDAP compare cache. Use 0 or -1 to disable the compare cache "
3064                  "(default: 1024)"),
3065
3066    AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl,
3067                  NULL, RSRC_CONF,
3068                  "Set the maximum time (in seconds) that an item is cached "
3069                  "in the LDAP operation cache. Use 0 for no limit. "
3070                  "(default: 600)"),
3071
3072    AP_INIT_TAKE23("LDAPTrustedGlobalCert", util_ldap_set_trusted_global_cert,
3073                   NULL, RSRC_CONF,
3074                   "Takes three arguments; the first argument is the cert "
3075                   "type of the second argument, one of CA_DER, CA_BASE64, "
3076                   "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
3077                   "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
3078                   "specifes the file and/or directory containing the trusted CA "
3079                   "certificates (and global client certs for Netware) used to "
3080                   "validate the LDAP server. The third argument is an optional "
3081                   "passphrase if applicable."),
3082
3083    AP_INIT_TAKE23("LDAPTrustedClientCert", util_ldap_set_trusted_client_cert,
3084                   NULL, OR_AUTHCFG,
3085                   "Takes three arguments: the first argument is the certificate "
3086                   "type of the second argument, one of CA_DER, CA_BASE64, "
3087                   "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
3088                   "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
3089                   "specifies the file and/or directory containing the client "
3090                   "certificate, or certificate ID used to validate this LDAP "
3091                   "client.  The third argument is an optional passphrase if "
3092                   "applicable."),
3093
3094    AP_INIT_TAKE1("LDAPTrustedMode", util_ldap_set_trusted_mode,
3095                  NULL, RSRC_CONF,
3096                  "Specify the type of security that should be applied to "
3097                  "an LDAP connection. One of; NONE, SSL or STARTTLS."),
3098
3099    AP_INIT_FLAG("LDAPVerifyServerCert", util_ldap_set_verify_srv_cert,
3100                  NULL, RSRC_CONF,
3101                  "Set to 'ON' requires that the server certificate be verified"
3102                  " before a secure LDAP connection can be establish.  Default"
3103                  " 'ON'"),
3104
3105    AP_INIT_TAKE1("LDAPConnectionTimeout", util_ldap_set_connection_timeout,
3106                  NULL, RSRC_CONF,
3107                  "Specify the LDAP socket connection timeout in seconds "
3108                  "(default: 10)"),
3109
3110    AP_INIT_TAKE1("LDAPReferrals", util_ldap_set_chase_referrals,
3111                  NULL, OR_AUTHCFG,
3112                  "Choose whether referrals are chased ['ON'|'OFF'|'DEFAULT'].  Default 'ON'"),
3113
3114    AP_INIT_TAKE1("LDAPReferralHopLimit", util_ldap_set_referral_hop_limit,
3115                  NULL, OR_AUTHCFG,
3116                  "Limit the number of referral hops that LDAP can follow. "
3117                  "(Integer value, Consult LDAP SDK documentation for applicability and defaults"),
3118
3119    AP_INIT_TAKE1("LDAPLibraryDebug", util_ldap_set_debug_level,
3120                  NULL, RSRC_CONF,
3121                  "Enable debugging in LDAP SDK (Default: off, values: SDK specific"),
3122
3123    AP_INIT_TAKE1("LDAPTimeout", util_ldap_set_op_timeout,
3124                  NULL, RSRC_CONF,
3125                  "Specify the LDAP bind/search timeout in seconds "
3126                  "(0 = no limit). Default: 60"),
3127    AP_INIT_TAKE1("LDAPConnectionPoolTTL", util_ldap_set_conn_ttl,
3128                  NULL, RSRC_CONF,
3129                  "Specify the maximum amount of time a bound connection can sit "
3130                  "idle and still be considered valid for reuse"
3131                  "(0 = no pool, -1 = no limit, n = time in seconds). Default: -1"),
3132    AP_INIT_TAKE1("LDAPRetries", util_ldap_set_retries,
3133                  NULL, RSRC_CONF,
3134                  "Specify the number of times a failed LDAP operation should be retried "
3135                  "(0 = no retries). Default: 3"),
3136    AP_INIT_TAKE1("LDAPRetryDelay", util_ldap_set_retry_delay,
3137                  NULL, RSRC_CONF,
3138                  "Specify the delay between retries of a failed LDAP operation "
3139                  "(0 = no delay). Default: 0"),
3140
3141
3142    {NULL}
3143};
3144
3145static void util_ldap_register_hooks(apr_pool_t *p)
3146{
3147    APR_REGISTER_OPTIONAL_FN(uldap_connection_open);
3148    APR_REGISTER_OPTIONAL_FN(uldap_connection_close);
3149    APR_REGISTER_OPTIONAL_FN(uldap_connection_unbind);
3150    APR_REGISTER_OPTIONAL_FN(uldap_connection_find);
3151    APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn);
3152    APR_REGISTER_OPTIONAL_FN(uldap_cache_compare);
3153    APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid);
3154    APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn);
3155    APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported);
3156    APR_REGISTER_OPTIONAL_FN(uldap_cache_check_subgroups);
3157
3158    ap_hook_pre_config(util_ldap_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
3159    ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
3160    ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
3161    ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
3162}
3163
3164AP_DECLARE_MODULE(ldap) = {
3165   STANDARD20_MODULE_STUFF,
3166   util_ldap_create_dir_config, /* create dir config */
3167   NULL,                        /* merge dir config */
3168   util_ldap_create_config,     /* create server config */
3169   util_ldap_merge_config,      /* merge server config */
3170   util_ldap_cmds,              /* command table */
3171   util_ldap_register_hooks,    /* set up request processing hooks */
3172};
3173