3 * Main SQL module file. Most ICRADIUS code is located in sql.c
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.
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.
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
21 * Copyright 2000 The FreeRADIUS server project
22 * Copyright 2000 Mike Machado <mike@innercite.com>
23 * Copyright 2000 Alan DeKok <aland@ox.org>
26 static const char rcsid[] =
37 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
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"},
84 {NULL, -1, 0, NULL, NULL}
87 /***********************************************************************
88 * start of main routines
89 ***********************************************************************/
90 static int rlm_sql_init(void) {
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
103 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) {
108 inst = rad_malloc(sizeof(SQL_INST));
109 memset(inst, 0, sizeof(SQL_INST));
111 inst->config = rad_malloc(sizeof(SQL_CONFIG));
112 memset(inst->config, 0, sizeof(SQL_CONFIG));
115 * If the configuration parameters can't be parsed, then
118 if (cf_section_parse(conf, inst->config, module_config) < 0) {
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);
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());
137 inst->module = (rlm_sql_module_t *) lt_dlsym(handle, inst->config->sql_driver);
139 radlog(L_ERR, "rlm_sql: Could not link symbol %s: %s", inst->config->sql_driver, lt_dlerror());
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);
146 if (sql_init_socketpool(inst) < 0) {
154 return RLM_MODULE_OK;
157 static int rlm_sql_destroy(void) {
162 static int rlm_sql_detach(void *instance) {
164 SQL_INST *inst = instance;
174 static int rlm_sql_authorize(void *instance, REQUEST * request) {
176 VALUE_PAIR *check_tmp = NULL;
177 VALUE_PAIR *reply_tmp = NULL;
180 SQL_INST *inst = instance;
181 char querystr[MAX_QUERY_LEN];
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];
191 * They MUST have a user name to do SQL authorization.
193 if ((!request->username) ||
194 (request->username->length == 0)) {
195 radlog(L_ERR, "zero length username not permitted\n");
196 return RLM_MODULE_INVALID;
199 sqlsocket = sql_get_socket(inst);
200 if (sqlsocket == NULL)
201 return(RLM_MODULE_NOOP);
204 * After this point, ALL 'return's MUST release the SQL socket!
208 * Set, escape, and check the user attr here
210 if(sql_set_user(inst, request, sqlusername, 0) < 0) {
211 sql_release_socket(inst, sqlsocket);
212 return RLM_MODULE_FAIL;
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);
217 * Find the entry for the user.
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;
238 * We didn't find the user, so we try looking
239 * for a DEFAULT entry
241 if(sql_set_user(inst, request, sqlusername, "DEFAULT") < 0) {
242 sql_release_socket(inst, sqlsocket);
243 return RLM_MODULE_FAIL;
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);
253 /* Remove the username we (maybe) added above */
254 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
256 sql_release_socket(inst, sqlsocket);
259 radlog(L_DBG, "rlm_sql: User %s not found and DEFAULT not found", sqlusername);
260 return RLM_MODULE_NOTFOUND;
263 #if 0 /* FIXME: Debug being printed elsewhere? */
265 * Uncomment these lines for debugging
266 * Recompile, and run 'radiusd -X'
268 DEBUG2("rlm_sql: check items");
269 vp_printlist(stderr, check_tmp);
270 DEBUG2("rlm_sql: reply items");
271 vp_printlist(stderr, reply_tmp);
275 #if 0 /* FIXME: this is the *real* authorizing */
276 vp_printlist(stderr, check_tmp);
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;
283 pairmove(&request->reply->vps, &reply_tmp);
284 pairmove(&request->config_items, &check_tmp);
285 pairfree(&reply_tmp);
286 pairfree(&check_tmp);
288 return RLM_MODULE_OK;
291 static int rlm_sql_authenticate(void *instance, REQUEST * request) {
293 char sqlusername[MAX_STRING_LEN];
294 char querystr[MAX_QUERY_LEN];
297 SQL_INST *inst = instance;
300 * Ensure that a password attribute exists.
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;
309 sqlsocket = sql_get_socket(inst);
310 if (sqlsocket == NULL)
311 return(RLM_MODULE_NOOP);
314 * After this point, ALL 'return's MUST release the SQL socket!
318 * 1. Set username to escaped value
319 * 2. Translate vars in the query
320 * 3. Remove SQL-User-Name local attr
322 if(sql_set_user(inst, request, sqlusername, 0) < 0) {
323 sql_release_socket(inst, sqlsocket);
324 return RLM_MODULE_FAIL;
327 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authenticate_query, request, NULL);
328 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
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;
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);
341 radlog(L_ERR, "rlm_sql_authenticate: no rows returned from query (no such user)");
342 return RLM_MODULE_REJECT;
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;
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
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;
363 return RLM_MODULE_REJECT;
368 * Accounting: save the account data to our sql table
370 static int rlm_sql_accounting(void *instance, REQUEST * request) {
374 SQL_INST *inst = instance;
376 int acctstatustype = 0;
377 char querystr[MAX_QUERY_LEN];
378 char logstr[MAX_QUERY_LEN];
379 char sqlusername[MAX_STRING_LEN];
381 #ifdef CISCO_ACCOUNTING_HACK
382 int acctsessiontime = 0;
385 sqlsocket = sql_get_socket(inst);
386 if (sqlsocket == NULL)
387 return(RLM_MODULE_NOOP);
390 * After this point, ALL 'return's MUST release the SQL socket!
393 memset(querystr, 0, MAX_QUERY_LEN);
396 * Find the Acct Status Type
398 if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
399 acctstatustype = pair->lvalue;
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;
407 #ifdef CISCO_ACCOUNTING_HACK
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
414 if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL)
415 acctsessiontime = pair->lvalue;
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;
425 switch (acctstatustype) {
427 * The Terminal server informed us that it was rebooted
428 * STOP all records from this NAS
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);
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);
445 * Got an update accounting packet
447 case PW_STATUS_ALIVE:
449 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_update_query, request, NULL);
450 query_log(inst, 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);
461 * Got accounting start packet
463 case PW_STATUS_START:
466 * Set, escape, and check the user attr here
468 if(sql_set_user(inst, request, sqlusername, 0) < 0) {
469 sql_release_socket(inst, sqlsocket);
470 return RLM_MODULE_FAIL;
473 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_start_query, request, NULL);
474 query_log(inst, 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);
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)
486 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_start_query_alt, request, NULL);
487 query_log(inst, 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));
493 (inst->module->sql_finish_query)(sqlsocket, inst->config);
500 * Got accounting stop packet
505 * Set, escape, and check the user attr here
507 if(sql_set_user(inst, request, sqlusername, 0) < 0) {
508 sql_release_socket(inst, sqlsocket);
509 return RLM_MODULE_FAIL;
512 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_stop_query, request, NULL);
513 query_log(inst, 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));
519 (inst->module->sql_finish_query)(sqlsocket, inst->config);
522 numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
523 if (numaffected < 1) {
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
530 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_stop_query_alt, request, NULL);
531 query_log(inst, 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));
537 (inst->module->sql_finish_query)(sqlsocket, inst->config);
543 sql_release_socket(inst, sqlsocket);
545 return RLM_MODULE_OK;
549 /* globally exported name */
552 RLM_TYPE_THREAD_SAFE, /* type: reserved */
553 rlm_sql_init, /* initialization */
554 rlm_sql_instantiate, /* instantiation */
556 rlm_sql_authenticate, /* authentication */
557 rlm_sql_authorize, /* authorization */
558 NULL, /* preaccounting */
559 rlm_sql_accounting, /* accounting */
560 NULL /* checksimul */
562 rlm_sql_detach, /* detach */
563 rlm_sql_destroy, /* destroy */