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