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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2001 The FreeRADIUS server project
21 * Copyright 2001 Alan DeKok <aland@ox.org>
22 * Copyright 2001-3 Kostas Kalevras <kkalev@noc.ntua.gr>
40 #ifdef NEEDS_GDBM_SYNC
41 # define GDBM_SYNCOPT GDBM_SYNC
43 # define GDBM_SYNCOPT 0
47 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
49 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
52 #ifndef HAVE_GDBM_FDESC
53 #define gdbm_fdesc(foo) (-1)
56 #define UNIQUEID_MAX_LEN 32
58 static const char rcsid[] = "$Id$";
61 * Define a structure for our module configuration.
63 * These variables do not need to be in a structure, but it's
64 * a lot cleaner to do so, and a pointer to the structure can
65 * be used as the instance handle.
67 typedef struct rlm_caching_t {
68 char *filename; /* name of the database file */
69 char *key; /* An xlated string to use as key for the records */
70 char *post_auth; /* If set and we find a cached entry, set Post-Auth to this value */
71 char *cache_ttl_str; /* The string represantation of the TTL */
72 int cache_ttl; /* The cache TTL */
73 int hit_ratio; /* Show cache hit ratio every so many queries */
74 int cache_rejects; /* Do we also cache rejects? */
75 int cache_size; /* The cache size to pass to GDBM */
76 uint32_t cache_queries; /* The number of cache requests */
77 uint32_t cache_hits; /* The number of cache hits */
78 GDBM_FILE gdbm; /* The gdbm file handle */
80 pthread_mutex_t mutex; /* A mutex to lock the gdbm file for only one reader/writer */
84 #define MAX_RECORD_LEN 750
85 #define MAX_AUTH_TYPE 32
87 #define show_hit_ratio \
88 if (data->hit_ratio && (data->cache_queries % data->hit_ratio) == 0) \
89 radlog(L_INFO, "rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%", \
90 data->cache_queries,data->cache_hits,hit_ratio)
92 typedef struct rlm_caching_data {
94 char data[MAX_RECORD_LEN];
95 char auth_type[MAX_AUTH_TYPE];
99 #ifndef HAVE_PTHREAD_H
101 * This is a lot simpler than putting ifdef's around
102 * every use of the pthread functions.
104 #define pthread_mutex_lock(a)
105 #define pthread_mutex_unlock(a)
106 #define pthread_mutex_init(a,b)
107 #define pthread_mutex_destroy(a)
111 * A mapping of configuration file names to internal variables.
113 * Note that the string is dynamically allocated, so it MUST
114 * be freed. When the configuration file parse re-reads the string,
115 * it free's the old one, and strdup's the new one, placing the pointer
116 * to the strdup'd string into 'config.string'. This gets around
119 static const CONF_PARSER module_config[] = {
120 { "filename", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,filename), NULL, NULL },
121 { "key", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,key), NULL, "%{Acct-Unique-Session-Id}" },
122 { "post-auth", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,post_auth), NULL, NULL },
123 { "cache-ttl", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,cache_ttl_str), NULL, "1d" },
124 { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_caching_t,cache_size), NULL, "1000" },
125 { "hit-ratio", PW_TYPE_INTEGER, offsetof(rlm_caching_t,hit_ratio), NULL, "0" },
126 { "cache-rejects", PW_TYPE_BOOLEAN, offsetof(rlm_caching_t,cache_rejects), NULL, "yes" },
127 { NULL, -1, 0, NULL, NULL }
130 static int caching_detach(void *instance);
132 static int find_ttl(char *ttl)
137 if (isdigit((int) ttl[0])){
142 if (!isalpha((int) last))
145 DEBUG("rlm_caching::find_ttl: num=%d, last=%c",len,last);
165 DEBUG("rlm_caching::find_ttl: Returning '%d'",len);
171 * Do any per-module initialization that is separate to each
172 * configured instance of the module. e.g. set up connections
173 * to external databases, read configuration files, set up
174 * dictionary entries, etc.
176 * If configuration information is given in the config section
177 * that must be referenced in later calls, store a handle to it
178 * in *instance otherwise put a null pointer there.
180 static int caching_instantiate(CONF_SECTION *conf, void **instance)
186 * Set up a storage area for instance data
188 data = rad_malloc(sizeof(*data));
190 radlog(L_ERR, "rlm_caching: rad_malloc() failed.");
193 memset(data, 0, sizeof(*data));
196 * If the configuration parameters can't be parsed, then
199 if (cf_section_parse(conf, data, module_config) < 0) {
203 cache_size = data->cache_size;
206 * Discover the attribute number of the key.
208 if (data->key == NULL) {
209 radlog(L_ERR, "rlm_caching: 'key' must be set.");
210 caching_detach(data);
213 if (data->cache_ttl_str == NULL) {
214 radlog(L_ERR, "rlm_caching: 'cache-ttl' must be set.");
215 caching_detach(data);
219 data->cache_ttl = find_ttl(data->cache_ttl_str);
220 if (data->cache_ttl == 0) {
221 radlog(L_ERR, "rlm_caching: 'cache-ttl' is invalid.");
222 caching_detach(data);
227 if (data->filename == NULL) {
228 radlog(L_ERR, "rlm_caching: 'filename' must be set.");
229 caching_detach(data);
232 data->gdbm = gdbm_open(data->filename, sizeof(int),
233 GDBM_WRCREAT | GDBM_COUNTER_OPTS, 0600, NULL);
234 if (data->gdbm == NULL) {
235 radlog(L_ERR, "rlm_caching: Failed to open file %s: %s",
236 data->filename, strerror(errno));
237 caching_detach(data);
240 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
241 radlog(L_ERR, "rlm_caching: Failed to set cache size");
246 pthread_mutex_init(&data->mutex, NULL);
254 * Cache the reply items and the Auth-Type
256 static int caching_postauth(void *instance, REQUEST *request)
258 rlm_caching_t *data = (rlm_caching_t *)instance;
259 char key[MAX_STRING_LEN];
262 VALUE_PAIR *reply_vp;
263 VALUE_PAIR *auth_type;
264 rlm_caching_data cache_data;
270 if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
271 DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
272 return RLM_MODULE_NOOP;
274 if ((auth_type = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL){
275 DEBUG("rlm_caching: Found Auth-Type, value: '%s'",auth_type->strvalue);
276 if (strcmp(auth_type->strvalue,"Reject") == 0 && data->cache_rejects == 0){
277 DEBUG("rlm_caching: No caching of Rejects. Returning NOOP");
278 return RLM_MODULE_NOOP;
280 if (strlen(auth_type->strvalue) > MAX_AUTH_TYPE - 1){
281 DEBUG("rlm_caching: Auth-Type value too large");
282 return RLM_MODULE_NOOP;
286 DEBUG("rlm_caching: No Auth-Type found. Returning NOOP");
287 return RLM_MODULE_NOOP;
290 reply_vp = request->reply->vps;
292 if (reply_vp == NULL) {
293 DEBUG("rlm_caching: The Request does not contain any reply attributes");
294 return RLM_MODULE_NOOP;
296 if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
297 radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
298 return RLM_MODULE_FAIL;
301 memset(&cache_data,0,sizeof(rlm_caching_data));
303 cache_data.creation = time(NULL);
304 strcpy(cache_data.auth_type,auth_type->strvalue);
306 size = MAX_RECORD_LEN;
310 DEBUG("rlm_caching: Not enough space.");
311 return RLM_MODULE_NOOP;
313 ret = vp_prints(cache_data.data + count,size,reply_vp);
315 DEBUG("rlm_caching: Record is too large, will not store it.");
316 return RLM_MODULE_NOOP;
320 DEBUG("rlm_caching: VP=%s,VALUE=%s,length=%d,cache record length=%d, space left=%d",
321 reply_vp->name,reply_vp->strvalue,ret,count,size);
322 reply_vp = reply_vp->next;
324 cache_data.len = count;
326 DEBUG("rlm_caching: Storing cache for Key='%s'",key);
327 data_datum.dptr = (rlm_caching_data *) &cache_data;
328 data_datum.dsize = sizeof(rlm_caching_data);
330 key_datum.dptr = (char *) key;
331 key_datum.dsize = strlen(key);
333 pthread_mutex_lock(&data->mutex);
334 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
335 pthread_mutex_unlock(&data->mutex);
337 radlog(L_ERR, "rlm_caching: Failed storing data to %s: %s",
338 data->filename, gdbm_strerror(gdbm_errno));
339 return RLM_MODULE_FAIL;
341 DEBUG("rlm_caching: New value stored successfully.");
343 return RLM_MODULE_OK;
347 * Find the named user in this modules database. Create the set
348 * of attribute-value pairs to check and reply with for this user
349 * from the database. The authentication code only needs to check
350 * the password, the rest is done here.
352 static int caching_authorize(void *instance, REQUEST *request)
354 rlm_caching_t *data = (rlm_caching_t *) instance;
355 char key[MAX_STRING_LEN];
358 rlm_caching_data cache_data;
359 VALUE_PAIR *reply_item;
363 int delete_cache = 0;
364 float hit_ratio = 0.0;
366 /* quiet the compiler */
370 if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
371 DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
372 return RLM_MODULE_NOOP;
374 if (pairfind(request->packet->vps, PW_CACHE_DELETE_CACHE) != NULL){
375 DEBUG("rlm_caching: Found Cache-Delete-Cache. Will delete record if found");
379 if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
380 radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
381 return RLM_MODULE_FAIL;
384 key_datum.dptr = key;
385 key_datum.dsize = strlen(key);
388 DEBUG("rlm_caching: Searching the database for key '%s'",key);
389 pthread_mutex_lock(&data->mutex);
390 data_datum = gdbm_fetch(data->gdbm, key_datum);
391 pthread_mutex_unlock(&data->mutex);
392 data->cache_queries++;
393 if (data_datum.dptr != NULL){
394 DEBUG("rlm_caching: Key Found.");
396 hit_ratio = (float)data->cache_hits / data->cache_queries;
398 memcpy(&cache_data, data_datum.dptr, sizeof(rlm_caching_data));
399 free(data_datum.dptr);
401 if (delete_cache == 0 && cache_data.creation + data->cache_ttl <= time(NULL)){
402 DEBUG("rlm_cacing: Cache entry has expired");
403 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
404 data->cache_queries,data->cache_hits,hit_ratio);
409 DEBUG("rlm_caching: Deleting record");
411 pthread_mutex_lock(&data->mutex);
412 gdbm_delete(data->gdbm, key_datum);
413 pthread_mutex_unlock(&data->mutex);
415 return RLM_MODULE_NOOP;
417 tmp = cache_data.data;
419 pairfree(&request->reply->vps);
420 while(tmp && len < cache_data.len){
422 if (userparse(tmp, &reply_item) > 0 && reply_item != NULL)
423 pairadd(&request->reply->vps, reply_item);
424 len += (strlen(tmp) + 1);
425 DEBUG("rlm_caching: VP='%s',VALUE='%s',lenth='%d',cache record length='%d'",
426 reply_item->name,reply_item->strvalue,reply_item->length,len);
427 tmp = cache_data.data + len;
431 DEBUG("rlm_caching: No reply items found. Returning NOOP");
432 return RLM_MODULE_NOOP;
434 if (cache_data.auth_type){
435 DEBUG("rlm_caching: Adding Auth-Type '%s'",cache_data.auth_type);
437 if ((item = pairfind(request->config_items, PW_AUTH_TYPE)) == NULL){
438 item = pairmake("Auth-Type", cache_data.auth_type, T_OP_SET);
439 pairadd(&request->config_items, item);
442 strcmp(item->strvalue, cache_data.auth_type);
443 item->length = strlen(cache_data.auth_type);
446 if (data->post_auth){
447 DEBUG("rlm_caching: Adding Post-Auth-Type '%s'",data->post_auth);
449 if ((item = pairfind(request->config_items, PW_POST_AUTH_TYPE)) == NULL){
450 item = pairmake("Post-Auth-Type", data->post_auth, T_OP_SET);
451 pairadd(&request->config_items, item);
454 strcmp(item->strvalue, data->post_auth);
455 item->length = strlen(data->post_auth);
458 item = pairmake("Cache-No-Caching", "YES", T_OP_EQ);
459 pairadd(&request->packet->vps, item);
461 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
462 data->cache_queries,data->cache_hits,hit_ratio);
465 return RLM_MODULE_OK;
468 DEBUG("rlm_caching: Could not find the requested key in the database.");
469 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
470 data->cache_queries,data->cache_hits,hit_ratio);
474 return RLM_MODULE_NOOP;
477 static int caching_detach(void *instance)
479 rlm_caching_t *data = (rlm_caching_t *) instance;
482 gdbm_close(data->gdbm);
483 free(data->filename);
485 free(data->post_auth);
486 free(data->cache_ttl_str);
487 pthread_mutex_destroy(&data->mutex);
494 * The module name should be the only globally exported symbol.
495 * That is, everything else should be 'static'.
497 * If the module needs to temporarily modify it's instantiation
498 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
499 * The server will then take care of ensuring that the module
500 * is single-threaded.
502 module_t rlm_caching = {
505 RLM_TYPE_THREAD_SAFE, /* type */
506 caching_instantiate, /* instantiation */
507 caching_detach, /* detach */
509 NULL, /* authentication */
510 caching_authorize, /* authorization */
511 NULL, /* preaccounting */
512 NULL, /* accounting */
513 NULL, /* checksimul */
514 NULL, /* pre-proxy */
515 NULL, /* post-proxy */
516 caching_postauth /* post-auth */