]> granicus.if.org Git - apache/blob - modules/experimental/util_ldap.c
Remove LDAP toolkit specific code from util_ldap and mod_auth_ldap.
[apache] / modules / experimental / util_ldap.c
1 /* Copyright 2001-2004 The Apache Software Foundation
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /*
17  * util_ldap.c: LDAP things
18  * 
19  * Original code from auth_ldap module for Apache v1.3:
20  * Copyright 1998, 1999 Enbridge Pipelines Inc. 
21  * Copyright 1999-2001 Dave Carrigan
22  */
23
24 #include <apr_ldap.h>
25 #include <apr_strings.h>
26
27 #include "ap_config.h"
28 #include "httpd.h"
29 #include "http_config.h"
30 #include "http_core.h"
31 #include "http_log.h"
32 #include "http_protocol.h"
33 #include "http_request.h"
34 #include "util_ldap.h"
35 #include "util_ldap_cache.h"
36
37 #if APR_HAVE_UNISTD_H
38 #include <unistd.h>
39 #endif
40
41 #if !APR_HAS_LDAP
42 #error mod_ldap requires APR-util to have LDAP support built in
43 #endif
44
45     /* defines for certificate file types
46     */
47 #define LDAP_CA_TYPE_UNKNOWN            0
48 #define LDAP_CA_TYPE_DER                1
49 #define LDAP_CA_TYPE_BASE64             2
50 #define LDAP_CA_TYPE_CERT7_DB           3
51
52
53 module AP_MODULE_DECLARE_DATA ldap_module;
54
55 int util_ldap_handler(request_rec *r);
56 void *util_ldap_create_config(apr_pool_t *p, server_rec *s);
57
58
59 /*
60  * Some definitions to help between various versions of apache.
61  */
62
63 #ifndef DOCTYPE_HTML_2_0
64 #define DOCTYPE_HTML_2_0  "<!DOCTYPE HTML PUBLIC \"-//IETF//" \
65                           "DTD HTML 2.0//EN\">\n"
66 #endif
67
68 #ifndef DOCTYPE_HTML_3_2
69 #define DOCTYPE_HTML_3_2  "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
70                           "DTD HTML 3.2 Final//EN\">\n"
71 #endif
72
73 #ifndef DOCTYPE_HTML_4_0S
74 #define DOCTYPE_HTML_4_0S "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
75                           "DTD HTML 4.0//EN\"\n" \
76                           "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
77 #endif
78
79 #ifndef DOCTYPE_HTML_4_0T
80 #define DOCTYPE_HTML_4_0T "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
81                           "DTD HTML 4.0 Transitional//EN\"\n" \
82                           "\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n"
83 #endif
84
85 #ifndef DOCTYPE_HTML_4_0F
86 #define DOCTYPE_HTML_4_0F "<!DOCTYPE HTML PUBLIC \"-//W3C//" \
87                           "DTD HTML 4.0 Frameset//EN\"\n" \
88                           "\"http://www.w3.org/TR/REC-html40/frameset.dtd\">\n"
89 #endif
90
91 #define LDAP_CACHE_LOCK() \
92     apr_global_mutex_lock(st->util_ldap_cache_lock)
93 #define LDAP_CACHE_UNLOCK() \
94     apr_global_mutex_unlock(st->util_ldap_cache_lock)
95
96
97 static void util_ldap_strdup (char **str, const char *newstr)
98 {
99     if (*str) {
100         free(*str);
101         *str = NULL;
102     }
103
104     if (newstr) {
105         *str = calloc(1, strlen(newstr)+1);
106         strcpy (*str, newstr);
107     }
108 }
109
110 /*
111  * Status Handler
112  * --------------
113  *
114  * This handler generates a status page about the current performance of
115  * the LDAP cache. It is enabled as follows:
116  *
117  * <Location /ldap-status>
118  *   SetHandler ldap-status
119  * </Location>
120  *
121  */
122 int util_ldap_handler(request_rec *r)
123 {
124     util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config, &ldap_module);
125
126     r->allowed |= (1 << M_GET);
127     if (r->method_number != M_GET)
128         return DECLINED;
129
130     if (strcmp(r->handler, "ldap-status")) {
131         return DECLINED;
132     }
133
134     r->content_type = "text/html";
135     if (r->header_only)
136         return OK;
137
138     ap_rputs(DOCTYPE_HTML_3_2
139              "<html><head><title>LDAP Cache Information</title></head>\n", r);
140     ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information</h1>\n", r);
141
142     util_ald_cache_display(r, st);
143
144     return OK;
145 }
146
147 /* ------------------------------------------------------------------ */
148
149
150 /*
151  * Closes an LDAP connection by unlocking it. The next time
152  * util_ldap_connection_find() is called this connection will be
153  * available for reuse.
154  */
155 LDAP_DECLARE(void) util_ldap_connection_close(util_ldap_connection_t *ldc)
156 {
157
158     /*
159      * QUESTION:
160      *
161      * Is it safe leaving bound connections floating around between the
162      * different modules? Keeping the user bound is a performance boost,
163      * but it is also a potential security problem - maybe.
164      *
165      * For now we unbind the user when we finish with a connection, but
166      * we don't have to...
167      */
168
169     /* mark our connection as available for reuse */
170
171 #if APR_HAS_THREADS
172     apr_thread_mutex_unlock(ldc->lock);
173 #endif
174 }
175
176
177 /*
178  * Destroys an LDAP connection by unbinding and closing the connection to
179  * the LDAP server. It is used to bring the connection back to a known
180  * state after an error, and during pool cleanup.
181  */
182 LDAP_DECLARE_NONSTD(apr_status_t) util_ldap_connection_unbind(void *param)
183 {
184     util_ldap_connection_t *ldc = param;
185
186     if (ldc) {
187         if (ldc->ldap) {
188             ldap_unbind_s(ldc->ldap);
189             ldc->ldap = NULL;
190         }
191         ldc->bound = 0;
192     }
193
194     return APR_SUCCESS;
195 }
196
197
198 /*
199  * Clean up an LDAP connection by unbinding and unlocking the connection.
200  * This function is registered with the pool cleanup function - causing
201  * the LDAP connections to be shut down cleanly on graceful restart.
202  */
203 LDAP_DECLARE_NONSTD(apr_status_t) util_ldap_connection_cleanup(void *param)
204 {
205     util_ldap_connection_t *ldc = param;
206
207     if (ldc) {
208
209         /* unbind and disconnect from the LDAP server */
210         util_ldap_connection_unbind(ldc);
211
212         /* free the username and password */
213         if (ldc->bindpw) {
214             free((void*)ldc->bindpw);
215         }
216         if (ldc->binddn) {
217             free((void*)ldc->binddn);
218         }
219
220         /* unlock this entry */
221         util_ldap_connection_close(ldc);
222     
223     }
224
225     return APR_SUCCESS;
226 }
227
228
229 /*
230  * Connect to the LDAP server and binds. Does not connect if already
231  * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
232  *
233  * Returns LDAP_SUCCESS on success; and an error code on failure
234  */
235 LDAP_DECLARE(int) util_ldap_connection_open(request_rec *r, 
236                                             util_ldap_connection_t *ldc)
237 {
238     int result = 0;
239     int failures = 0;
240     int version  = LDAP_VERSION3;
241
242     util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
243                                 r->server->module_config, &ldap_module);
244
245     /* If the connection is already bound, return
246     */
247     if (ldc->bound)
248     {
249         ldc->reason = "LDAP: connection open successful (already bound)";
250         return LDAP_SUCCESS;
251     }
252
253     /* create the ldap session handle
254     */
255     if (NULL == ldc->ldap)
256     {
257         apr_ldap_err_t *result = NULL;
258         int rc = apr_ldap_init(r->pool,
259                                &(ldc->ldap),
260                                ldc->host,
261                                ldc->port,
262                                ldc->secure,
263                                &(result));
264
265         if (result != NULL) {
266             ldc->reason = result->reason;
267         }
268
269         if (NULL == ldc->ldap)
270         {
271             ldc->bound = 0;
272             if (NULL == ldc->reason)
273                 ldc->reason = "LDAP: ldap initialization failed";
274             return(-1);
275         }
276
277         /* Set the alias dereferencing option */
278         ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &(ldc->deref));
279
280         /* always default to LDAP V3 */
281         ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
282
283     }
284
285
286     /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is
287      * returned.  Break out of the loop on Success or any other error.
288      *
289      * NOTE: Looping is probably not a great idea. If the server isn't 
290      * responding the chances it will respond after a few tries are poor.
291      * However, the original code looped and it only happens on
292      * the error condition.
293       */
294     for (failures=0; failures<10; failures++)
295     {
296         result = ldap_simple_bind_s(ldc->ldap, ldc->binddn, ldc->bindpw);
297         if (LDAP_SERVER_DOWN != result)
298             break;
299     }
300
301     /* free the handle if there was an error
302     */
303     if (LDAP_SUCCESS != result)
304     {
305         ldap_unbind_s(ldc->ldap);
306         ldc->ldap = NULL;
307         ldc->bound = 0;
308         ldc->reason = "LDAP: ldap_simple_bind_s() failed";
309     }
310     else {
311         ldc->bound = 1;
312         ldc->reason = "LDAP: connection open successful";
313     }
314
315     return(result);
316 }
317
318
319 /*
320  * Find an existing ldap connection struct that matches the
321  * provided ldap connection parameters.
322  *
323  * If not found in the cache, a new ldc structure will be allocated from st->pool
324  * and returned to the caller. If found in the cache, a pointer to the existing
325  * ldc structure will be returned.
326  */
327 LDAP_DECLARE(util_ldap_connection_t *)util_ldap_connection_find(request_rec *r, const char *host, int port,
328                                               const char *binddn, const char *bindpw, deref_options deref,
329                                               int secure )
330 {
331     struct util_ldap_connection_t *l, *p;       /* To traverse the linked list */
332
333     util_ldap_state_t *st = 
334         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
335         &ldap_module);
336
337
338 #if APR_HAS_THREADS
339     /* mutex lock this function */
340     if (!st->mutex) {
341         apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
342     }
343     apr_thread_mutex_lock(st->mutex);
344 #endif
345
346     /* Search for an exact connection match in the list that is not
347      * being used.
348      */
349     for (l=st->connections,p=NULL; l; l=l->next) {
350 #if APR_HAS_THREADS
351         if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
352 #endif
353         if ((l->port == port) && (strcmp(l->host, host) == 0) && 
354             ((!l->binddn && !binddn) || (l->binddn && binddn && !strcmp(l->binddn, binddn))) && 
355             ((!l->bindpw && !bindpw) || (l->bindpw && bindpw && !strcmp(l->bindpw, bindpw))) && 
356             (l->deref == deref) && (l->secure == secure)) {
357
358             break;
359         }
360 #if APR_HAS_THREADS
361             /* If this connection didn't match the criteria, then we
362              * need to unlock the mutex so it is available to be reused.
363              */
364             apr_thread_mutex_unlock(l->lock);
365         }
366 #endif
367         p = l;
368     }
369
370     /* If nothing found, search again, but we don't care about the
371      * binddn and bindpw this time.
372      */
373     if (!l) {
374         for (l=st->connections,p=NULL; l; l=l->next) {
375 #if APR_HAS_THREADS
376             if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
377
378 #endif
379             if ((l->port == port) && (strcmp(l->host, host) == 0) && 
380                 (l->deref == deref) && (l->secure == secure)) {
381
382                 /* the bind credentials have changed */
383                 l->bound = 0;
384                 util_ldap_strdup((char**)&(l->binddn), binddn);
385                 util_ldap_strdup((char**)&(l->bindpw), bindpw);
386                 break;
387             }
388 #if APR_HAS_THREADS
389                 /* If this connection didn't match the criteria, then we
390                  * need to unlock the mutex so it is available to be reused.
391                  */
392                 apr_thread_mutex_unlock(l->lock);
393             }
394 #endif
395             p = l;
396         }
397     }
398
399 /* artificially disable cache */
400 /* l = NULL; */
401
402     /* If no connection what found after the second search, we
403      * must create one.
404      */
405     if (!l) {
406
407         /* 
408          * Add the new connection entry to the linked list. Note that we
409          * don't actually establish an LDAP connection yet; that happens
410          * the first time authentication is requested.
411          */
412         /* create the details to the pool in st */
413         l = apr_pcalloc(st->pool, sizeof(util_ldap_connection_t));
414 #if APR_HAS_THREADS
415         apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, st->pool);
416         apr_thread_mutex_lock(l->lock);
417 #endif
418         l->pool = st->pool;
419         l->bound = 0;
420         l->host = apr_pstrdup(st->pool, host);
421         l->port = port;
422         l->deref = deref;
423         util_ldap_strdup((char**)&(l->binddn), binddn);
424         util_ldap_strdup((char**)&(l->bindpw), bindpw);
425         l->secure = secure;
426
427         /* add the cleanup to the pool */
428         apr_pool_cleanup_register(l->pool, l,
429                                   util_ldap_connection_cleanup,
430                                   apr_pool_cleanup_null);
431
432         if (p) {
433             p->next = l;
434         }
435         else {
436             st->connections = l;
437         }
438     }
439
440 #if APR_HAS_THREADS
441     apr_thread_mutex_unlock(st->mutex);
442 #endif
443     return l;
444 }
445
446 /* ------------------------------------------------------------------ */
447
448 /*
449  * Compares two DNs to see if they're equal. The only way to do this correctly is to 
450  * search for the dn and then do ldap_get_dn() on the result. This should match the 
451  * initial dn, since it would have been also retrieved with ldap_get_dn(). This is
452  * expensive, so if the configuration value compare_dn_on_server is
453  * false, just does an ordinary strcmp.
454  *
455  * The lock for the ldap cache should already be acquired.
456  */
457 LDAP_DECLARE(int) util_ldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc, 
458                             const char *url, const char *dn, const char *reqdn, 
459                             int compare_dn_on_server)
460 {
461     int result = 0;
462     util_url_node_t *curl; 
463     util_url_node_t curnode;
464     util_dn_compare_node_t *node;
465     util_dn_compare_node_t newnode;
466     int failures = 0;
467     LDAPMessage *res, *entry;
468     char *searchdn;
469
470     util_ldap_state_t *st =  (util_ldap_state_t *)ap_get_module_config(r->server->module_config, &ldap_module);
471
472     /* get cache entry (or create one) */
473     LDAP_CACHE_LOCK();
474
475     curnode.url = url;
476     curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
477     if (curl == NULL) {
478         curl = util_ald_create_caches(st, url);
479     }
480     LDAP_CACHE_UNLOCK();
481
482     /* a simple compare? */
483     if (!compare_dn_on_server) {
484         /* unlock this read lock */
485         if (strcmp(dn, reqdn)) {
486             ldc->reason = "DN Comparison FALSE (direct strcmp())";
487             return LDAP_COMPARE_FALSE;
488         }
489         else {
490             ldc->reason = "DN Comparison TRUE (direct strcmp())";
491             return LDAP_COMPARE_TRUE;
492         }
493     }
494
495     if (curl) {
496         /* no - it's a server side compare */
497         LDAP_CACHE_LOCK();
498     
499         /* is it in the compare cache? */
500         newnode.reqdn = (char *)reqdn;
501         node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
502         if (node != NULL) {
503             /* If it's in the cache, it's good */
504             /* unlock this read lock */
505             LDAP_CACHE_UNLOCK();
506             ldc->reason = "DN Comparison TRUE (cached)";
507             return LDAP_COMPARE_TRUE;
508         }
509     
510         /* unlock this read lock */
511         LDAP_CACHE_UNLOCK();
512     }
513
514 start_over:
515     if (failures++ > 10) {
516         /* too many failures */
517         return result;
518     }
519
520     /* make a server connection */
521     if (LDAP_SUCCESS != (result = util_ldap_connection_open(r, ldc))) {
522         /* connect to server failed */
523         return result;
524     }
525
526     /* search for reqdn */
527     if ((result = ldap_search_ext_s(ldc->ldap, reqdn, LDAP_SCOPE_BASE, 
528                                     "(objectclass=*)", NULL, 1, 
529                                     NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) {
530         ldc->reason = "DN Comparison ldap_search_ext_s() failed with server down";
531         util_ldap_connection_unbind(ldc);
532         goto start_over;
533     }
534     if (result != LDAP_SUCCESS) {
535         /* search for reqdn failed - no match */
536         ldc->reason = "DN Comparison ldap_search_ext_s() failed";
537         return result;
538     }
539
540     entry = ldap_first_entry(ldc->ldap, res);
541     searchdn = ldap_get_dn(ldc->ldap, entry);
542
543     ldap_msgfree(res);
544     if (strcmp(dn, searchdn) != 0) {
545         /* compare unsuccessful */
546         ldc->reason = "DN Comparison FALSE (checked on server)";
547         result = LDAP_COMPARE_FALSE;
548     }
549     else {
550         if (curl) {
551             /* compare successful - add to the compare cache */
552             LDAP_CACHE_LOCK();
553             newnode.reqdn = (char *)reqdn;
554             newnode.dn = (char *)dn;
555             
556             node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
557             if ((node == NULL) || 
558                 (strcmp(reqdn, node->reqdn) != 0) || (strcmp(dn, node->dn) != 0)) {
559
560                 util_ald_cache_insert(curl->dn_compare_cache, &newnode);
561             }
562             LDAP_CACHE_UNLOCK();
563         }
564         ldc->reason = "DN Comparison TRUE (checked on server)";
565         result = LDAP_COMPARE_TRUE;
566     }
567     ldap_memfree(searchdn);
568     return result;
569
570 }
571
572 /*
573  * Does an generic ldap_compare operation. It accepts a cache that it will use
574  * to lookup the compare in the cache. We cache two kinds of compares 
575  * (require group compares) and (require user compares). Each compare has a different
576  * cache node: require group includes the DN; require user does not because the
577  * require user cache is owned by the 
578  *
579  */
580 LDAP_DECLARE(int) util_ldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
581                           const char *url, const char *dn,
582                           const char *attrib, const char *value)
583 {
584     int result = 0;
585     util_url_node_t *curl; 
586     util_url_node_t curnode;
587     util_compare_node_t *compare_nodep;
588     util_compare_node_t the_compare_node;
589     apr_time_t curtime = 0; /* silence gcc -Wall */
590     int failures = 0;
591
592     util_ldap_state_t *st = 
593         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
594         &ldap_module);
595
596     /* get cache entry (or create one) */
597     LDAP_CACHE_LOCK();
598     curnode.url = url;
599     curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
600     if (curl == NULL) {
601         curl = util_ald_create_caches(st, url);
602     }
603     LDAP_CACHE_UNLOCK();
604
605     if (curl) {
606         /* make a comparison to the cache */
607         LDAP_CACHE_LOCK();
608         curtime = apr_time_now();
609     
610         the_compare_node.dn = (char *)dn;
611         the_compare_node.attrib = (char *)attrib;
612         the_compare_node.value = (char *)value;
613         the_compare_node.result = 0;
614     
615         compare_nodep = util_ald_cache_fetch(curl->compare_cache, &the_compare_node);
616     
617         if (compare_nodep != NULL) {
618             /* found it... */
619             if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
620                 /* ...but it is too old */
621                 util_ald_cache_remove(curl->compare_cache, compare_nodep);
622             }
623             else {
624                 /* ...and it is good */
625                 /* unlock this read lock */
626                 LDAP_CACHE_UNLOCK();
627                 if (LDAP_COMPARE_TRUE == compare_nodep->result) {
628                     ldc->reason = "Comparison true (cached)";
629                     return compare_nodep->result;
630                 }
631                 else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
632                     ldc->reason = "Comparison false (cached)";
633                     return compare_nodep->result;
634                 }
635                 else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
636                     ldc->reason = "Comparison no such attribute (cached)";
637                     return compare_nodep->result;
638                 }
639                 else {
640                     ldc->reason = "Comparison undefined (cached)";
641                     return compare_nodep->result;
642                 }
643             }
644         }
645         /* unlock this read lock */
646         LDAP_CACHE_UNLOCK();
647     }
648
649 start_over:
650     if (failures++ > 10) {
651         /* too many failures */
652         return result;
653     }
654     if (LDAP_SUCCESS != (result = util_ldap_connection_open(r, ldc))) {
655         /* connect failed */
656         return result;
657     }
658
659     if ((result = ldap_compare_s(ldc->ldap, dn, attrib, value))
660         == LDAP_SERVER_DOWN) { 
661         /* connection failed - try again */
662         ldc->reason = "ldap_compare_s() failed with server down";
663         util_ldap_connection_unbind(ldc);
664         goto start_over;
665     }
666
667     ldc->reason = "Comparison complete";
668     if ((LDAP_COMPARE_TRUE == result) || 
669         (LDAP_COMPARE_FALSE == result) ||
670         (LDAP_NO_SUCH_ATTRIBUTE == result)) {
671         if (curl) {
672             /* compare completed; caching result */
673             LDAP_CACHE_LOCK();
674             the_compare_node.lastcompare = curtime;
675             the_compare_node.result = result;
676
677             /* If the node doesn't exist then insert it, otherwise just update it with
678                the last results */
679             compare_nodep = util_ald_cache_fetch(curl->compare_cache, &the_compare_node);
680             if ((compare_nodep == NULL) || 
681                 (strcmp(the_compare_node.dn, compare_nodep->dn) != 0) || 
682                 (strcmp(the_compare_node.attrib, compare_nodep->attrib) != 0) || 
683                 (strcmp(the_compare_node.value, compare_nodep->value) != 0)) {
684
685                 util_ald_cache_insert(curl->compare_cache, &the_compare_node);
686             }
687             else {
688                 compare_nodep->lastcompare = curtime;
689                 compare_nodep->result = result;
690             }
691             LDAP_CACHE_UNLOCK();
692         }
693         if (LDAP_COMPARE_TRUE == result) {
694             ldc->reason = "Comparison true (adding to cache)";
695             return LDAP_COMPARE_TRUE;
696         }
697         else if (LDAP_COMPARE_FALSE == result) {
698             ldc->reason = "Comparison false (adding to cache)";
699             return LDAP_COMPARE_FALSE;
700         }
701         else {
702             ldc->reason = "Comparison no such attribute (adding to cache)";
703             return LDAP_NO_SUCH_ATTRIBUTE;
704         }
705     }
706     return result;
707 }
708
709 LDAP_DECLARE(int) util_ldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
710                               const char *url, const char *basedn, int scope, char **attrs,
711                               const char *filter, const char *bindpw, const char **binddn,
712                               const char ***retvals)
713 {
714     const char **vals = NULL;
715     int result = 0;
716     LDAPMessage *res, *entry;
717     char *dn;
718     int count;
719     int failures = 0;
720     util_url_node_t *curl;              /* Cached URL node */
721     util_url_node_t curnode;
722     util_search_node_t *search_nodep;   /* Cached search node */
723     util_search_node_t the_search_node;
724     apr_time_t curtime;
725
726     util_ldap_state_t *st = 
727         (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
728         &ldap_module);
729
730     /* Get the cache node for this url */
731     LDAP_CACHE_LOCK();
732     curnode.url = url;
733     curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache, &curnode);
734     if (curl == NULL) {
735         curl = util_ald_create_caches(st, url);
736     }
737     LDAP_CACHE_UNLOCK();
738
739     if (curl) {
740         LDAP_CACHE_LOCK();
741         the_search_node.username = filter;
742         search_nodep = util_ald_cache_fetch(curl->search_cache, &the_search_node);
743         if (search_nodep != NULL && search_nodep->bindpw) {
744     
745             /* found entry in search cache... */
746             curtime = apr_time_now();
747     
748             /*
749              * Remove this item from the cache if its expired, or if the 
750              * sent password doesn't match the storepassword.
751              */
752             if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
753                 /* ...but entry is too old */
754                 util_ald_cache_remove(curl->search_cache, search_nodep);
755             }
756             else if (strcmp(search_nodep->bindpw, bindpw) != 0) {
757             /* ...but cached password doesn't match sent password */
758                 util_ald_cache_remove(curl->search_cache, search_nodep);
759             }
760             else {
761                 /* ...and entry is valid */
762                 *binddn = search_nodep->dn;
763                 *retvals = search_nodep->vals;
764                 LDAP_CACHE_UNLOCK();
765                 ldc->reason = "Authentication successful (cached)";
766                 return LDAP_SUCCESS;
767             }
768         }
769         /* unlock this read lock */
770         LDAP_CACHE_UNLOCK();
771     }
772
773     /*  
774      * At this point, there is no valid cached search, so lets do the search.
775      */
776
777     /*
778      * If any LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
779      */
780 start_over:
781     if (failures++ > 10) {
782         return result;
783     }
784     if (LDAP_SUCCESS != (result = util_ldap_connection_open(r, ldc))) {
785         return result;
786     }
787
788     /* try do the search */
789     if ((result = ldap_search_ext_s(ldc->ldap,
790                                     basedn, scope, 
791                                     filter, attrs, 0, 
792                                     NULL, NULL, NULL, -1, &res)) == LDAP_SERVER_DOWN) {
793         ldc->reason = "ldap_search_ext_s() for user failed with server down";
794         util_ldap_connection_unbind(ldc);
795         goto start_over;
796     }
797
798     /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
799     if (result != LDAP_SUCCESS) {
800         ldc->reason = "ldap_search_ext_s() for user failed";
801         return result;
802     }
803
804     /* 
805      * We should have found exactly one entry; to find a different
806      * number is an error.
807      */
808     count = ldap_count_entries(ldc->ldap, res);
809     if (count != 1) 
810     {
811         if (count == 0 )
812             ldc->reason = "User not found";
813         else
814             ldc->reason = "User is not unique (search found two or more matches)";
815         ldap_msgfree(res);
816         return LDAP_NO_SUCH_OBJECT;
817     }
818
819     entry = ldap_first_entry(ldc->ldap, res);
820
821     /* Grab the dn, copy it into the pool, and free it again */
822     dn = ldap_get_dn(ldc->ldap, entry);
823     *binddn = apr_pstrdup(r->pool, dn);
824     ldap_memfree(dn);
825
826     /* 
827      * A bind to the server with an empty password always succeeds, so
828      * we check to ensure that the password is not empty. This implies
829      * that users who actually do have empty passwords will never be
830      * able to authenticate with this module. I don't see this as a big
831      * problem.
832      */
833     if (strlen(bindpw) <= 0) {
834         ldap_msgfree(res);
835         ldc->reason = "Empty password not allowed";
836         return LDAP_INVALID_CREDENTIALS;
837     }
838
839     /* 
840      * Attempt to bind with the retrieved dn and the password. If the bind
841      * fails, it means that the password is wrong (the dn obviously
842      * exists, since we just retrieved it)
843      */
844     if ((result = 
845          ldap_simple_bind_s(ldc->ldap, *binddn, bindpw)) == 
846          LDAP_SERVER_DOWN) {
847         ldc->reason = "ldap_simple_bind_s() to check user credentials failed with server down";
848         ldap_msgfree(res);
849         util_ldap_connection_unbind(ldc);
850         goto start_over;
851     }
852
853     /* failure? if so - return */
854     if (result != LDAP_SUCCESS) {
855         ldc->reason = "ldap_simple_bind_s() to check user credentials failed";
856         ldap_msgfree(res);
857         util_ldap_connection_unbind(ldc);
858         return result;
859     }
860     else {
861         /*
862          * We have just bound the connection to a different user and password
863          * combination, which might be reused unintentionally next time this
864          * connection is used from the connection pool. To ensure no confusion,
865          * we mark the connection as unbound.
866          */
867         ldc->bound = 0;
868     }
869
870     /*
871      * Get values for the provided attributes.
872      */
873     if (attrs) {
874         int k = 0;
875         int i = 0;
876         while (attrs[k++]);
877         vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
878         while (attrs[i]) {
879             char **values;
880             int j = 0;
881             char *str = NULL;
882             /* get values */
883             values = ldap_get_values(ldc->ldap, entry, attrs[i]);
884             while (values && values[j]) {
885                 str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL) : apr_pstrdup(r->pool, values[j]);
886                 j++;
887             }
888             ldap_value_free(values);
889             vals[i] = str;
890             i++;
891         }
892         *retvals = vals;
893     }
894
895     /*          
896      * Add the new username to the search cache.
897      */
898     if (curl) {
899         LDAP_CACHE_LOCK();
900         the_search_node.username = filter;
901         the_search_node.dn = *binddn;
902         the_search_node.bindpw = bindpw;
903         the_search_node.lastbind = apr_time_now();
904         the_search_node.vals = vals;
905
906         /* Search again to make sure that another thread didn't ready insert this node
907            into the cache before we got here. If it does exist then update the lastbind */
908         search_nodep = util_ald_cache_fetch(curl->search_cache, &the_search_node);
909         if ((search_nodep == NULL) || 
910             (strcmp(*binddn, search_nodep->dn) != 0) || (strcmp(bindpw, search_nodep->bindpw) != 0)) {
911
912             util_ald_cache_insert(curl->search_cache, &the_search_node);
913         }
914         else {
915             search_nodep->lastbind = the_search_node.lastbind;
916         }
917         LDAP_CACHE_UNLOCK();
918     }
919     ldap_msgfree(res);
920
921     ldc->reason = "Authentication successful";
922     return LDAP_SUCCESS;
923 }
924
925
926 /*
927  * Reports if ssl support is enabled 
928  *
929  * 1 = enabled, 0 = not enabled
930  */
931 LDAP_DECLARE(int) util_ldap_ssl_supported(request_rec *r)
932 {
933    util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
934                                 r->server->module_config, &ldap_module);
935
936    return(st->ssl_support);
937 }
938
939
940 /* ---------------------------------------- */
941 /* config directives */
942
943
944 static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy, const char *bytes)
945 {
946     util_ldap_state_t *st = 
947         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
948                                                   &ldap_module);
949
950     st->cache_bytes = atol(bytes);
951
952     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
953                  "[%" APR_PID_T_FMT "] ldap cache: Setting shared memory "
954                  " cache size to %" APR_SIZE_T_FMT " bytes.", 
955                  getpid(), st->cache_bytes);
956
957     return NULL;
958 }
959
960 static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy, const char *file)
961 {
962     util_ldap_state_t *st = 
963         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
964                                                   &ldap_module);
965
966     if (file) {
967         st->cache_file = ap_server_root_relative(st->pool, file);
968     }
969     else {
970         st->cache_file = NULL;
971     }
972
973     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
974                  "LDAP cache: Setting shared memory cache file to %s bytes.", 
975                  st->cache_file);
976
977     return NULL;
978 }
979
980 static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy, const char *ttl)
981 {
982     util_ldap_state_t *st = 
983         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
984                                                   &ldap_module);
985
986     st->search_cache_ttl = atol(ttl) * 1000000;
987
988     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
989                       "[%d] ldap cache: Setting cache TTL to %ld microseconds.", 
990                       getpid(), st->search_cache_ttl);
991
992     return NULL;
993 }
994
995 static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy, const char *size)
996 {
997     util_ldap_state_t *st = 
998         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
999                                                   &ldap_module);
1000
1001
1002     st->search_cache_size = atol(size);
1003     if (st->search_cache_size < 0) {
1004         st->search_cache_size = 0;
1005     }
1006
1007     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
1008                       "[%d] ldap cache: Setting search cache size to %ld entries.", 
1009                       getpid(), st->search_cache_size);
1010
1011     return NULL;
1012 }
1013
1014 static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy, const char *ttl)
1015 {
1016     util_ldap_state_t *st = 
1017         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
1018                                                   &ldap_module);
1019
1020     st->compare_cache_ttl = atol(ttl) * 1000000;
1021
1022     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
1023                       "[%d] ldap cache: Setting operation cache TTL to %ld microseconds.", 
1024                       getpid(), st->compare_cache_ttl);
1025
1026     return NULL;
1027 }
1028
1029 static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy, const char *size)
1030 {
1031     util_ldap_state_t *st = 
1032         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
1033                                                   &ldap_module);
1034
1035     st->compare_cache_size = atol(size);
1036     if (st->compare_cache_size < 0) {
1037         st->compare_cache_size = 0;
1038     }
1039
1040     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
1041                       "[%d] ldap cache: Setting operation cache size to %ld entries.", 
1042                       getpid(), st->compare_cache_size);
1043
1044     return NULL;
1045 }
1046
1047 static const char *util_ldap_set_cert_auth(cmd_parms *cmd, void *dummy, const char *file)
1048 {
1049     util_ldap_state_t *st = 
1050         (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
1051                                                   &ldap_module);
1052     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1053     if (err != NULL) {
1054         return err;
1055     }
1056
1057     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
1058                       "LDAP: SSL trusted certificate authority file - %s", 
1059                        file);
1060
1061     st->cert_auth_file = ap_server_root_relative(cmd->pool, file);
1062
1063     return(NULL);
1064 }
1065
1066
1067 static const char *util_ldap_set_cert_type(cmd_parms *cmd, void *dummy, const char *Type)
1068 {
1069     util_ldap_state_t *st = 
1070     (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config, 
1071                                               &ldap_module);
1072     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1073     if (err != NULL) {
1074         return err;
1075     }
1076
1077     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, 
1078                       "LDAP: SSL trusted certificate authority file type - %s", 
1079                        Type);
1080
1081     if (0 == strcmp("DER_FILE", Type))
1082         st->cert_file_type = LDAP_CA_TYPE_DER;
1083
1084     else if (0 == strcmp("BASE64_FILE", Type))
1085         st->cert_file_type = LDAP_CA_TYPE_BASE64;
1086
1087     else if (0 == strcmp("CERT7_DB_PATH", Type))
1088         st->cert_file_type = LDAP_CA_TYPE_CERT7_DB;
1089
1090     else
1091         st->cert_file_type = LDAP_CA_TYPE_UNKNOWN;
1092
1093     return(NULL);
1094 }
1095
1096
1097 void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
1098 {
1099     util_ldap_state_t *st = 
1100         (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
1101
1102     st->pool = p;
1103
1104     st->cache_bytes = 100000;
1105     st->search_cache_ttl = 600000000;
1106     st->search_cache_size = 1024;
1107     st->compare_cache_ttl = 600000000;
1108     st->compare_cache_size = 1024;
1109     st->connections = NULL;
1110     st->cert_auth_file = NULL;
1111     st->cert_file_type = LDAP_CA_TYPE_UNKNOWN;
1112     st->ssl_support = 0;
1113
1114     return st;
1115 }
1116
1117 static apr_status_t util_ldap_cleanup_module(void *data)
1118 {
1119
1120     server_rec *s = data;
1121     util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
1122         s->module_config, &ldap_module);
1123     
1124     if (st->ssl_support) {
1125         apr_ldap_ssl_deinit();
1126     }
1127
1128     return APR_SUCCESS;
1129
1130 }
1131
1132 static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, 
1133                                  apr_pool_t *ptemp, server_rec *s)
1134 {
1135     int rc = LDAP_SUCCESS;
1136     apr_status_t result;
1137     char buf[MAX_STRING_LEN];
1138     server_rec *s_vhost;
1139     util_ldap_state_t *st_vhost;
1140
1141     util_ldap_state_t *st =
1142         (util_ldap_state_t *)ap_get_module_config(s->module_config, &ldap_module);
1143
1144     void *data;
1145     const char *userdata_key = "util_ldap_init";
1146
1147     /* util_ldap_post_config() will be called twice. Don't bother
1148      * going through all of the initialization on the first call
1149      * because it will just be thrown away.*/
1150     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
1151     if (!data) {
1152         apr_pool_userdata_set((const void *)1, userdata_key,
1153                                apr_pool_cleanup_null, s->process->pool);
1154
1155 #if APR_HAS_SHARED_MEMORY
1156         /* If the cache file already exists then delete it.  Otherwise we are
1157          * going to run into problems creating the shared memory. */
1158         if (st->cache_file) {
1159             char *lck_file = apr_pstrcat (st->pool, st->cache_file, ".lck", NULL);
1160             apr_file_remove(st->cache_file, ptemp);
1161             apr_file_remove(lck_file, ptemp);
1162         }
1163 #endif
1164         return OK;
1165     }
1166
1167 #if APR_HAS_SHARED_MEMORY
1168     /* initializing cache if shared memory size is not zero and we already don't have shm address */
1169     if (!st->cache_shm && st->cache_bytes > 0) {
1170 #endif
1171         result = util_ldap_cache_init(p, st);
1172         if (result != APR_SUCCESS) {
1173             apr_strerror(result, buf, sizeof(buf));
1174             ap_log_error(APLOG_MARK, APLOG_ERR, result, s,
1175                          "LDAP cache: error while creating a shared memory segment: %s", buf);
1176         }
1177
1178
1179 #if APR_HAS_SHARED_MEMORY
1180         if (st->cache_file) {
1181             st->lock_file = apr_pstrcat (st->pool, st->cache_file, ".lck", NULL);
1182         }
1183         else
1184 #endif
1185             st->lock_file = ap_server_root_relative(st->pool, tmpnam(NULL));
1186
1187         result = apr_global_mutex_create(&st->util_ldap_cache_lock, st->lock_file, APR_LOCK_DEFAULT, st->pool);
1188         if (result != APR_SUCCESS) {
1189             return result;
1190         }
1191
1192         /* merge config in all vhost */
1193         s_vhost = s->next;
1194         while (s_vhost) {
1195             st_vhost = (util_ldap_state_t *)ap_get_module_config(s_vhost->module_config, &ldap_module);
1196
1197 #if APR_HAS_SHARED_MEMORY
1198             st_vhost->cache_shm = st->cache_shm;
1199             st_vhost->cache_rmm = st->cache_rmm;
1200             st_vhost->cache_file = st->cache_file;
1201             ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, result, s, 
1202                          "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp for VHOST: %s",
1203                          st->cache_shm, st->cache_rmm, s_vhost->server_hostname);
1204 #endif
1205             st_vhost->lock_file = st->lock_file;
1206             s_vhost = s_vhost->next;
1207         }
1208 #if APR_HAS_SHARED_MEMORY
1209     }
1210     else {
1211         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "LDAP cache: LDAPSharedCacheSize is zero, disabling shared memory cache");
1212     }
1213 #endif
1214     
1215     /* log the LDAP SDK used 
1216      */
1217     {
1218         apr_ldap_err_t *result = NULL;
1219         apr_ldap_info(&(result), p);
1220         if (result != NULL) {
1221             ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, result->reason);
1222         }
1223     }
1224
1225     apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
1226                               util_ldap_cleanup_module); 
1227
1228     /* initialize SSL support if requested
1229     */
1230     if (st->cert_auth_file) {
1231
1232         apr_ldap_err_t *result = NULL;
1233         int rc = apr_ldap_ssl_init(p,
1234                                    st->cert_auth_file,
1235                                    st->cert_file_type,
1236                                    &(result));
1237
1238         if (LDAP_SUCCESS == rc) {
1239             st->ssl_support = 1;
1240         }
1241         else if (NULL != result) {
1242             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, result->reason);
1243             st->ssl_support = 0;
1244         }
1245
1246     }
1247       
1248     /* log SSL status - If SSL isn't available it isn't necessarily
1249      * an error because the modules asking for LDAP connections 
1250      * may not ask for SSL support
1251      */
1252     if (st->ssl_support) {
1253        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, 
1254                          "LDAP: SSL support available" );
1255     }
1256     else {
1257        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, 
1258                          "LDAP: SSL support unavailable" );
1259     }
1260     
1261     return(OK);
1262 }
1263
1264 static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
1265 {
1266     apr_status_t sts;
1267     util_ldap_state_t *st =
1268         (util_ldap_state_t *)ap_get_module_config(s->module_config, &ldap_module);
1269
1270     sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock, st->lock_file, p);
1271     if (sts != APR_SUCCESS) {
1272         ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s, "failed to init caching lock in child process");
1273         return;
1274     }
1275     else {
1276         ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, s, 
1277                      "INIT global mutex %s in child %d ", st->lock_file, getpid());
1278     }
1279 }
1280
1281 command_rec util_ldap_cmds[] = {
1282     AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes, NULL, RSRC_CONF,
1283                   "Sets the size of the shared memory cache in bytes. "
1284                   "Zero means disable the shared memory cache. Defaults to 100KB."),
1285
1286     AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file, NULL, RSRC_CONF,
1287                   "Sets the file of the shared memory cache."
1288                   "Nothing means disable the shared memory cache."),
1289
1290     AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries, NULL, RSRC_CONF,
1291                   "Sets the maximum number of entries that are possible in the LDAP "
1292                   "search cache. "
1293                   "Zero means no limit; -1 disables the cache. Defaults to 1024 entries."),
1294
1295     AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl, NULL, RSRC_CONF,
1296                   "Sets the maximum time (in seconds) that an item can be cached in the LDAP "
1297                   "search cache. Zero means no limit. Defaults to 600 seconds (10 minutes)."),
1298
1299     AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries, NULL, RSRC_CONF,
1300                   "Sets the maximum number of entries that are possible in the LDAP "
1301                   "compare cache. "
1302                   "Zero means no limit; -1 disables the cache. Defaults to 1024 entries."),
1303
1304     AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl, NULL, RSRC_CONF,
1305                   "Sets the maximum time (in seconds) that an item is cached in the LDAP "
1306                   "operation cache. Zero means no limit. Defaults to 600 seconds (10 minutes)."),
1307
1308     AP_INIT_TAKE1("LDAPTrustedCA", util_ldap_set_cert_auth, NULL, RSRC_CONF,
1309                   "Sets the file containing the trusted Certificate Authority certificate. "
1310                   "Used to validate the LDAP server certificate for SSL connections."),
1311
1312     AP_INIT_TAKE1("LDAPTrustedCAType", util_ldap_set_cert_type, NULL, RSRC_CONF,
1313                  "Specifies the type of the Certificate Authority file.  "
1314                  "The following types are supported:  "
1315                  "    DER_FILE      - file in binary DER format "
1316                  "    BASE64_FILE   - file in Base64 format "
1317                  "    CERT7_DB_PATH - Netscape certificate database file "),
1318     {NULL}
1319 };
1320
1321 static void util_ldap_register_hooks(apr_pool_t *p)
1322 {
1323     ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
1324     ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
1325     ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1326 }
1327
1328 module ldap_module = {
1329    STANDARD20_MODULE_STUFF,
1330    NULL,                                /* dir config creater */
1331    NULL,                                /* dir merger --- default is to override */
1332    util_ldap_create_config,             /* server config */
1333    NULL,                                /* merge server config */
1334    util_ldap_cmds,                      /* command table */
1335    util_ldap_register_hooks,            /* set up request processing hooks */
1336 };