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