create string only if it's needed
[freeradius.git] / src / modules / rlm_sqlcounter / rlm_sqlcounter.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_sqlcounter.c
20  * @brief Tracks data usage and other counters using SQL.
21  *
22  * @copyright 2001,2006  The FreeRADIUS server project
23  * @copyright 2001  Alan DeKok <aland@ox.org>
24  */
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #include <ctype.h>
32
33 #define MAX_QUERY_LEN 2048
34
35 /*
36  *      Note: When your counter spans more than 1 period (ie 3 months
37  *      or 2 weeks), this module probably does NOT do what you want! It
38  *      calculates the range of dates to count across by first calculating
39  *      the End of the Current period and then subtracting the number of
40  *      periods you specify from that to determine the beginning of the
41  *      range.
42  *
43  *      For example, if you specify a 3 month counter and today is June 15th,
44  *      the end of the current period is June 30. Subtracting 3 months from
45  *      that gives April 1st. So, the counter will sum radacct entries from
46  *      April 1st to June 30. Then, next month, it will sum entries from
47  *      May 1st to July 31st.
48  *
49  *      To fix this behavior, we need to add some way of storing the Next
50  *      Reset Time.
51  */
52
53 /*
54  *      Define a structure for our module configuration.
55  *
56  *      These variables do not need to be in a structure, but it's
57  *      a lot cleaner to do so, and a pointer to the structure can
58  *      be used as the instance handle.
59  */
60 typedef struct rlm_sqlcounter_t {
61         char const      *counter_name;  //!< Daily-Session-Time.
62         char const      *limit_name;    //!< Max-Daily-Session.
63         char const      *reply_name;    //!< Session-Timeout.
64         char const      *key_name;      //!< User-Name.
65         char const      *sqlmod_inst;   //!< Instance of SQL module to use,
66                                         //!< usually just 'sql'.
67         char const      *query;         //!< SQL query to retrieve current
68                                         //!< session time.
69         char const      *reset;         //!< Daily, weekly, monthly,
70                                         //!< never or user defined.
71         time_t          reset_time;
72         time_t          last_reset;
73         DICT_ATTR const *key_attr;      //!< Attribute number for key field.
74         DICT_ATTR const *dict_attr;     //!< Attribute number for the counter.
75         DICT_ATTR const *reply_attr;    //!< Attribute number for the reply.
76 } rlm_sqlcounter_t;
77
78 /*
79  *      A mapping of configuration file names to internal variables.
80  *
81  *      Note that the string is dynamically allocated, so it MUST
82  *      be freed.  When the configuration file parse re-reads the string,
83  *      it free's the old one, and strdup's the new one, placing the pointer
84  *      to the strdup'd string into 'config.string'.  This gets around
85  *      buffer over-flows.
86  */
87 static const CONF_PARSER module_config[] = {
88         { "sql-module-instance", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlcounter_t, sqlmod_inst), NULL },
89         { "sql_module_instance", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlcounter_t, sqlmod_inst), NULL },
90
91         { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_ATTRIBUTE, rlm_sqlcounter_t, key_name), NULL },
92         { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_REQUIRED, rlm_sqlcounter_t, query), NULL },
93         { "reset", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlcounter_t, reset), NULL },
94
95         { "counter-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlcounter_t, counter_name), NULL },
96         { "counter_name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlcounter_t, counter_name), NULL },
97
98         { "check-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlcounter_t, limit_name), NULL },
99         { "check_name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlcounter_t, limit_name), NULL },
100
101         { "reply-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlcounter_t, reply_name), NULL },
102         { "reply_name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_ATTRIBUTE, rlm_sqlcounter_t, reply_name), "Session-Timeout" },
103         CONF_PARSER_TERMINATOR
104 };
105
106 static int find_next_reset(rlm_sqlcounter_t *inst, REQUEST *request, time_t timeval)
107 {
108         int ret = 0;
109         size_t len;
110         unsigned int num = 1;
111         char last = '\0';
112         struct tm *tm, s_tm;
113         char sCurrentTime[40], sNextTime[40];
114
115         tm = localtime_r(&timeval, &s_tm);
116         tm->tm_sec = tm->tm_min = 0;
117
118         rad_assert(inst->reset != NULL);
119
120         /*
121          *      Reset every N hours, days, weeks, months.
122          */
123         if (isdigit((int) inst->reset[0])){
124                 len = strlen(inst->reset);
125                 if (len == 0) return -1;
126
127                 last = inst->reset[len - 1];
128                 if (!isalpha((int) last)) {
129                         last = 'd';
130                 }
131
132                 num = atoi(inst->reset);
133                 DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last);
134         }
135
136         if (strcmp(inst->reset, "hourly") == 0 || last == 'h') {
137                 /*
138                  *  Round up to the next nearest hour.
139                  */
140                 tm->tm_hour += num;
141                 inst->reset_time = mktime(tm);
142
143         } else if (strcmp(inst->reset, "daily") == 0 || last == 'd') {
144                 /*
145                  *  Round up to the next nearest day.
146                  */
147                 tm->tm_hour = 0;
148                 tm->tm_mday += num;
149                 inst->reset_time = mktime(tm);
150
151         } else if (strcmp(inst->reset, "weekly") == 0 || last == 'w') {
152                 /*
153                  *  Round up to the next nearest week.
154                  */
155                 tm->tm_hour = 0;
156                 tm->tm_mday += (7 - tm->tm_wday) +(7*(num-1));
157                 inst->reset_time = mktime(tm);
158
159         } else if (strcmp(inst->reset, "monthly") == 0 || last == 'm') {
160                 tm->tm_hour = 0;
161                 tm->tm_mday = 1;
162                 tm->tm_mon += num;
163                 inst->reset_time = mktime(tm);
164
165         } else if (strcmp(inst->reset, "never") == 0) {
166                 inst->reset_time = 0;
167
168         } else {
169                 return -1;
170         }
171
172         if (!request || (rad_debug_lvl < 2)) return ret;
173
174         len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm);
175         if (len == 0) *sCurrentTime = '\0';
176
177         len = strftime(sNextTime, sizeof(sNextTime),"%Y-%m-%d %H:%M:%S",tm);
178         if (len == 0) *sNextTime = '\0';
179         RDEBUG2("rlm_sqlcounter: Current Time: %" PRId64 " [%s], Next reset %" PRId64 " [%s]",
180                 (int64_t) timeval, sCurrentTime, (int64_t) inst->reset_time, sNextTime);
181
182         return ret;
183 }
184
185
186 /*  I don't believe that this routine handles Daylight Saving Time adjustments
187     properly.  Any suggestions?
188 */
189
190 static int find_prev_reset(rlm_sqlcounter_t *inst, time_t timeval)
191 {
192         int ret = 0;
193         size_t len;
194         unsigned int num = 1;
195         char last = '\0';
196         struct tm *tm, s_tm;
197         char sCurrentTime[40], sPrevTime[40];
198
199         tm = localtime_r(&timeval, &s_tm);
200         len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm);
201         if (len == 0) *sCurrentTime = '\0';
202         tm->tm_sec = tm->tm_min = 0;
203
204         rad_assert(inst->reset != NULL);
205
206         if (isdigit((int) inst->reset[0])){
207                 len = strlen(inst->reset);
208                 if (len == 0)
209                         return -1;
210                 last = inst->reset[len - 1];
211                 if (!isalpha((int) last))
212                         last = 'd';
213                 num = atoi(inst->reset);
214                 DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last);
215         }
216         if (strcmp(inst->reset, "hourly") == 0 || last == 'h') {
217                 /*
218                  *  Round down to the prev nearest hour.
219                  */
220                 tm->tm_hour -= num - 1;
221                 inst->last_reset = mktime(tm);
222
223         } else if (strcmp(inst->reset, "daily") == 0 || last == 'd') {
224                 /*
225                  *  Round down to the prev nearest day.
226                  */
227                 tm->tm_hour = 0;
228                 tm->tm_mday -= num - 1;
229                 inst->last_reset = mktime(tm);
230
231         } else if (strcmp(inst->reset, "weekly") == 0 || last == 'w') {
232                 /*
233                  *  Round down to the prev nearest week.
234                  */
235                 tm->tm_hour = 0;
236                 tm->tm_mday -= tm->tm_wday +(7*(num-1));
237                 inst->last_reset = mktime(tm);
238
239         } else if (strcmp(inst->reset, "monthly") == 0 || last == 'm') {
240                 tm->tm_hour = 0;
241                 tm->tm_mday = 1;
242                 tm->tm_mon -= num - 1;
243                 inst->last_reset = mktime(tm);
244
245         } else if (strcmp(inst->reset, "never") == 0) {
246                 inst->reset_time = 0;
247
248         } else {
249                 return -1;
250         }
251         len = strftime(sPrevTime, sizeof(sPrevTime), "%Y-%m-%d %H:%M:%S", tm);
252         if (len == 0) *sPrevTime = '\0';
253         DEBUG2("rlm_sqlcounter: Current Time: %" PRId64 " [%s], Prev reset %" PRId64 " [%s]",
254                (int64_t) timeval, sCurrentTime, (int64_t) inst->last_reset, sPrevTime);
255
256         return ret;
257 }
258
259
260 /*
261  *      Replace %<whatever> in a string.
262  *
263  *      %b      last_reset
264  *      %e      reset_time
265  *      %k      key_name
266  *      %S      sqlmod_inst
267  *
268  */
269
270 static size_t sqlcounter_expand(char *out, int outlen, char const *fmt, rlm_sqlcounter_t *inst)
271 {
272         int freespace;
273         char const *p;
274         char *q;
275         char tmpdt[40]; /* For temporary storing of dates */
276
277         q = out;
278         p = fmt;
279         while (*p) {
280                 /* Calculate freespace in output */
281                 freespace = outlen - (q - out);
282                 if (freespace <= 1) {
283                         return -1;
284                 }
285
286                 /*
287                  *      Non-% get copied as-is.
288                  */
289                 if (*p != '%') {
290                         *q++ = *p++;
291                         continue;
292                 }
293                 p++;
294                 if (!*p) {      /* % and then EOS --> % */
295                         *q++ = '%';
296                         break;
297                 }
298
299                 if (freespace <= 2) return -1;
300
301                 /*
302                  *      We need TWO %% in a row before we do our expansions.
303                  *      If we only get one, just copy the %s as-is.
304                  */
305                 if (*p != '%') {
306                         *q++ = '%';
307                         *q++ = *p++;
308                         continue;
309                 }
310                 p++;
311                 if (!*p) {
312                         *q++ = '%';
313                         *q++ = '%';
314                         break;
315                 }
316
317                 if (freespace <= 3) return -1;
318
319                 switch (*p) {
320                         case 'b': /* last_reset */
321                                 snprintf(tmpdt, sizeof(tmpdt), "%" PRId64, (int64_t) inst->last_reset);
322                                 strlcpy(q, tmpdt, freespace);
323                                 q += strlen(q);
324                                 p++;
325                                 break;
326                         case 'e': /* reset_time */
327                                 snprintf(tmpdt, sizeof(tmpdt), "%" PRId64, (int64_t) inst->reset_time);
328                                 strlcpy(q, tmpdt, freespace);
329                                 q += strlen(q);
330                                 p++;
331                                 break;
332                         case 'k': /* Key Name */
333                                 WARN("Please replace '%%k' with '${key}'");
334                                 strlcpy(q, inst->key_name, freespace);
335                                 q += strlen(q);
336                                 p++;
337                                 break;
338
339                                 /*
340                                  *      %%s gets copied over as-is.
341                                  */
342                         default:
343                                 *q++ = '%';
344                                 *q++ = '%';
345                                 *q++ = *p++;
346                                 break;
347                 }
348         }
349         *q = '\0';
350
351         DEBUG2("sqlcounter_expand: '%s'", out);
352
353         return strlen(out);
354 }
355
356
357 /*
358  *      See if the counter matches.
359  */
360 static int sqlcounter_cmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *req , VALUE_PAIR *check,
361                           UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
362 {
363         rlm_sqlcounter_t *inst = instance;
364         uint64_t counter;
365
366         char query[MAX_QUERY_LEN], subst[MAX_QUERY_LEN];
367         char *expanded = NULL;
368         size_t len;
369
370         /* First, expand %k, %b and %e in query */
371         if (sqlcounter_expand(subst, sizeof(subst), inst->query, inst) <= 0) {
372                 REDEBUG("Insufficient query buffer space");
373
374                 return RLM_MODULE_FAIL;
375         }
376
377         /* Then combine that with the name of the module were using to do the query */
378         len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, subst);
379         if (len >= sizeof(query) - 1) {
380                 REDEBUG("Insufficient query buffer space");
381
382                 return RLM_MODULE_FAIL;
383         }
384
385         /* Finally, xlat resulting SQL query */
386         if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) {
387                 return RLM_MODULE_FAIL;
388         }
389
390         if (sscanf(expanded, "%" PRIu64, &counter) != 1) {
391                 RDEBUG2("No integer found in string \"%s\"", expanded);
392         }
393         talloc_free(expanded);
394
395         if (counter < check->vp_integer64) {
396                 return -1;
397         }
398         if (counter > check->vp_integer64) {
399                 return 1;
400         }
401         return 0;
402 }
403
404 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
405 {
406         rlm_sqlcounter_t *inst = instance;
407         DICT_ATTR const *da;
408         ATTR_FLAGS flags;
409
410         memset(&flags, 0, sizeof(flags));
411         flags.compare = 1;      /* ugly hack */
412         da = dict_attrbyname(inst->counter_name);
413         if (da && (da->type != PW_TYPE_INTEGER64)) {
414                 cf_log_err_cs(conf, "Counter attribute %s MUST be integer64", inst->counter_name);
415                 return -1;
416         }
417
418         if (!da && (dict_addattr(inst->counter_name, -1, 0, PW_TYPE_INTEGER64, flags) < 0)) {
419                 cf_log_err_cs(conf, "Failed to create counter attribute %s: %s", inst->counter_name, fr_strerror());
420                 return -1;
421         }
422
423         /*
424          *  Register the counter comparison operation.
425          */
426         if (paircompare_register_byname(inst->counter_name, NULL, true, sqlcounter_cmp, inst) < 0) {
427                 cf_log_err_cs(conf, "Failed registering counter attribute %s: %s", inst->counter_name, fr_strerror());
428                 return -1;
429         }
430
431         inst->dict_attr = dict_attrbyname(inst->counter_name);
432         if (!inst->dict_attr) {
433                 cf_log_err_cs(conf, "Failed to find counter attribute %s", inst->counter_name);
434                 return -1;
435         }
436
437         /*
438          *  Create a new attribute for the check item.
439          */
440         flags.compare = 0;
441         if ((dict_addattr(inst->limit_name, -1, 0, PW_TYPE_INTEGER64, flags) < 0) ||
442             !dict_attrbyname(inst->limit_name)) {
443                 cf_log_err_cs(conf, "Failed to create check attribute %s: %s", inst->limit_name, fr_strerror());
444                 return -1;
445         }
446
447         return 0;
448 }
449
450 /*
451  *      Do any per-module initialization that is separate to each
452  *      configured instance of the module.  e.g. set up connections
453  *      to external databases, read configuration files, set up
454  *      dictionary entries, etc.
455  *
456  *      If configuration information is given in the config section
457  *      that must be referenced in later calls, store a handle to it
458  *      in *instance otherwise put a null pointer there.
459  */
460 static int mod_instantiate(CONF_SECTION *conf, void *instance)
461 {
462         rlm_sqlcounter_t *inst = instance;
463         DICT_ATTR const *da;
464         time_t now;
465
466         rad_assert(inst->query && *inst->query);
467
468         da = dict_attrbyname(inst->key_name);
469         if (!da) {
470                 cf_log_err_cs(conf, "Invalid attribute '%s'", inst->key_name);
471                 return -1;
472         }
473         inst->key_attr = da;
474
475         da = dict_attrbyname(inst->reply_name);
476         if (!da) {
477                 cf_log_err_cs(conf, "Invalid attribute '%s'", inst->reply_name);
478                 return -1;
479         }
480         inst->reply_attr = da;
481
482         now = time(NULL);
483         inst->reset_time = 0;
484
485         if (find_next_reset(inst, NULL, now) < 0) {
486                 cf_log_err_cs(conf, "Invalid reset '%s'", inst->reset);
487                 return -1;
488         }
489
490         /*
491          *  Discover the beginning of the current time period.
492          */
493         inst->last_reset = 0;
494
495         if (find_prev_reset(inst, now) < 0) {
496                 cf_log_err_cs(conf, "Invalid reset '%s'", inst->reset);
497                 return -1;
498         }
499
500         return 0;
501 }
502
503 /*
504  *      Find the named user in this modules database.  Create the set
505  *      of attribute-value pairs to check and reply with for this user
506  *      from the database. The authentication code only needs to check
507  *      the password, the rest is done here.
508  */
509 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
510 {
511         rlm_sqlcounter_t *inst = instance;
512         int rcode = RLM_MODULE_NOOP;
513         uint64_t counter, res;
514         DICT_ATTR const *da;
515         VALUE_PAIR *key_vp, *limit;
516         VALUE_PAIR *reply_item;
517         char msg[128];
518
519         char query[MAX_QUERY_LEN], subst[MAX_QUERY_LEN];
520         char *expanded = NULL;
521
522         size_t len;
523
524         /*
525          *      Before doing anything else, see if we have to reset
526          *      the counters.
527          */
528         if (inst->reset_time && (inst->reset_time <= request->timestamp)) {
529                 /*
530                  *      Re-set the next time and prev_time for this counters range
531                  */
532                 inst->last_reset = inst->reset_time;
533                 find_next_reset(inst, request, request->timestamp);
534         }
535
536         /*
537          *      Look for the key.  User-Name is special.  It means
538          *      The REAL username, after stripping.
539          */
540         if ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) {
541                 key_vp = request->username;
542         } else {
543                 key_vp = fr_pair_find_by_da(request->packet->vps, inst->key_attr, TAG_ANY);
544         }
545         if (!key_vp) {
546                 RWDEBUG2("Couldn't find key attribute, request:%s, doing nothing...", inst->key_attr->name);
547                 return rcode;
548         }
549
550         /*
551          *      Look for the check item
552          */
553         if ((da = dict_attrbyname(inst->limit_name)) == NULL) {
554                 return rcode;
555         }
556
557         limit = fr_pair_find_by_da(request->config, da, TAG_ANY);
558         if (limit == NULL) {
559                 /* Yes this really is 'check' as distinct from control */
560                 RWDEBUG2("Couldn't find check attribute, control:%s, doing nothing...", inst->limit_name);
561                 return rcode;
562         }
563
564         /* First, expand %k, %b and %e in query */
565         if (sqlcounter_expand(subst, sizeof(subst), inst->query, inst) <= 0) {
566                 REDEBUG("Insufficient query buffer space");
567
568                 return RLM_MODULE_FAIL;
569         }
570
571         /* Then combine that with the name of the module were using to do the query */
572         len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, subst);
573         if (len >= (sizeof(query) - 1)) {
574                 REDEBUG("Insufficient query buffer space");
575
576                 return RLM_MODULE_FAIL;
577         }
578
579         /* Finally, xlat resulting SQL query */
580         if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) {
581                 return RLM_MODULE_FAIL;
582         }
583         talloc_free(expanded);
584
585         if (sscanf(expanded, "%" PRIu64, &counter) != 1) {
586                 RDEBUG2("No integer found in result string \"%s\".  May be first session, setting counter to 0",
587                         expanded);
588                 counter = 0;
589         }
590
591         /*
592          *      Check if check item > counter
593          */
594         if (limit->vp_integer64 <= counter) {
595                 /* User is denied access, send back a reply message */
596                 snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset);
597                 pair_make_reply("Reply-Message", msg, T_OP_EQ);
598
599                 REDEBUG2("Maximum %s usage time reached", inst->reset);
600                 REDEBUG2("Rejecting user, &control:%s value (%" PRIu64 ") is less than counter value (%" PRIu64 ")",
601                          inst->limit_name, limit->vp_integer64, counter);
602
603                 return RLM_MODULE_REJECT;
604         }
605
606         res = limit->vp_integer64 - counter;
607         RDEBUG2("Allowing user, &control:%s value (%" PRIu64 ") is greater than counter value (%" PRIu64 ")",
608                 inst->limit_name, limit->vp_integer64, counter);
609         /*
610          *      We are assuming that simultaneous-use=1. But
611          *      even if that does not happen then our user
612          *      could login at max for 2*max-usage-time Is
613          *      that acceptable?
614          */
615
616         /*
617          *      If we are near a reset then add the next
618          *      limit, so that the user will not need to login
619          *      again.  Do this only for Session-Timeout.
620          */
621         if (((inst->reply_attr->vendor == 0) && (inst->reply_attr->attr == PW_SESSION_TIMEOUT)) &&
622             inst->reset_time && (res >= (uint64_t)(inst->reset_time - request->timestamp))) {
623                 uint64_t to_reset = inst->reset_time - request->timestamp;
624
625                 RDEBUG2("Time remaining (%" PRIu64 "s) is greater than time to reset (%" PRIu64 "s).  "
626                         "Adding %" PRIu64 "s to reply value", to_reset, res, to_reset);
627                 res = to_reset + limit->vp_integer;
628         }
629
630         /*
631          *      Limit the reply attribute to the minimum of the existing value, or this new one.
632          */
633         reply_item = fr_pair_find_by_da(request->reply->vps, inst->reply_attr, TAG_ANY);
634         if (reply_item) {
635                 if (reply_item->vp_integer64 <= res) {
636                         RDEBUG2("Leaving existing &reply:%s value of %" PRIu64, inst->reply_attr->name,
637                                 reply_item->vp_integer64);
638
639                         return RLM_MODULE_OK;
640                 }
641         } else {
642                 reply_item = radius_pair_create(request->reply, &request->reply->vps, inst->reply_attr->attr,
643                                                inst->reply_attr->vendor);
644         }
645         reply_item->vp_integer64 = res;
646
647         RDEBUG2("Setting &reply:%s value to %" PRIu64, inst->reply_name, reply_item->vp_integer64);
648
649         return RLM_MODULE_OK;
650 }
651
652 /*
653  *      The module name should be the only globally exported symbol.
654  *      That is, everything else should be 'static'.
655  *
656  *      If the module needs to temporarily modify it's instantiation
657  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
658  *      The server will then take care of ensuring that the module
659  *      is single-threaded.
660  */
661 extern module_t rlm_sqlcounter;
662 module_t rlm_sqlcounter = {
663         .magic          = RLM_MODULE_INIT,
664         .name           = "sqlcounter",
665         .type           = RLM_TYPE_THREAD_SAFE,
666         .inst_size      = sizeof(rlm_sqlcounter_t),
667         .config         = module_config,
668         .bootstrap      = mod_bootstrap,
669         .instantiate    = mod_instantiate,
670         .methods = {
671                 [MOD_AUTHORIZE]         = mod_authorize
672         },
673 };
674