2 * sql.c rlm_sql - FreeRADIUS SQL Module
3 * Main code directly taken from ICRADIUS
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * Copyright 2001,2006 The FreeRADIUS server project
22 * Copyright 2000 Mike Machado <mike@innercite.com>
23 * Copyright 2000 Alan DeKok <aland@ox.org>
24 * Copyright 2001 Chad Miller <cmiller@surfsouth.com>
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/rad_assert.h>
43 * Translate rlm_sql rcodes to humanly
44 * readable reason strings.
46 const FR_NAME_NUMBER sql_rcode_table[] = {
47 { "success", RLM_SQL_OK },
48 { "need alt query", RLM_SQL_ALT_QUERY },
49 { "server error", RLM_SQL_ERROR },
50 { "query invalid", RLM_SQL_QUERY_INVALID },
51 { "no connection", RLM_SQL_RECONNECT },
56 void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
59 rlm_sql_t *inst = instance;
60 rlm_sql_handle_t *handle;
63 * Connections cannot be alloced from the inst or
64 * pool contexts due to threading issues.
66 handle = talloc_zero(ctx, rlm_sql_handle_t);
67 if (!handle) return NULL;
69 handle->log_ctx = talloc_pool(handle, 2048);
70 if (!handle->log_ctx) {
76 * Handle requires a pointer to the SQL inst so the
77 * destructor has access to the module configuration.
81 rcode = (inst->module->sql_socket_init)(handle, inst->config);
85 * Destroy any half opened connections.
91 if (inst->config->connect_query) {
92 if (rlm_sql_select_query(inst, NULL, &handle, inst->config->connect_query) != RLM_SQL_OK) goto fail;
93 (inst->module->sql_finish_select_query)(handle, inst->config);
99 /*************************************************************************
101 * Function: sql_fr_pair_list_afrom_str
103 * Purpose: Read entries from the database and fill VALUE_PAIR structures
105 *************************************************************************/
106 int sql_fr_pair_list_afrom_str(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_row_t row)
109 char const *ptr, *value;
110 char buf[MAX_STRING_LEN];
112 FR_TOKEN token, operator = T_EOL;
115 * Verify the 'Attribute' field
117 if (!row[2] || row[2][0] == '\0') {
118 REDEBUG("The 'Attribute' field is empty or NULL, skipping the entire row");
123 * Verify the 'op' field
125 if (row[4] != NULL && row[4][0] != '\0') {
127 operator = gettoken(&ptr, buf, sizeof(buf), false);
128 if ((operator < T_OP_ADD) ||
129 (operator > T_OP_CMP_EQ)) {
130 REDEBUG("Invalid operator \"%s\" for attribute %s", row[4], row[2]);
136 * Complain about empty or invalid 'op' field
138 operator = T_OP_CMP_EQ;
139 REDEBUG("The 'op' field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]);
140 REDEBUG("You MUST FIX THIS if you want the configuration to behave as you expect");
144 * The 'Value' field may be empty or NULL
148 * If we have a new-style quoted string, where the
149 * *entire* string is quoted, do xlat's.
151 if (row[3] != NULL &&
152 ((row[3][0] == '\'') || (row[3][0] == '`') || (row[3][0] == '"')) &&
153 (row[3][0] == row[3][strlen(row[3])-1])) {
155 token = gettoken(&value, buf, sizeof(buf), false);
158 * Take the unquoted string.
160 case T_SINGLE_QUOTED_STRING:
161 case T_DOUBLE_QUOTED_STRING:
166 * Mark the pair to be allocated later.
168 case T_BACK_QUOTED_STRING:
174 * Keep the original string.
185 vp = fr_pair_make(ctx, NULL, row[2], NULL, operator);
187 REDEBUG("Failed to create the pair: %s", fr_strerror());
192 if (fr_pair_mark_xlat(vp, value) < 0) {
193 REDEBUG("Error marking pair for xlat");
199 if (fr_pair_value_from_str(vp, value, -1) < 0) {
200 REDEBUG("Error parsing value: %s", fr_strerror());
208 * Add the pair into the packet
210 fr_pair_add(head, vp);
214 /** Call the driver's sql_fetch_row function
216 * Calls the driver's sql_fetch_row logging any errors. On success, will
217 * write row data to (*handle)->row.
219 * @param inst Instance of rlm_sql.
220 * @param request The Current request, may be NULL.
221 * @param handle Handle to retrieve errors for.
222 * @return on success RLM_SQL_OK, other sql_rcode_t constants on error.
224 sql_rcode_t rlm_sql_fetch_row(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle)
228 if (!*handle || !(*handle)->conn) return RLM_SQL_ERROR;
231 * We can't implement reconnect logic here, because the caller
232 * may require the original connection to free up queries or
233 * result sets associated with that connection.
235 ret = (inst->module->sql_fetch_row)(*handle, inst->config);
237 MOD_ROPTIONAL(RERROR, ERROR, "Error fetching row");
239 rlm_sql_print_error(inst, request, *handle, false);
245 /** Retrieve any errors from the SQL driver
247 * Retrieves errors from the driver from the last operation and writes them to
248 * to request/global log, in the ERROR, WARN, INFO and DEBUG categories.
250 * @param inst Instance of rlm_sql.
251 * @param request Current request, may be NULL.
252 * @param handle Handle to retrieve errors for.
253 * @param force_debug Force all errors to be logged as debug messages.
255 void rlm_sql_print_error(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *handle, bool force_debug)
258 sql_log_entry_t log[20];
261 num = (inst->module->sql_error)(handle->log_ctx, log, (sizeof(log) / sizeof(*log)), handle, inst->config);
263 MOD_ROPTIONAL(RERROR, ERROR, "Unknown error");
267 driver = inst->config->sql_driver_name;
269 for (i = 0; i < num; i++) {
270 if (force_debug) goto debug;
272 switch (log[i].type) {
274 MOD_ROPTIONAL(RERROR, ERROR, "%s: %s", driver, log[i].msg);
278 MOD_ROPTIONAL(RWARN, WARN, "%s: %s", driver, log[i].msg);
282 MOD_ROPTIONAL(RINFO, INFO, "%s: %s", driver, log[i].msg);
288 MOD_ROPTIONAL(RDEBUG, DEBUG, "%s: %s", driver, log[i].msg);
293 talloc_free_children(handle->log_ctx);
296 /** Call the driver's sql_query method, reconnecting if necessary.
298 * @note Caller must call (inst->module->sql_finish_query)(handle, inst->config);
299 * after they're done with the result.
301 * @param handle to query the database with. *handle should not be NULL, as this indicates
302 * previous reconnection attempt has failed.
303 * @param request Current request.
304 * @param inst rlm_sql instance data.
305 * @param query to execute. Should not be zero length.
306 * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required
307 * (also sets *handle = NULL), RLM_SQL_QUERY_INVALID/RLM_SQL_ERROR on invalid query or
308 * connection error, RLM_SQL_ALT_QUERY on constraints violation.
310 sql_rcode_t rlm_sql_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, char const *query)
312 int ret = RLM_SQL_ERROR;
315 /* Caller should check they have a valid handle */
318 /* There's no query to run, return an error */
319 if (query[0] == '\0') {
320 if (request) REDEBUG("Zero length query");
321 return RLM_SQL_QUERY_INVALID;
325 * inst->pool may be NULL is this function is called by mod_conn_create.
327 count = inst->pool ? fr_connection_pool_get_num(inst->pool) : 0;
330 * Here we try with each of the existing connections, then try to create
331 * a new connection, then give up.
333 for (i = 0; i < (count + 1); i++) {
334 MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query);
336 ret = (inst->module->sql_query)(*handle, inst->config, query);
342 * Run through all available sockets until we exhaust all existing
343 * sockets in the pool and fail to establish a *new* connection.
345 case RLM_SQL_RECONNECT:
346 *handle = fr_connection_reconnect(inst->pool, *handle);
347 /* Reconnection failed */
348 if (!*handle) return RLM_SQL_RECONNECT;
349 /* Reconnection succeeded, try again with the new handle */
353 * These are bad and should make rlm_sql return invalid
355 case RLM_SQL_QUERY_INVALID:
356 rlm_sql_print_error(inst, request, *handle, false);
357 (inst->module->sql_finish_query)(*handle, inst->config);
361 * Server or client errors.
363 * If the driver claims to be able to distinguish between
364 * duplicate row errors and other errors, and we hit a
365 * general error treat it as a failure.
367 * Otherwise rewrite it to RLM_SQL_ALT_QUERY.
370 if (inst->module->flags & RLM_SQL_RCODE_FLAGS_ALT_QUERY) {
371 rlm_sql_print_error(inst, request, *handle, false);
372 (inst->module->sql_finish_query)(*handle, inst->config);
375 ret = RLM_SQL_ALT_QUERY;
379 * Driver suggested using an alternative query
381 case RLM_SQL_ALT_QUERY:
382 rlm_sql_print_error(inst, request, *handle, true);
383 (inst->module->sql_finish_query)(*handle, inst->config);
391 MOD_ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
393 return RLM_SQL_ERROR;
396 /** Call the driver's sql_select_query method, reconnecting if necessary.
398 * @note Caller must call (inst->module->sql_finish_select_query)(handle, inst->config);
399 * after they're done with the result.
401 * @param inst rlm_sql instance data.
402 * @param request Current request.
403 * @param handle to query the database with. *handle should not be NULL, as this indicates
404 * previous reconnection attempt has failed.
405 * @param query to execute. Should not be zero length.
406 * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required (also sets *handle = NULL),
407 * RLM_SQL_QUERY_INVALID/RLM_SQL_ERROR on invalid query or connection error.
409 sql_rcode_t rlm_sql_select_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, char const *query)
411 int ret = RLM_SQL_ERROR;
414 /* Caller should check they have a valid handle */
417 /* There's no query to run, return an error */
418 if (query[0] == '\0') {
419 if (request) REDEBUG("Zero length query");
421 return RLM_SQL_QUERY_INVALID;
425 * inst->pool may be NULL is this function is called by mod_conn_create.
427 count = inst->pool ? fr_connection_pool_get_num(inst->pool) : 0;
430 * For sanity, for when no connections are viable, and we can't make a new one
432 for (i = 0; i < (count + 1); i++) {
433 MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Executing select query: %s", query);
435 ret = (inst->module->sql_select_query)(*handle, inst->config, query);
441 * Run through all available sockets until we exhaust all existing
442 * sockets in the pool and fail to establish a *new* connection.
444 case RLM_SQL_RECONNECT:
445 *handle = fr_connection_reconnect(inst->pool, *handle);
446 /* Reconnection failed */
447 if (!*handle) return RLM_SQL_RECONNECT;
448 /* Reconnection succeeded, try again with the new handle */
451 case RLM_SQL_QUERY_INVALID:
454 rlm_sql_print_error(inst, request, *handle, false);
455 (inst->module->sql_finish_select_query)(*handle, inst->config);
462 MOD_ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
464 return RLM_SQL_ERROR;
468 /*************************************************************************
470 * Function: sql_getvpdata
472 * Purpose: Get any group check or reply pairs
474 *************************************************************************/
475 int sql_getvpdata(TALLOC_CTX *ctx, rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
476 VALUE_PAIR **pair, char const *query)
484 rcode = rlm_sql_select_query(inst, request, handle, query);
485 if (rcode != RLM_SQL_OK) return -1; /* error handled by rlm_sql_select_query */
487 while (rlm_sql_fetch_row(inst, request, handle) == 0) {
488 row = (*handle)->row;
490 if (sql_fr_pair_list_afrom_str(ctx, request, pair, row) != 0) {
491 REDEBUG("Error parsing user data from database result");
493 (inst->module->sql_finish_select_query)(*handle, inst->config);
499 (inst->module->sql_finish_select_query)(*handle, inst->config);
505 * Log the query to a file.
507 void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request,
508 sql_acct_section_t *section, char const *query)
511 char const *filename = NULL;
512 char *expanded = NULL;
514 bool failed = false; /* Write the log message outside of the critical region */
516 filename = inst->config->logfile;
517 if (section && section->logfile) filename = section->logfile;
519 if (!filename || !*filename) {
523 if (radius_axlat(&expanded, request, filename, NULL, NULL) < 0) {
527 fd = exfile_open(inst->ef, filename, 0640, true);
529 ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->name,
530 expanded, fr_syserror(errno));
532 talloc_free(expanded);
537 if ((write(fd, query, len) < 0) || (write(fd, ";\n", 2) < 0)) {
542 ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->name, expanded,
546 talloc_free(expanded);
547 exfile_close(inst->ef, fd);