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