Sync rlm_cache.c with master
[freeradius.git] / src / modules / rlm_cache / rlm_cache.c
1 /*
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.
5  *
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.
10  *
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
14  */
15
16 /**
17  * $Id$
18  * @file rlm_cache.c
19  * @brief Cache values and merge them back into future requests.
20  *
21  * @copyright 2012-2014 The FreeRADIUS server project
22  */
23 RCSID("$Id$")
24
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/modules.h>
27 #include <freeradius-devel/modpriv.h>
28 #include <freeradius-devel/modcall.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #include "rlm_cache.h"
32
33 /*
34  *      A mapping of configuration file names to internal variables.
35  *
36  *      Note that the string is dynamically allocated, so it MUST
37  *      be freed.  When the configuration file parse re-reads the string,
38  *      it free's the old one, and strdup's the new one, placing the pointer
39  *      to the strdup'd string into 'config.string'.  This gets around
40  *      buffer over-flows.
41  */
42 static const CONF_PARSER module_config[] = {
43         { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_cache_t, driver_name), "rlm_cache_rbtree" },
44         { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_cache_t, key), NULL },
45         { "ttl", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, ttl), "500" },
46         { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_cache_t, max_entries), "0" },
47
48         /* Should be a type which matches time_t, @fixme before 2038 */
49         { "epoch", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_cache_t, epoch), "0" },
50         { "add_stats", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_cache_t, stats), "no" },
51
52         { NULL, -1, 0, NULL, NULL }             /* end the list */
53 };
54
55 static int cache_acquire(rlm_cache_handle_t **out, rlm_cache_t *inst, REQUEST *request)
56 {
57         if (!inst->module->acquire) return 0;
58
59         return inst->module->acquire(out, inst, request);
60 }
61
62 static void cache_release(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle)
63 {
64         if (!inst->module->release) return;
65         if (!*handle) return;
66
67         inst->module->release(inst, request, handle);
68 }
69
70 static int cache_reconnect(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle)
71 {
72         rad_assert(inst->module->reconnect);
73
74         return inst->module->reconnect(inst, request, handle);
75 }
76
77 /** Allocate a cache entry
78  *
79  *  This is used so that drivers may use their own allocation functions
80  *  to allocate structures larger than the normal rlm_cache_entry_t.
81  *
82  *  If the driver doesn't specify a custom allocation function, the cache
83  *  entry is talloced in the NULL ctx.
84  */
85 static rlm_cache_entry_t *cache_alloc(rlm_cache_t *inst, REQUEST *request)
86 {
87         if (inst->module->alloc) return inst->module->alloc(inst, request);
88
89         return talloc_zero(NULL, rlm_cache_entry_t);
90 }
91
92 /** Free memory associated with a cache entry
93  *
94  * This does not necessarily remove the entry from the cache, cache_expire
95  * should be used for that.
96  *
97  * This function should be called when an entry that is known to have been
98  * retrieved or inserted into a data store successfully, is no longer needed.
99  *
100  * Some drivers (like rlm_cache_rbtree) don't register a free function.
101  * This means that the cache entry never needs to be explicitly freed.
102  *
103  * @param c Cache entry to free.
104  * @param inst Module instance.
105  */
106 static void cache_free(rlm_cache_t *inst, rlm_cache_entry_t **c)
107 {
108         if (!*c || !inst->module->free) return;
109
110         inst->module->free(*c);
111         *c = NULL;
112 }
113
114 /*
115  *      Merge a cached entry into a REQUEST.
116  */
117 static void CC_HINT(nonnull) cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c)
118 {
119         VALUE_PAIR *vp;
120
121         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
122         if (vp && (vp->vp_integer == 0)) {
123                 RDEBUG2("Told not to merge entry into request");
124                 return;
125         }
126
127         RDEBUG2("Merging cache entry into request");
128
129         if (c->control) {
130                 rdebug_pair_list(L_DBG_LVL_2, request, c->control, "&control:");
131                 radius_pairmove(request, &request->config_items, paircopy(request, c->control), false);
132         }
133
134         if (c->packet && request->packet) {
135                 rdebug_pair_list(L_DBG_LVL_2, request, c->packet, "&request:");
136                 radius_pairmove(request, &request->packet->vps, paircopy(request->packet, c->packet), false);
137         }
138
139         if (c->reply && request->reply) {
140                 rdebug_pair_list(L_DBG_LVL_2, request, c->reply, "&reply:");
141                 radius_pairmove(request, &request->reply->vps, paircopy(request->reply, c->reply), false);
142         }
143
144         if (inst->stats) {
145                 rad_assert(request->packet != NULL);
146                 vp = pairfind(request->packet->vps, PW_CACHE_ENTRY_HITS, 0, TAG_ANY);
147                 if (!vp) {
148                         vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0);
149                         rad_assert(vp != NULL);
150                         pairadd(&request->packet->vps, vp);
151                 }
152                 vp->vp_integer = c->hits;
153         }
154 }
155
156
157 /** Find a cached entry.
158  *
159  * @return RLM_MODULE_OK on success, RLM_MODULE_FAIL on failure, RLM_MODULE_NOTFOUND if notfound.
160  */
161 static rlm_rcode_t cache_find(rlm_cache_entry_t **out, rlm_cache_t *inst, REQUEST *request,
162                               rlm_cache_handle_t **handle, char const *key)
163 {
164         cache_status_t ret;
165
166         rlm_cache_entry_t *c;
167
168         *out = NULL;
169
170         for (;;) {
171                 ret = inst->module->find(&c, inst, request, handle, key);
172                 switch (ret) {
173                 case CACHE_RECONNECT:
174                         RDEBUG("Reconnecting...");
175                         if (cache_reconnect(inst, request, handle) == 0) continue;
176                         return RLM_MODULE_FAIL;
177
178                 case CACHE_OK:
179                         break;
180
181                 case CACHE_MISS:
182                         RDEBUG("No cache entry found for \"%s\"", key);
183                         return RLM_MODULE_NOTFOUND;
184
185                 /* FALL-THROUGH */
186                 default:
187                         return RLM_MODULE_FAIL;
188
189                 }
190
191                 break;
192         }
193
194         /*
195          *      Yes, but it expired, OR the "forget all" epoch has
196          *      passed.  Delete it, and pretend it doesn't exist.
197          */
198         if ((c->expires < request->timestamp) || (c->created < inst->epoch)) {
199                 RDEBUG("Removing expired entry");
200
201                 inst->module->expire(inst, request, handle, c);
202                 cache_free(inst, &c);
203                 return RLM_MODULE_NOTFOUND;     /* Couldn't find a non-expired entry */
204         }
205
206         RDEBUG("Found entry for \"%s\"", key);
207
208         c->hits++;
209         *out = c;
210
211         return RLM_MODULE_OK;
212 }
213
214 /** Expire a cache entry (removing it from the datastore)
215  *
216  */
217 static void cache_expire(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle, rlm_cache_entry_t **c)
218 {
219         rad_assert(*c);
220
221         for (;;) switch (inst->module->expire(inst, request, handle, *c)) {
222         case CACHE_RECONNECT:
223                 if (cache_reconnect(inst, request, handle) == 0) continue;
224
225         /* FALL-THROUGH */
226         default:
227                 cache_free(inst, c);
228                 *c = NULL;
229                 return;
230         }
231 }
232
233 /** Create and insert a cache entry.
234  *
235  * @return RLM_MODULE_OK on success, RLM_MODULE_UPDATED if we merged the cache entry and RLM_MODULE_FAIL on failure.
236  */
237 static rlm_rcode_t cache_insert(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle,
238                                 char const *key, int ttl)
239 {
240         VALUE_PAIR *vp, *to_cache;
241         vp_cursor_t src_list, cached_request, cached_reply, cached_control;
242
243         value_pair_map_t const *map;
244
245         bool merge = false;
246         rlm_cache_entry_t *c;
247
248         if ((inst->max_entries > 0) && inst->module->count &&
249             (inst->module->count(inst, request, handle) > inst->max_entries)) {
250                 RWDEBUG("Cache is full: %d entries", inst->max_entries);
251                 return RLM_MODULE_FAIL;
252         }
253
254         c = cache_alloc(inst, request);
255         if (!c) return RLM_MODULE_FAIL;
256
257         c->key = talloc_typed_strdup(c, key);
258         c->created = c->expires = request->timestamp;
259         c->expires += ttl;
260
261         RDEBUG("Creating new cache entry");
262
263         fr_cursor_init(&cached_request, &c->packet);
264         fr_cursor_init(&cached_reply, &c->reply);
265         fr_cursor_init(&cached_control, &c->control);
266
267         for (map = inst->maps; map != NULL; map = map->next) {
268                 rad_assert(map->lhs && map->rhs);
269
270                 if (map_to_vp(&to_cache, request, map, NULL) < 0) {
271                         RDEBUG("Skipping %s", map->rhs->name);
272                         continue;
273                 }
274
275                 /*
276                  *      Reparent the VPs map_to_vp may return multiple.
277                  */
278                 for (vp = fr_cursor_init(&src_list, &to_cache);
279                      vp;
280                      vp = fr_cursor_next(&src_list)) {
281                         VERIFY_VP(vp);
282
283                         /*
284                          *      Prevent people from accidentally caching
285                          *      cache control attributes.
286                          */
287                         if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
288                         case PW_CACHE_TTL:
289                         case PW_CACHE_STATUS_ONLY:
290                         case PW_CACHE_READ_ONLY:
291                         case PW_CACHE_MERGE:
292                         case PW_CACHE_ENTRY_HITS:
293                                 RDEBUG2("Skipping %s", vp->da->name);
294                                 continue;
295
296                         default:
297                                 break;
298                         }
299
300                         RINDENT();
301                         if (RDEBUG_ENABLED2) map_debug_log(request, map, vp);
302                         REXDENT();
303                         (void) talloc_steal(c, vp);
304
305                         vp->op = map->op;
306
307                         switch (map->lhs->tmpl_list) {
308                         case PAIR_LIST_REQUEST:
309                                 fr_cursor_insert(&cached_request, vp);
310                                 break;
311
312                         case PAIR_LIST_REPLY:
313                                 fr_cursor_insert(&cached_reply, vp);
314                                 break;
315
316                         case PAIR_LIST_CONTROL:
317                                 fr_cursor_insert(&cached_control, vp);
318                                 break;
319
320                         default:
321                                 rad_assert(0);  /* should have been caught by validation */
322                         }
323                 }
324         }
325
326         /*
327          *      Check to see if we need to merge the entry into the request
328          */
329         vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
330         if (vp && (vp->vp_integer > 0)) merge = true;
331
332         if (merge) cache_merge(inst, request, c);
333
334         for (;;) {
335                 cache_status_t ret;
336
337                 ret = inst->module->insert(inst, request, handle, c);
338                 switch (ret) {
339                 case CACHE_RECONNECT:
340                         if (cache_reconnect(inst, request, handle) == 0) continue;
341                         return RLM_MODULE_FAIL;
342
343                 case CACHE_OK:
344                         RDEBUG("Commited entry, TTL %d seconds", ttl);
345                         cache_free(inst, &c);
346                         return merge ? RLM_MODULE_UPDATED :
347                                        RLM_MODULE_OK;
348
349                 default:
350                         talloc_free(c); /* Failed insertion - use talloc_free not the driver free */
351                         return RLM_MODULE_FAIL;
352                 }
353         }
354 }
355
356 /** Verify that a map in the cache section makes sense
357  *
358  */
359 static int cache_verify(value_pair_map_t *map, UNUSED void *ctx)
360 {
361         if (modcall_fixup_update(map, ctx) < 0) return -1;
362
363         if ((map->lhs->type != TMPL_TYPE_ATTR) &&
364             (map->lhs->type != TMPL_TYPE_LIST)) {
365                 cf_log_err(map->ci, "Left operand must be an attribute ref or a list");
366                 return -1;
367         }
368
369         switch (map->rhs->type) {
370         case TMPL_TYPE_EXEC:
371                 cf_log_err(map->ci, "Exec values are not allowed");
372                 return -1;
373         /*
374          *      Only =, :=, += and -= operators are supported for
375          *      cache entries.
376          */
377         case TMPL_TYPE_LITERAL:
378         case TMPL_TYPE_XLAT:
379         case TMPL_TYPE_ATTR:
380                 switch (map->op) {
381                 case T_OP_SET:
382                 case T_OP_EQ:
383                 case T_OP_SUB:
384                 case T_OP_ADD:
385                         break;
386
387                 default:
388                         cf_log_err(map->ci, "Operator \"%s\" not allowed for %s values",
389                                    fr_int2str(fr_tokens, map->op, "<INVALID>"),
390                                    fr_int2str(tmpl_types, map->rhs->type, "<INVALID>"));
391                         return -1;
392                 }
393         default:
394                 break;
395         }
396
397         return 0;
398 }
399
400 /*
401  *      Do caching checks.  Since we can update ANY VP list, we do
402  *      exactly the same thing for all sections (autz / auth / etc.)
403  *
404  *      If you want to cache something different in different sections,
405  *      configure another cache module.
406  */
407 static rlm_rcode_t CC_HINT(nonnull) mod_cache_it(void *instance, REQUEST *request)
408 {
409         rlm_cache_entry_t *c;
410         rlm_cache_t *inst = instance;
411
412         rlm_cache_handle_t *handle;
413
414         vp_cursor_t cursor;
415         VALUE_PAIR *vp;
416         char buffer[1024];
417         rlm_rcode_t rcode;
418
419         int ttl = inst->ttl;
420
421         if (radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL) < 0) return RLM_MODULE_FAIL;
422
423         if (buffer[0] == '\0') {
424                 REDEBUG("Zero length key string is invalid");
425                 return RLM_MODULE_INVALID;
426         }
427
428         if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL;
429
430         rcode = cache_find(&c, inst, request, &handle, buffer);
431         if (rcode == RLM_MODULE_FAIL) goto finish;
432         rad_assert(handle);
433
434         /*
435          *      If Cache-Status-Only == yes, only return whether we found a
436          *      valid cache entry
437          */
438         vp = pairfind(request->config_items, PW_CACHE_STATUS_ONLY, 0, TAG_ANY);
439         if (vp && vp->vp_integer) {
440                 rcode = c ? RLM_MODULE_OK:
441                             RLM_MODULE_NOTFOUND;
442                 goto finish;
443         }
444
445         /*
446          *      Update the expiry time based on the TTL.
447          *      A TTL of 0 means "delete from the cache".
448          *      A TTL < 0 means "delete from the cache and recreate the entry".
449          */
450         vp = pairfind(request->config_items, PW_CACHE_TTL, 0, TAG_ANY);
451         if (vp) ttl = vp->vp_signed;
452
453         /*
454          *      If there's no existing cache entry, go and create a new one.
455          */
456         if (!c) {
457                 if (ttl <= 0) ttl = inst->ttl;
458                 goto insert;
459         }
460
461         /*
462          *      Expire the entry if requested to do so
463          */
464         if (vp) {
465                 if (ttl == 0) {
466                         cache_expire(inst, request, &handle, &c);
467                         RDEBUG("Forcing expiry of entry");
468                         rcode = RLM_MODULE_OK;
469                         goto finish;
470                 }
471
472                 if (ttl < 0) {
473                         RDEBUG("Forcing expiry of existing entry");
474                         cache_expire(inst, request, &handle, &c);
475                         ttl *= -1;
476                         goto insert;
477                 }
478                 c->expires = request->timestamp + ttl;
479                 RDEBUG("Setting TTL to %d", ttl);
480         }
481
482         /*
483          *      Cache entry was still valid, so we merge it into the request
484          *      and return. No need to add a new entry.
485          */
486         cache_merge(inst, request, c);
487         rcode = RLM_MODULE_UPDATED;
488
489         goto finish;
490
491 insert:
492         /*
493          *      If Cache-Read-Only == yes, then we only allow already cached entries
494          *      to be merged into the request
495          */
496         vp = pairfind(request->config_items, PW_CACHE_READ_ONLY, 0, TAG_ANY);
497         if (vp && vp->vp_integer) {
498                 rcode = RLM_MODULE_NOTFOUND;
499                 goto finish;
500         }
501
502         /*
503          *      Create a new entry.
504          */
505         rcode = cache_insert(inst, request, &handle, buffer, ttl);
506         rad_assert(handle);
507
508 finish:
509         cache_free(inst, &c);
510         cache_release(inst, request, &handle);
511
512         /*
513          *      Clear control attributes
514          */
515         for (vp = fr_cursor_init(&cursor, &request->config_items);
516              vp;
517              vp = fr_cursor_next(&cursor)) {
518                 if (vp->da->vendor == 0) switch (vp->da->attr) {
519                 case PW_CACHE_TTL:
520                 case PW_CACHE_STATUS_ONLY:
521                 case PW_CACHE_READ_ONLY:
522                 case PW_CACHE_MERGE:
523                         vp = fr_cursor_remove(&cursor);
524                         talloc_free(vp);
525                         break;
526                 }
527         }
528
529         return rcode;
530 }
531
532 /*
533  *      Allow single attribute values to be retrieved from the cache.
534  */
535 static ssize_t cache_xlat(void *instance, REQUEST *request,
536                           char const *fmt, char *out, size_t freespace)
537 {
538         rlm_cache_entry_t       *c;
539         rlm_cache_t             *inst = instance;
540         rlm_cache_handle_t      *handle;
541
542         VALUE_PAIR              *vp, *vps;
543         pair_lists_t            list;
544         DICT_ATTR const         *target;
545         char const              *p = fmt;
546         size_t                  len;
547         int                     ret = 0;
548
549         p += radius_list_name(&list, p, PAIR_LIST_REQUEST);
550         if (list == PAIR_LIST_UNKNOWN) {
551                 REDEBUG("Unknown list qualifier in \"%s\"", fmt);
552                 ret = -1;
553                 goto finish;
554         }
555
556         target = dict_attrbyname(p);
557         if (!target) {
558                 REDEBUG("Unknown attribute \"%s\"", p);
559                 return -1;
560         }
561
562         if (cache_acquire(&handle, inst, request) < 0) return -1;
563
564         switch (cache_find(&c, inst, request, handle, fmt)) {
565         case RLM_MODULE_OK:             /* found */
566                 break;
567
568         case RLM_MODULE_NOTFOUND:       /* not found */
569                 *out = '\0';
570                 return 0;
571
572         default:
573                 return -1;
574         }
575
576         switch (list) {
577         case PAIR_LIST_REQUEST:
578                 vps = c->packet;
579                 break;
580
581         case PAIR_LIST_REPLY:
582                 vps = c->reply;
583                 break;
584
585         case PAIR_LIST_CONTROL:
586                 vps = c->control;
587                 break;
588
589         case PAIR_LIST_UNKNOWN:
590
591         default:
592                 REDEBUG("Unsupported list \"%s\"", fr_int2str(pair_lists, list, "<UNKNOWN>"));
593                 ret = -1;
594                 goto finish;
595         }
596
597         vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
598         if (!vp) {
599                 RDEBUG("No instance of this attribute has been cached");
600                 *out = '\0';
601                 goto finish;
602         }
603
604         len = vp_prints_value(out, freespace, vp, 0);
605         if (is_truncated(len, freespace)) {
606                 REDEBUG("Insufficient buffer space to write cached value");
607                 ret = -1;
608                 goto finish;
609         }
610
611 finish:
612         cache_free(inst, &c);
613         cache_release(inst, request, &handle);
614
615         return ret;
616 }
617
618 /*
619  *      Only free memory we allocated.  The strings allocated via
620  *      cf_section_parse() do not need to be freed.
621  */
622 static int mod_detach(void *instance)
623 {
624         rlm_cache_t *inst = instance;
625
626         talloc_free(inst->maps);
627
628         /*
629          *  We need to explicitly free all children, so if the driver
630          *  parented any memory off the instance, their destructors
631          *  run before we unload the bytecode for them.
632          *
633          *  If we don't do this, we get a SEGV deep inside the talloc code
634          *  when it tries to call a destructor that no longer exists.
635          */
636         talloc_free_children(inst);
637
638         /*
639          *  Decrements the reference count. The driver object won't be unloaded
640          *  until all instances of rlm_cache that use it have been destroyed.
641          */
642         if (inst->handle) dlclose(inst->handle);
643
644         return 0;
645 }
646
647 /*
648  *      Instantiate the module.
649  */
650 static int mod_instantiate(CONF_SECTION *conf, void *instance)
651 {
652         rlm_cache_t *inst = instance;
653
654         inst->cs = conf;
655
656         inst->xlat_name = cf_section_name2(conf);
657         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
658
659         /*
660          *      Register the cache xlat function
661          */
662         xlat_register(inst->xlat_name, cache_xlat, NULL, inst);
663
664         /*
665          *      Sanity check for crazy people.
666          */
667         if (strncmp(inst->driver_name, "rlm_cache_", 8) != 0) {
668                 ERROR("%s: \"%s\" is NOT an Cache driver!", inst->xlat_name, inst->driver_name);
669                 return -1;
670         }
671
672         /*
673          *      Load the appropriate driver for our database
674          */
675         inst->handle = lt_dlopenext(inst->driver_name);
676         if (!inst->handle) {
677                 ERROR("Could not link driver %s: %s", inst->driver_name, dlerror());
678                 ERROR("Make sure it (and all its dependent libraries!) are in the search path of your system's ld");
679                 return -1;
680         }
681
682         inst->module = (cache_module_t *) dlsym(inst->handle, inst->driver_name);
683         if (!inst->module) {
684                 ERROR("Could not link symbol %s: %s", inst->driver_name, dlerror());
685                 return -1;
686         }
687
688         DEBUG3("Driver %s loaded successfully", inst->module->name);
689
690         /*
691          *      Non optional fields and callbacks
692          */
693         rad_assert(inst->module->name);
694         rad_assert(inst->module->find);
695         rad_assert(inst->module->insert);
696         rad_assert(inst->module->expire);
697
698         if (inst->module->mod_instantiate) {
699                 CONF_SECTION *cs;
700                 char const *name;
701
702                 name = strrchr(inst->driver_name, '_');
703                 if (!name) {
704                         name = inst->driver_name;
705                 } else {
706                         name++;
707                 }
708
709                 cs = cf_section_sub_find(conf, name);
710                 if (!cs) {
711                         cs = cf_section_alloc(conf, name, NULL);
712                         if (!cs) return -1;
713                 }
714
715                 /*
716                  *      It's up to the driver to register a destructor (using talloc)
717                  *
718                  *      Should write its instance data in inst->driver,
719                  *      and parent it off of inst.
720                  */
721                 if (inst->module->mod_instantiate(cs, inst) < 0) return -1;
722         }
723
724         rad_assert(inst->key && *inst->key);
725
726         if (inst->ttl == 0) {
727                 cf_log_err_cs(conf, "Must set 'ttl' to non-zero");
728                 return -1;
729         }
730
731         if (inst->epoch != 0) {
732                 cf_log_err_cs(conf, "Must not set 'epoch' in the configuration files");
733                 return -1;
734         }
735
736         /*
737          *      Make sure the users don't screw up too badly.
738          */
739         if (map_afrom_cs(&inst->maps, cf_section_sub_find(inst->cs, "update"),
740                          PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, cache_verify, NULL, MAX_ATTRMAP) < 0) {
741                 return -1;
742         }
743
744         if (!inst->maps) {
745                 cf_log_err_cs(inst->cs, "Cache config must contain an update section, and "
746                               "that section must not be empty");
747
748                 return -1;
749         }
750         return 0;
751 }
752
753 /*
754  *      The module name should be the only globally exported symbol.
755  *      That is, everything else should be 'static'.
756  *
757  *      If the module needs to temporarily modify it's instantiation
758  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
759  *      The server will then take care of ensuring that the module
760  *      is single-threaded.
761  */
762 module_t rlm_cache = {
763         RLM_MODULE_INIT,
764         "cache",
765         0,                              /* type */
766         sizeof(rlm_cache_t),
767         module_config,
768         mod_instantiate,                /* instantiation */
769         mod_detach,                     /* detach */
770         {
771                 NULL,                   /* authentication */
772                 mod_cache_it,           /* authorization */
773                 mod_cache_it,           /* preaccounting */
774                 mod_cache_it,           /* accounting */
775                 NULL,                   /* checksimul */
776                 mod_cache_it,           /* pre-proxy */
777                 mod_cache_it,           /* post-proxy */
778                 mod_cache_it,           /* post-auth */
779         },
780 };