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 as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Cache values and merge them back into future requests.
22 * @copyright 2012-2014 The FreeRADIUS server project
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/modpriv.h>
29 #include <freeradius-devel/modcall.h>
30 #include <freeradius-devel/rad_assert.h>
32 #include "rlm_cache.h"
35 * A mapping of configuration file names to internal variables.
37 * Note that the string is dynamically allocated, so it MUST
38 * be freed. When the configuration file parse re-reads the string,
39 * it free's the old one, and strdup's the new one, placing the pointer
40 * to the strdup'd string into 'config.string'. This gets around
43 static const CONF_PARSER module_config[] = {
44 { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_cache_t, driver_name), "rlm_cache_rbtree" },
45 { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_cache_t, key), NULL },
46 { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
47 { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "0" },
49 /* Should be a type which matches time_t, @fixme before 2038 */
50 { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
51 { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
53 { NULL, -1, 0, NULL, NULL } /* end the list */
56 static int cache_acquire(rlm_cache_handle_t **out, rlm_cache_t *inst, REQUEST *request)
58 if (!inst->module->acquire) return 0;
60 return inst->module->acquire(out, inst, request);
63 static void cache_release(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle)
65 if (!inst->module->release) return;
66 if (!handle || !*handle) return;
68 inst->module->release(inst, request, handle);
71 static int cache_reconnect(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle)
73 rad_assert(inst->module->reconnect);
75 return inst->module->reconnect(inst, request, handle);
78 /** Allocate a cache entry
80 * This is used so that drivers may use their own allocation functions
81 * to allocate structures larger than the normal rlm_cache_entry_t.
83 * If the driver doesn't specify a custom allocation function, the cache
84 * entry is talloced in the NULL ctx.
86 static rlm_cache_entry_t *cache_alloc(rlm_cache_t *inst, REQUEST *request)
88 if (inst->module->alloc) return inst->module->alloc(inst, request);
90 return talloc_zero(NULL, rlm_cache_entry_t);
93 /** Free memory associated with a cache entry
95 * This does not necessarily remove the entry from the cache, cache_expire
96 * should be used for that.
98 * This function should be called when an entry that is known to have been
99 * retrieved or inserted into a data store successfully, is no longer needed.
101 * Some drivers (like rlm_cache_rbtree) don't register a free function.
102 * This means that the cache entry never needs to be explicitly freed.
104 * @param c Cache entry to free.
105 * @param inst Module instance.
107 static void cache_free(rlm_cache_t *inst, rlm_cache_entry_t **c)
109 if (!c || !*c || !inst->module->free) return;
111 inst->module->free(*c);
116 * Merge a cached entry into a REQUEST.
118 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
122 vp = pairfind(request->config, PW_CACHE_MERGE, 0, TAG_ANY);
123 if (vp && (vp->vp_integer == 0)) {
124 RDEBUG2("Told not to merge entry into request");
128 RDEBUG2("Merging cache entry into request");
130 if (c->packet && request->packet) {
131 rdebug_pair_list(L_DBG_LVL_2, request, c->packet, "&request:");
132 radius_pairmove(request, &request->packet->vps, paircopy(request->packet, c->packet), false);
135 if (c->reply && request->reply) {
136 rdebug_pair_list(L_DBG_LVL_2, request, c->reply, "&reply:");
137 radius_pairmove(request, &request->reply->vps, paircopy(request->reply, c->reply), false);
141 rdebug_pair_list(L_DBG_LVL_2, request, c->control, "&control:");
142 radius_pairmove(request, &request->config, paircopy(request, c->control), false);
146 rdebug_pair_list(L_DBG_LVL_2, request, c->state, "&session-state:");
147 radius_pairmove(request, &request->state, paircopy(request->state, c->state), false);
151 rad_assert(request->packet != NULL);
152 vp = pairfind(request->packet->vps, PW_CACHE_ENTRY_HITS, 0, TAG_ANY);
154 vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
155 rad_assert(vp != NULL);
156 pairadd(&request->packet->vps, vp);
158 vp->vp_integer = c->hits;
163 /** Find a cached entry.
165 * @return RLM_MODULE_OK on success, RLM_MODULE_FAIL on failure, RLM_MODULE_NOTFOUND if notfound.
167 static rlm_rcode_t cache_find(rlm_cache_entry_t **out, rlm_cache_t *inst, REQUEST *request,
168 rlm_cache_handle_t **handle, char const *key)
172 rlm_cache_entry_t *c;
177 ret = inst->module->find(&c, inst, request, handle, key);
179 case CACHE_RECONNECT:
180 RDEBUG("Reconnecting...");
181 if (cache_reconnect(inst, request, handle) == 0) continue;
182 return RLM_MODULE_FAIL;
188 RDEBUG("No cache entry found for \"%s\"", key);
189 return RLM_MODULE_NOTFOUND;
193 return RLM_MODULE_FAIL;
201 * Yes, but it expired, OR the "forget all" epoch has
202 * passed. Delete it, and pretend it doesn't exist.
204 if ((c->expires < request->timestamp) || (c->created < inst->epoch)) {
205 RDEBUG("Removing expired entry");
207 inst->module->expire(inst, request, handle, c);
208 cache_free(inst, &c);
209 return RLM_MODULE_NOTFOUND; /* Couldn't find a non-expired entry */
212 RDEBUG("Found entry for \"%s\"", key);
217 return RLM_MODULE_OK;
220 /** Expire a cache entry (removing it from the datastore)
223 static void cache_expire(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle, rlm_cache_entry_t **c)
227 for (;;) switch (inst->module->expire(inst, request, handle, *c)) {
228 case CACHE_RECONNECT:
229 if (cache_reconnect(inst, request, handle) == 0) continue;
239 /** Create and insert a cache entry.
241 * @return RLM_MODULE_OK on success, RLM_MODULE_UPDATED if we merged the cache entry and RLM_MODULE_FAIL on failure.
243 static rlm_rcode_t cache_insert(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle,
244 char const *key, int ttl)
246 VALUE_PAIR *vp, *to_cache;
247 vp_cursor_t src_list, packet, reply, control, state;
252 rlm_cache_entry_t *c;
254 if ((inst->max_entries > 0) && inst->module->count &&
255 (inst->module->count(inst, request, handle) > inst->max_entries)) {
256 RWDEBUG("Cache is full: %d entries", inst->max_entries);
257 return RLM_MODULE_FAIL;
260 c = cache_alloc(inst, request);
261 if (!c) return RLM_MODULE_FAIL;
263 c->key = talloc_typed_strdup(c, key);
264 c->created = c->expires = request->timestamp;
267 RDEBUG("Creating new cache entry");
269 fr_cursor_init(&packet, &c->packet);
270 fr_cursor_init(&reply, &c->reply);
271 fr_cursor_init(&control, &c->control);
272 fr_cursor_init(&state, &c->state);
274 for (map = inst->maps; map != NULL; map = map->next) {
275 rad_assert(map->lhs && map->rhs);
277 if (map_to_vp(&to_cache, request, map, NULL) < 0) {
278 RDEBUG("Skipping %s", map->rhs->name);
283 * Reparent the VPs map_to_vp may return multiple.
285 for (vp = fr_cursor_init(&src_list, &to_cache);
287 vp = fr_cursor_next(&src_list)) {
291 * Prevent people from accidentally caching
292 * cache control attributes.
294 if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
296 case PW_CACHE_STATUS_ONLY:
297 case PW_CACHE_READ_ONLY:
299 case PW_CACHE_ENTRY_HITS:
300 RDEBUG2("Skipping %s", vp->da->name);
308 if (RDEBUG_ENABLED2) map_debug_log(request, map, vp);
310 (void) talloc_steal(c, vp);
314 switch (map->lhs->tmpl_list) {
315 case PAIR_LIST_REQUEST:
316 fr_cursor_insert(&packet, vp);
319 case PAIR_LIST_REPLY:
320 fr_cursor_insert(&reply, vp);
323 case PAIR_LIST_CONTROL:
324 fr_cursor_insert(&control, vp);
327 case PAIR_LIST_STATE:
328 fr_cursor_insert(&state, vp);
332 rad_assert(0); /* should have been caught by validation */
338 * Check to see if we need to merge the entry into the request
340 vp = pairfind(request->config, PW_CACHE_MERGE, 0, TAG_ANY);
341 if (vp && (vp->vp_integer == 0)) merge = false;
343 if (merge) cache_merge(inst, request, c);
348 ret = inst->module->insert(inst, request, handle, c);
350 case CACHE_RECONNECT:
351 if (cache_reconnect(inst, request, handle) == 0) continue;
352 return RLM_MODULE_FAIL;
355 RDEBUG("Commited entry, TTL %d seconds", ttl);
356 cache_free(inst, &c);
357 return RLM_MODULE_UPDATED;
360 talloc_free(c); /* Failed insertion - use talloc_free not the driver free */
361 return RLM_MODULE_FAIL;
366 /** Verify that a map in the cache section makes sense
369 static int cache_verify(vp_map_t *map, void *ctx)
371 if (modcall_fixup_update(map, ctx) < 0) return -1;
373 if ((map->lhs->type != TMPL_TYPE_ATTR) &&
374 (map->lhs->type != TMPL_TYPE_LIST)) {
375 cf_log_err(map->ci, "Destination must be an attribute ref or a list");
379 switch (map->lhs->tmpl_list) {
380 case PAIR_LIST_REQUEST:
381 case PAIR_LIST_REPLY:
382 case PAIR_LIST_CONTROL:
383 case PAIR_LIST_STATE:
387 cf_log_err(map->ci, "Destination list must be one of request, reply, control or session-state");
391 if (map->lhs->tmpl_request != REQUEST_CURRENT) {
392 cf_log_err(map->ci, "Cached attributes can only be inserted into the current request");
396 switch (map->rhs->type) {
398 cf_log_err(map->ci, "Exec values are not allowed");
401 * Only =, :=, += and -= operators are supported for
404 case TMPL_TYPE_LITERAL:
415 cf_log_err(map->ci, "Operator \"%s\" not allowed for %s values",
416 fr_int2str(fr_tokens, map->op, "<INVALID>"),
417 fr_int2str(tmpl_names, map->rhs->type, "<INVALID>"));
428 * Do caching checks. Since we can update ANY VP list, we do
429 * exactly the same thing for all sections (autz / auth / etc.)
431 * If you want to cache something different in different sections,
432 * configure another cache module.
434 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
436 rlm_cache_entry_t *c;
437 rlm_cache_t *inst = instance;
439 rlm_cache_handle_t *handle;
448 if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) return RLM_MODULE_FAIL;
450 if (buffer[0] == '\0') {
451 REDEBUG("Zero length key string is invalid");
452 return RLM_MODULE_INVALID;
455 if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL;
457 rcode = cache_find(&c, inst, request, &handle, buffer);
458 if (rcode == RLM_MODULE_FAIL) goto finish;
462 * If Cache-Status-Only == yes, only return whether we found a
465 vp = pairfind(request->config, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
466 if (vp && vp->vp_integer) {
467 rcode = c ? RLM_MODULE_OK:
473 * Update the expiry time based on the TTL.
474 * A TTL of 0 means "delete from the cache".
475 * A TTL < 0 means "delete from the cache and recreate the entry".
477 vp = pairfind(request->config, PW_CACHE_TTL, 0, TAG_ANY);
478 if (vp) ttl = vp->vp_signed;
481 * If there's no existing cache entry, go and create a new one.
484 if (ttl <= 0) ttl = inst->ttl;
489 * Expire the entry if requested to do so
493 cache_expire(inst, request, &handle, &c);
494 RDEBUG("Forcing expiry of entry");
495 rcode = RLM_MODULE_OK;
500 RDEBUG("Forcing expiry of existing entry");
501 cache_expire(inst, request, &handle, &c);
505 c->expires = request->timestamp + ttl;
506 RDEBUG("Setting TTL to %d", ttl);
510 * Cache entry was still valid, so we merge it into the request
511 * and return. No need to add a new entry.
513 cache_merge(inst, request, c);
514 rcode = RLM_MODULE_OK;
520 * If Cache-Read-Only == yes, then we only allow already cached entries
521 * to be merged into the request
523 vp = pairfind(request->config, PW_CACHE_READ_ONLY, 0, TAG_ANY);
524 if (vp && vp->vp_integer) {
525 rcode = RLM_MODULE_NOTFOUND;
530 * Create a new entry.
532 rcode = cache_insert(inst, request, &handle, buffer, ttl);
536 cache_free(inst, &c);
537 cache_release(inst, request, &handle);
540 * Clear control attributes
542 for (vp = fr_cursor_init(&cursor, &request->config);
544 vp = fr_cursor_next(&cursor)) {
545 if (vp->da->vendor == 0) switch (vp->da->attr) {
547 case PW_CACHE_STATUS_ONLY:
548 case PW_CACHE_READ_ONLY:
550 vp = fr_cursor_remove(&cursor);
559 static ssize_t CC_HINT(nonnull) cache_xlat(void *instance, REQUEST *request,
560 char const *fmt, char *out, size_t freespace);
563 * Allow single attribute values to be retrieved from the cache.
565 static ssize_t cache_xlat(void *instance, REQUEST *request,
566 char const *fmt, char *out, size_t freespace)
568 rlm_cache_entry_t *c = NULL;
569 rlm_cache_t *inst = instance;
570 rlm_cache_handle_t *handle = NULL;
572 VALUE_PAIR *vp, *vps;
574 DICT_ATTR const *target;
579 p += radius_list_name(&list, p, PAIR_LIST_REQUEST);
580 if (list == PAIR_LIST_UNKNOWN) {
581 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
586 target = dict_attrbyname(p);
588 REDEBUG("Unknown attribute \"%s\"", p);
592 if (cache_acquire(&handle, inst, request) < 0) return -1;
594 switch (cache_find(&c, inst, request, handle, fmt)) {
595 case RLM_MODULE_OK: /* found */
598 case RLM_MODULE_NOTFOUND: /* not found */
607 case PAIR_LIST_REQUEST:
611 case PAIR_LIST_REPLY:
615 case PAIR_LIST_CONTROL:
619 case PAIR_LIST_STATE:
624 REDEBUG("Unsupported list \"%s\"", fr_int2str(pair_lists, list, "<UNKNOWN>"));
629 vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
631 RDEBUG("No instance of this attribute has been cached");
636 len = vp_prints_value(out, freespace, vp, 0);
637 if (is_truncated(len, freespace)) {
638 REDEBUG("Insufficient buffer space to write cached value");
644 cache_free(inst, &c);
645 cache_release(inst, request, &handle);
651 * Only free memory we allocated. The strings allocated via
652 * cf_section_parse() do not need to be freed.
654 static int mod_detach(void *instance)
656 rlm_cache_t *inst = instance;
658 talloc_free(inst->maps);
661 * We need to explicitly free all children, so if the driver
662 * parented any memory off the instance, their destructors
663 * run before we unload the bytecode for them.
665 * If we don't do this, we get a SEGV deep inside the talloc code
666 * when it tries to call a destructor that no longer exists.
668 talloc_free_children(inst);
671 * Decrements the reference count. The driver object won't be unloaded
672 * until all instances of rlm_cache that use it have been destroyed.
674 if (inst->handle) dlclose(inst->handle);
680 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
682 rlm_cache_t *inst = instance;
686 inst->xlat_name = cf_section_name2(conf);
687 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
690 * Register the cache xlat function
692 xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
699 * Instantiate the module.
701 static int mod_instantiate(CONF_SECTION *conf, void *instance)
703 rlm_cache_t *inst = instance;
704 CONF_SECTION *update;
709 * Sanity check for crazy people.
711 if (strncmp(inst->driver_name, "rlm_cache_", 8) != 0) {
712 cf_log_err_cs(conf, "\"%s\" is NOT an Cache driver!", inst->driver_name);
717 * Load the appropriate driver for our database
719 inst->handle = lt_dlopenext(inst->driver_name);
721 cf_log_err_cs(conf, "Could not link driver %s: %s", inst->driver_name, dlerror());
722 cf_log_err_cs(conf, "Make sure it (and all its dependent libraries!) are in the search path"
723 "of your system's ld");
727 inst->module = (cache_module_t *) dlsym(inst->handle, inst->driver_name);
729 cf_log_err_cs(conf, "Could not link symbol %s: %s", inst->driver_name, dlerror());
733 INFO("rlm_cache (%s): Driver %s (module %s) loaded and linked", inst->xlat_name,
734 inst->driver_name, inst->module->name);
737 * Non optional fields and callbacks
739 rad_assert(inst->module->name);
740 rad_assert(inst->module->find);
741 rad_assert(inst->module->insert);
742 rad_assert(inst->module->expire);
744 if (inst->module->mod_instantiate) {
748 name = strrchr(inst->driver_name, '_');
750 name = inst->driver_name;
755 cs = cf_section_sub_find(conf, name);
757 cs = cf_section_alloc(conf, name, NULL);
762 * It's up to the driver to register a destructor (using talloc)
764 * Should write its instance data in inst->driver,
765 * and parent it off of inst.
767 if (inst->module->mod_instantiate(cs, inst) < 0) return -1;
770 rad_assert(inst->key && *inst->key);
772 if (inst->ttl == 0) {
773 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
777 if (inst->epoch != 0) {
778 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
782 update = cf_section_sub_find(inst->cs, "update");
784 cf_log_err_cs(conf, "Must have an 'update' section in order to cache anything.");
789 * Make sure the users don't screw up too badly.
791 if (map_afrom_cs(&inst->maps, update,
792 PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, cache_verify, NULL, MAX_ATTRMAP) < 0) {
797 cf_log_err_cs(inst->cs, "Cache config must contain an update section, and "
798 "that section must not be empty");
806 * The module name should be the only globally exported symbol.
807 * That is, everything else should be 'static'.
809 * If the module needs to temporarily modify it's instantiation
810 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
811 * The server will then take care of ensuring that the module
812 * is single-threaded.
814 extern module_t rlm_cache;
815 module_t rlm_cache = {
816 .magic = RLM_MODULE_INIT,
818 .inst_size = sizeof(rlm_cache_t),
819 .config = module_config,
820 .bootstrap = mod_bootstrap,
821 .instantiate = mod_instantiate,
822 .detach = mod_detach,
824 [MOD_AUTHORIZE] = mod_cache_it,
825 [MOD_PREACCT] = mod_cache_it,
826 [MOD_ACCOUNTING] = mod_cache_it,
827 [MOD_PRE_PROXY] = mod_cache_it,
828 [MOD_POST_PROXY] = mod_cache_it,
829 [MOD_POST_AUTH] = mod_cache_it