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