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