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