check & memset
[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         if (!data) {
315                 return -1;
316         }
317         memset(data, 0, sizeof(*data));
318
319         /*
320          *      If the configuration parameters can't be parsed, then
321          *      fail.
322          */
323         if (cf_section_parse(conf, data, module_config) < 0) {
324                 free(data);
325                 return -1;
326         }
327         cache_size = data->cache_size;
328
329         /*
330          *      Discover the attribute number of the key. 
331          */
332         if (data->key_name == NULL) {
333                 radlog(L_ERR, "rlm_counter: 'key' must be set.");
334                 exit(0);
335         }
336         dattr = dict_attrbyname(data->key_name);
337         if (dattr == NULL) {
338                 radlog(L_ERR, "rlm_counter: No such attribute %s",
339                                 data->key_name);
340                 return -1;
341         }
342         data->key_attr = dattr->attr;
343         
344         /*
345          *      Discover the attribute number of the counter. 
346          */
347         if (data->count_attribute == NULL) {
348                 radlog(L_ERR, "rlm_counter: 'count-attribute' must be set.");
349                 exit(0);
350         }
351         dattr = dict_attrbyname(data->count_attribute);
352         if (dattr == NULL) {
353                 radlog(L_ERR, "rlm_counter: No such attribute %s",
354                                 data->count_attribute);
355                 return -1;
356         }
357         data->count_attr = dattr->attr;
358
359         /*
360          *  Create a new attribute for the counter.
361          */
362         if (data->counter_name == NULL) {
363                 radlog(L_ERR, "rlm_counter: 'counter-name' must be set.");
364                 exit(0);
365         }
366
367         memset(&flags, 0, sizeof(flags));
368         dict_addattr(data->counter_name, 0, PW_TYPE_INTEGER, -1, flags);
369         dattr = dict_attrbyname(data->counter_name);
370         if (dattr == NULL) {
371                 radlog(L_ERR, "rlm_counter: Failed to create counter attribute %s",
372                                 data->counter_name);
373                 return -1;
374         }
375         data->dict_attr = dattr->attr;
376         DEBUG2("rlm_counter: Counter attribute %s is number %d",
377                         data->counter_name, data->dict_attr);
378
379         /*
380          * Create a new attribute for the check item.
381          */
382         if (data->check_name == NULL) {
383                 radlog(L_ERR, "rlm_counter: 'check-name' must be set.");
384                 exit(0);
385         }
386         dict_addattr(data->check_name, 0, PW_TYPE_INTEGER, -1, flags);
387         dattr = dict_attrbyname(data->check_name);
388         if (dattr == NULL) {
389                 radlog(L_ERR, "rlm_counter: Failed to create check attribute %s",
390                                 data->counter_name);
391                 return -1;
392         }
393         data->check_attr = dattr->attr;
394
395         /*
396          * Find the attribute for the allowed protocol
397          */
398         if (data->service_type != NULL) {
399                 if ((dval = dict_valbyname(PW_SERVICE_TYPE, data->service_type)) == NULL) {
400                         radlog(L_ERR, "rlm_counter: Failed to find attribute number for %s",
401                                         data->service_type);
402                         return -1;
403                 }
404                 data->service_val = dval->value;
405         }       
406
407         /*
408          * Find when to reset the database.
409          */
410         if (data->reset == NULL) {
411                 radlog(L_ERR, "rlm_counter: 'reset' must be set.");
412                 exit(0);
413         }
414         now = time(NULL);
415         data->reset_time = 0;
416         data->last_reset = now;
417
418         if (find_next_reset(data,now) == -1)
419                 return -1;
420
421         if (data->filename == NULL) {
422                 radlog(L_ERR, "rlm_counter: 'filename' must be set.");
423                 exit(0);
424         }
425         data->gdbm = gdbm_open(data->filename, sizeof(int),
426                         GDBM_WRCREAT | GDBM_COUNTER_OPTS, 0600, NULL);
427         if (data->gdbm == NULL) {
428                 radlog(L_ERR, "rlm_counter: Failed to open file %s: %s",
429                                 data->filename, strerror(errno));
430                 return -1;
431         }
432         if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
433                 radlog(L_ERR, "rlm_counter: Failed to set cache size");
434
435         /*
436          * Look for the DEFAULT1 entry. This entry if it exists contains the
437          * time of the next database reset. This time is set each time we reset
438          * the database. If next_reset < now then we reset the database.
439          * That way we can overcome the problem where radiusd is down during a database
440          * reset time. If we did not keep state information in the database then the reset
441          * would be extended and that would create problems.
442          *
443          * We also store the time of the last reset in the DEFAULT2 entry.
444          *
445          * If DEFAULT1 and DEFAULT2 do not exist (new database) we add them to the database
446          */
447
448         key_datum.dptr = (const char *)default1;
449         key_datum.dsize = strlen(default1);
450
451         time_datum = gdbm_fetch(data->gdbm, key_datum);
452         if (time_datum.dptr != NULL){
453                 time_t next_reset = 0;
454
455                 memcpy(&next_reset, time_datum.dptr, sizeof(time_t));
456                 free(time_datum.dptr);
457                 if (next_reset && next_reset <= now){
458
459                         data->last_reset = now;
460                         ret = reset_db(data);
461                         if (ret != RLM_MODULE_OK)
462                                 return -1;
463                 }
464                 else
465                         data->reset_time = next_reset;
466                 key_datum.dptr = (const char *)default2;
467                 key_datum.dsize = strlen(default2);
468
469                 time_datum = gdbm_fetch(data->gdbm, key_datum); 
470                 if (time_datum.dptr != NULL){
471                         memcpy(&data->last_reset, time_datum.dptr, sizeof(time_t));
472                         free(time_datum.dptr);
473                 }
474         }
475         else{
476                 ret = add_defaults(data);
477                 if (ret != RLM_MODULE_OK)
478                         return -1;
479         }
480
481
482         /*
483          *      Register the counter comparison operation.
484          */
485         paircompare_register(data->dict_attr, 0, counter_cmp, data);
486
487         /*
488          * Init the mutex
489          */
490         pthread_mutex_init(&data->mutex, NULL);
491
492         *instance = data;
493         
494         return 0;
495 }
496
497 /*
498  *      Write accounting information to this modules database.
499  */
500 static int counter_accounting(void *instance, REQUEST *request)
501 {
502         rlm_counter_t *data = (rlm_counter_t *)instance;
503         datum key_datum;
504         datum count_datum;
505         VALUE_PAIR *key_vp, *count_vp, *proto_vp, *uniqueid_vp;
506         rad_counter counter;
507         int rcode;
508         int acctstatustype = 0;
509         time_t diff;
510
511         if ((key_vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
512                 acctstatustype = key_vp->lvalue;
513         else {
514                 DEBUG("rlm_counter: Could not find account status type in packet.");
515                 return RLM_MODULE_NOOP;
516         }
517         if (acctstatustype != PW_STATUS_STOP){
518                 DEBUG("rlm_counter: We only run on Accounting-Stop packets.");
519                 return RLM_MODULE_NOOP;
520         }
521         uniqueid_vp = pairfind(request->packet->vps, PW_ACCT_UNIQUE_SESSION_ID);
522         if (uniqueid_vp != NULL)
523                 DEBUG("rlm_counter: Packet Unique ID = '%s'",uniqueid_vp->strvalue);
524
525         /*
526          *      Before doing anything else, see if we have to reset
527          *      the counters.
528          */
529         if (data->reset_time && (data->reset_time <= request->timestamp)) {
530                 int ret;
531
532                 data->last_reset = data->reset_time;
533                 find_next_reset(data,request->timestamp);
534                 pthread_mutex_lock(&data->mutex);
535                 ret = reset_db(data);
536                 pthread_mutex_unlock(&data->mutex);
537                 if (ret != RLM_MODULE_OK)
538                         return ret;
539         }
540         /*
541          * Check if we need to watch out for a specific service-type. If yes then check it
542          */
543         if (data->service_type != NULL) {
544                 if ((proto_vp = pairfind(request->packet->vps, PW_SERVICE_TYPE)) == NULL)
545                         return RLM_MODULE_NOOP;
546                 if (proto_vp->lvalue != data->service_val)
547                         return RLM_MODULE_NOOP;
548
549         }       
550         
551
552         /*
553          *      Look for the key.  User-Name is special.  It means
554          *      The REAL username, after stripping.
555          */
556         key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
557         if (key_vp == NULL){
558                 DEBUG("rlm_counter: Could not find the key-attribute in the request.");
559                 return RLM_MODULE_NOOP;
560         }
561
562         /*
563          *      Look for the attribute to use as a counter.
564          */
565         count_vp = pairfind(request->packet->vps, data->count_attr);
566         if (count_vp == NULL){
567                 DEBUG("rlm_counter: Could not find the count-attribute in the request.");
568                 return RLM_MODULE_NOOP;
569         }
570
571         key_datum.dptr = key_vp->strvalue;
572         key_datum.dsize = key_vp->length;
573
574         pthread_mutex_lock(&data->mutex);
575         count_datum = gdbm_fetch(data->gdbm, key_datum);
576         pthread_mutex_unlock(&data->mutex);
577         if (count_datum.dptr == NULL){
578                 counter.user_counter = 0;
579                 if (uniqueid_vp != NULL)
580                         strncpy(uniqueid_vp->strvalue,counter.uniqueid,UNIQUEID_MAX_LEN - 1);
581                 else
582                         memset((char *)counter.uniqueid,0,UNIQUEID_MAX_LEN);
583         }
584         else{
585                 memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
586                 free(count_datum.dptr);
587                 if (counter.uniqueid)
588                         DEBUG("rlm_counter: Counter Unique ID = '%s'",counter.uniqueid);
589                 if (uniqueid_vp != NULL){ 
590                         if (counter.uniqueid != NULL && 
591                                 strncmp(uniqueid_vp->strvalue,counter.uniqueid, UNIQUEID_MAX_LEN - 1) == 0){
592                                 DEBUG("rlm_counter: Unique IDs for user match. Droping the request.");
593                                 return RLM_MODULE_NOOP;
594                         }
595                         strncpy(counter.uniqueid,uniqueid_vp->strvalue,UNIQUEID_MAX_LEN - 1);
596                 }
597                 DEBUG("rlm_counter: User=%s, Counter=%d.",request->username->strvalue,counter.user_counter);
598         }
599
600         if (data->count_attr == PW_ACCT_SESSION_TIME) {
601                 /*
602                  *      If session time < diff then the user got in after the
603                  *      last reset. So add his session time, otherwise add the
604                  *      diff.
605                  *
606                  *      That way if he logged in at 23:00 and we reset the
607                  *      daily counter at 24:00 and he logged out at 01:00
608                  *      then we will only count one hour (the one in the new
609                  *      day). That is the right thing
610                  */
611                 diff = request->timestamp - data->last_reset;
612                 counter.user_counter += (count_vp->lvalue < diff) ? count_vp->lvalue : diff;
613
614         } else if (count_vp->type == PW_TYPE_INTEGER) {
615                 /*
616                  *      Integers get counted, without worrying about
617                  *      reset dates.
618                  */
619                 counter.user_counter += count_vp->lvalue;
620
621         } else {
622                 /*
623                  *      The attribute is NOT an integer, just count once
624                  *      more that we've seen it.
625                  */
626                 counter.user_counter++;
627         }
628
629         DEBUG("rlm_counter: User=%s, New Counter=%d.",request->username->strvalue,counter.user_counter);
630         count_datum.dptr = (rad_counter *) &counter;
631         count_datum.dsize = sizeof(rad_counter);
632
633         pthread_mutex_lock(&data->mutex);
634         rcode = gdbm_store(data->gdbm, key_datum, count_datum, GDBM_REPLACE);
635         pthread_mutex_unlock(&data->mutex);
636         if (rcode < 0) {
637                 radlog(L_ERR, "rlm_counter: Failed storing data to %s: %s",
638                                 data->filename, gdbm_strerror(gdbm_errno));
639                 return RLM_MODULE_FAIL;
640         }
641
642         return RLM_MODULE_OK;
643 }
644
645 /*
646  *      Find the named user in this modules database.  Create the set
647  *      of attribute-value pairs to check and reply with for this user
648  *      from the database. The authentication code only needs to check
649  *      the password, the rest is done here.
650  */
651 static int counter_authorize(void *instance, REQUEST *request)
652 {
653         rlm_counter_t *data = (rlm_counter_t *) instance;
654         int ret=RLM_MODULE_NOOP;
655         datum key_datum;
656         datum count_datum;
657         rad_counter counter;
658         int res=0;
659         VALUE_PAIR *key_vp, *check_vp;
660         VALUE_PAIR *reply_item;
661         char msg[128];
662
663         /* quiet the compiler */
664         instance = instance;
665         request = request;
666
667         /*
668          *      Before doing anything else, see if we have to reset
669          *      the counters.
670          */
671         if (data->reset_time && (data->reset_time <= request->timestamp)) {
672                 int ret2;
673
674                 data->last_reset = data->reset_time;
675                 find_next_reset(data,request->timestamp);
676                 pthread_mutex_lock(&data->mutex);
677                 ret2 = reset_db(data);
678                 pthread_mutex_unlock(&data->mutex);
679                 if (ret2 != RLM_MODULE_OK)
680                         return ret2;
681         }
682
683
684         /*
685          *      Look for the key.  User-Name is special.  It means
686          *      The REAL username, after stripping.
687          */
688         DEBUG2("rlm_counter: Entering module authorize code");
689         key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
690         if (key_vp == NULL) {
691                 DEBUG2("rlm_counter: Could not find Key value pair");
692                 return ret;
693         }
694
695         /*
696          *      Look for the check item
697          */
698         if ((check_vp= pairfind(request->config_items, data->check_attr)) == NULL) {
699                 DEBUG2("rlm_counter: Could not find Check item value pair");
700                 return ret;
701         }
702
703         key_datum.dptr = key_vp->strvalue;
704         key_datum.dsize = key_vp->length;
705
706
707         /*
708          * Init to be sure
709          */
710
711         counter.user_counter = 0;
712         
713         pthread_mutex_lock(&data->mutex);
714         count_datum = gdbm_fetch(data->gdbm, key_datum);
715         pthread_mutex_unlock(&data->mutex);
716         if (count_datum.dptr != NULL){
717                 memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
718                 free(count_datum.dptr);
719         }
720
721         /*
722          * Check if check item > counter
723          */
724         res=check_vp->lvalue - counter.user_counter;
725         if (res > 0) {
726                 if (data->count_attr == PW_ACCT_SESSION_TIME) {
727                         /*
728                          * Do the following only if the count attribute is 
729                          * AcctSessionTime
730                          */
731
732                         /*
733                         *       We are assuming that simultaneous-use=1. But
734                         *       even if that does not happen then our user
735                         *       could login at max for 2*max-usage-time Is
736                         *       that acceptable?
737                         */
738
739                         /*
740                         *       User is allowed, but set Session-Timeout.
741                         *       Stolen from main/auth.c
742                         */
743
744                         /*
745                         *       If we are near a reset then add the next
746                         *       limit, so that the user will not need to
747                         *       login again
748                         */
749                         if (data->reset_time && (
750                                 res >= (data->reset_time - request->timestamp))) {
751                                 res += check_vp->lvalue;
752                         }
753
754                         if ((reply_item = pairfind(request->reply->vps, PW_SESSION_TIMEOUT)) != NULL) {
755                                 if (reply_item->lvalue > res)
756                                         reply_item->lvalue = res;
757                         } else {
758                                 if ((reply_item = paircreate(PW_SESSION_TIMEOUT, PW_TYPE_INTEGER)) == NULL) {
759                                         radlog(L_ERR|L_CONS, "no memory");
760                                         return RLM_MODULE_NOOP;
761                                 }
762                                 reply_item->lvalue = res;
763                                 pairadd(&request->reply->vps, reply_item);
764                         }
765                 }
766
767                 ret=RLM_MODULE_OK;
768
769                 DEBUG2("rlm_counter: (Check item - counter) is greater than zero");
770                 DEBUG2("rlm_counter: Authorized user %s, check_item=%d, counter=%d",
771                                 key_vp->strvalue,check_vp->lvalue,counter.user_counter);
772                 DEBUG2("rlm_counter: Sent Reply-Item for user %s, Type=Session-Timeout, value=%d",
773                                 key_vp->strvalue,res);
774         }
775         else{
776                 char module_fmsg[MAX_STRING_LEN];
777                 VALUE_PAIR *module_fmsg_vp;
778
779                 /*
780                  * User is denied access, send back a reply message
781                 */
782                 sprintf(msg, "Your maximum %s usage time has been reached", data->reset);
783                 reply_item=pairmake("Reply-Message", msg, T_OP_EQ);
784                 pairadd(&request->reply->vps, reply_item);
785
786                 snprintf(module_fmsg,sizeof(module_fmsg), "rlm_counter: Maximum %s usage time reached", data->reset);
787                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
788                 pairadd(&request->packet->vps, module_fmsg_vp); 
789
790                 ret=RLM_MODULE_REJECT;
791
792                 DEBUG2("rlm_counter: Rejected user %s, check_item=%d, counter=%d",
793                                 key_vp->strvalue,check_vp->lvalue,counter.user_counter);
794         }
795
796         return ret;
797 }
798
799 static int counter_detach(void *instance)
800 {
801         rlm_counter_t *data = (rlm_counter_t *) instance;
802
803         paircompare_unregister(data->dict_attr, counter_cmp);
804         gdbm_close(data->gdbm);
805         free(data->filename);
806         free(data->reset);
807         free(data->key_name);
808         free(data->count_attribute);
809         free(data->counter_name);
810         pthread_mutex_destroy(&data->mutex);
811
812         free(instance);
813         return 0;
814 }
815
816 /*
817  *      The module name should be the only globally exported symbol.
818  *      That is, everything else should be 'static'.
819  *
820  *      If the module needs to temporarily modify it's instantiation
821  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
822  *      The server will then take care of ensuring that the module
823  *      is single-threaded.
824  */
825 module_t rlm_counter = {
826         "Counter",      
827         RLM_TYPE_THREAD_SAFE,           /* type */
828         NULL,                           /* initialization */
829         counter_instantiate,            /* instantiation */
830         {
831                 NULL,                   /* authentication */
832                 counter_authorize,      /* authorization */
833                 NULL,                   /* preaccounting */
834                 counter_accounting,     /* accounting */
835                 NULL,                   /* checksimul */
836                 NULL,                   /* pre-proxy */
837                 NULL,                   /* post-proxy */
838                 NULL                    /* post-auth */
839         },
840         counter_detach,                 /* detach */
841         NULL,                           /* destroy */
842 };