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>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
38 #ifdef NEEDS_GDBM_SYNC
39 # define GDBM_SYNCOPT GDBM_SYNC
41 # define GDBM_SYNCOPT 0
45 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
47 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
50 #ifndef HAVE_GDBM_FDESC
51 #define gdbm_fdesc(foo) (-1)
54 #define UNIQUEID_MAX_LEN 32
57 * Define a structure for our module configuration.
59 * These variables do not need to be in a structure, but it's
60 * a lot cleaner to do so, and a pointer to the structure can
61 * be used as the instance handle.
63 typedef struct rlm_caching_t {
64 char *filename; /* name of the database file */
65 char *key; /* An xlated string to use as key for the records */
66 char *post_auth; /* If set and we find a cached entry, set Post-Auth to this value */
67 char *cache_ttl_str; /* The string represantation of the TTL */
68 int cache_ttl; /* The cache TTL */
69 int hit_ratio; /* Show cache hit ratio every so many queries */
70 int cache_rejects; /* Do we also cache rejects? */
71 int cache_size; /* The cache size to pass to GDBM */
72 uint32_t cache_queries; /* The number of cache requests */
73 uint32_t cache_hits; /* The number of cache hits */
74 GDBM_FILE gdbm; /* The gdbm file handle */
76 pthread_mutex_t mutex; /* A mutex to lock the gdbm file for only one reader/writer */
80 #define MAX_RECORD_LEN 750
81 #define MAX_AUTH_TYPE 32
83 #define show_hit_ratio \
84 if (data->hit_ratio && (data->cache_queries % data->hit_ratio) == 0) \
85 radlog(L_INFO, "rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%", \
86 data->cache_queries,data->cache_hits,hit_ratio)
88 typedef struct rlm_caching_data {
90 char data[MAX_RECORD_LEN];
91 char auth_type[MAX_AUTH_TYPE];
95 #ifndef HAVE_PTHREAD_H
97 * This is a lot simpler than putting ifdef's around
98 * every use of the pthread functions.
100 #define pthread_mutex_lock(a)
101 #define pthread_mutex_unlock(a)
102 #define pthread_mutex_init(a,b)
103 #define pthread_mutex_destroy(a)
107 * A mapping of configuration file names to internal variables.
109 * Note that the string is dynamically allocated, so it MUST
110 * be freed. When the configuration file parse re-reads the string,
111 * it free's the old one, and strdup's the new one, placing the pointer
112 * to the strdup'd string into 'config.string'. This gets around
115 static const CONF_PARSER module_config[] = {
116 { "filename", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,filename), NULL, NULL },
117 { "key", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,key), NULL, "%{Acct-Unique-Session-Id}" },
118 { "post-auth", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,post_auth), NULL, NULL },
119 { "cache-ttl", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,cache_ttl_str), NULL, "1d" },
120 { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_caching_t,cache_size), NULL, "1000" },
121 { "hit-ratio", PW_TYPE_INTEGER, offsetof(rlm_caching_t,hit_ratio), NULL, "0" },
122 { "cache-rejects", PW_TYPE_BOOLEAN, offsetof(rlm_caching_t,cache_rejects), NULL, "yes" },
123 { NULL, -1, 0, NULL, NULL }
126 static int caching_detach(void *instance);
128 static int find_ttl(char *ttl)
133 if (isdigit((int) ttl[0])){
138 if (!isalpha((int) last))
141 DEBUG("rlm_caching::find_ttl: num=%d, last=%c",len,last);
161 DEBUG("rlm_caching::find_ttl: Returning '%d'",len);
167 * Do any per-module initialization that is separate to each
168 * configured instance of the module. e.g. set up connections
169 * to external databases, read configuration files, set up
170 * dictionary entries, etc.
172 * If configuration information is given in the config section
173 * that must be referenced in later calls, store a handle to it
174 * in *instance otherwise put a null pointer there.
176 static int caching_instantiate(CONF_SECTION *conf, void **instance)
182 * Set up a storage area for instance data
184 data = rad_malloc(sizeof(*data));
186 radlog(L_ERR, "rlm_caching: rad_malloc() failed.");
189 memset(data, 0, sizeof(*data));
192 * If the configuration parameters can't be parsed, then
195 if (cf_section_parse(conf, data, module_config) < 0) {
199 cache_size = data->cache_size;
202 * Discover the attribute number of the key.
204 if (data->key == NULL) {
205 radlog(L_ERR, "rlm_caching: 'key' must be set.");
206 caching_detach(data);
209 if (data->cache_ttl_str == NULL) {
210 radlog(L_ERR, "rlm_caching: 'cache-ttl' must be set.");
211 caching_detach(data);
215 data->cache_ttl = find_ttl(data->cache_ttl_str);
216 if (data->cache_ttl == 0) {
217 radlog(L_ERR, "rlm_caching: 'cache-ttl' is invalid.");
218 caching_detach(data);
223 if (data->filename == NULL) {
224 radlog(L_ERR, "rlm_caching: 'filename' must be set.");
225 caching_detach(data);
228 data->gdbm = gdbm_open(data->filename, sizeof(int),
229 GDBM_WRCREAT | GDBM_COUNTER_OPTS, 0600, NULL);
230 if (data->gdbm == NULL) {
231 radlog(L_ERR, "rlm_caching: Failed to open file %s: %s",
232 data->filename, strerror(errno));
233 caching_detach(data);
236 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
237 radlog(L_ERR, "rlm_caching: Failed to set cache size");
242 pthread_mutex_init(&data->mutex, NULL);
250 * Cache the reply items and the Auth-Type
252 static int caching_postauth(void *instance, REQUEST *request)
254 rlm_caching_t *data = (rlm_caching_t *)instance;
255 char key[MAX_STRING_LEN];
258 VALUE_PAIR *reply_vp;
259 VALUE_PAIR *auth_type;
260 rlm_caching_data cache_data;
266 if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
267 DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
268 return RLM_MODULE_NOOP;
270 if ((auth_type = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL){
271 DEBUG("rlm_caching: Found Auth-Type, value: '%s'",auth_type->vp_strvalue);
272 if (strcmp(auth_type->vp_strvalue,"Reject") == 0 && data->cache_rejects == 0){
273 DEBUG("rlm_caching: No caching of Rejects. Returning NOOP");
274 return RLM_MODULE_NOOP;
276 if (strlen(auth_type->vp_strvalue) > MAX_AUTH_TYPE - 1){
277 DEBUG("rlm_caching: Auth-Type value too large");
278 return RLM_MODULE_NOOP;
282 DEBUG("rlm_caching: No Auth-Type found. Returning NOOP");
283 return RLM_MODULE_NOOP;
286 reply_vp = request->reply->vps;
288 if (reply_vp == NULL) {
289 DEBUG("rlm_caching: The Request does not contain any reply attributes");
290 return RLM_MODULE_NOOP;
292 if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
293 radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
294 return RLM_MODULE_FAIL;
297 memset(&cache_data,0,sizeof(rlm_caching_data));
299 cache_data.creation = time(NULL);
300 strcpy(cache_data.auth_type,auth_type->vp_strvalue);
302 size = MAX_RECORD_LEN;
306 DEBUG("rlm_caching: Not enough space.");
307 return RLM_MODULE_NOOP;
309 ret = vp_prints(cache_data.data + count,size,reply_vp);
311 DEBUG("rlm_caching: Record is too large, will not store it.");
312 return RLM_MODULE_NOOP;
316 DEBUG("rlm_caching: VP=%s,VALUE=%s,length=%d,cache record length=%d, space left=%d",
317 reply_vp->name,reply_vp->vp_strvalue,ret,count,size);
318 reply_vp = reply_vp->next;
320 cache_data.len = count;
322 DEBUG("rlm_caching: Storing cache for Key='%s'",key);
323 data_datum.dptr = (rlm_caching_data *) &cache_data;
324 data_datum.dsize = sizeof(rlm_caching_data);
326 key_datum.dptr = (char *) key;
327 key_datum.dsize = strlen(key);
329 pthread_mutex_lock(&data->mutex);
330 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
331 pthread_mutex_unlock(&data->mutex);
333 radlog(L_ERR, "rlm_caching: Failed storing data to %s: %s",
334 data->filename, gdbm_strerror(gdbm_errno));
335 return RLM_MODULE_FAIL;
337 DEBUG("rlm_caching: New value stored successfully.");
339 return RLM_MODULE_OK;
343 * Find the named user in this modules database. Create the set
344 * of attribute-value pairs to check and reply with for this user
345 * from the database. The authentication code only needs to check
346 * the password, the rest is done here.
348 static int caching_authorize(void *instance, REQUEST *request)
350 rlm_caching_t *data = (rlm_caching_t *) instance;
351 char key[MAX_STRING_LEN];
354 rlm_caching_data cache_data;
355 VALUE_PAIR *reply_item;
359 int delete_cache = 0;
360 float hit_ratio = 0.0;
362 /* quiet the compiler */
366 if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
367 DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
368 return RLM_MODULE_NOOP;
370 if (pairfind(request->packet->vps, PW_CACHE_DELETE_CACHE) != NULL){
371 DEBUG("rlm_caching: Found Cache-Delete-Cache. Will delete record if found");
375 if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
376 radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
377 return RLM_MODULE_FAIL;
380 key_datum.dptr = key;
381 key_datum.dsize = strlen(key);
384 DEBUG("rlm_caching: Searching the database for key '%s'",key);
385 pthread_mutex_lock(&data->mutex);
386 data_datum = gdbm_fetch(data->gdbm, key_datum);
387 pthread_mutex_unlock(&data->mutex);
388 data->cache_queries++;
389 if (data_datum.dptr != NULL){
390 DEBUG("rlm_caching: Key Found.");
392 hit_ratio = (float)data->cache_hits / data->cache_queries;
394 memcpy(&cache_data, data_datum.dptr, sizeof(rlm_caching_data));
395 free(data_datum.dptr);
397 if (delete_cache == 0 && cache_data.creation + data->cache_ttl <= time(NULL)){
398 DEBUG("rlm_caching: Cache entry has expired");
399 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
400 data->cache_queries,data->cache_hits,hit_ratio);
405 DEBUG("rlm_caching: Deleting record");
407 pthread_mutex_lock(&data->mutex);
408 gdbm_delete(data->gdbm, key_datum);
409 pthread_mutex_unlock(&data->mutex);
411 return RLM_MODULE_NOOP;
413 tmp = cache_data.data;
415 pairfree(&request->reply->vps);
416 while(tmp && len < cache_data.len){
418 if (userparse(tmp, &reply_item) > 0 && reply_item != NULL)
419 pairadd(&request->reply->vps, reply_item);
420 len += (strlen(tmp) + 1);
421 DEBUG("rlm_caching: VP='%s',VALUE='%s',lenth='%d',cache record length='%d'",
422 reply_item->name,reply_item->vp_strvalue,reply_item->length,len);
423 tmp = cache_data.data + len;
427 DEBUG("rlm_caching: No reply items found. Returning NOOP");
428 return RLM_MODULE_NOOP;
430 if (cache_data.auth_type){
431 DEBUG("rlm_caching: Adding Auth-Type '%s'",cache_data.auth_type);
433 if ((item = pairfind(request->config_items, PW_AUTH_TYPE)) == NULL){
434 item = pairmake("Auth-Type", cache_data.auth_type, T_OP_SET);
435 pairadd(&request->config_items, item);
438 strcmp(item->vp_strvalue, cache_data.auth_type);
439 item->length = strlen(cache_data.auth_type);
442 if (data->post_auth){
443 DEBUG("rlm_caching: Adding Post-Auth-Type '%s'",data->post_auth);
445 if ((item = pairfind(request->config_items, PW_POST_AUTH_TYPE)) == NULL){
446 item = pairmake("Post-Auth-Type", data->post_auth, T_OP_SET);
447 pairadd(&request->config_items, item);
450 strcmp(item->vp_strvalue, data->post_auth);
451 item->length = strlen(data->post_auth);
454 item = pairmake("Cache-No-Caching", "YES", T_OP_EQ);
455 pairadd(&request->packet->vps, item);
457 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
458 data->cache_queries,data->cache_hits,hit_ratio);
461 return RLM_MODULE_OK;
464 DEBUG("rlm_caching: Could not find the requested key in the database.");
465 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
466 data->cache_queries,data->cache_hits,hit_ratio);
470 return RLM_MODULE_NOOP;
473 static int caching_detach(void *instance)
475 rlm_caching_t *data = (rlm_caching_t *) instance;
478 gdbm_close(data->gdbm);
479 pthread_mutex_destroy(&data->mutex);
486 * The module name should be the only globally exported symbol.
487 * That is, everything else should be 'static'.
489 * If the module needs to temporarily modify it's instantiation
490 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
491 * The server will then take care of ensuring that the module
492 * is single-threaded.
494 module_t rlm_caching = {
497 RLM_TYPE_THREAD_SAFE, /* type */
498 caching_instantiate, /* instantiation */
499 caching_detach, /* detach */
501 NULL, /* authentication */
502 caching_authorize, /* authorization */
503 NULL, /* preaccounting */
504 NULL, /* accounting */
505 NULL, /* checksimul */
506 NULL, /* pre-proxy */
507 NULL, /* post-proxy */
508 caching_postauth /* post-auth */