6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2001,2006 The FreeRADIUS server project
21 * Copyright 2001 Alan DeKok <aland@ox.org>
22 * Copyright 2001-3 Kostas Kalevras <kkalev@noc.ntua.gr>
25 #include <freeradius-devel/ident.h>
29 #include <freeradius-devel/autoconf.h>
36 #include <freeradius-devel/radiusd.h>
37 #include <freeradius-devel/modules.h>
38 #include <freeradius-devel/conffile.h>
43 #ifdef NEEDS_GDBM_SYNC
44 # define GDBM_SYNCOPT GDBM_SYNC
46 # define GDBM_SYNCOPT 0
50 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
52 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
55 #ifndef HAVE_GDBM_FDESC
56 #define gdbm_fdesc(foo) (-1)
59 #define UNIQUEID_MAX_LEN 32
62 * Define a structure for our module configuration.
64 * These variables do not need to be in a structure, but it's
65 * a lot cleaner to do so, and a pointer to the structure can
66 * be used as the instance handle.
68 typedef struct rlm_caching_t {
69 char *filename; /* name of the database file */
70 char *key; /* An xlated string to use as key for the records */
71 char *post_auth; /* If set and we find a cached entry, set Post-Auth to this value */
72 char *cache_ttl_str; /* The string represantation of the TTL */
73 int cache_ttl; /* The cache TTL */
74 int hit_ratio; /* Show cache hit ratio every so many queries */
75 int cache_rejects; /* Do we also cache rejects? */
76 int cache_size; /* The cache size to pass to GDBM */
77 uint32_t cache_queries; /* The number of cache requests */
78 uint32_t cache_hits; /* The number of cache hits */
79 GDBM_FILE gdbm; /* The gdbm file handle */
81 pthread_mutex_t mutex; /* A mutex to lock the gdbm file for only one reader/writer */
85 #define MAX_RECORD_LEN 750
86 #define MAX_AUTH_TYPE 32
88 #define show_hit_ratio \
89 if (data->hit_ratio && (data->cache_queries % data->hit_ratio) == 0) \
90 radlog(L_INFO, "rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%", \
91 data->cache_queries,data->cache_hits,hit_ratio)
93 typedef struct rlm_caching_data {
95 char data[MAX_RECORD_LEN];
96 char auth_type[MAX_AUTH_TYPE];
100 #ifndef HAVE_PTHREAD_H
102 * This is a lot simpler than putting ifdef's around
103 * every use of the pthread functions.
105 #define pthread_mutex_lock(a)
106 #define pthread_mutex_unlock(a)
107 #define pthread_mutex_init(a,b)
108 #define pthread_mutex_destroy(a)
112 * A mapping of configuration file names to internal variables.
114 * Note that the string is dynamically allocated, so it MUST
115 * be freed. When the configuration file parse re-reads the string,
116 * it free's the old one, and strdup's the new one, placing the pointer
117 * to the strdup'd string into 'config.string'. This gets around
120 static const CONF_PARSER module_config[] = {
121 { "filename", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,filename), NULL, NULL },
122 { "key", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,key), NULL, "%{Acct-Unique-Session-Id}" },
123 { "post-auth", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,post_auth), NULL, NULL },
124 { "cache-ttl", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,cache_ttl_str), NULL, "1d" },
125 { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_caching_t,cache_size), NULL, "1000" },
126 { "hit-ratio", PW_TYPE_INTEGER, offsetof(rlm_caching_t,hit_ratio), NULL, "0" },
127 { "cache-rejects", PW_TYPE_BOOLEAN, offsetof(rlm_caching_t,cache_rejects), NULL, "yes" },
128 { NULL, -1, 0, NULL, NULL }
131 static int caching_detach(void *instance);
133 static int find_ttl(char *ttl)
138 if (isdigit((int) ttl[0])){
143 if (!isalpha((int) last))
146 DEBUG("rlm_caching::find_ttl: num=%d, last=%c",len,last);
166 DEBUG("rlm_caching::find_ttl: Returning '%d'",len);
172 * Do any per-module initialization that is separate to each
173 * configured instance of the module. e.g. set up connections
174 * to external databases, read configuration files, set up
175 * dictionary entries, etc.
177 * If configuration information is given in the config section
178 * that must be referenced in later calls, store a handle to it
179 * in *instance otherwise put a null pointer there.
181 static int caching_instantiate(CONF_SECTION *conf, void **instance)
187 * Set up a storage area for instance data
189 data = rad_malloc(sizeof(*data));
191 radlog(L_ERR, "rlm_caching: rad_malloc() failed.");
194 memset(data, 0, sizeof(*data));
197 * If the configuration parameters can't be parsed, then
200 if (cf_section_parse(conf, data, module_config) < 0) {
204 cache_size = data->cache_size;
207 * Discover the attribute number of the key.
209 if (data->key == NULL) {
210 radlog(L_ERR, "rlm_caching: 'key' must be set.");
211 caching_detach(data);
214 if (data->cache_ttl_str == NULL) {
215 radlog(L_ERR, "rlm_caching: 'cache-ttl' must be set.");
216 caching_detach(data);
220 data->cache_ttl = find_ttl(data->cache_ttl_str);
221 if (data->cache_ttl == 0) {
222 radlog(L_ERR, "rlm_caching: 'cache-ttl' is invalid.");
223 caching_detach(data);
228 if (data->filename == NULL) {
229 radlog(L_ERR, "rlm_caching: 'filename' must be set.");
230 caching_detach(data);
233 data->gdbm = gdbm_open(data->filename, sizeof(int),
234 GDBM_WRCREAT | GDBM_COUNTER_OPTS, 0600, NULL);
235 if (data->gdbm == NULL) {
236 radlog(L_ERR, "rlm_caching: Failed to open file %s: %s",
237 data->filename, strerror(errno));
238 caching_detach(data);
241 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
242 radlog(L_ERR, "rlm_caching: Failed to set cache size");
247 pthread_mutex_init(&data->mutex, NULL);
255 * Cache the reply items and the Auth-Type
257 static int caching_postauth(void *instance, REQUEST *request)
259 rlm_caching_t *data = (rlm_caching_t *)instance;
260 char key[MAX_STRING_LEN];
263 VALUE_PAIR *reply_vp;
264 VALUE_PAIR *auth_type;
265 rlm_caching_data cache_data;
271 if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
272 DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
273 return RLM_MODULE_NOOP;
275 if ((auth_type = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL){
276 DEBUG("rlm_caching: Found Auth-Type, value: '%s'",auth_type->vp_strvalue);
277 if (strcmp(auth_type->vp_strvalue,"Reject") == 0 && data->cache_rejects == 0){
278 DEBUG("rlm_caching: No caching of Rejects. Returning NOOP");
279 return RLM_MODULE_NOOP;
281 if (strlen(auth_type->vp_strvalue) > MAX_AUTH_TYPE - 1){
282 DEBUG("rlm_caching: Auth-Type value too large");
283 return RLM_MODULE_NOOP;
287 DEBUG("rlm_caching: No Auth-Type found. Returning NOOP");
288 return RLM_MODULE_NOOP;
291 reply_vp = request->reply->vps;
293 if (reply_vp == NULL) {
294 DEBUG("rlm_caching: The Request does not contain any reply attributes");
295 return RLM_MODULE_NOOP;
297 if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
298 radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
299 return RLM_MODULE_FAIL;
302 memset(&cache_data,0,sizeof(rlm_caching_data));
304 cache_data.creation = time(NULL);
305 strcpy(cache_data.auth_type,auth_type->vp_strvalue);
307 size = MAX_RECORD_LEN;
311 DEBUG("rlm_caching: Not enough space.");
312 return RLM_MODULE_NOOP;
314 ret = vp_prints(cache_data.data + count,size,reply_vp);
316 DEBUG("rlm_caching: Record is too large, will not store it.");
317 return RLM_MODULE_NOOP;
321 DEBUG("rlm_caching: VP=%s,VALUE=%s,length=%d,cache record length=%d, space left=%d",
322 reply_vp->name,reply_vp->vp_strvalue,ret,count,size);
323 reply_vp = reply_vp->next;
325 cache_data.len = count;
327 DEBUG("rlm_caching: Storing cache for Key='%s'",key);
328 data_datum.dptr = (rlm_caching_data *) &cache_data;
329 data_datum.dsize = sizeof(rlm_caching_data);
331 key_datum.dptr = (char *) key;
332 key_datum.dsize = strlen(key);
334 pthread_mutex_lock(&data->mutex);
335 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
336 pthread_mutex_unlock(&data->mutex);
338 radlog(L_ERR, "rlm_caching: Failed storing data to %s: %s",
339 data->filename, gdbm_strerror(gdbm_errno));
340 return RLM_MODULE_FAIL;
342 DEBUG("rlm_caching: New value stored successfully.");
344 return RLM_MODULE_OK;
348 * Find the named user in this modules database. Create the set
349 * of attribute-value pairs to check and reply with for this user
350 * from the database. The authentication code only needs to check
351 * the password, the rest is done here.
353 static int caching_authorize(void *instance, REQUEST *request)
355 rlm_caching_t *data = (rlm_caching_t *) instance;
356 char key[MAX_STRING_LEN];
359 rlm_caching_data cache_data;
360 VALUE_PAIR *reply_item;
364 int delete_cache = 0;
365 float hit_ratio = 0.0;
367 /* quiet the compiler */
371 if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
372 DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
373 return RLM_MODULE_NOOP;
375 if (pairfind(request->packet->vps, PW_CACHE_DELETE_CACHE) != NULL){
376 DEBUG("rlm_caching: Found Cache-Delete-Cache. Will delete record if found");
380 if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
381 radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
382 return RLM_MODULE_FAIL;
385 key_datum.dptr = key;
386 key_datum.dsize = strlen(key);
389 DEBUG("rlm_caching: Searching the database for key '%s'",key);
390 pthread_mutex_lock(&data->mutex);
391 data_datum = gdbm_fetch(data->gdbm, key_datum);
392 pthread_mutex_unlock(&data->mutex);
393 data->cache_queries++;
394 if (data_datum.dptr != NULL){
395 DEBUG("rlm_caching: Key Found.");
397 hit_ratio = (float)data->cache_hits / data->cache_queries;
399 memcpy(&cache_data, data_datum.dptr, sizeof(rlm_caching_data));
400 free(data_datum.dptr);
402 if (delete_cache == 0 && cache_data.creation + data->cache_ttl <= time(NULL)){
403 DEBUG("rlm_caching: Cache entry has expired");
404 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
405 data->cache_queries,data->cache_hits,hit_ratio);
410 DEBUG("rlm_caching: Deleting record");
412 pthread_mutex_lock(&data->mutex);
413 gdbm_delete(data->gdbm, key_datum);
414 pthread_mutex_unlock(&data->mutex);
416 return RLM_MODULE_NOOP;
418 tmp = cache_data.data;
420 pairfree(&request->reply->vps);
421 while(tmp && len < cache_data.len){
423 if (userparse(tmp, &reply_item) > 0 && reply_item != NULL)
424 pairadd(&request->reply->vps, reply_item);
425 len += (strlen(tmp) + 1);
426 DEBUG("rlm_caching: VP='%s',VALUE='%s',lenth='%d',cache record length='%d'",
427 reply_item->name,reply_item->vp_strvalue,reply_item->length,len);
428 tmp = cache_data.data + len;
432 DEBUG("rlm_caching: No reply items found. Returning NOOP");
433 return RLM_MODULE_NOOP;
435 if (cache_data.auth_type){
436 DEBUG("rlm_caching: Adding Auth-Type '%s'",cache_data.auth_type);
438 if ((item = pairfind(request->config_items, PW_AUTH_TYPE)) == NULL){
439 item = pairmake("Auth-Type", cache_data.auth_type, T_OP_SET);
440 pairadd(&request->config_items, item);
443 strcmp(item->vp_strvalue, cache_data.auth_type);
444 item->length = strlen(cache_data.auth_type);
447 if (data->post_auth){
448 DEBUG("rlm_caching: Adding Post-Auth-Type '%s'",data->post_auth);
450 if ((item = pairfind(request->config_items, PW_POST_AUTH_TYPE)) == NULL){
451 item = pairmake("Post-Auth-Type", data->post_auth, T_OP_SET);
452 pairadd(&request->config_items, item);
455 strcmp(item->vp_strvalue, data->post_auth);
456 item->length = strlen(data->post_auth);
459 item = pairmake("Cache-No-Caching", "YES", T_OP_EQ);
460 pairadd(&request->packet->vps, item);
462 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
463 data->cache_queries,data->cache_hits,hit_ratio);
466 return RLM_MODULE_OK;
469 DEBUG("rlm_caching: Could not find the requested key in the database.");
470 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
471 data->cache_queries,data->cache_hits,hit_ratio);
475 return RLM_MODULE_NOOP;
478 static int caching_detach(void *instance)
480 rlm_caching_t *data = (rlm_caching_t *) instance;
483 gdbm_close(data->gdbm);
484 pthread_mutex_destroy(&data->mutex);
491 * The module name should be the only globally exported symbol.
492 * That is, everything else should be 'static'.
494 * If the module needs to temporarily modify it's instantiation
495 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
496 * The server will then take care of ensuring that the module
497 * is single-threaded.
499 module_t rlm_caching = {
502 RLM_TYPE_THREAD_SAFE, /* type */
503 caching_instantiate, /* instantiation */
504 caching_detach, /* detach */
506 NULL, /* authentication */
507 caching_authorize, /* authorization */
508 NULL, /* preaccounting */
509 NULL, /* accounting */
510 NULL, /* checksimul */
511 NULL, /* pre-proxy */
512 NULL, /* post-proxy */
513 caching_postauth /* post-auth */