Remove asserts which were used to mask scan-build errors. Fix RDEBUG macros so they...
[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 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                                 context = request;
355                                 if (radius_request(&context, map->src->vpt_request) == 0) {
356                                         from = radius_list(context, map->src->vpt_list);
357                                 }
358
359                                 /*
360                                  *      Can't add the attribute if the list isn't
361                                  *      valid.
362                                  */
363                                 if (!from) continue;
364
365                                 fr_cursor_init(&cursor, from);
366                                 found = fr_cursor_next_by_num(&cursor, da->attr, da->vendor, TAG_ANY);
367                                 if (!found) {
368                                         RWDEBUG("\"%s\" not found, skipping",
369                                                map->src->name);
370                                         continue;
371                                 }
372
373                                 RDEBUG("\t%s %s &%s", map->dst->name,
374                                        fr_int2str(fr_tokens, map->op, "<INVALID>"),
375                                        map->src->name);
376
377                                 switch (map->op) {
378                                 case T_OP_SET:
379                                 case T_OP_EQ:
380                                 case T_OP_SUB:
381                                         vp = map->dst->type == VPT_TYPE_LIST ?
382                                                 paircopyvp(c, found) :
383                                                 paircopyvpdata(c, map->dst->vpt_da, found);
384
385                                         if (!vp) continue;
386
387                                         pairadd(to_cache, vp);
388
389                                         if (to_req) {
390                                                 vp = paircopyvp(request, vp);
391                                                 radius_pairmove(request, to_req, vp, false);
392                                         }
393
394                                         break;
395                                 case T_OP_ADD:
396                                         do {
397                                                 vp = map->dst->type == VPT_TYPE_LIST ?
398                                                         paircopyvp(c, found) :
399                                                         paircopyvpdata(c, map->dst->vpt_da, found);
400                                                 if (!vp) continue;
401
402                                                 vp->op = map->op;
403                                                 pairadd(to_cache, vp);
404
405                                                 if (to_req) {
406                                                         vp = paircopyvp(request, vp);
407                                                         radius_pairmove(request, to_req, vp, false);
408
409                                                 }
410                                         } while ((found = fr_cursor_next_by_num(&cursor, da->attr, da->vendor, TAG_ANY)));
411                                         break;
412
413                                 default:
414                                         rad_assert(0);
415                                         return NULL;
416                                 }
417                                 break;
418                         }
419                 case VPT_TYPE_LIST:
420                         {
421                                 vp_cursor_t in, out;
422                                 VALUE_PAIR *i;
423
424                                 rad_assert(map->src->type == VPT_TYPE_LIST);
425
426                                 from = NULL;
427                                 context = request;
428                                 if (radius_request(&context, map->src->vpt_request) == 0) {
429                                         from = radius_list(context, map->src->vpt_list);
430                                 }
431                                 if (!from) continue;
432
433                                 found = NULL;
434                                 fr_cursor_init(&out, &found);
435                                 for (i = fr_cursor_init(&in, from);
436                                      i != NULL;
437                                      i = fr_cursor_next(&in)) {
438                                         /*
439                                          *      Prevent cache control attributes being added to the cache.
440                                          */
441                                         switch (i->da->attr) {
442                                         case PW_CACHE_TTL:
443                                         case PW_CACHE_STATUS_ONLY:
444                                         case PW_CACHE_MERGE:
445                                         case PW_CACHE_ENTRY_HITS:
446                                                 RDEBUG("\tskipping %s", i->da->name);
447                                                 continue;
448                                         default:
449                                                 break;
450                                         }
451
452                                         vp = paircopyvp(c, i);
453                                         if (!vp) {
454                                                 pairfree(&found);
455                                                 return NULL;
456                                         }
457                                         RDEBUG("\t%s %s &%s:%s", map->dst->name,
458                                                fr_int2str(fr_tokens, map->op, "<INVALID>"),
459                                                fr_int2str(pair_lists, map->src->vpt_list, "<INVALID>"), vp->da->name);
460                                         vp->op = map->op;
461                                         fr_cursor_insert(&out, vp);
462                                 }
463
464                                 pairadd(to_cache, found);
465                                 if (to_req) {
466                                         vp = paircopy(request, found);
467                                         radius_pairmove(request, to_req, vp, false);
468                                 }
469
470                                 break;
471                         }
472                 /*
473                  *      It was most likely a double quoted string that now
474                  *      needs to be expanded.
475                  */
476                 case VPT_TYPE_XLAT:
477                         if (radius_xlat(buffer, sizeof(buffer), request, map->src->name, NULL, NULL) <= 0) {
478                                 continue;
479                         }
480
481                         RDEBUG("\t%s %s \"%s\"", map->dst->name,
482                                fr_int2str(fr_tokens, map->op, "<INVALID>"),
483                                buffer);
484
485                         vp = pairalloc(map->dst, map->dst->vpt_da);
486                         if (!vp) continue;
487
488                         vp->op = map->op;
489                         if (!pairparsevalue(vp, buffer)) {
490                                 pairfree(&vp);
491                                 continue;
492                         }
493
494                         pairadd(to_cache, vp);
495
496                         if (to_req) {
497                                 vp = paircopyvp(request, vp);
498                                 radius_pairmove(request, to_req, vp, false);
499                         }
500
501                         break;
502                 /*
503                  *      Literal string.
504                  */
505                 case VPT_TYPE_LITERAL:
506                         RDEBUG("\t%s %s '%s'", map->dst->name,
507                                fr_int2str(fr_tokens, map->op, "<INVALID>"),
508                                map->src->name);
509
510                         vp = pairalloc(map->dst, map->dst->vpt_da);
511                         if (!vp) continue;
512
513                         vp->op = map->op;
514                         if (!pairparsevalue(vp, map->src->name)) {
515                                 pairfree(&vp);
516                                 continue;
517                         }
518
519                         pairadd(to_cache, vp);
520
521                         if (to_req) {
522                                 vp = paircopyvp(request, vp);
523                                 radius_pairmove(request, to_req, vp, false);
524                         }
525
526                         break;
527
528                 default:
529                         rad_assert(0);
530                         return NULL;
531                 }
532         }
533
534         if (!rbtree_insert(inst->cache, c)) {
535                 REDEBUG("FAILED adding entry for key %s", key);
536                 cache_entry_free(c);
537                 return NULL;
538         }
539
540         if (!fr_heap_insert(inst->heap, c)) {
541                 REDEBUG("FAILED adding entry for key %s", key);
542                 rbtree_deletebydata(inst->cache, c);
543                 return NULL;
544         }
545
546         RDEBUG("Inserted entry, TTL %d seconds", ttl);
547
548         return c;
549 }
550
551 /*
552  *      Verify that the cache section makes sense.
553  */
554 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
555 {
556         value_pair_map_t *map;
557
558         if (radius_attrmap(cf_section_sub_find(inst->cs, "update"),
559                            head, PAIR_LIST_REQUEST,
560                            PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
561                 return -1;
562         }
563
564         if (!*head) {
565                 cf_log_err_cs(inst->cs,
566                            "Cache config must contain an update section, and "
567                            "that section must not be empty");
568
569                 return -1;
570         }
571
572         for (map = *head; map != NULL; map = map->next) {
573                 if ((map->dst->type != VPT_TYPE_ATTR) &&
574                     (map->dst->type != VPT_TYPE_LIST)) {
575                         cf_log_err(map->ci, "Left operand must be an attribute "
576                                    "ref or a list");
577
578                         return -1;
579                 }
580
581                 /*
582                  *      Can't copy an xlat expansion or literal into a list,
583                  *      we don't know what type of attribute we'd need
584                  *      to create.
585                  *
586                  *      The only exception is where were using a unary
587                  *      operator like !*.
588                  */
589                 if ((map->dst->type == VPT_TYPE_LIST) &&
590                     (map->op != T_OP_CMP_FALSE) &&
591                     ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
592                         cf_log_err(map->ci, "Can't copy value into list (we don't know which attribute to create)");
593
594                         return -1;
595                 }
596
597                 switch (map->src->type) {
598                 case VPT_TYPE_EXEC:
599                         cf_log_err(map->ci, "Exec values are not allowed");
600
601                         return -1;
602
603                 /*
604                  *      Only =, :=, += and -= operators are supported for
605                  *      cache entries.
606                  */
607                 case VPT_TYPE_LITERAL:
608                         /*
609                          *      @fixme: This should be moved into a common function
610                          *      with the check in do_compile_modupdate.
611                          */
612                         if (map->dst->type == VPT_TYPE_ATTR) {
613                                 VALUE_PAIR *vp;
614                                 bool ret;
615
616                                 MEM(vp = pairalloc(map->dst, map->dst->vpt_da));
617                                 vp->op = map->op;
618
619                                 ret = pairparsevalue(vp, map->src->name);
620                                 talloc_free(vp);
621                                 if (!ret) {
622                                         cf_log_err(map->ci, "%s", fr_strerror());
623                                         return -1;
624                                 }
625                         }
626                         /* FALL-THROUGH */
627
628                 case VPT_TYPE_XLAT:
629                 case VPT_TYPE_ATTR:
630                         switch (map->op) {
631                         case T_OP_SET:
632                         case T_OP_EQ:
633                         case T_OP_SUB:
634                         case T_OP_ADD:
635                                 break;
636
637                         default:
638                                 cf_log_err(map->ci, "Operator \"%s\" not "
639                                            "allowed for %s values",
640                                            fr_int2str(fr_tokens, map->op,
641                                                       "<INVALID>"),
642                                            fr_int2str(vpt_types, map->src->type,
643                                                       "<INVALID>"));
644                                 return -1;
645                         }
646                 default:
647                         break;
648                 }
649         }
650         return 0;
651 }
652
653 /*
654  *      Allow single attribute values to be retrieved from the cache.
655  */
656 static ssize_t cache_xlat(void *instance, REQUEST *request,
657                           char const *fmt, char *out, size_t freespace)
658 {
659         rlm_cache_entry_t       *c;
660         rlm_cache_t             *inst = instance;
661         VALUE_PAIR              *vp, *vps;
662         pair_lists_t            list;
663         DICT_ATTR const         *target;
664         char const              *p = fmt;
665         size_t                  len;
666         int                     ret = 0;
667
668         list = radius_list_name(&p, PAIR_LIST_REQUEST);
669
670         target = dict_attrbyname(p);
671         if (!target) {
672                 REDEBUG("Unknown attribute \"%s\"", p);
673                 return -1;
674         }
675
676         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
677         c = cache_find(inst, request, fmt);
678
679         if (!c) {
680                 RDEBUG("No cache entry for key \"%s\"", fmt);
681                 *out = '\0';
682                 goto done;
683         }
684
685         switch (list) {
686         case PAIR_LIST_REQUEST:
687                 vps = c->packet;
688                 break;
689
690         case PAIR_LIST_REPLY:
691                 vps = c->reply;
692                 break;
693
694         case PAIR_LIST_CONTROL:
695                 vps = c->control;
696                 break;
697
698         case PAIR_LIST_UNKNOWN:
699                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
700                 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
701                 return -1;
702
703         default:
704                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
705                 REDEBUG("Unsupported list \"%s\"",
706                         fr_int2str(pair_lists, list, "¿Unknown?"));
707                 return -1;
708         }
709
710         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
711         if (!vp) {
712                 RDEBUG("No instance of this attribute has been cached");
713                 *out = '\0';
714                 goto done;
715         }
716
717         len = vp_prints_value(out, freespace, vp, 0);
718         if (is_truncated(len, freespace)) {
719                 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
720                 REDEBUG("Insufficient buffer space to write cached value");
721                 return -1;
722         }
723 done:
724         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
725
726         return ret;
727 }
728
729 /*
730  *      A mapping of configuration file names to internal variables.
731  *
732  *      Note that the string is dynamically allocated, so it MUST
733  *      be freed.  When the configuration file parse re-reads the string,
734  *      it free's the old one, and strdup's the new one, placing the pointer
735  *      to the strdup'd string into 'config.string'.  This gets around
736  *      buffer over-flows.
737  */
738 static const CONF_PARSER module_config[] = {
739         { "key",  PW_TYPE_STRING_PTR | PW_TYPE_REQUIRED,
740           offsetof(rlm_cache_t, key), NULL, NULL},
741         { "ttl", PW_TYPE_INTEGER,
742           offsetof(rlm_cache_t, ttl), NULL, "500" },
743         { "max_entries", PW_TYPE_INTEGER,
744           offsetof(rlm_cache_t, max_entries), NULL, "16384" },
745         { "epoch", PW_TYPE_INTEGER,
746           offsetof(rlm_cache_t, epoch), NULL, "0" },
747         { "add_stats", PW_TYPE_BOOLEAN,
748           offsetof(rlm_cache_t, stats), NULL, "no" },
749
750         { NULL, -1, 0, NULL, NULL }             /* end the list */
751 };
752
753
754 /*
755  *      Only free memory we allocated.  The strings allocated via
756  *      cf_section_parse() do not need to be freed.
757  */
758 static int mod_detach(void *instance)
759 {
760         rlm_cache_t *inst = instance;
761
762         talloc_free(inst->maps);
763
764         fr_heap_delete(inst->heap);
765         rbtree_free(inst->cache);
766
767 #ifdef HAVE_PTHREAD_H
768         pthread_mutex_destroy(&inst->cache_mutex);
769 #endif
770         return 0;
771 }
772
773
774 /*
775  *      Instantiate the module.
776  */
777 static int mod_instantiate(CONF_SECTION *conf, void *instance)
778 {
779         rlm_cache_t *inst = instance;
780
781         inst->cs = conf;
782
783         inst->xlat_name = cf_section_name2(conf);
784         if (!inst->xlat_name) {
785                 inst->xlat_name = cf_section_name1(conf);
786         }
787
788         /*
789          *      Register the cache xlat function
790          */
791         xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
792
793         rad_assert(inst->key && *inst->key);
794
795         if (inst->ttl == 0) {
796                 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
797                 return -1;
798         }
799
800         if (inst->epoch != 0) {
801                 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
802                 return -1;
803         }
804
805 #ifdef HAVE_PTHREAD_H
806         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
807                 EDEBUG("Failed initializing mutex: %s",
808                        fr_syserror(errno));
809                 return -1;
810         }
811 #endif
812
813         /*
814          *      The cache.
815          */
816         inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
817         if (!inst->cache) {
818                 EDEBUG("Failed to create cache");
819                 return -1;
820         }
821
822         /*
823          *      The heap of entries to expire.
824          */
825         inst->heap = fr_heap_create(cache_heap_cmp,
826                                     offsetof(rlm_cache_entry_t, offset));
827         if (!inst->heap) {
828                 EDEBUG("Failed to create heap for the cache");
829                 return -1;
830         }
831
832         /*
833          *      Make sure the users don't screw up too badly.
834          */
835         if (cache_verify(inst, &inst->maps) < 0) {
836                 return -1;
837         }
838
839         return 0;
840 }
841
842 /*
843  *      Do caching checks.  Since we can update ANY VP list, we do
844  *      exactly the same thing for all sections (autz / auth / etc.)
845  *
846  *      If you want to cache something different in different sections,
847  *      configure another cache module.
848  */
849 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
850 {
851         rlm_cache_entry_t *c;
852         rlm_cache_t *inst = instance;
853         VALUE_PAIR *vp;
854         char buffer[1024];
855         rlm_rcode_t rcode;
856
857         if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
858                 return RLM_MODULE_FAIL;
859         }
860
861         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
862         c = cache_find(inst, request, buffer);
863
864         /*
865          *      If yes, only return whether we found a valid cache entry
866          */
867         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
868         if (vp && vp->vp_integer) {
869                 rcode = c ? RLM_MODULE_OK:
870                             RLM_MODULE_NOTFOUND;
871                 goto done;
872         }
873
874         if (c) {
875                 cache_merge(inst, request, c);
876
877                 rcode = RLM_MODULE_OK;
878                 goto done;
879         }
880
881         c = cache_add(inst, request, buffer);
882         if (!c) {
883                 rcode = RLM_MODULE_NOOP;
884                 goto done;
885         }
886
887         rcode = RLM_MODULE_UPDATED;
888
889 done:
890         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
891         return rcode;
892 }
893
894
895 /*
896  *      The module name should be the only globally exported symbol.
897  *      That is, everything else should be 'static'.
898  *
899  *      If the module needs to temporarily modify it's instantiation
900  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
901  *      The server will then take care of ensuring that the module
902  *      is single-threaded.
903  */
904 module_t rlm_cache = {
905         RLM_MODULE_INIT,
906         "cache",
907         0,                              /* type */
908         sizeof(rlm_cache_t),
909         module_config,
910         mod_instantiate,                /* instantiation */
911         mod_detach,                     /* detach */
912         {
913                 NULL,                   /* authentication */
914                 mod_cache_it,           /* authorization */
915                 mod_cache_it,           /* preaccounting */
916                 mod_cache_it,           /* accounting */
917                 NULL,                   /* checksimul */
918                 mod_cache_it,           /* pre-proxy */
919                 mod_cache_it,           /* post-proxy */
920                 mod_cache_it,           /* post-auth */
921         },
922 };