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
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>
31 #define PW_CACHE_TTL 1140
32 #define PW_CACHE_STATUS_ONLY 1141
33 #define PW_CACHE_MERGE 1142
34 #define PW_CACHE_ENTRY_HITS 1143
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 const char *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 * Compare two entries by key. There may only be one entry with
86 static int cache_entry_cmp(const void *one, const void *two)
88 const rlm_cache_entry_t *a = one;
89 const rlm_cache_entry_t *b = two;
91 return strcmp(a->key, b->key);
94 static void cache_entry_free(void *data)
96 rlm_cache_entry_t *c = data;
98 pairfree(&c->control);
99 pairfree(&c->request);
106 * Compare two entries by expiry time. There may be multiple
107 * entries with the same expiry time.
109 static int cache_heap_cmp(const void *one, const void *two)
111 const rlm_cache_entry_t *a = one;
112 const rlm_cache_entry_t *b = two;
114 if (a->expires < b->expires) return -1;
115 if (a->expires > b->expires) return +1;
121 * Merge a cached entry into a REQUEST.
123 static void cache_merge(rlm_cache_t *inst, REQUEST *request,
124 rlm_cache_entry_t *c)
128 rad_assert(request != NULL);
129 rad_assert(c != NULL);
131 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
132 if (vp && (vp->vp_integer == 0)) {
133 RDEBUG2("Told not to merge entry into request");
138 RDEBUG2("Merging cached control list:");
139 rdebug_pair_list(2, request, c->control);
141 vp = paircopy(c->control);
142 pairmove(&request->config_items, &vp);
146 if (c->request && request->packet) {
147 RDEBUG2("Merging cached request list:");
148 rdebug_pair_list(2, request, c->request);
150 vp = paircopy(c->request);
151 pairmove(&request->packet->vps, &vp);
155 if (c->reply && request->reply) {
156 RDEBUG2("Merging cached reply list:");
157 rdebug_pair_list(2, request, c->reply);
159 vp = paircopy(c->reply);
160 pairmove(&request->reply->vps, &vp);
165 vp = paircreate(PW_CACHE_ENTRY_HITS, 0);
166 rad_assert(vp != NULL);
168 vp->vp_integer = c->hits;
170 pairadd(&request->packet->vps, vp);
176 * Find a cached entry.
178 static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request,
182 rlm_cache_entry_t *c, my_c;
186 * Look at the expiry heap.
188 c = fr_heap_peek(inst->heap);
190 rad_assert(rbtree_num_elements(inst->cache) == 0);
195 * If it's time to expire an old entry, do so now.
197 if (c->expires < request->timestamp) {
198 fr_heap_extract(inst->heap, c);
199 rbtree_deletebydata(inst->cache, c);
203 * Is there an entry for this key?
206 c = rbtree_finddata(inst->cache, &my_c);
210 * Yes, but it expired, OR the "forget all" epoch has
211 * passed. Delete it, and pretend it doesn't exist.
213 if ((c->expires < request->timestamp) ||
214 (c->created < inst->epoch)) {
216 RDEBUG("Entry has expired, removing");
218 fr_heap_extract(inst->heap, c);
219 rbtree_deletebydata(inst->cache, c);
224 RDEBUG("Found entry for \"%s\"", key);
227 * Update the expiry time based on the TTL.
228 * A TTL of 0 means "delete from the cache".
230 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
232 if (vp->vp_integer == 0) goto delete;
234 ttl = vp->vp_integer;
235 c->expires = request->timestamp + ttl;
236 RDEBUG("Adding %d to the TTL", ttl);
245 * Add an entry to the cache.
247 static rlm_cache_entry_t *cache_add(rlm_cache_t *inst, REQUEST *request,
251 VALUE_PAIR *vp, *found, **to_req, **to_cache, **from;
257 const value_pair_map_t *map;
259 rlm_cache_entry_t *c;
262 if (rbtree_num_elements(inst->cache) >= inst->max_entries) {
263 RDEBUG("Cache is full: %d entries", inst->max_entries);
268 * TTL of 0 means "don't cache this entry"
270 vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
271 if (vp && (vp->vp_integer == 0)) return NULL;
273 c = talloc_zero(NULL, rlm_cache_entry_t);
274 c->key = talloc_strdup(c, key);
275 c->created = c->expires = request->timestamp;
278 * Use per-entry TTL, or globally defined one.
281 ttl = vp->vp_integer;
287 RDEBUG("Creating entry for \"%s\"", key);
290 * Check to see if we need to merge the entry into the request
292 vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
293 if (vp && (vp->vp_integer == 0)) {
295 RDEBUG2("Told not to merge new entry into request");
298 for (map = inst->maps; map != NULL; map = map->next) {
299 rad_assert(map->dst && map->src);
302 * Specifying inner/outer request doesn't work here
303 * but there's no easy fix...
305 switch (map->dst->list) {
306 case PAIR_LIST_REQUEST:
307 to_cache = &c->request;
310 case PAIR_LIST_REPLY:
311 to_cache = &c->reply;
314 case PAIR_LIST_CONTROL:
315 to_cache = &c->control;
324 * Resolve the destination in the current request.
325 * We need to add the to_cache there too if any of these
328 * - Map specifies an xlat'd string.
329 * - Map specifies a literal string.
330 * - Map src and dst lists differ.
331 * - Map src and dst attributes differ
334 if (merge && ( !map->src->da ||
335 (map->src->list != map->dst->list) ||
336 (map->src->da != map->dst->da))) {
339 * It's ok if the list isn't valid here...
340 * It might be valid later when we merge
343 if (radius_request(&context, map->dst->request) == 0) {
344 to_req = radius_list(context, map->dst->list);
349 * We infer that src was an attribute ref from the fact
352 RDEBUG4(":: dst is \"%s\" src is \"%s\"",
353 fr_int2str(vpt_types, map->dst->type, "¿unknown?"),
354 fr_int2str(vpt_types, map->src->type, "¿unknown?"));
356 switch (map->src->type)
362 if (radius_request(&context, map->src->request) == 0) {
363 from = radius_list(context, map->src->list);
367 * Can't add the attribute if the list isn't
372 found = pairfind(*from, da->attr, da->vendor, TAG_ANY);
374 RDEBUGW("\"%s\" not found, skipping",
379 RDEBUG("\t%s %s %s", map->dst->name,
380 fr_int2str(fr_tokens, map->op, "¿unknown?"),
387 vp = map->dst->type == VPT_TYPE_LIST ?
389 paircopyvpdata(map->dst->da, found);
393 pairadd(to_cache, vp);
397 radius_pairmove(request, to_req, vp);
403 vp = map->dst->type == VPT_TYPE_LIST ?
405 paircopyvpdata(map->dst->da,
410 pairadd(to_cache, vp);
414 radius_pairmove(request, to_req,
419 found = pairfind(found->next,
433 rad_assert(map->src->type == VPT_TYPE_LIST);
437 if (radius_request(&context, map->src->request) == 0) {
438 from = radius_list(context, map->src->list);
442 found = paircopy(*from);
443 if (!found) continue;
445 for (vp = found; vp != NULL; vp = vp->next) {
446 RDEBUG("\t%s %s %s (%s)", map->dst->name,
447 fr_int2str(fr_tokens, map->op,
454 pairadd(to_cache, found);
457 vp = paircopy(found);
458 radius_pairmove(request, to_req, vp);
463 * It was most likely a double quoted string that now
464 * needs to be expanded.
467 if (radius_xlat(buffer, sizeof(buffer), map->src->name,
468 request, NULL, NULL) <= 0) {
472 RDEBUG("\t%s %s \"%s\"", map->dst->name,
473 fr_int2str(fr_tokens, map->op, "¿unknown?"),
476 vp = pairalloc(NULL, map->dst->da);
480 if (!pairparsevalue(vp, buffer)) {
485 pairadd(to_cache, vp);
489 radius_pairmove(request, to_req, vp);
496 case VPT_TYPE_LITERAL:
497 RDEBUG("\t%s %s '%s'", map->dst->name,
498 fr_int2str(fr_tokens, map->op, "¿unknown?"),
501 vp = pairalloc(NULL, map->dst->da);
505 if (!pairparsevalue(vp, map->src->name)) {
510 pairadd(to_cache, vp);
514 radius_pairmove(request, to_req, vp);
525 if (!rbtree_insert(inst->cache, c)) {
526 radlog(L_ERR, "rlm_cache: FAILED adding entry for key %s", key);
531 if (!fr_heap_insert(inst->heap, c)) {
532 radlog(L_ERR, "rlm_cache: FAILED adding entry for key %s", key);
533 rbtree_deletebydata(inst->cache, c);
537 RDEBUG("Inserted entry, TTL %d seconds", ttl);
543 * Verify that the cache section makes sense.
545 static int cache_verify(rlm_cache_t *inst, value_pair_map_t **head)
547 value_pair_map_t *map;
549 if (radius_attrmap(inst->cs, head, PAIR_LIST_REQUEST,
550 PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) {
555 cf_log_err(cf_sectiontoitem(inst->cs),
556 "Cache config must contain an update section, and "
557 "that section must not be empty");
562 for (map = *head; map != NULL; map = map->next) {
563 if ((map->dst->type != VPT_TYPE_ATTR) &&
564 (map->dst->type != VPT_TYPE_LIST)) {
565 cf_log_err(map->ci, "Left operand must be an attribute "
571 switch (map->src->type)
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 size_t cache_xlat(void *instance, REQUEST *request,
607 const char *fmt, char *out, size_t freespace)
609 rlm_cache_entry_t *c;
610 rlm_cache_t *inst = instance;
611 VALUE_PAIR *vp, *vps;
613 const DICT_ATTR *target;
618 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
620 list = radius_list_name(&p, PAIR_LIST_REQUEST);
622 target = dict_attrbyname(p);
624 radlog(L_ERR, "rlm_cache: Unknown attribute \"%s\"", p);
628 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
629 c = cache_find(inst, request, buffer);
632 RDEBUG("No cache entry for key \"%s\"", buffer);
637 case PAIR_LIST_REQUEST:
641 case PAIR_LIST_REPLY:
645 case PAIR_LIST_CONTROL:
649 case PAIR_LIST_UNKNOWN:
650 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
651 radlog(L_ERR, "rlm_cache: Unknown list qualifier in \"%s\"", fmt);
655 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
656 radlog(L_ERR, "rlm_cache: Unsupported list \"%s\"",
657 fr_int2str(pair_lists, list, "¿Unknown?"));
661 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
663 RDEBUG("No instance of this attribute has been cached");
667 ret = vp_prints_value(out, freespace, vp, 0);
669 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
675 * A mapping of configuration file names to internal variables.
677 * Note that the string is dynamically allocated, so it MUST
678 * be freed. When the configuration file parse re-reads the string,
679 * it free's the old one, and strdup's the new one, placing the pointer
680 * to the strdup'd string into 'config.string'. This gets around
683 static const CONF_PARSER module_config[] = {
684 { "key", PW_TYPE_STRING_PTR,
685 offsetof(rlm_cache_t, key), NULL, NULL},
686 { "ttl", PW_TYPE_INTEGER,
687 offsetof(rlm_cache_t, ttl), NULL, "500" },
688 { "max_entries", PW_TYPE_INTEGER,
689 offsetof(rlm_cache_t, max_entries), NULL, "16384" },
690 { "epoch", PW_TYPE_INTEGER,
691 offsetof(rlm_cache_t, epoch), NULL, "0" },
692 { "add_stats", PW_TYPE_BOOLEAN,
693 offsetof(rlm_cache_t, stats), NULL, "no" },
695 { NULL, -1, 0, NULL, NULL } /* end the list */
700 * Only free memory we allocated. The strings allocated via
701 * cf_section_parse() do not need to be freed.
703 static int cache_detach(void *instance)
705 rlm_cache_t *inst = instance;
707 radius_mapfree(&inst->maps);
709 fr_heap_delete(inst->heap);
710 rbtree_free(inst->cache);
712 #ifdef HAVE_PTHREAD_H
713 pthread_mutex_destroy(&inst->cache_mutex);
720 * Instantiate the module.
722 static int cache_instantiate(CONF_SECTION *conf, void **instance)
726 *instance = inst = talloc_zero(conf, rlm_cache_t);
730 * If the configuration parameters can't be parsed, then
733 if (cf_section_parse(conf, inst, module_config) < 0) {
737 inst->xlat_name = cf_section_name2(conf);
738 if (!inst->xlat_name) {
739 inst->xlat_name = cf_section_name1(conf);
743 * Register the cache xlat function
745 xlat_register(inst->xlat_name, cache_xlat, inst);
747 if (!inst->key || !*inst->key) {
748 radlog(L_ERR, "rlm_cache: You must specify a key");
752 if (inst->ttl == 0) {
753 radlog(L_ERR, "rlm_cache: TTL must be greater than zero");
757 if (inst->epoch != 0){
758 radlog(L_ERR, "rlm_cache: Epoch should only be set dynamically");
762 #ifdef HAVE_PTHREAD_H
763 if (pthread_mutex_init(&inst->cache_mutex, NULL) < 0) {
764 radlog(L_ERR, "rlm_cache: Failed initializing mutex: %s",
773 inst->cache = rbtree_create(cache_entry_cmp, cache_entry_free, 0);
775 radlog(L_ERR, "rlm_cache: Failed to create cache");
780 * The heap of entries to expire.
782 inst->heap = fr_heap_create(cache_heap_cmp,
783 offsetof(rlm_cache_entry_t, offset));
785 radlog(L_ERR, "rlm_cache: Failed to create cache");
790 * Make sure the users don't screw up too badly.
792 if (cache_verify(inst, &inst->maps) < 0) {
800 * Do caching checks. Since we can update ANY VP list, we do
801 * exactly the same thing for all sections (autz / auth / etc.)
803 * If you want to cache something different in different sections,
804 * configure another cache module.
806 static rlm_rcode_t cache_it(void *instance, REQUEST *request)
808 rlm_cache_entry_t *c;
809 rlm_cache_t *inst = instance;
814 radius_xlat(buffer, sizeof(buffer), inst->key, request, NULL, NULL);
816 PTHREAD_MUTEX_LOCK(&inst->cache_mutex);
817 c = cache_find(inst, request, buffer);
820 * If yes, only return whether we found a valid cache entry
822 vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
823 if (vp && vp->vp_integer) {
824 rcode = c ? RLM_MODULE_OK:
830 cache_merge(inst, request, c);
832 rcode = RLM_MODULE_OK;
836 c = cache_add(inst, request, buffer);
838 rcode = RLM_MODULE_NOOP;
842 rcode = RLM_MODULE_UPDATED;
845 PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
851 * The module name should be the only globally exported symbol.
852 * That is, everything else should be 'static'.
854 * If the module needs to temporarily modify it's instantiation
855 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
856 * The server will then take care of ensuring that the module
857 * is single-threaded.
859 module_t rlm_cache = {
863 cache_instantiate, /* instantiation */
864 cache_detach, /* detach */
866 NULL, /* authentication */
867 cache_it, /* authorization */
868 cache_it, /* preaccounting */
869 cache_it, /* accounting */
870 NULL, /* checksimul */
871 cache_it, /* pre-proxy */
872 cache_it, /* post-proxy */
873 cache_it, /* post-auth */