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.
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.
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
20 * Copyright 2012 The FreeRADIUS server project
23 #include <freeradius-devel/ident.h>
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>
32 * Define a structure for our module configuration.
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.
38 typedef struct rlm_cache_t {
47 typedef struct rlm_cache_entry_t {
59 * Compare two entries by key. There may only be one entry with
62 static int cache_entry_cmp(const void *one, const void *two)
64 const rlm_cache_entry_t *a = one;
65 const rlm_cache_entry_t *b = two;
67 return strcmp(a->key, b->key);
70 static void cache_entry_free(void *data)
72 rlm_cache_entry_t *c = data;
75 pairfree(&c->control);
76 pairfree(&c->request);
83 * Compare two entries by expiry time. There may be multiple
84 * entries with the same expiry time.
86 static int cache_heap_cmp(const void *one, const void *two)
88 const rlm_cache_entry_t *a = one;
89 const rlm_cache_entry_t *b = two;
91 if (a->expires < b->expires) return -1;
92 if (a->expires > b->expires) return +1;
98 * Merge a cached entry into a REQUEST.
100 static void cache_merge(REQUEST *request, rlm_cache_entry_t *c)
104 rad_assert(request != NULL);
105 rad_assert(c != NULL);
108 vp = paircopy(c->control);
109 pairmove(&request->config_items, &vp);
113 if (c->request && request->packet) {
114 vp = paircopy(c->request);
115 pairmove(&request->packet->vps, &vp);
119 if (c->reply && request->reply) {
120 vp = paircopy(c->reply);
121 pairmove(&request->reply->vps, &vp);
128 * Find a cached entry.
130 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
134 rlm_cache_entry_t *c, my_c;
138 * Look at the expiry heap.
140 c = fr_heap_peek(inst->heap);
142 rad_assert(rbtree_num_elements(inst->cache) == 0);
147 * If it's time to expire an old entry, do so now.
149 if (c->expires < request->timestamp) {
150 fr_heap_extract(inst->heap, c);
151 rbtree_deletebydata(inst->cache, c);
155 * Is there an entry for this key?
158 c = rbtree_finddata(inst->cache, &my_c);
162 * Yes, but it expired, OR the "forget all" epoch has
163 * passed. Delete it, and pretend it doesn't exist.
165 if ((c->expires < request->timestamp) ||
166 (c->created < inst->epoch)) {
168 DEBUG("rlm_cache: Entry has expired, removing");
170 fr_heap_extract(inst->heap, c);
171 rbtree_deletebydata(inst->cache, c);
176 DEBUG("rlm_cache: Found entry for \"%s\"", key);
179 * Update the expiry time based on the TTL.
180 * A TTL of 0 means "delete from the cache".
182 vp = pairfind(request->config_items, PW_CACHE_TTL, 0);
184 if (vp->vp_integer == 0) goto delete;
186 ttl = vp->vp_integer;
187 c->expires = request->timestamp + ttl;
188 DEBUG("rlm_cache: Adding %d to the TTL", ttl);
196 * Add an entry to the cache.
198 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
202 const char *attr, *p;
203 VALUE_PAIR *vp, **list;
206 rlm_cache_entry_t *c;
210 * TTL of 0 means "don't cache this entry"
212 vp = pairfind(request->config_items, PW_CACHE_TTL, 0);
213 if (vp && (vp->vp_integer == 0)) return NULL;
215 c = rad_malloc(sizeof(*c));
216 memset(c, 0, sizeof(*c));
218 c->key = strdup(key);
219 c->created = c->expires = request->timestamp;
222 * Use per-entry TTL, or globally defined one.
225 ttl = vp->vp_integer;
232 * Walk over the attributes to cache, dynamically
233 * expanding them, and adding them to the correct list.
235 for (ci = cf_item_find_next(inst->cs, NULL);
237 ci = cf_item_find_next(inst->cs, ci)) {
238 rad_assert(cf_item_is_pair(ci));
240 cp = cf_itemtopair(ci);
241 attr = cf_pair_attr(cp);
243 if (strncmp(attr, "control:", 8) == 0) {
247 } else if (strncmp(attr, "request:", 8) == 0) {
251 } else if (strncmp(attr, "reply:", 6) == 0) {
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.
265 radius_xlat(buffer, sizeof(buffer), cf_pair_value(cp),
268 vp = pairmake(p, buffer, cf_pair_operator(cp));
272 if (!rbtree_insert(inst->cache, c)) {
273 DEBUG("rlm_cache: FAILED adding entry for key %s", key);
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);
284 DEBUG("rlm_cache: Adding entry for \"%s\", with TTL of %d",
292 * Verify that the cache section makes sense.
294 static int cache_verify(rlm_cache_t *inst)
296 const char *attr, *p;
300 for (ci = cf_item_find_next(inst->cs, 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");
308 cp = cf_itemtopair(ci);
309 attr = cf_pair_attr(cp);
311 if (strncmp(attr, "control:", 8) == 0) {
314 } else if (strncmp(attr, "request:", 8) == 0) {
317 } else if (strncmp(attr, "reply:", 6) == 0) {
325 * FIXME: Can't do tags for now...
327 if (!dict_attrbyname(p)) {
328 cf_log_err(ci, "rlm_cache: Unknown attribute \"%s\"", p);
332 if (!cf_pair_value(cp)) {
333 cf_log_err(ci, "rlm_cache: Attribute has no value");
343 * A mapping of configuration file names to internal variables.
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
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" },
359 { NULL, -1, 0, NULL, NULL } /* end the list */
364 * Only free memory we allocated. The strings allocated via
365 * cf_section_parse() do not need to be freed.
367 static int cache_detach(void *instance)
369 rlm_cache_t *inst = instance;
373 fr_heap_delete(inst->heap);
374 rbtree_free(inst->cache);
381 * Instantiate the module.
383 static int cache_instantiate(CONF_SECTION *conf, void **instance)
387 inst = rad_malloc(sizeof(*inst));
391 memset(inst, 0, sizeof(*inst));
394 * If the configuration parameters can't be parsed, then
397 if (cf_section_parse(conf, inst, module_config) < 0) {
402 if (!inst->key || !*inst->key) {
403 radlog(L_ERR, "rlm_cache: You must specify a key");
408 if (inst->ttl == 0) {
409 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
414 if (inst->epoch != 0){
415 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
423 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
425 radlog(L_ERR, "rlm_cache: Failed to create cache");
431 * The heap of entries to expire.
433 inst->heap = fr_heap_create(cache_heap_cmp,
434 offsetof(rlm_cache_entry_t, offset));
436 radlog(L_ERR, "rlm_cache: Failed to create cache");
442 inst->cs = cf_section_sub_find(conf, "update");
444 radlog(L_ERR, "rlm_cache: Failed to find \"attributes\" subsection");
450 * Make sure the users don't screw up too badly.
452 if (!cache_verify(inst)) {
463 * Do caching checks. Since we can update ANY VP list, we do
464 * exactly the same thing for all sections (autz / auth / etc.)
466 * If you want to cache something different in different sections,
467 * configure another cache module.
469 static int cache_it(void *instance, REQUEST *request)
471 rlm_cache_entry_t *c;
472 rlm_cache_t *inst = instance;
476 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL);
478 c = cache_find(inst, request, buffer);
481 * If yes, only return whether we found a valid cache entry
483 vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0);
484 if (vp && vp->vp_integer) {
491 cache_merge(request, c);
492 return RLM_MODULE_OK;
495 c = cache_add(inst, request, buffer);
496 if (!c) return RLM_MODULE_NOOP;
498 cache_merge(request, c);
500 return RLM_MODULE_UPDATED;
505 * The module name should be the only globally exported symbol.
506 * That is, everything else should be 'static'.
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.
513 module_t rlm_cache = {
517 cache_instantiate, /* instantiation */
518 cache_detach, /* detach */
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 */