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