Add debug statements for allowed service type
[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                         DEBUG("rlm_counter: Could not find Service-Type attribute in the request. Returning NOOP.");
546                         return RLM_MODULE_NOOP;
547                 }
548                 if (proto_vp->lvalue != data->service_val){
549                         DEBUG("rlm_counter: This Service-Type is not allowed. Returning NOOP.");
550                         return RLM_MODULE_NOOP;
551                 }
552         }       
553         
554
555         /*
556          *      Look for the key.  User-Name is special.  It means
557          *      The REAL username, after stripping.
558          */
559         key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
560         if (key_vp == NULL){
561                 DEBUG("rlm_counter: Could not find the key-attribute in the request. Returning NOOP.");
562                 return RLM_MODULE_NOOP;
563         }
564
565         /*
566          *      Look for the attribute to use as a counter.
567          */
568         count_vp = pairfind(request->packet->vps, data->count_attr);
569         if (count_vp == NULL){
570                 DEBUG("rlm_counter: Could not find the count-attribute in the request.");
571                 return RLM_MODULE_NOOP;
572         }
573
574         key_datum.dptr = key_vp->strvalue;
575         key_datum.dsize = key_vp->length;
576
577         pthread_mutex_lock(&data->mutex);
578         count_datum = gdbm_fetch(data->gdbm, key_datum);
579         pthread_mutex_unlock(&data->mutex);
580         if (count_datum.dptr == NULL){
581                 counter.user_counter = 0;
582                 if (uniqueid_vp != NULL)
583                         strncpy(uniqueid_vp->strvalue,counter.uniqueid,UNIQUEID_MAX_LEN - 1);
584                 else
585                         memset((char *)counter.uniqueid,0,UNIQUEID_MAX_LEN);
586         }
587         else{
588                 memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
589                 free(count_datum.dptr);
590                 if (counter.uniqueid)
591                         DEBUG("rlm_counter: Counter Unique ID = '%s'",counter.uniqueid);
592                 if (uniqueid_vp != NULL){ 
593                         if (counter.uniqueid != NULL && 
594                                 strncmp(uniqueid_vp->strvalue,counter.uniqueid, UNIQUEID_MAX_LEN - 1) == 0){
595                                 DEBUG("rlm_counter: Unique IDs for user match. Droping the request.");
596                                 return RLM_MODULE_NOOP;
597                         }
598                         strncpy(counter.uniqueid,uniqueid_vp->strvalue,UNIQUEID_MAX_LEN - 1);
599                 }
600                 DEBUG("rlm_counter: User=%s, Counter=%d.",request->username->strvalue,counter.user_counter);
601         }
602
603         if (data->count_attr == PW_ACCT_SESSION_TIME) {
604                 /*
605                  *      If session time < diff then the user got in after the
606                  *      last reset. So add his session time, otherwise add the
607                  *      diff.
608                  *
609                  *      That way if he logged in at 23:00 and we reset the
610                  *      daily counter at 24:00 and he logged out at 01:00
611                  *      then we will only count one hour (the one in the new
612                  *      day). That is the right thing
613                  */
614                 diff = request->timestamp - data->last_reset;
615                 counter.user_counter += (count_vp->lvalue < diff) ? count_vp->lvalue : diff;
616
617         } else if (count_vp->type == PW_TYPE_INTEGER) {
618                 /*
619                  *      Integers get counted, without worrying about
620                  *      reset dates.
621                  */
622                 counter.user_counter += count_vp->lvalue;
623
624         } else {
625                 /*
626                  *      The attribute is NOT an integer, just count once
627                  *      more that we've seen it.
628                  */
629                 counter.user_counter++;
630         }
631
632         DEBUG("rlm_counter: User=%s, New Counter=%d.",request->username->strvalue,counter.user_counter);
633         count_datum.dptr = (rad_counter *) &counter;
634         count_datum.dsize = sizeof(rad_counter);
635
636         pthread_mutex_lock(&data->mutex);
637         rcode = gdbm_store(data->gdbm, key_datum, count_datum, GDBM_REPLACE);
638         pthread_mutex_unlock(&data->mutex);
639         if (rcode < 0) {
640                 radlog(L_ERR, "rlm_counter: Failed storing data to %s: %s",
641                                 data->filename, gdbm_strerror(gdbm_errno));
642                 return RLM_MODULE_FAIL;
643         }
644
645         return RLM_MODULE_OK;
646 }
647
648 /*
649  *      Find the named user in this modules database.  Create the set
650  *      of attribute-value pairs to check and reply with for this user
651  *      from the database. The authentication code only needs to check
652  *      the password, the rest is done here.
653  */
654 static int counter_authorize(void *instance, REQUEST *request)
655 {
656         rlm_counter_t *data = (rlm_counter_t *) instance;
657         int ret=RLM_MODULE_NOOP;
658         datum key_datum;
659         datum count_datum;
660         rad_counter counter;
661         int res=0;
662         VALUE_PAIR *key_vp, *check_vp;
663         VALUE_PAIR *reply_item;
664         char msg[128];
665
666         /* quiet the compiler */
667         instance = instance;
668         request = request;
669
670         /*
671          *      Before doing anything else, see if we have to reset
672          *      the counters.
673          */
674         if (data->reset_time && (data->reset_time <= request->timestamp)) {
675                 int ret2;
676
677                 data->last_reset = data->reset_time;
678                 find_next_reset(data,request->timestamp);
679                 pthread_mutex_lock(&data->mutex);
680                 ret2 = reset_db(data);
681                 pthread_mutex_unlock(&data->mutex);
682                 if (ret2 != RLM_MODULE_OK)
683                         return ret2;
684         }
685
686
687         /*
688          *      Look for the key.  User-Name is special.  It means
689          *      The REAL username, after stripping.
690          */
691         DEBUG2("rlm_counter: Entering module authorize code");
692         key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
693         if (key_vp == NULL) {
694                 DEBUG2("rlm_counter: Could not find Key value pair");
695                 return ret;
696         }
697
698         /*
699          *      Look for the check item
700          */
701         if ((check_vp= pairfind(request->config_items, data->check_attr)) == NULL) {
702                 DEBUG2("rlm_counter: Could not find Check item value pair");
703                 return ret;
704         }
705
706         key_datum.dptr = key_vp->strvalue;
707         key_datum.dsize = key_vp->length;
708
709
710         /*
711          * Init to be sure
712          */
713
714         counter.user_counter = 0;
715         
716         pthread_mutex_lock(&data->mutex);
717         count_datum = gdbm_fetch(data->gdbm, key_datum);
718         pthread_mutex_unlock(&data->mutex);
719         if (count_datum.dptr != NULL){
720                 memcpy(&counter, count_datum.dptr, sizeof(rad_counter));
721                 free(count_datum.dptr);
722         }
723
724         /*
725          * Check if check item > counter
726          */
727         res=check_vp->lvalue - counter.user_counter;
728         if (res > 0) {
729                 if (data->count_attr == PW_ACCT_SESSION_TIME) {
730                         /*
731                          * Do the following only if the count attribute is 
732                          * AcctSessionTime
733                          */
734
735                         /*
736                         *       We are assuming that simultaneous-use=1. But
737                         *       even if that does not happen then our user
738                         *       could login at max for 2*max-usage-time Is
739                         *       that acceptable?
740                         */
741
742                         /*
743                         *       User is allowed, but set Session-Timeout.
744                         *       Stolen from main/auth.c
745                         */
746
747                         /*
748                         *       If we are near a reset then add the next
749                         *       limit, so that the user will not need to
750                         *       login again
751                         */
752                         if (data->reset_time && (
753                                 res >= (data->reset_time - request->timestamp))) {
754                                 res += check_vp->lvalue;
755                         }
756
757                         if ((reply_item = pairfind(request->reply->vps, PW_SESSION_TIMEOUT)) != NULL) {
758                                 if (reply_item->lvalue > res)
759                                         reply_item->lvalue = res;
760                         } else {
761                                 if ((reply_item = paircreate(PW_SESSION_TIMEOUT, PW_TYPE_INTEGER)) == NULL) {
762                                         radlog(L_ERR|L_CONS, "no memory");
763                                         return RLM_MODULE_NOOP;
764                                 }
765                                 reply_item->lvalue = res;
766                                 pairadd(&request->reply->vps, reply_item);
767                         }
768                 }
769
770                 ret=RLM_MODULE_OK;
771
772                 DEBUG2("rlm_counter: (Check item - counter) is greater than zero");
773                 DEBUG2("rlm_counter: Authorized user %s, check_item=%d, counter=%d",
774                                 key_vp->strvalue,check_vp->lvalue,counter.user_counter);
775                 DEBUG2("rlm_counter: Sent Reply-Item for user %s, Type=Session-Timeout, value=%d",
776                                 key_vp->strvalue,res);
777         }
778         else{
779                 char module_fmsg[MAX_STRING_LEN];
780                 VALUE_PAIR *module_fmsg_vp;
781
782                 /*
783                  * User is denied access, send back a reply message
784                 */
785                 sprintf(msg, "Your maximum %s usage time has been reached", data->reset);
786                 reply_item=pairmake("Reply-Message", msg, T_OP_EQ);
787                 pairadd(&request->reply->vps, reply_item);
788
789                 snprintf(module_fmsg,sizeof(module_fmsg), "rlm_counter: Maximum %s usage time reached", data->reset);
790                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
791                 pairadd(&request->packet->vps, module_fmsg_vp); 
792
793                 ret=RLM_MODULE_REJECT;
794
795                 DEBUG2("rlm_counter: Rejected user %s, check_item=%d, counter=%d",
796                                 key_vp->strvalue,check_vp->lvalue,counter.user_counter);
797         }
798
799         return ret;
800 }
801
802 static int counter_detach(void *instance)
803 {
804         rlm_counter_t *data = (rlm_counter_t *) instance;
805
806         paircompare_unregister(data->dict_attr, counter_cmp);
807         gdbm_close(data->gdbm);
808         free(data->filename);
809         free(data->reset);
810         free(data->key_name);
811         free(data->count_attribute);
812         free(data->counter_name);
813         pthread_mutex_destroy(&data->mutex);
814
815         free(instance);
816         return 0;
817 }
818
819 /*
820  *      The module name should be the only globally exported symbol.
821  *      That is, everything else should be 'static'.
822  *
823  *      If the module needs to temporarily modify it's instantiation
824  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
825  *      The server will then take care of ensuring that the module
826  *      is single-threaded.
827  */
828 module_t rlm_counter = {
829         "Counter",      
830         RLM_TYPE_THREAD_SAFE,           /* type */
831         NULL,                           /* initialization */
832         counter_instantiate,            /* instantiation */
833         {
834                 NULL,                   /* authentication */
835                 counter_authorize,      /* authorization */
836                 NULL,                   /* preaccounting */
837                 counter_accounting,     /* accounting */
838                 NULL,                   /* checksimul */
839                 NULL,                   /* pre-proxy */
840                 NULL,                   /* post-proxy */
841                 NULL                    /* post-auth */
842         },
843         counter_detach,                 /* detach */
844         NULL,                           /* destroy */
845 };