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 * Compare two entries by key. There may only be one entry with
85 static int cache_entry_cmp(void const *one, void const *two)
87 rlm_cache_entry_t const *a = one;
88 rlm_cache_entry_t const *b = two;
90 return strcmp(a->key, b->key);
93 static void cache_entry_free(void *data)
95 rlm_cache_entry_t *c = data;
97 pairfree(&c->control);
105 * Compare two entries by expiry time. There may be multiple
106 * entries with the same expiry time.
108 static int cache_heap_cmp(void const *one, void const *two)
110 rlm_cache_entry_t const *a = one;
111 rlm_cache_entry_t const *b = two;
113 if (a->expires < b->expires) return -1;
114 if (a->expires > b->expires) return +1;
120 * Merge a cached entry into a REQUEST.
122 static void cache_merge(rlm_cache_t *inst, REQUEST *request,
123 rlm_cache_entry_t *c)
127 rad_assert(request != NULL);
128 rad_assert(c != NULL);
130 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
131 if (vp && (vp->vp_integer == 0)) {
132 RDEBUG2("Told not to merge entry into request");
137 RDEBUG2("Merging cached control list:");
138 rdebug_pair_list(2, request, c->control);
140 pairadd(&request->config_items, paircopy(request, c->control));
143 if (c->packet && request->packet) {
144 RDEBUG2("Merging cached request list:");
145 rdebug_pair_list(2, request, c->packet);
147 pairadd(&request->packet->vps,
148 paircopy(request->packet, c->packet));
151 if (c->reply && request->reply) {
152 RDEBUG2("Merging cached reply list:");
153 rdebug_pair_list(2, request, c->reply);
155 pairadd(&request->reply->vps,
156 paircopy(request->reply, c->reply));
160 vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
161 rad_assert(vp != NULL);
163 vp->vp_integer = c->hits;
165 pairadd(&request->packet->vps, vp);
171 * Find a cached entry.
173 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
177 rlm_cache_entry_t *c, my_c;
181 * Look at the expiry heap.
183 c = fr_heap_peek(inst->heap);
185 rad_assert(rbtree_num_elements(inst->cache) == 0);
190 * If it's time to expire an old entry, do so now.
192 if (c->expires < request->timestamp) {
193 fr_heap_extract(inst->heap, c);
194 rbtree_deletebydata(inst->cache, c);
198 * Is there an entry for this key?
201 c = rbtree_finddata(inst->cache, &my_c);
205 * Yes, but it expired, OR the "forget all" epoch has
206 * passed. Delete it, and pretend it doesn't exist.
208 if ((c->expires < request->timestamp) ||
209 (c->created < inst->epoch)) {
211 RDEBUG("Entry has expired, removing");
213 fr_heap_extract(inst->heap, c);
214 rbtree_deletebydata(inst->cache, c);
219 RDEBUG("Found entry for \"%s\"", key);
222 * Update the expiry time based on the TTL.
223 * A TTL of 0 means "delete from the cache".
225 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
227 if (vp->vp_integer == 0) goto delete;
229 ttl = vp->vp_integer;
230 c->expires = request->timestamp + ttl;
231 RDEBUG("Adding %d to the TTL", ttl);
240 * Add an entry to the cache.
242 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
246 VALUE_PAIR *vp, *found, **to_req, **to_cache, **from;
252 value_pair_map_t const *map;
254 rlm_cache_entry_t *c;
257 if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
258 RDEBUG("Cache is full: %d entries", inst->max_entries);
263 * TTL of 0 means "don't cache this entry"
265 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
266 if (vp && (vp->vp_integer == 0)) return NULL;
268 c = talloc_zero(inst, rlm_cache_entry_t);
269 c->key = talloc_strdup(c, key);
270 c->created = c->expires = request->timestamp;
273 * Use per-entry TTL, or globally defined one.
276 ttl = vp->vp_integer;
282 RDEBUG("Creating entry for \"%s\"", key);
285 * Check to see if we need to merge the entry into the request
287 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
288 if (vp && (vp->vp_integer == 0)) {
290 RDEBUG2("Told not to merge new entry into request");
293 for (map = inst->maps; map != NULL; map = map->next) {
294 rad_assert(map->dst && map->src);
297 * Specifying inner/outer request doesn't work here
298 * but there's no easy fix...
300 switch (map->dst->list) {
301 case PAIR_LIST_REQUEST:
302 to_cache = &c->packet;
305 case PAIR_LIST_REPLY:
306 to_cache = &c->reply;
309 case PAIR_LIST_CONTROL:
310 to_cache = &c->control;
319 * Resolve the destination in the current request.
320 * We need to add the to_cache there too if any of these
323 * - Map specifies an xlat'd string.
324 * - Map specifies a literal string.
325 * - Map src and dst lists differ.
326 * - Map src and dst attributes differ
329 if (merge && ( !map->src->da ||
330 (map->src->list != map->dst->list) ||
331 (map->src->da != map->dst->da))) {
334 * It's ok if the list isn't valid here...
335 * It might be valid later when we merge
338 if (radius_request(&context, map->dst->request) == 0) {
339 to_req = radius_list(context, map->dst->list);
344 * We infer that src was an attribute ref from the fact
347 RDEBUG4(":: dst is \"%s\" src is \"%s\"",
348 fr_int2str(vpt_types, map->dst->type, "<INVALID>"),
349 fr_int2str(vpt_types, map->src->type, "<INVALID>"));
351 switch (map->src->type) {
359 if (radius_request(&context, map->src->request) == 0) {
360 from = radius_list(context, map->src->list);
364 * Can't add the attribute if the list isn't
369 paircursor(&cursor, from);
370 found = pairfindnext(&cursor, da->attr, da->vendor, TAG_ANY);
372 RWDEBUG("\"%s\" not found, skipping",
377 RDEBUG("\t%s %s %s", map->dst->name,
378 fr_int2str(fr_tokens, map->op, "<INVALID>"),
385 vp = map->dst->type == VPT_TYPE_LIST ?
386 paircopyvp(c, found) :
387 paircopyvpdata(c, map->dst->da, found);
391 pairadd(to_cache, vp);
394 vp = paircopyvp(request, vp);
395 radius_pairmove(request, to_req, vp);
401 vp = map->dst->type == VPT_TYPE_LIST ?
402 paircopyvp(c, found) :
403 paircopyvpdata(c, map->dst->da, found);
407 pairadd(to_cache, vp);
410 vp = paircopyvp(request, vp);
411 radius_pairmove(request, to_req, vp);
414 } while ((found = pairfindnext(&cursor, da->attr, da->vendor, TAG_ANY)));
428 rad_assert(map->src->type == VPT_TYPE_LIST);
432 if (radius_request(&context, map->src->request) == 0) {
433 from = radius_list(context, map->src->list);
437 found = paircopy(c, *from);
438 if (!found) continue;
440 for (i = paircursor(&cursor, &vp);
442 i = pairnext(&cursor)) {
443 RDEBUG("\t%s %s %s (%s)", map->dst->name,
444 fr_int2str(fr_tokens, map->op, "<INVALID>"),
445 map->src->name, i->da->name);
449 pairadd(to_cache, found);
452 vp = paircopy(request, found);
453 radius_pairmove(request, to_req, vp);
459 * It was most likely a double quoted string that now
460 * needs to be expanded.
463 if (radius_xlat(buffer, sizeof(buffer), request, map->src->name, NULL, NULL) <= 0) {
467 RDEBUG("\t%s %s \"%s\"", map->dst->name,
468 fr_int2str(fr_tokens, map->op, "<INVALID>"),
471 vp = pairalloc(NULL, map->dst->da);
475 if (!pairparsevalue(vp, buffer)) {
480 pairadd(to_cache, vp);
483 vp = paircopyvp(request, vp);
484 radius_pairmove(request, to_req, vp);
491 case VPT_TYPE_LITERAL:
492 RDEBUG("\t%s %s '%s'", map->dst->name,
493 fr_int2str(fr_tokens, map->op, "<INVALID>"),
496 vp = pairalloc(NULL, map->dst->da);
500 if (!pairparsevalue(vp, map->src->name)) {
505 pairadd(to_cache, vp);
508 vp = paircopyvp(request, vp);
509 radius_pairmove(request, to_req, vp);
520 if (!rbtree_insert(inst->cache, c)) {
521 REDEBUG("FAILED adding entry for key %s", key);
526 if (!fr_heap_insert(inst->heap, c)) {
527 REDEBUG("FAILED adding entry for key %s", key);
528 rbtree_deletebydata(inst->cache, c);
532 RDEBUG("Inserted entry, TTL %d seconds", ttl);
538 * Verify that the cache section makes sense.
540 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
542 value_pair_map_t *map;
544 if (radius_attrmap(cf_section_sub_find(inst->cs, "update"),
545 head, PAIR_LIST_REQUEST,
546 PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
551 cf_log_err_cs(inst->cs,
552 "Cache config must contain an update section, and "
553 "that section must not be empty");
558 for (map = *head; map != NULL; map = map->next) {
559 if ((map->dst->type != VPT_TYPE_ATTR) &&
560 (map->dst->type != VPT_TYPE_LIST)) {
561 cf_log_err(map->ci, "Left operand must be an attribute "
567 switch (map->src->type) {
569 cf_log_err(map->ci, "Exec values are not allowed");
574 * Only =, :=, += and -= operators are supported for
577 case VPT_TYPE_LITERAL:
588 cf_log_err(map->ci, "Operator \"%s\" not "
589 "allowed for %s values",
590 fr_int2str(fr_tokens, map->op,
592 fr_int2str(vpt_types, map->src->type,
604 * Allow single attribute values to be retrieved from the cache.
606 static ssize_t cache_xlat(void *instance, REQUEST *request,
607 char const *fmt, char *out, size_t freespace)
609 rlm_cache_entry_t *c;
610 rlm_cache_t *inst = instance;
611 VALUE_PAIR *vp, *vps;
613 DICT_ATTR const *target;
617 list = radius_list_name(&p, PAIR_LIST_REQUEST);
619 target = dict_attrbyname(p);
621 REDEBUG("Unknown attribute \"%s\"", p);
625 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
626 c = cache_find(inst, request, fmt);
629 RDEBUG("No cache entry for key \"%s\"", fmt);
635 case PAIR_LIST_REQUEST:
639 case PAIR_LIST_REPLY:
643 case PAIR_LIST_CONTROL:
647 case PAIR_LIST_UNKNOWN:
648 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
649 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
653 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
654 REDEBUG("Unsupported list \"%s\"",
655 fr_int2str(pair_lists, list, "¿Unknown?"));
659 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
661 RDEBUG("No instance of this attribute has been cached");
666 ret = vp_prints_value(out, freespace, vp, 0);
668 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
674 * A mapping of configuration file names to internal variables.
676 * Note that the string is dynamically allocated, so it MUST
677 * be freed. When the configuration file parse re-reads the string,
678 * it free's the old one, and strdup's the new one, placing the pointer
679 * to the strdup'd string into 'config.string'. This gets around
682 static const CONF_PARSER module_config[] = {
683 { "key", PW_TYPE_STRING_PTR | PW_TYPE_REQUIRED,
684 offsetof(rlm_cache_t, key), NULL, NULL},
685 { "ttl", PW_TYPE_INTEGER,
686 offsetof(rlm_cache_t, ttl), NULL, "500" },
687 { "max_entries", PW_TYPE_INTEGER,
688 offsetof(rlm_cache_t, max_entries), NULL, "16384" },
689 { "epoch", PW_TYPE_INTEGER,
690 offsetof(rlm_cache_t, epoch), NULL, "0" },
691 { "add_stats", PW_TYPE_BOOLEAN,
692 offsetof(rlm_cache_t, stats), NULL, "no" },
694 { NULL, -1, 0, NULL, NULL } /* end the list */
699 * Only free memory we allocated. The strings allocated via
700 * cf_section_parse() do not need to be freed.
702 static int mod_detach(void *instance)
704 rlm_cache_t *inst = instance;
706 talloc_free(inst->maps);
708 fr_heap_delete(inst->heap);
709 rbtree_free(inst->cache);
711 #ifdef HAVE_PTHREAD_H
712 pthread_mutex_destroy(&inst->cache_mutex);
719 * Instantiate the module.
721 static int mod_instantiate(CONF_SECTION *conf, void *instance)
723 rlm_cache_t *inst = instance;
727 inst->xlat_name = cf_section_name2(conf);
728 if (!inst->xlat_name) {
729 inst->xlat_name = cf_section_name1(conf);
733 * Register the cache xlat function
735 xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
737 rad_assert(inst->key && *inst->key);
739 if (inst->ttl == 0) {
740 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
744 if (inst->epoch != 0) {
745 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
749 #ifdef HAVE_PTHREAD_H
750 if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
751 EDEBUG("Failed initializing mutex: %s",
760 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
762 EDEBUG("Failed to create cache");
767 * The heap of entries to expire.
769 inst->heap = fr_heap_create(cache_heap_cmp,
770 offsetof(rlm_cache_entry_t, offset));
772 EDEBUG("Failed to create heap for the cache");
777 * Make sure the users don't screw up too badly.
779 if (cache_verify(inst, &inst->maps) < 0) {
787 * Do caching checks. Since we can update ANY VP list, we do
788 * exactly the same thing for all sections (autz / auth / etc.)
790 * If you want to cache something different in different sections,
791 * configure another cache module.
793 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
795 rlm_cache_entry_t *c;
796 rlm_cache_t *inst = instance;
801 if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) {
802 return RLM_MODULE_FAIL;
805 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
806 c = cache_find(inst, request, buffer);
809 * If yes, only return whether we found a valid cache entry
811 vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
812 if (vp && vp->vp_integer) {
813 rcode = c ? RLM_MODULE_OK:
819 cache_merge(inst, request, c);
821 rcode = RLM_MODULE_OK;
825 c = cache_add(inst, request, buffer);
827 rcode = RLM_MODULE_NOOP;
831 rcode = RLM_MODULE_UPDATED;
834 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
840 * The module name should be the only globally exported symbol.
841 * That is, everything else should be 'static'.
843 * If the module needs to temporarily modify it's instantiation
844 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
845 * The server will then take care of ensuring that the module
846 * is single-threaded.
848 module_t rlm_cache = {
854 mod_instantiate, /* instantiation */
855 mod_detach, /* detach */
857 NULL, /* authentication */
858 cache_it, /* authorization */
859 cache_it, /* preaccounting */
860 cache_it, /* accounting */
861 NULL, /* checksimul */
862 cache_it, /* pre-proxy */
863 cache_it, /* post-proxy */
864 cache_it, /* post-auth */