Allocate from the local map ctx
[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         bool 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_typed_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                                 fr_cursor_init(&cursor, from);
370                                 found = fr_cursor_next_by_num(&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 = fr_cursor_next_by_num(&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                                 fr_cursor_init(&out, &found);
439                                 for (i = fr_cursor_init(&in, from);
440                                      i != NULL;
441                                      i = fr_cursor_next(&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                                         fr_cursor_insert(&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(map->dst, 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(map->dst, 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                 /*
586                  *      Can't copy an xlat expansion or literal into a list,
587                  *      we don't know what type of attribute we'd need
588                  *      to create.
589                  *
590                  *      The only exception is where were using a unary
591                  *      operator like !*.
592                  */
593                 if ((map->dst->type == VPT_TYPE_LIST) &&
594                     (map->op != T_OP_CMP_FALSE) &&
595                     ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
596                         cf_log_err(map->ci, "Can't copy value into list (we don't know which attribute to create)");
597
598                         return -1;
599                 }
600
601                 switch (map->src->type) {
602                 case VPT_TYPE_EXEC:
603                         cf_log_err(map->ci, "Exec values are not allowed");
604
605                         return -1;
606
607                 /*
608                  *      Only =, :=, += and -= operators are supported for
609                  *      cache entries.
610                  */
611                 case VPT_TYPE_LITERAL:
612                         /*
613                          *      @fixme: This should be moved into a common function
614                          *      with the check in do_compile_modupdate.
615                          */
616                         if (map->dst->type == VPT_TYPE_ATTR) {
617                                 VALUE_PAIR *vp;
618                                 bool ret;
619
620                                 MEM(vp = pairalloc(map->dst, map->dst->da));
621                                 vp->op = map->op;
622
623                                 ret = pairparsevalue(vp, map->src->name);
624                                 talloc_free(vp);
625                                 if (!ret) {
626                                         cf_log_err(map->ci, "%s", fr_strerror());
627                                         return -1;
628                                 }
629                         }
630                         /* FALL-THROUGH */
631
632                 case VPT_TYPE_XLAT:
633                 case VPT_TYPE_ATTR:
634                         switch (map->op) {
635                         case T_OP_SET:
636                         case T_OP_EQ:
637                         case T_OP_SUB:
638                         case T_OP_ADD:
639                                 break;
640
641                         default:
642                                 cf_log_err(map->ci, "Operator \"%s\" not "
643                                            "allowed for %s values",
644                                            fr_int2str(fr_tokens, map->op,
645                                                       "<INVALID>"),
646                                            fr_int2str(vpt_types, map->src->type,
647                                                       "<INVALID>"));
648                                 return -1;
649                         }
650                 default:
651                         break;
652                 }
653         }
654         return 0;
655 }
656
657 /*
658  *      Allow single attribute values to be retrieved from the cache.
659  */
660 static ssize_t cache_xlat(void *instance, REQUEST *request,
661                           char const *fmt, char *out, size_t freespace)
662 {
663         rlm_cache_entry_t       *c;
664         rlm_cache_t             *inst = instance;
665         VALUE_PAIR              *vp, *vps;
666         pair_lists_t            list;
667         DICT_ATTR const         *target;
668         char const              *p = fmt;
669         size_t                  len;
670         int                     ret = 0;
671
672         list = radius_list_name(&p, PAIR_LIST_REQUEST);
673
674         target = dict_attrbyname(p);
675         if (!target) {
676                 REDEBUG("Unknown attribute \"%s\"", p);
677                 return -1;
678         }
679
680         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
681         c = cache_find(inst, request, fmt);
682
683         if (!c) {
684                 RDEBUG("No cache entry for key \"%s\"", fmt);
685                 *out = '\0';
686                 goto done;
687         }
688
689         switch (list) {
690         case PAIR_LIST_REQUEST:
691                 vps = c->packet;
692                 break;
693
694         case PAIR_LIST_REPLY:
695                 vps = c->reply;
696                 break;
697
698         case PAIR_LIST_CONTROL:
699                 vps = c->control;
700                 break;
701
702         case PAIR_LIST_UNKNOWN:
703                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
704                 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
705                 return -1;
706
707         default:
708                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
709                 REDEBUG("Unsupported list \"%s\"",
710                         fr_int2str(pair_lists, list, "¿Unknown?"));
711                 return -1;
712         }
713
714         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
715         if (!vp) {
716                 RDEBUG("No instance of this attribute has been cached");
717                 *out = '\0';
718                 goto done;
719         }
720
721         len = vp_prints_value(out, freespace, vp, 0);
722         if (is_truncated(len, freespace)) {
723                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
724                 REDEBUG("Insufficient buffer space to write cached value");
725                 return -1;
726         }
727 done:
728         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
729
730         return ret;
731 }
732
733 /*
734  *      A mapping of configuration file names to internal variables.
735  *
736  *      Note that the string is dynamically allocated, so it MUST
737  *      be freed.  When the configuration file parse re-reads the string,
738  *      it free's the old one, and strdup's the new one, placing the pointer
739  *      to the strdup'd string into 'config.string'.  This gets around
740  *      buffer over-flows.
741  */
742 static const CONF_PARSER module_config[] = {
743         { "key",  PW_TYPE_STRING_PTR | PW_TYPE_REQUIRED,
744           offsetof(rlm_cache_t, key), NULL, NULL},
745         { "ttl", PW_TYPE_INTEGER,
746           offsetof(rlm_cache_t, ttl), NULL, "500" },
747         { "max_entries", PW_TYPE_INTEGER,
748           offsetof(rlm_cache_t, max_entries), NULL, "16384" },
749         { "epoch", PW_TYPE_INTEGER,
750           offsetof(rlm_cache_t, epoch), NULL, "0" },
751         { "add_stats", PW_TYPE_BOOLEAN,
752           offsetof(rlm_cache_t, stats), NULL, "no" },
753
754         { NULL, -1, 0, NULL, NULL }             /* end the list */
755 };
756
757
758 /*
759  *      Only free memory we allocated.  The strings allocated via
760  *      cf_section_parse() do not need to be freed.
761  */
762 static int mod_detach(void *instance)
763 {
764         rlm_cache_t *inst = instance;
765
766         talloc_free(inst->maps);
767
768         fr_heap_delete(inst->heap);
769         rbtree_free(inst->cache);
770
771 #ifdef HAVE_PTHREAD_H
772         pthread_mutex_destroy(&inst->cache_mutex);
773 #endif
774         return 0;
775 }
776
777
778 /*
779  *      Instantiate the module.
780  */
781 static int mod_instantiate(CONF_SECTION *conf, void *instance)
782 {
783         rlm_cache_t *inst = instance;
784
785         inst->cs = conf;
786
787         inst->xlat_name = cf_section_name2(conf);
788         if (!inst->xlat_name) {
789                 inst->xlat_name = cf_section_name1(conf);
790         }
791
792         /*
793          *      Register the cache xlat function
794          */
795         xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
796
797         rad_assert(inst->key && *inst->key);
798
799         if (inst->ttl == 0) {
800                 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
801                 return -1;
802         }
803
804         if (inst->epoch != 0) {
805                 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
806                 return -1;
807         }
808
809 #ifdef HAVE_PTHREAD_H
810         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
811                 EDEBUG("Failed initializing mutex: %s",
812                        fr_syserror(errno));
813                 return -1;
814         }
815 #endif
816
817         /*
818          *      The cache.
819          */
820         inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
821         if (!inst->cache) {
822                 EDEBUG("Failed to create cache");
823                 return -1;
824         }
825
826         /*
827          *      The heap of entries to expire.
828          */
829         inst->heap = fr_heap_create(cache_heap_cmp,
830                                     offsetof(rlm_cache_entry_t, offset));
831         if (!inst->heap) {
832                 EDEBUG("Failed to create heap for the cache");
833                 return -1;
834         }
835
836         /*
837          *      Make sure the users don't screw up too badly.
838          */
839         if (cache_verify(inst, &inst->maps) < 0) {
840                 return -1;
841         }
842
843         return 0;
844 }
845
846 /*
847  *      Do caching checks.  Since we can update ANY VP list, we do
848  *      exactly the same thing for all sections (autz / auth / etc.)
849  *
850  *      If you want to cache something different in different sections,
851  *      configure another cache module.
852  */
853 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
854 {
855         rlm_cache_entry_t *c;
856         rlm_cache_t *inst = instance;
857         VALUE_PAIR *vp;
858         char buffer[1024];
859         rlm_rcode_t rcode;
860
861         if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
862                 return RLM_MODULE_FAIL;
863         }
864
865         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
866         c = cache_find(inst, request, buffer);
867
868         /*
869          *      If yes, only return whether we found a valid cache entry
870          */
871         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
872         if (vp && vp->vp_integer) {
873                 rcode = c ? RLM_MODULE_OK:
874                             RLM_MODULE_NOTFOUND;
875                 goto done;
876         }
877
878         if (c) {
879                 cache_merge(inst, request, c);
880
881                 rcode = RLM_MODULE_OK;
882                 goto done;
883         }
884
885         c = cache_add(inst, request, buffer);
886         if (!c) {
887                 rcode = RLM_MODULE_NOOP;
888                 goto done;
889         }
890
891         rcode = RLM_MODULE_UPDATED;
892
893 done:
894         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
895         return rcode;
896 }
897
898
899 /*
900  *      The module name should be the only globally exported symbol.
901  *      That is, everything else should be 'static'.
902  *
903  *      If the module needs to temporarily modify it's instantiation
904  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
905  *      The server will then take care of ensuring that the module
906  *      is single-threaded.
907  */
908 module_t rlm_cache = {
909         RLM_MODULE_INIT,
910         "cache",
911         0,                              /* type */
912         sizeof(rlm_cache_t),
913         module_config,
914         mod_instantiate,                /* instantiation */
915         mod_detach,                     /* detach */
916         {
917                 NULL,                   /* authentication */
918                 cache_it,               /* authorization */
919                 cache_it,               /* preaccounting */
920                 cache_it,               /* accounting */
921                 NULL,                   /* checksimul */
922                 cache_it,               /* pre-proxy */
923                 cache_it,               /* post-proxy */
924                 cache_it,               /* post-auth */
925         },
926 };