Require that the modules call talloc for their instance handle.
[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 #include <freeradius-devel/ident.h>
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/heap.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #define PW_CACHE_TTL            1140
32 #define PW_CACHE_STATUS_ONLY    1141
33 #define PW_CACHE_MERGE          1142
34 #define PW_CACHE_ENTRY_HITS     1143
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         const char              *xlat_name;
45         char                    *key;
46         int                     ttl;
47         int                     max_entries;
48         int                     epoch;
49         int                     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         const char      *key;
63         int             offset;
64         long long int   hits;
65         time_t          created;
66         time_t          expires;
67         VALUE_PAIR      *control;
68         VALUE_PAIR      *request;
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  *      Compare two entries by key.  There may only be one entry with
84  *      the same key.
85  */
86 static int cache_entry_cmp(const void *one, const void *two)
87 {
88         const rlm_cache_entry_t *a = one;
89         const rlm_cache_entry_t *b = two;
90
91         return strcmp(a->key, b->key);
92 }
93
94 static void cache_entry_free(void *data)
95 {
96         rlm_cache_entry_t *c = data;
97
98         pairfree(&c->control);
99         pairfree(&c->request);
100         pairfree(&c->reply);
101
102         talloc_free(c);
103 }
104
105 /*
106  *      Compare two entries by expiry time.  There may be multiple
107  *      entries with the same expiry time.
108  */
109 static int cache_heap_cmp(const void *one, const void *two)
110 {
111         const rlm_cache_entry_t *a = one;
112         const rlm_cache_entry_t *b = two;
113
114         if (a->expires < b->expires) return -1;
115         if (a->expires > b->expires) return +1;
116
117         return 0;
118 }
119
120 /*
121  *      Merge a cached entry into a REQUEST.
122  */
123 static void cache_merge(rlm_cache_t *inst, REQUEST *request,
124                         rlm_cache_entry_t *c)
125 {
126         VALUE_PAIR *vp;
127
128         rad_assert(request != NULL);
129         rad_assert(c != NULL);
130         
131         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
132         if (vp && (vp->vp_integer == 0)) {
133                 RDEBUG2("Told not to merge entry into request");
134                 return;
135         }
136
137         if (c->control) {
138                 RDEBUG2("Merging cached control list:");
139                 rdebug_pair_list(2, request, c->control);
140                 
141                 vp = paircopy(c->control);
142                 pairmove(&request->config_items, &vp);
143                 pairfree(&vp);
144         }
145
146         if (c->request && request->packet) {
147                 RDEBUG2("Merging cached request list:");
148                 rdebug_pair_list(2, request, c->request);
149                 
150                 vp = paircopy(c->request);
151                 pairmove(&request->packet->vps, &vp);
152                 pairfree(&vp);
153         }
154
155         if (c->reply && request->reply) {
156                 RDEBUG2("Merging cached reply list:");
157                 rdebug_pair_list(2, request, c->reply);
158                 
159                 vp = paircopy(c->reply);
160                 pairmove(&request->reply->vps, &vp);
161                 pairfree(&vp);
162         }
163         
164         if (inst->stats) {
165                 vp = paircreate(PW_CACHE_ENTRY_HITS, 0);
166                 rad_assert(vp != NULL);
167                 
168                 vp->vp_integer = c->hits;
169
170                 pairadd(&request->packet->vps, vp);
171         }
172 }
173
174
175 /*
176  *      Find a cached entry.
177  */
178 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
179                                      const char *key)
180 {
181         int ttl;
182         rlm_cache_entry_t *c, my_c;
183         VALUE_PAIR *vp;
184
185         /*
186          *      Look at the expiry heap.
187          */
188         c = fr_heap_peek(inst->heap);
189         if (!c) {
190                 rad_assert(rbtree_num_elements(inst->cache) == 0);
191                 return NULL;
192         }
193
194         /*
195          *      If it's time to expire an old entry, do so now.
196          */
197         if (c->expires < request->timestamp) {
198                 fr_heap_extract(inst->heap, c);
199                 rbtree_deletebydata(inst->cache, c);
200         }
201
202         /*
203          *      Is there an entry for this key?
204          */
205         my_c.key = key;
206         c = rbtree_finddata(inst->cache, &my_c);
207         if (!c) return NULL;
208
209         /*
210          *      Yes, but it expired, OR the "forget all" epoch has
211          *      passed.  Delete it, and pretend it doesn't exist.
212          */
213         if ((c->expires < request->timestamp) ||
214             (c->created < inst->epoch)) {
215         delete:
216                 RDEBUG("Entry has expired, removing");
217
218                 fr_heap_extract(inst->heap, c);
219                 rbtree_deletebydata(inst->cache, c);
220                 
221                 return NULL;
222         }
223
224         RDEBUG("Found entry for \"%s\"", key);
225
226         /*
227          *      Update the expiry time based on the TTL.
228          *      A TTL of 0 means "delete from the cache".
229          */
230         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
231         if (vp) {
232                 if (vp->vp_integer == 0) goto delete;
233                 
234                 ttl = vp->vp_integer;
235                 c->expires = request->timestamp + ttl;
236                 RDEBUG("Adding %d to the TTL", ttl);
237         }
238         c->hits++;
239
240         return c;
241 }
242
243
244 /*
245  *      Add an entry to the cache.
246  */
247 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
248                                     const char *key)
249 {
250         int ttl;
251         VALUE_PAIR *vp, *found, **to_req, **to_cache, **from;
252         const DICT_ATTR *da;
253
254         int merge = TRUE;
255         REQUEST *context;
256
257         const value_pair_map_t *map;
258
259         rlm_cache_entry_t *c;
260         char buffer[1024];
261
262         if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
263                 RDEBUG("Cache is full: %d entries", inst->max_entries);
264                 return NULL;
265         }
266
267         /*
268          *      TTL of 0 means "don't cache this entry"
269          */
270         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
271         if (vp && (vp->vp_integer == 0)) return NULL;
272
273         c = talloc_zero(NULL, rlm_cache_entry_t);
274         c->key = talloc_strdup(c, key);
275         c->created = c->expires = request->timestamp;
276
277         /*
278          *      Use per-entry TTL, or globally defined one.
279          */
280         if (vp) {
281                 ttl = vp->vp_integer;
282         } else {
283                 ttl = inst->ttl;
284         }
285         c->expires += ttl;
286
287         RDEBUG("Creating entry for \"%s\"", key);
288         
289         /*
290          *      Check to see if we need to merge the entry into the request
291          */
292         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
293         if (vp && (vp->vp_integer == 0)) {
294                 merge = FALSE;
295                 RDEBUG2("Told not to merge new entry into request");
296         }
297         
298         for (map = inst->maps; map != NULL; map = map->next) {
299                 rad_assert(map->dst && map->src);
300
301                 /* 
302                  *      Specifying inner/outer request doesn't work here
303                  *      but there's no easy fix...
304                  */
305                 switch (map->dst->list) {
306                 case PAIR_LIST_REQUEST:
307                         to_cache = &c->request;
308                         break;
309                         
310                 case PAIR_LIST_REPLY:
311                         to_cache = &c->reply;
312                         break;
313                         
314                 case PAIR_LIST_CONTROL:
315                         to_cache = &c->control;
316                         break;
317
318                 default:
319                         rad_assert(0); 
320                         return NULL;            
321                 }
322         
323                 /*
324                  *      Resolve the destination in the current request.
325                  *      We need to add the to_cache there too if any of these
326                  *      are.
327                  *      true :
328                  *        - Map specifies an xlat'd string.
329                  *        - Map specifies a literal string.
330                  *        - Map src and dst lists differ.
331                  *        - Map src and dst attributes differ
332                  */
333                 to_req = NULL;
334                 if (merge && ( !map->src->da || 
335                     (map->src->list != map->dst->list) ||
336                     (map->src->da != map->dst->da))) {
337                         context = request;
338                         /*
339                          *      It's ok if the list isn't valid here...
340                          *      It might be valid later when we merge 
341                          *      the cache entry.
342                          */
343                         if (radius_request(&context, map->dst->request) == 0) {
344                                 to_req = radius_list(context, map->dst->list);
345                         }
346                 }
347         
348                 /*
349                  *      We infer that src was an attribute ref from the fact
350                  *      it contains a da.
351                  */
352                 RDEBUG4(":: dst is \"%s\" src is \"%s\"", 
353                         fr_int2str(vpt_types, map->dst->type, "¿unknown?"),
354                         fr_int2str(vpt_types, map->src->type, "¿unknown?"));
355                         
356                 switch (map->src->type)
357                 {
358                 case VPT_TYPE_ATTR:
359                         from = NULL;
360                         da = map->src->da;
361                         context = request;
362                         if (radius_request(&context, map->src->request) == 0) {
363                                 from = radius_list(context, map->src->list);
364                         }
365                         
366                         /*
367                          *      Can't add the attribute if the list isn't
368                          *      valid.
369                          */
370                         if (!from) continue;
371
372                         found = pairfind(*from, da->attr, da->vendor, TAG_ANY);
373                         if (!found) {
374                                 RDEBUGW("\"%s\" not found, skipping",
375                                        map->src->name);
376                                 continue;
377                         }
378                         
379                         RDEBUG("\t%s %s %s", map->dst->name,
380                                fr_int2str(fr_tokens, map->op, "¿unknown?"),
381                                map->src->name);
382                         
383                         switch (map->op) {
384                         case T_OP_SET:
385                         case T_OP_EQ:
386                         case T_OP_SUB:
387                                 vp = map->dst->type == VPT_TYPE_LIST ?
388                                         paircopyvp(found) :
389                                         paircopyvpdata(map->dst->da, found);
390                                 
391                                 if (!vp) continue;
392                                 
393                                 pairadd(to_cache, vp);
394
395                                 if (to_req) {
396                                         vp = paircopyvp(vp);
397                                         radius_pairmove(request, to_req, vp);                   
398                                 }
399                                 
400                                 break;
401                         case T_OP_ADD:
402                                 do {
403                                         vp = map->dst->type == VPT_TYPE_LIST ?
404                                                 paircopyvp(found) :
405                                                 paircopyvpdata(map->dst->da,
406                                                                found);
407                                         if (!vp) continue;
408                                         
409                                         vp->op = map->op;
410                                         pairadd(to_cache, vp);
411                                         
412                                         if (to_req) {
413                                                 vp = paircopyvp(vp);
414                                                 radius_pairmove(request, to_req,
415                                                                 vp);
416                                                                 
417                                         }
418                                         
419                                         found = pairfind(found->next,
420                                                          da->attr,
421                                                          da->vendor,
422                                                          TAG_ANY);
423                                 } while (found);
424                                                                 
425                                 break;
426                                 
427                         default:
428                                 rad_assert(0);
429                                 return NULL;
430                         }
431                         break;
432                 case VPT_TYPE_LIST:
433                         rad_assert(map->src->type == VPT_TYPE_LIST);
434                         
435                         from = NULL;
436                         context = request;
437                         if (radius_request(&context, map->src->request) == 0) {
438                                 from = radius_list(context, map->src->list);
439                         }
440                         if (!from) continue;
441                         
442                         found = paircopy(*from);
443                         if (!found) continue;
444                         
445                         for (vp = found; vp != NULL; vp = vp->next) {
446                                 RDEBUG("\t%s %s %s (%s)", map->dst->name,
447                                        fr_int2str(fr_tokens, map->op,
448                                                   "¿unknown?"),
449                                        map->src->name,
450                                        vp->da->name);
451                                 vp->op = map->op;
452                         }
453                         
454                         pairadd(to_cache, found);
455                         
456                         if (to_req) {
457                                 vp = paircopy(found);
458                                 radius_pairmove(request, to_req, vp);
459                         }
460                         
461                         break;
462                 /*
463                  *      It was most likely a double quoted string that now
464                  *      needs to be expanded.
465                  */
466                 case VPT_TYPE_XLAT:
467                         if (radius_xlat(buffer, sizeof(buffer), map->src->name,
468                                         request, NULL, NULL) <= 0) {
469                                 continue;               
470                         }
471
472                         RDEBUG("\t%s %s \"%s\"", map->dst->name,
473                                fr_int2str(fr_tokens, map->op, "¿unknown?"),
474                                buffer);
475
476                         vp = pairalloc(NULL, map->dst->da);
477                         if (!vp) continue;
478                         
479                         vp->op = map->op;
480                         if (!pairparsevalue(vp, buffer)) {
481                                 pairfree(&vp);
482                                 continue;
483                         }
484                         
485                         pairadd(to_cache, vp);
486                         
487                         if (to_req) {
488                                 vp = paircopyvp(vp);
489                                 radius_pairmove(request, to_req, vp);                   
490                         }
491                         
492                         break;
493                 /*
494                  *      Literal string.
495                  */
496                 case VPT_TYPE_LITERAL:
497                         RDEBUG("\t%s %s '%s'", map->dst->name,
498                                fr_int2str(fr_tokens, map->op, "¿unknown?"),
499                                map->src->name);
500                                
501                         vp = pairalloc(NULL, map->dst->da);
502                         if (!vp) continue;
503                         
504                         vp->op = map->op;
505                         if (!pairparsevalue(vp, map->src->name)) {
506                                 pairfree(&vp);
507                                 continue;
508                         }
509                         
510                         pairadd(to_cache, vp);
511                         
512                         if (to_req) {
513                                 vp = paircopyvp(vp);
514                                 radius_pairmove(request, to_req, vp);   
515                         }
516                         
517                         break;
518                         
519                 default:
520                         rad_assert(0);
521                         return NULL;
522                 }
523         }
524         
525         if (!rbtree_insert(inst->cache, c)) {
526                 radlog(L_ERR, "rlm_cache: FAILED adding entry for key %s", key);
527                 cache_entry_free(c);
528                 return NULL;
529         }
530
531         if (!fr_heap_insert(inst->heap, c)) {
532                 radlog(L_ERR, "rlm_cache: FAILED adding entry for key %s", key);
533                 rbtree_deletebydata(inst->cache, c);
534                 return NULL;
535         }
536
537         RDEBUG("Inserted entry, TTL %d seconds", ttl);
538
539         return c;
540 }
541
542 /*
543  *      Verify that the cache section makes sense.
544  */
545 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
546 {
547         value_pair_map_t *map;
548
549         if (radius_attrmap(inst->cs, head, PAIR_LIST_REQUEST,
550                            PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
551                 return -1;                 
552         }
553         
554         if (!*head) {
555                 cf_log_err(cf_sectiontoitem(inst->cs),
556                            "Cache config must contain an update section, and "
557                            "that section must not be empty");
558
559                 return -1;
560         }
561
562         for (map = *head; map != NULL; map = map->next) {
563                 if ((map->dst->type != VPT_TYPE_ATTR) &&
564                     (map->dst->type != VPT_TYPE_LIST)) {
565                         cf_log_err(map->ci, "Left operand must be an attribute "
566                                    "ref or a list");
567                            
568                         return -1;
569                 }
570         
571                 switch (map->src->type) 
572                 {
573                 /*
574                  *      Only =, :=, += and -= operators are supported for
575                  *      cache entries.
576                  */
577                 case VPT_TYPE_LITERAL:
578                 case VPT_TYPE_XLAT:
579                 case VPT_TYPE_ATTR:
580                         switch (map->op) {
581                         case T_OP_SET:
582                         case T_OP_EQ:
583                         case T_OP_SUB:
584                         case T_OP_ADD:
585                                 break;
586                 
587                         default:
588                                 cf_log_err(map->ci, "Operator \"%s\" not "
589                                            "allowed for %s values",
590                                            fr_int2str(fr_tokens, map->op,
591                                                       "¿unknown?"),
592                                            fr_int2str(vpt_types, map->src->type,
593                                                       "¿unknown?"));
594                                 return -1;
595                         }
596                 default:
597                         break;
598                 }
599         }
600         return 0;
601 }
602
603 /*
604  *      Allow single attribute values to be retrieved from the cache.
605  */
606 static size_t cache_xlat(void *instance, REQUEST *request,
607                          const char *fmt, char *out, size_t freespace)
608 {
609         rlm_cache_entry_t *c;
610         rlm_cache_t *inst = instance;
611         VALUE_PAIR *vp, *vps;
612         pair_lists_t list;
613         const DICT_ATTR *target;
614         const char *p = fmt;
615         char buffer[1024];
616         int ret = 0;
617
618         radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
619
620         list = radius_list_name(&p, PAIR_LIST_REQUEST);
621         
622         target = dict_attrbyname(p);
623         if (!target) {
624                 radlog(L_ERR, "rlm_cache: Unknown attribute \"%s\"", p);
625                 return -1;
626         }
627         
628         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
629         c = cache_find(inst, request, buffer);
630         
631         if (!c) {
632                 RDEBUG("No cache entry for key \"%s\"", buffer);
633                 goto done;
634         }
635
636         switch (list) {
637         case PAIR_LIST_REQUEST:
638                 vps = c->request;
639                 break;
640                 
641         case PAIR_LIST_REPLY:
642                 vps = c->reply;
643                 break;
644                 
645         case PAIR_LIST_CONTROL:
646                 vps = c->control;
647                 break;
648                 
649         case PAIR_LIST_UNKNOWN:
650                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
651                 radlog(L_ERR, "rlm_cache: Unknown list qualifier in \"%s\"", fmt);
652                 return 0;
653                 
654         default:
655                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
656                 radlog(L_ERR, "rlm_cache: Unsupported list \"%s\"",
657                        fr_int2str(pair_lists, list, "¿Unknown?"));
658                 return 0;
659         }
660
661         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
662         if (!vp) {
663                 RDEBUG("No instance of this attribute has been cached");
664                 goto done;
665         }
666         
667         ret = vp_prints_value(out, freespace, vp, 0);
668 done:
669         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
670         
671         return ret;
672 }
673
674 /*
675  *      A mapping of configuration file names to internal variables.
676  *
677  *      Note that the string is dynamically allocated, so it MUST
678  *      be freed.  When the configuration file parse re-reads the string,
679  *      it free's the old one, and strdup's the new one, placing the pointer
680  *      to the strdup'd string into 'config.string'.  This gets around
681  *      buffer over-flows.
682  */
683 static const CONF_PARSER module_config[] = {
684         { "key",  PW_TYPE_STRING_PTR,
685           offsetof(rlm_cache_t, key), NULL, NULL},
686         { "ttl", PW_TYPE_INTEGER,
687           offsetof(rlm_cache_t, ttl), NULL, "500" },
688         { "max_entries", PW_TYPE_INTEGER,
689           offsetof(rlm_cache_t, max_entries), NULL, "16384" },
690         { "epoch", PW_TYPE_INTEGER,
691           offsetof(rlm_cache_t, epoch), NULL, "0" },
692         { "add_stats", PW_TYPE_BOOLEAN,
693           offsetof(rlm_cache_t, stats), NULL, "no" },
694   
695         { NULL, -1, 0, NULL, NULL }             /* end the list */
696 };
697
698
699 /*
700  *      Only free memory we allocated.  The strings allocated via
701  *      cf_section_parse() do not need to be freed.
702  */
703 static int cache_detach(void *instance)
704 {
705         rlm_cache_t *inst = instance;
706         
707         radius_mapfree(&inst->maps);
708
709         fr_heap_delete(inst->heap);
710         rbtree_free(inst->cache);
711
712 #ifdef HAVE_PTHREAD_H
713         pthread_mutex_destroy(&inst->cache_mutex);
714 #endif
715         return 0;
716 }
717
718
719 /*
720  *      Instantiate the module.
721  */
722 static int cache_instantiate(CONF_SECTION *conf, void **instance)
723 {
724         rlm_cache_t *inst;
725
726         *instance = inst = talloc_zero(conf, rlm_cache_t);
727         inst->cs = conf;
728         
729         /*
730          *      If the configuration parameters can't be parsed, then
731          *      fail.
732          */
733         if (cf_section_parse(conf, inst, module_config) < 0) {
734                 return -1;
735         }
736
737         inst->xlat_name = cf_section_name2(conf);
738         if (!inst->xlat_name) {
739                 inst->xlat_name = cf_section_name1(conf);
740         }
741
742         /*
743          *      Register the cache xlat function
744          */
745         xlat_register(inst->xlat_name, cache_xlat, inst);
746
747         if (!inst->key || !*inst->key) {
748                 radlog(L_ERR, "rlm_cache: You must specify a key");
749                 return -1;
750         }
751
752         if (inst->ttl == 0) {
753                 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
754                 return -1;
755         }
756         
757         if (inst->epoch != 0){
758                 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
759                 return -1;
760         }
761
762 #ifdef HAVE_PTHREAD_H
763         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
764                 radlog(L_ERR, "rlm_cache: Failed initializing mutex: %s",
765                        strerror(errno));
766                 return -1;
767         }
768 #endif
769
770         /*
771          *      The cache.
772          */
773         inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
774         if (!inst->cache) {
775                 radlog(L_ERR, "rlm_cache: Failed to create cache");
776                 return -1;
777         }
778
779         /*
780          *      The heap of entries to expire.
781          */
782         inst->heap = fr_heap_create(cache_heap_cmp,
783                                     offsetof(rlm_cache_entry_t, offset));
784         if (!inst->heap) {
785                 radlog(L_ERR, "rlm_cache: Failed to create cache");
786                 return -1;
787         }
788
789         /*
790          *      Make sure the users don't screw up too badly.
791          */
792         if (cache_verify(inst, &inst->maps) < 0) {
793                 return -1;
794         }
795
796         return 0;
797 }
798
799 /*
800  *      Do caching checks.  Since we can update ANY VP list, we do
801  *      exactly the same thing for all sections (autz / auth / etc.)
802  *
803  *      If you want to cache something different in different sections,
804  *      configure another cache module.
805  */
806 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
807 {
808         rlm_cache_entry_t *c;
809         rlm_cache_t *inst = instance;
810         VALUE_PAIR *vp;
811         char buffer[1024];
812         int rcode;
813
814         radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
815
816         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
817         c = cache_find(inst, request, buffer);
818         
819         /*
820          *      If yes, only return whether we found a valid cache entry
821          */
822         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
823         if (vp && vp->vp_integer) {
824                 rcode = c ? RLM_MODULE_OK:
825                             RLM_MODULE_NOTFOUND;
826                 goto done;
827         }
828         
829         if (c) {
830                 cache_merge(inst, request, c);
831                 
832                 rcode = RLM_MODULE_OK;
833                 goto done;
834         }
835
836         c = cache_add(inst, request, buffer);
837         if (!c) {
838                 rcode = RLM_MODULE_NOOP;
839                 goto done;
840         }
841
842         rcode = RLM_MODULE_UPDATED;
843         
844 done:
845         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
846         return rcode;
847 }
848
849
850 /*
851  *      The module name should be the only globally exported symbol.
852  *      That is, everything else should be 'static'.
853  *
854  *      If the module needs to temporarily modify it's instantiation
855  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
856  *      The server will then take care of ensuring that the module
857  *      is single-threaded.
858  */
859 module_t rlm_cache = {
860         RLM_MODULE_INIT,
861         "cache",
862         0,                              /* type */
863         cache_instantiate,              /* instantiation */
864         cache_detach,                   /* detach */
865         {
866                 NULL,                   /* authentication */
867                 cache_it,               /* authorization */
868                 cache_it,               /* preaccounting */
869                 cache_it,               /* accounting */
870                 NULL,                   /* checksimul */
871                 cache_it,               /* pre-proxy */
872                 cache_it,               /* post-proxy */
873                 cache_it,               /* post-auth */
874         },
875 };