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