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