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