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