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 value_pair_map_t *maps; //!< Attribute map applied to users
51 pthread_mutex_t cache_mutex;
55 typedef struct rlm_cache_entry_t {
67 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
68 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
70 #define PTHREAD_MUTEX_LOCK(_x)
71 #define PTHREAD_MUTEX_UNLOCK(_x)
74 #define MAX_ATTRMAP 128
77 * Compare two entries by key. There may only be one entry with
80 static int cache_entry_cmp(const void *one, const void *two)
82 const rlm_cache_entry_t *a = one;
83 const rlm_cache_entry_t *b = two;
85 return strcmp(a->key, b->key);
88 static void cache_entry_free(void *data)
90 rlm_cache_entry_t *c = data;
93 pairfree(&c->control);
94 pairfree(&c->request);
100 * Compare two entries by expiry time. There may be multiple
101 * entries with the same expiry time.
103 static int cache_heap_cmp(const void *one, const void *two)
105 const rlm_cache_entry_t *a = one;
106 const rlm_cache_entry_t *b = two;
108 if (a->expires < b->expires) return -1;
109 if (a->expires > b->expires) return +1;
115 * Merge a cached entry into a REQUEST.
117 static void cache_merge(rlm_cache_t *inst, REQUEST *request,
118 rlm_cache_entry_t *c)
122 rad_assert(request != NULL);
123 rad_assert(c != NULL);
126 RDEBUG2("Merging cached control list:");
127 rdebug_pair_list(2, request, c->control);
129 vp = paircopy(c->control);
130 pairmove(&request->config_items, &vp);
134 if (c->request && request->packet) {
135 RDEBUG2("Merging cached request list:");
136 rdebug_pair_list(2, request, c->request);
138 vp = paircopy(c->request);
139 pairmove(&request->packet->vps, &vp);
143 if (c->reply && request->reply) {
144 RDEBUG2("Merging cached reply list:");
145 rdebug_pair_list(2, request, c->reply);
147 vp = paircopy(c->reply);
148 pairmove(&request->reply->vps, &vp);
153 vp = paircreate(PW_CACHE_ENTRY_HITS, 0, PW_TYPE_INTEGER);
154 rad_assert(vp != NULL);
156 vp->vp_integer = c->hits;
158 pairadd(&request->packet->vps, vp);
164 * Find a cached entry.
166 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
170 rlm_cache_entry_t *c, my_c;
174 * Look at the expiry heap.
176 c = fr_heap_peek(inst->heap);
178 rad_assert(rbtree_num_elements(inst->cache) == 0);
183 * If it's time to expire an old entry, do so now.
185 if (c->expires < request->timestamp) {
186 fr_heap_extract(inst->heap, c);
187 rbtree_deletebydata(inst->cache, c);
191 * Is there an entry for this key?
194 c = rbtree_finddata(inst->cache, &my_c);
198 * Yes, but it expired, OR the "forget all" epoch has
199 * passed. Delete it, and pretend it doesn't exist.
201 if ((c->expires < request->timestamp) ||
202 (c->created < inst->epoch)) {
204 RDEBUG("Entry has expired, removing");
206 fr_heap_extract(inst->heap, c);
207 rbtree_deletebydata(inst->cache, c);
212 RDEBUG("Found entry for \"%s\"", key);
215 * Update the expiry time based on the TTL.
216 * A TTL of 0 means "delete from the cache".
218 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
220 if (vp->vp_integer == 0) goto delete;
222 ttl = vp->vp_integer;
223 c->expires = request->timestamp + ttl;
224 RDEBUG("Adding %d to the TTL", ttl);
233 * Add an entry to the cache.
235 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
239 VALUE_PAIR *vp, *found, **to_req, **to_cache, **from;
244 const value_pair_map_t *map;
246 rlm_cache_entry_t *c;
250 * TTL of 0 means "don't cache this entry"
252 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
253 if (vp && (vp->vp_integer == 0)) return NULL;
255 c = rad_malloc(sizeof(*c));
256 memset(c, 0, sizeof(*c));
258 c->key = strdup(key);
259 c->created = c->expires = request->timestamp;
262 * Use per-entry TTL, or globally defined one.
265 ttl = vp->vp_integer;
271 RDEBUG("Creating entry for \"%s\"", key);
273 for (map = inst->maps; map != NULL; map = map->next)
275 rad_assert(map->dst && map->src);
278 * Specifying inner/outer request doesn't work here
279 * but there's no easy fix...
281 switch (map->dst->list) {
282 case PAIR_LIST_REQUEST:
283 to_cache = &c->request;
286 case PAIR_LIST_REPLY:
287 to_cache = &c->reply;
290 case PAIR_LIST_CONTROL:
291 to_cache = &c->control;
300 * Resolve the destination in the current request.
301 * We need to add the to_cache there too if any of these
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
310 if (!map->src->da || (map->src->list != map->dst->list) ||
311 (map->src->da != map->dst->da))
315 * It's ok if the list isn't valid here...
316 * It might be valid later when we merge
319 if (radius_request(&context, map->dst->request) == 0) {
320 to_req = radius_list(context, map->dst->list);
325 * We infer that src was an attribute ref from the fact
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?"));
332 switch (map->src->type)
338 if (radius_request(&context, map->src->request) == 0) {
339 from = radius_list(context, map->src->list);
343 * Can't add the attribute if the list isn't
348 found = pairfind(*from, da->attr, da->vendor, TAG_ANY);
350 RDEBUG("WARNING: \"%s\" not found, skipping",
355 RDEBUG("\t%s %s %s", map->dst->name,
356 fr_int2str(fr_tokens, map->op, "¿unknown?"),
363 vp = map->dst->type == VPT_TYPE_LIST ?
365 paircopyvpdata(map->dst->da, found);
369 pairadd(to_cache, vp);
373 radius_pairmove(request, to_req, vp);
379 vp = map->dst->type == VPT_TYPE_LIST ?
381 paircopyvpdata(map->dst->da,
385 vp->operator = map->op;
386 pairadd(to_cache, vp);
390 radius_pairmove(request, to_req,
395 found = pairfind(found->next,
409 rad_assert(map->src->type == VPT_TYPE_LIST);
413 if (radius_request(&context, map->src->request) == 0) {
414 from = radius_list(context, map->src->list);
418 found = paircopy(*from);
419 if (!found) continue;
421 for (vp = found; vp != NULL; vp = vp->next) {
422 RDEBUG("\t%s%s %s %s%s", map->dst->name,
424 fr_int2str(fr_tokens, map->op, "¿unknown?"),
427 vp->operator = map->op;
430 pairadd(to_cache, found);
433 vp = paircopy(found);
434 radius_pairmove(request, to_req, vp);
439 * It was most likely a double quoted string that now
440 * needs to be expanded.
443 if (radius_xlat(buffer, sizeof(buffer), map->src->name,
444 request, NULL, NULL) <= 0) {
448 RDEBUG("\t%s %s \"%s\"", map->dst->name,
449 fr_int2str(fr_tokens, map->op, "¿unknown?"),
452 vp = pairalloc(map->dst->da);
455 vp->operator = map->op;
456 vp = pairparsevalue(vp, buffer);
459 pairadd(to_cache, vp);
463 radius_pairmove(request, to_req, vp);
470 case VPT_TYPE_LITERAL:
471 RDEBUG("\t%s %s '%s'", map->dst->name,
472 fr_int2str(fr_tokens, map->op, "¿unknown?"),
475 vp = pairalloc(map->dst->da);
478 vp->operator = map->op;
479 vp = pairparsevalue(vp, map->src->name);
482 pairadd(to_cache, vp);
486 radius_pairmove(request, to_req, vp);
497 if (!rbtree_insert(inst->cache, c)) {
498 radlog(L_ERR, "rlm_cache: FAILED adding entry for key %s", key);
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);
509 RDEBUG("Inserted entry, TTL %d seconds", ttl);
515 * Verify that the cache section makes sense.
517 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
519 value_pair_map_t *map;
521 if (radius_attrmap(inst->cs, head, PAIR_LIST_REQUEST,
522 PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
527 cf_log_err(cf_sectiontoitem(inst->cs),
528 "Cache config must contain an update section, and "
529 "that section must not be empty");
534 for (map = *head; map != NULL; map = map->next) {
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 "
544 switch (map->src->type)
547 * Only =, :=, += and -= operators are supported for
550 case VPT_TYPE_LITERAL:
561 cf_log_err(map->ci, "Operator \"%s\" not "
562 "allowed for %s values",
563 fr_int2str(fr_tokens, map->op,
565 fr_int2str(vpt_types, map->src->type,
577 * Allow single attribute values to be retrieved from the cache.
579 static size_t cache_xlat(void *instance, REQUEST *request,
580 const char *fmt, char *out, size_t freespace)
582 rlm_cache_entry_t *c;
583 rlm_cache_t *inst = instance;
584 VALUE_PAIR *vp, *vps;
591 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
593 list = radius_list_name(&p, PAIR_LIST_REQUEST);
595 target = dict_attrbyname(p);
597 radlog(L_ERR, "rlm_cache: Unknown attribute \"%s\"", p);
601 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
602 c = cache_find(inst, request, buffer);
605 RDEBUG("No cache entry for key \"%s\"", buffer);
610 case PAIR_LIST_REQUEST:
614 case PAIR_LIST_REPLY:
618 case PAIR_LIST_CONTROL:
622 case PAIR_LIST_UNKNOWN:
623 radlog(L_ERR, "rlm_cache: Unknown list qualifier in \"%s\"", fmt);
627 radlog(L_ERR, "rlm_cache: Unsupported list \"%s\"",
628 fr_int2str(pair_lists, list, "¿Unknown?"));
632 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
634 RDEBUG("No instance of this attribute has been cached");
638 ret = vp_prints_value(out, freespace, vp, 0);
640 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
646 * A mapping of configuration file names to internal variables.
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
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" },
664 { NULL, -1, 0, NULL, NULL } /* end the list */
669 * Only free memory we allocated. The strings allocated via
670 * cf_section_parse() do not need to be freed.
672 static int cache_detach(void *instance)
674 rlm_cache_t *inst = instance;
677 rad_cfree(inst->xlat_name);
679 radius_mapfree(&inst->maps);
681 fr_heap_delete(inst->heap);
682 rbtree_free(inst->cache);
683 #ifdef HAVE_PTHREAD_H
684 pthread_mutex_destroy(&inst->cache_mutex);
692 * Instantiate the module.
694 static int cache_instantiate(CONF_SECTION *conf, void **instance)
696 const char *xlat_name;
699 inst = rad_calloc(sizeof(*inst));
702 * If the configuration parameters can't be parsed, then
705 if (cf_section_parse(conf, inst, module_config) < 0) {
710 xlat_name = cf_section_name2(conf);
711 if (xlat_name == NULL) {
712 xlat_name = cf_section_name1(conf);
715 rad_assert(xlat_name);
718 * Register the cache xlat function
720 inst->xlat_name = strdup(xlat_name);
721 xlat_register(xlat_name, cache_xlat, inst);
723 if (!inst->key || !*inst->key) {
724 radlog(L_ERR, "rlm_cache: You must specify a key");
729 if (inst->ttl == 0) {
730 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
735 if (inst->epoch != 0){
736 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
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));
752 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
754 radlog(L_ERR, "rlm_cache: Failed to create cache");
760 * The heap of entries to expire.
762 inst->heap = fr_heap_create(cache_heap_cmp,
763 offsetof(rlm_cache_entry_t, offset));
765 radlog(L_ERR, "rlm_cache: Failed to create cache");
771 * Make sure the users don't screw up too badly.
773 if (cache_verify(inst, &inst->maps) < 0) {
784 * Do caching checks. Since we can update ANY VP list, we do
785 * exactly the same thing for all sections (autz / auth / etc.)
787 * If you want to cache something different in different sections,
788 * configure another cache module.
790 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
792 rlm_cache_entry_t *c;
793 rlm_cache_t *inst = instance;
798 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
800 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
801 c = cache_find(inst, request, buffer);
804 * If yes, only return whether we found a valid cache entry
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:
814 cache_merge(inst, request, c);
816 rcode = RLM_MODULE_OK;
820 c = cache_add(inst, request, buffer);
822 rcode = RLM_MODULE_NOOP;
826 rcode = RLM_MODULE_UPDATED;
829 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
835 * The module name should be the only globally exported symbol.
836 * That is, everything else should be 'static'.
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.
843 module_t rlm_cache = {
847 cache_instantiate, /* instantiation */
848 cache_detach, /* detach */
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 */