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