]> granicus.if.org Git - apache/blob - modules/ldap/util_ldap.c
* No need for an else block here because we do a return above.
[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,
273                         APR_LDAP_OPT_VERIFY_CERT, &(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 different
714  * cache node: require group includes the DN; require user does not because the
715  * 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, &the_compare_node);
837                 if(junk == NULL) {
838                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] cache_compare: Cache insertion failure.", getpid());
839                 }
840             }
841             else {
842                 compare_nodep->lastcompare = curtime;
843                 compare_nodep->result = result;
844             }
845             LDAP_CACHE_UNLOCK();
846         }
847         if (LDAP_COMPARE_TRUE == result) {
848             ldc->reason = "Comparison true (adding to cache)";
849             return LDAP_COMPARE_TRUE;
850         }
851         else if (LDAP_COMPARE_FALSE == result) {
852             ldc->reason = "Comparison false (adding to cache)";
853             return LDAP_COMPARE_FALSE;
854         }
855         else {
856             ldc->reason = "Comparison no such attribute (adding to cache)";
857             return LDAP_NO_SUCH_ATTRIBUTE;
858         }
859     }
860     return result;
861 }
862
863
864 static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r, util_ldap_connection_t *ldc,
865                                        const char *url, const char *dn,
866                                        char **subgroupAttrs, apr_array_header_t *subgroupclasses) {
867     int failures = 0;
868     int result = LDAP_COMPARE_FALSE;
869     util_compare_subgroup_t *res = NULL;
870     LDAPMessage *sga_res, *entry;
871     struct mod_auth_ldap_groupattr_entry_t *sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
872     apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
873
874     if (!subgroupAttrs) {
875         return res;
876     }
877
878 start_over:
879     /* 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups. */
880     if (failures++ > 10) {
881         /* too many failures */
882         return res;
883     }
884
885     if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
886         /* connect failed */
887         return res;
888     }
889
890     /* try to do the search */
891     result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
892                                (char *)"cn=*", subgroupAttrs, 0,
893                                NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
894     if (result == LDAP_SERVER_DOWN) {
895         ldc->reason = "ldap_search_ext_s() for subgroups failed with server down";
896         uldap_connection_unbind(ldc);
897         goto start_over;
898     }
899
900     /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
901     if (result != LDAP_SUCCESS) {
902         ldc->reason = "ldap_search_ext_s() for subgroups failed";
903         return res;
904     }
905
906     entry = ldap_first_entry(ldc->ldap, sga_res);
907
908     /*
909      * Get values for the provided sub-group attributes.
910      */
911     if (subgroupAttrs) {
912         int indx = 0, tmp_sgcIndex;
913
914         while (subgroupAttrs[indx]) {
915             char **values;
916             int val_index = 0;
917
918             /* Get *all* matching "member" values from this group. */
919             values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
920
921             if (values) {
922                 val_index = 0;
923                 /*
924                  * Now we are going to pare the subgroup members of this group to *just*
925                  * the subgroups, add them to the compare_nodep, and then proceed to check
926                  * the new level of subgroups.
927                  */
928                 while (values[val_index]) {
929                     /* Check if this entry really is a group. */
930                     tmp_sgcIndex = 0;
931                     result = LDAP_COMPARE_FALSE;
932                     while ((tmp_sgcIndex < subgroupclasses->nelts) && (result != LDAP_COMPARE_TRUE)) {
933                         result = uldap_cache_compare(r, ldc, url, values[val_index], "objectClass",
934                                                      sgc_ents[tmp_sgcIndex].name);
935
936                         if (result != LDAP_COMPARE_TRUE) {
937                             tmp_sgcIndex++;
938                         }
939                     }
940                     /* It's a group, so add it to the array.  */
941                     if (result == LDAP_COMPARE_TRUE) {
942                         char **newgrp = (char **) apr_array_push(subgroups);
943                         *newgrp = apr_pstrdup(r->pool, values[val_index]);
944                     }
945                     val_index++;
946                 }
947                 ldap_value_free(values);
948             }
949             indx++;
950         }
951     }
952
953     ldap_msgfree(sga_res);
954
955     if (subgroups->nelts > 0) {
956         /* We need to fill in tmp_local_subgroups using the data from LDAP */
957         int sgindex;
958         char **group;
959         res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
960         res->subgroupDNs  = apr_pcalloc(r->pool, sizeof(char *) * (subgroups->nelts));
961         for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
962             res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
963         }
964         res->len = sgindex;
965     }
966
967     return res;
968 }
969
970
971 /*
972  * Does a recursive lookup operation to try to find a user within (cached) nested
973  * groups. It accepts a cache that it will use to lookup previous compare attempts.
974  * We cache two kinds of compares (require group compares) and (require user
975  * compares). Each compare has a different cache node: require group includes the DN;
976  * require user does not because the require user cache is owned by the
977  *
978  * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
979  */
980
981     /*
982      * 1. Call uldap_cache_compare for each subgroupclass value to check the generic,
983      *    user-agnostic, cached group entry. This will create a new generic cache entry if there
984      *    wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we have no groups.
985      * 2. Lock The cache and get the generic cache entry.
986      * 3. Check if there is already a subgrouplist in this generic group's cache entry.
987      *    A. If there is, go to step 4.
988      *    B. If there isn't:
989      *       i) Use ldap_search to get the full list
990      *          of subgroup "members" (which may include non-group "members").
991      *       ii) Use uldap_cache_compare to strip the list down to just groups.
992      *       iii) Lock and add this stripped down list to the cache of the generic group.
993      * 4. Loop through the sgl and call uldap_cache_compare (using the user info) for each
994      *    subgroup to see if the subgroup contains the user and to get the subgroups added to the
995      *    cache (with user-afinity, if they aren't already there).
996      *    A. If the user is in the subgroup, then we'll be returning LDAP_COMPARE_TRUE.
997      *    B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via uldap_cache_compare) then
998      *       recursively call this function to get the sub-subgroups added...
999      * 5. Cleanup local allocations.
1000      * 6. Return the final result.
1001      */
1002
1003 static int uldap_cache_check_subgroups(request_rec *r, util_ldap_connection_t *ldc,
1004                                        const char *url, const char *dn,
1005                                        const char *attrib, const char *value,
1006                                        char **subgroupAttrs, apr_array_header_t *subgroupclasses,
1007                                        int cur_subgroup_depth, int max_subgroup_depth)
1008 {
1009     int result = LDAP_COMPARE_FALSE;
1010     util_url_node_t *curl;
1011     util_url_node_t curnode;
1012     util_compare_node_t *compare_nodep;
1013     util_compare_node_t the_compare_node;
1014     util_compare_subgroup_t *tmp_local_sgl = NULL;
1015     int lcl_sgl_processedFlag = 0, failures = 0,  sgindex = 0, base_sgcIndex = 0;
1016     struct mod_auth_ldap_groupattr_entry_t *sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
1017
1018     util_ldap_state_t *st = (util_ldap_state_t *)
1019                             ap_get_module_config(r->server->module_config,
1020                                                  &ldap_module);
1021
1022     /* Stop looking at deeper levels of nested groups if we have reached the max.
1023      * Since we already checked the top-level group in uldap_cache_compare, we don't
1024      * need to check it again here - so if max_subgroup_depth is set to 0, we won't
1025      * check it (i.e. that is why we check < rather than <=).
1026      * We'll be calling uldap_cache_compare from here to check if the user is in the
1027      * next level before we recurse into that next level looking for more subgroups.
1028      */
1029     if (cur_subgroup_depth >= max_subgroup_depth) {
1030         return LDAP_COMPARE_FALSE;
1031     }
1032
1033     /* 1. Check the "groupiness" of the specified basedn. Stopping at the first TRUE return. */
1034     while ((base_sgcIndex < subgroupclasses->nelts) && (result != LDAP_COMPARE_TRUE)) {
1035         result = uldap_cache_compare(r, ldc, url, dn, "objectClass", sgc_ents[base_sgcIndex].name);
1036         if (result != LDAP_COMPARE_TRUE) {
1037             base_sgcIndex++;
1038         }
1039     }
1040
1041     if (result != LDAP_COMPARE_TRUE) {
1042         ldc->reason = "DN failed group verification.";
1043         return result;
1044     }
1045
1046     /* 2. Find previously created cache entry and check if there is already a subgrouplist. */
1047     LDAP_CACHE_LOCK();
1048     curnode.url = url;
1049     curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
1050     LDAP_CACHE_UNLOCK();
1051
1052     if (curl && curl->compare_cache) {
1053         /* make a comparison to the cache */
1054         LDAP_CACHE_LOCK();
1055
1056         the_compare_node.dn = (char *)dn;
1057         the_compare_node.attrib = (char *)"objectClass";
1058         the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1059         the_compare_node.result = 0;
1060         the_compare_node.sgl_processed = 0;
1061         the_compare_node.subgroupList = NULL;
1062
1063         compare_nodep = util_ald_cache_fetch(curl->compare_cache, &the_compare_node);
1064
1065         if (compare_nodep != NULL) {
1066             /* Found the generic group entry... but the user isn't in this group or we wouldn't be here. */
1067             lcl_sgl_processedFlag = compare_nodep->sgl_processed;
1068             if(compare_nodep->sgl_processed && compare_nodep->subgroupList) {
1069                 /* Make a local copy of the subgroup list */
1070                 int i;
1071                 tmp_local_sgl = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
1072                 tmp_local_sgl->len = compare_nodep->subgroupList->len;
1073                 tmp_local_sgl->subgroupDNs = apr_pcalloc(r->pool, sizeof(char *) * compare_nodep->subgroupList->len);
1074                 for (i = 0; i < compare_nodep->subgroupList->len; i++) {
1075                     tmp_local_sgl->subgroupDNs[i] = apr_pstrdup(r->pool, compare_nodep->subgroupList->subgroupDNs[i]);
1076                 }
1077             }
1078         }
1079         LDAP_CACHE_UNLOCK();
1080     }
1081     else {
1082           /* If we get here, something is wrong. Caches should have been created and
1083              this group entry should be found in the cache. */
1084         ldc->reason = "check_subgroups failed to find any caches.";
1085         return LDAP_COMPARE_FALSE;
1086     }
1087
1088     result = LDAP_COMPARE_FALSE;
1089
1090     if ((lcl_sgl_processedFlag == 0) && (!tmp_local_sgl)) {
1091         /* No Cached SGL, retrieve from LDAP */
1092         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] util_ldap: no cached SGL for %s, retrieving from LDAP" , getpid(), dn);
1093         tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs, subgroupclasses);
1094         if (!tmp_local_sgl) {
1095             /* No SGL aailable via LDAP either */
1096             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] util_ldap: no subgroups for %s" , getpid(), dn);
1097         }
1098         lcl_sgl_processedFlag = 1;
1099
1100         /* Find the generic group cache entry and add the sgl we just retrieved. */
1101         LDAP_CACHE_LOCK();
1102
1103         the_compare_node.dn = (char *)dn;
1104         the_compare_node.attrib = (char *)"objectClass";
1105         the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
1106         the_compare_node.result = 0;
1107         the_compare_node.sgl_processed = 0;
1108         the_compare_node.subgroupList = NULL;
1109
1110         compare_nodep = util_ald_cache_fetch(curl->compare_cache, &the_compare_node);
1111
1112         if (compare_nodep == NULL) {
1113             /* Didn't find it. This shouldn't happen since we just called uldap_cache_compare. */
1114             LDAP_CACHE_UNLOCK();
1115             ldc->reason = "check_subgroups failed to find the cache entry to add sub-group list to.";
1116             return LDAP_COMPARE_FALSE;
1117         }
1118         /*
1119          * overwrite SGL if it was previously updated between the last
1120          * two times we looked at the cache
1121          */
1122         compare_nodep->sgl_processed = 1;
1123         if (tmp_local_sgl) {
1124             compare_nodep->subgroupList = util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
1125         }
1126         else {
1127             /* We didn't find a single subgroup, next time save us from looking */
1128             compare_nodep->subgroupList = NULL;
1129         }
1130         LDAP_CACHE_UNLOCK();
1131     }
1132
1133     /* tmp_local_sgl has either been created, or copied out of the cache */
1134     /* If tmp_local_sgl is NULL, there are no subgroups to process and we'll return false */
1135     result = LDAP_COMPARE_FALSE;
1136     if (!tmp_local_sgl) {
1137         return result;
1138     }
1139
1140     while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
1141         const char *group = NULL;
1142         group = tmp_local_sgl->subgroupDNs[sgindex];
1143         /* 4. Now loop through the subgroupList and call uldap_cache_compare to check for the user. */
1144         result = uldap_cache_compare(r, ldc, url, group, attrib, value);
1145         if (result == LDAP_COMPARE_TRUE) {
1146             /* 4.A. We found the user in the subgroup. Return LDAP_COMPARE_TRUE. */
1147             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] util_ldap:"
1148                           " Found user %s in a subgroup (%s) at level %d of %d.",
1149                           getpid(), r->user, group, cur_subgroup_depth+1, max_subgroup_depth);
1150         }
1151         else {
1152             /* 4.B. We didn't find the user in this subgroup, so recurse into it and keep looking. */
1153             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] util_ldap:"
1154                           " user %s not found in subgroup (%s) at level %d of %d.",
1155                           getpid(), r->user, group, cur_subgroup_depth+1, max_subgroup_depth);
1156             result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
1157                                                  value, subgroupAttrs, subgroupclasses,
1158                                                  cur_subgroup_depth+1, max_subgroup_depth);
1159         }
1160         sgindex++;
1161     }
1162
1163     return result;
1164 }
1165
1166
1167 static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
1168                                    const char *url, const char *basedn,
1169                                    int scope, char **attrs, const char *filter,
1170                                    const char *bindpw, const char **binddn,
1171                                    const char ***retvals)
1172 {
1173     const char **vals = NULL;
1174     int numvals = 0;
1175     int result = 0;
1176     LDAPMessage *res, *entry;
1177     char *dn;
1178     int count;
1179     int failures = 0;
1180     util_url_node_t *curl;              /* Cached URL node */
1181     util_url_node_t curnode;
1182     util_search_node_t *search_nodep;   /* Cached search node */
1183     util_search_node_t the_search_node;
1184     apr_time_t curtime;
1185
1186     util_ldap_state_t *st =
1187         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1188         &ldap_module);
1189
1190     /* Get the cache node for this url */
1191     LDAP_CACHE_LOCK();
1192     curnode.url = url;
1193     curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1194                                                    &curnode);
1195     if (curl == NULL) {
1196         curl = util_ald_create_caches(st, url);
1197     }
1198     LDAP_CACHE_UNLOCK();
1199
1200     if (curl) {
1201         LDAP_CACHE_LOCK();
1202         the_search_node.username = filter;
1203         search_nodep = util_ald_cache_fetch(curl->search_cache,
1204                                             &the_search_node);
1205         if (search_nodep != NULL) {
1206
1207             /* found entry in search cache... */
1208             curtime = apr_time_now();
1209
1210             /*
1211              * Remove this item from the cache if its expired. If the sent
1212              * password doesn't match the storepassword, the entry will
1213              * be removed and readded later if the credentials pass
1214              * authentication.
1215              */
1216             if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1217                 /* ...but entry is too old */
1218                 util_ald_cache_remove(curl->search_cache, search_nodep);
1219             }
1220             else if (   (search_nodep->bindpw)
1221                      && (search_nodep->bindpw[0] != '\0')
1222                      && (strcmp(search_nodep->bindpw, bindpw) == 0))
1223             {
1224                 /* ...and entry is valid */
1225                 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1226                 if (attrs) {
1227                     int i = 0, k = 0;
1228                     while (attrs[k++]);
1229                     *retvals = apr_pcalloc(r->pool, sizeof(char *) * k);
1230                     while (search_nodep->vals[i]) {
1231                         (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1232                         i++;
1233                     }
1234                 }
1235                 LDAP_CACHE_UNLOCK();
1236                 ldc->reason = "Authentication successful (cached)";
1237                 return LDAP_SUCCESS;
1238             }
1239         }
1240         /* unlock this read lock */
1241         LDAP_CACHE_UNLOCK();
1242     }
1243
1244     /*
1245      * At this point, there is no valid cached search, so lets do the search.
1246      */
1247
1248     /*
1249      * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1250      */
1251 start_over:
1252     if (failures++ > 10) {
1253         return result;
1254     }
1255     if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1256         return result;
1257     }
1258
1259     /* try do the search */
1260     if ((result = ldap_search_ext_s(ldc->ldap,
1261                                     (char *)basedn, scope,
1262                                     (char *)filter, attrs, 0,
1263                                     NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &res))
1264             == LDAP_SERVER_DOWN)
1265     {
1266         ldc->reason = "ldap_search_ext_s() for user failed with server down";
1267         uldap_connection_unbind(ldc);
1268         goto start_over;
1269     }
1270
1271     /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1272     if (result != LDAP_SUCCESS) {
1273         ldc->reason = "ldap_search_ext_s() for user failed";
1274         return result;
1275     }
1276
1277     /*
1278      * We should have found exactly one entry; to find a different
1279      * number is an error.
1280      */
1281     count = ldap_count_entries(ldc->ldap, res);
1282     if (count != 1)
1283     {
1284         if (count == 0 )
1285             ldc->reason = "User not found";
1286         else
1287             ldc->reason = "User is not unique (search found two "
1288                           "or more matches)";
1289         ldap_msgfree(res);
1290         return LDAP_NO_SUCH_OBJECT;
1291     }
1292
1293     entry = ldap_first_entry(ldc->ldap, res);
1294
1295     /* Grab the dn, copy it into the pool, and free it again */
1296     dn = ldap_get_dn(ldc->ldap, entry);
1297     *binddn = apr_pstrdup(r->pool, dn);
1298     ldap_memfree(dn);
1299
1300     /*
1301      * A bind to the server with an empty password always succeeds, so
1302      * we check to ensure that the password is not empty. This implies
1303      * that users who actually do have empty passwords will never be
1304      * able to authenticate with this module. I don't see this as a big
1305      * problem.
1306      */
1307     if (!bindpw || strlen(bindpw) <= 0) {
1308         ldap_msgfree(res);
1309         ldc->reason = "Empty password not allowed";
1310         return LDAP_INVALID_CREDENTIALS;
1311     }
1312
1313     /*
1314      * Attempt to bind with the retrieved dn and the password. If the bind
1315      * fails, it means that the password is wrong (the dn obviously
1316      * exists, since we just retrieved it)
1317      */
1318     if ((result = ldap_simple_bind_s(ldc->ldap,
1319                                      (char *)*binddn,
1320                                      (char *)bindpw)) == LDAP_SERVER_DOWN) {
1321         ldc->reason = "ldap_simple_bind_s() to check user credentials "
1322                       "failed with server down";
1323         ldap_msgfree(res);
1324         uldap_connection_unbind(ldc);
1325         goto start_over;
1326     }
1327
1328     /* failure? if so - return */
1329     if (result != LDAP_SUCCESS) {
1330         ldc->reason = "ldap_simple_bind_s() to check user credentials failed";
1331         ldap_msgfree(res);
1332         uldap_connection_unbind(ldc);
1333         return result;
1334     }
1335     else {
1336         /*
1337          * We have just bound the connection to a different user and password
1338          * combination, which might be reused unintentionally next time this
1339          * connection is used from the connection pool. To ensure no confusion,
1340          * we mark the connection as unbound.
1341          */
1342         ldc->bound = 0;
1343     }
1344
1345     /*
1346      * Get values for the provided attributes.
1347      */
1348     if (attrs) {
1349         int k = 0;
1350         int i = 0;
1351         while (attrs[k++]);
1352         vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1353         numvals = k;
1354         while (attrs[i]) {
1355             char **values;
1356             int j = 0;
1357             char *str = NULL;
1358             /* get values */
1359             values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1360             while (values && values[j]) {
1361                 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1362                           : apr_pstrdup(r->pool, values[j]);
1363                 j++;
1364             }
1365             ldap_value_free(values);
1366             vals[i] = str;
1367             i++;
1368         }
1369         *retvals = vals;
1370     }
1371
1372     /*
1373      * Add the new username to the search cache.
1374      */
1375     if (curl) {
1376         LDAP_CACHE_LOCK();
1377         the_search_node.username = filter;
1378         the_search_node.dn = *binddn;
1379         the_search_node.bindpw = bindpw;
1380         the_search_node.lastbind = apr_time_now();
1381         the_search_node.vals = vals;
1382         the_search_node.numvals = numvals;
1383
1384         /* Search again to make sure that another thread didn't ready insert
1385          * this node into the cache before we got here. If it does exist then
1386          * update the lastbind
1387          */
1388         search_nodep = util_ald_cache_fetch(curl->search_cache,
1389                                             &the_search_node);
1390         if ((search_nodep == NULL) ||
1391             (strcmp(*binddn, search_nodep->dn) != 0)) {
1392
1393             /* Nothing in cache, insert new entry */
1394             util_ald_cache_insert(curl->search_cache, &the_search_node);
1395         }
1396         else if ((!search_nodep->bindpw) ||
1397             (strcmp(bindpw, search_nodep->bindpw) != 0)) {
1398
1399             /* Entry in cache is invalid, remove it and insert new one */
1400             util_ald_cache_remove(curl->search_cache, search_nodep);
1401             util_ald_cache_insert(curl->search_cache, &the_search_node);
1402         }
1403         else {
1404             /* Cache entry is valid, update lastbind */
1405             search_nodep->lastbind = the_search_node.lastbind;
1406         }
1407         LDAP_CACHE_UNLOCK();
1408     }
1409     ldap_msgfree(res);
1410
1411     ldc->reason = "Authentication successful";
1412     return LDAP_SUCCESS;
1413 }
1414
1415 /*
1416  * This function will return the DN of the entry matching userid.
1417  * It is used to get the DN in case some other module than mod_auth_ldap
1418  * has authenticated the user.
1419  * The function is basically a copy of uldap_cache_checkuserid
1420  * with password checking removed.
1421  */
1422 static int uldap_cache_getuserdn(request_rec *r, util_ldap_connection_t *ldc,
1423                                  const char *url, const char *basedn,
1424                                  int scope, char **attrs, const char *filter,
1425                                  const char **binddn, const char ***retvals)
1426 {
1427     const char **vals = NULL;
1428     int numvals = 0;
1429     int result = 0;
1430     LDAPMessage *res, *entry;
1431     char *dn;
1432     int count;
1433     int failures = 0;
1434     util_url_node_t *curl;              /* Cached URL node */
1435     util_url_node_t curnode;
1436     util_search_node_t *search_nodep;   /* Cached search node */
1437     util_search_node_t the_search_node;
1438     apr_time_t curtime;
1439
1440     util_ldap_state_t *st =
1441         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
1442         &ldap_module);
1443
1444     /* Get the cache node for this url */
1445     LDAP_CACHE_LOCK();
1446     curnode.url = url;
1447     curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
1448                                                    &curnode);
1449     if (curl == NULL) {
1450         curl = util_ald_create_caches(st, url);
1451     }
1452     LDAP_CACHE_UNLOCK();
1453
1454     if (curl) {
1455         LDAP_CACHE_LOCK();
1456         the_search_node.username = filter;
1457         search_nodep = util_ald_cache_fetch(curl->search_cache,
1458                                             &the_search_node);
1459         if (search_nodep != NULL) {
1460
1461             /* found entry in search cache... */
1462             curtime = apr_time_now();
1463
1464             /*
1465              * Remove this item from the cache if its expired.
1466              */
1467             if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
1468                 /* ...but entry is too old */
1469                 util_ald_cache_remove(curl->search_cache, search_nodep);
1470             }
1471             else {
1472                 /* ...and entry is valid */
1473                 *binddn = apr_pstrdup(r->pool, search_nodep->dn);
1474                 if (attrs) {
1475                     int i = 0, k = 0;
1476                     while (attrs[k++]);
1477                     *retvals = apr_pcalloc(r->pool, sizeof(char *) * k);
1478                     while (search_nodep->vals[i]) {
1479                         (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
1480                         i++;
1481                     }
1482                 }
1483                 LDAP_CACHE_UNLOCK();
1484                 ldc->reason = "Search successful (cached)";
1485                 return LDAP_SUCCESS;
1486             }
1487         }
1488         /* unlock this read lock */
1489         LDAP_CACHE_UNLOCK();
1490     }
1491
1492     /*
1493      * At this point, there is no valid cached search, so lets do the search.
1494      */
1495
1496     /*
1497      * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
1498      */
1499 start_over:
1500     if (failures++ > 10) {
1501         return result;
1502     }
1503     if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
1504         return result;
1505     }
1506
1507     /* try do the search */
1508     if ((result = ldap_search_ext_s(ldc->ldap,
1509                                     (char *)basedn, scope,
1510                                     (char *)filter, attrs, 0,
1511                                     NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &res))
1512             == LDAP_SERVER_DOWN)
1513     {
1514         ldc->reason = "ldap_search_ext_s() for user failed with server down";
1515         uldap_connection_unbind(ldc);
1516         goto start_over;
1517     }
1518
1519     /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
1520     if (result != LDAP_SUCCESS) {
1521         ldc->reason = "ldap_search_ext_s() for user failed";
1522         return result;
1523     }
1524
1525     /*
1526      * We should have found exactly one entry; to find a different
1527      * number is an error.
1528      */
1529     count = ldap_count_entries(ldc->ldap, res);
1530     if (count != 1)
1531     {
1532         if (count == 0 )
1533             ldc->reason = "User not found";
1534         else
1535             ldc->reason = "User is not unique (search found two "
1536                           "or more matches)";
1537         ldap_msgfree(res);
1538         return LDAP_NO_SUCH_OBJECT;
1539     }
1540
1541     entry = ldap_first_entry(ldc->ldap, res);
1542
1543     /* Grab the dn, copy it into the pool, and free it again */
1544     dn = ldap_get_dn(ldc->ldap, entry);
1545     *binddn = apr_pstrdup(r->pool, dn);
1546     ldap_memfree(dn);
1547
1548     /*
1549      * Get values for the provided attributes.
1550      */
1551     if (attrs) {
1552         int k = 0;
1553         int i = 0;
1554         while (attrs[k++]);
1555         vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
1556         numvals = k;
1557         while (attrs[i]) {
1558             char **values;
1559             int j = 0;
1560             char *str = NULL;
1561             /* get values */
1562             values = ldap_get_values(ldc->ldap, entry, attrs[i]);
1563             while (values && values[j]) {
1564                 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
1565                           : apr_pstrdup(r->pool, values[j]);
1566                 j++;
1567             }
1568             ldap_value_free(values);
1569             vals[i] = str;
1570             i++;
1571         }
1572         *retvals = vals;
1573     }
1574
1575     /*
1576      * Add the new username to the search cache.
1577      */
1578     if (curl) {
1579         LDAP_CACHE_LOCK();
1580         the_search_node.username = filter;
1581         the_search_node.dn = *binddn;
1582         the_search_node.bindpw = NULL;
1583         the_search_node.lastbind = apr_time_now();
1584         the_search_node.vals = vals;
1585         the_search_node.numvals = numvals;
1586
1587         /* Search again to make sure that another thread didn't ready insert
1588          * this node into the cache before we got here. If it does exist then
1589          * update the lastbind
1590          */
1591         search_nodep = util_ald_cache_fetch(curl->search_cache,
1592                                             &the_search_node);
1593         if ((search_nodep == NULL) ||
1594             (strcmp(*binddn, search_nodep->dn) != 0)) {
1595
1596             /* Nothing in cache, insert new entry */
1597             util_ald_cache_insert(curl->search_cache, &the_search_node);
1598         }
1599         /*
1600          * Don't update lastbind on entries with bindpw because
1601          * we haven't verified that password. It's OK to update
1602          * the entry if there is no password in it.
1603          */
1604         else if (!search_nodep->bindpw) {
1605             /* Cache entry is valid, update lastbind */
1606             search_nodep->lastbind = the_search_node.lastbind;
1607         }
1608         LDAP_CACHE_UNLOCK();
1609     }
1610
1611     ldap_msgfree(res);
1612
1613     ldc->reason = "Search successful";
1614     return LDAP_SUCCESS;
1615 }
1616
1617 /*
1618  * Reports if ssl support is enabled
1619  *
1620  * 1 = enabled, 0 = not enabled
1621  */
1622 static int uldap_ssl_supported(request_rec *r)
1623 {
1624    util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
1625                                 r->server->module_config, &ldap_module);
1626
1627    return(st->ssl_supported);
1628 }
1629
1630
1631 /* ---------------------------------------- */
1632 /* config directives */
1633
1634
1635 static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy,
1636                                              const char *bytes)
1637 {
1638     util_ldap_state_t *st =
1639         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1640                                                   &ldap_module);
1641     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1642
1643     if (err != NULL) {
1644         return err;
1645     }
1646
1647     st->cache_bytes = atol(bytes);
1648
1649     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1650                  "[%" APR_PID_T_FMT "] ldap cache: Setting shared memory "
1651                  " cache size to %" APR_SIZE_T_FMT " bytes.",
1652                  getpid(), st->cache_bytes);
1653
1654     return NULL;
1655 }
1656
1657 static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy,
1658                                             const char *file)
1659 {
1660     util_ldap_state_t *st =
1661         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1662                                                   &ldap_module);
1663     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1664
1665     if (err != NULL) {
1666         return err;
1667     }
1668
1669     if (file) {
1670         st->cache_file = ap_server_root_relative(st->pool, file);
1671     }
1672     else {
1673         st->cache_file = NULL;
1674     }
1675
1676     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1677                  "LDAP cache: Setting shared memory cache file to %s bytes.",
1678                  st->cache_file);
1679
1680     return NULL;
1681 }
1682
1683 static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy,
1684                                            const char *ttl)
1685 {
1686     util_ldap_state_t *st =
1687         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1688                                                   &ldap_module);
1689     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1690
1691     if (err != NULL) {
1692         return err;
1693     }
1694
1695     st->search_cache_ttl = atol(ttl) * 1000000;
1696
1697     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1698                  "[%" APR_PID_T_FMT "] ldap cache: Setting cache TTL to %ld microseconds.",
1699                  getpid(), st->search_cache_ttl);
1700
1701     return NULL;
1702 }
1703
1704 static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy,
1705                                                const char *size)
1706 {
1707     util_ldap_state_t *st =
1708         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1709                                                   &ldap_module);
1710     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1711
1712     if (err != NULL) {
1713         return err;
1714     }
1715
1716     st->search_cache_size = atol(size);
1717     if (st->search_cache_size < 0) {
1718         st->search_cache_size = 0;
1719     }
1720
1721     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1722                  "[%" APR_PID_T_FMT "] ldap cache: Setting search cache size to %ld entries.",
1723                  getpid(), st->search_cache_size);
1724
1725     return NULL;
1726 }
1727
1728 static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy,
1729                                              const char *ttl)
1730 {
1731     util_ldap_state_t *st =
1732         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1733                                                   &ldap_module);
1734     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1735
1736     if (err != NULL) {
1737         return err;
1738     }
1739
1740     st->compare_cache_ttl = atol(ttl) * 1000000;
1741
1742     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1743                  "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache TTL to %ld microseconds.",
1744                  getpid(), st->compare_cache_ttl);
1745
1746     return NULL;
1747 }
1748
1749 static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy,
1750                                                  const char *size)
1751 {
1752     util_ldap_state_t *st =
1753         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1754                                                   &ldap_module);
1755     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1756
1757     if (err != NULL) {
1758         return err;
1759     }
1760
1761     st->compare_cache_size = atol(size);
1762     if (st->compare_cache_size < 0) {
1763         st->compare_cache_size = 0;
1764     }
1765
1766     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1767                  "[%" APR_PID_T_FMT "] ldap cache: Setting operation cache size to %ld "
1768                  "entries.", getpid(), st->compare_cache_size);
1769
1770     return NULL;
1771 }
1772
1773
1774 /**
1775  * Parse the certificate type.
1776  *
1777  * The type can be one of the following:
1778  * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64,
1779  * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64
1780  *
1781  * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned.
1782  */
1783 static int util_ldap_parse_cert_type(const char *type)
1784 {
1785     /* Authority file in binary DER format */
1786     if (0 == strcasecmp("CA_DER", type)) {
1787         return APR_LDAP_CA_TYPE_DER;
1788     }
1789
1790     /* Authority file in Base64 format */
1791     else if (0 == strcasecmp("CA_BASE64", type)) {
1792         return APR_LDAP_CA_TYPE_BASE64;
1793     }
1794
1795     /* Netscape certificate database file/directory */
1796     else if (0 == strcasecmp("CA_CERT7_DB", type)) {
1797         return APR_LDAP_CA_TYPE_CERT7_DB;
1798     }
1799
1800     /* Netscape secmod file/directory */
1801     else if (0 == strcasecmp("CA_SECMOD", type)) {
1802         return APR_LDAP_CA_TYPE_SECMOD;
1803     }
1804
1805     /* Client cert file in DER format */
1806     else if (0 == strcasecmp("CERT_DER", type)) {
1807         return APR_LDAP_CERT_TYPE_DER;
1808     }
1809
1810     /* Client cert file in Base64 format */
1811     else if (0 == strcasecmp("CERT_BASE64", type)) {
1812         return APR_LDAP_CERT_TYPE_BASE64;
1813     }
1814
1815     /* Client cert file in PKCS#12 format */
1816     else if (0 == strcasecmp("CERT_PFX", type)) {
1817         return APR_LDAP_CERT_TYPE_PFX;
1818     }
1819
1820     /* Netscape client cert database file/directory */
1821     else if (0 == strcasecmp("CERT_KEY3_DB", type)) {
1822         return APR_LDAP_CERT_TYPE_KEY3_DB;
1823     }
1824
1825     /* Netscape client cert nickname */
1826     else if (0 == strcasecmp("CERT_NICKNAME", type)) {
1827         return APR_LDAP_CERT_TYPE_NICKNAME;
1828     }
1829
1830     /* Client cert key file in DER format */
1831     else if (0 == strcasecmp("KEY_DER", type)) {
1832         return APR_LDAP_KEY_TYPE_DER;
1833     }
1834
1835     /* Client cert key file in Base64 format */
1836     else if (0 == strcasecmp("KEY_BASE64", type)) {
1837         return APR_LDAP_KEY_TYPE_BASE64;
1838     }
1839
1840     /* Client cert key file in PKCS#12 format */
1841     else if (0 == strcasecmp("KEY_PFX", type)) {
1842         return APR_LDAP_KEY_TYPE_PFX;
1843     }
1844
1845     else {
1846         return APR_LDAP_CA_TYPE_UNKNOWN;
1847     }
1848
1849 }
1850
1851
1852 /**
1853  * Set LDAPTrustedGlobalCert.
1854  *
1855  * This directive takes either two or three arguments:
1856  * - certificate type
1857  * - certificate file / directory / nickname
1858  * - certificate password (optional)
1859  *
1860  * This directive may only be used globally.
1861  */
1862 static const char *util_ldap_set_trusted_global_cert(cmd_parms *cmd,
1863                                                      void *dummy,
1864                                                      const char *type,
1865                                                      const char *file,
1866                                                      const char *password)
1867 {
1868     util_ldap_state_t *st =
1869         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1870                                                   &ldap_module);
1871     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1872     apr_finfo_t finfo;
1873     apr_status_t rv;
1874     int cert_type = 0;
1875     apr_ldap_opt_tls_cert_t *cert;
1876
1877     if (err != NULL) {
1878         return err;
1879     }
1880
1881     /* handle the certificate type */
1882     if (type) {
1883         cert_type = util_ldap_parse_cert_type(type);
1884         if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
1885            return apr_psprintf(cmd->pool, "The certificate type %s is "
1886                                           "not recognised. It should be one "
1887                                           "of CA_DER, CA_BASE64, CA_CERT7_DB, "
1888                                           "CA_SECMOD, CERT_DER, CERT_BASE64, "
1889                                           "CERT_KEY3_DB, CERT_NICKNAME, "
1890                                           "KEY_DER, KEY_BASE64", type);
1891         }
1892     }
1893     else {
1894         return "Certificate type was not specified.";
1895     }
1896
1897     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1898                       "LDAP: SSL trusted global cert - %s (type %s)",
1899                        file, type);
1900
1901     /* add the certificate to the global array */
1902     cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
1903     cert->type = cert_type;
1904     cert->path = file;
1905     cert->password = password;
1906
1907     /* if file is a file or path, fix the path */
1908     if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
1909         cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
1910
1911         cert->path = ap_server_root_relative(cmd->pool, file);
1912         if (cert->path &&
1913             ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
1914                 != APR_SUCCESS))
1915         {
1916             ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
1917                          "LDAP: Could not open SSL trusted certificate "
1918                          "authority file - %s",
1919                          cert->path == NULL ? file : cert->path);
1920             return "Invalid global certificate file path";
1921         }
1922     }
1923
1924     return(NULL);
1925 }
1926
1927
1928 /**
1929  * Set LDAPTrustedClientCert.
1930  *
1931  * This directive takes either two or three arguments:
1932  * - certificate type
1933  * - certificate file / directory / nickname
1934  * - certificate password (optional)
1935  */
1936 static const char *util_ldap_set_trusted_client_cert(cmd_parms *cmd,
1937                                                      void *config,
1938                                                      const char *type,
1939                                                      const char *file,
1940                                                      const char *password)
1941 {
1942     util_ldap_state_t *st =
1943         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
1944                                                   &ldap_module);
1945     apr_finfo_t finfo;
1946     apr_status_t rv;
1947     int cert_type = 0;
1948     apr_ldap_opt_tls_cert_t *cert;
1949
1950     /* handle the certificate type */
1951     if (type) {
1952         cert_type = util_ldap_parse_cert_type(type);
1953         if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
1954             return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
1955                                            "not recognised. It should be one "
1956                                            "of CERT_DER, CERT_BASE64, "
1957                                            "CERT_NICKNAME, CERT_PFX,"
1958                                            "KEY_DER, KEY_BASE64, KEY_PFX",
1959                                            type);
1960         }
1961         else if (APR_LDAP_CA_TYPE_DER == cert_type ||
1962                  APR_LDAP_CA_TYPE_BASE64 == cert_type ||
1963                  APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
1964                  APR_LDAP_CA_TYPE_SECMOD == cert_type ||
1965                  APR_LDAP_CERT_TYPE_PFX == cert_type ||
1966                  APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
1967             return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
1968                                            "only valid within a "
1969                                            "LDAPTrustedGlobalCert directive. "
1970                                            "Only CERT_DER, CERT_BASE64, "
1971                                            "CERT_NICKNAME, KEY_DER, and "
1972                                            "KEY_BASE64 may be used.", type);
1973         }
1974     }
1975     else {
1976         return "Certificate type was not specified.";
1977     }
1978
1979     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
1980                       "LDAP: SSL trusted client cert - %s (type %s)",
1981                        file, type);
1982
1983     /* add the certificate to the global array */
1984     cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
1985     cert->type = cert_type;
1986     cert->path = file;
1987     cert->password = password;
1988
1989     /* if file is a file or path, fix the path */
1990     if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
1991         cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
1992
1993         cert->path = ap_server_root_relative(cmd->pool, file);
1994         if (cert->path &&
1995             ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
1996                 != APR_SUCCESS))
1997         {
1998             ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server,
1999                          "LDAP: Could not open SSL client certificate "
2000                          "file - %s",
2001                          cert->path == NULL ? file : cert->path);
2002             return "Invalid client certificate file path";
2003         }
2004
2005     }
2006
2007     return(NULL);
2008 }
2009
2010
2011 /**
2012  * Set LDAPTrustedMode.
2013  *
2014  * This directive sets what encryption mode to use on a connection:
2015  * - None (No encryption)
2016  * - SSL (SSL encryption)
2017  * - STARTTLS (TLS encryption)
2018  */
2019 static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
2020                                               const char *mode)
2021 {
2022     util_ldap_state_t *st =
2023     (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2024                                               &ldap_module);
2025
2026     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2027                       "LDAP: SSL trusted mode - %s",
2028                        mode);
2029
2030     if (0 == strcasecmp("NONE", mode)) {
2031         st->secure = APR_LDAP_NONE;
2032     }
2033     else if (0 == strcasecmp("SSL", mode)) {
2034         st->secure = APR_LDAP_SSL;
2035     }
2036     else if (   (0 == strcasecmp("TLS", mode))
2037              || (0 == strcasecmp("STARTTLS", mode))) {
2038         st->secure = APR_LDAP_STARTTLS;
2039     }
2040     else {
2041         return "Invalid LDAPTrustedMode setting: must be one of NONE, "
2042                "SSL, or TLS/STARTTLS";
2043     }
2044
2045     st->secure_set = 1;
2046     return(NULL);
2047 }
2048
2049 static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
2050                                                  void *dummy,
2051                                                  int mode)
2052 {
2053     util_ldap_state_t *st =
2054     (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2055                                               &ldap_module);
2056     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2057
2058     if (err != NULL) {
2059         return err;
2060     }
2061
2062     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2063                       "LDAP: SSL verify server certificate - %s",
2064                       mode?"TRUE":"FALSE");
2065
2066     st->verify_svr_cert = mode;
2067
2068     return(NULL);
2069 }
2070
2071
2072 static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
2073                                                     void *dummy,
2074                                                     const char *ttl)
2075 {
2076     util_ldap_state_t *st =
2077         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
2078                                                   &ldap_module);
2079     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
2080
2081     if (err != NULL) {
2082         return err;
2083     }
2084
2085 #ifdef LDAP_OPT_NETWORK_TIMEOUT
2086     st->connectionTimeout = atol(ttl);
2087
2088     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
2089                  "[%" APR_PID_T_FMT "] ldap connection: Setting connection timeout to "
2090                  "%ld seconds.", getpid(), st->connectionTimeout);
2091 #else
2092     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
2093                  "LDAP: Connection timout option not supported by the "
2094                  "LDAP SDK in use." );
2095 #endif
2096
2097     return NULL;
2098 }
2099
2100
2101 static void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
2102 {
2103     util_ldap_state_t *st =
2104         (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
2105
2106     /* Create a per vhost pool for mod_ldap to use, serialized with 
2107      * st->mutex (also one per vhost).  both are replicated by fork(),
2108      * no shared memory managed by either.
2109      */
2110     apr_pool_create(&st->pool, p);
2111 #if APR_HAS_THREADS
2112     apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
2113 #endif
2114
2115     st->cache_bytes = 100000;
2116     st->search_cache_ttl = 600000000;
2117     st->search_cache_size = 1024;
2118     st->compare_cache_ttl = 600000000;
2119     st->compare_cache_size = 1024;
2120     st->connections = NULL;
2121     st->ssl_supported = 0;
2122     st->global_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2123     st->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
2124     st->secure = APR_LDAP_NONE;
2125     st->secure_set = 0;
2126     st->connectionTimeout = 10;
2127     st->verify_svr_cert = 1;
2128
2129     return st;
2130 }
2131
2132 static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
2133                                     void *overridesv)
2134 {
2135     util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
2136     util_ldap_state_t *base = (util_ldap_state_t *) basev;
2137     util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
2138
2139     st->pool = overrides->pool;
2140 #if APR_HAS_THREADS
2141     st->mutex = overrides->mutex;
2142 #endif
2143
2144     /* The cache settings can not be modified in a 
2145         virtual host since all server use the same
2146         shared memory cache. */
2147     st->cache_bytes = base->cache_bytes;
2148     st->search_cache_ttl = base->search_cache_ttl;
2149     st->search_cache_size = base->search_cache_size;
2150     st->compare_cache_ttl = base->compare_cache_ttl;
2151     st->compare_cache_size = base->compare_cache_size;
2152     st->util_ldap_cache_lock = base->util_ldap_cache_lock; 
2153
2154     st->connections = NULL;
2155     st->ssl_supported = 0;
2156     st->global_certs = apr_array_append(p, base->global_certs,
2157                                            overrides->global_certs);
2158     st->client_certs = apr_array_append(p, base->client_certs,
2159                                            overrides->client_certs);
2160     st->secure = (overrides->secure_set == 0) ? base->secure
2161                                               : overrides->secure;
2162
2163     /* These LDAP connection settings can not be overwritten in 
2164         a virtual host. Once set in the base server, they must 
2165         remain the same. None of the LDAP SDKs seem to be able
2166         to handle setting the verify_svr_cert flag on a 
2167         per-connection basis.  The OpenLDAP client appears to be
2168         able to handle the connection timeout per-connection
2169         but the Novell SDK cannot.  Allowing the timeout to
2170         be set by each vhost is of little value so rather than
2171         trying to make special expections for one LDAP SDK, GLOBAL_ONLY 
2172         is being enforced on this setting as well. */
2173     st->connectionTimeout = base->connectionTimeout;
2174     st->verify_svr_cert = base->verify_svr_cert;
2175
2176     return st;
2177 }
2178
2179 static apr_status_t util_ldap_cleanup_module(void *data)
2180 {
2181
2182     server_rec *s = data;
2183     util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
2184         s->module_config, &ldap_module);
2185
2186     if (st->ssl_supported) {
2187         apr_ldap_ssl_deinit();
2188     }
2189
2190     return APR_SUCCESS;
2191
2192 }
2193
2194 static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog,
2195                                  apr_pool_t *ptemp, server_rec *s)
2196 {
2197     apr_status_t result;
2198     server_rec *s_vhost;
2199     util_ldap_state_t *st_vhost;
2200
2201     util_ldap_state_t *st = (util_ldap_state_t *)
2202                             ap_get_module_config(s->module_config,
2203                                                  &ldap_module);
2204
2205     void *data;
2206     const char *userdata_key = "util_ldap_init";
2207     apr_ldap_err_t *result_err = NULL;
2208     int rc;
2209
2210     /* util_ldap_post_config() will be called twice. Don't bother
2211      * going through all of the initialization on the first call
2212      * because it will just be thrown away.*/
2213     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
2214     if (!data) {
2215         apr_pool_userdata_set((const void *)1, userdata_key,
2216                                apr_pool_cleanup_null, s->process->pool);
2217
2218 #if APR_HAS_SHARED_MEMORY
2219         /* If the cache file already exists then delete it.  Otherwise we are
2220          * going to run into problems creating the shared memory. */
2221         if (st->cache_file) {
2222             char *lck_file = apr_pstrcat(ptemp, st->cache_file, ".lck",
2223                                          NULL);
2224             apr_file_remove(lck_file, ptemp);
2225         }
2226 #endif
2227         return OK;
2228     }
2229
2230 #if APR_HAS_SHARED_MEMORY
2231     /* initializing cache if shared memory size is not zero and we already
2232      * don't have shm address
2233      */
2234     if (!st->cache_shm && st->cache_bytes > 0) {
2235 #endif
2236         result = util_ldap_cache_init(p, st);
2237         if (result != APR_SUCCESS) {
2238             ap_log_error(APLOG_MARK, APLOG_ERR, result, s,
2239                          "LDAP cache: could not create shared memory segment");
2240             return DONE;
2241         }
2242
2243
2244 #if APR_HAS_SHARED_MEMORY
2245         if (st->cache_file) {
2246             st->lock_file = apr_pstrcat(st->pool, st->cache_file, ".lck",
2247                                         NULL);
2248         }
2249 #endif
2250
2251         result = apr_global_mutex_create(&st->util_ldap_cache_lock,
2252                                          st->lock_file, APR_LOCK_DEFAULT,
2253                                          st->pool);
2254         if (result != APR_SUCCESS) {
2255             return result;
2256         }
2257
2258 #ifdef AP_NEED_SET_MUTEX_PERMS
2259         result = unixd_set_global_mutex_perms(st->util_ldap_cache_lock);
2260         if (result != APR_SUCCESS) {
2261             ap_log_error(APLOG_MARK, APLOG_CRIT, result, s,
2262                          "LDAP cache: failed to set mutex permissions");
2263             return result;
2264         }
2265 #endif
2266
2267         /* merge config in all vhost */
2268         s_vhost = s->next;
2269         while (s_vhost) {
2270             st_vhost = (util_ldap_state_t *)
2271                        ap_get_module_config(s_vhost->module_config,
2272                                             &ldap_module);
2273
2274 #if APR_HAS_SHARED_MEMORY
2275             st_vhost->cache_shm = st->cache_shm;
2276             st_vhost->cache_rmm = st->cache_rmm;
2277             st_vhost->cache_file = st->cache_file;
2278             ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s,
2279                          "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
2280                          "for VHOST: %s", st->cache_shm, st->cache_rmm,
2281                          s_vhost->server_hostname);
2282 #endif
2283             st_vhost->lock_file = st->lock_file;
2284             s_vhost = s_vhost->next;
2285         }
2286 #if APR_HAS_SHARED_MEMORY
2287     }
2288     else {
2289         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
2290                      "LDAP cache: LDAPSharedCacheSize is zero, disabling "
2291                      "shared memory cache");
2292     }
2293 #endif
2294
2295     /* log the LDAP SDK used
2296      */
2297     {
2298         apr_ldap_err_t *result = NULL;
2299         apr_ldap_info(p, &(result));
2300         if (result != NULL) {
2301             ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s", result->reason);
2302         }
2303     }
2304
2305     apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
2306                               util_ldap_cleanup_module);
2307
2308     /*
2309      * Initialize SSL support, and log the result for the benefit of the admin.
2310      *
2311      * If SSL is not supported it is not necessarily an error, as the
2312      * application may not want to use it.
2313      */
2314     rc = apr_ldap_ssl_init(p,
2315                       NULL,
2316                       0,
2317                       &(result_err));
2318     if (APR_SUCCESS == rc) {
2319         rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
2320                                  (void *)st->global_certs, &(result_err));
2321     }
2322
2323     if (APR_SUCCESS == rc) {
2324         st->ssl_supported = 1;
2325         ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2326                      "LDAP: SSL support available" );
2327     }
2328     else {
2329         st->ssl_supported = 0;
2330         ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
2331                      "LDAP: SSL support unavailable%s%s",
2332                      result_err ? ": " : "",
2333                      result_err ? result_err->reason : "");
2334     }
2335
2336     return(OK);
2337 }
2338
2339 static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
2340 {
2341     apr_status_t sts;
2342     util_ldap_state_t *st = ap_get_module_config(s->module_config,
2343                                                  &ldap_module);
2344
2345     if (!st->util_ldap_cache_lock) return;
2346
2347     sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
2348                                       st->lock_file, p);
2349     if (sts != APR_SUCCESS) {
2350         ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s,
2351                      "Failed to initialise global mutex %s in child process %"
2352                      APR_PID_T_FMT ".",
2353                      st->lock_file, getpid());
2354     }
2355 }
2356
2357 static const command_rec util_ldap_cmds[] = {
2358     AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes,
2359                   NULL, RSRC_CONF,
2360                   "Set the size of the shared memory cache (in bytes). Use "
2361                   "0 to disable the shared memory cache. (default: 100000)"),
2362
2363     AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file,
2364                   NULL, RSRC_CONF,
2365                   "Set the file name for the shared memory cache."),
2366
2367     AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries,
2368                   NULL, RSRC_CONF,
2369                   "Set the maximum number of entries that are possible in the "
2370                   "LDAP search cache. Use 0 for no limit. "
2371                   "-1 disables the cache. (default: 1024)"),
2372
2373     AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl,
2374                   NULL, RSRC_CONF,
2375                   "Set the maximum time (in seconds) that an item can be "
2376                   "cached in the LDAP search cache. Use 0 for no limit. "
2377                   "(default 600)"),
2378
2379     AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries,
2380                   NULL, RSRC_CONF,
2381                   "Set the maximum number of entries that are possible "
2382                   "in the LDAP compare cache. Use 0 for no limit. "
2383                   "Use -1 to disable the cache. (default: 1024)"),
2384
2385     AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl,
2386                   NULL, RSRC_CONF,
2387                   "Set the maximum time (in seconds) that an item is cached "
2388                   "in the LDAP operation cache. Use 0 for no limit. "
2389                   "(default: 600)"),
2390
2391     AP_INIT_TAKE23("LDAPTrustedGlobalCert", util_ldap_set_trusted_global_cert,
2392                    NULL, RSRC_CONF,
2393                    "Takes three args; the file and/or directory containing "
2394                    "the trusted CA certificates (and global client certs "
2395                    "for Netware) used to validate the LDAP server.  Second "
2396                    "arg is the cert type for the first arg, one of CA_DER, "
2397                    "CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, "
2398                    "CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, or KEY_BASE64. "
2399                    "Third arg is an optional passphrase if applicable."),
2400
2401     AP_INIT_TAKE23("LDAPTrustedClientCert", util_ldap_set_trusted_client_cert,
2402                    NULL, RSRC_CONF,
2403                    "Takes three args; the file and/or directory containing "
2404                    "the client certificate, or certificate ID used to "
2405                    "validate this LDAP client.  Second arg is the cert type "
2406                    "for the first arg, one of CA_DER, CA_BASE64, CA_CERT7_DB, "
2407                    "CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
2408                    "CERT_NICKNAME, KEY_DER, or KEY_BASE64. Third arg is an "
2409                    "optional passphrase if applicable."),
2410
2411     AP_INIT_TAKE1("LDAPTrustedMode", util_ldap_set_trusted_mode,
2412                   NULL, RSRC_CONF,
2413                   "Specify the type of security that should be applied to "
2414                   "an LDAP connection. One of; NONE, SSL or STARTTLS."),
2415
2416     AP_INIT_FLAG("LDAPVerifyServerCert", util_ldap_set_verify_srv_cert,
2417                   NULL, RSRC_CONF,
2418                   "Set to 'ON' requires that the server certificate be verified "
2419                   "before a secure LDAP connection can be establish.  Default 'ON'"),
2420
2421     AP_INIT_TAKE1("LDAPConnectionTimeout", util_ldap_set_connection_timeout,
2422                   NULL, RSRC_CONF,
2423                   "Specify the LDAP socket connection timeout in seconds "
2424                   "(default: 10)"),
2425
2426     {NULL}
2427 };
2428
2429 static void util_ldap_register_hooks(apr_pool_t *p)
2430 {
2431     APR_REGISTER_OPTIONAL_FN(uldap_connection_open);
2432     APR_REGISTER_OPTIONAL_FN(uldap_connection_close);
2433     APR_REGISTER_OPTIONAL_FN(uldap_connection_unbind);
2434     APR_REGISTER_OPTIONAL_FN(uldap_connection_cleanup);
2435     APR_REGISTER_OPTIONAL_FN(uldap_connection_find);
2436     APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn);
2437     APR_REGISTER_OPTIONAL_FN(uldap_cache_compare);
2438     APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid);
2439     APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn);
2440     APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported);
2441     APR_REGISTER_OPTIONAL_FN(uldap_cache_check_subgroups);
2442
2443     ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
2444     ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
2445     ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
2446 }
2447
2448 module AP_MODULE_DECLARE_DATA ldap_module = {
2449    STANDARD20_MODULE_STUFF,
2450    NULL,                        /* create dir config */
2451    NULL,                        /* merge dir config */
2452    util_ldap_create_config,     /* create server config */
2453    util_ldap_merge_config,      /* merge server config */
2454    util_ldap_cmds,              /* command table */
2455    util_ldap_register_hooks,    /* set up request processing hooks */
2456 };