Require that the modules call talloc for their instance handle.
[freeradius.git] / src / modules / rlm_counter / rlm_counter.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15  
16 /**
17  * $Id$
18  * @file rlm_counter.c
19  * @brief Provides a packet counter to track data usage and other values.
20  * 
21  * @copyright 2001,2006  The FreeRADIUS server project
22  * @copyright 2001  Alan DeKok <aland@ox.org>
23  * @copyright 2001-2003  Kostas Kalevras <kkalev@noc.ntua.gr>
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
37 #ifdef NEEDS_GDBM_SYNC
38 #       define GDBM_SYNCOPT GDBM_SYNC
39 #else
40 #       define GDBM_SYNCOPT 0
41 #endif
42
43 #ifdef GDBM_NOLOCK
44 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
45 #else
46 #define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
47 #endif
48
49 #ifndef HAVE_GDBM_FDESC
50 #define gdbm_fdesc(foo) (-1)
51 #endif
52
53 #define UNIQUEID_MAX_LEN 32
54
55 /*
56  *      Define a structure for our module configuration.
57  *
58  *      These variables do not need to be in a structure, but it's
59  *      a lot cleaner to do so, and a pointer to the structure can
60  *      be used as the instance handle.
61  */
62 typedef struct rlm_counter_t {
63         const char *filename;           /* name of the database file */
64         const char *reset;              /* daily, weekly, monthly, never or user defined */
65         const char *key_name;           /* User-Name */
66         const char *count_attribute;    /* Acct-Session-Time */
67         const char *counter_name;       /* Daily-Session-Time */
68         const char *check_name;         /* Daily-Max-Session */
69         const char *reply_name;         /* Session-Timeout */
70         const char *service_type;       /* Service-Type to search for */
71         
72         int cache_size;
73         uint32_t service_val;
74         
75         int key_attr;
76         int count_attr;
77         int check_attr;
78         int reply_attr;
79         int dict_attr;          /* attribute number for the counter. */
80         
81         time_t reset_time;      /* The time of the next reset. */
82         time_t last_reset;      /* The time of the last reset. */
83
84         GDBM_FILE gdbm;         /* The gdbm file handle */
85 #ifdef HAVE_PTHREAD_H
86         pthread_mutex_t mutex;  /* A mutex to lock the gdbm file for only one reader/writer */
87 #endif
88 } rlm_counter_t;
89
90 #ifndef HAVE_PTHREAD_H
91 /*
92  *      This is a lot simpler than putting ifdef's around
93  *      every use of the pthread functions.
94  */
95 #define pthread_mutex_lock(a)
96 #define pthread_mutex_unlock(a)
97 #define pthread_mutex_init(a,b)
98 #define pthread_mutex_destroy(a)
99 #endif
100
101 typedef struct rad_counter {
102         unsigned int user_counter;
103         char uniqueid[UNIQUEID_MAX_LEN];
104 } rad_counter;
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_counter_t,filename), NULL, NULL },
117   { "key", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,key_name), NULL, NULL },
118   { "reset", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,reset), NULL,  NULL },
119   { "count-attribute", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,count_attribute), NULL, NULL },
120   { "counter-name", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,counter_name), NULL,  NULL },
121   { "check-name", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,check_name), NULL, NULL },
122   { "reply-name", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,reply_name), NULL, NULL },
123   { "allowed-servicetype", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,service_type),NULL, NULL },
124   { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_counter_t,cache_size), NULL, "1000" },
125   { NULL, -1, 0, NULL, NULL }
126 };
127
128 static int counter_detach(void *instance);
129
130
131 /*
132  *      See if the counter matches.
133  */
134 static int counter_cmp(void *instance,
135                        REQUEST *req UNUSED,
136                        VALUE_PAIR *request, VALUE_PAIR *check,
137                        VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
138 {
139         rlm_counter_t *inst = instance;
140         datum key_datum;
141         datum count_datum;
142         VALUE_PAIR *key_vp;
143         rad_counter counter;
144
145         check_pairs = check_pairs; /* shut the compiler up */
146         reply_pairs = reply_pairs;
147         req = req;
148
149         /*
150          *      Find the key attribute.
151          */
152         key_vp = pairfind(request, inst->key_attr, 0, TAG_ANY);
153         if (key_vp == NULL) {
154                 return RLM_MODULE_NOOP;
155         }
156
157         key_datum.dptr = key_vp->vp_strvalue;
158         key_datum.dsize = key_vp->length;
159
160         count_datum = gdbm_fetch(inst->gdbm, key_datum);
161
162         if (count_datum.dptr == NULL) {
163                 return -1;
164         }
165         memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
166         free(count_datum.dptr);
167
168         return counter.user_counter - check->vp_integer;
169 }
170
171
172 static rlm_rcode_t add_defaults(rlm_counter_t *inst)
173 {
174         datum key_datum;
175         datum time_datum;
176         static const char const *default1 = "DEFAULT1";
177         static const char const *default2 = "DEFAULT2";
178
179         DEBUG2("rlm_counter: add_defaults: Start");
180
181         memcpy(&key_datum.dptr, &default1, sizeof(key_datum.dptr));
182         key_datum.dsize = strlen(key_datum.dptr);
183         time_datum.dptr = (char *) &inst->reset_time;
184         time_datum.dsize = sizeof(time_t);
185
186         if (gdbm_store(inst->gdbm, key_datum, time_datum, GDBM_REPLACE) < 0){
187                 radlog(L_ERR, "rlm_counter: Failed storing data to %s: %s",
188                                 inst->filename, gdbm_strerror(gdbm_errno));
189                 return RLM_MODULE_FAIL;
190         }
191         DEBUG2("rlm_counter: DEFAULT1 set to %u", (unsigned int) inst->reset_time);
192
193         memcpy(&key_datum.dptr, &default2, sizeof(key_datum.dptr));
194         key_datum.dsize = strlen(key_datum.dptr);
195         key_datum.dsize = strlen(default2);
196         time_datum.dptr = (char *) &inst->last_reset;
197         time_datum.dsize = sizeof(time_t);
198
199         if (gdbm_store(inst->gdbm, key_datum, time_datum, GDBM_REPLACE) < 0){
200                 radlog(L_ERR, "rlm_counter: Failed storing data to %s: %s",
201                                 inst->filename, gdbm_strerror(gdbm_errno));
202                 return RLM_MODULE_FAIL;
203         }
204         DEBUG2("rlm_counter: DEFAULT2 set to %u", (unsigned int) inst->last_reset);
205         DEBUG2("rlm_counter: add_defaults: End");
206
207         return RLM_MODULE_OK;
208 }
209
210 static rlm_rcode_t reset_db(rlm_counter_t *inst)
211 {
212         int cache_size = inst->cache_size;
213         rlm_rcode_t rcode;
214
215         DEBUG2("rlm_counter: reset_db: Closing database");
216         gdbm_close(inst->gdbm);
217
218         /*
219          *      Open a completely new database.
220          */
221         inst->gdbm = gdbm_open(inst->filename, sizeof(int),
222                         GDBM_NEWDB | GDBM_COUNTER_OPTS, 0600, NULL);
223         if (inst->gdbm == NULL) {
224                 radlog(L_ERR, "rlm_counter: Failed to open file %s: %s",
225                                 inst->filename, strerror(errno));
226                 return RLM_MODULE_FAIL;
227         }
228         if (gdbm_setopt(inst->gdbm, GDBM_CACHESIZE, &cache_size,
229                         sizeof(cache_size)) == -1) {
230                 radlog(L_ERR, "rlm_counter: Failed to set cache size");
231         }
232         
233         DEBUG2("rlm_counter: reset_db: Opened new database");
234
235         /*
236          * Add defaults
237          */
238         rcode = add_defaults(inst);
239         if (rcode != RLM_MODULE_OK)
240                 return rcode;
241
242         DEBUG2("rlm_counter: reset_db ended");
243
244         return RLM_MODULE_OK;
245 }
246
247 static int find_next_reset(rlm_counter_t *inst, time_t timeval)
248 {
249         int ret = 0;
250         size_t len;
251         unsigned int num = 1;
252         char last = '\0';
253         struct tm *tm, s_tm;
254         char sCurrentTime[40], sNextTime[40];
255
256         tm = localtime_r(&timeval, &s_tm);
257         len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm);
258         if (len == 0) *sCurrentTime = '\0';
259         tm->tm_sec = tm->tm_min = 0;
260
261         if (inst->reset == NULL)
262                 return -1;
263         if (isdigit((int) inst->reset[0])){
264                 len = strlen(inst->reset);
265                 if (len == 0)
266                         return -1;
267                 last = inst->reset[len - 1];
268                 if (!isalpha((int) last))
269                         last = 'd';
270                 num = atoi(inst->reset);
271                 DEBUG("rlm_counter: num=%d, last=%c",num,last);
272         }
273         if (strcmp(inst->reset, "hourly") == 0 || last == 'h') {
274                 /*
275                  *  Round up to the next nearest hour.
276                  */
277                 tm->tm_hour += num;
278                 inst->reset_time = mktime(tm);
279         } else if (strcmp(inst->reset, "daily") == 0 || last == 'd') {
280                 /*
281                  *  Round up to the next nearest day.
282                  */
283                 tm->tm_hour = 0;
284                 tm->tm_mday += num;
285                 inst->reset_time = mktime(tm);
286         } else if (strcmp(inst->reset, "weekly") == 0 || last == 'w') {
287                 /*
288                  *  Round up to the next nearest week.
289                  */
290                 tm->tm_hour = 0;
291                 tm->tm_mday += (7 - tm->tm_wday) +(7*(num-1));
292                 inst->reset_time = mktime(tm);
293         } else if (strcmp(inst->reset, "monthly") == 0 || last == 'm') {
294                 tm->tm_hour = 0;
295                 tm->tm_mday = 1;
296                 tm->tm_mon += num;
297                 inst->reset_time = mktime(tm);
298         } else if (strcmp(inst->reset, "never") == 0) {
299                 inst->reset_time = 0;
300         } else {
301                 radlog(L_ERR, "rlm_counter: Unknown reset timer \"%s\"",
302                         inst->reset);
303                 return -1;
304         }
305
306         len = strftime(sNextTime, sizeof(sNextTime), "%Y-%m-%d %H:%M:%S", tm);
307         if (len == 0) *sNextTime = '\0';
308         DEBUG2("rlm_counter: Current Time: %li [%s], Next reset %li [%s]",
309                 timeval, sCurrentTime, inst->reset_time, sNextTime);
310
311         return ret;
312 }
313
314
315 /*
316  *      Do any per-module initialization that is separate to each
317  *      configured instance of the module.  e.g. set up connections
318  *      to external databases, read configuration files, set up
319  *      dictionary entries, etc.
320  *
321  *      If configuration information is given in the config section
322  *      that must be referenced in later calls, store a handle to it
323  *      in *instance otherwise put a null pointer there.
324  */
325 static int counter_instantiate(CONF_SECTION *conf, void **instance)
326 {
327         rlm_counter_t *inst;
328         const DICT_ATTR *dattr;
329         DICT_VALUE *dval;
330         ATTR_FLAGS flags;
331         time_t now;
332         int cache_size;
333         int ret;
334         datum key_datum;
335         datum time_datum;
336         const char *default1 = "DEFAULT1";
337         const char *default2 = "DEFAULT2";
338
339         /*
340          *      Set up a storage area for instance data
341          */
342         *instance = inst = talloc_zero(conf, rlm_counter_t);
343
344         /*
345          *      If the configuration parameters can't be parsed, then
346          *      fail.
347          */
348         if (cf_section_parse(conf, inst, module_config) < 0) {
349                 return -1;
350         }
351         cache_size = inst->cache_size;
352
353         /*
354          *      Discover the attribute number of the key.
355          */
356         if (inst->key_name == NULL) {
357                 radlog(L_ERR, "rlm_counter: 'key' must be set.");
358                 counter_detach(inst);
359                 return -1;
360         }
361         dattr = dict_attrbyname(inst->key_name);
362         if (dattr == NULL) {
363                 radlog(L_ERR, "rlm_counter: No such attribute %s",
364                                 inst->key_name);
365                 counter_detach(inst);
366                 return -1;
367         }
368         inst->key_attr = dattr->attr;
369
370         /*
371          *      Discover the attribute number of the counter.
372          */
373         if (inst->count_attribute == NULL) {
374                 radlog(L_ERR, "rlm_counter: 'count-attribute' must be set.");
375                 counter_detach(inst);
376                 return -1;
377         }
378         dattr = dict_attrbyname(inst->count_attribute);
379         if (dattr == NULL) {
380                 radlog(L_ERR, "rlm_counter: No such attribute %s",
381                                 inst->count_attribute);
382                 counter_detach(inst);
383                 return -1;
384         }
385         inst->count_attr = dattr->attr;
386
387         /*
388          * Discover the attribute number of the reply attribute.
389          */
390         if (inst->reply_name != NULL) {
391                 dattr = dict_attrbyname(inst->reply_name);
392                 if (dattr == NULL) {
393                         radlog(L_ERR, "rlm_counter: No such attribute %s",
394                                         inst->reply_name);
395                         counter_detach(inst);
396                         return -1;
397                 }
398                 if (dattr->type != PW_TYPE_INTEGER) {
399                         radlog(L_ERR, "rlm_counter: Reply attribute %s is not of type integer",
400                                 inst->reply_name);
401                         counter_detach(inst);
402                         return -1;
403                 }
404                 inst->reply_attr = dattr->attr;
405         }
406
407
408         /*
409          *  Create a new attribute for the counter.
410          */
411         if (inst->counter_name == NULL) {
412                 radlog(L_ERR, "rlm_counter: 'counter-name' must be set.");
413                 return -1;
414         }
415
416         memset(&flags, 0, sizeof(flags));
417         dict_addattr(inst->counter_name, -1, 0, PW_TYPE_INTEGER, flags);
418         dattr = dict_attrbyname(inst->counter_name);
419         if (dattr == NULL) {
420                 radlog(L_ERR, "rlm_counter: Failed to create counter attribute %s",
421                                 inst->counter_name);
422                 return -1;
423         }
424         inst->dict_attr = dattr->attr;
425         DEBUG2("rlm_counter: Counter attribute %s is number %d",
426                         inst->counter_name, inst->dict_attr);
427
428         /*
429          * Create a new attribute for the check item.
430          */
431         if (inst->check_name == NULL) {
432                 radlog(L_ERR, "rlm_counter: 'check-name' must be set.");
433                 return -1;
434         }
435         dict_addattr(inst->check_name, 0, PW_TYPE_INTEGER, -1, flags);
436         dattr = dict_attrbyname(inst->check_name);
437         if (dattr == NULL) {
438                 radlog(L_ERR, "rlm_counter: Failed to create check attribute %s",
439                                 inst->counter_name);
440                 return -1;
441         }
442         inst->check_attr = dattr->attr;
443
444         /*
445          * Find the attribute for the allowed protocol
446          */
447         if (inst->service_type != NULL) {
448                 if ((dval = dict_valbyname(PW_SERVICE_TYPE, 0, inst->service_type)) == NULL) {
449                         radlog(L_ERR, "rlm_counter: Failed to find attribute number for %s",
450                                         inst->service_type);
451                         return -1;
452                 }
453                 inst->service_val = dval->value;
454         }
455
456         /*
457          * Find when to reset the database.
458          */
459         if (inst->reset == NULL) {
460                 radlog(L_ERR, "rlm_counter: 'reset' must be set.");
461                 return -1;
462         }
463         now = time(NULL);
464         inst->reset_time = 0;
465         inst->last_reset = now;
466
467         if (find_next_reset(inst,now) == -1){
468                 radlog(L_ERR, "rlm_counter: find_next_reset() returned -1. Exiting.");
469                 return -1;
470         }
471
472         if (inst->filename == NULL) {
473                 radlog(L_ERR, "rlm_counter: 'filename' must be set.");
474                 return -1;
475         }
476         inst->gdbm = gdbm_open(inst->filename, sizeof(int),
477                                GDBM_WRCREAT | GDBM_COUNTER_OPTS,
478                                0600, NULL);
479         if (inst->gdbm == NULL) {
480                 radlog(L_ERR, "rlm_counter: Failed to open file %s: %s",
481                                 inst->filename, strerror(errno));
482                 return -1;
483         }
484         if (gdbm_setopt(inst->gdbm, GDBM_CACHESIZE, &cache_size,
485                         sizeof(cache_size)) == -1) {
486                 radlog(L_ERR, "rlm_counter: Failed to set cache size");
487         }
488
489         /*
490          * Look for the DEFAULT1 entry. This entry if it exists contains the
491          * time of the next database reset. This time is set each time we reset
492          * the database. If next_reset < now then we reset the database.
493          * That way we can overcome the problem where radiusd is down during a database
494          * reset time. If we did not keep state information in the database then the reset
495          * would be extended and that would create problems.
496          *
497          * We also store the time of the last reset in the DEFAULT2 entry.
498          *
499          * If DEFAULT1 and DEFAULT2 do not exist (new database) we add them to the database
500          */
501
502         memcpy(&key_datum.dptr, &default1, sizeof(key_datum.dptr));
503         key_datum.dsize = strlen(key_datum.dptr);
504
505         time_datum = gdbm_fetch(inst->gdbm, key_datum);
506         if (time_datum.dptr != NULL){
507                 time_t next_reset = 0;
508
509                 memcpy(&next_reset, time_datum.dptr, sizeof(time_t));
510                 free(time_datum.dptr);
511                 time_datum.dptr = NULL;
512                 if (next_reset && next_reset <= now){
513
514                         inst->last_reset = now;
515                         ret = reset_db(inst);
516                         if (ret != RLM_MODULE_OK){
517                                 radlog(L_ERR, "rlm_counter: reset_db() failed");
518                                 return -1;
519                         }
520                 } else {
521                         inst->reset_time = next_reset;
522                 }
523                 
524                 memcpy(&key_datum.dptr, &default2, sizeof(key_datum.dptr));
525                 key_datum.dsize = strlen(key_datum.dptr);
526
527                 time_datum = gdbm_fetch(inst->gdbm, key_datum);
528                 if (time_datum.dptr != NULL){
529                         memcpy(&inst->last_reset, time_datum.dptr, sizeof(time_t));
530                         free(time_datum.dptr);
531                 }
532         }
533         else{
534                 ret = add_defaults(inst);
535                 if (ret != RLM_MODULE_OK){
536                         radlog(L_ERR, "rlm_counter: add_defaults() failed");
537                         return -1;
538                 }
539         }
540
541
542         /*
543          *      Register the counter comparison operation.
544          */
545         paircompare_register(inst->dict_attr, 0, counter_cmp, inst);
546
547         /*
548          * Init the mutex
549          */
550         pthread_mutex_init(&inst->mutex, NULL);
551
552         return 0;
553 }
554
555 /*
556  *      Write accounting information to this modules database.
557  */
558 static rlm_rcode_t counter_accounting(void *instance, REQUEST *request)
559 {
560         rlm_counter_t *inst = instance;
561         datum key_datum;
562         datum count_datum;
563         VALUE_PAIR *key_vp, *count_vp, *proto_vp, *uniqueid_vp;
564         rad_counter counter;
565         rlm_rcode_t rcode;
566         int ret;
567         int acctstatustype = 0;
568         time_t diff;
569
570         if ((key_vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) != NULL)
571                 acctstatustype = key_vp->vp_integer;
572         else {
573                 DEBUG("rlm_counter: Could not find account status type in packet.");
574                 return RLM_MODULE_NOOP;
575         }
576         if (acctstatustype != PW_STATUS_STOP){
577                 DEBUG("rlm_counter: We only run on Accounting-Stop packets.");
578                 return RLM_MODULE_NOOP;
579         }
580         uniqueid_vp = pairfind(request->packet->vps, PW_ACCT_UNIQUE_SESSION_ID, 0, TAG_ANY);
581         if (uniqueid_vp != NULL)
582                 DEBUG("rlm_counter: Packet Unique ID = '%s'",uniqueid_vp->vp_strvalue);
583
584         /*
585          *      Before doing anything else, see if we have to reset
586          *      the counters.
587          */
588         if (inst->reset_time && (inst->reset_time <= request->timestamp)) {
589                 DEBUG("rlm_counter: Time to reset the database.");
590                 inst->last_reset = inst->reset_time;
591                 find_next_reset(inst,request->timestamp);
592                 pthread_mutex_lock(&inst->mutex);
593                 rcode = reset_db(inst);
594                 pthread_mutex_unlock(&inst->mutex);
595                 if (rcode != RLM_MODULE_OK)
596                         return rcode;
597         }
598         /*
599          * Check if we need to watch out for a specific service-type. If yes then check it
600          */
601         if (inst->service_type != NULL) {
602                 if ((proto_vp = pairfind(request->packet->vps, PW_SERVICE_TYPE, 0, TAG_ANY)) == NULL){
603                         DEBUG("rlm_counter: Could not find Service-Type attribute in the request. Returning NOOP.");
604                         return RLM_MODULE_NOOP;
605                 }
606                 if ((unsigned)proto_vp->vp_integer != inst->service_val){
607                         DEBUG("rlm_counter: This Service-Type is not allowed. Returning NOOP.");
608                         return RLM_MODULE_NOOP;
609                 }
610         }
611         /*
612          * Check if request->timestamp - {Acct-Delay-Time} < last_reset
613          * If yes reject the packet since it is very old
614          */
615         key_vp = pairfind(request->packet->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY);
616         if (key_vp != NULL){
617                 if (key_vp->vp_integer != 0 &&
618                     (request->timestamp - key_vp->vp_integer) < inst->last_reset){
619                         DEBUG("rlm_counter: This packet is too old. Returning NOOP.");
620                         return RLM_MODULE_NOOP;
621                 }
622         }
623
624
625
626         /*
627          *      Look for the key.  User-Name is special.  It means
628          *      The REAL username, after stripping.
629          */
630         key_vp = (inst->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, inst->key_attr, 0, TAG_ANY);
631         if (key_vp == NULL){
632                 DEBUG("rlm_counter: Could not find the key-attribute in the request. Returning NOOP.");
633                 return RLM_MODULE_NOOP;
634         }
635
636         /*
637          *      Look for the attribute to use as a counter.
638          */
639         count_vp = pairfind(request->packet->vps, inst->count_attr, 0, TAG_ANY);
640         if (count_vp == NULL){
641                 DEBUG("rlm_counter: Could not find the count-attribute in the request.");
642                 return RLM_MODULE_NOOP;
643         }
644
645         key_datum.dptr = key_vp->vp_strvalue;
646         key_datum.dsize = key_vp->length;
647
648         DEBUG("rlm_counter: Searching the database for key '%s'",key_vp->vp_strvalue);
649         pthread_mutex_lock(&inst->mutex);
650         count_datum = gdbm_fetch(inst->gdbm, key_datum);
651         pthread_mutex_unlock(&inst->mutex);
652         if (count_datum.dptr == NULL){
653                 DEBUG("rlm_counter: Could not find the requested key in the database.");
654                 counter.user_counter = 0;
655                 if (uniqueid_vp != NULL)
656                         strlcpy(counter.uniqueid,uniqueid_vp->vp_strvalue,
657                                 sizeof(counter.uniqueid));
658                 else
659                         memset((char *)counter.uniqueid,0,UNIQUEID_MAX_LEN);
660         }
661         else{
662                 DEBUG("rlm_counter: Key found.");
663                 memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
664                 free(count_datum.dptr);
665                 if (counter.uniqueid)
666                         DEBUG("rlm_counter: Counter Unique ID = '%s'",counter.uniqueid);
667                 if (uniqueid_vp != NULL){
668                         if (strncmp(uniqueid_vp->vp_strvalue,counter.uniqueid, UNIQUEID_MAX_LEN - 1) == 0){
669                                 DEBUG("rlm_counter: Unique IDs for user match. Droping the request.");
670                                 return RLM_MODULE_NOOP;
671                         }
672                         strlcpy(counter.uniqueid,uniqueid_vp->vp_strvalue,
673                                 sizeof(counter.uniqueid));
674                 }
675                 DEBUG("rlm_counter: User=%s, Counter=%d.",request->username->vp_strvalue,counter.user_counter);
676         }
677
678         if (inst->count_attr == PW_ACCT_SESSION_TIME) {
679                 /*
680                  *      If session time < diff then the user got in after the
681                  *      last reset. So add his session time, otherwise add the
682                  *      diff.
683                  *
684                  *      That way if he logged in at 23:00 and we reset the
685                  *      daily counter at 24:00 and he logged out at 01:00
686                  *      then we will only count one hour (the one in the new
687                  *      day). That is the right thing
688                  */
689                 diff = request->timestamp - inst->last_reset;
690                 counter.user_counter += (count_vp->vp_integer < diff) ? count_vp->vp_integer : diff;
691
692         } else if (count_vp->da->type == PW_TYPE_INTEGER) {
693                 /*
694                  *      Integers get counted, without worrying about
695                  *      reset dates.
696                  */
697                 counter.user_counter += count_vp->vp_integer;
698
699         } else {
700                 /*
701                  *      The attribute is NOT an integer, just count once
702                  *      more that we've seen it.
703                  */
704                 counter.user_counter++;
705         }
706
707         DEBUG("rlm_counter: User=%s, New Counter=%d.",request->username->vp_strvalue,counter.user_counter);
708         count_datum.dptr = (char *) &counter;
709         count_datum.dsize = sizeof(rad_counter);
710
711         DEBUG("rlm_counter: Storing new value in database.");
712         pthread_mutex_lock(&inst->mutex);
713         ret = gdbm_store(inst->gdbm, key_datum, count_datum, GDBM_REPLACE);
714         pthread_mutex_unlock(&inst->mutex);
715         if (ret < 0) {
716                 radlog(L_ERR, "rlm_counter: Failed storing data to %s: %s",
717                                 inst->filename, gdbm_strerror(gdbm_errno));
718                 return RLM_MODULE_FAIL;
719         }
720         DEBUG("rlm_counter: New value stored successfully.");
721
722         return RLM_MODULE_OK;
723 }
724
725 /*
726  *      Find the named user in this modules database.  Create the set
727  *      of attribute-value pairs to check and reply with for this user
728  *      from the database. The authentication code only needs to check
729  *      the password, the rest is done here.
730  */
731 static rlm_rcode_t counter_authorize(void *instance, REQUEST *request)
732 {
733         rlm_counter_t *inst = instance;
734         rlm_rcode_t rcode = RLM_MODULE_NOOP;
735         datum key_datum;
736         datum count_datum;
737         rad_counter counter;
738         unsigned int res = 0;
739         VALUE_PAIR *key_vp, *check_vp;
740         VALUE_PAIR *reply_item;
741         char msg[128];
742
743         /* quiet the compiler */
744         instance = instance;
745         request = request;
746
747         /*
748          *      Before doing anything else, see if we have to reset
749          *      the counters.
750          */
751         if (inst->reset_time && (inst->reset_time <= request->timestamp)) {
752                 rlm_rcode_t rcode2;
753
754                 inst->last_reset = inst->reset_time;
755                 find_next_reset(inst,request->timestamp);
756                 pthread_mutex_lock(&inst->mutex);
757                 rcode2 = reset_db(inst);
758                 pthread_mutex_unlock(&inst->mutex);
759                 if (rcode2 != RLM_MODULE_OK)
760                         return rcode2;
761         }
762
763
764         /*
765          *      Look for the key.  User-Name is special.  It means
766          *      The REAL username, after stripping.
767          */
768         DEBUG2("rlm_counter: Entering module authorize code");
769         key_vp = (inst->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, inst->key_attr, 0, TAG_ANY);
770         if (key_vp == NULL) {
771                 DEBUG2("rlm_counter: Could not find Key value pair");
772                 return rcode;
773         }
774
775         /*
776          *      Look for the check item
777          */
778         if ((check_vp= pairfind(request->config_items, inst->check_attr, 0, TAG_ANY)) == NULL) {
779                 DEBUG2("rlm_counter: Could not find Check item value pair");
780                 return rcode;
781         }
782
783         key_datum.dptr = key_vp->vp_strvalue;
784         key_datum.dsize = key_vp->length;
785
786
787         /*
788          * Init to be sure
789          */
790
791         counter.user_counter = 0;
792
793         DEBUG("rlm_counter: Searching the database for key '%s'",key_vp->vp_strvalue);
794         pthread_mutex_lock(&inst->mutex);
795         count_datum = gdbm_fetch(inst->gdbm, key_datum);
796         pthread_mutex_unlock(&inst->mutex);
797         if (count_datum.dptr != NULL){
798                 DEBUG("rlm_counter: Key Found.");
799                 memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
800                 free(count_datum.dptr);
801         }
802         else
803                 DEBUG("rlm_counter: Could not find the requested key in the database.");
804
805         /*
806          * Check if check item > counter
807          */
808         DEBUG("rlm_counter: Check item = %d, Count = %d",check_vp->vp_integer,counter.user_counter);
809         res=check_vp->vp_integer - counter.user_counter;
810         if (res > 0) {
811                 DEBUG("rlm_counter: res is greater than zero");
812                 if (inst->count_attr == PW_ACCT_SESSION_TIME) {
813                         /*
814                          * Do the following only if the count attribute is
815                          * AcctSessionTime
816                          */
817
818                         /*
819                         *       We are assuming that simultaneous-use=1. But
820                         *       even if that does not happen then our user
821                         *       could login at max for 2*max-usage-time Is
822                         *       that acceptable?
823                         */
824
825                         /*
826                         *       User is allowed, but set Session-Timeout.
827                         *       Stolen from main/auth.c
828                         */
829
830                         /*
831                         *       If we are near a reset then add the next
832                         *       limit, so that the user will not need to
833                         *       login again
834                         *       Before that set the return value to the time
835                         *       remaining to next reset
836                         */
837                         if (inst->reset_time && (
838                                 res >= (inst->reset_time - request->timestamp))) {
839                                 res = inst->reset_time - request->timestamp;
840                                 res += check_vp->vp_integer;
841                         }
842
843                         reply_item = pairfind(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
844                         if (reply_item && (reply_item->vp_integer > res)) {
845                                 reply_item->vp_integer = res;
846                         } else {
847                                 reply_item = radius_paircreate(request, &request->reply->vps, PW_SESSION_TIMEOUT, 0);
848                                 reply_item->vp_integer = res;
849                         }
850                 }
851                 else if (inst->reply_attr) {
852                         reply_item = pairfind(request->reply->vps, inst->reply_attr, 0, TAG_ANY);
853                         if (reply_item && (reply_item->vp_integer > res)) {
854                                 reply_item->vp_integer = res;
855                         } else {
856                                 reply_item = radius_paircreate(request, &request->reply->vps, inst->reply_attr, 0);
857                                 reply_item->vp_integer = res;
858                         }
859                 }
860
861                 rcode = RLM_MODULE_OK;
862
863                 DEBUG2("rlm_counter: (Check item - counter) is greater than zero");
864                 DEBUG2("rlm_counter: Authorized user %s, check_item=%d, counter=%d",
865                                 key_vp->vp_strvalue,check_vp->vp_integer,counter.user_counter);
866                 DEBUG2("rlm_counter: Sent Reply-Item for user %s, Type=Session-Timeout, value=%d",
867                                 key_vp->vp_strvalue,res);
868         }
869         else{
870                 char module_fmsg[MAX_STRING_LEN];
871                 VALUE_PAIR *module_fmsg_vp;
872
873                 /*
874                  * User is denied access, send back a reply message
875                 */
876                 sprintf(msg, "Your maximum %s usage time has been reached", inst->reset);
877                 reply_item=pairmake("Reply-Message", msg, T_OP_EQ);
878                 pairadd(&request->reply->vps, reply_item);
879
880                 snprintf(module_fmsg,sizeof(module_fmsg), "rlm_counter: Maximum %s usage time reached", inst->reset);
881                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
882                 pairadd(&request->packet->vps, module_fmsg_vp);
883
884                 rcode = RLM_MODULE_REJECT;
885
886                 DEBUG2("rlm_counter: Rejected user %s, check_item=%d, counter=%d",
887                                 key_vp->vp_strvalue,check_vp->vp_integer,counter.user_counter);
888         }
889
890         return rcode;
891 }
892
893 static int counter_detach(void *instance)
894 {
895         rlm_counter_t *inst = instance;
896
897         paircompare_unregister(inst->dict_attr, counter_cmp);
898         if (inst->gdbm) {
899                 gdbm_close(inst->gdbm);
900         }
901         
902         pthread_mutex_destroy(&inst->mutex);
903
904         return 0;
905 }
906
907 /*
908  *      The module name should be the only globally exported symbol.
909  *      That is, everything else should be 'static'.
910  *
911  *      If the module needs to temporarily modify it's instantiation
912  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
913  *      The server will then take care of ensuring that the module
914  *      is single-threaded.
915  */
916 module_t rlm_counter = {
917          RLM_MODULE_INIT,
918         "counter",
919         RLM_TYPE_THREAD_SAFE,           /* type */
920         counter_instantiate,            /* instantiation */
921         counter_detach,                 /* detach */
922         {
923                 NULL,                   /* authentication */
924                 counter_authorize,      /* authorization */
925                 NULL,                   /* preaccounting */
926                 counter_accounting,     /* accounting */
927                 NULL,                   /* checksimul */
928                 NULL,                   /* pre-proxy */
929                 NULL,                   /* post-proxy */
930                 NULL                    /* post-auth */
931         },
932 };