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 {
39 const char *xlat_name;
48 pthread_mutex_t cache_mutex;
52 typedef struct rlm_cache_entry_t {
64 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
65 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
67 #define PTHREAD_MUTEX_LOCK(_x)
68 #define PTHREAD_MUTEX_UNLOCK(_x)
72 * Compare two entries by key. There may only be one entry with
75 static int cache_entry_cmp(const void *one, const void *two)
77 const rlm_cache_entry_t *a = one;
78 const rlm_cache_entry_t *b = two;
80 return strcmp(a->key, b->key);
83 static void cache_entry_free(void *data)
85 rlm_cache_entry_t *c = data;
88 pairfree(&c->control);
89 pairfree(&c->request);
95 * Compare two entries by expiry time. There may be multiple
96 * entries with the same expiry time.
98 static int cache_heap_cmp(const void *one, const void *two)
100 const rlm_cache_entry_t *a = one;
101 const rlm_cache_entry_t *b = two;
103 if (a->expires < b->expires) return -1;
104 if (a->expires > b->expires) return +1;
110 * Merge a cached entry into a REQUEST.
112 static void cache_merge(rlm_cache_t *inst, REQUEST *request,
113 rlm_cache_entry_t *c)
117 rad_assert(request != NULL);
118 rad_assert(c != NULL);
121 vp = paircopy(c->control);
122 pairmove(&request->config_items, &vp);
126 if (c->request && request->packet) {
127 vp = paircopy(c->request);
128 pairmove(&request->packet->vps, &vp);
132 if (c->reply && request->reply) {
133 vp = paircopy(c->reply);
134 pairmove(&request->reply->vps, &vp);
139 vp = paircreate(PW_CACHE_ENTRY_HITS, 0, PW_TYPE_INTEGER);
140 rad_assert(vp != NULL);
142 vp->vp_integer = c->hits;
144 pairadd(&request->packet->vps, vp);
150 * Find a cached entry.
152 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
156 rlm_cache_entry_t *c, my_c;
160 * Look at the expiry heap.
162 c = fr_heap_peek(inst->heap);
164 rad_assert(rbtree_num_elements(inst->cache) == 0);
169 * If it's time to expire an old entry, do so now.
171 if (c->expires < request->timestamp) {
172 fr_heap_extract(inst->heap, c);
173 rbtree_deletebydata(inst->cache, c);
177 * Is there an entry for this key?
180 c = rbtree_finddata(inst->cache, &my_c);
184 * Yes, but it expired, OR the "forget all" epoch has
185 * passed. Delete it, and pretend it doesn't exist.
187 if ((c->expires < request->timestamp) ||
188 (c->created < inst->epoch)) {
190 DEBUG("rlm_cache: Entry has expired, removing");
192 fr_heap_extract(inst->heap, c);
193 rbtree_deletebydata(inst->cache, c);
198 DEBUG("rlm_cache: Found entry for \"%s\"", key);
201 * Update the expiry time based on the TTL.
202 * A TTL of 0 means "delete from the cache".
204 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
206 if (vp->vp_integer == 0) goto delete;
208 ttl = vp->vp_integer;
209 c->expires = request->timestamp + ttl;
210 DEBUG("rlm_cache: Adding %d to the TTL", ttl);
219 * Add an entry to the cache.
221 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
225 const char *attr, *p, *value;
226 VALUE_PAIR *vp, *vp_req, **vps;
230 rlm_cache_entry_t *c;
234 * TTL of 0 means "don't cache this entry"
236 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
237 if (vp && (vp->vp_integer == 0)) return NULL;
239 c = rad_malloc(sizeof(*c));
240 memset(c, 0, sizeof(*c));
242 c->key = strdup(key);
243 c->created = c->expires = request->timestamp;
246 * Use per-entry TTL, or globally defined one.
249 ttl = vp->vp_integer;
256 * Walk over the attributes to cache, dynamically
257 * expanding them (if needed), and adding them to the correct list.
259 for (ci = cf_item_find_next(inst->cs, NULL);
261 ci = cf_item_find_next(inst->cs, ci)) {
262 rad_assert(cf_item_is_pair(ci));
264 cp = cf_itemtopair(ci);
265 attr = p = cf_pair_attr(cp);
266 value = cf_pair_value(cp);
267 op = cf_pair_operator(cp);
269 switch (radius_list_name(&p, PAIR_LIST_REQUEST)) {
270 case PAIR_LIST_REQUEST:
274 case PAIR_LIST_REPLY:
278 case PAIR_LIST_CONTROL:
287 switch (cf_pair_value_type(cp)) {
289 if ((radius_get_vp(request, value, &vp_req) < 0)) {
297 vp = paircopyvp(vp_req);
304 vp = paircopyvp(vp_req);
308 vp_req = pairfind(vp_req->next,
322 case T_SINGLE_QUOTED_STRING:
323 vp = pairmake(p, value, op);
327 case T_DOUBLE_QUOTED_STRING:
328 radius_xlat(buffer, sizeof(buffer), value,
329 request, NULL, NULL);
331 vp = pairmake(p, buffer, op);
342 if (!rbtree_insert(inst->cache, c)) {
343 DEBUG("rlm_cache: FAILED adding entry for key %s", key);
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);
354 DEBUG("rlm_cache: Adding entry for \"%s\", with TTL of %d",
361 * Verify that the cache section makes sense.
363 static int cache_verify(rlm_cache_t *inst)
365 const char *attr, *p;
371 for (ci = cf_item_find_next(inst->cs, 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");
379 cp = cf_itemtopair(ci);
380 attr = p = cf_pair_attr(cp);
381 op = cf_pair_operator(cp);
383 list = radius_list_name(&p, PAIR_LIST_REQUEST);
386 case PAIR_LIST_REQUEST:
387 case PAIR_LIST_REPLY:
388 case PAIR_LIST_CONTROL:
391 case PAIR_LIST_UNKNOWN:
392 cf_log_err(ci, "rlm_cache: Unknown list qualifier in \"%s\"", attr);
395 cf_log_err(ci, "rlm_cache: Unsupported list \"%s\"",
396 fr_int2str(pair_lists, list, "¿Unknown?"));
401 * FIXME: Can't do tags for now...
403 if (!dict_attrbyname(p)) {
404 cf_log_err(ci, "rlm_cache: Unknown attribute \"%s\"", p);
408 if (!cf_pair_value(cp)) {
409 cf_log_err(ci, "rlm_cache: Attribute has no value");
413 switch (cf_pair_value_type(cp)) {
423 cf_log_err(ci, "rlm_cache: Operator \"%s\" not "
424 "allowed for attribute references",
425 fr_int2str(fr_tokens, op,
431 case T_SINGLE_QUOTED_STRING:
432 case T_DOUBLE_QUOTED_STRING:
436 cf_log_err(ci, "rlm_cache: Unsupported value type");
445 * Allow single attribute values to be retrieved from the cache.
447 static size_t cache_xlat(void *instance, REQUEST *request,
448 const char *fmt, char *out, size_t freespace)
450 rlm_cache_entry_t *c;
451 rlm_cache_t *inst = instance;
452 VALUE_PAIR *vp, *vps;
459 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
461 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
462 c = cache_find(inst, request, buffer);
465 RDEBUG("No cache entry for key \"%s\"", buffer);
469 list = radius_list_name(&p, PAIR_LIST_REQUEST);
471 case PAIR_LIST_REQUEST:
475 case PAIR_LIST_REPLY:
479 case PAIR_LIST_CONTROL:
483 case PAIR_LIST_UNKNOWN:
484 radlog(L_ERR, "rlm_cache: Unknown list qualifier in \"%s\"", fmt);
488 radlog(L_ERR, "rlm_cache: Unsupported list \"%s\"",
489 fr_int2str(pair_lists, list, "¿Unknown?"));
493 target = dict_attrbyname(p);
495 radlog(L_ERR, "rlm_cache: Unknown attribute \"%s\"", p);
499 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
501 RDEBUG("No instance of this attribute has been cached");
505 ret = vp_prints_value(out, freespace, vp, 0);
507 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
513 * A mapping of configuration file names to internal variables.
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
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" },
531 { NULL, -1, 0, NULL, NULL } /* end the list */
536 * Only free memory we allocated. The strings allocated via
537 * cf_section_parse() do not need to be freed.
539 static int cache_detach(void *instance)
541 rlm_cache_t *inst = instance;
544 rad_cfree(inst->xlat_name);
546 fr_heap_delete(inst->heap);
547 rbtree_free(inst->cache);
548 #ifdef HAVE_PTHREAD_H
549 pthread_mutex_destroy(&inst->cache_mutex);
557 * Instantiate the module.
559 static int cache_instantiate(CONF_SECTION *conf, void **instance)
561 const char *xlat_name;
564 inst = rad_malloc(sizeof(*inst));
568 memset(inst, 0, sizeof(*inst));
571 * If the configuration parameters can't be parsed, then
574 if (cf_section_parse(conf, inst, module_config) < 0) {
579 xlat_name = cf_section_name2(conf);
580 if (xlat_name == NULL) {
581 xlat_name = cf_section_name1(conf);
584 rad_assert(xlat_name);
587 * Register the cache xlat function
589 inst->xlat_name = strdup(xlat_name);
590 xlat_register(xlat_name, cache_xlat, inst);
592 if (!inst->key || !*inst->key) {
593 radlog(L_ERR, "rlm_cache: You must specify a key");
598 if (inst->ttl == 0) {
599 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
604 if (inst->epoch != 0){
605 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
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));
621 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
623 radlog(L_ERR, "rlm_cache: Failed to create cache");
629 * The heap of entries to expire.
631 inst->heap = fr_heap_create(cache_heap_cmp,
632 offsetof(rlm_cache_entry_t, offset));
634 radlog(L_ERR, "rlm_cache: Failed to create cache");
640 inst->cs = cf_section_sub_find(conf, "update");
642 radlog(L_ERR, "rlm_cache: Failed to find \"update\" subsection");
648 * Make sure the users don't screw up too badly.
650 if (!cache_verify(inst)) {
661 * Do caching checks. Since we can update ANY VP list, we do
662 * exactly the same thing for all sections (autz / auth / etc.)
664 * If you want to cache something different in different sections,
665 * configure another cache module.
667 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
669 rlm_cache_entry_t *c;
670 rlm_cache_t *inst = instance;
675 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
677 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
678 c = cache_find(inst, request, buffer);
681 * If yes, only return whether we found a valid cache entry
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:
691 cache_merge(inst, request, c);
693 rcode = RLM_MODULE_OK;
697 c = cache_add(inst, request, buffer);
699 rcode = RLM_MODULE_NOOP;
703 cache_merge(inst, request, c);
704 rcode = RLM_MODULE_UPDATED;
707 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
713 * The module name should be the only globally exported symbol.
714 * That is, everything else should be 'static'.
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.
721 module_t rlm_cache = {
725 cache_instantiate, /* instantiation */
726 cache_detach, /* detach */
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 */