]> granicus.if.org Git - apache/blob - modules/ldap/util_ldap.c
mod_ldap: Eliminate a potential crash with multiple LDAPTrustedClientCert when
[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 module AP_MODULE_DECLARE_DATA ldap_module;
67 static const char *ldap_cache_mutex_type = "ldap-cache";
68
69 #define LDAP_CACHE_LOCK() do {                                  \
70     if (st->util_ldap_cache_lock)                               \
71         apr_global_mutex_lock(st->util_ldap_cache_lock);        \
72 } while (0)
73
74 #define LDAP_CACHE_UNLOCK() do {                                \
75     if (st->util_ldap_cache_lock)                               \
76         apr_global_mutex_unlock(st->util_ldap_cache_lock);      \
77 } while (0)
78
79 static apr_status_t util_ldap_connection_remove (void *param);
80
81 static void util_ldap_strdup (char **str, const char *newstr)
82 {
83     if (*str) {
84         free(*str);
85         *str = NULL;
86     }
87
88     if (newstr) {
89         *str = strdup(newstr);
90     }
91 }
92
93 /*
94  * Status Handler
95  * --------------
96  *
97  * This handler generates a status page about the current performance of
98  * the LDAP cache. It is enabled as follows:
99  *
100  * <Location /ldap-status>
101  *   SetHandler ldap-status
102  * </Location>
103  *
104  */
105 static int util_ldap_handler(request_rec *r)
106 {
107     util_ldap_state_t *st = (util_ldap_state_t *)
108                             ap_get_module_config(r->server->module_config,
109                                                  &ldap_module);
110
111     r->allowed |= (1 << M_GET);
112     if (r->method_number != M_GET)
113         return DECLINED;
114
115     if (strcmp(r->handler, "ldap-status")) {
116         return DECLINED;
117     }
118
119     ap_set_content_type(r, "text/html; charset=ISO-8859-1");
120
121     if (r->header_only)
122         return OK;
123
124     ap_rputs(DOCTYPE_HTML_3_2
125              "<html><head><title>LDAP Cache Information</title></head>\n", r);
126     ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information"
127              "</h1>\n", r);
128
129     util_ald_cache_display(r, st);
130
131     return OK;
132 }
133
134
135
136 /* ------------------------------------------------------------------ */
137 /*
138  * Closes an LDAP connection by unlocking it. The next time
139  * uldap_connection_find() is called this connection will be
140  * available for reuse.
141  */
142 static void uldap_connection_close(util_ldap_connection_t *ldc)
143 {
144
145     /*
146      * QUESTION:
147      *
148      * Is it safe leaving bound connections floating around between the
149      * different modules? Keeping the user bound is a performance boost,
150      * but it is also a potential security problem - maybe.
151      *
152      * For now we unbind the user when we finish with a connection, but
153      * we don't have to...
154      */
155
156      if (!ldc->keep) { 
157          util_ldap_connection_remove(ldc);
158      }
159      else { 
160          /* mark our connection as available for reuse */
161 #if APR_HAS_THREADS
162          apr_thread_mutex_unlock(ldc->lock);
163 #endif
164      }
165 }
166
167
168 /*
169  * Destroys an LDAP connection by unbinding and closing the connection to
170  * the LDAP server. It is used to bring the connection back to a known
171  * state after an error.
172  */
173 static apr_status_t uldap_connection_unbind(void *param)
174 {
175     util_ldap_connection_t *ldc = param;
176
177     if (ldc) {
178         if (ldc->ldap) {
179             ldap_unbind_s(ldc->ldap);
180             ldc->ldap = NULL;
181         }
182         ldc->bound = 0;
183     }
184
185     return APR_SUCCESS;
186 }
187
188
189 /*
190  * Clean up an LDAP connection by unbinding and unlocking the connection.
191  * This cleanup does not remove the util_ldap_connection_t from the 
192  * per-virtualhost list of connections, does not remove the storage
193  * for the util_ldap_connection_t or it's data, and is NOT run automatically.
194  */
195 static apr_status_t uldap_connection_cleanup(void *param)
196 {
197     util_ldap_connection_t *ldc = param;
198
199     if (ldc) {
200         /* Release the rebind info for this connection. No more referral rebinds required. */
201         apr_ldap_rebind_remove(ldc->ldap);
202
203         /* unbind and disconnect from the LDAP server */
204         uldap_connection_unbind(ldc);
205
206         /* free the username and password */
207         if (ldc->bindpw) {
208             free((void*)ldc->bindpw);
209         }
210         if (ldc->binddn) {
211             free((void*)ldc->binddn);
212         }
213         /* ldc->reason is allocated from r->pool */
214         if (ldc->reason) {
215             ldc->reason = NULL;
216         }
217         /* unlock this entry */
218         uldap_connection_close(ldc);
219
220      }
221
222     return APR_SUCCESS;
223 }
224
225 /*
226  * util_ldap_connection_remove frees all storage associated with the LDAP
227  * connection and removes it completely from the per-virtualhost list of
228  * connections
229  *
230  * The caller should hold the lock for this connection
231  */
232 static apr_status_t util_ldap_connection_remove (void *param) { 
233     util_ldap_connection_t *ldc = param, *l  = NULL, *prev = NULL;
234     util_ldap_state_t *st = ldc->st;
235
236     if (!ldc) return APR_SUCCESS;
237
238     uldap_connection_unbind(ldc);
239
240 #if APR_HAS_THREADS
241     apr_thread_mutex_lock(st->mutex);
242 #endif
243
244     /* Remove ldc from the list */
245     for (l=st->connections; l; l=l->next) {
246         if (l == ldc) {
247             if (prev) {
248                 prev->next = l->next; 
249             }
250             else { 
251                 st->connections = l->next;
252             }
253             break;
254         }
255         prev = l;
256     }
257
258     /* Some unfortunate duplication between this method
259      * and uldap_connection_cleanup()
260     */
261     if (ldc->bindpw) {
262         free((void*)ldc->bindpw);
263     }
264     if (ldc->binddn) {
265         free((void*)ldc->binddn);
266     }
267
268 #if APR_HAS_THREADS
269     apr_thread_mutex_unlock(ldc->lock);
270     apr_thread_mutex_unlock(st->mutex);
271 #endif
272
273     /* Destory the pool associated with this connection */
274
275     apr_pool_destroy(ldc->pool);   
276    
277     return APR_SUCCESS;
278 }
279
280 static int uldap_connection_init(request_rec *r,
281                                  util_ldap_connection_t *ldc)
282 {
283     int rc = 0, ldap_option = 0;
284     int version  = LDAP_VERSION3;
285     apr_ldap_err_t *result = NULL;
286 #ifdef LDAP_OPT_NETWORK_TIMEOUT
287     struct timeval connectionTimeout = {10,0};    /* 10 second connection timeout */
288 #endif
289     util_ldap_state_t *st =
290         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
291         &ldap_module);
292
293     /* Since the host will include a port if the default port is not used,
294      * always specify the default ports for the port parameter.  This will
295      * allow a host string that contains multiple hosts the ability to mix
296      * some hosts with ports and some without. All hosts which do not
297      * specify a port will use the default port.
298      */
299     apr_ldap_init(r->pool, &(ldc->ldap),
300                   ldc->host,
301                   APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
302                   APR_LDAP_NONE,
303                   &(result));
304
305     if (NULL == result) {
306         /* something really bad happened */
307         ldc->bound = 0;
308         if (NULL == ldc->reason) {
309             ldc->reason = "LDAP: ldap initialization failed";
310         }
311         return(APR_EGENERAL);
312     }
313
314     if (result->rc) {
315         ldc->reason = result->reason;
316     }
317
318     if (NULL == ldc->ldap)
319     {
320         ldc->bound = 0;
321         if (NULL == ldc->reason) {
322             ldc->reason = "LDAP: ldap initialization failed";
323         }
324         else {
325             ldc->reason = result->reason;
326         }
327         return(result->rc);
328     }
329
330     /* Now that we have an ldap struct, add it to the referral list for rebinds. */
331     rc = apr_ldap_rebind_add(ldc->pool, ldc->ldap, ldc->binddn, ldc->bindpw);
332     if (rc != APR_SUCCESS) {
333         ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
334                      "LDAP: Unable to add rebind cross reference entry. Out of memory?");
335         uldap_connection_unbind(ldc);
336         ldc->reason = "LDAP: Unable to add rebind cross reference entry.";
337         return(rc);
338     }
339
340     /* always default to LDAP V3 */
341     ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
342
343     /* set client certificates */
344     if (!apr_is_empty_array(ldc->client_certs)) {
345         apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
346                             ldc->client_certs, &(result));
347         if (LDAP_SUCCESS != result->rc) {
348             uldap_connection_unbind( ldc );
349             ldc->reason = result->reason;
350             return(result->rc);
351         }
352     }
353
354     /* switch on SSL/TLS */
355     if (APR_LDAP_NONE != ldc->secure) {
356         apr_ldap_set_option(r->pool, ldc->ldap,
357                             APR_LDAP_OPT_TLS, &ldc->secure, &(result));
358         if (LDAP_SUCCESS != result->rc) {
359             uldap_connection_unbind( ldc );
360             ldc->reason = result->reason;
361             return(result->rc);
362         }
363     }
364
365     /* Set the alias dereferencing option */
366     ldap_option = ldc->deref;
367     ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);
368
369     /* Set options for rebind and referrals. */
370     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
371                  "LDAP: Setting referrals to %s.",
372                  ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"));
373     apr_ldap_set_option(r->pool, ldc->ldap,
374                         APR_LDAP_OPT_REFERRALS,
375                         (void *)((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ?
376                                  LDAP_OPT_ON : LDAP_OPT_OFF),
377                         &(result));
378     if (result->rc != LDAP_SUCCESS) {
379         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
380                      "Unable to set LDAP_OPT_REFERRALS option to %s: %d.",
381                      ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"),
382                      result->rc);
383         result->reason = "Unable to set LDAP_OPT_REFERRALS.";
384         ldc->reason = result->reason;
385         uldap_connection_unbind(ldc);
386         return(result->rc);
387     }
388
389     if ((ldc->ReferralHopLimit != AP_LDAP_HOPLIMIT_UNSET) && ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
390         /* Referral hop limit - only if referrals are enabled and a hop limit is explicitly requested */
391         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
392                      "Setting referral hop limit to %d.",
393                      ldc->ReferralHopLimit);
394         apr_ldap_set_option(r->pool, ldc->ldap,
395                             APR_LDAP_OPT_REFHOPLIMIT,
396                             (void *)&ldc->ReferralHopLimit,
397                             &(result));
398         if (result->rc != LDAP_SUCCESS) {
399           ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
400                        "Unable to set LDAP_OPT_REFHOPLIMIT option to %d: %d.",
401                        ldc->ReferralHopLimit,
402                        result->rc);
403           result->reason = "Unable to set LDAP_OPT_REFHOPLIMIT.";
404           ldc->reason = result->reason;
405           uldap_connection_unbind(ldc);
406           return(result->rc);
407         }
408     }
409
410 /*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
411 #ifdef APR_LDAP_OPT_VERIFY_CERT
412     apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_VERIFY_CERT,
413                         &(st->verify_svr_cert), &(result));
414 #else
415 #if defined(LDAPSSL_VERIFY_SERVER)
416     if (st->verify_svr_cert) {
417         result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
418     }
419     else {
420         result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
421     }
422 #elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
423     /* This is not a per-connection setting so just pass NULL for the
424        Ldap connection handle */
425     if (st->verify_svr_cert) {
426         int i = LDAP_OPT_X_TLS_DEMAND;
427         result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
428     }
429     else {
430         int i = LDAP_OPT_X_TLS_NEVER;
431         result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
432     }
433 #endif
434 #endif
435
436 #ifdef LDAP_OPT_NETWORK_TIMEOUT
437     if (st->connectionTimeout > 0) {
438         connectionTimeout.tv_sec = st->connectionTimeout;
439     }
440
441     if (st->connectionTimeout >= 0) {
442         rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
443                                  (void *)&connectionTimeout, &(result));
444         if (APR_SUCCESS != rc) {
445             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
446                              "LDAP: Could not set the connection timeout");
447         }
448     }
449 #endif
450
451 #ifdef LDAP_OPT_TIMEOUT
452     /*
453      * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap
454      * function calls and not just ldap_search_ext_s(), which accepts a timeout
455      * parameter.
456      * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all
457      * XXX: synchronous ldap function calls with asynchronous calls and using
458      * XXX: ldap_result() with a timeout.
459      */
460     if (st->opTimeout) {
461         rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_TIMEOUT,
462                                  st->opTimeout, &(result));
463         if (APR_SUCCESS != rc) {
464             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
465                              "LDAP: Could not set LDAP_OPT_TIMEOUT");
466         }
467     }
468 #endif
469
470     return(rc);
471 }
472
473 /*
474  * Replacement function for ldap_simple_bind_s() with a timeout.
475  * To do this in a portable way, we have to use ldap_simple_bind() and 
476  * ldap_result().
477  *
478  * Returns LDAP_SUCCESS on success; and an error code on failure
479  */
480 static int uldap_simple_bind(util_ldap_connection_t *ldc, char *binddn,
481                              char* bindpw, struct timeval *timeout)
482 {
483     LDAPMessage *result;
484     int rc;
485     int msgid = ldap_simple_bind(ldc->ldap, binddn, bindpw);
486     if (msgid == -1) {
487         ldc->reason = "LDAP: ldap_simple_bind() failed";
488         /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
489         return LDAP_OTHER;
490     }
491     rc = ldap_result(ldc->ldap, msgid, 0, timeout, &result);
492     if (rc == -1) {
493         ldc->reason = "LDAP: ldap_simple_bind() result retrieval failed";
494         /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
495         rc = LDAP_OTHER;
496     }
497     else if (rc == 0) {
498         ldc->reason = "LDAP: ldap_simple_bind() timed out";
499         rc = LDAP_TIMEOUT;
500     } else if (ldap_parse_result(ldc->ldap, result, &rc, NULL, NULL, NULL,
501                                  NULL, 1) == -1) {
502         ldc->reason = "LDAP: ldap_simple_bind() parse result failed";
503     }
504     return rc;
505 }
506
507 /*
508  * Connect to the LDAP server and binds. Does not connect if already
509  * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
510  *
511  * Returns LDAP_SUCCESS on success; and an error code on failure
512  */
513 static int uldap_connection_open(request_rec *r,
514                                  util_ldap_connection_t *ldc)
515 {
516     int rc = 0;
517     int failures = 0;
518     int new_connection = 0;
519     util_ldap_state_t *st;
520
521     /* sanity check for NULL */
522     if (!ldc) {
523         return -1;
524     }
525
526     /* If the connection is already bound, return
527     */
528     if (ldc->bound)
529     {
530         ldc->reason = "LDAP: connection open successful (already bound)";
531         return LDAP_SUCCESS;
532     }
533
534     /* create the ldap session handle
535     */
536     if (NULL == ldc->ldap)
537     {
538        new_connection = 1;
539        rc = uldap_connection_init( r, ldc );
540        if (LDAP_SUCCESS != rc)
541        {
542            return rc;
543        }
544     }
545
546
547     st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
548                                                    &ldap_module);
549
550     /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is
551      * returned. If LDAP_TIMEOUT is returned on the first try, maybe the
552      * connection was idle for a long time and has been dropped by a firewall.
553      * In this case close the connection immediately and try again.
554      *
555      * On Success or any other error, break out of the loop.
556      *
557      * NOTE: Looping is probably not a great idea. If the server isn't
558      * responding the chances it will respond after a few tries are poor.
559      * However, the original code looped and it only happens on
560      * the error condition.
561      */
562     for (failures=0; failures<10; failures++)
563     {
564         rc = uldap_simple_bind(ldc, (char *)ldc->binddn, (char *)ldc->bindpw,
565                                st->opTimeout);
566         if ((AP_LDAP_IS_SERVER_DOWN(rc) && failures == 5) ||
567             (rc == LDAP_TIMEOUT && failures == 0))
568         {
569            if (rc == LDAP_TIMEOUT && !new_connection) {
570                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
571                              "ldap_simple_bind() timed out on reused "
572                              "connection, dropped by firewall?");
573            }
574            /* attempt to init the connection once again */
575            uldap_connection_unbind( ldc );
576            rc = uldap_connection_init( r, ldc );
577            if (LDAP_SUCCESS != rc)
578            {
579                break;
580            }
581         }
582         else if (!AP_LDAP_IS_SERVER_DOWN(rc)) {
583             break;
584         }
585     }
586
587     /* free the handle if there was an error
588     */
589     if (LDAP_SUCCESS != rc)
590     {
591        uldap_connection_unbind(ldc);
592         ldc->reason = "LDAP: ldap_simple_bind() failed";
593     }
594     else {
595         ldc->bound = 1;
596         ldc->reason = "LDAP: connection open successful";
597     }
598
599     return(rc);
600 }
601
602
603 /*
604  * Compare client certificate arrays.
605  *
606  * Returns 1 on compare failure, 0 otherwise.
607  */
608 static int compare_client_certs(apr_array_header_t *srcs,
609                                 apr_array_header_t *dests)
610 {
611     int i = 0;
612     struct apr_ldap_opt_tls_cert_t *src, *dest;
613
614     /* arrays both NULL? if so, then equal */
615     if (srcs == NULL && dests == NULL) {
616         return 0;
617     }
618
619     /* arrays different length or either NULL? If so, then not equal */
620     if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
621         return 1;
622     }
623
624     /* run an actual comparison */
625     src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
626     dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
627     for (i = 0; i < srcs->nelts; i++) {
628         if ((strcmp(src[i].path, dest[i].path)) ||
629             (src[i].type != dest[i].type) ||
630             /* One is passwordless? If so, then not equal */
631             ((src[i].password == NULL) ^ (dest[i].password == NULL)) ||
632             (src[i].password != NULL && dest[i].password != NULL &&
633              strcmp(src[i].password, dest[i].password))) {
634             return 1;
635         }
636     }
637
638     /* if we got here, the cert arrays were identical */
639     return 0;
640
641 }
642
643
644 /*
645  * Find an existing ldap connection struct that matches the
646  * provided ldap connection parameters.
647  *
648  * If not found in the cache, a new ldc structure will be allocated
649  * from st->pool and returned to the caller.  If found in the cache,
650  * a pointer to the existing ldc structure will be returned.
651  */
652 static util_ldap_connection_t *
653             uldap_connection_find(request_rec *r,
654                                   const char *host, int port,
655                                   const char *binddn, const char *bindpw,
656                                   deref_options deref, int secure)
657 {
658     struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
659     int secureflag = secure;
660
661     util_ldap_state_t *st =
662         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
663         &ldap_module);
664     util_ldap_config_t *dc =
665         (util_ldap_config_t *) ap_get_module_config(r->per_dir_config, &ldap_module);
666
667 #if APR_HAS_THREADS
668     /* mutex lock this function */
669     apr_thread_mutex_lock(st->mutex);
670 #endif
671
672     if (secure < APR_LDAP_NONE) {
673         secureflag = st->secure;
674     }
675
676     /* Search for an exact connection match in the list that is not
677      * being used.
678      */
679     for (l=st->connections,p=NULL; l; l=l->next) {
680 #if APR_HAS_THREADS
681         if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
682 #endif
683         if (   (l->port == port) && (strcmp(l->host, host) == 0)
684             && ((!l->binddn && !binddn) || (l->binddn && binddn
685                                              && !strcmp(l->binddn, binddn)))
686             && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
687                                              && !strcmp(l->bindpw, bindpw)))
688             && (l->deref == deref) && (l->secure == secureflag)
689             && !compare_client_certs(st->client_certs, l->client_certs))
690         {
691             break;
692         }
693 #if APR_HAS_THREADS
694             /* If this connection didn't match the criteria, then we
695              * need to unlock the mutex so it is available to be reused.
696              */
697             apr_thread_mutex_unlock(l->lock);
698         }
699 #endif
700         p = l;
701     }
702
703     /* If nothing found, search again, but we don't care about the
704      * binddn and bindpw this time.
705      */
706     if (!l) {
707         for (l=st->connections,p=NULL; l; l=l->next) {
708 #if APR_HAS_THREADS
709             if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
710
711 #endif
712             if ((l->port == port) && (strcmp(l->host, host) == 0) &&
713                 (l->deref == deref) && (l->secure == secureflag) &&
714                 !compare_client_certs(st->client_certs, l->client_certs))
715             {
716                 /* the bind credentials have changed */
717                 l->bound = 0;
718                 util_ldap_strdup((char**)&(l->binddn), binddn);
719                 util_ldap_strdup((char**)&(l->bindpw), bindpw);
720                 break;
721             }
722 #if APR_HAS_THREADS
723                 /* If this connection didn't match the criteria, then we
724                  * need to unlock the mutex so it is available to be reused.
725                  */
726                 apr_thread_mutex_unlock(l->lock);
727             }
728 #endif
729             p = l;
730         }
731     }
732
733 /* artificially disable cache */
734 /* l = NULL; */
735
736     /* If no connection was found after the second search, we
737      * must create one.
738      */
739     if (!l) {
740         apr_pool_t *newpool;
741         if (apr_pool_create(&newpool, NULL) != APR_SUCCESS) {
742             ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
743                           "util_ldap: Failed to create memory pool");
744 #if APR_HAS_THREADS
745             apr_thread_mutex_unlock(st->mutex);
746 #endif
747             return NULL;
748         }
749  
750         /*
751          * Add the new connection entry to the linked list. Note that we
752          * don't actually establish an LDAP connection yet; that happens
753          * the first time authentication is requested.
754          */
755
756         /* create the details of this connection in the new pool */
757         l = apr_pcalloc(newpool, sizeof(util_ldap_connection_t));
758         l->pool = newpool;
759         l->st = st;
760
761 #if APR_HAS_THREADS
762         apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, l->pool);
763         apr_thread_mutex_lock(l->lock);
764 #endif
765         l->bound = 0;
766         l->host = apr_pstrdup(l->pool, host);
767         l->port = port;
768         l->deref = deref;
769         util_ldap_strdup((char**)&(l->binddn), binddn);
770         util_ldap_strdup((char**)&(l->bindpw), bindpw);
771         l->ChaseReferrals = dc->ChaseReferrals;
772         l->ReferralHopLimit = dc->ReferralHopLimit;
773
774         /* The security mode after parsing the URL will always be either
775          * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
776          * If the security setting is NONE, override it to the security
777          * setting optionally supplied by the admin using LDAPTrustedMode
778          */
779         l->secure = secureflag;
780
781         /* save away a copy of the client cert list that is presently valid */
782         l->client_certs = apr_array_copy_hdr(l->pool, st->client_certs);
783
784         l->keep = 1;
785
786         if (p) {
787             p->next = l;
788         }
789         else {
790             st->connections = l;
791         }
792     }
793
794 #if APR_HAS_THREADS
795     apr_thread_mutex_unlock(st->mutex);
796 #endif
797     return l;
798 }
799
800 /* ------------------------------------------------------------------ */
801
802 /*
803  * Compares two DNs to see if they're equal. The only way to do this correctly
804  * is to search for the dn and then do ldap_get_dn() on the result. This should
805  * match the initial dn, since it would have been also retrieved with
806  * ldap_get_dn(). This is expensive, so if the configuration value
807  * compare_dn_on_server is false, just does an ordinary strcmp.
808  *
809  * The lock for the ldap cache should already be acquired.
810  */
811 static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
812                                  const char *url, const char *dn,
813                                  const char *reqdn, int compare_dn_on_server)
814 {
815     int result = 0;
816     util_url_node_t *curl;
817     util_url_node_t curnode;
818     util_dn_compare_node_t *node;
819     util_dn_compare_node_t newnode;
820     int failures = 0;
821     LDAPMessage *res, *entry;
822     char *searchdn;
823
824     util_ldap_state_t *st = (util_ldap_state_t *)
825                             ap_get_module_config(r->server->module_config,
826                                                  &ldap_module);
827
828     /* get cache entry (or create one) */
829     LDAP_CACHE_LOCK();
830
831     curnode.url = url;
832     curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
833     if (curl == NULL) {
834         curl = util_ald_create_caches(st, url);
835     }
836     LDAP_CACHE_UNLOCK();
837
838     /* a simple compare? */
839     if (!compare_dn_on_server) {
840         /* unlock this read lock */
841         if (strcmp(dn, reqdn)) {
842             ldc->reason = "DN Comparison FALSE (direct strcmp())";
843             return LDAP_COMPARE_FALSE;
844         }
845         else {
846             ldc->reason = "DN Comparison TRUE (direct strcmp())";
847             return LDAP_COMPARE_TRUE;
848         }
849     }
850
851     if (curl) {
852         /* no - it's a server side compare */
853         LDAP_CACHE_LOCK();
854
855         /* is it in the compare cache? */
856         newnode.reqdn = (char *)reqdn;
857         node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
858         if (node != NULL) {
859             /* If it's in the cache, it's good */
860             /* unlock this read lock */
861             LDAP_CACHE_UNLOCK();
862             ldc->reason = "DN Comparison TRUE (cached)";
863             return LDAP_COMPARE_TRUE;
864         }
865
866         /* unlock this read lock */
867         LDAP_CACHE_UNLOCK();
868     }
869
870 start_over:
871     if (failures++ > 10) {
872         /* too many failures */
873         return result;
874     }
875
876     /* make a server connection */
877     if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
878         /* connect to server failed */
879         return result;
880     }
881
882     /* search for reqdn */
883     result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
884                                "(objectclass=*)", NULL, 1,
885                                NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
886     if (AP_LDAP_IS_SERVER_DOWN(result))
887     {
888         ldc->reason = "DN Comparison ldap_search_ext_s() "
889                       "failed with server down";
890         uldap_connection_unbind(ldc);
891         goto start_over;
892     }
893     if (result == LDAP_TIMEOUT && failures == 0) {
894         /*
895          * we are reusing a connection that doesn't seem to be active anymore
896          * (firewall state drop?), let's try a new connection.
897          */
898         ldc->reason = "DN Comparison ldap_search_ext_s() "
899                       "failed with timeout";
900         uldap_connection_unbind(ldc);
901         goto start_over;
902     }
903     if (result != LDAP_SUCCESS) {
904         /* search for reqdn failed - no match */
905         ldc->reason = "DN Comparison ldap_search_ext_s() failed";
906         return result;
907     }
908
909     entry = ldap_first_entry(ldc->ldap, res);
910     searchdn = ldap_get_dn(ldc->ldap, entry);
911
912     ldap_msgfree(res);
913     if (strcmp(dn, searchdn) != 0) {
914         /* compare unsuccessful */
915         ldc->reason = "DN Comparison FALSE (checked on server)";
916         result = LDAP_COMPARE_FALSE;
917     }
918     else {
919         if (curl) {
920             /* compare successful - add to the compare cache */
921             LDAP_CACHE_LOCK();
922             newnode.reqdn = (char *)reqdn;
923             newnode.dn = (char *)dn;
924
925             node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
926             if (   (node == NULL)
927                 || (strcmp(reqdn, node->reqdn) != 0)
928                 || (strcmp(dn, node->dn) != 0))
929             {
930                 util_ald_cache_insert(curl->dn_compare_cache, &newnode);
931             }
932             LDAP_CACHE_UNLOCK();
933         }
934         ldc->reason = "DN Comparison TRUE (checked on server)";
935         result = LDAP_COMPARE_TRUE;
936     }
937     ldap_memfree(searchdn);
938     return result;
939
940 }
941
942 /*
943  * Does an generic ldap_compare operation. It accepts a cache that it will use
944  * to lookup the compare in the cache. We cache two kinds of compares
945  * (require group compares) and (require user compares). Each compare has a
946  * different cache node: require group includes the DN; require user does not
947  * because the require user cache is owned by the
948  *
949  */
950 static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
951                                const char *url, const char *dn,
952                                const char *attrib, const char *value)
953 {
954     int result = 0;
955     util_url_node_t *curl;
956     util_url_node_t curnode;
957     util_compare_node_t *compare_nodep;
958     util_compare_node_t the_compare_node;
959     apr_time_t curtime = 0; /* silence gcc -Wall */
960     int failures = 0;
961
962     util_ldap_state_t *st = (util_ldap_state_t *)
963                             ap_get_module_config(r->server->module_config,
964                                                  &ldap_module);
965
966     /* get cache entry (or create one) */
967     LDAP_CACHE_LOCK();
968     curnode.url = url;
969     curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
970     if (curl == NULL) {
971         curl = util_ald_create_caches(st, url);
972     }
973     LDAP_CACHE_UNLOCK();
974
975     if (curl) {
976         /* make a comparison to the cache */
977         LDAP_CACHE_LOCK();
978         curtime = apr_time_now();
979
980         the_compare_node.dn = (char *)dn;
981         the_compare_node.attrib = (char *)attrib;
982         the_compare_node.value = (char *)value;
983         the_compare_node.result = 0;
984         the_compare_node.sgl_processed = 0;
985         the_compare_node.subgroupList = NULL;
986
987         compare_nodep = util_ald_cache_fetch(curl->compare_cache,
988                                              &the_compare_node);
989
990         if (compare_nodep != NULL) {
991             /* found it... */
992             if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
993                 /* ...but it is too old */
994                 util_ald_cache_remove(curl->compare_cache, compare_nodep);
995             }
996             else {
997                 /* ...and it is good */
998                 if (LDAP_COMPARE_TRUE == compare_nodep->result) {
999                     ldc->reason = "Comparison true (cached)";
1000                 }
1001                 else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
1002                     ldc->reason = "Comparison false (cached)";
1003                 }
1004                 else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
1005                     ldc->reason = "Comparison no such attribute (cached)";
1006                 }
1007                 else {
1008                     ldc->reason = "Comparison undefined (cached)";
1009                 }
1010
1011                 /* record the result code to return with the reason... */
1012                 result = compare_nodep->result;
1013                 /* and unlock this read lock */
1014                 LDAP_CACHE_UNLOCK();
1015                 return result;
1016             }
1017         }
1018         /* unlock this read lock */
1019         LDAP_CACHE_UNLOCK();
1020     }
1021
1022 start_over:
1023     if (failures++ > 10) {
1024         /* too many failures */
1025         return result;
1026     }
1027
1028     if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1029         /* connect failed */
1030         return result;
1031     }
1032
1033     result = ldap_compare_s(ldc->ldap,
1034                             (char *)dn,
1035                             (char *)attrib,
1036                             (char *)value);
1037     if (AP_LDAP_IS_SERVER_DOWN(result)) { 
1038         /* connection failed - try again */
1039         ldc->reason = "ldap_compare_s() failed with server down";
1040         uldap_connection_unbind(ldc);
1041         goto start_over;
1042     }
1043     if (result == LDAP_TIMEOUT && failures == 0) {
1044         /*
1045          * we are reusing a connection that doesn't seem to be active anymore
1046          * (firewall state drop?), let's try a new connection.
1047          */
1048         ldc->reason = "ldap_compare_s() failed with timeout";
1049         uldap_connection_unbind(ldc);
1050         goto start_over;
1051     }
1052
1053     ldc->reason = "Comparison complete";
1054     if ((LDAP_COMPARE_TRUE == result) ||
1055         (LDAP_COMPARE_FALSE == result) ||
1056         (LDAP_NO_SUCH_ATTRIBUTE == result)) {
1057         if (curl) {
1058             /* compare completed; caching result */
1059             LDAP_CACHE_LOCK();
1060             the_compare_node.lastcompare = curtime;
1061             the_compare_node.result = result;
1062             the_compare_node.sgl_processed = 0;
1063             the_compare_node.subgroupList = NULL;
1064
1065             /* If the node doesn't exist then insert it, otherwise just update
1066              * it with the last results
1067              */
1068             compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1069                                                  &the_compare_node);
1070             if (   (compare_nodep == NULL)
1071                 || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
1072                 || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
1073                 || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
1074             {
1075                 void *junk;
1076
1077                 junk = util_ald_cache_insert(curl->compare_cache,
1078                                              &the_compare_node);
1079                 if(junk == NULL) {
1080                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1081                                   "[%" APR_PID_T_FMT "] cache_compare: Cache"
1082                                   " insertion failure.", getpid());
1083                 }
1084             }
1085             else {
1086                 compare_nodep->lastcompare = curtime;
1087                 compare_nodep->result = result;
1088             }
1089             LDAP_CACHE_UNLOCK();
1090         }
1091         if (LDAP_COMPARE_TRUE == result) {
1092             ldc->reason = "Comparison true (adding to cache)";
1093             return LDAP_COMPARE_TRUE;
1094         }
1095         else if (LDAP_COMPARE_FALSE == result) {
1096             ldc->reason = "Comparison false (adding to cache)";
1097             return LDAP_COMPARE_FALSE;
1098         }
1099         else {
1100             ldc->reason = "Comparison no such attribute (adding to cache)";
1101             return LDAP_NO_SUCH_ATTRIBUTE;
1102         }
1103     }
1104     return result;
1105 }
1106
1107
1108 static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r,
1109                                                     util_ldap_connection_t *ldc,
1110                                                     const char *url,
1111                                                     const char *dn,
1112                                                     char **subgroupAttrs,
1113                                                     apr_array_header_t *subgroupclasses)
1114 {
1115     int failures = 0;
1116     int result = LDAP_COMPARE_FALSE;
1117     util_compare_subgroup_t *res = NULL;
1118     LDAPMessage *sga_res, *entry;
1119     struct mod_auth_ldap_groupattr_entry_t *sgc_ents;
1120     apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
1121
1122     sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1123
1124     if (!subgroupAttrs) {
1125         return res;
1126     }
1127
1128 start_over:
1129     /*
1130      * 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups.
1131      */
1132     if (failures++ > 10) {
1133         /* too many failures */
1134         return res;
1135     }
1136
1137     if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1138         /* connect failed */
1139         return res;
1140     }
1141
1142     /* try to do the search */
1143     result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
1144                                (char *)"cn=*", subgroupAttrs, 0,
1145                                NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
1146     if (AP_LDAP_IS_SERVER_DOWN(result)) {
1147         ldc->reason = "ldap_search_ext_s() for subgroups failed with server"
1148                       " down";
1149         uldap_connection_unbind(ldc);
1150         goto start_over;
1151     }
1152     if (result == LDAP_TIMEOUT && failures == 0) {
1153         /*
1154          * we are reusing a connection that doesn't seem to be active anymore
1155          * (firewall state drop?), let's try a new connection.
1156          */
1157         ldc->reason = "ldap_search_ext_s() for subgroups failed with timeout";
1158         uldap_connection_unbind(ldc);
1159         goto start_over;
1160     }
1161
1162     /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1163     if (result != LDAP_SUCCESS) {
1164         ldc->reason = "ldap_search_ext_s() for subgroups failed";
1165         return res;
1166     }
1167
1168     entry = ldap_first_entry(ldc->ldap, sga_res);
1169
1170     /*
1171      * Get values for the provided sub-group attributes.
1172      */
1173     if (subgroupAttrs) {
1174         int indx = 0, tmp_sgcIndex;
1175
1176         while (subgroupAttrs[indx]) {
1177             char **values;
1178             int val_index = 0;
1179
1180             /* Get *all* matching "member" values from this group. */
1181             values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
1182
1183             if (values) {
1184                 val_index = 0;
1185                 /*
1186                  * Now we are going to pare the subgroup members of this group
1187                  * to *just* the subgroups, add them to the compare_nodep, and
1188                  * then proceed to check the new level of subgroups.
1189                  */
1190                 while (values[val_index]) {
1191                     /* Check if this entry really is a group. */
1192                     tmp_sgcIndex = 0;
1193                     result = LDAP_COMPARE_FALSE;
1194                     while ((tmp_sgcIndex < subgroupclasses->nelts)
1195                            && (result != LDAP_COMPARE_TRUE)) {
1196                         result = uldap_cache_compare(r, ldc, url,
1197                                                      values[val_index],
1198                                                      "objectClass",
1199                                                      sgc_ents[tmp_sgcIndex].name
1200                                                      );
1201
1202                         if (result != LDAP_COMPARE_TRUE) {
1203                             tmp_sgcIndex++;
1204                         }
1205                     }
1206                     /* It's a group, so add it to the array.  */
1207                     if (result == LDAP_COMPARE_TRUE) {
1208                         char **newgrp = (char **) apr_array_push(subgroups);
1209                         *newgrp = apr_pstrdup(r->pool, values[val_index]);
1210                     }
1211                     val_index++;
1212                 }
1213                 ldap_value_free(values);
1214             }
1215             indx++;
1216         }
1217     }
1218
1219     ldap_msgfree(sga_res);
1220
1221     if (subgroups->nelts > 0) {
1222         /* We need to fill in tmp_local_subgroups using the data from LDAP */
1223         int sgindex;
1224         char **group;
1225         res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
1226         res->subgroupDNs  = apr_pcalloc(r->pool,
1227                                         sizeof(char *) * (subgroups->nelts));
1228         for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
1229             res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
1230         }
1231         res->len = sgindex;
1232     }
1233
1234     return res;
1235 }
1236
1237
1238 /*
1239  * Does a recursive lookup operation to try to find a user within (cached)
1240  * nested groups. It accepts a cache that it will use to lookup previous
1241  * compare attempts. We cache two kinds of compares (require group compares)
1242  * and (require user compares). Each compare has a different cache node:
1243  * require group includes the DN; require user does not because the require
1244  * user cache is owned by the
1245  *
1246  * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
1247  *
1248  *
1249  * 1. Call uldap_cache_compare for each subgroupclass value to check the
1250  *    generic, user-agnostic, cached group entry. This will create a new generic
1251  *    cache entry if there
1252  *    wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we
1253  *    have no groups.
1254  * 2. Lock The cache and get the generic cache entry.
1255  * 3. Check if there is already a subgrouplist in this generic group's cache
1256  *    entry.
1257  *    A. If there is, go to step 4.
1258  *    B. If there isn't:
1259  *       i)   Use ldap_search to get the full list
1260  *            of subgroup "members" (which may include non-group "members").
1261  *       ii)  Use uldap_cache_compare to strip the list down to just groups.
1262  *       iii) Lock and add this stripped down list to the cache of the generic
1263  *            group.
1264  * 4. Loop through the sgl and call uldap_cache_compare (using the user info)
1265  *    for each
1266  *    subgroup to see if the subgroup contains the user and to get the subgroups
1267  *    added to the
1268  *    cache (with user-afinity, if they aren't already there).
1269  *    A. If the user is in the subgroup, then we'll be returning
1270  *       LDAP_COMPARE_TRUE.
1271  *    B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via
1272  *       uldap_cache_compare) then recursively call this function to get the
1273  *       sub-subgroups added...
1274  * 5. Cleanup local allocations.
1275  * 6. Return the final result.
1276  */
1277
1278 static int uldap_cache_check_subgroups(request_rec *r,
1279                                        util_ldap_connection_t *ldc,
1280                                        const char *url, const char *dn,
1281                                        const char *attrib, const char *value,
1282                                        char **subgroupAttrs,
1283                                        apr_array_header_t *subgroupclasses,
1284                                        int cur_subgroup_depth,
1285                                        int max_subgroup_depth)
1286 {
1287     int result = LDAP_COMPARE_FALSE;
1288     util_url_node_t *curl;
1289     util_url_node_t curnode;
1290     util_compare_node_t *compare_nodep;
1291     util_compare_node_t the_compare_node;
1292     util_compare_subgroup_t *tmp_local_sgl = NULL;
1293     int sgl_cached_empty = 0, sgindex = 0, base_sgcIndex = 0;
1294     struct mod_auth_ldap_groupattr_entry_t *sgc_ents =
1295             (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1296     util_ldap_state_t *st = (util_ldap_state_t *)
1297                             ap_get_module_config(r->server->module_config,
1298                                                  &ldap_module);
1299
1300     /*
1301      * Stop looking at deeper levels of nested groups if we have reached the
1302      * max. Since we already checked the top-level group in uldap_cache_compare,
1303      * we don't need to check it again here - so if max_subgroup_depth is set
1304      * to 0, we won't check it (i.e. that is why we check < rather than <=).
1305      * We'll be calling uldap_cache_compare from here to check if the user is
1306      * in the next level before we recurse into that next level looking for
1307      * more subgroups.
1308      */
1309     if (cur_subgroup_depth >= max_subgroup_depth) {
1310         return LDAP_COMPARE_FALSE;
1311     }
1312
1313     /*
1314      * 1. Check the "groupiness" of the specified basedn. Stopping at the first
1315      *    TRUE return.
1316      */
1317     while ((base_sgcIndex < subgroupclasses->nelts)
1318            && (result != LDAP_COMPARE_TRUE)) {
1319         result = uldap_cache_compare(r, ldc, url, dn, "objectClass",
1320                                      sgc_ents[base_sgcIndex].name);
1321         if (result != LDAP_COMPARE_TRUE) {
1322             base_sgcIndex++;
1323         }
1324     }
1325
1326     if (result != LDAP_COMPARE_TRUE) {
1327         ldc->reason = "DN failed group verification.";
1328         return result;
1329     }
1330
1331     /*
1332      * 2. Find previously created cache entry and check if there is already a
1333      *    subgrouplist.
1334      */
1335     LDAP_CACHE_LOCK();
1336     curnode.url = url;
1337     curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
1338     LDAP_CACHE_UNLOCK();
1339
1340     if (curl && curl->compare_cache) {
1341         /* make a comparison to the cache */
1342         LDAP_CACHE_LOCK();
1343
1344         the_compare_node.dn = (char *)dn;
1345         the_compare_node.attrib = (char *)"objectClass";
1346         the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1347         the_compare_node.result = 0;
1348         the_compare_node.sgl_processed = 0;
1349         the_compare_node.subgroupList = NULL;
1350
1351         compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1352                                              &the_compare_node);
1353
1354         if (compare_nodep != NULL) {
1355             /*
1356              * Found the generic group entry... but the user isn't in this
1357              * group or we wouldn't be here.
1358              */
1359             if (compare_nodep->sgl_processed) {
1360                 if (compare_nodep->subgroupList) {
1361                     /* Make a local copy of the subgroup list */
1362                     int i;
1363                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1364                                   "[%" APR_PID_T_FMT "] util_ldap:"
1365                                   " Making local copy of SGL for "
1366                                   "group (%s)(objectClass=%s) ",
1367                                   getpid(), dn,
1368                                   (char *)sgc_ents[base_sgcIndex].name);
1369                     tmp_local_sgl = apr_pcalloc(r->pool,
1370                                                 sizeof(util_compare_subgroup_t));
1371                     tmp_local_sgl->len = compare_nodep->subgroupList->len;
1372                     tmp_local_sgl->subgroupDNs =
1373                         apr_pcalloc(r->pool,
1374                                     sizeof(char *) * compare_nodep->subgroupList->len);
1375                     for (i = 0; i < compare_nodep->subgroupList->len; i++) {
1376                         tmp_local_sgl->subgroupDNs[i] =
1377                             apr_pstrdup(r->pool,
1378                                         compare_nodep->subgroupList->subgroupDNs[i]);
1379                     }
1380                 }
1381                 else {
1382                     sgl_cached_empty = 1;
1383                 }
1384             }
1385         }
1386         LDAP_CACHE_UNLOCK();
1387     }
1388
1389     if (!tmp_local_sgl && !sgl_cached_empty) {
1390         /* No Cached SGL, retrieve from LDAP */
1391         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1392                       "[%" APR_PID_T_FMT "] util_ldap: no cached SGL for %s,"
1393                       " retrieving from LDAP" , getpid(), dn);
1394         tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs,
1395                                             subgroupclasses);
1396         if (!tmp_local_sgl) {
1397             /* No SGL aailable via LDAP either */
1398             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "]"
1399                           " util_ldap: no subgroups for %s" , getpid(), dn);
1400         }
1401
1402       if (curl && curl->compare_cache) {
1403         /*
1404          * Find the generic group cache entry and add the sgl we just retrieved.
1405          */
1406         LDAP_CACHE_LOCK();
1407
1408         the_compare_node.dn = (char *)dn;
1409         the_compare_node.attrib = (char *)"objectClass";
1410         the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1411         the_compare_node.result = 0;
1412         the_compare_node.sgl_processed = 0;
1413         the_compare_node.subgroupList = NULL;
1414
1415         compare_nodep = util_ald_cache_fetch(curl->compare_cache,
1416                                              &the_compare_node);
1417
1418         if (compare_nodep == NULL) {
1419             /*
1420              * The group entry we want to attach our SGL to doesn't exist.
1421              * We only got here if we verified this DN was actually a group
1422              * based on the objectClass, but we can't call the compare function
1423              * while we already hold the cache lock -- only the insert.
1424              */
1425             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1426                           "[%" APR_PID_T_FMT "] util_ldap: Cache entry "
1427                           "for %s doesn't exist",
1428                            getpid(), 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                               "[%" APR_PID_T_FMT "] util_ldap: Couldn't "
1436                               "retrieve group entry for %s from cache",
1437                                getpid(), 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, "[%" APR_PID_T_FMT "]"
1501                           " util_ldap: Found user %s in a subgroup (%s) at"
1502                           " level %d of %d.", getpid(), r->user, group,
1503                           cur_subgroup_depth+1, max_subgroup_depth);
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, "[%" APR_PID_T_FMT "]"
1511                           " util_ldap: user %s not found in subgroup (%s) at"
1512                           " level %d of %d.", getpid(), r->user, group,
1513                           cur_subgroup_depth+1, max_subgroup_depth);
1514             result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
1515                                                  value, subgroupAttrs,
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                  "[%" APR_PID_T_FMT "] ldap cache: Setting shared memory "
2012                  " cache size to %" APR_SIZE_T_FMT " bytes.",
2013                  getpid(), 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                  "[%" APR_PID_T_FMT "] ldap cache: Setting cache TTL to %ld"
2060                  " microseconds.", getpid(), 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                  "[%" APR_PID_T_FMT "] ldap cache: Setting search cache size"
2084                  " to %ld entries.", getpid(), 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                  "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache TTL"
2105                  " to %ld microseconds.", getpid(), 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                  "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache size"
2129                  " to %ld entries.", getpid(), 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_state_t *st =
2304         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2305                                                   &ldap_module);
2306     apr_finfo_t finfo;
2307     apr_status_t rv;
2308     int cert_type = 0;
2309     apr_ldap_opt_tls_cert_t *cert;
2310
2311     /* handle the certificate type */
2312     if (type) {
2313         cert_type = util_ldap_parse_cert_type(type);
2314         if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
2315             return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2316                                            "not recognised. It should be one "
2317                                            "of CERT_DER, CERT_BASE64, "
2318                                            "CERT_NICKNAME, CERT_PFX,"
2319                                            "KEY_DER, KEY_BASE64, KEY_PFX",
2320                                            type);
2321         }
2322         else if (APR_LDAP_CA_TYPE_DER == cert_type ||
2323                  APR_LDAP_CA_TYPE_BASE64 == cert_type ||
2324                  APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
2325                  APR_LDAP_CA_TYPE_SECMOD == cert_type ||
2326                  APR_LDAP_CERT_TYPE_PFX == cert_type ||
2327                  APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
2328             return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
2329                                            "only valid within a "
2330                                            "LDAPTrustedGlobalCert directive. "
2331                                            "Only CERT_DER, CERT_BASE64, "
2332                                            "CERT_NICKNAME, KEY_DER, and "
2333                                            "KEY_BASE64 may be used.", type);
2334         }
2335     }
2336     else {
2337         return "Certificate type was not specified.";
2338     }
2339
2340     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2341                       "LDAP: SSL trusted client cert - %s (type %s)",
2342                        file, type);
2343
2344     /* add the certificate to the global array */
2345     cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
2346     cert->type = cert_type;
2347     cert->path = file;
2348     cert->password = password;
2349
2350     /* if file is a file or path, fix the path */
2351     if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
2352         cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
2353
2354         cert->path = ap_server_root_relative(cmd->pool, file);
2355         if (cert->path &&
2356             ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
2357                 != APR_SUCCESS))
2358         {
2359             ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
2360                          "LDAP: Could not open SSL client certificate "
2361                          "file - %s",
2362                          cert->path == NULL ? file : cert->path);
2363             return "Invalid client certificate file path";
2364         }
2365
2366     }
2367
2368     return(NULL);
2369 }
2370
2371
2372 /**
2373  * Set LDAPTrustedMode.
2374  *
2375  * This directive sets what encryption mode to use on a connection:
2376  * - None (No encryption)
2377  * - SSL (SSL encryption)
2378  * - STARTTLS (TLS encryption)
2379  */
2380 static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
2381                                               const char *mode)
2382 {
2383     util_ldap_state_t *st =
2384     (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2385                                               &ldap_module);
2386
2387     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2388                       "LDAP: SSL trusted mode - %s",
2389                        mode);
2390
2391     if (0 == strcasecmp("NONE", mode)) {
2392         st->secure = APR_LDAP_NONE;
2393     }
2394     else if (0 == strcasecmp("SSL", mode)) {
2395         st->secure = APR_LDAP_SSL;
2396     }
2397     else if (   (0 == strcasecmp("TLS", mode))
2398              || (0 == strcasecmp("STARTTLS", mode))) {
2399         st->secure = APR_LDAP_STARTTLS;
2400     }
2401     else {
2402         return "Invalid LDAPTrustedMode setting: must be one of NONE, "
2403                "SSL, or TLS/STARTTLS";
2404     }
2405
2406     st->secure_set = 1;
2407     return(NULL);
2408 }
2409
2410 static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
2411                                                  void *dummy,
2412                                                  int mode)
2413 {
2414     util_ldap_state_t *st =
2415     (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2416                                               &ldap_module);
2417     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2418
2419     if (err != NULL) {
2420         return err;
2421     }
2422
2423     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2424                       "LDAP: SSL verify server certificate - %s",
2425                       mode?"TRUE":"FALSE");
2426
2427     st->verify_svr_cert = mode;
2428
2429     return(NULL);
2430 }
2431
2432
2433 static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
2434                                                     void *dummy,
2435                                                     const char *ttl)
2436 {
2437 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2438     util_ldap_state_t *st =
2439         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2440                                                   &ldap_module);
2441 #endif
2442     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2443
2444     if (err != NULL) {
2445         return err;
2446     }
2447
2448 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2449     st->connectionTimeout = atol(ttl);
2450
2451     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2452                  "[%" APR_PID_T_FMT "] ldap connection: Setting connection"
2453                  " timeout to %ld seconds.", getpid(), st->connectionTimeout);
2454 #else
2455     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
2456                  "LDAP: Connection timeout option not supported by the "
2457                  "LDAP SDK in use." );
2458 #endif
2459
2460     return NULL;
2461 }
2462
2463
2464 static const char *util_ldap_set_chase_referrals(cmd_parms *cmd,
2465                                                  void *config,
2466                                                  int mode)
2467 {
2468     util_ldap_config_t *dc =  config;
2469
2470     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2471                       "LDAP: Setting referral chasing %s",
2472                       (mode == AP_LDAP_CHASEREFERRALS_ON) ? "ON" : "OFF");
2473
2474     dc->ChaseReferrals = mode;
2475
2476     return(NULL);
2477 }
2478
2479 static const char *util_ldap_set_debug_level(cmd_parms *cmd,
2480                                              void *config,
2481                                              const char *arg) { 
2482     util_ldap_state_t *st =
2483         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2484                                                   &ldap_module);
2485
2486     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2487     if (err != NULL) {
2488         return err;
2489     }
2490
2491 #ifndef AP_LDAP_OPT_DEBUG
2492     return "This directive is not supported with the currently linked LDAP library";
2493 #endif
2494
2495     st->debug_level = atoi(arg);
2496     return NULL;
2497
2498
2499 static const char *util_ldap_set_referral_hop_limit(cmd_parms *cmd,
2500                                                     void *config,
2501                                                     const char *hop_limit)
2502 {
2503     util_ldap_config_t *dc =  config;
2504
2505     dc->ReferralHopLimit = atol(hop_limit);
2506
2507     if (dc->ReferralHopLimit <= 0) { 
2508         return "LDAPReferralHopLimit must be greater than zero (Use 'LDAPReferrals Off' to disable referral chasing)";
2509     }
2510
2511     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2512                  "LDAP: Limit chased referrals to maximum of %d hops.",
2513                  dc->ReferralHopLimit);
2514
2515     return NULL;
2516 }
2517
2518 static void *util_ldap_create_dir_config(apr_pool_t *p, char *d) {
2519    util_ldap_config_t *dc =
2520        (util_ldap_config_t *) apr_pcalloc(p,sizeof(util_ldap_config_t));
2521
2522    /* defaults are AP_LDAP_CHASEREFERRALS_ON and AP_LDAP_DEFAULT_HOPLIMIT */
2523    dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_ON;
2524    dc->ReferralHopLimit = AP_LDAP_HOPLIMIT_UNSET;
2525
2526    return dc;
2527 }
2528
2529 static const char *util_ldap_set_op_timeout(cmd_parms *cmd,
2530                                             void *dummy,
2531                                             const char *val)
2532 {
2533     long timeout;
2534     char *endptr;
2535     util_ldap_state_t *st =
2536         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2537                                                   &ldap_module);
2538     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2539
2540     if (err != NULL) {
2541         return err;
2542     }
2543
2544     timeout = strtol(val, &endptr, 10);
2545     if ((val == endptr) || (*endptr != '\0')) {
2546         return "Timeout not numerical";
2547     }
2548     if (timeout < 0) {
2549         return "Timeout must be non-negative";
2550     }
2551
2552     if (timeout) {
2553         if (!st->opTimeout) {
2554             st->opTimeout = apr_pcalloc(cmd->pool, sizeof(struct timeval));
2555         }
2556         st->opTimeout->tv_sec = timeout;
2557     }
2558     else {
2559         st->opTimeout = NULL;
2560     }
2561
2562     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2563                  "[%" APR_PID_T_FMT "] ldap connection: Setting op timeout "
2564                  "to %ld seconds.", getpid(), timeout);
2565
2566 #ifndef LDAP_OPT_TIMEOUT
2567
2568     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2569                  "LDAP: LDAP_OPT_TIMEOUT option not supported by the "
2570                  "LDAP library in use. Using LDAPTimeout value as search "
2571                  "timeout only." );
2572 #endif
2573
2574     return NULL;
2575 }
2576
2577
2578
2579 static void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
2580 {
2581     util_ldap_state_t *st =
2582         (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
2583
2584     /* Create a per vhost pool for mod_ldap to use, serialized with 
2585      * st->mutex (also one per vhost).  both are replicated by fork(),
2586      * no shared memory managed by either.
2587      */
2588     apr_pool_create(&st->pool, p);
2589 #if APR_HAS_THREADS
2590     apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
2591 #endif
2592
2593     st->cache_bytes = 500000;
2594     st->search_cache_ttl = 600000000;
2595     st->search_cache_size = 1024;
2596     st->compare_cache_ttl = 600000000;
2597     st->compare_cache_size = 1024;
2598     st->connections = NULL;
2599     st->ssl_supported = 0;
2600     st->global_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2601     st->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2602     st->secure = APR_LDAP_NONE;
2603     st->secure_set = 0;
2604     st->connectionTimeout = 10;
2605     st->opTimeout = apr_pcalloc(p, sizeof(struct timeval));
2606     st->opTimeout->tv_sec = 60;
2607     st->verify_svr_cert = 1;
2608
2609     return st;
2610 }
2611
2612 static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
2613                                     void *overridesv)
2614 {
2615     util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
2616     util_ldap_state_t *base = (util_ldap_state_t *) basev;
2617     util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
2618
2619     st->pool = overrides->pool;
2620 #if APR_HAS_THREADS
2621     st->mutex = overrides->mutex;
2622 #endif
2623
2624     /* The cache settings can not be modified in a 
2625         virtual host since all server use the same
2626         shared memory cache. */
2627     st->cache_bytes = base->cache_bytes;
2628     st->search_cache_ttl = base->search_cache_ttl;
2629     st->search_cache_size = base->search_cache_size;
2630     st->compare_cache_ttl = base->compare_cache_ttl;
2631     st->compare_cache_size = base->compare_cache_size;
2632     st->util_ldap_cache_lock = base->util_ldap_cache_lock; 
2633
2634     st->connections = NULL;
2635     st->ssl_supported = 0;
2636     st->global_certs = apr_array_append(p, base->global_certs,
2637                                            overrides->global_certs);
2638     st->client_certs = apr_array_append(p, base->client_certs,
2639                                            overrides->client_certs);
2640     st->secure = (overrides->secure_set == 0) ? base->secure
2641                                               : overrides->secure;
2642
2643     /* These LDAP connection settings can not be overwritten in 
2644         a virtual host. Once set in the base server, they must 
2645         remain the same. None of the LDAP SDKs seem to be able
2646         to handle setting the verify_svr_cert flag on a 
2647         per-connection basis.  The OpenLDAP client appears to be
2648         able to handle the connection timeout per-connection
2649         but the Novell SDK cannot.  Allowing the timeout to
2650         be set by each vhost is of little value so rather than
2651         trying to make special expections for one LDAP SDK, GLOBAL_ONLY 
2652         is being enforced on this setting as well. */
2653     st->connectionTimeout = base->connectionTimeout;
2654     st->opTimeout = base->opTimeout;
2655     st->verify_svr_cert = base->verify_svr_cert;
2656     st->debug_level = base->debug_level;
2657
2658     return st;
2659 }
2660
2661 static apr_status_t util_ldap_cleanup_module(void *data)
2662 {
2663
2664     server_rec *s = data;
2665     util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
2666         s->module_config, &ldap_module);
2667
2668     if (st->ssl_supported) {
2669         apr_ldap_ssl_deinit();
2670     }
2671
2672     return APR_SUCCESS;
2673
2674 }
2675
2676 static int util_ldap_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
2677                                 apr_pool_t *ptemp)
2678 {
2679     apr_status_t result;
2680
2681     result = ap_mutex_register(pconf, ldap_cache_mutex_type, NULL,
2682                                APR_LOCK_DEFAULT, 0);
2683     if (result != APR_SUCCESS) {
2684         return result;
2685     }
2686
2687     return OK;
2688 }
2689
2690 static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog,
2691                                  apr_pool_t *ptemp, server_rec *s)
2692 {
2693     apr_status_t result;
2694     server_rec *s_vhost;
2695     util_ldap_state_t *st_vhost;
2696
2697     util_ldap_state_t *st = (util_ldap_state_t *)
2698                             ap_get_module_config(s->module_config,
2699                                                  &ldap_module);
2700
2701     void *data;
2702     const char *userdata_key = "util_ldap_init";
2703     apr_ldap_err_t *result_err = NULL;
2704     int rc;
2705
2706     /* util_ldap_post_config() will be called twice. Don't bother
2707      * going through all of the initialization on the first call
2708      * because it will just be thrown away.*/
2709     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
2710     if (!data) {
2711         apr_pool_userdata_set((const void *)1, userdata_key,
2712                                apr_pool_cleanup_null, s->process->pool);
2713
2714 #if APR_HAS_SHARED_MEMORY
2715         /* If the cache file already exists then delete it.  Otherwise we are
2716          * going to run into problems creating the shared memory. */
2717         if (st->cache_file) {
2718             char *lck_file = apr_pstrcat(ptemp, st->cache_file, ".lck",
2719                                          NULL);
2720             apr_file_remove(lck_file, ptemp);
2721         }
2722 #endif
2723         return OK;
2724     }
2725
2726 #if APR_HAS_SHARED_MEMORY
2727     /* initializing cache if shared memory size is not zero and we already
2728      * don't have shm address
2729      */
2730     if (!st->cache_shm && st->cache_bytes > 0) {
2731 #endif
2732         result = util_ldap_cache_init(p, st);
2733         if (result != APR_SUCCESS) {
2734             ap_log_error(APLOG_MARK, APLOG_ERR, result, s,
2735                          "LDAP cache: could not create shared memory segment");
2736             return DONE;
2737         }
2738
2739         result = ap_global_mutex_create(&st->util_ldap_cache_lock,
2740                                         ldap_cache_mutex_type, NULL, s, p, 0);
2741         if (result != APR_SUCCESS) {
2742             return result;
2743         }
2744
2745         /* merge config in all vhost */
2746         s_vhost = s->next;
2747         while (s_vhost) {
2748             st_vhost = (util_ldap_state_t *)
2749                        ap_get_module_config(s_vhost->module_config,
2750                                             &ldap_module);
2751
2752 #if APR_HAS_SHARED_MEMORY
2753             st_vhost->cache_shm = st->cache_shm;
2754             st_vhost->cache_rmm = st->cache_rmm;
2755             st_vhost->cache_file = st->cache_file;
2756             ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s,
2757                          "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
2758                          "for VHOST: %s", st->cache_shm, st->cache_rmm,
2759                          s_vhost->server_hostname);
2760 #endif
2761             s_vhost = s_vhost->next;
2762         }
2763 #if APR_HAS_SHARED_MEMORY
2764     }
2765     else {
2766         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
2767                      "LDAP cache: LDAPSharedCacheSize is zero, disabling "
2768                      "shared memory cache");
2769     }
2770 #endif
2771
2772     /* log the LDAP SDK used
2773      */
2774     {
2775         apr_ldap_err_t *result = NULL;
2776         apr_ldap_info(p, &(result));
2777         if (result != NULL) {
2778             ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s", result->reason);
2779         }
2780     }
2781
2782     apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
2783                               util_ldap_cleanup_module);
2784
2785     /*
2786      * Initialize SSL support, and log the result for the benefit of the admin.
2787      *
2788      * If SSL is not supported it is not necessarily an error, as the
2789      * application may not want to use it.
2790      */
2791     rc = apr_ldap_ssl_init(p,
2792                       NULL,
2793                       0,
2794                       &(result_err));
2795     if (APR_SUCCESS == rc) {
2796         rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
2797                                  (void *)st->global_certs, &(result_err));
2798     }
2799
2800     if (APR_SUCCESS == rc) {
2801         st->ssl_supported = 1;
2802         ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2803                      "LDAP: SSL support available" );
2804     }
2805     else {
2806         st->ssl_supported = 0;
2807         ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2808                      "LDAP: SSL support unavailable%s%s",
2809                      result_err ? ": " : "",
2810                      result_err ? result_err->reason : "");
2811     }
2812
2813     /* Initialize the rebind callback's cross reference list. */
2814     apr_ldap_rebind_init (p);
2815
2816 #ifdef AP_LDAP_OPT_DEBUG
2817     if (st->debug_level > 0) { 
2818         result = ldap_set_option(NULL, AP_LDAP_OPT_DEBUG, &st->debug_level);
2819         if (result != LDAP_SUCCESS) {
2820             ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
2821                     "LDAP: Could not set the LDAP library debug level to %d:(%d) %s", 
2822                     st->debug_level, result, ldap_err2string(result));
2823         }
2824     }
2825 #endif
2826
2827     return(OK);
2828 }
2829
2830 static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
2831 {
2832     apr_status_t sts;
2833     util_ldap_state_t *st = ap_get_module_config(s->module_config,
2834                                                  &ldap_module);
2835
2836     if (!st->util_ldap_cache_lock) return;
2837
2838     sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
2839               apr_global_mutex_lockfile(st->util_ldap_cache_lock), p);
2840     if (sts != APR_SUCCESS) {
2841         ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s,
2842                      "Failed to initialise global mutex %s in child process %"
2843                      APR_PID_T_FMT ".",
2844                      ldap_cache_mutex_type, getpid());
2845     }
2846 }
2847
2848 static const command_rec util_ldap_cmds[] = {
2849     AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes,
2850                   NULL, RSRC_CONF,
2851                   "Set the size of the shared memory cache (in bytes). Use "
2852                   "0 to disable the shared memory cache. (default: 100000)"),
2853
2854     AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file,
2855                   NULL, RSRC_CONF,
2856                   "Set the file name for the shared memory cache."),
2857
2858     AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries,
2859                   NULL, RSRC_CONF,
2860                   "Set the maximum number of entries that are possible in the "
2861                   "LDAP search cache. Use 0 or -1 to disable the search cache " 
2862                   "(default: 1024)"),
2863                   
2864     AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl,
2865                   NULL, RSRC_CONF,
2866                   "Set the maximum time (in seconds) that an item can be "
2867                   "cached in the LDAP search cache. Use 0 for no limit. "
2868                   "(default 600)"),
2869
2870     AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries,
2871                   NULL, RSRC_CONF,
2872                   "Set the maximum number of entries that are possible "
2873                   "in the LDAP compare cache. Use 0 or -1 to disable the compare cache " 
2874                   "(default: 1024)"),
2875
2876     AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl,
2877                   NULL, RSRC_CONF,
2878                   "Set the maximum time (in seconds) that an item is cached "
2879                   "in the LDAP operation cache. Use 0 for no limit. "
2880                   "(default: 600)"),
2881
2882     AP_INIT_TAKE23("LDAPTrustedGlobalCert", util_ldap_set_trusted_global_cert,
2883                    NULL, RSRC_CONF,
2884                    "Takes three arguments; the first argument is the cert "
2885                    "type of the second argument, one of CA_DER, CA_BASE64, "
2886                    "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
2887                    "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
2888                    "specifes the file and/or directory containing the trusted CA "
2889                    "certificates (and global client certs for Netware) used to "
2890                    "validate the LDAP server. The third argument is an optional "
2891                    "passphrase if applicable."),
2892
2893     AP_INIT_TAKE23("LDAPTrustedClientCert", util_ldap_set_trusted_client_cert,
2894                    NULL, RSRC_CONF,
2895                    "Takes three arguments: the first argument is the certificate "
2896                    "type of the second argument, one of CA_DER, CA_BASE64, "
2897                    "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
2898                    "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
2899                    "specifies the file and/or directory containing the client "
2900                    "certificate, or certificate ID used to validate this LDAP "
2901                    "client.  The third argument is an optional passphrase if "
2902                    "applicable."),
2903
2904     AP_INIT_TAKE1("LDAPTrustedMode", util_ldap_set_trusted_mode,
2905                   NULL, RSRC_CONF,
2906                   "Specify the type of security that should be applied to "
2907                   "an LDAP connection. One of; NONE, SSL or STARTTLS."),
2908
2909     AP_INIT_FLAG("LDAPVerifyServerCert", util_ldap_set_verify_srv_cert,
2910                   NULL, RSRC_CONF,
2911                   "Set to 'ON' requires that the server certificate be verified"
2912                   " before a secure LDAP connection can be establish.  Default"
2913                   " 'ON'"),
2914
2915     AP_INIT_TAKE1("LDAPConnectionTimeout", util_ldap_set_connection_timeout,
2916                   NULL, RSRC_CONF,
2917                   "Specify the LDAP socket connection timeout in seconds "
2918                   "(default: 10)"),
2919
2920     AP_INIT_FLAG("LDAPReferrals", util_ldap_set_chase_referrals,
2921                   NULL, OR_AUTHCFG,
2922                   "Choose whether referrals are chased ['ON'|'OFF'].  Default 'ON'"),
2923
2924     AP_INIT_TAKE1("LDAPReferralHopLimit", util_ldap_set_referral_hop_limit,
2925                   NULL, OR_AUTHCFG,
2926                   "Limit the number of referral hops that LDAP can follow. "
2927                   "(Integer value, Consult LDAP SDK documentation for applicability and defaults"),
2928
2929     AP_INIT_TAKE1("LDAPLibraryDebug", util_ldap_set_debug_level,
2930                   NULL, RSRC_CONF,
2931                   "Enable debugging in LDAP SDK (Default: off, values: SDK specific"),
2932
2933     AP_INIT_TAKE1("LDAPTimeout", util_ldap_set_op_timeout,
2934                   NULL, RSRC_CONF,
2935                   "Specify the LDAP bind/search timeout in seconds "
2936                   "(0 = no limit). Default: 60"),
2937
2938     {NULL}
2939 };
2940
2941 static void util_ldap_register_hooks(apr_pool_t *p)
2942 {
2943     APR_REGISTER_OPTIONAL_FN(uldap_connection_open);
2944     APR_REGISTER_OPTIONAL_FN(uldap_connection_close);
2945     APR_REGISTER_OPTIONAL_FN(uldap_connection_unbind);
2946     APR_REGISTER_OPTIONAL_FN(uldap_connection_cleanup);
2947     APR_REGISTER_OPTIONAL_FN(uldap_connection_find);
2948     APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn);
2949     APR_REGISTER_OPTIONAL_FN(uldap_cache_compare);
2950     APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid);
2951     APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn);
2952     APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported);
2953     APR_REGISTER_OPTIONAL_FN(uldap_cache_check_subgroups);
2954
2955     ap_hook_pre_config(util_ldap_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
2956     ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
2957     ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
2958     ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
2959 }
2960
2961 module AP_MODULE_DECLARE_DATA ldap_module = {
2962    STANDARD20_MODULE_STUFF,
2963    util_ldap_create_dir_config, /* create dir config */
2964    NULL,                        /* merge dir config */
2965    util_ldap_create_config,     /* create server config */
2966    util_ldap_merge_config,      /* merge server config */
2967    util_ldap_cmds,              /* command table */
2968    util_ldap_register_hooks,    /* set up request processing hooks */
2969 };