4c3f6e5b389d6c6f3dff8cab1dfd2a7abf06fff1
[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 #ifdef HAVE_PTHREAD_H
48         pthread_mutex_t cache_mutex;
49 #endif
50 } rlm_cache_t;
51
52 typedef struct rlm_cache_entry_t {
53         const char      *key;
54         int             offset;
55         long long int   hits;
56         time_t          created;
57         time_t          expires;
58         VALUE_PAIR      *control;
59         VALUE_PAIR      *request;
60         VALUE_PAIR      *reply;
61 } rlm_cache_entry_t;
62
63 #ifdef HAVE_PTHREAD_H
64 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
65 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
66 #else
67 #define PTHREAD_MUTEX_LOCK(_x)
68 #define PTHREAD_MUTEX_UNLOCK(_x)
69 #endif
70
71 /*
72  *      Compare two entries by key.  There may only be one entry with
73  *      the same key.
74  */
75 static int cache_entry_cmp(const void *one, const void *two)
76 {
77         const rlm_cache_entry_t *a = one;
78         const rlm_cache_entry_t *b = two;
79
80         return strcmp(a->key, b->key);
81 }
82
83 static void cache_entry_free(void *data)
84 {
85         rlm_cache_entry_t *c = data;
86
87         rad_cfree(c->key);
88         pairfree(&c->control);
89         pairfree(&c->request);
90         pairfree(&c->reply);
91         free(c);
92 }
93
94 /*
95  *      Compare two entries by expiry time.  There may be multiple
96  *      entries with the same expiry time.
97  */
98 static int cache_heap_cmp(const void *one, const void *two)
99 {
100         const rlm_cache_entry_t *a = one;
101         const rlm_cache_entry_t *b = two;
102
103         if (a->expires < b->expires) return -1;
104         if (a->expires > b->expires) return +1;
105
106         return 0;
107 }
108
109 /*
110  *      Merge a cached entry into a REQUEST.
111  */
112 static void cache_merge(rlm_cache_t *inst, REQUEST *request,
113                         rlm_cache_entry_t *c)
114 {
115         VALUE_PAIR *vp;
116
117         rad_assert(request != NULL);
118         rad_assert(c != NULL);
119
120         if (c->control) {
121                 vp = paircopy(c->control);
122                 pairmove(&request->config_items, &vp);
123                 pairfree(&vp);
124         }
125
126         if (c->request && request->packet) {
127                 vp = paircopy(c->request);
128                 pairmove(&request->packet->vps, &vp);
129                 pairfree(&vp);
130         }
131
132         if (c->reply && request->reply) {
133                 vp = paircopy(c->reply);
134                 pairmove(&request->reply->vps, &vp);
135                 pairfree(&vp);
136         }
137         
138         if (inst->stats) {
139                 vp = paircreate(PW_CACHE_ENTRY_HITS, 0, PW_TYPE_INTEGER);
140                 rad_assert(vp != NULL);
141                 
142                 vp->vp_integer = c->hits;
143
144                 pairadd(&request->packet->vps, vp);
145         }
146 }
147
148
149 /*
150  *      Find a cached entry.
151  */
152 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
153                                      const char *key)
154 {
155         int ttl;
156         rlm_cache_entry_t *c, my_c;
157         VALUE_PAIR *vp;
158
159         /*
160          *      Look at the expiry heap.
161          */
162         c = fr_heap_peek(inst->heap);
163         if (!c) {
164                 rad_assert(rbtree_num_elements(inst->cache) == 0);
165                 return NULL;
166         }
167
168         /*
169          *      If it's time to expire an old entry, do so now.
170          */
171         if (c->expires < request->timestamp) {
172                 fr_heap_extract(inst->heap, c);
173                 rbtree_deletebydata(inst->cache, c);
174         }
175
176         /*
177          *      Is there an entry for this key?
178          */
179         my_c.key = key;
180         c = rbtree_finddata(inst->cache, &my_c);
181         if (!c) return NULL;
182
183         /*
184          *      Yes, but it expired, OR the "forget all" epoch has
185          *      passed.  Delete it, and pretend it doesn't exist.
186          */
187         if ((c->expires < request->timestamp) ||
188             (c->created < inst->epoch)) {
189         delete:
190                 DEBUG("rlm_cache: Entry has expired, removing");
191
192                 fr_heap_extract(inst->heap, c);
193                 rbtree_deletebydata(inst->cache, c);
194                 
195                 return NULL;
196         }
197
198         DEBUG("rlm_cache: Found entry for \"%s\"", key);
199
200         /*
201          *      Update the expiry time based on the TTL.
202          *      A TTL of 0 means "delete from the cache".
203          */
204         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
205         if (vp) {
206                 if (vp->vp_integer == 0) goto delete;
207                 
208                 ttl = vp->vp_integer;
209                 c->expires = request->timestamp + ttl;
210                 DEBUG("rlm_cache: Adding %d to the TTL", ttl);
211         }
212         c->hits++;
213
214         return c;
215 }
216
217
218 /*
219  *      Add an entry to the cache.
220  */
221 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
222                                     const char *key)
223 {
224         int ttl;
225         const char *attr, *p, *value;
226         VALUE_PAIR *vp, *vp_req, **vps;
227         CONF_ITEM *ci;
228         CONF_PAIR *cp;
229         FR_TOKEN op;
230         rlm_cache_entry_t *c;
231         char buffer[1024];
232
233         /*
234          *      TTL of 0 means "don't cache this entry"
235          */
236         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
237         if (vp && (vp->vp_integer == 0)) return NULL;
238
239         c = rad_malloc(sizeof(*c));
240         memset(c, 0, sizeof(*c));
241
242         c->key = strdup(key);
243         c->created = c->expires = request->timestamp;
244
245         /*
246          *      Use per-entry TTL, or globally defined one.
247          */
248         if (vp) {
249                 ttl = vp->vp_integer;
250         } else {
251                 ttl = inst->ttl;
252         }
253         c->expires += ttl;
254
255         /*
256          *      Walk over the attributes to cache, dynamically
257          *      expanding them (if needed), and adding them to the correct list.
258          */
259         for (ci = cf_item_find_next(inst->cs, NULL);
260              ci != NULL;
261              ci = cf_item_find_next(inst->cs, ci)) {
262                 rad_assert(cf_item_is_pair(ci));
263
264                 cp = cf_itemtopair(ci);
265                 attr = p = cf_pair_attr(cp);
266                 value = cf_pair_value(cp);
267                 op = cf_pair_operator(cp);
268                 
269                 switch (radius_list_name(&p, PAIR_LIST_REQUEST)) {
270                 case PAIR_LIST_REQUEST:
271                         vps = &c->request;
272                         break;
273                         
274                 case PAIR_LIST_REPLY:
275                         vps = &c->reply;
276                         break;
277                         
278                 case PAIR_LIST_CONTROL:
279                         vps = &c->control;
280                         break;
281
282                 default:
283                         rad_assert(0); 
284                         return NULL;            
285                 }
286                 
287                 switch (cf_pair_value_type(cp)) {
288                 case T_BARE_WORD:
289                         if ((radius_get_vp(request, value, &vp_req) < 0)) {
290                                 continue;
291                         }
292                         
293                         switch (op) {
294                         case T_OP_SET:
295                         case T_OP_EQ:
296                         case T_OP_SUB:
297                                 vp = paircopyvp(vp_req);
298                                 vp->operator = op;
299                                 pairadd(vps, vp);
300                                 
301                                 break;
302                         case T_OP_ADD:
303                                 do {
304                                         vp = paircopyvp(vp_req);
305                                         vp->operator = op;
306                                         pairadd(vps, vp);
307                                         
308                                         vp_req = pairfind(vp_req->next,
309                                                           vp_req->attribute,
310                                                           vp_req->vendor,
311                                                           TAG_ANY);
312                                 } while (vp_req);
313                                                                 
314                                 break;
315                                 
316                         default:
317                                 rad_assert(0);
318                                 return NULL;
319                         }
320                         
321                         break;
322                 case T_SINGLE_QUOTED_STRING:
323                         vp = pairmake(p, value, op);
324                         pairadd(vps, vp);
325                         
326                         break;
327                 case T_DOUBLE_QUOTED_STRING:
328                         radius_xlat(buffer, sizeof(buffer), value,
329                                     request, NULL, NULL);
330
331                         vp = pairmake(p, buffer, op);
332                         pairadd(vps, vp);
333                         
334                         break;
335                 default:
336                         rad_assert(0);
337                         return NULL;
338                 }
339                                 
340         }
341         
342         if (!rbtree_insert(inst->cache, c)) {
343                 DEBUG("rlm_cache: FAILED adding entry for key %s", key);
344                 cache_entry_free(c);
345                 return NULL;
346         }
347
348         if (!fr_heap_insert(inst->heap, c)) {
349                 DEBUG("rlm_cache: FAILED adding entry for key %s", key);
350                 rbtree_deletebydata(inst->cache, c);
351                 return NULL;
352         }
353
354         DEBUG("rlm_cache: Adding entry for \"%s\", with TTL of %d",
355               key, ttl);
356
357         return c;
358 }
359
360 /*
361  *      Verify that the cache section makes sense.
362  */
363 static int cache_verify(rlm_cache_t *inst)
364 {
365         const char *attr, *p;
366         pair_lists_t list;
367         CONF_ITEM *ci;
368         CONF_PAIR *cp;
369         FR_TOKEN op;
370
371         for (ci = cf_item_find_next(inst->cs, NULL);
372              ci != NULL;
373              ci = cf_item_find_next(inst->cs, ci)) {
374                 if (!cf_item_is_pair(ci)) {
375                         cf_log_err(ci, "rlm_cache: Entry is not in \"attribute = value\" format");
376                         return 0;
377                 }
378
379                 cp = cf_itemtopair(ci);
380                 attr = p = cf_pair_attr(cp);
381                 op = cf_pair_operator(cp);
382                 
383                 list = radius_list_name(&p, PAIR_LIST_REQUEST);
384                 
385                 switch (list) {
386                 case PAIR_LIST_REQUEST:
387                 case PAIR_LIST_REPLY:
388                 case PAIR_LIST_CONTROL:
389                         break;
390                         
391                 case PAIR_LIST_UNKNOWN:
392                         cf_log_err(ci, "rlm_cache: Unknown list qualifier in \"%s\"", attr);
393                         return 0;
394                 default:
395                         cf_log_err(ci, "rlm_cache: Unsupported list \"%s\"",
396                                    fr_int2str(pair_lists, list, "¿Unknown?"));
397                         return 0;
398                 }
399
400                 /*
401                  *      FIXME: Can't do tags for now...
402                  */
403                 if (!dict_attrbyname(p)) {
404                         cf_log_err(ci, "rlm_cache: Unknown attribute \"%s\"", p);
405                         return 0;
406                 }
407
408                 if (!cf_pair_value(cp)) {
409                         cf_log_err(ci, "rlm_cache: Attribute has no value");
410                         return 0;
411                 }
412                 
413                 switch (cf_pair_value_type(cp)) {
414                 case T_BARE_WORD:
415                         switch (op) {
416                         case T_OP_SET:
417                         case T_OP_EQ:
418                         case T_OP_SUB:
419                         case T_OP_ADD:
420                                 break;
421                                 
422                         default:
423                                 cf_log_err(ci, "rlm_cache: Operator \"%s\" not "
424                                            "allowed for attribute references",
425                                            fr_int2str(fr_tokens, op,
426                                                       "¿unknown?"));
427                                 return 0;
428                         }
429                         
430                         break;
431                 case T_SINGLE_QUOTED_STRING:
432                 case T_DOUBLE_QUOTED_STRING:
433                         break;
434                         
435                 default:
436                         cf_log_err(ci, "rlm_cache: Unsupported value type");
437                         return 0;
438                 }
439         }
440
441         return 1;
442 }
443
444 /*
445  *      Allow single attribute values to be retrieved from the cache.
446  */
447 static size_t cache_xlat(void *instance, REQUEST *request,
448                          const char *fmt, char *out, size_t freespace)
449 {
450         rlm_cache_entry_t *c;
451         rlm_cache_t *inst = instance;
452         VALUE_PAIR *vp, *vps;
453         pair_lists_t list;
454         DICT_ATTR *target;
455         const char *p = fmt;
456         char buffer[1024];
457         int ret = 0;
458
459         radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
460
461         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
462         c = cache_find(inst, request, buffer);
463         
464         if (!c) {
465                 RDEBUG("No cache entry for key \"%s\"", buffer);
466                 goto done;
467         }
468         
469         list = radius_list_name(&p, PAIR_LIST_REQUEST);
470         switch (list) {
471         case PAIR_LIST_REQUEST:
472                 vps = c->request;
473                 break;
474                 
475         case PAIR_LIST_REPLY:
476                 vps = c->reply;
477                 break;
478                 
479         case PAIR_LIST_CONTROL:
480                 vps = c->control;
481                 break;
482                 
483         case PAIR_LIST_UNKNOWN:
484                 radlog(L_ERR, "rlm_cache: Unknown list qualifier in \"%s\"", fmt);
485                 return 0;
486                 
487         default:
488                 radlog(L_ERR, "rlm_cache: Unsupported list \"%s\"",
489                        fr_int2str(pair_lists, list, "¿Unknown?"));
490                 return 0;
491         }
492         
493         target = dict_attrbyname(p);
494         if (!target) {
495                 radlog(L_ERR, "rlm_cache: Unknown attribute \"%s\"", p);
496                 goto done;
497         }
498         
499         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
500         if (!vp) {
501                 RDEBUG("No instance of this attribute has been cached");
502                 goto done;
503         }
504         
505         ret = vp_prints_value(out, freespace, vp, 0);
506 done:
507         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
508         
509         return ret;
510 }
511
512 /*
513  *      A mapping of configuration file names to internal variables.
514  *
515  *      Note that the string is dynamically allocated, so it MUST
516  *      be freed.  When the configuration file parse re-reads the string,
517  *      it free's the old one, and strdup's the new one, placing the pointer
518  *      to the strdup'd string into 'config.string'.  This gets around
519  *      buffer over-flows.
520  */
521 static const CONF_PARSER module_config[] = {
522         { "key",  PW_TYPE_STRING_PTR,
523           offsetof(rlm_cache_t, key), NULL, NULL},
524         { "ttl", PW_TYPE_INTEGER,
525           offsetof(rlm_cache_t, ttl), NULL, "500" },
526         { "epoch", PW_TYPE_INTEGER,
527           offsetof(rlm_cache_t, epoch), NULL, "0" },
528         { "add-stats", PW_TYPE_BOOLEAN,
529           offsetof(rlm_cache_t, stats), NULL, "no" },
530
531         { NULL, -1, 0, NULL, NULL }             /* end the list */
532 };
533
534
535 /*
536  *      Only free memory we allocated.  The strings allocated via
537  *      cf_section_parse() do not need to be freed.
538  */
539 static int cache_detach(void *instance)
540 {
541         rlm_cache_t *inst = instance;
542
543         free(inst->key);
544         rad_cfree(inst->xlat_name);
545
546         fr_heap_delete(inst->heap);
547         rbtree_free(inst->cache);
548 #ifdef HAVE_PTHREAD_H
549         pthread_mutex_destroy(&inst->cache_mutex);
550 #endif
551         free(instance);
552         return 0;
553 }
554
555
556 /*
557  *      Instantiate the module.
558  */
559 static int cache_instantiate(CONF_SECTION *conf, void **instance)
560 {
561         const char *xlat_name;
562         rlm_cache_t *inst;
563
564         inst = rad_malloc(sizeof(*inst));
565         if (!inst) {
566                 return -1;
567         }
568         memset(inst, 0, sizeof(*inst));
569
570         /*
571          *      If the configuration parameters can't be parsed, then
572          *      fail.
573          */
574         if (cf_section_parse(conf, inst, module_config) < 0) {
575                 free(inst);
576                 return -1;
577         }
578
579         xlat_name = cf_section_name2(conf);
580         if (xlat_name == NULL) {
581                 xlat_name = cf_section_name1(conf);
582         }
583         
584         rad_assert(xlat_name);
585
586         /*
587          *      Register the cache xlat function
588          */
589         inst->xlat_name = strdup(xlat_name);
590         xlat_register(xlat_name, cache_xlat, inst);
591
592         if (!inst->key || !*inst->key) {
593                 radlog(L_ERR, "rlm_cache: You must specify a key");
594                 cache_detach(inst);
595                 return -1;
596         }
597
598         if (inst->ttl == 0) {
599                 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
600                 cache_detach(inst);
601                 return -1;
602         }
603         
604         if (inst->epoch != 0){
605                 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
606                 cache_detach(inst);
607                 return -1;
608         }
609
610 #ifdef HAVE_PTHREAD_H
611         if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
612                 radlog(L_ERR, "rlm_cache: Failed initializing mutex: %s", strerror(errno));
613                 cache_detach(inst);
614                 return -1;
615         }
616 #endif
617
618         /*
619          *      The cache.
620          */
621         inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
622         if (!inst->cache) {
623                 radlog(L_ERR, "rlm_cache: Failed to create cache");
624                 cache_detach(inst);
625                 return -1;
626         }
627
628         /*
629          *      The heap of entries to expire.
630          */
631         inst->heap = fr_heap_create(cache_heap_cmp,
632                                     offsetof(rlm_cache_entry_t, offset));
633         if (!inst->heap) {
634                 radlog(L_ERR, "rlm_cache: Failed to create cache");
635                 cache_detach(inst);
636                 return -1;
637         }
638         
639
640         inst->cs = cf_section_sub_find(conf, "update");
641         if (!inst->cs) {
642                 radlog(L_ERR, "rlm_cache: Failed to find \"update\" subsection");
643                 cache_detach(inst);
644                 return -1;
645         }
646
647         /*
648          *      Make sure the users don't screw up too badly.
649          */
650         if (!cache_verify(inst)) {
651                 cache_detach(inst);
652                 return -1;
653         }
654
655         *instance = inst;
656
657         return 0;
658 }
659
660 /*
661  *      Do caching checks.  Since we can update ANY VP list, we do
662  *      exactly the same thing for all sections (autz / auth / etc.)
663  *
664  *      If you want to cache something different in different sections,
665  *      configure another cache module.
666  */
667 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
668 {
669         rlm_cache_entry_t *c;
670         rlm_cache_t *inst = instance;
671         VALUE_PAIR *vp;
672         char buffer[1024];
673         int rcode;
674
675         radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
676
677         PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
678         c = cache_find(inst, request, buffer);
679         
680         /*
681          *      If yes, only return whether we found a valid cache entry
682          */
683         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
684         if (vp && vp->vp_integer) {
685                 rcode = c ? RLM_MODULE_OK:
686                             RLM_MODULE_NOTFOUND;
687                 goto done;
688         }
689         
690         if (c) {
691                 cache_merge(inst, request, c);
692                 
693                 rcode = RLM_MODULE_OK;
694                 goto done;
695         }
696
697         c = cache_add(inst, request, buffer);
698         if (!c) {
699                 rcode = RLM_MODULE_NOOP;
700                 goto done;
701         }
702
703         cache_merge(inst, request, c);
704         rcode = RLM_MODULE_UPDATED;
705         
706 done:
707         PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
708         return rcode;
709 }
710
711
712 /*
713  *      The module name should be the only globally exported symbol.
714  *      That is, everything else should be 'static'.
715  *
716  *      If the module needs to temporarily modify it's instantiation
717  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
718  *      The server will then take care of ensuring that the module
719  *      is single-threaded.
720  */
721 module_t rlm_cache = {
722         RLM_MODULE_INIT,
723         "cache",
724         0,                              /* type */
725         cache_instantiate,              /* instantiation */
726         cache_detach,                   /* detach */
727         {
728                 NULL,                   /* authentication */
729                 cache_it,               /* authorization */
730                 cache_it,               /* preaccounting */
731                 cache_it,               /* accounting */
732                 NULL,                   /* checksimul */
733                 cache_it,               /* pre-proxy */
734                 cache_it,               /* post-proxy */
735                 cache_it,               /* post-auth */
736         },
737 };