Removed all free(inst->foo) where "foo" comes from parsing
[freeradius.git] / src / modules / rlm_caching / rlm_caching.c
1 /*
2  * rlm_caching.c
3  *
4  * Version:  $Id$
5  *
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.
10  *
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.
15  *
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
19  *
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>
23  */
24
25 #include <freeradius-devel/ident.h>
26 RCSID("$Id$")
27
28 #include "config.h"
29 #include <freeradius-devel/autoconf.h>
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35
36 #include <freeradius-devel/radiusd.h>
37 #include <freeradius-devel/modules.h>
38 #include <freeradius-devel/conffile.h>
39
40 #include <gdbm.h>
41 #include <time.h>
42
43 #ifdef NEEDS_GDBM_SYNC
44 #       define GDBM_SYNCOPT GDBM_SYNC
45 #else
46 #       define GDBM_SYNCOPT 0
47 #endif
48
49 #ifdef GDBM_NOLOCK
50 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
51 #else
52 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
53 #endif
54
55 #ifndef HAVE_GDBM_FDESC
56 #define gdbm_fdesc(foo) (-1)
57 #endif
58
59 #define UNIQUEID_MAX_LEN 32
60
61 /*
62  *      Define a structure for our module configuration.
63  *
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.
67  */
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 */
80 #ifdef HAVE_PTHREAD_H
81         pthread_mutex_t mutex;  /* A mutex to lock the gdbm file for only one reader/writer */
82 #endif
83 } rlm_caching_t;
84
85 #define MAX_RECORD_LEN 750
86 #define MAX_AUTH_TYPE 32
87
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)
92
93 typedef struct rlm_caching_data {
94         time_t creation;
95         char data[MAX_RECORD_LEN];
96         char auth_type[MAX_AUTH_TYPE];
97         int len;
98 } rlm_caching_data;
99
100 #ifndef HAVE_PTHREAD_H
101 /*
102  *      This is a lot simpler than putting ifdef's around
103  *      every use of the pthread functions.
104  */
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)
109 #endif
110
111 /*
112  *      A mapping of configuration file names to internal variables.
113  *
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
118  *      buffer over-flows.
119  */
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 }
129 };
130
131 static int caching_detach(void *instance);
132
133 static int find_ttl(char *ttl)
134 {
135         unsigned len = 0;
136         char last = 's';
137
138         if (isdigit((int) ttl[0])){
139                 len = strlen(ttl);
140                 if (len == 0)
141                         return -1;
142                 last = ttl[len - 1];
143                 if (!isalpha((int) last))
144                         last = 's';
145                 len = atoi(ttl);
146                 DEBUG("rlm_caching::find_ttl: num=%d, last=%c",len,last);
147         }
148         switch (last){
149                 case 's':
150                 default:
151                         break;
152                 case 'm':
153                         len *= 60;
154                         break;
155                 case 'h':
156                         len *= 3600;
157                         break;
158                 case 'd':
159                         len *= 86400;
160                         break;
161                 case 'w':
162                         len *= 604800;
163                         break;
164         }
165
166         DEBUG("rlm_caching::find_ttl: Returning '%d'",len);
167
168         return len;
169 }
170
171 /*
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.
176  *
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.
180  */
181 static int caching_instantiate(CONF_SECTION *conf, void **instance)
182 {
183         rlm_caching_t *data;
184         int cache_size;
185
186         /*
187          *      Set up a storage area for instance data
188          */
189         data = rad_malloc(sizeof(*data));
190         if (!data) {
191                 radlog(L_ERR, "rlm_caching: rad_malloc() failed.");
192                 return -1;
193         }
194         memset(data, 0, sizeof(*data));
195
196         /*
197          *      If the configuration parameters can't be parsed, then
198          *      fail.
199          */
200         if (cf_section_parse(conf, data, module_config) < 0) {
201                 free(data);
202                 return -1;
203         }
204         cache_size = data->cache_size;
205
206         /*
207          *      Discover the attribute number of the key.
208          */
209         if (data->key == NULL) {
210                 radlog(L_ERR, "rlm_caching: 'key' must be set.");
211                 caching_detach(data);
212                 return -1;
213         }
214         if (data->cache_ttl_str == NULL) {
215                 radlog(L_ERR, "rlm_caching: 'cache-ttl' must be set.");
216                 caching_detach(data);
217                 return -1;
218         }
219         else {
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);
224                         return -1;
225                 }
226         }
227
228         if (data->filename == NULL) {
229                 radlog(L_ERR, "rlm_caching: 'filename' must be set.");
230                 caching_detach(data);
231                 return -1;
232         }
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);
239                 return -1;
240         }
241         if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
242                 radlog(L_ERR, "rlm_caching: Failed to set cache size");
243
244         /*
245          * Init the mutex
246          */
247         pthread_mutex_init(&data->mutex, NULL);
248
249         *instance = data;
250
251         return 0;
252 }
253
254 /*
255  *      Cache the reply items and the Auth-Type
256  */
257 static int caching_postauth(void *instance, REQUEST *request)
258 {
259         rlm_caching_t *data = (rlm_caching_t *)instance;
260         char key[MAX_STRING_LEN];
261         datum key_datum;
262         datum data_datum;
263         VALUE_PAIR *reply_vp;
264         VALUE_PAIR *auth_type;
265         rlm_caching_data cache_data;
266         int count = 0;
267         int ret = 0;    
268         int size = 0;
269         int rcode = 0;
270
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;
274         }
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;
280                 }
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;
284                 }
285         }
286         else{
287                 DEBUG("rlm_caching: No Auth-Type found. Returning NOOP");
288                 return RLM_MODULE_NOOP;
289         }
290
291         reply_vp = request->reply->vps;
292
293         if (reply_vp == NULL) {
294                 DEBUG("rlm_caching: The Request does not contain any reply attributes");
295                 return RLM_MODULE_NOOP;
296         }
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;
300         }
301
302         memset(&cache_data,0,sizeof(rlm_caching_data));
303
304         cache_data.creation = time(NULL);
305         strcpy(cache_data.auth_type,auth_type->vp_strvalue);
306
307         size = MAX_RECORD_LEN;
308
309         while(reply_vp) {
310                 if (size <= 1){
311                         DEBUG("rlm_caching: Not enough space.");
312                         return RLM_MODULE_NOOP;
313                 }
314                 ret = vp_prints(cache_data.data + count,size,reply_vp);
315                 if (ret == 0) {
316                         DEBUG("rlm_caching: Record is too large, will not store it.");
317                         return RLM_MODULE_NOOP;
318                 }
319                 count += (ret + 1);
320                 size -= (ret + 1);
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;
324         }
325         cache_data.len = count;
326
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);
330
331         key_datum.dptr = (char *) key;
332         key_datum.dsize = strlen(key);
333
334         pthread_mutex_lock(&data->mutex);
335         rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
336         pthread_mutex_unlock(&data->mutex);
337         if (rcode < 0) {
338                 radlog(L_ERR, "rlm_caching: Failed storing data to %s: %s",
339                                 data->filename, gdbm_strerror(gdbm_errno));
340                 return RLM_MODULE_FAIL;
341         }
342         DEBUG("rlm_caching: New value stored successfully.");
343
344         return RLM_MODULE_OK;
345 }
346
347 /*
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.
352  */
353 static int caching_authorize(void *instance, REQUEST *request)
354 {
355         rlm_caching_t *data = (rlm_caching_t *) instance;
356         char key[MAX_STRING_LEN];
357         datum key_datum;
358         datum data_datum;
359         rlm_caching_data cache_data;
360         VALUE_PAIR *reply_item;
361         VALUE_PAIR *item;
362         char *tmp;
363         int len = 0;
364         int delete_cache = 0;
365         float hit_ratio = 0.0;
366
367         /* quiet the compiler */
368         instance = instance;
369         request = request;
370
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;
374         }
375         if (pairfind(request->packet->vps, PW_CACHE_DELETE_CACHE) != NULL){
376                 DEBUG("rlm_caching: Found Cache-Delete-Cache. Will delete record if found");
377                 delete_cache = 1;
378         }
379
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;
383         }
384
385         key_datum.dptr = key;
386         key_datum.dsize = strlen(key);
387
388
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.");
396                 data->cache_hits++;
397                 hit_ratio = (float)data->cache_hits / data->cache_queries;
398                 hit_ratio *= 100.0;
399                 memcpy(&cache_data, data_datum.dptr, sizeof(rlm_caching_data));
400                 free(data_datum.dptr);
401
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);
406                         show_hit_ratio;
407                         delete_cache = 1;
408                 }
409                 if (delete_cache){
410                         DEBUG("rlm_caching: Deleting record");
411                         
412                         pthread_mutex_lock(&data->mutex);
413                         gdbm_delete(data->gdbm, key_datum);
414                         pthread_mutex_unlock(&data->mutex);
415
416                         return RLM_MODULE_NOOP;
417                 }
418                 tmp = cache_data.data;
419                 if (tmp){
420                         pairfree(&request->reply->vps);
421                         while(tmp && len < cache_data.len){
422                                 reply_item = NULL;
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;
429                         }
430                 }
431                 else{
432                         DEBUG("rlm_caching: No reply items found. Returning NOOP");
433                         return RLM_MODULE_NOOP;
434                 }               
435                 if (cache_data.auth_type){
436                         DEBUG("rlm_caching: Adding Auth-Type '%s'",cache_data.auth_type);
437
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);
441                         }
442                         else{
443                                 strcmp(item->vp_strvalue, cache_data.auth_type);
444                                 item->length = strlen(cache_data.auth_type);
445                         }
446                 }
447                 if (data->post_auth){
448                         DEBUG("rlm_caching: Adding Post-Auth-Type '%s'",data->post_auth);
449
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);
453                         }
454                         else{
455                                 strcmp(item->vp_strvalue, data->post_auth);
456                                 item->length = strlen(data->post_auth);
457                         }
458                 }
459                 item = pairmake("Cache-No-Caching", "YES", T_OP_EQ);
460                 pairadd(&request->packet->vps, item);
461
462                 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
463                         data->cache_queries,data->cache_hits,hit_ratio);
464                 show_hit_ratio;
465
466                 return RLM_MODULE_OK;
467         }
468         else{
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);
472                 show_hit_ratio;
473         }
474
475         return RLM_MODULE_NOOP;
476 }
477
478 static int caching_detach(void *instance)
479 {
480         rlm_caching_t *data = (rlm_caching_t *) instance;
481
482         if (data->gdbm)
483                 gdbm_close(data->gdbm);
484         pthread_mutex_destroy(&data->mutex);
485
486         free(instance);
487         return 0;
488 }
489
490 /*
491  *      The module name should be the only globally exported symbol.
492  *      That is, everything else should be 'static'.
493  *
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.
498  */
499 module_t rlm_caching = {
500         RLM_MODULE_INIT,
501         "Caching",
502         RLM_TYPE_THREAD_SAFE,           /* type */
503         caching_instantiate,            /* instantiation */
504         caching_detach,                 /* detach */
505         {
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 */
514         },
515 };