Distinguish between single VP inserts, and list merges
[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/heap.h>
28 #include <freeradius-devel/rad_assert.h>
29
30 /*
31  *      Define a structure for our module configuration.
32  *
33  *      These variables do not need to be in a structure, but it's
34  *      a lot cleaner to do so, and a pointer to the structure can
35  *      be used as the instance handle.
36  */
37 typedef struct rlm_cache_t {
38         char const              *xlat_name;
39         char const              *key;
40         uint32_t                ttl;
41         uint32_t                max_entries;
42         int32_t                 epoch;
43         bool                    stats;
44         CONF_SECTION            *cs;
45         rbtree_t                *cache;
46         fr_heap_t               *heap;
47
48         value_pair_map_t        *maps;  //!< Attribute map applied to users
49                                         //!< and profiles.
50 #ifdef HAVE_PTHREAD_H
51         pthread_mutex_t cache_mutex;
52 #endif
53 } rlm_cache_t;
54
55 typedef struct rlm_cache_entry_t {
56         char const      *key;
57         int             offset;
58         long long int   hits;
59         time_t          created;
60         time_t          expires;
61         VALUE_PAIR      *control;
62         VALUE_PAIR      *packet;
63         VALUE_PAIR      *reply;
64 } rlm_cache_entry_t;
65
66 #ifdef HAVE_PTHREAD_H
67 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
68 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
69 #else
70 #define PTHREAD_MUTEX_LOCK(_x)
71 #define PTHREAD_MUTEX_UNLOCK(_x)
72 #endif
73
74 #define MAX_ATTRMAP     128
75
76 /*
77  *      A mapping of configuration file names to internal variables.
78  *
79  *      Note that the string is dynamically allocated, so it MUST
80  *      be freed.  When the configuration file parse re-reads the string,
81  *      it free's the old one, and strdup's the new one, placing the pointer
82  *      to the strdup'd string into 'config.string'.  This gets around
83  *      buffer over-flows.
84  */
85 static const CONF_PARSER module_config[] = {
86         { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_cache_t, key), NULL },
87         { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
88         { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "16384" },
89
90         /* Should be a type which matches time_t, @fixme before 2038 */
91         { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
92         { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
93
94         { NULL, -1, 0, NULL, NULL }             /* end the list */
95 };
96
97
98 /*
99  *      Compare two entries by key.  There may only be one entry with
100  *      the same key.
101  */
102 static int cache_entry_cmp(void const *one, void const *two)
103 {
104         rlm_cache_entry_t const *a = one;
105         rlm_cache_entry_t const *b = two;
106
107         return strcmp(a->key, b->key);
108 }
109
110 static void cache_entry_free(void *data)
111 {
112         rlm_cache_entry_t *c = data;
113
114         pairfree(&c->control);
115         pairfree(&c->packet);
116         pairfree(&c->reply);
117
118         talloc_free(c);
119 }
120
121 /*
122  *      Compare two entries by expiry time.  There may be multiple
123  *      entries with the same expiry time.
124  */
125 static int cache_heap_cmp(void const *one, void const *two)
126 {
127         rlm_cache_entry_t const *a = one;
128         rlm_cache_entry_t const *b = two;
129
130         if (a->expires < b->expires) return -1;
131         if (a->expires > b->expires) return +1;
132
133         return 0;
134 }
135
136 /*
137  *      Merge a cached entry into a REQUEST.
138  */
139 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
140 {
141         VALUE_PAIR *vp;
142
143         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
144         if (vp && (vp->vp_integer == 0)) {
145                 RDEBUG2("Told not to merge entry into request");
146                 return;
147         }
148
149         if (c->control) {
150                 RDEBUG2("Merging cached control list:");
151                 rdebug_pair_list(2, request, c->control);
152
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(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(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("Entry has expired, removing");
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 radius_map2request
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         /* radius_map2request 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->dst && map->src);
324
325                 if (radius_map2vp(&to_cache, request, map, NULL) < 0) {
326                         RDEBUG("Skipping %s", map->src->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->src->type) {
341                 case VPT_TYPE_LITERAL:
342                 case VPT_TYPE_XLAT:
343                 case VPT_TYPE_EXEC:
344                         break;
345
346                 case VPT_TYPE_LIST:
347                         if (map->src->vpt_list == map->dst->vpt_list) do_merge = false;
348                         break;
349
350                 case VPT_TYPE_ATTR:
351                         if (map->src->vpt_da == map->dst->vpt_da) do_merge = false;
352                         break;
353
354                 default:
355                         do_merge = false;
356                 }
357
358                 /*
359                  *      Reparent the VPs radius_map2vp may return multiple.
360                  */
361                 for (vp = fr_cursor_init(&src_list, &to_cache);
362                      vp;
363                      vp = fr_cursor_next(&src_list)) {
364                         VERIFY_VP(vp);
365
366                         /*
367                          *      Prevent people from accidentally caching
368                          *      cache control attributes.
369                          */
370                         if (map->src->type == VPT_TYPE_LIST) switch (vp->da->attr) {
371                         case PW_CACHE_TTL:
372                         case PW_CACHE_STATUS_ONLY:
373                         case PW_CACHE_READ_ONLY:
374                         case PW_CACHE_MERGE:
375                         case PW_CACHE_ENTRY_HITS:
376                                 RDEBUG2("Skipping %s", vp->da->name);
377                                 continue;
378
379                         default:
380                                 break;
381                         }
382
383                         if (debug_flag) radius_map_debug(request, map, vp);
384                         (void) talloc_steal(c, vp);
385
386                         vp->op = map->op;
387
388                         switch (map->dst->vpt_list) {
389                         case PAIR_LIST_REQUEST:
390                                 fr_cursor_insert(&cached_request, vp);
391                                 break;
392
393                         case PAIR_LIST_REPLY:
394                                 fr_cursor_insert(&cached_reply, vp);
395                                 break;
396
397                         case PAIR_LIST_CONTROL:
398                                 fr_cursor_insert(&cached_control, vp);
399                                 break;
400
401                         default:
402                                 rad_assert(0);  /* should have been caught by validation */
403                         }
404
405                         if (do_merge && radius_map_dst_valid(request, map)) {
406                                 /* There's no reason for this to fail (we checked the dst was valid) */
407                                 RDEBUG2("Adding to request:");
408                                 if (radius_map2request(request, map, _cache_add, vp) < 0) rad_assert(0);
409                         }
410                 }
411         }
412
413         if (!rbtree_insert(inst->cache, c)) {
414                 REDEBUG("FAILED adding entry for key %s", key);
415                 cache_entry_free(c);
416                 return NULL;
417         }
418
419         if (!fr_heap_insert(inst->heap, c)) {
420                 REDEBUG("FAILED adding entry for key %s", key);
421                 rbtree_deletebydata(inst->cache, c);
422                 return NULL;
423         }
424
425         RDEBUG("Inserted entry, TTL %d seconds", ttl);
426
427         return c;
428 }
429
430 /*
431  *      Verify that the cache section makes sense.
432  */
433 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
434 {
435         value_pair_map_t *map;
436
437         if (radius_attrmap(cf_section_sub_find(inst->cs, "update"),
438                            head, PAIR_LIST_REQUEST,
439                            PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
440                 return -1;
441         }
442
443         if (!*head) {
444                 cf_log_err_cs(inst->cs,
445                            "Cache config must contain an update section, and "
446                            "that section must not be empty");
447
448                 return -1;
449         }
450
451         for (map = *head; map != NULL; map = map->next) {
452                 if ((map->dst->type != VPT_TYPE_ATTR) &&
453                     (map->dst->type != VPT_TYPE_LIST)) {
454                         cf_log_err(map->ci, "Left operand must be an attribute "
455                                    "ref or a list");
456
457                         return -1;
458                 }
459
460                 /*
461                  *      Can't copy an xlat expansion or literal into a list,
462                  *      we don't know what type of attribute we'd need
463                  *      to create.
464                  *
465                  *      The only exception is where were using a unary
466                  *      operator like !*.
467                  */
468                 if ((map->dst->type == VPT_TYPE_LIST) &&
469                     (map->op != T_OP_CMP_FALSE) &&
470                     ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
471                         cf_log_err(map->ci, "Can't copy value into list (we don't know which attribute to create)");
472
473                         return -1;
474                 }
475
476                 switch (map->src->type) {
477                 case VPT_TYPE_EXEC:
478                         cf_log_err(map->ci, "Exec values are not allowed");
479
480                         return -1;
481
482                 /*
483                  *      Only =, :=, += and -= operators are supported for
484                  *      cache entries.
485                  */
486                 case VPT_TYPE_LITERAL:
487                         /*
488                          *      @fixme: This should be moved into a common function
489                          *      with the check in do_compile_modupdate.
490                          */
491                         if (map->dst->type == VPT_TYPE_ATTR) {
492                                 VALUE_PAIR *vp;
493                                 int ret;
494
495                                 MEM(vp = pairalloc(map->dst, map->dst->vpt_da));
496                                 vp->op = map->op;
497
498                                 ret = pairparsevalue(vp, map->src->name, 0);
499                                 talloc_free(vp);
500                                 if (ret < 0) {
501                                         cf_log_err(map->ci, "%s", fr_strerror());
502                                         return -1;
503                                 }
504                         }
505                         /* FALL-THROUGH */
506
507                 case VPT_TYPE_XLAT:
508                 case VPT_TYPE_ATTR:
509                         switch (map->op) {
510                         case T_OP_SET:
511                         case T_OP_EQ:
512                         case T_OP_SUB:
513                         case T_OP_ADD:
514                                 break;
515
516                         default:
517                                 cf_log_err(map->ci, "Operator \"%s\" not "
518                                            "allowed for %s values",
519                                            fr_int2str(fr_tokens, map->op,
520                                                       "<INVALID>"),
521                                            fr_int2str(vpt_types, map->src->type,
522                                                       "<INVALID>"));
523                                 return -1;
524                         }
525                 default:
526                         break;
527                 }
528         }
529         return 0;
530 }
531
532 /*
533  *      Allow single attribute values to be retrieved from the cache.
534  */
535 static ssize_t cache_xlat(void *instance, REQUEST *request,
536                           char const *fmt, char *out, size_t freespace)
537 {
538         rlm_cache_entry_t       *c;
539         rlm_cache_t             *inst = instance;
540         VALUE_PAIR              *vp, *vps;
541         pair_lists_t            list;
542         DICT_ATTR const         *target;
543         char const              *p = fmt;
544         size_t                  len;
545         int                     ret = 0;
546
547         list = radius_list_name(&p, PAIR_LIST_REQUEST);
548
549         target = dict_attrbyname(p);
550         if (!target) {
551                 REDEBUG("Unknown attribute \"%s\"", p);
552                 return -1;
553         }
554
555         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
556         c = cache_find(inst, request, fmt);
557
558         if (!c) {
559                 RDEBUG("No cache entry for key \"%s\"", fmt);
560                 *out = '\0';
561                 goto done;
562         }
563
564         switch (list) {
565         case PAIR_LIST_REQUEST:
566                 vps = c->packet;
567                 break;
568
569         case PAIR_LIST_REPLY:
570                 vps = c->reply;
571                 break;
572
573         case PAIR_LIST_CONTROL:
574                 vps = c->control;
575                 break;
576
577         case PAIR_LIST_UNKNOWN:
578                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
579                 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
580                 return -1;
581
582         default:
583                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
584                 REDEBUG("Unsupported list \"%s\"",
585                         fr_int2str(pair_lists, list, "<UNKNOWN>"));
586                 return -1;
587         }
588
589         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
590         if (!vp) {
591                 RDEBUG("No instance of this attribute has been cached");
592                 *out = '\0';
593                 goto done;
594         }
595
596         len = vp_prints_value(out, freespace, vp, 0);
597         if (is_truncated(len, freespace)) {
598                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
599                 REDEBUG("Insufficient buffer space to write cached value");
600                 return -1;
601         }
602 done:
603         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
604
605         return ret;
606 }
607
608 /*
609  *      Only free memory we allocated.  The strings allocated via
610  *      cf_section_parse() do not need to be freed.
611  */
612 static int mod_detach(void *instance)
613 {
614         rlm_cache_t *inst = instance;
615
616         talloc_free(inst->maps);
617
618         fr_heap_delete(inst->heap);
619         rbtree_free(inst->cache);
620
621 #ifdef HAVE_PTHREAD_H
622         pthread_mutex_destroy(&inst->cache_mutex);
623 #endif
624         return 0;
625 }
626
627
628 /*
629  *      Instantiate the module.
630  */
631 static int mod_instantiate(CONF_SECTION *conf, void *instance)
632 {
633         rlm_cache_t *inst = instance;
634
635         inst->cs = conf;
636
637         inst->xlat_name = cf_section_name2(conf);
638         if (!inst->xlat_name) {
639                 inst->xlat_name = cf_section_name1(conf);
640         }
641
642         /*
643          *      Register the cache xlat function
644          */
645         xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
646
647         rad_assert(inst->key && *inst->key);
648
649         if (inst->ttl == 0) {
650                 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
651                 return -1;
652         }
653
654         if (inst->epoch != 0) {
655                 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
656                 return -1;
657         }
658
659 #ifdef HAVE_PTHREAD_H
660         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
661                 ERROR("Failed initializing mutex: %s",
662                        fr_syserror(errno));
663                 return -1;
664         }
665 #endif
666
667         /*
668          *      The cache.
669          */
670
671         inst->cache = rbtree_create(NULL, cache_entry_cmp, cache_entry_free, 0);
672         if (!inst->cache) {
673                 ERROR("Failed to create cache");
674                 return -1;
675         }
676         fr_link_talloc_ctx_free(inst, inst->cache);
677
678         /*
679          *      The heap of entries to expire.
680          */
681         inst->heap = fr_heap_create(cache_heap_cmp,
682                                     offsetof(rlm_cache_entry_t, offset));
683         if (!inst->heap) {
684                 ERROR("Failed to create heap for the cache");
685                 return -1;
686         }
687
688         /*
689          *      Make sure the users don't screw up too badly.
690          */
691         if (cache_verify(inst, &inst->maps) < 0) {
692                 return -1;
693         }
694
695         return 0;
696 }
697
698 /*
699  *      Do caching checks.  Since we can update ANY VP list, we do
700  *      exactly the same thing for all sections (autz / auth / etc.)
701  *
702  *      If you want to cache something different in different sections,
703  *      configure another cache module.
704  */
705 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
706 {
707         rlm_cache_entry_t *c;
708         rlm_cache_t *inst = instance;
709         vp_cursor_t cursor;
710         VALUE_PAIR *vp;
711         char buffer[1024];
712         rlm_rcode_t rcode;
713
714         if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
715                 return RLM_MODULE_FAIL;
716         }
717
718         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
719         c = cache_find(inst, request, buffer);
720
721         /*
722          *      If yes, only return whether we found a valid cache entry
723          */
724         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
725         if (vp && vp->vp_integer) {
726                 rcode = c ? RLM_MODULE_OK:
727                             RLM_MODULE_NOTFOUND;
728                 goto done;
729         }
730
731         if (c) {
732                 cache_merge(inst, request, c);
733
734                 rcode = RLM_MODULE_OK;
735                 goto done;
736         }
737
738         vp = pairfind(request->config_items, PW_CACHE_READ_ONLY, 0, TAG_ANY);
739         if (vp && vp->vp_integer) {
740                 rcode = RLM_MODULE_NOTFOUND;
741                 goto done;
742         }
743
744         c = cache_add(inst, request, buffer);
745         if (!c) {
746                 rcode = RLM_MODULE_NOOP;
747                 goto done;
748         }
749
750         rcode = RLM_MODULE_UPDATED;
751
752 done:
753         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
754
755         /*
756          *      Reset control attributes
757          */
758         for (vp = fr_cursor_init(&cursor, &request->config_items);
759              vp;
760              vp = fr_cursor_next(&cursor)) {
761                 if (vp->da->vendor == 0) switch (vp->da->attr) {
762                 case PW_CACHE_TTL:
763                 case PW_CACHE_READ_ONLY:
764                 case PW_CACHE_MERGE:
765                         vp = fr_cursor_remove(&cursor);
766                         talloc_free(vp);
767                         break;
768                 }
769         }
770
771         return rcode;
772 }
773
774
775 /*
776  *      The module name should be the only globally exported symbol.
777  *      That is, everything else should be 'static'.
778  *
779  *      If the module needs to temporarily modify it's instantiation
780  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
781  *      The server will then take care of ensuring that the module
782  *      is single-threaded.
783  */
784 module_t rlm_cache = {
785         RLM_MODULE_INIT,
786         "cache",
787         0,                              /* type */
788         sizeof(rlm_cache_t),
789         module_config,
790         mod_instantiate,                /* instantiation */
791         mod_detach,                     /* detach */
792         {
793                 NULL,                   /* authentication */
794                 mod_cache_it,           /* authorization */
795                 mod_cache_it,           /* preaccounting */
796                 mod_cache_it,           /* accounting */
797                 NULL,                   /* checksimul */
798                 mod_cache_it,           /* pre-proxy */
799                 mod_cache_it,           /* post-proxy */
800                 mod_cache_it,           /* post-auth */
801         },
802 };