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