Use rdebug_pair_list in more places and remove debug_pair_list
[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         if (c->control) {
151                 RDEBUG2("Merging cached control list");
152                 rdebug_pair_list(L_DBG_LVL_2, request, c->control);
153                 pairadd(&request->config_items, paircopy(request, c->control));
154         }
155
156         if (c->packet && request->packet) {
157                 RDEBUG2("Merging cached request list");
158                 rdebug_pair_list(L_DBG_LVL_2, request, c->packet);
159
160                 pairadd(&request->packet->vps,
161                         paircopy(request->packet, c->packet));
162         }
163
164         if (c->reply && request->reply) {
165                 RDEBUG2("Merging cached reply list");
166                 rdebug_pair_list(L_DBG_LVL_2, request, c->reply);
167
168                 pairadd(&request->reply->vps,
169                         paircopy(request->reply, c->reply));
170         }
171
172         if (inst->stats) {
173                 vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
174                 rad_assert(vp != NULL);
175
176                 vp->vp_integer = c->hits;
177
178                 pairadd(&request->packet->vps, vp);
179         }
180 }
181
182
183 /*
184  *      Find a cached entry.
185  */
186 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
187                                      char const *key)
188 {
189         int ttl;
190         rlm_cache_entry_t *c, my_c;
191         VALUE_PAIR *vp;
192
193         /*
194          *      Look at the expiry heap.
195          */
196         c = fr_heap_peek(inst->heap);
197         if (!c) {
198                 rad_assert(rbtree_num_elements(inst->cache) == 0);
199                 return NULL;
200         }
201
202         /*
203          *      If it's time to expire an old entry, do so now.
204          */
205         if (c->expires < request->timestamp) {
206                 fr_heap_extract(inst->heap, c);
207                 rbtree_deletebydata(inst->cache, c);
208         }
209
210         /*
211          *      Is there an entry for this key?
212          */
213         my_c.key = key;
214         c = rbtree_finddata(inst->cache, &my_c);
215         if (!c) return NULL;
216
217         /*
218          *      Yes, but it expired, OR the "forget all" epoch has
219          *      passed.  Delete it, and pretend it doesn't exist.
220          */
221         if ((c->expires < request->timestamp) ||
222             (c->created < inst->epoch)) {
223         delete:
224                 RDEBUG("Removing expired entry");
225
226                 fr_heap_extract(inst->heap, c);
227                 rbtree_deletebydata(inst->cache, c);
228
229                 return NULL;
230         }
231
232         RDEBUG("Found entry for \"%s\"", key);
233
234         /*
235          *      Update the expiry time based on the TTL.
236          *      A TTL of 0 means "delete from the cache".
237          *      A TTL < 0 means "delete from the cache and recreate the entry".
238          */
239         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
240         if (vp) {
241                 if (vp->vp_signed <= 0) goto delete;
242
243                 ttl = vp->vp_signed;
244                 c->expires = request->timestamp + ttl;
245                 RDEBUG("Adding %d to the TTL", ttl);
246         }
247         c->hits++;
248
249         return c;
250 }
251
252
253 /** Callback for map_to_request
254  *
255  * Simplifies merging VALUE_PAIRs into the current request.
256  */
257 static int _cache_add(VALUE_PAIR **out, REQUEST *request, UNUSED value_pair_map_t const *map, void *ctx)
258 {
259         VALUE_PAIR *vp;
260
261         vp = talloc_get_type_abort(ctx, VALUE_PAIR);
262         /* map_to_request will reparent */
263         *out = paircopy(request, vp);
264
265         if (!*out) return -1;
266         return 0;
267 }
268
269 /*
270  *      Add an entry to the cache.
271  */
272 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request, char const *key)
273 {
274         int ttl;
275         VALUE_PAIR *vp, *to_cache;
276         vp_cursor_t src_list, cached_request, cached_reply, cached_control;
277
278         bool merge = true;
279
280         value_pair_map_t const *map;
281
282         rlm_cache_entry_t *c;
283
284         if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
285                 RDEBUG("Cache is full: %d entries", inst->max_entries);
286                 return NULL;
287         }
288
289         /*
290          *      TTL of 0 means "don't cache this entry"
291          */
292         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
293         if (vp && (vp->vp_signed == 0)) return NULL;
294
295         c = talloc_zero(NULL, rlm_cache_entry_t);
296         c->key = talloc_typed_strdup(c, key);
297         c->created = c->expires = request->timestamp;
298
299         /*
300          *      Use per-entry TTL if > 0, or globally defined one.
301          */
302         ttl = vp && (vp->vp_signed > 0) ? vp->vp_integer : inst->ttl;
303         c->expires += ttl;
304
305         RDEBUG("Creating entry for \"%s\"", key);
306
307         /*
308          *      Check to see if we need to merge the entry into the request
309          */
310         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
311         if (vp && (vp->vp_integer == 0)) {
312                 merge = false;
313                 RDEBUG2("Told not to merge new entry into request");
314         }
315
316         fr_cursor_init(&cached_request, &c->packet);
317         fr_cursor_init(&cached_reply, &c->reply);
318         fr_cursor_init(&cached_control, &c->control);
319
320         for (map = inst->maps; map != NULL; map = map->next) {
321                 bool do_merge = merge;
322
323                 rad_assert(map->lhs && map->rhs);
324
325                 if (map_to_vp(&to_cache, request, map, NULL) < 0) {
326                         RDEBUG("Skipping %s", map->rhs->name);
327                         continue;
328                 }
329
330                 /*
331                  *      Merge attributes into the current request if:
332                  *        - Map specifies an xlat'd string.
333                  *        - Map specifies a literal string.
334                  *        - Map specifies an exec.
335                  *        - Map src and dst lists differ.
336                  *        - Map src and dst attributes differ
337                  *
338                  *       Unless Cache-Merge = no
339                  */
340                 if (do_merge) switch (map->rhs->type) {
341                 case TMPL_TYPE_LITERAL:
342                 case TMPL_TYPE_XLAT:
343                 case TMPL_TYPE_EXEC:
344                         break;
345
346                 case TMPL_TYPE_LIST:
347                         if (map->rhs->tmpl_list == map->lhs->tmpl_list) do_merge = false;
348                         break;
349
350                 case TMPL_TYPE_ATTR:
351                         rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
352                         if (map->rhs->tmpl_da == map->lhs->tmpl_da) do_merge = false;
353                         break;
354
355                 default:
356                         do_merge = false;
357                 }
358
359                 /*
360                  *      Reparent the VPs map_to_vp may return multiple.
361                  */
362                 for (vp = fr_cursor_init(&src_list, &to_cache);
363                      vp;
364                      vp = fr_cursor_next(&src_list)) {
365                         VERIFY_VP(vp);
366
367                         /*
368                          *      Prevent people from accidentally caching
369                          *      cache control attributes.
370                          */
371                         if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
372                         case PW_CACHE_TTL:
373                         case PW_CACHE_STATUS_ONLY:
374                         case PW_CACHE_READ_ONLY:
375                         case PW_CACHE_MERGE:
376                         case PW_CACHE_ENTRY_HITS:
377                                 RDEBUG2("Skipping %s", vp->da->name);
378                                 continue;
379
380                         default:
381                                 break;
382                         }
383
384                         if (debug_flag) map_debug_log(request, map, vp);
385                         (void) talloc_steal(c, vp);
386
387                         vp->op = map->op;
388
389                         switch (map->lhs->tmpl_list) {
390                         case PAIR_LIST_REQUEST:
391                                 fr_cursor_insert(&cached_request, vp);
392                                 break;
393
394                         case PAIR_LIST_REPLY:
395                                 fr_cursor_insert(&cached_reply, vp);
396                                 break;
397
398                         case PAIR_LIST_CONTROL:
399                                 fr_cursor_insert(&cached_control, vp);
400                                 break;
401
402                         default:
403                                 rad_assert(0);  /* should have been caught by validation */
404                         }
405
406                         if (do_merge && map_dst_valid(request, map)) {
407                                 /* There's no reason for this to fail (we checked the dst was valid) */
408                                 RDEBUG2("Adding to request:");
409                                 if (map_to_request(request, map, _cache_add, vp) < 0) rad_assert(0);
410                         }
411                 }
412         }
413
414         if (!rbtree_insert(inst->cache, c)) {
415                 REDEBUG("FAILED adding entry for key %s", key);
416                 cache_entry_free(c);
417                 return NULL;
418         }
419
420         if (!fr_heap_insert(inst->heap, c)) {
421                 REDEBUG("FAILED adding entry for key %s", key);
422                 rbtree_deletebydata(inst->cache, c);
423                 return NULL;
424         }
425
426         RDEBUG("Inserted entry, TTL %d seconds", ttl);
427
428         return c;
429 }
430
431 /** Verify that a map in the cache section makes sense
432  *
433  */
434 static int cache_verify(value_pair_map_t *map, UNUSED void *ctx)
435 {
436         if (modcall_fixup_update(map, ctx) < 0) return -1;
437
438         if ((map->lhs->type != TMPL_TYPE_ATTR) &&
439             (map->lhs->type != TMPL_TYPE_LIST)) {
440                 cf_log_err(map->ci, "Left operand must be an attribute ref or a list");
441                 return -1;
442         }
443
444         switch (map->rhs->type) {
445         case TMPL_TYPE_EXEC:
446                 cf_log_err(map->ci, "Exec values are not allowed");
447                 return -1;
448         /*
449          *      Only =, :=, += and -= operators are supported for
450          *      cache entries.
451          */
452         case TMPL_TYPE_LITERAL:
453         case TMPL_TYPE_XLAT:
454         case TMPL_TYPE_ATTR:
455                 switch (map->op) {
456                 case T_OP_SET:
457                 case T_OP_EQ:
458                 case T_OP_SUB:
459                 case T_OP_ADD:
460                         break;
461
462                 default:
463                         cf_log_err(map->ci, "Operator \"%s\" not allowed for %s values",
464                                    fr_int2str(fr_tokens, map->op, "<INVALID>"),
465                                    fr_int2str(tmpl_types, map->rhs->type, "<INVALID>"));
466                         return -1;
467                 }
468         default:
469                 break;
470         }
471
472         return 0;
473 }
474
475 /*
476  *      Allow single attribute values to be retrieved from the cache.
477  */
478 static ssize_t cache_xlat(void *instance, REQUEST *request,
479                           char const *fmt, char *out, size_t freespace)
480 {
481         rlm_cache_entry_t       *c;
482         rlm_cache_t             *inst = instance;
483         VALUE_PAIR              *vp, *vps;
484         pair_lists_t            list;
485         DICT_ATTR const         *target;
486         char const              *p = fmt;
487         size_t                  len;
488         int                     ret = 0;
489
490         list = radius_list_name(&p, PAIR_LIST_REQUEST);
491
492         target = dict_attrbyname(p);
493         if (!target) {
494                 REDEBUG("Unknown attribute \"%s\"", p);
495                 return -1;
496         }
497
498         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
499         c = cache_find(inst, request, fmt);
500
501         if (!c) {
502                 RDEBUG("No cache entry for key \"%s\"", fmt);
503                 *out = '\0';
504                 goto done;
505         }
506
507         switch (list) {
508         case PAIR_LIST_REQUEST:
509                 vps = c->packet;
510                 break;
511
512         case PAIR_LIST_REPLY:
513                 vps = c->reply;
514                 break;
515
516         case PAIR_LIST_CONTROL:
517                 vps = c->control;
518                 break;
519
520         case PAIR_LIST_UNKNOWN:
521                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
522                 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
523                 return -1;
524
525         default:
526                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
527                 REDEBUG("Unsupported list \"%s\"",
528                         fr_int2str(pair_lists, list, "<UNKNOWN>"));
529                 return -1;
530         }
531
532         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
533         if (!vp) {
534                 RDEBUG("No instance of this attribute has been cached");
535                 *out = '\0';
536                 goto done;
537         }
538
539         len = vp_prints_value(out, freespace, vp, 0);
540         if (is_truncated(len, freespace)) {
541                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
542                 REDEBUG("Insufficient buffer space to write cached value");
543                 return -1;
544         }
545 done:
546         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
547
548         return ret;
549 }
550
551 /*
552  *      Only free memory we allocated.  The strings allocated via
553  *      cf_section_parse() do not need to be freed.
554  */
555 static int mod_detach(void *instance)
556 {
557         rlm_cache_t *inst = instance;
558
559         talloc_free(inst->maps);
560
561         fr_heap_delete(inst->heap);
562         rbtree_free(inst->cache);
563
564 #ifdef HAVE_PTHREAD_H
565         pthread_mutex_destroy(&inst->cache_mutex);
566 #endif
567         return 0;
568 }
569
570
571 /*
572  *      Instantiate the module.
573  */
574 static int mod_instantiate(CONF_SECTION *conf, void *instance)
575 {
576         rlm_cache_t *inst = instance;
577
578         inst->cs = conf;
579
580         inst->xlat_name = cf_section_name2(conf);
581         if (!inst->xlat_name) {
582                 inst->xlat_name = cf_section_name1(conf);
583         }
584
585         /*
586          *      Register the cache xlat function
587          */
588         xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
589
590         rad_assert(inst->key && *inst->key);
591
592         if (inst->ttl == 0) {
593                 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
594                 return -1;
595         }
596
597         if (inst->epoch != 0) {
598                 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
599                 return -1;
600         }
601
602 #ifdef HAVE_PTHREAD_H
603         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
604                 ERROR("Failed initializing mutex: %s",
605                        fr_syserror(errno));
606                 return -1;
607         }
608 #endif
609
610         /*
611          *      The cache.
612          */
613
614         inst->cache = rbtree_create(NULL, cache_entry_cmp, cache_entry_free, 0);
615         if (!inst->cache) {
616                 ERROR("Failed to create cache");
617                 return -1;
618         }
619         fr_link_talloc_ctx_free(inst, inst->cache);
620
621         /*
622          *      The heap of entries to expire.
623          */
624         inst->heap = fr_heap_create(cache_heap_cmp,
625                                     offsetof(rlm_cache_entry_t, offset));
626         if (!inst->heap) {
627                 ERROR("Failed to create heap for the cache");
628                 return -1;
629         }
630
631         /*
632          *      Make sure the users don't screw up too badly.
633          */
634         if (map_afrom_cs(&inst->maps, cf_section_sub_find(inst->cs, "update"),
635                          PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, cache_verify, NULL, MAX_ATTRMAP) < 0) {
636                 return -1;
637         }
638
639         if (!inst->maps) {
640                 cf_log_err_cs(inst->cs, "Cache config must contain an update section, and "
641                               "that section must not be empty");
642
643                 return -1;
644         }
645
646         return 0;
647 }
648
649 /*
650  *      Do caching checks.  Since we can update ANY VP list, we do
651  *      exactly the same thing for all sections (autz / auth / etc.)
652  *
653  *      If you want to cache something different in different sections,
654  *      configure another cache module.
655  */
656 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
657 {
658         rlm_cache_entry_t *c;
659         rlm_cache_t *inst = instance;
660         vp_cursor_t cursor;
661         VALUE_PAIR *vp;
662         char buffer[1024];
663         rlm_rcode_t rcode;
664
665         if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
666                 return RLM_MODULE_FAIL;
667         }
668
669         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
670         c = cache_find(inst, request, buffer);
671
672         /*
673          *      If yes, only return whether we found a valid cache entry
674          */
675         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
676         if (vp && vp->vp_integer) {
677                 rcode = c ? RLM_MODULE_OK:
678                             RLM_MODULE_NOTFOUND;
679                 goto done;
680         }
681
682         if (c) {
683                 cache_merge(inst, request, c);
684
685                 rcode = RLM_MODULE_OK;
686                 goto done;
687         }
688
689         vp = pairfind(request->config_items, PW_CACHE_READ_ONLY, 0, TAG_ANY);
690         if (vp && vp->vp_integer) {
691                 rcode = RLM_MODULE_NOTFOUND;
692                 goto done;
693         }
694
695         c = cache_add(inst, request, buffer);
696         if (!c) {
697                 rcode = RLM_MODULE_NOOP;
698                 goto done;
699         }
700
701         rcode = RLM_MODULE_UPDATED;
702
703 done:
704         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
705
706         /*
707          *      Reset control attributes
708          */
709         for (vp = fr_cursor_init(&cursor, &request->config_items);
710              vp;
711              vp = fr_cursor_next(&cursor)) {
712                 if (vp->da->vendor == 0) switch (vp->da->attr) {
713                 case PW_CACHE_TTL:
714                 case PW_CACHE_READ_ONLY:
715                 case PW_CACHE_MERGE:
716                         vp = fr_cursor_remove(&cursor);
717                         talloc_free(vp);
718                         break;
719                 }
720         }
721
722         return rcode;
723 }
724
725
726 /*
727  *      The module name should be the only globally exported symbol.
728  *      That is, everything else should be 'static'.
729  *
730  *      If the module needs to temporarily modify it's instantiation
731  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
732  *      The server will then take care of ensuring that the module
733  *      is single-threaded.
734  */
735 module_t rlm_cache = {
736         RLM_MODULE_INIT,
737         "cache",
738         0,                              /* type */
739         sizeof(rlm_cache_t),
740         module_config,
741         mod_instantiate,                /* instantiation */
742         mod_detach,                     /* detach */
743         {
744                 NULL,                   /* authentication */
745                 mod_cache_it,           /* authorization */
746                 mod_cache_it,           /* preaccounting */
747                 mod_cache_it,           /* accounting */
748                 NULL,                   /* checksimul */
749                 mod_cache_it,           /* pre-proxy */
750                 mod_cache_it,           /* post-proxy */
751                 mod_cache_it,           /* post-auth */
752         },
753 };