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/modcall.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 char const *xlat_name;
49 value_pair_map_t *maps; //!< Attribute map applied to users
52 pthread_mutex_t cache_mutex;
56 typedef struct rlm_cache_entry_t {
68 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
69 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
71 #define PTHREAD_MUTEX_LOCK(_x)
72 #define PTHREAD_MUTEX_UNLOCK(_x)
75 #define MAX_ATTRMAP 128
78 * A mapping of configuration file names to internal variables.
80 * Note that the string is dynamically allocated, so it MUST
81 * be freed. When the configuration file parse re-reads the string,
82 * it free's the old one, and strdup's the new one, placing the pointer
83 * to the strdup'd string into 'config.string'. This gets around
86 static const CONF_PARSER module_config[] = {
87 { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_cache_t, key), NULL },
88 { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
89 { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "16384" },
91 /* Should be a type which matches time_t, @fixme before 2038 */
92 { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
93 { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
95 { NULL, -1, 0, NULL, NULL } /* end the list */
100 * Compare two entries by key. There may only be one entry with
103 static int cache_entry_cmp(void const *one, void const *two)
105 rlm_cache_entry_t const *a = one;
106 rlm_cache_entry_t const *b = two;
108 return strcmp(a->key, b->key);
111 static void cache_entry_free(void *data)
113 rlm_cache_entry_t *c = data;
115 pairfree(&c->control);
116 pairfree(&c->packet);
123 * Compare two entries by expiry time. There may be multiple
124 * entries with the same expiry time.
126 static int cache_heap_cmp(void const *one, void const *two)
128 rlm_cache_entry_t const *a = one;
129 rlm_cache_entry_t const *b = two;
131 if (a->expires < b->expires) return -1;
132 if (a->expires > b->expires) return +1;
138 * Merge a cached entry into a REQUEST.
140 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
144 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
145 if (vp && (vp->vp_integer == 0)) {
146 RDEBUG2("Told not to merge entry into request");
151 RDEBUG2("Merging cached control list");
152 rdebug_pair_list(L_DBG_LVL_2, request, c->control);
153 pairadd(&request->config_items, paircopy(request, c->control));
156 if (c->packet && request->packet) {
157 RDEBUG2("Merging cached request list");
158 rdebug_pair_list(L_DBG_LVL_2, request, c->packet);
160 pairadd(&request->packet->vps,
161 paircopy(request->packet, c->packet));
164 if (c->reply && request->reply) {
165 RDEBUG2("Merging cached reply list");
166 rdebug_pair_list(L_DBG_LVL_2, request, c->reply);
168 pairadd(&request->reply->vps,
169 paircopy(request->reply, c->reply));
173 vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
174 rad_assert(vp != NULL);
176 vp->vp_integer = c->hits;
178 pairadd(&request->packet->vps, vp);
184 * Find a cached entry.
186 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
190 rlm_cache_entry_t *c, my_c;
194 * Look at the expiry heap.
196 c = fr_heap_peek(inst->heap);
198 rad_assert(rbtree_num_elements(inst->cache) == 0);
203 * If it's time to expire an old entry, do so now.
205 if (c->expires < request->timestamp) {
206 fr_heap_extract(inst->heap, c);
207 rbtree_deletebydata(inst->cache, c);
211 * Is there an entry for this key?
214 c = rbtree_finddata(inst->cache, &my_c);
218 * Yes, but it expired, OR the "forget all" epoch has
219 * passed. Delete it, and pretend it doesn't exist.
221 if ((c->expires < request->timestamp) ||
222 (c->created < inst->epoch)) {
224 RDEBUG("Removing expired entry");
226 fr_heap_extract(inst->heap, c);
227 rbtree_deletebydata(inst->cache, c);
232 RDEBUG("Found entry for \"%s\"", key);
235 * Update the expiry time based on the TTL.
236 * A TTL of 0 means "delete from the cache".
237 * A TTL < 0 means "delete from the cache and recreate the entry".
239 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
241 if (vp->vp_signed <= 0) goto delete;
244 c->expires = request->timestamp + ttl;
245 RDEBUG("Adding %d to the TTL", ttl);
253 /** Callback for map_to_request
255 * Simplifies merging VALUE_PAIRs into the current request.
257 static int _cache_add(VALUE_PAIR **out, REQUEST *request, UNUSED value_pair_map_t const *map, void *ctx)
261 vp = talloc_get_type_abort(ctx, VALUE_PAIR);
262 /* map_to_request will reparent */
263 *out = paircopy(request, vp);
265 if (!*out) return -1;
270 * Add an entry to the cache.
272 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request, char const *key)
275 VALUE_PAIR *vp, *to_cache;
276 vp_cursor_t src_list, cached_request, cached_reply, cached_control;
280 value_pair_map_t const *map;
282 rlm_cache_entry_t *c;
284 if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
285 RDEBUG("Cache is full: %d entries", inst->max_entries);
290 * TTL of 0 means "don't cache this entry"
292 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
293 if (vp && (vp->vp_signed == 0)) return NULL;
295 c = talloc_zero(NULL, rlm_cache_entry_t);
296 c->key = talloc_typed_strdup(c, key);
297 c->created = c->expires = request->timestamp;
300 * Use per-entry TTL if > 0, or globally defined one.
302 ttl = vp && (vp->vp_signed > 0) ? vp->vp_integer : inst->ttl;
305 RDEBUG("Creating entry for \"%s\"", key);
308 * Check to see if we need to merge the entry into the request
310 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
311 if (vp && (vp->vp_integer == 0)) {
313 RDEBUG2("Told not to merge new entry into request");
316 fr_cursor_init(&cached_request, &c->packet);
317 fr_cursor_init(&cached_reply, &c->reply);
318 fr_cursor_init(&cached_control, &c->control);
320 for (map = inst->maps; map != NULL; map = map->next) {
321 bool do_merge = merge;
323 rad_assert(map->lhs && map->rhs);
325 if (map_to_vp(&to_cache, request, map, NULL) < 0) {
326 RDEBUG("Skipping %s", map->rhs->name);
331 * Merge attributes into the current request if:
332 * - Map specifies an xlat'd string.
333 * - Map specifies a literal string.
334 * - Map specifies an exec.
335 * - Map src and dst lists differ.
336 * - Map src and dst attributes differ
338 * Unless Cache-Merge = no
340 if (do_merge) switch (map->rhs->type) {
341 case TMPL_TYPE_LITERAL:
347 if (map->rhs->tmpl_list == map->lhs->tmpl_list) do_merge = false;
351 rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
352 if (map->rhs->tmpl_da == map->lhs->tmpl_da) do_merge = false;
360 * Reparent the VPs map_to_vp may return multiple.
362 for (vp = fr_cursor_init(&src_list, &to_cache);
364 vp = fr_cursor_next(&src_list)) {
368 * Prevent people from accidentally caching
369 * cache control attributes.
371 if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
373 case PW_CACHE_STATUS_ONLY:
374 case PW_CACHE_READ_ONLY:
376 case PW_CACHE_ENTRY_HITS:
377 RDEBUG2("Skipping %s", vp->da->name);
384 if (debug_flag) map_debug_log(request, map, vp);
385 (void) talloc_steal(c, vp);
389 switch (map->lhs->tmpl_list) {
390 case PAIR_LIST_REQUEST:
391 fr_cursor_insert(&cached_request, vp);
394 case PAIR_LIST_REPLY:
395 fr_cursor_insert(&cached_reply, vp);
398 case PAIR_LIST_CONTROL:
399 fr_cursor_insert(&cached_control, vp);
403 rad_assert(0); /* should have been caught by validation */
406 if (do_merge && map_dst_valid(request, map)) {
407 /* There's no reason for this to fail (we checked the dst was valid) */
408 RDEBUG2("Adding to request:");
409 if (map_to_request(request, map, _cache_add, vp) < 0) rad_assert(0);
414 if (!rbtree_insert(inst->cache, c)) {
415 REDEBUG("FAILED adding entry for key %s", key);
420 if (!fr_heap_insert(inst->heap, c)) {
421 REDEBUG("FAILED adding entry for key %s", key);
422 rbtree_deletebydata(inst->cache, c);
426 RDEBUG("Inserted entry, TTL %d seconds", ttl);
431 /** Verify that a map in the cache section makes sense
434 static int cache_verify(value_pair_map_t *map, UNUSED void *ctx)
436 if (modcall_fixup_update(map, ctx) < 0) return -1;
438 if ((map->lhs->type != TMPL_TYPE_ATTR) &&
439 (map->lhs->type != TMPL_TYPE_LIST)) {
440 cf_log_err(map->ci, "Left operand must be an attribute ref or a list");
444 switch (map->rhs->type) {
446 cf_log_err(map->ci, "Exec values are not allowed");
449 * Only =, :=, += and -= operators are supported for
452 case TMPL_TYPE_LITERAL:
463 cf_log_err(map->ci, "Operator \"%s\" not allowed for %s values",
464 fr_int2str(fr_tokens, map->op, "<INVALID>"),
465 fr_int2str(tmpl_types, map->rhs->type, "<INVALID>"));
476 * Allow single attribute values to be retrieved from the cache.
478 static ssize_t cache_xlat(void *instance, REQUEST *request,
479 char const *fmt, char *out, size_t freespace)
481 rlm_cache_entry_t *c;
482 rlm_cache_t *inst = instance;
483 VALUE_PAIR *vp, *vps;
485 DICT_ATTR const *target;
490 list = radius_list_name(&p, PAIR_LIST_REQUEST);
492 target = dict_attrbyname(p);
494 REDEBUG("Unknown attribute \"%s\"", p);
498 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
499 c = cache_find(inst, request, fmt);
502 RDEBUG("No cache entry for key \"%s\"", fmt);
508 case PAIR_LIST_REQUEST:
512 case PAIR_LIST_REPLY:
516 case PAIR_LIST_CONTROL:
520 case PAIR_LIST_UNKNOWN:
521 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
522 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
526 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
527 REDEBUG("Unsupported list \"%s\"",
528 fr_int2str(pair_lists, list, "<UNKNOWN>"));
532 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
534 RDEBUG("No instance of this attribute has been cached");
539 len = vp_prints_value(out, freespace, vp, 0);
540 if (is_truncated(len, freespace)) {
541 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
542 REDEBUG("Insufficient buffer space to write cached value");
546 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
552 * Only free memory we allocated. The strings allocated via
553 * cf_section_parse() do not need to be freed.
555 static int mod_detach(void *instance)
557 rlm_cache_t *inst = instance;
559 talloc_free(inst->maps);
561 fr_heap_delete(inst->heap);
562 rbtree_free(inst->cache);
564 #ifdef HAVE_PTHREAD_H
565 pthread_mutex_destroy(&inst->cache_mutex);
572 * Instantiate the module.
574 static int mod_instantiate(CONF_SECTION *conf, void *instance)
576 rlm_cache_t *inst = instance;
580 inst->xlat_name = cf_section_name2(conf);
581 if (!inst->xlat_name) {
582 inst->xlat_name = cf_section_name1(conf);
586 * Register the cache xlat function
588 xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
590 rad_assert(inst->key && *inst->key);
592 if (inst->ttl == 0) {
593 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
597 if (inst->epoch != 0) {
598 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
602 #ifdef HAVE_PTHREAD_H
603 if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
604 ERROR("Failed initializing mutex: %s",
614 inst->cache = rbtree_create(NULL, cache_entry_cmp, cache_entry_free, 0);
616 ERROR("Failed to create cache");
619 fr_link_talloc_ctx_free(inst, inst->cache);
622 * The heap of entries to expire.
624 inst->heap = fr_heap_create(cache_heap_cmp,
625 offsetof(rlm_cache_entry_t, offset));
627 ERROR("Failed to create heap for the cache");
632 * Make sure the users don't screw up too badly.
634 if (map_afrom_cs(&inst->maps, cf_section_sub_find(inst->cs, "update"),
635 PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, cache_verify, NULL, MAX_ATTRMAP) < 0) {
640 cf_log_err_cs(inst->cs, "Cache config must contain an update section, and "
641 "that section must not be empty");
650 * Do caching checks. Since we can update ANY VP list, we do
651 * exactly the same thing for all sections (autz / auth / etc.)
653 * If you want to cache something different in different sections,
654 * configure another cache module.
656 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
658 rlm_cache_entry_t *c;
659 rlm_cache_t *inst = instance;
665 if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
666 return RLM_MODULE_FAIL;
669 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
670 c = cache_find(inst, request, buffer);
673 * If yes, only return whether we found a valid cache entry
675 vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
676 if (vp && vp->vp_integer) {
677 rcode = c ? RLM_MODULE_OK:
683 cache_merge(inst, request, c);
685 rcode = RLM_MODULE_OK;
689 vp = pairfind(request->config_items, PW_CACHE_READ_ONLY, 0, TAG_ANY);
690 if (vp && vp->vp_integer) {
691 rcode = RLM_MODULE_NOTFOUND;
695 c = cache_add(inst, request, buffer);
697 rcode = RLM_MODULE_NOOP;
701 rcode = RLM_MODULE_UPDATED;
704 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
707 * Reset control attributes
709 for (vp = fr_cursor_init(&cursor, &request->config_items);
711 vp = fr_cursor_next(&cursor)) {
712 if (vp->da->vendor == 0) switch (vp->da->attr) {
714 case PW_CACHE_READ_ONLY:
716 vp = fr_cursor_remove(&cursor);
727 * The module name should be the only globally exported symbol.
728 * That is, everything else should be 'static'.
730 * If the module needs to temporarily modify it's instantiation
731 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
732 * The server will then take care of ensuring that the module
733 * is single-threaded.
735 module_t rlm_cache = {
741 mod_instantiate, /* instantiation */
742 mod_detach, /* detach */
744 NULL, /* authentication */
745 mod_cache_it, /* authorization */
746 mod_cache_it, /* preaccounting */
747 mod_cache_it, /* accounting */
748 NULL, /* checksimul */
749 mod_cache_it, /* pre-proxy */
750 mod_cache_it, /* post-proxy */
751 mod_cache_it, /* post-auth */