Merge pull request #837 from leprechau/feature/check_simul
[freeradius.git] / src / modules / rlm_cache / rlm_cache.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_cache.c
19  * @brief Cache values and merge them back into future requests.
20  *
21  * @copyright 2012-2013  The FreeRADIUS server project
22  */
23 RCSID("$Id$")
24
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/modules.h>
27 #include <freeradius-devel/modcall.h>
28 #include <freeradius-devel/heap.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 /*
32  *      Define a structure for our module configuration.
33  *
34  *      These variables do not need to be in a structure, but it's
35  *      a lot cleaner to do so, and a pointer to the structure can
36  *      be used as the instance handle.
37  */
38 typedef struct rlm_cache_t {
39         char const              *xlat_name;
40         char const              *key;
41         uint32_t                ttl;
42         uint32_t                max_entries;
43         int32_t                 epoch;
44         bool                    stats;
45         CONF_SECTION            *cs;
46         rbtree_t                *cache;
47         fr_heap_t               *heap;
48
49         value_pair_map_t        *maps;  //!< Attribute map applied to users
50                                         //!< and profiles.
51 #ifdef HAVE_PTHREAD_H
52         pthread_mutex_t cache_mutex;
53 #endif
54 } rlm_cache_t;
55
56 typedef struct rlm_cache_entry_t {
57         char const      *key;
58         int             offset;
59         long long int   hits;
60         time_t          created;
61         time_t          expires;
62         VALUE_PAIR      *control;
63         VALUE_PAIR      *packet;
64         VALUE_PAIR      *reply;
65 } rlm_cache_entry_t;
66
67 #ifdef HAVE_PTHREAD_H
68 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
69 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
70 #else
71 #define PTHREAD_MUTEX_LOCK(_x)
72 #define PTHREAD_MUTEX_UNLOCK(_x)
73 #endif
74
75 #define MAX_ATTRMAP     128
76
77 /*
78  *      A mapping of configuration file names to internal variables.
79  *
80  *      Note that the string is dynamically allocated, so it MUST
81  *      be freed.  When the configuration file parse re-reads the string,
82  *      it free's the old one, and strdup's the new one, placing the pointer
83  *      to the strdup'd string into 'config.string'.  This gets around
84  *      buffer over-flows.
85  */
86 static const CONF_PARSER module_config[] = {
87         { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_cache_t, key), NULL },
88         { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
89         { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "16384" },
90
91         /* Should be a type which matches time_t, @fixme before 2038 */
92         { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
93         { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
94
95         { NULL, -1, 0, NULL, NULL }             /* end the list */
96 };
97
98
99 /*
100  *      Compare two entries by key.  There may only be one entry with
101  *      the same key.
102  */
103 static int cache_entry_cmp(void const *one, void const *two)
104 {
105         rlm_cache_entry_t const *a = one;
106         rlm_cache_entry_t const *b = two;
107
108         return strcmp(a->key, b->key);
109 }
110
111 static void cache_entry_free(void *data)
112 {
113         rlm_cache_entry_t *c = data;
114
115         pairfree(&c->control);
116         pairfree(&c->packet);
117         pairfree(&c->reply);
118
119         talloc_free(c);
120 }
121
122 /*
123  *      Compare two entries by expiry time.  There may be multiple
124  *      entries with the same expiry time.
125  */
126 static int cache_heap_cmp(void const *one, void const *two)
127 {
128         rlm_cache_entry_t const *a = one;
129         rlm_cache_entry_t const *b = two;
130
131         if (a->expires < b->expires) return -1;
132         if (a->expires > b->expires) return +1;
133
134         return 0;
135 }
136
137 /*
138  *      Merge a cached entry into a REQUEST.
139  */
140 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
141 {
142         VALUE_PAIR *vp;
143
144         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
145         if (vp && (vp->vp_integer == 0)) {
146                 RDEBUG2("Told not to merge entry into request");
147                 return;
148         }
149
150         RDEBUG2("Merging cache entry into request");
151
152         if (c->control) {
153                 RDEBUG2("Merging cached control list");
154                 radius_pairmove(request, &request->config_items, paircopy(request, c->control), false);
155         }
156
157         if (c->packet && request->packet) {
158                 RDEBUG2("Merging cached request list");
159                 radius_pairmove(request, &request->packet->vps, paircopy(request->packet, c->packet), false);
160         }
161
162         if (c->reply && request->reply) {
163                 RDEBUG2("Merging cached reply list");
164                 radius_pairmove(request, &request->reply->vps, paircopy(request->reply, c->reply), false);
165         }
166
167         if (inst->stats) {
168                 rad_assert(request->packet != NULL);
169                 vp = pairfind(request->packet->vps, PW_CACHE_ENTRY_HITS, 0, TAG_ANY);
170                 if (!vp) {
171                         vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
172                         rad_assert(vp != NULL);
173                         pairadd(&request->packet->vps, vp);
174                 }
175                 vp->vp_integer = c->hits;
176         }
177 }
178
179
180 /*
181  *      Find a cached entry.
182  */
183 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
184                                      char const *key)
185 {
186         int ttl;
187         rlm_cache_entry_t *c, my_c;
188         VALUE_PAIR *vp;
189
190         /*
191          *      Look at the expiry heap.
192          */
193         c = fr_heap_peek(inst->heap);
194         if (!c) {
195                 rad_assert(rbtree_num_elements(inst->cache) == 0);
196                 return NULL;
197         }
198
199         /*
200          *      If it's time to expire an old entry, do so now.
201          */
202         if (c->expires < request->timestamp) {
203                 fr_heap_extract(inst->heap, c);
204                 rbtree_deletebydata(inst->cache, c);
205         }
206
207         /*
208          *      Is there an entry for this key?
209          */
210         my_c.key = key;
211         c = rbtree_finddata(inst->cache, &my_c);
212         if (!c) return NULL;
213
214         /*
215          *      Yes, but it expired, OR the "forget all" epoch has
216          *      passed.  Delete it, and pretend it doesn't exist.
217          */
218         if ((c->expires < request->timestamp) ||
219             (c->created < inst->epoch)) {
220         delete:
221                 RDEBUG("Removing expired entry");
222
223                 fr_heap_extract(inst->heap, c);
224                 rbtree_deletebydata(inst->cache, c);
225
226                 return NULL;
227         }
228
229         RDEBUG("Found entry for \"%s\"", key);
230
231         /*
232          *      Update the expiry time based on the TTL.
233          *      A TTL of 0 means "delete from the cache".
234          *      A TTL < 0 means "delete from the cache and recreate the entry".
235          */
236         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
237         if (vp) {
238                 if (vp->vp_signed <= 0) goto delete;
239
240                 ttl = vp->vp_signed;
241                 c->expires = request->timestamp + ttl;
242                 RDEBUG("Adding %d to the TTL", ttl);
243         }
244         c->hits++;
245
246         return c;
247 }
248
249
250 /** Callback for map_to_request
251  *
252  * Simplifies merging VALUE_PAIRs into the current request.
253  */
254 static int _cache_add(VALUE_PAIR **out, REQUEST *request, UNUSED value_pair_map_t const *map, void *ctx)
255 {
256         VALUE_PAIR *vp;
257
258         vp = talloc_get_type_abort(ctx, VALUE_PAIR);
259         /* map_to_request will reparent */
260         *out = paircopy(request, vp);
261
262         if (!*out) return -1;
263         return 0;
264 }
265
266 /*
267  *      Add an entry to the cache.
268  */
269 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request, char const *key)
270 {
271         int ttl;
272         VALUE_PAIR *vp, *to_cache;
273         vp_cursor_t src_list, cached_request, cached_reply, cached_control;
274
275         bool merge = true;
276
277         value_pair_map_t const *map;
278
279         rlm_cache_entry_t *c;
280
281         if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
282                 RDEBUG("Cache is full: %d entries", inst->max_entries);
283                 return NULL;
284         }
285
286         /*
287          *      TTL of 0 means "don't cache this entry"
288          */
289         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
290         if (vp && (vp->vp_signed == 0)) return NULL;
291
292         c = talloc_zero(NULL, rlm_cache_entry_t);
293         c->key = talloc_typed_strdup(c, key);
294         c->created = c->expires = request->timestamp;
295
296         /*
297          *      Use per-entry TTL if > 0, or globally defined one.
298          */
299         ttl = vp && (vp->vp_signed > 0) ? vp->vp_integer : inst->ttl;
300         c->expires += ttl;
301
302         RDEBUG("Creating entry for \"%s\"", key);
303
304         /*
305          *      Check to see if we need to merge the entry into the request
306          */
307         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
308         if (vp && (vp->vp_integer == 0)) {
309                 merge = false;
310                 RDEBUG2("Told not to merge new entry into request");
311         }
312
313         fr_cursor_init(&cached_request, &c->packet);
314         fr_cursor_init(&cached_reply, &c->reply);
315         fr_cursor_init(&cached_control, &c->control);
316
317         for (map = inst->maps; map != NULL; map = map->next) {
318                 bool do_merge = merge;
319
320                 rad_assert(map->lhs && map->rhs);
321
322                 if (map_to_vp(&to_cache, request, map, NULL) < 0) {
323                         RDEBUG("Skipping %s", map->rhs->name);
324                         continue;
325                 }
326
327                 /*
328                  *      Merge attributes into the current request if:
329                  *        - Map specifies an xlat'd string.
330                  *        - Map specifies a literal string.
331                  *        - Map specifies an exec.
332                  *        - Map src and dst lists differ.
333                  *        - Map src and dst attributes differ
334                  *
335                  *       Unless Cache-Merge = no
336                  */
337                 if (do_merge) switch (map->rhs->type) {
338                 case TMPL_TYPE_LITERAL:
339                 case TMPL_TYPE_XLAT:
340                 case TMPL_TYPE_EXEC:
341                         break;
342
343                 case TMPL_TYPE_LIST:
344                         if (map->rhs->tmpl_list == map->lhs->tmpl_list) do_merge = false;
345                         break;
346
347                 case TMPL_TYPE_ATTR:
348                         rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
349                         if (map->rhs->tmpl_da == map->lhs->tmpl_da) do_merge = false;
350                         break;
351
352                 default:
353                         do_merge = false;
354                 }
355
356                 /*
357                  *      Reparent the VPs map_to_vp may return multiple.
358                  */
359                 for (vp = fr_cursor_init(&src_list, &to_cache);
360                      vp;
361                      vp = fr_cursor_next(&src_list)) {
362                         VERIFY_VP(vp);
363
364                         /*
365                          *      Prevent people from accidentally caching
366                          *      cache control attributes.
367                          */
368                         if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
369                         case PW_CACHE_TTL:
370                         case PW_CACHE_STATUS_ONLY:
371                         case PW_CACHE_READ_ONLY:
372                         case PW_CACHE_MERGE:
373                         case PW_CACHE_ENTRY_HITS:
374                                 RDEBUG2("Skipping %s", vp->da->name);
375                                 continue;
376
377                         default:
378                                 break;
379                         }
380
381                         if (debug_flag) map_debug_log(request, map, vp);
382                         (void) talloc_steal(c, vp);
383
384                         vp->op = map->op;
385
386                         switch (map->lhs->tmpl_list) {
387                         case PAIR_LIST_REQUEST:
388                                 fr_cursor_insert(&cached_request, vp);
389                                 break;
390
391                         case PAIR_LIST_REPLY:
392                                 fr_cursor_insert(&cached_reply, vp);
393                                 break;
394
395                         case PAIR_LIST_CONTROL:
396                                 fr_cursor_insert(&cached_control, vp);
397                                 break;
398
399                         default:
400                                 rad_assert(0);  /* should have been caught by validation */
401                         }
402
403                         if (do_merge && map_dst_valid(request, map)) {
404                                 /* There's no reason for this to fail (we checked the dst was valid) */
405                                 RDEBUG2("Adding to request:");
406                                 if (map_to_request(request, map, _cache_add, vp) < 0) rad_assert(0);
407                         }
408                 }
409         }
410
411         if (!rbtree_insert(inst->cache, c)) {
412                 REDEBUG("FAILED adding entry for key %s", key);
413                 cache_entry_free(c);
414                 return NULL;
415         }
416
417         if (!fr_heap_insert(inst->heap, c)) {
418                 REDEBUG("FAILED adding entry for key %s", key);
419                 rbtree_deletebydata(inst->cache, c);
420                 return NULL;
421         }
422
423         RDEBUG("Inserted entry, TTL %d seconds", ttl);
424
425         return c;
426 }
427
428 /** Verify that a map in the cache section makes sense
429  *
430  */
431 static int cache_verify(value_pair_map_t *map, UNUSED void *ctx)
432 {
433         if (modcall_fixup_update(map, ctx) < 0) return -1;
434
435         if ((map->lhs->type != TMPL_TYPE_ATTR) &&
436             (map->lhs->type != TMPL_TYPE_LIST)) {
437                 cf_log_err(map->ci, "Left operand must be an attribute ref or a list");
438                 return -1;
439         }
440
441         switch (map->rhs->type) {
442         case TMPL_TYPE_EXEC:
443                 cf_log_err(map->ci, "Exec values are not allowed");
444                 return -1;
445         /*
446          *      Only =, :=, += and -= operators are supported for
447          *      cache entries.
448          */
449         case TMPL_TYPE_LITERAL:
450         case TMPL_TYPE_XLAT:
451         case TMPL_TYPE_ATTR:
452                 switch (map->op) {
453                 case T_OP_SET:
454                 case T_OP_EQ:
455                 case T_OP_SUB:
456                 case T_OP_ADD:
457                         break;
458
459                 default:
460                         cf_log_err(map->ci, "Operator \"%s\" not allowed for %s values",
461                                    fr_int2str(fr_tokens, map->op, "<INVALID>"),
462                                    fr_int2str(tmpl_types, map->rhs->type, "<INVALID>"));
463                         return -1;
464                 }
465         default:
466                 break;
467         }
468
469         return 0;
470 }
471
472 /*
473  *      Allow single attribute values to be retrieved from the cache.
474  */
475 static ssize_t cache_xlat(void *instance, REQUEST *request,
476                           char const *fmt, char *out, size_t freespace)
477 {
478         rlm_cache_entry_t       *c;
479         rlm_cache_t             *inst = instance;
480         VALUE_PAIR              *vp, *vps;
481         pair_lists_t            list;
482         DICT_ATTR const         *target;
483         char const              *p = fmt;
484         size_t                  len;
485         int                     ret = 0;
486
487         p += radius_list_name(&list, p, PAIR_LIST_REQUEST);
488         if (list == PAIR_LIST_UNKNOWN) {
489                 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
490                 return -1;
491         }
492
493         target = dict_attrbyname(p);
494         if (!target) {
495                 REDEBUG("Unknown attribute \"%s\"", p);
496                 return -1;
497         }
498
499         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
500         c = cache_find(inst, request, fmt);
501
502         if (!c) {
503                 RDEBUG("No cache entry for key \"%s\"", fmt);
504                 *out = '\0';
505                 goto done;
506         }
507
508         switch (list) {
509         case PAIR_LIST_REQUEST:
510                 vps = c->packet;
511                 break;
512
513         case PAIR_LIST_REPLY:
514                 vps = c->reply;
515                 break;
516
517         case PAIR_LIST_CONTROL:
518                 vps = c->control;
519                 break;
520
521         default:
522                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
523                 REDEBUG("Unsupported list \"%s\"",
524                         fr_int2str(pair_lists, list, "<UNKNOWN>"));
525                 return -1;
526         }
527
528         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
529         if (!vp) {
530                 RDEBUG("No instance of this attribute has been cached");
531                 *out = '\0';
532                 goto done;
533         }
534
535         len = vp_prints_value(out, freespace, vp, 0);
536         if (is_truncated(len, freespace)) {
537                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
538                 REDEBUG("Insufficient buffer space to write cached value");
539                 return -1;
540         }
541 done:
542         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
543
544         return ret;
545 }
546
547 /*
548  *      Only free memory we allocated.  The strings allocated via
549  *      cf_section_parse() do not need to be freed.
550  */
551 static int mod_detach(void *instance)
552 {
553         rlm_cache_t *inst = instance;
554
555         talloc_free(inst->maps);
556
557         fr_heap_delete(inst->heap);
558         rbtree_free(inst->cache);
559
560 #ifdef HAVE_PTHREAD_H
561         pthread_mutex_destroy(&inst->cache_mutex);
562 #endif
563         return 0;
564 }
565
566
567 /*
568  *      Instantiate the module.
569  */
570 static int mod_instantiate(CONF_SECTION *conf, void *instance)
571 {
572         rlm_cache_t *inst = instance;
573
574         inst->cs = conf;
575
576         inst->xlat_name = cf_section_name2(conf);
577         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
578
579         /*
580          *      Register the cache xlat function
581          */
582         xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
583
584         rad_assert(inst->key && *inst->key);
585
586         if (inst->ttl == 0) {
587                 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
588                 return -1;
589         }
590
591         if (inst->epoch != 0) {
592                 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
593                 return -1;
594         }
595
596 #ifdef HAVE_PTHREAD_H
597         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
598                 ERROR("Failed initializing mutex: %s",
599                        fr_syserror(errno));
600                 return -1;
601         }
602 #endif
603
604         /*
605          *      The cache.
606          */
607
608         inst->cache = rbtree_create(NULL, cache_entry_cmp, cache_entry_free, 0);
609         if (!inst->cache) {
610                 ERROR("Failed to create cache");
611                 return -1;
612         }
613         fr_link_talloc_ctx_free(inst, inst->cache);
614
615         /*
616          *      The heap of entries to expire.
617          */
618         inst->heap = fr_heap_create(cache_heap_cmp,
619                                     offsetof(rlm_cache_entry_t, offset));
620         if (!inst->heap) {
621                 ERROR("Failed to create heap for the cache");
622                 return -1;
623         }
624
625         /*
626          *      Make sure the users don't screw up too badly.
627          */
628         if (map_afrom_cs(&inst->maps, cf_section_sub_find(inst->cs, "update"),
629                          PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, cache_verify, NULL, MAX_ATTRMAP) < 0) {
630                 return -1;
631         }
632
633         if (!inst->maps) {
634                 cf_log_err_cs(inst->cs, "Cache config must contain an update section, and "
635                               "that section must not be empty");
636
637                 return -1;
638         }
639
640         return 0;
641 }
642
643 /*
644  *      Do caching checks.  Since we can update ANY VP list, we do
645  *      exactly the same thing for all sections (autz / auth / etc.)
646  *
647  *      If you want to cache something different in different sections,
648  *      configure another cache module.
649  */
650 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
651 {
652         rlm_cache_entry_t *c;
653         rlm_cache_t *inst = instance;
654         vp_cursor_t cursor;
655         VALUE_PAIR *vp;
656         char buffer[1024];
657         rlm_rcode_t rcode;
658
659         if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
660                 return RLM_MODULE_FAIL;
661         }
662
663         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
664         c = cache_find(inst, request, buffer);
665
666         /*
667          *      If yes, only return whether we found a valid cache entry
668          */
669         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
670         if (vp && vp->vp_integer) {
671                 rcode = c ? RLM_MODULE_OK:
672                             RLM_MODULE_NOTFOUND;
673                 goto done;
674         }
675
676         if (c) {
677                 cache_merge(inst, request, c);
678
679                 rcode = RLM_MODULE_OK;
680                 goto done;
681         }
682
683         vp = pairfind(request->config_items, PW_CACHE_READ_ONLY, 0, TAG_ANY);
684         if (vp && vp->vp_integer) {
685                 rcode = RLM_MODULE_NOTFOUND;
686                 goto done;
687         }
688
689         c = cache_add(inst, request, buffer);
690         if (!c) {
691                 rcode = RLM_MODULE_NOOP;
692                 goto done;
693         }
694
695         rcode = RLM_MODULE_UPDATED;
696
697 done:
698         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
699
700         /*
701          *      Reset control attributes
702          */
703         for (vp = fr_cursor_init(&cursor, &request->config_items);
704              vp;
705              vp = fr_cursor_next(&cursor)) {
706                 if (vp->da->vendor == 0) switch (vp->da->attr) {
707                 case PW_CACHE_TTL:
708                 case PW_CACHE_READ_ONLY:
709                 case PW_CACHE_MERGE:
710                         vp = fr_cursor_remove(&cursor);
711                         talloc_free(vp);
712                         break;
713                 }
714         }
715
716         return rcode;
717 }
718
719
720 /*
721  *      The module name should be the only globally exported symbol.
722  *      That is, everything else should be 'static'.
723  *
724  *      If the module needs to temporarily modify it's instantiation
725  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
726  *      The server will then take care of ensuring that the module
727  *      is single-threaded.
728  */
729 module_t rlm_cache = {
730         RLM_MODULE_INIT,
731         "cache",
732         0,                              /* type */
733         sizeof(rlm_cache_t),
734         module_config,
735         mod_instantiate,                /* instantiation */
736         mod_detach,                     /* detach */
737         {
738                 NULL,                   /* authentication */
739                 mod_cache_it,           /* authorization */
740                 mod_cache_it,           /* preaccounting */
741                 mod_cache_it,           /* accounting */
742                 NULL,                   /* checksimul */
743                 mod_cache_it,           /* pre-proxy */
744                 mod_cache_it,           /* post-proxy */
745                 mod_cache_it,           /* post-auth */
746         },
747 };