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