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