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