1 /*
2 * sql.c rlm_sql - FreeRADIUS SQL Module
3 * Main code directly taken from ICRADIUS
4 *
5 * Version: $Id: 44093ee66bb4edd2dd617b663b5954762a414ff9 $
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 *
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>
25 */
26
27 RCSID("$Id: 44093ee66bb4edd2dd617b663b5954762a414ff9 $")
28
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/rad_assert.h>
31
32 #include <sys/file.h>
33 #include <sys/stat.h>
34
35 #include <ctype.h>
36
37 #include "rlm_sql.h"
38
39 #ifdef HAVE_PTHREAD_H
40 #endif
41
42 /*
43 * Translate rlm_sql rcodes to humanly
44 * readable reason strings.
45 */
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 },
52 { "no more rows", RLM_SQL_NO_MORE_ROWS },
53 { NULL, 0 }
54 };
55
56
57 /*************************************************************************
58 *
59 * Function: sql_fr_pair_list_afrom_str
60 *
61 * Purpose: Read entries from the database and fill VALUE_PAIR structures
62 *
63 *************************************************************************/
sql_fr_pair_list_afrom_str(TALLOC_CTX * ctx,REQUEST * request,VALUE_PAIR ** head,rlm_sql_row_t row)64 int sql_fr_pair_list_afrom_str(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_row_t row)
65 {
66 VALUE_PAIR *vp;
67 char const *ptr, *value;
68 char buf[MAX_STRING_LEN];
69 char do_xlat = 0;
70 FR_TOKEN token, op = T_EOL;
71 size_t num_fields = talloc_array_length(row) - 1; /* includes a trailing NULL ptr */
72
73 if (num_fields < 4) {
74 REDEBUG("Insufficient fields for 'id,username,attribute,value,operator'");
75 return -1;
76 }
77
78 /*
79 * Verify the 'Attribute' field
80 */
81 if (!row[2] || row[2][0] == '\0') {
82 REDEBUG("Attribute field is empty or NULL, skipping the entire row");
83 return -1;
84 }
85
86 /*
87 * Verify the 'op' field
88 */
89 if ((num_fields >= 4) && row[4] != NULL && row[4][0] != '\0') {
90 ptr = row[4];
91 op = gettoken(&ptr, buf, sizeof(buf), false);
92 if (!fr_assignment_op[op] && !fr_equality_op[op]) {
93 REDEBUG("Invalid op \"%s\" for attribute %s", row[4], row[2]);
94 return -1;
95 }
96
97 } else {
98 /*
99 * Complain about empty or invalid 'op' field
100 */
101 op = T_OP_CMP_EQ;
102 REDEBUG("The op field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]);
103 REDEBUG("You MUST FIX THIS if you want the configuration to behave as you expect");
104 }
105
106 /*
107 * The 'Value' field may be empty or NULL
108 */
109 if (!row[3]) {
110 REDEBUG("Value field is empty or NULL, skipping the entire row");
111 return -1;
112 }
113
114 value = row[3];
115
116 /*
117 * If we have a new-style quoted string, where the
118 * *entire* string is quoted, do xlat's.
119 */
120 if (row[3] != NULL &&
121 ((row[3][0] == '\'') || (row[3][0] == '`') || (row[3][0] == '"')) &&
122 (row[3][0] == row[3][strlen(row[3])-1])) {
123
124 token = gettoken(&value, buf, sizeof(buf), false);
125 switch (token) {
126 /*
127 * Mark the pair to be allocated later.
128 */
129 case T_BACK_QUOTED_STRING:
130 do_xlat = 1;
131 /* FALL-THROUGH */
132
133 /*
134 * Take the unquoted string.
135 */
136 case T_SINGLE_QUOTED_STRING:
137 case T_DOUBLE_QUOTED_STRING:
138 value = buf;
139 break;
140
141 /*
142 * Keep the original string.
143 */
144 default:
145 value = row[3];
146 break;
147 }
148 }
149
150 /*
151 * Create the pair
152 */
153 vp = fr_pair_make(ctx, NULL, row[2], NULL, op);
154 if (!vp) {
155 REDEBUG("Failed to create the pair: %s", fr_strerror());
156 return -1;
157 }
158
159 if (do_xlat) {
160 if (fr_pair_mark_xlat(vp, value) < 0) {
161 REDEBUG("Error marking pair for xlat: %s", fr_strerror());
162
163 talloc_free(vp);
164 return -1;
165 }
166 } else {
167 if (fr_pair_value_from_str(vp, value, -1) < 0) {
168 REDEBUG("Error parsing value: %s", fr_strerror());
169
170 talloc_free(vp);
171 return -1;
172 }
173 }
174
175 /*
176 * Add the pair into the packet
177 */
178 fr_pair_add(head, vp);
179 return 0;
180 }
181
182 /** Call the driver's sql_fetch_row function
183 *
184 * Calls the driver's sql_fetch_row logging any errors. On success, will
185 * write row data to (*handle)->row.
186 *
187 * @param inst Instance of rlm_sql.
188 * @param request The Current request, may be NULL.
189 * @param handle Handle to retrieve errors for.
190 * @return on success RLM_SQL_OK, other sql_rcode_t constants on error.
191 */
rlm_sql_fetch_row(rlm_sql_t * inst,REQUEST * request,rlm_sql_handle_t ** handle)192 sql_rcode_t rlm_sql_fetch_row(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle)
193 {
194 int ret;
195
196 if (!*handle || !(*handle)->conn) return RLM_SQL_ERROR;
197
198 /*
199 * We can't implement reconnect logic here, because the caller
200 * may require the original connection to free up queries or
201 * result sets associated with that connection.
202 */
203 ret = (inst->module->sql_fetch_row)(*handle, inst->config);
204 if (ret < 0) {
205 MOD_ROPTIONAL(RERROR, ERROR, "Error fetching row");
206
207 rlm_sql_print_error(inst, request, *handle, false);
208 }
209
210 return ret;
211 }
212
213 /** Retrieve any errors from the SQL driver
214 *
215 * Retrieves errors from the driver from the last operation and writes them to
216 * to request/global log, in the ERROR, WARN, INFO and DEBUG categories.
217 *
218 * @param inst Instance of rlm_sql.
219 * @param request Current request, may be NULL.
220 * @param handle Handle to retrieve errors for.
221 * @param force_debug Force all errors to be logged as debug messages.
222 */
rlm_sql_print_error(rlm_sql_t * inst,REQUEST * request,rlm_sql_handle_t * handle,bool force_debug)223 void rlm_sql_print_error(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *handle, bool force_debug)
224 {
225 char const *driver;
226 sql_log_entry_t log[20];
227 size_t num, i;
228
229 num = (inst->module->sql_error)(handle->log_ctx, log, (sizeof(log) / sizeof(*log)), handle, inst->config);
230 if (num == 0) {
231 MOD_ROPTIONAL(RERROR, ERROR, "Unknown error");
232 return;
233 }
234
235 driver = inst->config->sql_driver_name;
236
237 for (i = 0; i < num; i++) {
238 if (force_debug) goto debug;
239
240 switch (log[i].type) {
241 case L_ERR:
242 MOD_ROPTIONAL(RERROR, ERROR, "%s: %s", driver, log[i].msg);
243 break;
244
245 case L_WARN:
246 MOD_ROPTIONAL(RWARN, WARN, "%s: %s", driver, log[i].msg);
247 break;
248
249 case L_INFO:
250 MOD_ROPTIONAL(RINFO, INFO, "%s: %s", driver, log[i].msg);
251 break;
252
253 case L_DBG:
254 default:
255 debug:
256 MOD_ROPTIONAL(RDEBUG, DEBUG, "%s: %s", driver, log[i].msg);
257 break;
258 }
259 }
260
261 talloc_free_children(handle->log_ctx);
262 }
263
264 /** Call the driver's sql_query method, reconnecting if necessary.
265 *
266 * @note Caller must call (inst->module->sql_finish_query)(handle, inst->config);
267 * after they're done with the result.
268 *
269 * @param handle to query the database with. *handle should not be NULL, as this indicates
270 * previous reconnection attempt has failed.
271 * @param request Current request.
272 * @param inst rlm_sql instance data.
273 * @param query to execute. Should not be zero length.
274 * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required
275 * (also sets *handle = NULL), RLM_SQL_QUERY_INVALID/RLM_SQL_ERROR on invalid query or
276 * connection error, RLM_SQL_ALT_QUERY on constraints violation.
277 */
rlm_sql_query(rlm_sql_t * inst,REQUEST * request,rlm_sql_handle_t ** handle,char const * query)278 sql_rcode_t rlm_sql_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, char const *query)
279 {
280 int ret = RLM_SQL_ERROR;
281 int i, count;
282
283 /* Caller should check they have a valid handle */
284 rad_assert(*handle);
285
286 /* There's no query to run, return an error */
287 if (query[0] == '\0') {
288 if (request) REDEBUG("Zero length query");
289 return RLM_SQL_QUERY_INVALID;
290 }
291
292 /*
293 * inst->pool may be NULL is this function is called by mod_conn_create.
294 */
295 count = inst->pool ? fr_connection_pool_get_num(inst->pool) : 0;
296
297 /*
298 * Here we try with each of the existing connections, then try to create
299 * a new connection, then give up.
300 */
301 for (i = 0; i < (count + 1); i++) {
302 MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query);
303
304 ret = (inst->module->sql_query)(*handle, inst->config, query);
305 switch (ret) {
306 case RLM_SQL_OK:
307 break;
308
309 /*
310 * Run through all available sockets until we exhaust all existing
311 * sockets in the pool and fail to establish a *new* connection.
312 */
313 case RLM_SQL_RECONNECT:
314 *handle = fr_connection_reconnect(inst->pool, *handle);
315 /* Reconnection failed */
316 if (!*handle) return RLM_SQL_RECONNECT;
317 /* Reconnection succeeded, try again with the new handle */
318 continue;
319
320 /*
321 * These are bad and should make rlm_sql return invalid
322 */
323 case RLM_SQL_QUERY_INVALID:
324 rlm_sql_print_error(inst, request, *handle, false);
325 (inst->module->sql_finish_query)(*handle, inst->config);
326 break;
327
328 /*
329 * Server or client errors.
330 *
331 * If the driver claims to be able to distinguish between
332 * duplicate row errors and other errors, and we hit a
333 * general error treat it as a failure.
334 *
335 * Otherwise rewrite it to RLM_SQL_ALT_QUERY.
336 */
337 case RLM_SQL_ERROR:
338 if (inst->module->flags & RLM_SQL_RCODE_FLAGS_ALT_QUERY) {
339 rlm_sql_print_error(inst, request, *handle, false);
340 (inst->module->sql_finish_query)(*handle, inst->config);
341 break;
342 }
343 ret = RLM_SQL_ALT_QUERY;
344 /* FALL-THROUGH */
345
346 /*
347 * Driver suggested using an alternative query
348 */
349 case RLM_SQL_ALT_QUERY:
350 rlm_sql_print_error(inst, request, *handle, true);
351 (inst->module->sql_finish_query)(*handle, inst->config);
352 break;
353
354 }
355
356 return ret;
357 }
358
359 MOD_ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
360
361 return RLM_SQL_ERROR;
362 }
363
364 /** Call the driver's sql_select_query method, reconnecting if necessary.
365 *
366 * @note Caller must call (inst->module->sql_finish_select_query)(handle, inst->config);
367 * after they're done with the result.
368 *
369 * @param inst rlm_sql instance data.
370 * @param request Current request.
371 * @param handle to query the database with. *handle should not be NULL, as this indicates
372 * previous reconnection attempt has failed.
373 * @param query to execute. Should not be zero length.
374 * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required (also sets *handle = NULL),
375 * RLM_SQL_QUERY_INVALID/RLM_SQL_ERROR on invalid query or connection error.
376 */
rlm_sql_select_query(rlm_sql_t * inst,REQUEST * request,rlm_sql_handle_t ** handle,char const * query)377 sql_rcode_t rlm_sql_select_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, char const *query)
378 {
379 int ret = RLM_SQL_ERROR;
380 int i, count;
381
382 /* Caller should check they have a valid handle */
383 rad_assert(*handle);
384
385 /* There's no query to run, return an error */
386 if (query[0] == '\0') {
387 if (request) REDEBUG("Zero length query");
388
389 return RLM_SQL_QUERY_INVALID;
390 }
391
392 /*
393 * inst->pool may be NULL is this function is called by mod_conn_create.
394 */
395 count = inst->pool ? fr_connection_pool_get_num(inst->pool) : 0;
396
397 /*
398 * For sanity, for when no connections are viable, and we can't make a new one
399 */
400 for (i = 0; i < (count + 1); i++) {
401 MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Executing select query: %s", query);
402
403 ret = (inst->module->sql_select_query)(*handle, inst->config, query);
404 switch (ret) {
405 case RLM_SQL_OK:
406 break;
407
408 /*
409 * Run through all available sockets until we exhaust all existing
410 * sockets in the pool and fail to establish a *new* connection.
411 */
412 case RLM_SQL_RECONNECT:
413 *handle = fr_connection_reconnect(inst->pool, *handle);
414 /* Reconnection failed */
415 if (!*handle) return RLM_SQL_RECONNECT;
416 /* Reconnection succeeded, try again with the new handle */
417 continue;
418
419 case RLM_SQL_QUERY_INVALID:
420 case RLM_SQL_ERROR:
421 default:
422 rlm_sql_print_error(inst, request, *handle, false);
423 (inst->module->sql_finish_select_query)(*handle, inst->config);
424 break;
425 }
426
427 return ret;
428 }
429
430 MOD_ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
431
432 return RLM_SQL_ERROR;
433 }
434
435
436 /*************************************************************************
437 *
438 * Function: sql_getvpdata
439 *
440 * Purpose: Get any group check or reply pairs
441 *
442 *************************************************************************/
sql_getvpdata(TALLOC_CTX * ctx,rlm_sql_t * inst,REQUEST * request,rlm_sql_handle_t ** handle,VALUE_PAIR ** pair,char const * query)443 int sql_getvpdata(TALLOC_CTX *ctx, rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
444 VALUE_PAIR **pair, char const *query)
445 {
446 rlm_sql_row_t row;
447 int rows = 0;
448 sql_rcode_t rcode;
449
450 rad_assert(request);
451
452 rcode = rlm_sql_select_query(inst, request, handle, query);
453 if (rcode != RLM_SQL_OK) return -1; /* error handled by rlm_sql_select_query */
454
455 while (rlm_sql_fetch_row(inst, request, handle) == RLM_SQL_OK) {
456 row = (*handle)->row;
457 if (!row) break;
458 if (sql_fr_pair_list_afrom_str(ctx, request, pair, row) != 0) {
459 REDEBUG("Error parsing user data from database result");
460
461 (inst->module->sql_finish_select_query)(*handle, inst->config);
462
463 return -1;
464 }
465 rows++;
466 }
467 (inst->module->sql_finish_select_query)(*handle, inst->config);
468
469 return rows;
470 }
471
472 /*
473 * Log the query to a file.
474 */
rlm_sql_query_log(rlm_sql_t * inst,REQUEST * request,sql_acct_section_t * section,char const * query)475 void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request,
476 sql_acct_section_t *section, char const *query)
477 {
478 int fd;
479 char const *filename = NULL;
480 char *expanded = NULL;
481 size_t len;
482 bool failed = false; /* Write the log message outside of the critical region */
483
484 filename = inst->config->logfile;
485 if (section && section->logfile) filename = section->logfile;
486
487 if (!filename || !*filename) {
488 return;
489 }
490
491 if (radius_axlat(&expanded, request, filename, NULL, NULL) < 0) {
492 return;
493 }
494
495 fd = exfile_open(inst->ef, expanded, 0640);
496 if (fd < 0) {
497 ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->name,
498 expanded, fr_syserror(errno));
499
500 talloc_free(expanded);
501 return;
502 }
503
504 len = strlen(query);
505 if ((write(fd, query, len) < 0) || (write(fd, ";\n", 2) < 0)) {
506 failed = true;
507 }
508
509 if (failed) {
510 ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->name, expanded,
511 fr_syserror(errno));
512 }
513
514 talloc_free(expanded);
515 exfile_close(inst->ef, fd);
516 }
517