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