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