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