36c3066cd37265cef22164d49f664f7d2a34ae4e
[freeradius.git] / src / modules / rlm_sql / rlm_sql.c
1 /*
2  * rlm_sql.c            SQL Module
3  *              Main SQL module file. Most ICRADIUS code is located in sql.c
4  *
5  * Version:     $Id$
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * Copyright 2000  The FreeRADIUS server project
22  * Copyright 2000  Mike Machado <mike@innercite.com>
23  * Copyright 2000  Alan DeKok <aland@ox.org>
24  */
25
26 static const char rcsid[] =
27         "$Id$";
28
29 #include "autoconf.h"
30
31 #include <stdio.h>
32 #include <sys/stat.h>
33 #include <stdlib.h>
34
35 #include <time.h>
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <string.h>
40
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44
45 #include "radiusd.h"
46 #include "modules.h"
47 #include "conffile.h"
48 #include "rlm_sql.h"
49
50 static CONF_PARSER module_config[] = {
51         {"driver",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
52         {"server",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
53         {"port",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_port), NULL, ""},
54         {"login", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_login), NULL, ""},
55         {"password", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_password), NULL, ""},
56         {"radius_db", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
57         {"acct_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_acct_table), NULL, "radacct"},
58         {"acct_table2", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_acct_table2), NULL, "radacct"},
59         {"authcheck_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_authcheck_table), NULL, "radcheck"},
60         {"authreply_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_authreply_table), NULL, "radreply"},
61         {"groupcheck_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_groupcheck_table), NULL, "radgroupcheck"},
62         {"groupreply_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_groupreply_table), NULL, "radgroupreply"},
63         {"usergroup_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_usergroup_table), NULL, "usergroup"},
64         {"nas_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_nas_table), NULL, "nas"},
65         {"dict_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_dict_table), NULL, "dictionary"},
66         {"sqltrace", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,sqltrace), NULL, "0"},
67         {"sqltracefile", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,tracefile), NULL, SQLTRACEFILE},
68         {"deletestalesessions", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,deletestalesessions), NULL, "0"},
69         {"num_sql_socks", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,num_sql_socks), NULL, "5"},
70         {"sql_user_name", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,query_user), NULL, ""},
71         {"authorize_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
72         {"authorize_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_reply_query), NULL, ""},
73         {"authorize_group_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
74         {"authorize_group_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
75         {"authenticate_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authenticate_query), NULL, ""},
76         {"accounting_onoff_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_onoff_query), NULL, ""},
77         {"accounting_update_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_update_query), NULL, ""},
78         {"accounting_start_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_start_query), NULL, ""},
79         {"accounting_start_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_start_query_alt), NULL, ""},
80         {"accounting_stop_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query), NULL, ""},
81         {"accounting_stop_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query_alt), NULL, ""},
82         {"connect_failure_retry_delay", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,connect_failure_retry_delay), NULL, "60"},
83
84         {NULL, -1, 0, NULL, NULL}
85 };
86
87 /***********************************************************************
88  * start of main routines
89  ***********************************************************************/
90 static int rlm_sql_init(void) {
91
92         /*
93          * FIXME:
94          * We should put the sqlsocket array here once
95          * the module code is reworked to not unload
96          * modules on HUP.  This way we can have
97          * persistant connections.  -jcarneal
98          */
99         return 0;
100 }
101
102
103 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) {
104
105         SQL_INST *inst;
106         lt_dlhandle *handle;
107
108         inst = rad_malloc(sizeof(SQL_INST));
109         memset(inst, 0, sizeof(SQL_INST));
110
111         inst->config = rad_malloc(sizeof(SQL_CONFIG));
112         memset(inst->config, 0, sizeof(SQL_CONFIG));
113
114         /*
115          * If the configuration parameters can't be parsed, then
116          * fail.
117          */
118         if (cf_section_parse(conf, inst->config, module_config) < 0) {
119                 free(inst->config);
120                 free(inst);
121                 return -1;
122         }
123
124         if (inst->config->num_sql_socks > MAX_SQL_SOCKS) {
125                 radlog(L_ERR | L_CONS, "sql_instantiate:  number of sqlsockets cannot exceed %d", MAX_SQL_SOCKS);
126                 free(inst->config);
127                 free(inst);
128                 return -1;
129         }
130
131         handle = lt_dlopenext(inst->config->sql_driver);
132         if (handle == NULL) {
133                 radlog(L_ERR, "rlm_sql: Could not link driver %s: %s", inst->config->sql_driver, lt_dlerror());
134                 return -1;
135         }
136
137         inst->module = (rlm_sql_module_t *) lt_dlsym(handle, inst->config->sql_driver);
138         if (!inst->module) {
139                 radlog(L_ERR, "rlm_sql: Could not link symbol %s: %s", inst->config->sql_driver, lt_dlerror());
140                 return -1;
141         }
142
143         radlog(L_INFO, "rlm_sql: Driver %s loaded and linked", inst->config->sql_driver);
144         radlog(L_INFO, "rlm_sql: Attempting to connect to %s@%s:%s/%s", inst->config->sql_login, inst->config->sql_server, inst->config->sql_port, inst->config->sql_db);
145
146         if (sql_init_socketpool(inst) < 0) {
147                 free(inst->config);
148                 free(inst);
149                 return -1;
150         }
151
152         *instance = inst;
153
154         return RLM_MODULE_OK;
155 }
156
157 static int rlm_sql_destroy(void) {
158
159         return 0;
160 }
161
162 static int rlm_sql_detach(void *instance) {
163
164         SQL_INST *inst = instance;
165
166         sql_poolfree(inst);
167         free(inst->config);
168         free(inst);
169
170         return 0;
171 }
172
173
174 static int rlm_sql_authorize(void *instance, REQUEST * request) {
175
176         VALUE_PAIR *check_tmp = NULL;
177         VALUE_PAIR *reply_tmp = NULL;
178         int     found = 0;
179         SQLSOCK *sqlsocket;
180         SQL_INST *inst = instance;
181         char    querystr[MAX_QUERY_LEN];
182
183         /* sqlusername holds the sql escaped username. The original
184          * username is at most MAX_STRING_LEN chars long and
185          * *sql_escape_string doubles its length in the worst case.
186          * Throw in an extra 10 to account for trailing NULs and to have
187          * a safety margin. */
188         char   sqlusername[2 * MAX_STRING_LEN + 10];
189
190         /*
191          *      They MUST have a user name to do SQL authorization.
192          */
193         if ((!request->username) ||
194                         (request->username->length == 0)) {
195                 radlog(L_ERR, "zero length username not permitted\n");
196                 return RLM_MODULE_INVALID;
197         }
198
199         sqlsocket = sql_get_socket(inst);
200         if (sqlsocket == NULL)
201                 return(RLM_MODULE_NOOP);
202
203         /*
204          *  After this point, ALL 'return's MUST release the SQL socket!
205          */
206
207         /*
208          * Set, escape, and check the user attr here
209          */
210         if(sql_set_user(inst, request, sqlusername, 0) < 0) {
211                 sql_release_socket(inst, sqlsocket);
212                 return RLM_MODULE_FAIL;
213         }
214         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_check_query, request, NULL);
215         found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_USERDATA);
216         /*
217          *      Find the entry for the user.
218          */
219         if (found > 0) {
220                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_check_query, request, NULL);
221                 sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
222                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_reply_query, request, NULL);
223                 sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_USERDATA);
224                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_reply_query, request, NULL);
225                 sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
226         } else if (found < 0) {
227                 radlog(L_ERR, "rlm_sql:  SQL query error; rejecting user");
228                 sql_release_socket(inst, sqlsocket);
229                 /* Remove the username we (maybe) added above */
230                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
231                 return RLM_MODULE_INVALID;
232
233         } else {
234
235                 int     gcheck;
236
237                 /*
238                  * We didn't find the user, so we try looking
239                  * for a DEFAULT entry
240                  */
241                 if(sql_set_user(inst, request, sqlusername, "DEFAULT") < 0) {
242                         sql_release_socket(inst, sqlsocket);
243                         return RLM_MODULE_FAIL;
244                 }
245
246                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_check_query, request, NULL);
247                 gcheck = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
248                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_reply_query, request, NULL);
249                 gcheck = sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
250                 if (gcheck)
251                         found = 1;
252         }
253         /* Remove the username we (maybe) added above */
254         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
255
256         sql_release_socket(inst, sqlsocket);
257
258         if (!found) {
259                 radlog(L_DBG, "rlm_sql: User %s not found and DEFAULT not found", sqlusername);
260                 return RLM_MODULE_NOTFOUND;
261         }
262
263 #if 0 /* FIXME: Debug being printed elsewhere? */
264         /*
265          * Uncomment these lines for debugging
266          * Recompile, and run 'radiusd -X'
267          *
268          DEBUG2("rlm_sql:  check items");
269          vp_printlist(stderr, check_tmp);
270          DEBUG2("rlm_sql:  reply items");
271          vp_printlist(stderr, reply_tmp);
272          */
273 #endif
274
275 #if 0 /* FIXME: this is the *real* authorizing */
276         vp_printlist(stderr, check_tmp);
277 #endif
278         if (paircmp(request->packet->vps, check_tmp, &reply_tmp) != 0) {
279                 radlog(L_INFO, "rlm_sql: Pairs do not match [%s]", sqlusername);
280                 return RLM_MODULE_NOTFOUND;
281         }
282
283         pairmove(&request->reply->vps, &reply_tmp);
284         pairmove(&request->config_items, &check_tmp);
285         pairfree(&reply_tmp);
286         pairfree(&check_tmp);
287
288         return RLM_MODULE_OK;
289 }
290
291 static int rlm_sql_authenticate(void *instance, REQUEST * request) {
292
293         char   sqlusername[MAX_STRING_LEN];
294         char    querystr[MAX_QUERY_LEN];
295         SQL_ROW row;
296         SQLSOCK *sqlsocket;
297         SQL_INST *inst = instance;
298
299         /*
300          *      Ensure that a password attribute exists.
301          */
302         if ((request->password == NULL) ||
303                         (request->password->length == 0) ||
304                         (request->password->attribute != PW_PASSWORD)) {
305                 radlog(L_AUTH, "rlm_sql: Attribute \"Password\" is required for authentication.");
306                 return RLM_MODULE_INVALID;
307         }
308
309         sqlsocket = sql_get_socket(inst);
310         if (sqlsocket == NULL)
311                 return(RLM_MODULE_NOOP);
312
313         /*
314          *  After this point, ALL 'return's MUST release the SQL socket!
315          */
316
317         /*
318          * 1. Set username to escaped value
319          * 2. Translate vars in the query
320          * 3. Remove SQL-User-Name local attr
321          */
322         if(sql_set_user(inst, request, sqlusername, 0) < 0) {
323                 sql_release_socket(inst, sqlsocket);
324                 return RLM_MODULE_FAIL;
325         }
326
327         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authenticate_query, request, NULL);
328         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
329
330         if ((inst->module->sql_select_query)(sqlsocket, inst->config, querystr) < 0) {
331                 radlog(L_ERR, "rlm_sql_authenticate: database query error");
332                 sql_release_socket(inst, sqlsocket);
333                 return RLM_MODULE_REJECT;
334         }
335
336         row = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
337         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
338         sql_release_socket(inst, sqlsocket);
339
340         if (row == NULL) {
341                 radlog(L_ERR, "rlm_sql_authenticate: no rows returned from query (no such user)");
342                 return RLM_MODULE_REJECT;
343         }
344
345         /* If this is a null the server will seg fault */
346         if (row[0] == NULL) {
347                 radlog(L_ERR, "rlm_sql_authenticate: row[0] returned null.");
348                 return RLM_MODULE_REJECT;
349         }
350
351         /*
352          * Just compare the two, considering "Attribute" as second value
353          * If the attribute name is "Crypt-Password", we assume an encrypted pasword in DB
354          * Otherwise we should have a plain password in DB
355          */
356         if ((strcmp(row[1], "Crypt-Password") == 0) &&
357                         (strcmp(crypt(request->password->strvalue, row[0]), row[0]) == 0)) {
358                 return RLM_MODULE_OK;
359         } else if ((request->password->length == strlen(row[0])) && 
360                         (strcmp(request->password->strvalue, row[0]) == 0)) {
361                 return RLM_MODULE_OK;
362         }
363         return RLM_MODULE_REJECT;
364
365 }
366
367 /*
368  *      Accounting: save the account data to our sql table
369  */
370 static int rlm_sql_accounting(void *instance, REQUEST * request) {
371
372         SQLSOCK *sqlsocket;
373         VALUE_PAIR *pair;
374         SQL_INST *inst = instance;
375         int     numaffected = 0;
376         int     acctstatustype = 0;
377         char    querystr[MAX_QUERY_LEN];
378         char    logstr[MAX_QUERY_LEN];
379         char    sqlusername[MAX_STRING_LEN];
380
381 #ifdef CISCO_ACCOUNTING_HACK
382         int     acctsessiontime = 0;
383 #endif
384
385         sqlsocket = sql_get_socket(inst);
386         if (sqlsocket == NULL)
387                 return(RLM_MODULE_NOOP);
388
389         /*
390          *  After this point, ALL 'return's MUST release the SQL socket!
391          */
392
393         memset(querystr, 0, MAX_QUERY_LEN);
394
395         /*
396          * Find the Acct Status Type
397          */
398         if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
399                 acctstatustype = pair->lvalue;
400         } else {
401                 radius_xlat(logstr, MAX_QUERY_LEN, "rlm_sql:  packet has no account status type.  [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
402                 radlog(L_ERR, logstr);
403                 sql_release_socket(inst, sqlsocket);
404                 return RLM_MODULE_INVALID;
405         }
406
407 #ifdef CISCO_ACCOUNTING_HACK
408         /*
409          * If stop but zero session length AND no previous
410          * session found, drop it as in invalid packet 
411          * This is to fix CISCO's aaa from filling our  
412          * table with bogus crap
413          */
414         if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL)
415                 acctsessiontime = pair->lvalue;
416
417         if ((acctsessiontime <= 0) && (acctstatustype == PW_STATUS_STOP)) {
418                 radius_xlat(logstr, MAX_QUERY_LEN, "rlm_sql:  Stop packet with zero session" " length.  (user '%{User-Name}', nas '%{NAS-IP-Address}')", request, NULL);
419                 radlog(L_ERR, logstr);
420                 sql_release_socket(inst, sqlsocket);
421                 return RLM_MODULE_FAIL;
422         }
423 #endif
424
425         switch (acctstatustype) {
426                         /*
427                          * The Terminal server informed us that it was rebooted
428                          * STOP all records from this NAS 
429                          */
430                 case PW_STATUS_ACCOUNTING_ON:
431                 case PW_STATUS_ACCOUNTING_OFF:
432                         radlog(L_INFO, "rlm_sql:  received Acct On/Off packet");
433                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_onoff_query, request, NULL);
434                         query_log(inst, querystr);
435
436                         if (querystr) {
437                                 if ((inst->module->sql_query)(sqlsocket, inst->config, querystr) < 0)
438                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for ALIVE packet - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
439                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
440                         }
441
442                         break;
443
444                         /*
445                          * Got an update accounting packet
446                          */
447                 case PW_STATUS_ALIVE:
448
449                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_update_query, request, NULL);
450                         query_log(inst, querystr);
451
452                         if (querystr) {
453                                 if ((inst->module->sql_query)(sqlsocket, inst->config, querystr) < 0)
454                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for ALIVE packet - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
455                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
456                         }
457
458                         break;
459
460                         /*
461                          * Got accounting start packet
462                          */
463                 case PW_STATUS_START:
464
465                         /*
466                          * Set, escape, and check the user attr here
467                          */
468                         if(sql_set_user(inst, request, sqlusername, 0) < 0) {
469                                 sql_release_socket(inst, sqlsocket);
470                                 return RLM_MODULE_FAIL;
471                         }
472
473                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_start_query, request, NULL);
474                         query_log(inst, querystr);
475
476                         if (querystr) {
477                                 if ((inst->module->sql_query)(sqlsocket, inst->config, querystr) < 0) {
478                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting" " for ALIVE packet - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
479                                         (inst->module->sql_finish_query)(sqlsocket, inst->config);
480
481                                         /*
482                                          * We failed the insert above.  It's probably because 
483                                          * the stop record came before the start.  We try an
484                                          * our alternate query now (typically an UPDATE)
485                                          */
486                                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_start_query_alt, request, NULL);
487                                         query_log(inst, querystr);
488
489                                         if (querystr) {
490                                                 if ((inst->module->sql_query)(sqlsocket, inst->config, querystr) < 0) {
491                                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL" "accounting START record - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
492                                                 }
493                                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
494                                         }
495                                 }
496                         }
497                         break;
498
499                         /*
500                          * Got accounting stop packet
501                          */
502                 case PW_STATUS_STOP:
503
504                         /*
505                          * Set, escape, and check the user attr here
506                          */
507                         if(sql_set_user(inst, request, sqlusername, 0) < 0) {
508                                 sql_release_socket(inst, sqlsocket);
509                                 return RLM_MODULE_FAIL;
510                         }
511
512                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_stop_query, request, NULL);
513                         query_log(inst, querystr);
514
515                         if (querystr) {
516                                 if ((inst->module->sql_query)(sqlsocket, inst->config, querystr) < 0) {
517                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting START record - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
518                                 }
519                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
520                         }
521
522                         numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
523                         if (numaffected < 1) {
524                                 /*
525                                  * If our update above didn't match anything
526                                  * we assume it's because we haven't seen a 
527                                  * matching Start record.  So we have to
528                                  * insert this stop rather than do an update
529                                  */
530                                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_stop_query_alt, request, NULL);
531                                 query_log(inst, querystr);
532
533                                 if (querystr) {
534                                         if ((inst->module->sql_query)(sqlsocket, inst->config, querystr) < 0) {
535                                                 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting STOP record - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
536                                         }
537                                         (inst->module->sql_finish_query)(sqlsocket, inst->config);
538                                 }
539                         }
540                         break;
541         }
542
543         sql_release_socket(inst, sqlsocket);
544
545         return RLM_MODULE_OK;
546 }
547
548
549 /* globally exported name */
550 module_t rlm_sql = {
551         "SQL",
552         RLM_TYPE_THREAD_SAFE,   /* type: reserved */
553         rlm_sql_init,           /* initialization */
554         rlm_sql_instantiate,    /* instantiation */
555         {
556                 rlm_sql_authenticate,   /* authentication */
557                 rlm_sql_authorize,      /* authorization */
558                 NULL,                   /* preaccounting */
559                 rlm_sql_accounting,     /* accounting */
560                 NULL                    /* checksimul */
561         },
562         rlm_sql_detach,         /* detach */
563         rlm_sql_destroy,        /* destroy */
564 };