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