2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Cache values and merge them back into future requests.
21 * @copyright 2012-2013 The FreeRADIUS server project
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/modules.h>
27 #include <freeradius-devel/heap.h>
28 #include <freeradius-devel/rad_assert.h>
30 #define PW_CACHE_TTL 1140
31 #define PW_CACHE_STATUS_ONLY 1141
32 #define PW_CACHE_MERGE 1142
33 #define PW_CACHE_ENTRY_HITS 1143
36 * Define a structure for our module configuration.
38 * These variables do not need to be in a structure, but it's
39 * a lot cleaner to do so, and a pointer to the structure can
40 * be used as the instance handle.
42 typedef struct rlm_cache_t {
43 char const *xlat_name;
53 value_pair_map_t *maps; //!< Attribute map applied to users
56 pthread_mutex_t cache_mutex;
60 typedef struct rlm_cache_entry_t {
72 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
73 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
75 #define PTHREAD_MUTEX_LOCK(_x)
76 #define PTHREAD_MUTEX_UNLOCK(_x)
79 #define MAX_ATTRMAP 128
82 * A mapping of configuration file names to internal variables.
84 * Note that the string is dynamically allocated, so it MUST
85 * be freed. When the configuration file parse re-reads the string,
86 * it free's the old one, and strdup's the new one, placing the pointer
87 * to the strdup'd string into 'config.string'. This gets around
90 static const CONF_PARSER module_config[] = {
91 { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_cache_t, key), NULL },
92 { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
93 { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "16384" },
95 /* Should be a type which matches time_t, @fixme before 2038 */
96 { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
97 { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
99 { NULL, -1, 0, NULL, NULL } /* end the list */
104 * Compare two entries by key. There may only be one entry with
107 static int cache_entry_cmp(void const *one, void const *two)
109 rlm_cache_entry_t const *a = one;
110 rlm_cache_entry_t const *b = two;
112 return strcmp(a->key, b->key);
115 static void cache_entry_free(void *data)
117 rlm_cache_entry_t *c = data;
119 pairfree(&c->control);
120 pairfree(&c->packet);
127 * Compare two entries by expiry time. There may be multiple
128 * entries with the same expiry time.
130 static int cache_heap_cmp(void const *one, void const *two)
132 rlm_cache_entry_t const *a = one;
133 rlm_cache_entry_t const *b = two;
135 if (a->expires < b->expires) return -1;
136 if (a->expires > b->expires) return +1;
142 * Merge a cached entry into a REQUEST.
144 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
148 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
149 if (vp && (vp->vp_integer == 0)) {
150 RDEBUG2("Told not to merge entry into request");
155 RDEBUG2("Merging cached control list:");
156 rdebug_pair_list(2, request, c->control);
158 pairadd(&request->config_items, paircopy(request, c->control));
161 if (c->packet && request->packet) {
162 RDEBUG2("Merging cached request list:");
163 rdebug_pair_list(2, request, c->packet);
165 pairadd(&request->packet->vps,
166 paircopy(request->packet, c->packet));
169 if (c->reply && request->reply) {
170 RDEBUG2("Merging cached reply list:");
171 rdebug_pair_list(2, request, c->reply);
173 pairadd(&request->reply->vps,
174 paircopy(request->reply, c->reply));
178 vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
179 rad_assert(vp != NULL);
181 vp->vp_integer = c->hits;
183 pairadd(&request->packet->vps, vp);
189 * Find a cached entry.
191 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
195 rlm_cache_entry_t *c, my_c;
199 * Look at the expiry heap.
201 c = fr_heap_peek(inst->heap);
203 rad_assert(rbtree_num_elements(inst->cache) == 0);
208 * If it's time to expire an old entry, do so now.
210 if (c->expires < request->timestamp) {
211 fr_heap_extract(inst->heap, c);
212 rbtree_deletebydata(inst->cache, c);
216 * Is there an entry for this key?
219 c = rbtree_finddata(inst->cache, &my_c);
223 * Yes, but it expired, OR the "forget all" epoch has
224 * passed. Delete it, and pretend it doesn't exist.
226 if ((c->expires < request->timestamp) ||
227 (c->created < inst->epoch)) {
229 RDEBUG("Entry has expired, removing");
231 fr_heap_extract(inst->heap, c);
232 rbtree_deletebydata(inst->cache, c);
237 RDEBUG("Found entry for \"%s\"", key);
240 * Update the expiry time based on the TTL.
241 * A TTL of 0 means "delete from the cache".
243 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
245 if (vp->vp_integer == 0) goto delete;
247 ttl = vp->vp_integer;
248 c->expires = request->timestamp + ttl;
249 RDEBUG("Adding %d to the TTL", ttl);
258 * Add an entry to the cache.
260 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
264 VALUE_PAIR *vp, *found, **to_req, **to_cache, **from;
270 value_pair_map_t const *map;
272 rlm_cache_entry_t *c;
275 if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
276 RDEBUG("Cache is full: %d entries", inst->max_entries);
281 * TTL of 0 means "don't cache this entry"
283 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
284 if (vp && (vp->vp_integer == 0)) return NULL;
286 c = talloc_zero(inst, rlm_cache_entry_t);
287 c->key = talloc_typed_strdup(c, key);
288 c->created = c->expires = request->timestamp;
291 * Use per-entry TTL, or globally defined one.
294 ttl = vp->vp_integer;
300 RDEBUG("Creating entry for \"%s\"", key);
303 * Check to see if we need to merge the entry into the request
305 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
306 if (vp && (vp->vp_integer == 0)) {
308 RDEBUG2("Told not to merge new entry into request");
311 for (map = inst->maps; map != NULL; map = map->next) {
312 rad_assert(map->dst && map->src);
315 * Specifying inner/outer request doesn't work here
316 * but there's no easy fix...
318 switch (map->dst->vpt_list) {
319 case PAIR_LIST_REQUEST:
320 to_cache = &c->packet;
323 case PAIR_LIST_REPLY:
324 to_cache = &c->reply;
327 case PAIR_LIST_CONTROL:
328 to_cache = &c->control;
337 * Resolve the destination in the current request.
338 * We need to add the to_cache there too if any of these
341 * - Map specifies an xlat'd string.
342 * - Map specifies a literal string.
343 * - Map src and dst lists differ.
344 * - Map src and dst attributes differ
347 if (merge && (!map->src->vpt_da ||
348 (map->src->vpt_list != map->dst->vpt_list) ||
349 (map->src->vpt_da != map->dst->vpt_da))) {
352 * It's ok if the list isn't valid here...
353 * It might be valid later when we merge
356 if (radius_request(&context, map->dst->vpt_request) == 0) {
357 to_req = radius_list(context, map->dst->vpt_list);
362 * We infer that src was an attribute ref from the fact
365 RDEBUG4(":: dst is \"%s\" src is \"%s\"",
366 fr_int2str(vpt_types, map->dst->type, "<INVALID>"),
367 fr_int2str(vpt_types, map->src->type, "<INVALID>"));
369 switch (map->src->type) {
375 da = map->src->vpt_da;
376 rad_assert(da != NULL);
379 if (radius_request(&context, map->src->vpt_request) == 0) {
380 from = radius_list(context, map->src->vpt_list);
384 * Can't add the attribute if the list isn't
389 fr_cursor_init(&cursor, from);
390 found = fr_cursor_next_by_da(&cursor, da, TAG_ANY);
392 RWDEBUG("\"%s\" not found, skipping",
397 RDEBUG("\t%s %s &%s", map->dst->name,
398 fr_int2str(fr_tokens, map->op, "<INVALID>"),
405 vp = map->dst->type == VPT_TYPE_LIST ?
406 paircopyvp(c, found) :
407 paircopyvpdata(c, map->dst->vpt_da, found);
411 pairadd(to_cache, vp);
414 vp = paircopyvp(request, vp);
415 radius_pairmove(request, to_req, vp, false);
421 vp = map->dst->type == VPT_TYPE_LIST ?
422 paircopyvp(c, found) :
423 paircopyvpdata(c, map->dst->vpt_da, found);
427 pairadd(to_cache, vp);
430 vp = paircopyvp(request, vp);
431 radius_pairmove(request, to_req, vp, false);
434 } while ((found = fr_cursor_next_by_da(&cursor, da, TAG_ANY)));
448 rad_assert(map->src->type == VPT_TYPE_LIST);
452 if (radius_request(&context, map->src->vpt_request) == 0) {
453 from = radius_list(context, map->src->vpt_list);
458 fr_cursor_init(&out, &found);
459 for (i = fr_cursor_init(&in, from);
461 i = fr_cursor_next(&in)) {
463 * Prevent cache control attributes being added to the cache.
465 switch (i->da->attr) {
467 case PW_CACHE_STATUS_ONLY:
469 case PW_CACHE_ENTRY_HITS:
470 RDEBUG("\tskipping %s", i->da->name);
476 vp = paircopyvp(c, i);
481 RDEBUG("\t%s %s &%s:%s", map->dst->name,
482 fr_int2str(fr_tokens, map->op, "<INVALID>"),
483 fr_int2str(pair_lists, map->src->vpt_list, "<INVALID>"), vp->da->name);
485 fr_cursor_insert(&out, vp);
488 pairadd(to_cache, found);
490 vp = paircopy(request, found);
491 radius_pairmove(request, to_req, vp, false);
497 * It was most likely a double quoted string that now
498 * needs to be expanded.
501 if (radius_xlat(buffer, sizeof(buffer), request, map->src->name, NULL, NULL) <= 0) {
505 RDEBUG("\t%s %s \"%s\"", map->dst->name,
506 fr_int2str(fr_tokens, map->op, "<INVALID>"),
509 vp = pairalloc(map->dst, map->dst->vpt_da);
513 if (!pairparsevalue(vp, buffer)) {
518 pairadd(to_cache, vp);
521 vp = paircopyvp(request, vp);
522 radius_pairmove(request, to_req, vp, false);
529 case VPT_TYPE_LITERAL:
530 RDEBUG("\t%s %s '%s'", map->dst->name,
531 fr_int2str(fr_tokens, map->op, "<INVALID>"),
534 vp = pairalloc(map->dst, map->dst->vpt_da);
538 if (!pairparsevalue(vp, map->src->name)) {
543 pairadd(to_cache, vp);
546 vp = paircopyvp(request, vp);
547 radius_pairmove(request, to_req, vp, false);
558 if (!rbtree_insert(inst->cache, c)) {
559 REDEBUG("FAILED adding entry for key %s", key);
564 if (!fr_heap_insert(inst->heap, c)) {
565 REDEBUG("FAILED adding entry for key %s", key);
566 rbtree_deletebydata(inst->cache, c);
570 RDEBUG("Inserted entry, TTL %d seconds", ttl);
576 * Verify that the cache section makes sense.
578 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
580 value_pair_map_t *map;
582 if (radius_attrmap(cf_section_sub_find(inst->cs, "update"),
583 head, PAIR_LIST_REQUEST,
584 PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
589 cf_log_err_cs(inst->cs,
590 "Cache config must contain an update section, and "
591 "that section must not be empty");
596 for (map = *head; map != NULL; map = map->next) {
597 if ((map->dst->type != VPT_TYPE_ATTR) &&
598 (map->dst->type != VPT_TYPE_LIST)) {
599 cf_log_err(map->ci, "Left operand must be an attribute "
606 * Can't copy an xlat expansion or literal into a list,
607 * we don't know what type of attribute we'd need
610 * The only exception is where were using a unary
613 if ((map->dst->type == VPT_TYPE_LIST) &&
614 (map->op != T_OP_CMP_FALSE) &&
615 ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
616 cf_log_err(map->ci, "Can't copy value into list (we don't know which attribute to create)");
621 switch (map->src->type) {
623 cf_log_err(map->ci, "Exec values are not allowed");
628 * Only =, :=, += and -= operators are supported for
631 case VPT_TYPE_LITERAL:
633 * @fixme: This should be moved into a common function
634 * with the check in do_compile_modupdate.
636 if (map->dst->type == VPT_TYPE_ATTR) {
640 MEM(vp = pairalloc(map->dst, map->dst->vpt_da));
643 ret = pairparsevalue(vp, map->src->name);
646 cf_log_err(map->ci, "%s", fr_strerror());
662 cf_log_err(map->ci, "Operator \"%s\" not "
663 "allowed for %s values",
664 fr_int2str(fr_tokens, map->op,
666 fr_int2str(vpt_types, map->src->type,
678 * Allow single attribute values to be retrieved from the cache.
680 static ssize_t cache_xlat(void *instance, REQUEST *request,
681 char const *fmt, char *out, size_t freespace)
683 rlm_cache_entry_t *c;
684 rlm_cache_t *inst = instance;
685 VALUE_PAIR *vp, *vps;
687 DICT_ATTR const *target;
692 list = radius_list_name(&p, PAIR_LIST_REQUEST);
694 target = dict_attrbyname(p);
696 REDEBUG("Unknown attribute \"%s\"", p);
700 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
701 c = cache_find(inst, request, fmt);
704 RDEBUG("No cache entry for key \"%s\"", fmt);
710 case PAIR_LIST_REQUEST:
714 case PAIR_LIST_REPLY:
718 case PAIR_LIST_CONTROL:
722 case PAIR_LIST_UNKNOWN:
723 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
724 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
728 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
729 REDEBUG("Unsupported list \"%s\"",
730 fr_int2str(pair_lists, list, "¿Unknown?"));
734 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
736 RDEBUG("No instance of this attribute has been cached");
741 len = vp_prints_value(out, freespace, vp, 0);
742 if (is_truncated(len, freespace)) {
743 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
744 REDEBUG("Insufficient buffer space to write cached value");
748 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
754 * Only free memory we allocated. The strings allocated via
755 * cf_section_parse() do not need to be freed.
757 static int mod_detach(void *instance)
759 rlm_cache_t *inst = instance;
761 talloc_free(inst->maps);
763 fr_heap_delete(inst->heap);
764 rbtree_free(inst->cache);
766 #ifdef HAVE_PTHREAD_H
767 pthread_mutex_destroy(&inst->cache_mutex);
774 * Instantiate the module.
776 static int mod_instantiate(CONF_SECTION *conf, void *instance)
778 rlm_cache_t *inst = instance;
782 inst->xlat_name = cf_section_name2(conf);
783 if (!inst->xlat_name) {
784 inst->xlat_name = cf_section_name1(conf);
788 * Register the cache xlat function
790 xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
792 rad_assert(inst->key && *inst->key);
794 if (inst->ttl == 0) {
795 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
799 if (inst->epoch != 0) {
800 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
804 #ifdef HAVE_PTHREAD_H
805 if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
806 ERROR("Failed initializing mutex: %s",
815 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
817 ERROR("Failed to create cache");
822 * The heap of entries to expire.
824 inst->heap = fr_heap_create(cache_heap_cmp,
825 offsetof(rlm_cache_entry_t, offset));
827 ERROR("Failed to create heap for the cache");
832 * Make sure the users don't screw up too badly.
834 if (cache_verify(inst, &inst->maps) < 0) {
842 * Do caching checks. Since we can update ANY VP list, we do
843 * exactly the same thing for all sections (autz / auth / etc.)
845 * If you want to cache something different in different sections,
846 * configure another cache module.
848 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
850 rlm_cache_entry_t *c;
851 rlm_cache_t *inst = instance;
856 if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
857 return RLM_MODULE_FAIL;
860 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
861 c = cache_find(inst, request, buffer);
864 * If yes, only return whether we found a valid cache entry
866 vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
867 if (vp && vp->vp_integer) {
868 rcode = c ? RLM_MODULE_OK:
874 cache_merge(inst, request, c);
876 rcode = RLM_MODULE_OK;
880 c = cache_add(inst, request, buffer);
882 rcode = RLM_MODULE_NOOP;
886 rcode = RLM_MODULE_UPDATED;
889 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
895 * The module name should be the only globally exported symbol.
896 * That is, everything else should be 'static'.
898 * If the module needs to temporarily modify it's instantiation
899 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
900 * The server will then take care of ensuring that the module
901 * is single-threaded.
903 module_t rlm_cache = {
909 mod_instantiate, /* instantiation */
910 mod_detach, /* detach */
912 NULL, /* authentication */
913 mod_cache_it, /* authorization */
914 mod_cache_it, /* preaccounting */
915 mod_cache_it, /* accounting */
916 NULL, /* checksimul */
917 mod_cache_it, /* pre-proxy */
918 mod_cache_it, /* post-proxy */
919 mod_cache_it, /* post-auth */