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