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