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