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