Massively cleaned up #include's, so they're in a consistent
[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 <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
30
31 #include <ctype.h>
32
33 #include "config.h"
34
35 #include <gdbm.h>
36 #include <time.h>
37
38 #ifdef NEEDS_GDBM_SYNC
39 #       define GDBM_SYNCOPT GDBM_SYNC
40 #else
41 #       define GDBM_SYNCOPT 0
42 #endif
43
44 #ifdef GDBM_NOLOCK
45 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
46 #else
47 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
48 #endif
49
50 #ifndef HAVE_GDBM_FDESC
51 #define gdbm_fdesc(foo) (-1)
52 #endif
53
54 #define UNIQUEID_MAX_LEN 32
55
56 /*
57  *      Define a structure for our module configuration.
58  *
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.
62  */
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 */
75 #ifdef HAVE_PTHREAD_H
76         pthread_mutex_t mutex;  /* A mutex to lock the gdbm file for only one reader/writer */
77 #endif
78 } rlm_caching_t;
79
80 #define MAX_RECORD_LEN 750
81 #define MAX_AUTH_TYPE 32
82
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)
87
88 typedef struct rlm_caching_data {
89         time_t creation;
90         char data[MAX_RECORD_LEN];
91         char auth_type[MAX_AUTH_TYPE];
92         int len;
93 } rlm_caching_data;
94
95 #ifndef HAVE_PTHREAD_H
96 /*
97  *      This is a lot simpler than putting ifdef's around
98  *      every use of the pthread functions.
99  */
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)
104 #endif
105
106 /*
107  *      A mapping of configuration file names to internal variables.
108  *
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
113  *      buffer over-flows.
114  */
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 }
124 };
125
126 static int caching_detach(void *instance);
127
128 static int find_ttl(char *ttl)
129 {
130         unsigned len = 0;
131         char last = 's';
132
133         if (isdigit((int) ttl[0])){
134                 len = strlen(ttl);
135                 if (len == 0)
136                         return -1;
137                 last = ttl[len - 1];
138                 if (!isalpha((int) last))
139                         last = 's';
140                 len = atoi(ttl);
141                 DEBUG("rlm_caching::find_ttl: num=%d, last=%c",len,last);
142         }
143         switch (last){
144                 case 's':
145                 default:
146                         break;
147                 case 'm':
148                         len *= 60;
149                         break;
150                 case 'h':
151                         len *= 3600;
152                         break;
153                 case 'd':
154                         len *= 86400;
155                         break;
156                 case 'w':
157                         len *= 604800;
158                         break;
159         }
160
161         DEBUG("rlm_caching::find_ttl: Returning '%d'",len);
162
163         return len;
164 }
165
166 /*
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.
171  *
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.
175  */
176 static int caching_instantiate(CONF_SECTION *conf, void **instance)
177 {
178         rlm_caching_t *data;
179         int cache_size;
180
181         /*
182          *      Set up a storage area for instance data
183          */
184         data = rad_malloc(sizeof(*data));
185         if (!data) {
186                 radlog(L_ERR, "rlm_caching: rad_malloc() failed.");
187                 return -1;
188         }
189         memset(data, 0, sizeof(*data));
190
191         /*
192          *      If the configuration parameters can't be parsed, then
193          *      fail.
194          */
195         if (cf_section_parse(conf, data, module_config) < 0) {
196                 free(data);
197                 return -1;
198         }
199         cache_size = data->cache_size;
200
201         /*
202          *      Discover the attribute number of the key.
203          */
204         if (data->key == NULL) {
205                 radlog(L_ERR, "rlm_caching: 'key' must be set.");
206                 caching_detach(data);
207                 return -1;
208         }
209         if (data->cache_ttl_str == NULL) {
210                 radlog(L_ERR, "rlm_caching: 'cache-ttl' must be set.");
211                 caching_detach(data);
212                 return -1;
213         }
214         else {
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);
219                         return -1;
220                 }
221         }
222
223         if (data->filename == NULL) {
224                 radlog(L_ERR, "rlm_caching: 'filename' must be set.");
225                 caching_detach(data);
226                 return -1;
227         }
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);
234                 return -1;
235         }
236         if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
237                 radlog(L_ERR, "rlm_caching: Failed to set cache size");
238
239         /*
240          * Init the mutex
241          */
242         pthread_mutex_init(&data->mutex, NULL);
243
244         *instance = data;
245
246         return 0;
247 }
248
249 /*
250  *      Cache the reply items and the Auth-Type
251  */
252 static int caching_postauth(void *instance, REQUEST *request)
253 {
254         rlm_caching_t *data = (rlm_caching_t *)instance;
255         char key[MAX_STRING_LEN];
256         datum key_datum;
257         datum data_datum;
258         VALUE_PAIR *reply_vp;
259         VALUE_PAIR *auth_type;
260         rlm_caching_data cache_data;
261         int count = 0;
262         int ret = 0;    
263         int size = 0;
264         int rcode = 0;
265
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;
269         }
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;
275                 }
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;
279                 }
280         }
281         else{
282                 DEBUG("rlm_caching: No Auth-Type found. Returning NOOP");
283                 return RLM_MODULE_NOOP;
284         }
285
286         reply_vp = request->reply->vps;
287
288         if (reply_vp == NULL) {
289                 DEBUG("rlm_caching: The Request does not contain any reply attributes");
290                 return RLM_MODULE_NOOP;
291         }
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;
295         }
296
297         memset(&cache_data,0,sizeof(rlm_caching_data));
298
299         cache_data.creation = time(NULL);
300         strcpy(cache_data.auth_type,auth_type->vp_strvalue);
301
302         size = MAX_RECORD_LEN;
303
304         while(reply_vp) {
305                 if (size <= 1){
306                         DEBUG("rlm_caching: Not enough space.");
307                         return RLM_MODULE_NOOP;
308                 }
309                 ret = vp_prints(cache_data.data + count,size,reply_vp);
310                 if (ret == 0) {
311                         DEBUG("rlm_caching: Record is too large, will not store it.");
312                         return RLM_MODULE_NOOP;
313                 }
314                 count += (ret + 1);
315                 size -= (ret + 1);
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;
319         }
320         cache_data.len = count;
321
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);
325
326         key_datum.dptr = (char *) key;
327         key_datum.dsize = strlen(key);
328
329         pthread_mutex_lock(&data->mutex);
330         rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
331         pthread_mutex_unlock(&data->mutex);
332         if (rcode < 0) {
333                 radlog(L_ERR, "rlm_caching: Failed storing data to %s: %s",
334                                 data->filename, gdbm_strerror(gdbm_errno));
335                 return RLM_MODULE_FAIL;
336         }
337         DEBUG("rlm_caching: New value stored successfully.");
338
339         return RLM_MODULE_OK;
340 }
341
342 /*
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.
347  */
348 static int caching_authorize(void *instance, REQUEST *request)
349 {
350         rlm_caching_t *data = (rlm_caching_t *) instance;
351         char key[MAX_STRING_LEN];
352         datum key_datum;
353         datum data_datum;
354         rlm_caching_data cache_data;
355         VALUE_PAIR *reply_item;
356         VALUE_PAIR *item;
357         char *tmp;
358         int len = 0;
359         int delete_cache = 0;
360         float hit_ratio = 0.0;
361
362         /* quiet the compiler */
363         instance = instance;
364         request = request;
365
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;
369         }
370         if (pairfind(request->packet->vps, PW_CACHE_DELETE_CACHE) != NULL){
371                 DEBUG("rlm_caching: Found Cache-Delete-Cache. Will delete record if found");
372                 delete_cache = 1;
373         }
374
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;
378         }
379
380         key_datum.dptr = key;
381         key_datum.dsize = strlen(key);
382
383
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.");
391                 data->cache_hits++;
392                 hit_ratio = (float)data->cache_hits / data->cache_queries;
393                 hit_ratio *= 100.0;
394                 memcpy(&cache_data, data_datum.dptr, sizeof(rlm_caching_data));
395                 free(data_datum.dptr);
396
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);
401                         show_hit_ratio;
402                         delete_cache = 1;
403                 }
404                 if (delete_cache){
405                         DEBUG("rlm_caching: Deleting record");
406                         
407                         pthread_mutex_lock(&data->mutex);
408                         gdbm_delete(data->gdbm, key_datum);
409                         pthread_mutex_unlock(&data->mutex);
410
411                         return RLM_MODULE_NOOP;
412                 }
413                 tmp = cache_data.data;
414                 if (tmp){
415                         pairfree(&request->reply->vps);
416                         while(tmp && len < cache_data.len){
417                                 reply_item = NULL;
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;
424                         }
425                 }
426                 else{
427                         DEBUG("rlm_caching: No reply items found. Returning NOOP");
428                         return RLM_MODULE_NOOP;
429                 }               
430                 if (cache_data.auth_type){
431                         DEBUG("rlm_caching: Adding Auth-Type '%s'",cache_data.auth_type);
432
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);
436                         }
437                         else{
438                                 strcmp(item->vp_strvalue, cache_data.auth_type);
439                                 item->length = strlen(cache_data.auth_type);
440                         }
441                 }
442                 if (data->post_auth){
443                         DEBUG("rlm_caching: Adding Post-Auth-Type '%s'",data->post_auth);
444
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);
448                         }
449                         else{
450                                 strcmp(item->vp_strvalue, data->post_auth);
451                                 item->length = strlen(data->post_auth);
452                         }
453                 }
454                 item = pairmake("Cache-No-Caching", "YES", T_OP_EQ);
455                 pairadd(&request->packet->vps, item);
456
457                 DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
458                         data->cache_queries,data->cache_hits,hit_ratio);
459                 show_hit_ratio;
460
461                 return RLM_MODULE_OK;
462         }
463         else{
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);
467                 show_hit_ratio;
468         }
469
470         return RLM_MODULE_NOOP;
471 }
472
473 static int caching_detach(void *instance)
474 {
475         rlm_caching_t *data = (rlm_caching_t *) instance;
476
477         if (data->gdbm)
478                 gdbm_close(data->gdbm);
479         pthread_mutex_destroy(&data->mutex);
480
481         free(instance);
482         return 0;
483 }
484
485 /*
486  *      The module name should be the only globally exported symbol.
487  *      That is, everything else should be 'static'.
488  *
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.
493  */
494 module_t rlm_caching = {
495         RLM_MODULE_INIT,
496         "Caching",
497         RLM_TYPE_THREAD_SAFE,           /* type */
498         caching_instantiate,            /* instantiation */
499         caching_detach,                 /* detach */
500         {
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 */
509         },
510 };