2058e6c460cc6b4f67185dbb7da16eaaeb9ed73d
[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         char            *key;
40         int             ttl;
41         int             epoch;
42         CONF_SECTION    *cs;
43         rbtree_t        *cache;
44         fr_heap_t       *heap;
45 } rlm_cache_t;
46
47 typedef struct rlm_cache_entry_t {
48         const char      *key;
49         int             offset;
50         time_t          created;
51         time_t          expires;
52         VALUE_PAIR      *control;
53         VALUE_PAIR      *request;
54         VALUE_PAIR      *reply;
55 } rlm_cache_entry_t;
56
57
58 /*
59  *      Compare two entries by key.  There may only be one entry with
60  *      the same key.
61  */
62 static int cache_entry_cmp(const void *one, const void *two)
63 {
64         const rlm_cache_entry_t *a = one;
65         const rlm_cache_entry_t *b = two;
66
67         return strcmp(a->key, b->key);
68 }
69
70 static void cache_entry_free(void *data)
71 {
72         rlm_cache_entry_t *c = data;
73
74         free(c->key);
75         pairfree(&c->control);
76         pairfree(&c->request);
77         pairfree(&c->reply);
78         free(c);
79 }
80
81
82 /*
83  *      Compare two entries by expiry time.  There may be multiple
84  *      entries with the same expiry time.
85  */
86 static int cache_heap_cmp(const void *one, const void *two)
87 {
88         const rlm_cache_entry_t *a = one;
89         const rlm_cache_entry_t *b = two;
90
91         if (a->expires < b->expires) return -1;
92         if (a->expires > b->expires) return +1;
93
94         return 0;
95 }
96
97 /*
98  *      Merge a cached entry into a REQUEST.
99  */
100 static void cache_merge(REQUEST *request, rlm_cache_entry_t *c)
101 {
102         VALUE_PAIR *vp;
103
104         rad_assert(request != NULL);
105         rad_assert(c != NULL);
106
107         if (c->control) {
108                 vp = paircopy(c->control);
109                 pairmove(&request->config_items, &vp);
110                 pairfree(&vp);
111         }
112
113         if (c->request && request->packet) {
114                 vp = paircopy(c->request);
115                 pairmove(&request->packet->vps, &vp);
116                 pairfree(&vp);
117         }
118
119         if (c->reply && request->reply) {
120                 vp = paircopy(c->reply);
121                 pairmove(&request->reply->vps, &vp);
122                 pairfree(&vp);
123         }
124 }
125
126
127 /*
128  *      Find a cached entry.
129  */
130 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
131                                      const char *key)
132 {
133         int ttl;
134         rlm_cache_entry_t *c, my_c;
135         VALUE_PAIR *vp;
136
137         /*
138          *      Look at the expiry heap.
139          */
140         c = fr_heap_peek(inst->heap);
141         if (!c) {
142                 rad_assert(rbtree_num_elements(inst->cache) == 0);
143                 return NULL;
144         }
145
146         /*
147          *      If it's time to expire an old entry, do so now.
148          */
149         if (c->expires < request->timestamp) {
150                 fr_heap_extract(inst->heap, c);
151                 rbtree_deletebydata(inst->cache, c);
152         }
153
154         /*
155          *      Is there an entry for this key?
156          */
157         my_c.key = key;
158         c = rbtree_finddata(inst->cache, &my_c);
159         if (!c) return NULL;
160
161         /*
162          *      Yes, but it expired, OR the "forget all" epoch has
163          *      passed.  Delete it, and pretend it doesn't exist.
164          */
165         if ((c->expires < request->timestamp) ||
166             (c->created < inst->epoch)) {
167         delete:
168                 DEBUG("rlm_cache: Entry has expired, removing");
169
170                 fr_heap_extract(inst->heap, c);
171                 rbtree_deletebydata(inst->cache, c);
172                 
173                 return NULL;
174         }
175
176         DEBUG("rlm_cache: Found entry for \"%s\"", key);
177
178         /*
179          *      Update the expiry time based on the TTL.
180          *      A TTL of 0 means "delete from the cache".
181          */
182         vp = pairfind(request->config_items, PW_CACHE_TTL, 0);
183         if (vp) {
184                 if (vp->vp_integer == 0) goto delete;
185                 
186                 ttl = vp->vp_integer;
187                 c->expires = request->timestamp + ttl;
188                 DEBUG("rlm_cache: Adding %d to the TTL", ttl);
189         }
190
191         return c;
192 }
193
194
195 /*
196  *      Add an entry to the cache.
197  */
198 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
199                                     const char *key)
200 {
201         int ttl;
202         const char *attr, *p;
203         VALUE_PAIR *vp, **list;
204         CONF_ITEM *ci;
205         CONF_PAIR *cp;
206         rlm_cache_entry_t *c;
207         char buffer[1024];
208
209         /*
210          *      TTL of 0 means "don't cache this entry"
211          */
212         vp = pairfind(request->config_items, PW_CACHE_TTL, 0);
213         if (vp && (vp->vp_integer == 0)) return NULL;
214
215         c = rad_malloc(sizeof(*c));
216         memset(c, 0, sizeof(*c));
217
218         c->key = strdup(key);
219         c->created = c->expires = request->timestamp;
220
221         /*
222          *      Use per-entry TTL, or globally defined one.
223          */
224         if (vp) {
225                 ttl = vp->vp_integer;
226         } else {
227                 ttl = inst->ttl;
228         }
229         c->expires += ttl;
230
231         /*
232          *      Walk over the attributes to cache, dynamically
233          *      expanding them, and adding them to the correct list.
234          */
235         for (ci = cf_item_find_next(inst->cs, NULL);
236              ci != NULL;
237              ci = cf_item_find_next(inst->cs, ci)) {
238                 rad_assert(cf_item_is_pair(ci));
239
240                 cp = cf_itemtopair(ci);
241                 attr = cf_pair_attr(cp);
242
243                 if (strncmp(attr, "control:", 8) == 0) {
244                         p = attr + 8;
245                         list = &c->control;
246
247                 } else if (strncmp(attr, "request:", 8) == 0) {
248                         p = attr + 8;
249                         list = &c->request;
250
251                 } else if (strncmp(attr, "reply:", 6) == 0) {
252                         p = attr + 6;
253                         list = &c->reply;
254
255                 } else {
256                         p = attr;
257                         list = &c->request;
258                 }
259
260                 /*
261                  *      Repeat much of cf_pairtovp here...
262                  *      but we take list prefixes, and it doesn't.
263                  *      I don't want to make that change for 2.0.
264                  */
265                 radius_xlat(buffer, sizeof(buffer), cf_pair_value(cp),
266                             request, NULL);
267
268                 vp = pairmake(p, buffer, cf_pair_operator(cp));
269                 pairadd(list, vp);
270         }
271
272         if (!rbtree_insert(inst->cache, c)) {
273                 DEBUG("rlm_cache: FAILED adding entry for key %s", key);
274                 cache_entry_free(c);
275                 return NULL;
276         }
277
278         if (!fr_heap_insert(inst->heap, c)) {
279                 DEBUG("rlm_cache: FAILED adding entry for key %s", key);
280                 rbtree_deletebydata(inst->cache, c);
281                 return NULL;
282         }
283
284         DEBUG("rlm_cache: Adding entry for \"%s\", with TTL of %d",
285               key, ttl);
286
287         return c;
288 }
289
290
291 /*
292  *      Verify that the cache section makes sense.
293  */
294 static int cache_verify(rlm_cache_t *inst)
295 {
296         const char *attr, *p;
297         CONF_ITEM *ci;
298         CONF_PAIR *cp;
299
300         for (ci = cf_item_find_next(inst->cs, NULL);
301              ci != NULL;
302              ci = cf_item_find_next(inst->cs, ci)) {
303                 if (!cf_item_is_pair(ci)) {
304                         cf_log_err(ci, "rlm_cache: Entry is not in \"attribute = value\" format");
305                         return 0;
306                 }
307
308                 cp = cf_itemtopair(ci);
309                 attr = cf_pair_attr(cp);
310
311                 if (strncmp(attr, "control:", 8) == 0) {
312                         p = attr + 8;
313
314                 } else if (strncmp(attr, "request:", 8) == 0) {
315                         p = attr + 8;
316
317                 } else if (strncmp(attr, "reply:", 6) == 0) {
318                         p = attr + 6;
319
320                 } else {
321                         p = attr;
322                 }
323
324                 /*
325                  *      FIXME: Can't do tags for now...
326                  */
327                 if (!dict_attrbyname(p)) {
328                         cf_log_err(ci, "rlm_cache: Unknown attribute \"%s\"", p);
329                         return 0;
330                 }
331
332                 if (!cf_pair_value(cp)) {
333                         cf_log_err(ci, "rlm_cache: Attribute has no value");
334                         return 0;
335                 }
336         }
337
338         return 1;
339 }
340
341
342 /*
343  *      A mapping of configuration file names to internal variables.
344  *
345  *      Note that the string is dynamically allocated, so it MUST
346  *      be freed.  When the configuration file parse re-reads the string,
347  *      it free's the old one, and strdup's the new one, placing the pointer
348  *      to the strdup'd string into 'config.string'.  This gets around
349  *      buffer over-flows.
350  */
351 static const CONF_PARSER module_config[] = {
352         { "key",  PW_TYPE_STRING_PTR,
353           offsetof(rlm_cache_t, key), NULL,  NULL},
354         { "ttl", PW_TYPE_INTEGER,
355           offsetof(rlm_cache_t, ttl), NULL,   "500" },
356         { "epoch", PW_TYPE_INTEGER,
357           offsetof(rlm_cache_t, epoch), NULL,   "0" },
358
359         { NULL, -1, 0, NULL, NULL }             /* end the list */
360 };
361
362
363 /*
364  *      Only free memory we allocated.  The strings allocated via
365  *      cf_section_parse() do not need to be freed.
366  */
367 static int cache_detach(void *instance)
368 {
369         rlm_cache_t *inst = instance;
370
371         free(inst->key);
372
373         fr_heap_delete(inst->heap);
374         rbtree_free(inst->cache);
375         free(instance);
376         return 0;
377 }
378
379
380 /*
381  *      Instantiate the module.
382  */
383 static int cache_instantiate(CONF_SECTION *conf, void **instance)
384 {
385         rlm_cache_t *inst;
386
387         inst = rad_malloc(sizeof(*inst));
388         if (!inst) {
389                 return -1;
390         }
391         memset(inst, 0, sizeof(*inst));
392
393         /*
394          *      If the configuration parameters can't be parsed, then
395          *      fail.
396          */
397         if (cf_section_parse(conf, inst, module_config) < 0) {
398                 free(inst);
399                 return -1;
400         }
401
402         if (!inst->key || !*inst->key) {
403                 radlog(L_ERR, "rlm_cache: You must specify a key");
404                 cache_detach(inst);
405                 return -1;
406         }
407
408         if (inst->ttl == 0) {
409                 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
410                 cache_detach(inst);
411                 return -1;
412         }
413         
414         if (inst->epoch != 0){
415                 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
416                 cache_detach(inst);
417                 return -1;
418         }
419
420         /*
421          *      The cache.
422          */
423         inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
424         if (!inst->cache) {
425                 radlog(L_ERR, "rlm_cache: Failed to create cache");
426                 cache_detach(inst);
427                 return -1;
428         }
429
430         /*
431          *      The heap of entries to expire.
432          */
433         inst->heap = fr_heap_create(cache_heap_cmp,
434                                     offsetof(rlm_cache_entry_t, offset));
435         if (!inst->heap) {
436                 radlog(L_ERR, "rlm_cache: Failed to create cache");
437                 cache_detach(inst);
438                 return -1;
439         }
440         
441
442         inst->cs = cf_section_sub_find(conf, "update");
443         if (!inst->cs) {
444                 radlog(L_ERR, "rlm_cache: Failed to find \"attributes\" subsection");
445                 cache_detach(inst);
446                 return -1;
447         }
448
449         /*
450          *      Make sure the users don't screw up too badly.
451          */
452         if (!cache_verify(inst)) {
453                 cache_detach(inst);
454                 return -1;
455         }
456
457         *instance = inst;
458
459         return 0;
460 }
461
462 /*
463  *      Do caching checks.  Since we can update ANY VP list, we do
464  *      exactly the same thing for all sections (autz / auth / etc.)
465  *
466  *      If you want to cache something different in different sections,
467  *      configure another cache module.
468  */
469 static int cache_it(void *instance, REQUEST *request)
470 {
471         rlm_cache_entry_t *c;
472         rlm_cache_t *inst = instance;
473         VALUE_PAIR *vp;
474         char buffer[1024];
475
476         radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL);
477
478         c = cache_find(inst, request, buffer);
479         
480         /*
481          *      If yes, only return whether we found a valid cache entry
482          */
483         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0);
484         if (vp && vp->vp_integer) {
485                 return c ?
486                         RLM_MODULE_OK:
487                         RLM_MODULE_NOTFOUND;
488         }
489         
490         if (c) {
491                 cache_merge(request, c);
492                 return RLM_MODULE_OK;
493         }
494
495         c = cache_add(inst, request, buffer);
496         if (!c) return RLM_MODULE_NOOP;
497
498         cache_merge(request, c);
499
500         return RLM_MODULE_UPDATED;
501 }
502
503
504 /*
505  *      The module name should be the only globally exported symbol.
506  *      That is, everything else should be 'static'.
507  *
508  *      If the module needs to temporarily modify it's instantiation
509  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
510  *      The server will then take care of ensuring that the module
511  *      is single-threaded.
512  */
513 module_t rlm_cache = {
514         RLM_MODULE_INIT,
515         "cache",
516         0,                      /* type */
517         cache_instantiate,              /* instantiation */
518         cache_detach,                   /* detach */
519         {
520                 NULL,                   /* authentication */
521                 cache_it,               /* authorization */
522                 NULL,                   /* preaccounting */
523                 NULL,                   /* accounting */
524                 NULL,                   /* checksimul */
525                 cache_it,               /* pre-proxy */
526                 cache_it,               /* post-proxy */
527                 cache_it,               /* post-auth */
528         },
529 };