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_cache_mgr.c: LDAP cache manager 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 "util_ldap.h"
27#include "util_ldap_cache.h"
28#include <apr_strings.h>
29
30#if APR_HAS_LDAP
31
32/* only here until strdup is gone */
33#include <string.h>
34
35/* here till malloc is gone */
36#include <stdlib.h>
37
38static const unsigned long primes[] =
39{
40  11,
41  19,
42  37,
43  73,
44  109,
45  163,
46  251,
47  367,
48  557,
49  823,
50  1237,
51  1861,
52  2777,
53  4177,
54  6247,
55  9371,
56  14057,
57  21089,
58  31627,
59  47431,
60  71143,
61  106721,
62  160073,
63  240101,
64  360163,
65  540217,
66  810343,
67  1215497,
68  1823231,
69  2734867,
70  4102283,
71  6153409,
72  9230113,
73  13845163,
74  0
75};
76
77void util_ald_free(util_ald_cache_t *cache, const void *ptr)
78{
79#if APR_HAS_SHARED_MEMORY
80    if (cache->rmm_addr) {
81        if (ptr)
82            /* Free in shared memory */
83            apr_rmm_free(cache->rmm_addr, apr_rmm_offset_get(cache->rmm_addr, (void *)ptr));
84    }
85    else {
86        if (ptr)
87            /* Cache shm is not used */
88            free((void *)ptr);
89    }
90#else
91    if (ptr)
92        free((void *)ptr);
93#endif
94}
95
96void *util_ald_alloc(util_ald_cache_t *cache, unsigned long size)
97{
98    if (0 == size)
99        return NULL;
100#if APR_HAS_SHARED_MEMORY
101    if (cache->rmm_addr) {
102        /* allocate from shared memory */
103        apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, size);
104        return block ? (void *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
105    }
106    else {
107        /* Cache shm is not used */
108        return (void *)calloc(sizeof(char), size);
109    }
110#else
111    return (void *)calloc(sizeof(char), size);
112#endif
113}
114
115const char *util_ald_strdup(util_ald_cache_t *cache, const char *s)
116{
117#if APR_HAS_SHARED_MEMORY
118    if (cache->rmm_addr) {
119        /* allocate from shared memory */
120        apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, strlen(s)+1);
121        char *buf = block ? (char *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
122        if (buf) {
123            strcpy(buf, s);
124            return buf;
125        }
126        else {
127            return NULL;
128        }
129    } else {
130        /* Cache shm is not used */
131        return strdup(s);
132    }
133#else
134    return strdup(s);
135#endif
136}
137
138
139/*
140 * Computes the hash on a set of strings. The first argument is the number
141 * of strings to hash, the rest of the args are strings.
142 * Algorithm taken from glibc.
143 */
144unsigned long util_ald_hash_string(int nstr, ...)
145{
146    int i;
147    va_list args;
148    unsigned long h=0, g;
149    char *str, *p;
150
151    va_start(args, nstr);
152    for (i=0; i < nstr; ++i) {
153        str = va_arg(args, char *);
154        for (p = str; *p; ++p) {
155            h = ( h << 4 ) + *p;
156            if ( ( g = h & 0xf0000000 ) ) {
157                h = h ^ (g >> 24);
158                h = h ^ g;
159            }
160        }
161    }
162    va_end(args);
163
164    return h;
165}
166
167
168/*
169  Purges a cache that has gotten full. We keep track of the time that we
170  added the entry that made the cache 3/4 full, then delete all entries
171  that were added before that time. It's pretty simplistic, but time to
172  purge is only O(n), which is more important.
173*/
174void util_ald_cache_purge(util_ald_cache_t *cache)
175{
176    unsigned long i;
177    util_cache_node_t *p, *q, **pp;
178    apr_time_t t;
179
180    if (!cache)
181        return;
182
183    cache->last_purge = apr_time_now();
184    cache->npurged = 0;
185    cache->numpurges++;
186
187    for (i=0; i < cache->size; ++i) {
188        pp = cache->nodes + i;
189        p = *pp;
190        while (p != NULL) {
191            if (p->add_time < cache->marktime) {
192                q = p->next;
193                (*cache->free)(cache, p->payload);
194                util_ald_free(cache, p);
195                cache->numentries--;
196                cache->npurged++;
197                p = *pp = q;
198            }
199            else {
200                pp = &(p->next);
201                p = *pp;
202            }
203        }
204    }
205
206    t = apr_time_now();
207    cache->avg_purgetime =
208         ((t - cache->last_purge) + (cache->avg_purgetime * (cache->numpurges-1))) /
209         cache->numpurges;
210}
211
212
213/*
214 * create caches
215 */
216util_url_node_t *util_ald_create_caches(util_ldap_state_t *st, const char *url)
217{
218    util_url_node_t curl, *newcurl = NULL;
219    util_ald_cache_t *search_cache;
220    util_ald_cache_t *compare_cache;
221    util_ald_cache_t *dn_compare_cache;
222
223    /* create the three caches */
224    search_cache = util_ald_create_cache(st,
225                      st->search_cache_size,
226                      util_ldap_search_node_hash,
227                      util_ldap_search_node_compare,
228                      util_ldap_search_node_copy,
229                      util_ldap_search_node_free,
230                      util_ldap_search_node_display);
231    compare_cache = util_ald_create_cache(st,
232                      st->compare_cache_size,
233                      util_ldap_compare_node_hash,
234                      util_ldap_compare_node_compare,
235                      util_ldap_compare_node_copy,
236                      util_ldap_compare_node_free,
237                      util_ldap_compare_node_display);
238    dn_compare_cache = util_ald_create_cache(st,
239                      st->compare_cache_size,
240                      util_ldap_dn_compare_node_hash,
241                      util_ldap_dn_compare_node_compare,
242                      util_ldap_dn_compare_node_copy,
243                      util_ldap_dn_compare_node_free,
244                      util_ldap_dn_compare_node_display);
245
246    /* check that all the caches initialised successfully */
247    if (search_cache && compare_cache && dn_compare_cache) {
248
249        /* The contents of this structure will be duplicated in shared
250           memory during the insert.  So use stack memory rather than
251           pool memory to avoid a memory leak. */
252        memset (&curl, 0, sizeof(util_url_node_t));
253        curl.url = url;
254        curl.search_cache = search_cache;
255        curl.compare_cache = compare_cache;
256        curl.dn_compare_cache = dn_compare_cache;
257
258        newcurl = util_ald_cache_insert(st->util_ldap_cache, &curl);
259
260    }
261
262    return newcurl;
263}
264
265
266util_ald_cache_t *util_ald_create_cache(util_ldap_state_t *st,
267                                long cache_size,
268                                unsigned long (*hashfunc)(void *),
269                                int (*comparefunc)(void *, void *),
270                                void * (*copyfunc)(util_ald_cache_t *cache, void *),
271                                void (*freefunc)(util_ald_cache_t *cache, void *),
272                                void (*displayfunc)(request_rec *r, util_ald_cache_t *cache, void *))
273{
274    util_ald_cache_t *cache;
275    unsigned long i;
276
277    if (cache_size <= 0)
278        return NULL;
279
280#if APR_HAS_SHARED_MEMORY
281    if (!st->cache_rmm) {
282        return NULL;
283    }
284    else {
285        apr_rmm_off_t block = apr_rmm_calloc(st->cache_rmm, sizeof(util_ald_cache_t));
286        cache = block ? (util_ald_cache_t *)apr_rmm_addr_get(st->cache_rmm, block) : NULL;
287    }
288#else
289    cache = (util_ald_cache_t *)calloc(sizeof(util_ald_cache_t), 1);
290#endif
291    if (!cache)
292        return NULL;
293
294#if APR_HAS_SHARED_MEMORY
295    cache->rmm_addr = st->cache_rmm;
296    cache->shm_addr = st->cache_shm;
297#endif
298    cache->maxentries = cache_size;
299    cache->numentries = 0;
300    cache->size = cache_size / 3;
301    if (cache->size < 64) cache->size = 64;
302        for (i = 0; primes[i] && primes[i] < cache->size; ++i) ;
303            cache->size = primes[i]? primes[i] : primes[i-1];
304
305    cache->nodes = (util_cache_node_t **)util_ald_alloc(cache, cache->size * sizeof(util_cache_node_t *));
306    if (!cache->nodes) {
307        util_ald_free(cache, cache);
308        return NULL;
309    }
310
311    for (i=0; i < cache->size; ++i)
312        cache->nodes[i] = NULL;
313
314    cache->hash = hashfunc;
315    cache->compare = comparefunc;
316    cache->copy = copyfunc;
317    cache->free = freefunc;
318    cache->display = displayfunc;
319
320    cache->fullmark = cache->maxentries / 4 * 3;
321    cache->marktime = 0;
322    cache->avg_purgetime = 0.0;
323    cache->numpurges = 0;
324    cache->last_purge = 0;
325    cache->npurged = 0;
326
327    cache->fetches = 0;
328    cache->hits = 0;
329    cache->inserts = 0;
330    cache->removes = 0;
331
332    return cache;
333}
334
335void util_ald_destroy_cache(util_ald_cache_t *cache)
336{
337    unsigned long i;
338    util_cache_node_t *p, *q;
339
340    if (cache == NULL)
341        return;
342
343    for (i = 0; i < cache->size; ++i) {
344        p = cache->nodes[i];
345        q = NULL;
346        while (p != NULL) {
347            q = p->next;
348           (*cache->free)(cache, p->payload);
349           util_ald_free(cache, p);
350           p = q;
351        }
352    }
353    util_ald_free(cache, cache->nodes);
354    util_ald_free(cache, cache);
355}
356
357void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload)
358{
359    unsigned long hashval;
360    util_cache_node_t *p;
361
362    if (cache == NULL)
363        return NULL;
364
365    cache->fetches++;
366
367    hashval = (*cache->hash)(payload) % cache->size;
368    for (p = cache->nodes[hashval];
369         p && !(*cache->compare)(p->payload, payload);
370    p = p->next) ;
371
372    if (p != NULL) {
373        cache->hits++;
374        return p->payload;
375    }
376    else {
377        return NULL;
378    }
379}
380
381/*
382 * Insert an item into the cache.
383 * *** Does not catch duplicates!!! ***
384 */
385void *util_ald_cache_insert(util_ald_cache_t *cache, void *payload)
386{
387    unsigned long hashval;
388    void *tmp_payload;
389    util_cache_node_t *node;
390
391    /* sanity check */
392    if (cache == NULL || payload == NULL) {
393        return NULL;
394    }
395
396    /* check if we are full - if so, try purge */
397    if (cache->numentries >= cache->maxentries) {
398        util_ald_cache_purge(cache);
399        if (cache->numentries >= cache->maxentries) {
400            /* if the purge was not effective, we leave now to avoid an overflow */
401            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
402                         "Purge of LDAP cache failed");
403            return NULL;
404        }
405    }
406
407    node = (util_cache_node_t *)util_ald_alloc(cache,
408                                               sizeof(util_cache_node_t));
409    if (node == NULL) {
410        /*
411         * XXX: The cache management should be rewritten to work
412         * properly when LDAPSharedCacheSize is too small.
413         */
414        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
415                     "LDAPSharedCacheSize is too small. Increase it or "
416                     "reduce LDAPCacheEntries/LDAPOpCacheEntries!");
417        if (cache->numentries < cache->fullmark) {
418            /*
419             * We have not even reached fullmark, trigger a complete purge.
420             * This is still better than not being able to add new entries
421             * at all.
422             */
423            cache->marktime = apr_time_now();
424        }
425        util_ald_cache_purge(cache);
426        node = (util_cache_node_t *)util_ald_alloc(cache,
427                                                   sizeof(util_cache_node_t));
428        if (node == NULL) {
429            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
430                         "Could not allocate memory for LDAP cache entry");
431            return NULL;
432        }
433    }
434
435    /* Take a copy of the payload before proceeeding. */
436    tmp_payload = (*cache->copy)(cache, payload);
437    if (tmp_payload == NULL) {
438        /*
439         * XXX: The cache management should be rewritten to work
440         * properly when LDAPSharedCacheSize is too small.
441         */
442        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
443                     "LDAPSharedCacheSize is too small. Increase it or "
444                     "reduce LDAPCacheEntries/LDAPOpCacheEntries!");
445        if (cache->numentries < cache->fullmark) {
446            /*
447             * We have not even reached fullmark, trigger a complete purge.
448             * This is still better than not being able to add new entries
449             * at all.
450             */
451            cache->marktime = apr_time_now();
452        }
453        util_ald_cache_purge(cache);
454        tmp_payload = (*cache->copy)(cache, payload);
455        if (tmp_payload == NULL) {
456            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
457                         "Could not allocate memory for LDAP cache value");
458            util_ald_free(cache, node);
459            return NULL;
460        }
461    }
462    payload = tmp_payload;
463
464    /* populate the entry */
465    cache->inserts++;
466    hashval = (*cache->hash)(payload) % cache->size;
467    node->add_time = apr_time_now();
468    node->payload = payload;
469    node->next = cache->nodes[hashval];
470    cache->nodes[hashval] = node;
471
472    /* if we reach the full mark, note the time we did so
473     * for the benefit of the purge function
474     */
475    if (++cache->numentries == cache->fullmark) {
476        cache->marktime=apr_time_now();
477    }
478
479    return node->payload;
480}
481
482void util_ald_cache_remove(util_ald_cache_t *cache, void *payload)
483{
484    unsigned long hashval;
485    util_cache_node_t *p, *q;
486
487    if (cache == NULL)
488        return;
489
490    cache->removes++;
491    hashval = (*cache->hash)(payload) % cache->size;
492    for (p = cache->nodes[hashval], q=NULL;
493         p && !(*cache->compare)(p->payload, payload);
494         p = p->next) {
495         q = p;
496    }
497
498    /* If p is null, it means that we couldn't find the node, so just return */
499    if (p == NULL)
500        return;
501
502    if (q == NULL) {
503        /* We found the node, and it's the first in the list */
504        cache->nodes[hashval] = p->next;
505    }
506    else {
507        /* We found the node and it's not the first in the list */
508        q->next = p->next;
509    }
510    (*cache->free)(cache, p->payload);
511    util_ald_free(cache, p);
512    cache->numentries--;
513}
514
515char *util_ald_cache_display_stats(request_rec *r, util_ald_cache_t *cache, char *name, char *id)
516{
517    unsigned long i;
518    int totchainlen = 0;
519    int nchains = 0;
520    double chainlen;
521    util_cache_node_t *n;
522    char *buf, *buf2;
523    apr_pool_t *p = r->pool;
524
525    if (cache == NULL) {
526        return "";
527    }
528
529    for (i=0; i < cache->size; ++i) {
530        if (cache->nodes[i] != NULL) {
531            nchains++;
532            for (n = cache->nodes[i];
533                 n != NULL && n != n->next;
534                 n = n->next) {
535                totchainlen++;
536            }
537        }
538    }
539    chainlen = nchains? (double)totchainlen / (double)nchains : 0;
540
541    if (id) {
542        buf2 = apr_psprintf(p,
543                 "<a href=\"%s?%s\">%s</a>",
544             ap_escape_html(r->pool, ap_escape_uri(r->pool, r->uri)),
545             id,
546             name);
547    }
548    else {
549        buf2 = name;
550    }
551
552    buf = apr_psprintf(p,
553             "<tr valign='top'>"
554             "<td nowrap>%s</td>"
555             "<td align='right' nowrap>%lu (%.0f%% full)</td>"
556             "<td align='right'>%.1f</td>"
557             "<td align='right'>%lu/%lu</td>"
558             "<td align='right'>%.0f%%</td>"
559             "<td align='right'>%lu/%lu</td>",
560         buf2,
561         cache->numentries,
562         (double)cache->numentries / (double)cache->maxentries * 100.0,
563         chainlen,
564         cache->hits,
565         cache->fetches,
566         (cache->fetches > 0 ? (double)(cache->hits) / (double)(cache->fetches) * 100.0 : 100.0),
567         cache->inserts,
568         cache->removes);
569
570    if (cache->numpurges) {
571        char str_ctime[APR_CTIME_LEN];
572
573        apr_ctime(str_ctime, cache->last_purge);
574        buf = apr_psprintf(p,
575                 "%s"
576                 "<td align='right'>%lu</td>\n"
577                 "<td align='right' nowrap>%s</td>\n",
578             buf,
579             cache->numpurges,
580             str_ctime);
581    }
582    else {
583        buf = apr_psprintf(p,
584                 "%s<td colspan='2' align='center'>(none)</td>\n",
585             buf);
586    }
587
588    buf = apr_psprintf(p, "%s<td align='right'>%.2gms</td>\n</tr>", buf, cache->avg_purgetime);
589
590    return buf;
591}
592
593char *util_ald_cache_display(request_rec *r, util_ldap_state_t *st)
594{
595    unsigned long i,j;
596    char *buf, *t1, *t2, *t3;
597    char *id1, *id2, *id3;
598    char *argfmt = "cache=%s&id=%d&off=%d";
599    char *scanfmt = "cache=%4s&id=%u&off=%u%1s";
600    apr_pool_t *pool = r->pool;
601    util_cache_node_t *p = NULL;
602    util_url_node_t *n = NULL;
603
604    util_ald_cache_t *util_ldap_cache = st->util_ldap_cache;
605
606
607    if (!util_ldap_cache) {
608        ap_rputs("<tr valign='top'><td nowrap colspan=7>Cache has not been enabled/initialised.</td></tr>", r);
609        return NULL;
610    }
611
612    if (r->args && strlen(r->args)) {
613        char cachetype[5], lint[2];
614        unsigned int id, off;
615        char date_str[APR_CTIME_LEN+1];
616
617        if ((3 == sscanf(r->args, scanfmt, cachetype, &id, &off, lint)) &&
618            (id < util_ldap_cache->size)) {
619
620            if ((p = util_ldap_cache->nodes[id]) != NULL) {
621                n = (util_url_node_t *)p->payload;
622                buf = (char*)n->url;
623            }
624            else {
625                buf = "";
626            }
627
628            ap_rprintf(r,
629                       "<p>\n"
630                       "<table border='0'>\n"
631                       "<tr>\n"
632                       "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name:</b></font></td>"
633                       "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s (%s)</b></font></td>"
634                       "</tr>\n"
635                       "</table>\n</p>\n",
636                       buf,
637                       cachetype[0] == 'm'? "Main" :
638                       (cachetype[0] == 's' ? "Search" :
639                        (cachetype[0] == 'c' ? "Compares" : "DNCompares")));
640
641            switch (cachetype[0]) {
642                case 'm':
643                    if (util_ldap_cache->marktime) {
644                        apr_ctime(date_str, util_ldap_cache->marktime);
645                    }
646                    else
647                        date_str[0] = 0;
648
649                    ap_rprintf(r,
650                               "<p>\n"
651                               "<table border='0'>\n"
652                               "<tr>\n"
653                               "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size:</b></font></td>"
654                               "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
655                               "</tr>\n"
656                               "<tr>\n"
657                               "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries:</b></font></td>"
658                               "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
659                               "</tr>\n"
660                               "<tr>\n"
661                               "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries:</b></font></td>"
662                               "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
663                               "</tr>\n"
664                               "<tr>\n"
665                               "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark:</b></font></td>"
666                               "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
667                               "</tr>\n"
668                               "<tr>\n"
669                               "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time:</b></font></td>"
670                               "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s</b></font></td>"
671                               "</tr>\n"
672                               "</table>\n</p>\n",
673                               util_ldap_cache->size,
674                               util_ldap_cache->maxentries,
675                               util_ldap_cache->numentries,
676                               util_ldap_cache->fullmark,
677                               date_str);
678
679                    ap_rputs("<p>\n"
680                             "<table border='0'>\n"
681                             "<tr bgcolor='#000000'>\n"
682                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP URL</b></font></td>"
683                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size</b></font></td>"
684                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries</b></font></td>"
685                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries</b></font></td>"
686                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark</b></font></td>"
687                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time</b></font></td>"
688                             "</tr>\n", r
689                            );
690                    for (i=0; i < util_ldap_cache->size; ++i) {
691                        for (p = util_ldap_cache->nodes[i]; p != NULL; p = p->next) {
692
693                            (*util_ldap_cache->display)(r, util_ldap_cache, p->payload);
694                        }
695                    }
696                    ap_rputs("</table>\n</p>\n", r);
697
698
699                    break;
700                case 's':
701                    ap_rputs("<p>\n"
702                             "<table border='0'>\n"
703                             "<tr bgcolor='#000000'>\n"
704                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP Filter</b></font></td>"
705                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>User Name</b></font></td>"
706                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Bind</b></font></td>"
707                             "</tr>\n", r
708                            );
709                    if (n) {
710                        for (i=0; i < n->search_cache->size; ++i) {
711                            for (p = n->search_cache->nodes[i]; p != NULL; p = p->next) {
712
713                                (*n->search_cache->display)(r, n->search_cache, p->payload);
714                            }
715                        }
716                    }
717                    ap_rputs("</table>\n</p>\n", r);
718                    break;
719                case 'c':
720                    ap_rputs("<p>\n"
721                             "<table border='0'>\n"
722                             "<tr bgcolor='#000000'>\n"
723                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>DN</b></font></td>"
724                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Attribute</b></font></td>"
725                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Value</b></font></td>"
726                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Compare</b></font></td>"
727                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Result</b></font></td>"
728                             "</tr>\n", r
729                            );
730                    if (n) {
731                        for (i=0; i < n->compare_cache->size; ++i) {
732                            for (p = n->compare_cache->nodes[i]; p != NULL; p = p->next) {
733
734                                (*n->compare_cache->display)(r, n->compare_cache, p->payload);
735                            }
736                        }
737                    }
738                    ap_rputs("</table>\n</p>\n", r);
739                    break;
740                case 'd':
741                    ap_rputs("<p>\n"
742                             "<table border='0'>\n"
743                             "<tr bgcolor='#000000'>\n"
744                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Require DN</b></font></td>"
745                             "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Actual DN</b></font></td>"
746                             "</tr>\n", r
747                            );
748                    if (n) {
749                        for (i=0; i < n->dn_compare_cache->size; ++i) {
750                            for (p = n->dn_compare_cache->nodes[i]; p != NULL; p = p->next) {
751
752                                (*n->dn_compare_cache->display)(r, n->dn_compare_cache, p->payload);
753                            }
754                        }
755                    }
756                    ap_rputs("</table>\n</p>\n", r);
757                    break;
758                default:
759                    break;
760            }
761
762        }
763        else {
764            buf = "";
765        }
766    }
767    else {
768        ap_rputs("<p>\n"
769                 "<table border='0'>\n"
770                 "<tr bgcolor='#000000'>\n"
771                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name</b></font></td>"
772                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Entries</b></font></td>"
773                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg. Chain Len.</b></font></td>"
774                 "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Hits</b></font></td>"
775                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Ins/Rem</b></font></td>"
776                 "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Purges</b></font></td>"
777                 "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg Purge Time</b></font></td>"
778                 "</tr>\n", r
779                );
780
781
782        id1 = apr_psprintf(pool, argfmt, "main", 0, 0);
783        buf = util_ald_cache_display_stats(r, st->util_ldap_cache, "LDAP URL Cache", id1);
784
785        for (i=0; i < util_ldap_cache->size; ++i) {
786            for (p = util_ldap_cache->nodes[i],j=0; p != NULL; p = p->next,j++) {
787
788                n = (util_url_node_t *)p->payload;
789
790                t1 = apr_psprintf(pool, "%s (Searches)", n->url);
791                t2 = apr_psprintf(pool, "%s (Compares)", n->url);
792                t3 = apr_psprintf(pool, "%s (DNCompares)", n->url);
793                id1 = apr_psprintf(pool, argfmt, "srch", i, j);
794                id2 = apr_psprintf(pool, argfmt, "cmpr", i, j);
795                id3 = apr_psprintf(pool, argfmt, "dncp", i, j);
796
797                buf = apr_psprintf(pool, "%s\n\n"
798                                         "%s\n\n"
799                                         "%s\n\n"
800                                         "%s\n\n",
801                                         buf,
802                                         util_ald_cache_display_stats(r, n->search_cache, t1, id1),
803                                         util_ald_cache_display_stats(r, n->compare_cache, t2, id2),
804                                         util_ald_cache_display_stats(r, n->dn_compare_cache, t3, id3)
805                                  );
806            }
807        }
808        ap_rputs(buf, r);
809        ap_rputs("</table>\n</p>\n", r);
810    }
811
812    return buf;
813}
814
815#endif /* APR_HAS_LDAP */
816