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