1c6d79b0aa970c5511f520b0fcea63c020837368
[freeradius.git] / src / modules / rlm_sqlcounter / rlm_sqlcounter.c
1 /*
2  * rlm_sqlcounter.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  */
23
24 /* This module is based directly on the rlm_counter module */
25
26
27 #include <freeradius-devel/autoconf.h>
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h>
33
34 #include <freeradius-devel/radiusd.h>
35 #include <freeradius-devel/modules.h>
36 #include <freeradius-devel/conffile.h>
37
38 #define MAX_QUERY_LEN 1024
39
40 #include <time.h>
41
42
43 /*      Note: When your counter spans more than 1 period (ie 3 months or 2 weeks), this module
44  *      probably does NOT do what you want!  It calculates the range of dates to count across
45  *      by first calculating the End of the Current period and then subtracting the number of
46  *      periods you specify from that to determine the beginning of the range.
47  *
48  *      For example, if you specify a 3 month counter and today is June 15th, the end of the current
49  *      period is June 30. Subtracting 3 months from that gives April 1st.  So, the counter will
50  *      sum radacct entries from April 1st to June 30. Then, next month, it will sum entries
51  *      from May 1st to July 31st.
52  *
53  *      To fix this behavior, we need to add some way of storing the Next Reset Time
54  */
55
56
57 static const char rcsid[] = "$Id$";
58
59 /*
60  *      Define a structure for our module configuration.
61  *
62  *      These variables do not need to be in a structure, but it's
63  *      a lot cleaner to do so, and a pointer to the structure can
64  *      be used as the instance handle.
65  */
66 typedef struct rlm_sqlcounter_t {
67         char *counter_name;     /* Daily-Session-Time */
68         char *check_name;       /* Max-Daily-Session */
69         char *key_name;         /* User-Name */
70         char *sqlmod_inst;      /* instance of SQL module to use, usually just 'sql' */
71         char *query;            /* SQL query to retrieve current session time */
72         char *reset;            /* daily, weekly, monthly, never or user defined */
73         char *allowed_chars;    /* safe characters list for SQL queries */
74         time_t reset_time;
75         time_t last_reset;
76         int  key_attr;          /* attribute number for key field */
77         int  dict_attr;         /* attribute number for the counter. */
78 } rlm_sqlcounter_t;
79
80 /*
81  *      A mapping of configuration file names to internal variables.
82  *
83  *      Note that the string is dynamically allocated, so it MUST
84  *      be freed.  When the configuration file parse re-reads the string,
85  *      it free's the old one, and strdup's the new one, placing the pointer
86  *      to the strdup'd string into 'config.string'.  This gets around
87  *      buffer over-flows.
88  */
89 static const CONF_PARSER module_config[] = {
90   { "counter-name", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,counter_name), NULL,  NULL },
91   { "check-name", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,check_name), NULL, NULL },
92   { "key", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,key_name), NULL, NULL },
93   { "sqlmod-inst", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,sqlmod_inst), NULL, NULL },
94   { "query", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,query), NULL, NULL },
95   { "reset", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,reset), NULL,  NULL },
96   { "safe-characters", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,allowed_chars), NULL, "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
97   { NULL, -1, 0, NULL, NULL }
98 };
99
100 static char *allowed_chars = NULL;
101
102 /*
103  *      Translate the SQL queries.
104  */
105 static int sql_escape_func(char *out, int outlen, const char *in)
106 {
107         int len = 0;
108
109         while (in[0]) {
110                 /*
111                  *      Non-printable characters get replaced with their
112                  *      mime-encoded equivalents.
113                  */
114                 if ((in[0] < 32) ||
115                     strchr(allowed_chars, *in) == NULL) {
116                         /*
117                          *      Only 3 or less bytes available.
118                          */
119                         if (outlen <= 3) {
120                                 break;
121                         }
122
123                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
124                         in++;
125                         out += 3;
126                         outlen -= 3;
127                         len += 3;
128                         continue;
129                 }
130
131                 /*
132                  *      Only one byte left.
133                  */
134                 if (outlen <= 1) {
135                         break;
136                 }
137
138                 /*
139                  *      Allowed character.
140                  */
141                 *out = *in;
142                 out++;
143                 in++;
144                 outlen--;
145                 len++;
146         }
147         *out = '\0';
148         return len;
149 }
150
151 static int find_next_reset(rlm_sqlcounter_t *data, time_t timeval)
152 {
153         int ret = 0;
154         size_t len;
155         unsigned int num = 1;
156         char last = '\0';
157         struct tm *tm, s_tm;
158         char sCurrentTime[40], sNextTime[40];
159
160         tm = localtime_r(&timeval, &s_tm);
161         len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm);
162         if (len == 0) *sCurrentTime = '\0';
163         tm->tm_sec = tm->tm_min = 0;
164
165         if (data->reset == NULL)
166                 return -1;
167         if (isdigit((int) data->reset[0])){
168                 len = strlen(data->reset);
169                 if (len == 0)
170                         return -1;
171                 last = data->reset[len - 1];
172                 if (!isalpha((int) last))
173                         last = 'd';
174 /*              num = atoi(data->reset); */
175                 DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last);
176         }
177         if (strcmp(data->reset, "hourly") == 0 || last == 'h') {
178                 /*
179                  *  Round up to the next nearest hour.
180                  */
181                 tm->tm_hour += num;
182                 data->reset_time = mktime(tm);
183         } else if (strcmp(data->reset, "daily") == 0 || last == 'd') {
184                 /*
185                  *  Round up to the next nearest day.
186                  */
187                 tm->tm_hour = 0;
188                 tm->tm_mday += num;
189                 data->reset_time = mktime(tm);
190         } else if (strcmp(data->reset, "weekly") == 0 || last == 'w') {
191                 /*
192                  *  Round up to the next nearest week.
193                  */
194                 tm->tm_hour = 0;
195                 tm->tm_mday += (7 - tm->tm_wday) +(7*(num-1));
196                 data->reset_time = mktime(tm);
197         } else if (strcmp(data->reset, "monthly") == 0 || last == 'm') {
198                 tm->tm_hour = 0;
199                 tm->tm_mday = 1;
200                 tm->tm_mon += num;
201                 data->reset_time = mktime(tm);
202         } else if (strcmp(data->reset, "never") == 0) {
203                 data->reset_time = 0;
204         } else {
205                 radlog(L_ERR, "rlm_sqlcounter: Unknown reset timer \"%s\"",
206                         data->reset);
207                 return -1;
208         }
209
210         len = strftime(sNextTime, sizeof(sNextTime),"%Y-%m-%d %H:%M:%S",tm);
211         if (len == 0) *sNextTime = '\0';
212         DEBUG2("rlm_sqlcounter: Current Time: %li [%s], Next reset %li [%s]",
213                 timeval, sCurrentTime, data->reset_time, sNextTime);
214
215         return ret;
216 }
217
218
219 /*  I don't believe that this routine handles Daylight Saving Time adjustments
220     properly.  Any suggestions?
221 */
222
223 static int find_prev_reset(rlm_sqlcounter_t *data, time_t timeval)
224 {
225         int ret = 0;
226         size_t len;
227         unsigned int num = 1;
228         char last = '\0';
229         struct tm *tm, s_tm;
230         char sCurrentTime[40], sPrevTime[40];
231
232         tm = localtime_r(&timeval, &s_tm);
233         len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm);
234         if (len == 0) *sCurrentTime = '\0';
235         tm->tm_sec = tm->tm_min = 0;
236
237         if (data->reset == NULL)
238                 return -1;
239         if (isdigit((int) data->reset[0])){
240                 len = strlen(data->reset);
241                 if (len == 0)
242                         return -1;
243                 last = data->reset[len - 1];
244                 if (!isalpha((int) last))
245                         last = 'd';
246                 num = atoi(data->reset);
247                 DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last);
248         }
249         if (strcmp(data->reset, "hourly") == 0 || last == 'h') {
250                 /*
251                  *  Round down to the prev nearest hour.
252                  */
253                 tm->tm_hour -= num - 1;
254                 data->last_reset = mktime(tm);
255         } else if (strcmp(data->reset, "daily") == 0 || last == 'd') {
256                 /*
257                  *  Round down to the prev nearest day.
258                  */
259                 tm->tm_hour = 0;
260                 tm->tm_mday -= num - 1;
261                 data->last_reset = mktime(tm);
262         } else if (strcmp(data->reset, "weekly") == 0 || last == 'w') {
263                 /*
264                  *  Round down to the prev nearest week.
265                  */
266                 tm->tm_hour = 0;
267                 tm->tm_mday -= (7 - tm->tm_wday) +(7*(num-1));
268                 data->last_reset = mktime(tm);
269         } else if (strcmp(data->reset, "monthly") == 0 || last == 'm') {
270                 tm->tm_hour = 0;
271                 tm->tm_mday = 1;
272                 tm->tm_mon -= num - 1;
273                 data->last_reset = mktime(tm);
274         } else if (strcmp(data->reset, "never") == 0) {
275                 data->reset_time = 0;
276         } else {
277                 radlog(L_ERR, "rlm_sqlcounter: Unknown reset timer \"%s\"",
278                         data->reset);
279                 return -1;
280         }
281         len = strftime(sPrevTime, sizeof(sPrevTime), "%Y-%m-%d %H:%M:%S", tm);
282         if (len == 0) *sPrevTime = '\0';
283         DEBUG2("rlm_sqlcounter: Current Time: %li [%s], Prev reset %li [%s]",
284                timeval, sCurrentTime, data->last_reset, sPrevTime);
285
286         return ret;
287 }
288
289
290 /*
291  *      Replace %<whatever> in a string.
292  *
293  *      %b      last_reset
294  *      %e      reset_time
295  *      %k      key_name
296  *      %S      sqlmod_inst
297  *
298  */
299
300 static int sqlcounter_expand(char *out, int outlen, const char *fmt, void *instance)
301 {
302         rlm_sqlcounter_t *data = (rlm_sqlcounter_t *) instance;
303         int c,freespace;
304         const char *p;
305         char *q;
306         char tmpdt[40]; /* For temporary storing of dates */
307         int openbraces=0;
308
309         q = out;
310         for (p = fmt; *p ; p++) {
311         /* Calculate freespace in output */
312         freespace = outlen - (q - out);
313                 if (freespace <= 1)
314                         break;
315                 c = *p;
316                 if ((c != '%') && (c != '$') && (c != '\\')) {
317                         /*
318                          * We check if we're inside an open brace.  If we are
319                          * then we assume this brace is NOT literal, but is
320                          * a closing brace and apply it
321                          */
322                         if((c == '}') && openbraces) {
323                                 openbraces--;
324                                 continue;
325                         }
326                         *q++ = *p;
327                         continue;
328                 }
329                 if (*++p == '\0') break;
330                 if (c == '\\') switch(*p) {
331                         case '\\':
332                                 *q++ = *p;
333                                 break;
334                         case 't':
335                                 *q++ = '\t';
336                                 break;
337                         case 'n':
338                                 *q++ = '\n';
339                                 break;
340                         default:
341                                 *q++ = c;
342                                 *q++ = *p;
343                                 break;
344
345                 } else if (c == '%') switch(*p) {
346
347                         case '%':
348                                 *q++ = *p;
349                         case 'b': /* last_reset */
350                                 snprintf(tmpdt, sizeof(tmpdt), "%lu", data->last_reset);
351                                 strNcpy(q, tmpdt, freespace);
352                                 q += strlen(q);
353                                 break;
354                         case 'e': /* reset_time */
355                                 snprintf(tmpdt, sizeof(tmpdt), "%lu", data->reset_time);
356                                 strNcpy(q, tmpdt, freespace);
357                                 q += strlen(q);
358                                 break;
359                         case 'k': /* Key Name */
360                                 strNcpy(q, data->key_name, freespace);
361                                 q += strlen(q);
362                                 break;
363                         case 'S': /* SQL module instance */
364                                 strNcpy(q, data->sqlmod_inst, freespace);
365                                 q += strlen(q);
366                                 break;
367                         default:
368                                 *q++ = '%';
369                                 *q++ = *p;
370                                 break;
371                 }
372         }
373         *q = '\0';
374
375         DEBUG2("sqlcounter_expand:  '%s'", out);
376
377         return strlen(out);
378 }
379
380
381 /*
382  *      See if the counter matches.
383  */
384 static int sqlcounter_cmp(void *instance, REQUEST *req,
385                           UNUSED VALUE_PAIR *request, VALUE_PAIR *check,
386                           VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
387 {
388         rlm_sqlcounter_t *data = (rlm_sqlcounter_t *) instance;
389         int counter;
390         char querystr[MAX_QUERY_LEN];
391         char responsestr[MAX_QUERY_LEN];
392
393         check_pairs = check_pairs; /* shut the compiler up */
394         reply_pairs = reply_pairs;
395
396         /* first, expand %k, %b and %e in query */
397         sqlcounter_expand(querystr, MAX_QUERY_LEN, data->query, instance);
398
399         /* second, xlat any request attribs in query */
400         radius_xlat(responsestr, MAX_QUERY_LEN, querystr, req, sql_escape_func);
401
402         /* third, wrap query with sql module call & expand */
403         snprintf(querystr, sizeof(querystr), "%%{%%S:%s}", responsestr);
404         sqlcounter_expand(responsestr, MAX_QUERY_LEN, querystr, instance);
405
406         /* Finally, xlat resulting SQL query */
407         radius_xlat(querystr, MAX_QUERY_LEN, responsestr, req, sql_escape_func);
408
409         counter = atoi(querystr);
410
411         return counter - check->lvalue;
412 }
413
414
415 /*
416  *      Do any per-module initialization that is separate to each
417  *      configured instance of the module.  e.g. set up connections
418  *      to external databases, read configuration files, set up
419  *      dictionary entries, etc.
420  *
421  *      If configuration information is given in the config section
422  *      that must be referenced in later calls, store a handle to it
423  *      in *instance otherwise put a null pointer there.
424  */
425 static int sqlcounter_instantiate(CONF_SECTION *conf, void **instance)
426 {
427         rlm_sqlcounter_t *data;
428         DICT_ATTR *dattr;
429         ATTR_FLAGS flags;
430         time_t now;
431         char buffer[MAX_STRING_LEN];
432
433         /*
434          *      Set up a storage area for instance data
435          */
436         data = rad_malloc(sizeof(*data));
437         if (!data) {
438                 return -1;
439         }
440         memset(data, 0, sizeof(*data));
441
442         /*
443          *      If the configuration parameters can't be parsed, then
444          *      fail.
445          */
446         if (cf_section_parse(conf, data, module_config) < 0) {
447                 free(data);
448                 return -1;
449         }
450
451         /*
452          *      No query, die.
453          */
454         if (data->query == NULL) {
455                 radlog(L_ERR, "rlm_sqlcounter: 'query' must be set.");
456                 return -1;
457         }
458
459         /*
460          *      Safe characters list for sql queries. Everything else is
461          *      replaced with their mime-encoded equivalents.
462          */
463         allowed_chars = data->allowed_chars;
464
465         /*
466          *      Discover the attribute number of the key.
467          */
468         if (data->key_name == NULL) {
469                 radlog(L_ERR, "rlm_sqlcounter: 'key' must be set.");
470                 return -1;
471         }
472         sql_escape_func(buffer, sizeof(buffer), data->key_name);
473         if (strcmp(buffer, data->key_name) != 0) {
474                 radlog(L_ERR, "rlm_sqlcounter: The value for option 'key' is too long or contains unsafe characters.");
475                 return -1;
476         }
477         dattr = dict_attrbyname(data->key_name);
478         if (dattr == NULL) {
479                 radlog(L_ERR, "rlm_sqlcounter: No such attribute %s",
480                                 data->key_name);
481                 return -1;
482         }
483         data->key_attr = dattr->attr;
484
485         /*
486          *      Check the "sqlmod-inst" option.
487          */
488         if (data->sqlmod_inst == NULL) {
489                 radlog(L_ERR, "rlm_sqlcounter: 'sqlmod-inst' must be set.");
490                 return -1;
491         }
492         sql_escape_func(buffer, sizeof(buffer), data->sqlmod_inst);
493         if (strcmp(buffer, data->sqlmod_inst) != 0) {
494                 radlog(L_ERR, "rlm_sqlcounter: The value for option 'sqlmod-inst' is too long or contains unsafe characters.");
495                 return -1;
496         }
497
498         /*
499          *  Create a new attribute for the counter.
500          */
501         if (data->counter_name == NULL) {
502                 radlog(L_ERR, "rlm_sqlcounter: 'counter-name' must be set.");
503                 return -1;
504         }
505
506         memset(&flags, 0, sizeof(flags));
507         dict_addattr(data->counter_name, 0, PW_TYPE_INTEGER, -1, flags);
508         dattr = dict_attrbyname(data->counter_name);
509         if (dattr == NULL) {
510                 radlog(L_ERR, "rlm_sqlcounter: Failed to create counter attribute %s",
511                                 data->counter_name);
512                 return -1;
513         }
514         data->dict_attr = dattr->attr;
515         DEBUG2("rlm_sqlcounter: Counter attribute %s is number %d",
516                         data->counter_name, data->dict_attr);
517
518         /*
519          * Create a new attribute for the check item.
520          */
521         if (data->check_name == NULL) {
522                 radlog(L_ERR, "rlm_sqlcounter: 'check-name' must be set.");
523                 return -1;
524         }
525         dict_addattr(data->check_name, 0, PW_TYPE_INTEGER, -1, flags);
526         dattr = dict_attrbyname(data->check_name);
527         if (dattr == NULL) {
528                 radlog(L_ERR, "rlm_sqlcounter: Failed to create check attribute %s",
529                                 data->counter_name);
530                 return -1;
531         }
532         DEBUG2("rlm_sqlcounter: Check attribute %s is number %d",
533                         data->check_name, dattr->attr);
534
535         /*
536          *  Discover the end of the current time period.
537          */
538         if (data->reset == NULL) {
539                 radlog(L_ERR, "rlm_sqlcounter: 'reset' must be set.");
540                 return -1;
541         }
542         now = time(NULL);
543         data->reset_time = 0;
544
545         if (find_next_reset(data,now) == -1)
546                 return -1;
547
548         /*
549          *  Discover the beginning of the current time period.
550          */
551         data->last_reset = 0;
552
553         if (find_prev_reset(data,now) == -1)
554                 return -1;
555
556
557         /*
558          *      Register the counter comparison operation.
559          */
560         paircompare_register(data->dict_attr, 0, sqlcounter_cmp, data);
561
562         *instance = data;
563
564         return 0;
565 }
566
567 /*
568  *      Find the named user in this modules database.  Create the set
569  *      of attribute-value pairs to check and reply with for this user
570  *      from the database. The authentication code only needs to check
571  *      the password, the rest is done here.
572  */
573 static int sqlcounter_authorize(void *instance, REQUEST *request)
574 {
575         rlm_sqlcounter_t *data = (rlm_sqlcounter_t *) instance;
576         int ret=RLM_MODULE_NOOP;
577         int counter=0;
578         int res=0;
579         DICT_ATTR *dattr;
580         VALUE_PAIR *key_vp, *check_vp;
581         VALUE_PAIR *reply_item;
582         char msg[128];
583         char querystr[MAX_QUERY_LEN];
584         char responsestr[MAX_QUERY_LEN];
585
586         /* quiet the compiler */
587         instance = instance;
588         request = request;
589
590         /*
591          *      Before doing anything else, see if we have to reset
592          *      the counters.
593          */
594         if (data->reset_time && (data->reset_time <= request->timestamp)) {
595
596                 /*
597                  *      Re-set the next time and prev_time for this counters range
598                  */
599                 data->last_reset = data->reset_time;
600                 find_next_reset(data,request->timestamp);
601         }
602
603
604         /*
605          *      Look for the key.  User-Name is special.  It means
606          *      The REAL username, after stripping.
607          */
608         DEBUG2("rlm_sqlcounter: Entering module authorize code");
609         key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
610         if (key_vp == NULL) {
611                 DEBUG2("rlm_sqlcounter: Could not find Key value pair");
612                 return ret;
613         }
614
615         /*
616          *      Look for the check item
617          */
618         if ((dattr = dict_attrbyname(data->check_name)) == NULL) {
619                 return ret;
620         }
621         /* DEBUG2("rlm_sqlcounter: Found Check item attribute %d", dattr->attr); */
622         if ((check_vp= pairfind(request->config_items, dattr->attr)) == NULL) {
623                 DEBUG2("rlm_sqlcounter: Could not find Check item value pair");
624                 return ret;
625         }
626
627         /* first, expand %k, %b and %e in query */
628         sqlcounter_expand(querystr, MAX_QUERY_LEN, data->query, instance);
629
630         /* second, xlat any request attribs in query */
631         radius_xlat(responsestr, MAX_QUERY_LEN, querystr, request, sql_escape_func);
632
633         /* third, wrap query with sql module & expand */
634         snprintf(querystr, sizeof(querystr), "%%{%%S:%s}", responsestr);
635         sqlcounter_expand(responsestr, MAX_QUERY_LEN, querystr, instance);
636
637         /* Finally, xlat resulting SQL query */
638         radius_xlat(querystr, MAX_QUERY_LEN, responsestr, request, sql_escape_func);
639
640         counter = atoi(querystr);
641
642
643         /*
644          * Check if check item > counter
645          */
646         res=check_vp->lvalue - counter;
647         if (res > 0) {
648                 DEBUG2("rlm_sqlcounter: (Check item - counter) is greater than zero");
649                 /*
650                  *      We are assuming that simultaneous-use=1. But
651                  *      even if that does not happen then our user
652                  *      could login at max for 2*max-usage-time Is
653                  *      that acceptable?
654                  */
655
656                 /*
657                  *      User is allowed, but set Session-Timeout.
658                  *      Stolen from main/auth.c
659                  */
660
661                 /*
662                  *      If we are near a reset then add the next
663                  *      limit, so that the user will not need to
664                  *      login again
665                  */
666                 if (data->reset_time && (
667                         res >= (data->reset_time - request->timestamp))) {
668                         res = data->reset_time - request->timestamp;
669                         res += check_vp->lvalue;
670                 }
671
672                 if ((reply_item = pairfind(request->reply->vps, PW_SESSION_TIMEOUT)) != NULL) {
673                         if (reply_item->lvalue > res)
674                                 reply_item->lvalue = res;
675                 } else {
676                         if ((reply_item = paircreate(PW_SESSION_TIMEOUT, PW_TYPE_INTEGER)) == NULL) {
677                                 radlog(L_ERR|L_CONS, "no memory");
678                                 return RLM_MODULE_NOOP;
679                         }
680                         reply_item->lvalue = res;
681                         pairadd(&request->reply->vps, reply_item);
682                 }
683
684                 ret=RLM_MODULE_OK;
685
686                 DEBUG2("rlm_sqlcounter: Authorized user %s, check_item=%d, counter=%d",
687                                 key_vp->vp_strvalue,check_vp->lvalue,counter);
688                 DEBUG2("rlm_sqlcounter: Sent Reply-Item for user %s, Type=Session-Timeout, value=%d",
689                                 key_vp->vp_strvalue,reply_item->lvalue);
690         }
691         else{
692                 char module_fmsg[MAX_STRING_LEN];
693                 VALUE_PAIR *module_fmsg_vp;
694
695                 DEBUG2("rlm_sqlcounter: (Check item - counter) is less than zero");
696
697                 /*
698                  * User is denied access, send back a reply message
699                  */
700                 snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", data->reset);
701                 reply_item=pairmake("Reply-Message", msg, T_OP_EQ);
702                 pairadd(&request->reply->vps, reply_item);
703
704                 snprintf(module_fmsg, sizeof(module_fmsg), "rlm_sqlcounter: Maximum %s usage time reached", data->reset);
705                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
706                 pairadd(&request->packet->vps, module_fmsg_vp);
707
708                 ret=RLM_MODULE_REJECT;
709
710                 DEBUG2("rlm_sqlcounter: Rejected user %s, check_item=%d, counter=%d",
711                                 key_vp->vp_strvalue,check_vp->lvalue,counter);
712         }
713
714         return ret;
715 }
716
717 static int sqlcounter_detach(void *instance)
718 {
719         rlm_sqlcounter_t *data = (rlm_sqlcounter_t *) instance;
720
721         paircompare_unregister(data->dict_attr, sqlcounter_cmp);
722         free(data->reset);
723         free(data->query);
724         free(data->check_name);
725         free(data->sqlmod_inst);
726         free(data->counter_name);
727         free(data->allowed_chars);
728         allowed_chars = NULL;
729
730         free(instance);
731         return 0;
732 }
733
734 /*
735  *      The module name should be the only globally exported symbol.
736  *      That is, everything else should be 'static'.
737  *
738  *      If the module needs to temporarily modify it's instantiation
739  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
740  *      The server will then take care of ensuring that the module
741  *      is single-threaded.
742  */
743 module_t rlm_sqlcounter = {
744         RLM_MODULE_INIT,
745         "SQL Counter",
746         RLM_TYPE_THREAD_SAFE,           /* type */
747         sqlcounter_instantiate,         /* instantiation */
748         sqlcounter_detach,              /* detach */
749         {
750                 NULL,                   /* authentication */
751                 sqlcounter_authorize,   /* authorization */
752                 NULL,                   /* preaccounting */
753                 NULL,                   /* accounting */
754                 NULL,                   /* checksimul */
755                 NULL,                   /* pre-proxy */
756                 NULL,                   /* post-proxy */
757                 NULL                    /* post-auth */
758         },
759 };
760