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
34 #define PW_CACHE_READ_ONLY 1144
37 * Define a structure for our module configuration.
39 * These variables do not need to be in a structure, but it's
40 * a lot cleaner to do so, and a pointer to the structure can
41 * be used as the instance handle.
43 typedef struct rlm_cache_t {
44 char const *xlat_name;
54 value_pair_map_t *maps; //!< Attribute map applied to users
57 pthread_mutex_t cache_mutex;
61 typedef struct rlm_cache_entry_t {
73 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
74 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
76 #define PTHREAD_MUTEX_LOCK(_x)
77 #define PTHREAD_MUTEX_UNLOCK(_x)
80 #define MAX_ATTRMAP 128
83 * A mapping of configuration file names to internal variables.
85 * Note that the string is dynamically allocated, so it MUST
86 * be freed. When the configuration file parse re-reads the string,
87 * it free's the old one, and strdup's the new one, placing the pointer
88 * to the strdup'd string into 'config.string'. This gets around
91 static const CONF_PARSER module_config[] = {
92 { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_cache_t, key), NULL },
93 { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
94 { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "16384" },
96 /* Should be a type which matches time_t, @fixme before 2038 */
97 { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
98 { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
100 { NULL, -1, 0, NULL, NULL } /* end the list */
105 * Compare two entries by key. There may only be one entry with
108 static int cache_entry_cmp(void const *one, void const *two)
110 rlm_cache_entry_t const *a = one;
111 rlm_cache_entry_t const *b = two;
113 return strcmp(a->key, b->key);
116 static void cache_entry_free(void *data)
118 rlm_cache_entry_t *c = data;
120 pairfree(&c->control);
121 pairfree(&c->packet);
128 * Compare two entries by expiry time. There may be multiple
129 * entries with the same expiry time.
131 static int cache_heap_cmp(void const *one, void const *two)
133 rlm_cache_entry_t const *a = one;
134 rlm_cache_entry_t const *b = two;
136 if (a->expires < b->expires) return -1;
137 if (a->expires > b->expires) return +1;
143 * Merge a cached entry into a REQUEST.
145 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
149 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
150 if (vp && (vp->vp_integer == 0)) {
151 RDEBUG2("Told not to merge entry into request");
156 RDEBUG2("Merging cached control list:");
157 rdebug_pair_list(2, request, c->control);
159 pairadd(&request->config_items, paircopy(request, c->control));
162 if (c->packet && request->packet) {
163 RDEBUG2("Merging cached request list:");
164 rdebug_pair_list(2, request, c->packet);
166 pairadd(&request->packet->vps,
167 paircopy(request->packet, c->packet));
170 if (c->reply && request->reply) {
171 RDEBUG2("Merging cached reply list:");
172 rdebug_pair_list(2, request, c->reply);
174 pairadd(&request->reply->vps,
175 paircopy(request->reply, c->reply));
179 vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
180 rad_assert(vp != NULL);
182 vp->vp_integer = c->hits;
184 pairadd(&request->packet->vps, vp);
190 * Find a cached entry.
192 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
196 rlm_cache_entry_t *c, my_c;
200 * Look at the expiry heap.
202 c = fr_heap_peek(inst->heap);
204 rad_assert(rbtree_num_elements(inst->cache) == 0);
209 * If it's time to expire an old entry, do so now.
211 if (c->expires < request->timestamp) {
212 fr_heap_extract(inst->heap, c);
213 rbtree_deletebydata(inst->cache, c);
217 * Is there an entry for this key?
220 c = rbtree_finddata(inst->cache, &my_c);
224 * Yes, but it expired, OR the "forget all" epoch has
225 * passed. Delete it, and pretend it doesn't exist.
227 if ((c->expires < request->timestamp) ||
228 (c->created < inst->epoch)) {
230 RDEBUG("Entry has expired, removing");
232 fr_heap_extract(inst->heap, c);
233 rbtree_deletebydata(inst->cache, c);
238 RDEBUG("Found entry for \"%s\"", key);
241 * Update the expiry time based on the TTL.
242 * A TTL of 0 means "delete from the cache".
243 * A TTL < 0 means "delete from the cache and recreate the entry".
245 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
247 if (vp->vp_signed <= 0) goto delete;
250 c->expires = request->timestamp + ttl;
251 RDEBUG("Adding %d to the TTL", ttl);
259 /** Callback for radius_map2request
261 * Simplifies merging VALUE_PAIRs into the current request.
263 static int _cache_add(VALUE_PAIR **out, REQUEST *request, UNUSED value_pair_map_t const *map, void *ctx)
267 vp = talloc_get_type_abort(ctx, VALUE_PAIR);
268 /* radius_map2request will reparent */
269 *out = paircopy(request, vp);
271 if (!*out) return -1;
276 * Add an entry to the cache.
278 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request, char const *key)
281 VALUE_PAIR *vp, *to_cache;
282 vp_cursor_t src_list, cached_request, cached_reply, cached_control;
286 value_pair_map_t const *map;
288 rlm_cache_entry_t *c;
290 if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
291 RDEBUG("Cache is full: %d entries", inst->max_entries);
296 * TTL of 0 means "don't cache this entry"
298 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
299 if (vp && (vp->vp_signed == 0)) return NULL;
301 c = talloc_zero(inst, rlm_cache_entry_t);
302 c->key = talloc_typed_strdup(c, key);
303 c->created = c->expires = request->timestamp;
306 * Use per-entry TTL if > 0, or globally defined one.
308 ttl = vp && (vp->vp_signed > 0) ? vp->vp_integer : inst->ttl;
311 RDEBUG("Creating entry for \"%s\"", key);
314 * Check to see if we need to merge the entry into the request
316 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
317 if (vp && (vp->vp_integer == 0)) {
319 RDEBUG2("Told not to merge new entry into request");
322 fr_cursor_init(&cached_request, &c->packet);
323 fr_cursor_init(&cached_reply, &c->reply);
324 fr_cursor_init(&cached_control, &c->control);
326 for (map = inst->maps; map != NULL; map = map->next) {
327 bool do_merge = merge;
329 rad_assert(map->dst && map->src);
331 if (radius_map2vp(&to_cache, request, map, NULL) < 0) {
332 RDEBUG("Skipping %s", map->src->name);
337 * Merge attributes into the current request if:
338 * - Map specifies an xlat'd string.
339 * - Map specifies a literal string.
340 * - Map specifies an exec.
341 * - Map src and dst lists differ.
342 * - Map src and dst attributes differ
344 * Unless Cache-Merge = no
346 if (do_merge) switch (map->src->type) {
347 case VPT_TYPE_LITERAL:
353 if (map->src->vpt_list == map->dst->vpt_list) do_merge = false;
357 if (map->src->vpt_da == map->dst->vpt_da) do_merge = false;
365 * Reparent the VPs radius_map2vp may return multiple.
367 for (vp = fr_cursor_init(&src_list, &to_cache);
369 vp = fr_cursor_next(&src_list)) {
373 * Prevent people from accidentally caching
374 * cache control attributes.
376 if (map->src->type == VPT_TYPE_LIST) switch (vp->da->attr) {
378 case PW_CACHE_STATUS_ONLY:
379 case PW_CACHE_READ_ONLY:
381 case PW_CACHE_ENTRY_HITS:
382 RDEBUG2("Skipping %s", vp->da->name);
388 RDEBUG2("Adding to cache entry:");
389 if (debug_flag) radius_map_debug(request, map, vp);
390 (void) talloc_steal(c, vp);
394 switch (map->dst->vpt_list) {
395 case PAIR_LIST_REQUEST:
396 fr_cursor_insert(&cached_request, vp);
399 case PAIR_LIST_REPLY:
400 fr_cursor_insert(&cached_reply, vp);
403 case PAIR_LIST_CONTROL:
404 fr_cursor_insert(&cached_control, vp);
408 rad_assert(0); /* should have been caught by validation */
411 if (do_merge && radius_map_dst_valid(request, map)) {
412 /* There's no reason for this to fail (we checked the dst was valid) */
413 RDEBUG2("Adding to request:");
414 if (radius_map2request(request, map, _cache_add, vp) < 0) rad_assert(0);
419 if (!rbtree_insert(inst->cache, c)) {
420 REDEBUG("FAILED adding entry for key %s", key);
425 if (!fr_heap_insert(inst->heap, c)) {
426 REDEBUG("FAILED adding entry for key %s", key);
427 rbtree_deletebydata(inst->cache, c);
431 RDEBUG("Inserted entry, TTL %d seconds", ttl);
437 * Verify that the cache section makes sense.
439 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
441 value_pair_map_t *map;
443 if (radius_attrmap(cf_section_sub_find(inst->cs, "update"),
444 head, PAIR_LIST_REQUEST,
445 PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
450 cf_log_err_cs(inst->cs,
451 "Cache config must contain an update section, and "
452 "that section must not be empty");
457 for (map = *head; map != NULL; map = map->next) {
458 if ((map->dst->type != VPT_TYPE_ATTR) &&
459 (map->dst->type != VPT_TYPE_LIST)) {
460 cf_log_err(map->ci, "Left operand must be an attribute "
467 * Can't copy an xlat expansion or literal into a list,
468 * we don't know what type of attribute we'd need
471 * The only exception is where were using a unary
474 if ((map->dst->type == VPT_TYPE_LIST) &&
475 (map->op != T_OP_CMP_FALSE) &&
476 ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
477 cf_log_err(map->ci, "Can't copy value into list (we don't know which attribute to create)");
482 switch (map->src->type) {
484 cf_log_err(map->ci, "Exec values are not allowed");
489 * Only =, :=, += and -= operators are supported for
492 case VPT_TYPE_LITERAL:
494 * @fixme: This should be moved into a common function
495 * with the check in do_compile_modupdate.
497 if (map->dst->type == VPT_TYPE_ATTR) {
501 MEM(vp = pairalloc(map->dst, map->dst->vpt_da));
504 ret = pairparsevalue(vp, map->src->name, 0);
507 cf_log_err(map->ci, "%s", fr_strerror());
523 cf_log_err(map->ci, "Operator \"%s\" not "
524 "allowed for %s values",
525 fr_int2str(fr_tokens, map->op,
527 fr_int2str(vpt_types, map->src->type,
539 * Allow single attribute values to be retrieved from the cache.
541 static ssize_t cache_xlat(void *instance, REQUEST *request,
542 char const *fmt, char *out, size_t freespace)
544 rlm_cache_entry_t *c;
545 rlm_cache_t *inst = instance;
546 VALUE_PAIR *vp, *vps;
548 DICT_ATTR const *target;
553 list = radius_list_name(&p, PAIR_LIST_REQUEST);
555 target = dict_attrbyname(p);
557 REDEBUG("Unknown attribute \"%s\"", p);
561 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
562 c = cache_find(inst, request, fmt);
565 RDEBUG("No cache entry for key \"%s\"", fmt);
571 case PAIR_LIST_REQUEST:
575 case PAIR_LIST_REPLY:
579 case PAIR_LIST_CONTROL:
583 case PAIR_LIST_UNKNOWN:
584 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
585 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
589 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
590 REDEBUG("Unsupported list \"%s\"",
591 fr_int2str(pair_lists, list, "<UNKNOWN>"));
595 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
597 RDEBUG("No instance of this attribute has been cached");
602 len = vp_prints_value(out, freespace, vp, 0);
603 if (is_truncated(len, freespace)) {
604 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
605 REDEBUG("Insufficient buffer space to write cached value");
609 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
615 * Only free memory we allocated. The strings allocated via
616 * cf_section_parse() do not need to be freed.
618 static int mod_detach(void *instance)
620 rlm_cache_t *inst = instance;
622 talloc_free(inst->maps);
624 fr_heap_delete(inst->heap);
625 rbtree_free(inst->cache);
627 #ifdef HAVE_PTHREAD_H
628 pthread_mutex_destroy(&inst->cache_mutex);
635 * Instantiate the module.
637 static int mod_instantiate(CONF_SECTION *conf, void *instance)
639 rlm_cache_t *inst = instance;
643 inst->xlat_name = cf_section_name2(conf);
644 if (!inst->xlat_name) {
645 inst->xlat_name = cf_section_name1(conf);
649 * Register the cache xlat function
651 xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
653 rad_assert(inst->key && *inst->key);
655 if (inst->ttl == 0) {
656 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
660 if (inst->epoch != 0) {
661 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
665 #ifdef HAVE_PTHREAD_H
666 if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
667 ERROR("Failed initializing mutex: %s",
676 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
678 ERROR("Failed to create cache");
683 * The heap of entries to expire.
685 inst->heap = fr_heap_create(cache_heap_cmp,
686 offsetof(rlm_cache_entry_t, offset));
688 ERROR("Failed to create heap for the cache");
693 * Make sure the users don't screw up too badly.
695 if (cache_verify(inst, &inst->maps) < 0) {
703 * Do caching checks. Since we can update ANY VP list, we do
704 * exactly the same thing for all sections (autz / auth / etc.)
706 * If you want to cache something different in different sections,
707 * configure another cache module.
709 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
711 rlm_cache_entry_t *c;
712 rlm_cache_t *inst = instance;
718 if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
719 return RLM_MODULE_FAIL;
722 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
723 c = cache_find(inst, request, buffer);
726 * If yes, only return whether we found a valid cache entry
728 vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
729 if (vp && vp->vp_integer) {
730 rcode = c ? RLM_MODULE_OK:
736 cache_merge(inst, request, c);
738 rcode = RLM_MODULE_OK;
742 vp = pairfind(request->config_items, PW_CACHE_READ_ONLY, 0, TAG_ANY);
743 if (vp && vp->vp_integer) {
744 rcode = RLM_MODULE_NOTFOUND;
748 c = cache_add(inst, request, buffer);
750 rcode = RLM_MODULE_NOOP;
754 rcode = RLM_MODULE_UPDATED;
757 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
760 * Reset control attributes
762 for (vp = fr_cursor_init(&cursor, &request->config_items);
764 vp = fr_cursor_next(&cursor)) {
765 if (vp->da->vendor == 0) switch (vp->da->attr) {
767 case PW_CACHE_READ_ONLY:
769 fr_cursor_remove(&cursor);
779 * The module name should be the only globally exported symbol.
780 * That is, everything else should be 'static'.
782 * If the module needs to temporarily modify it's instantiation
783 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
784 * The server will then take care of ensuring that the module
785 * is single-threaded.
787 module_t rlm_cache = {
793 mod_instantiate, /* instantiation */
794 mod_detach, /* detach */
796 NULL, /* authentication */
797 mod_cache_it, /* authorization */
798 mod_cache_it, /* preaccounting */
799 mod_cache_it, /* accounting */
800 NULL, /* checksimul */
801 mod_cache_it, /* pre-proxy */
802 mod_cache_it, /* post-proxy */
803 mod_cache_it, /* post-auth */