1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: a7992647cd0febc5248b34d18acebd017f6825d7 $
19  * @file rlm_sql_mysql.c
20  * @brief MySQL driver.
21  *
22  * @copyright 2014-2015  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  * @copyright 2000-2007,2015  The FreeRADIUS server project
24  * @copyright 2000  Mike Machado <mike@innercite.com>
25  * @copyright 2000  Alan DeKok <aland@ox.org>
26  */
27 RCSID("$Id: a7992647cd0febc5248b34d18acebd017f6825d7 $")
28 
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/rad_assert.h>
31 
32 #include <sys/stat.h>
33 
34 #include "config.h"
35 
36 #ifdef HAVE_MYSQL_MYSQL_H
37 #  include <mysql/mysql_version.h>
38 #  include <mysql/errmsg.h>
39 #  include <mysql/mysql.h>
40 #  include <mysql/mysqld_error.h>
41 #elif defined(HAVE_MYSQL_H)
42 #  include <mysql_version.h>
43 #  include <errmsg.h>
44 #  include <mysql.h>
45 #  include <mysqld_error.h>
46 #endif
47 
48 #if (MYSQL_VERSION_ID >= 50555) && (MYSQL_VERSION_ID < 50600)
49 #  define HAVE_TLS_OPTIONS        1
50 #  define HAVE_CRL_OPTIONS        0
51 #  define HAVE_TLS_VERIFY_OPTIONS 0
52 #elif (MYSQL_VERSION_ID >= 50636) && (MYSQL_VERSION_ID < 50700)
53 #  define HAVE_TLS_OPTIONS        1
54 #  define HAVE_CRL_OPTIONS        1
55 #  define HAVE_TLS_VERIFY_OPTIONS 0
56 #elif MYSQL_VERSION_ID >= 50700
57 #  define HAVE_TLS_OPTIONS        1
58 #  define HAVE_CRL_OPTIONS        1
59 #  define HAVE_TLS_VERIFY_OPTIONS 1
60 #endif
61 
62 #include "rlm_sql.h"
63 
64 static int mysql_instance_count = 0;
65 
66 typedef enum {
67 	SERVER_WARNINGS_AUTO = 0,
68 	SERVER_WARNINGS_YES,
69 	SERVER_WARNINGS_NO
70 } rlm_sql_mysql_warnings;
71 
72 static const FR_NAME_NUMBER server_warnings_table[] = {
73 	{ "auto",	SERVER_WARNINGS_AUTO	},
74 	{ "yes",	SERVER_WARNINGS_YES	},
75 	{ "no",		SERVER_WARNINGS_NO	},
76 	{ NULL, 0 }
77 };
78 
79 typedef struct rlm_sql_mysql_conn {
80 	MYSQL		db;
81 	MYSQL		*sock;
82 	MYSQL_RES	*result;
83 } rlm_sql_mysql_conn_t;
84 
85 typedef struct rlm_sql_mysql_config {
86 	char const	*tls_ca_file;		//!< Path to the CA used to validate the server's certificate.
87 	char const	*tls_ca_path;		//!< Directory containing CAs that may be used to validate the
88 						//!< servers certificate.
89 	char const	*tls_certificate_file;	//!< Public certificate we present to the server.
90 	char const	*tls_private_key_file;	//!< Private key for the certificate we present to the server.
91 
92 	char const	*tls_crl_file;		//!< Public certificate we present to the server.
93 	char const	*tls_crl_path;		//!< Private key for the certificate we present to the server.
94 
95 	char const	*tls_cipher;		//!< Colon separated list of TLS ciphers for TLS <= 1.2.
96 
97 	bool		tls_required;		//!< Require that the connection is encrypted.
98 	bool		tls_check_cert;		//!< Verify there's a trust relationship between the server's
99 						///< cert and one of the CAs we have configured.
100 	bool		tls_check_cert_cn;	//!< Verify that the CN in the server cert matches the host
101 						///< we passed to mysql_real_connect().
102 
103 	char const	*warnings_str;		//!< Whether we always query the server for additional warnings.
104 	rlm_sql_mysql_warnings	warnings;	//!< mysql_warning_count() doesn't
105 						//!< appear to work with NDB cluster
106 } rlm_sql_mysql_config_t;
107 
108 static CONF_PARSER tls_config[] = {
109 	{ "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_file), NULL },
110 	{ "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_path), NULL },
111 	{ "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_certificate_file), NULL },
112 	{ "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_private_key_file), NULL },
113 
114 #if HAVE_CRL_OPTIONS
115 	{ "crl_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_crl_file), NULL },
116 	{ "crl_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_crl_path), NULL },
117 #endif
118 	/*
119 	 *	MySQL Specific TLS attributes
120 	 */
121 	{ "cipher", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, tls_cipher), NULL },
122 
123 	/*
124 	 *	The closest thing we have to these options in other modules is
125 	 *	in rlm_rest.  rlm_ldap has its own bizarre option set.
126 	 *
127 	 *	There, the options can be toggled independently, here they can't
128 	 *	but for consistency we break them out anyway, and warn if the user
129 	 *	has provided an invalid list of flags.
130 	 */
131 #if HAVE_TLS_OPTIONS
132 	{ "tls_required", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_mysql_config_t, tls_required), "no" },
133 #  if HAVE_TLS_VERIFY_OPTIONS
134 	{ "check_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_mysql_config_t, tls_check_cert), "no" },
135 	{ "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_mysql_config_t, tls_check_cert_cn), "no" },
136 #  endif
137 #endif
138 	CONF_PARSER_TERMINATOR
139 };
140 
141 static const CONF_PARSER driver_config[] = {
142 	{ "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
143 
144 	{ "warnings", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, warnings_str), "auto" },
145 	CONF_PARSER_TERMINATOR
146 };
147 
148 /* Prototypes */
149 static sql_rcode_t sql_free_result(rlm_sql_handle_t*, rlm_sql_config_t*);
150 
_sql_socket_destructor(rlm_sql_mysql_conn_t * conn)151 static int _sql_socket_destructor(rlm_sql_mysql_conn_t *conn)
152 {
153 	DEBUG2("rlm_sql_mysql: Socket destructor called, closing socket");
154 
155 	if (conn->sock){
156 		mysql_close(conn->sock);
157 	}
158 
159 	return 0;
160 }
161 
_mod_destructor(UNUSED rlm_sql_mysql_config_t * driver)162 static int _mod_destructor(UNUSED rlm_sql_mysql_config_t *driver)
163 {
164 	if (--mysql_instance_count == 0) mysql_library_end();
165 
166 #if HAVE_TLS_VERIFY_OPTIONS
167 	if (driver->tls_check_cert && !driver->tls_required) {
168 		WARN("Implicitly setting tls_required = yes, as tls_check_cert = yes");
169 		driver->tls_required = true;
170 	}
171 	if (driver->tls_check_cert_cn) {
172 		if (!driver->tls_required) {
173 			WARN("Implicitly setting tls_required = yes, as check_cert_cn = yes");
174 			driver->tls_required = true;
175 		}
176 
177 		if (!driver->tls_check_cert) {
178 			WARN("Implicitly setting check_cert = yes, as check_cert_cn = yes");
179 			driver->tls_check_cert = true;
180 		}
181 	}
182 #endif
183 	return 0;
184 }
185 
mod_instantiate(CONF_SECTION * conf,rlm_sql_config_t * config)186 static int mod_instantiate(CONF_SECTION *conf, rlm_sql_config_t *config)
187 {
188 	rlm_sql_mysql_config_t *driver;
189 	int warnings;
190 
191 	static bool version_done = false;
192 
193 	if (!version_done) {
194 		version_done = true;
195 
196 		INFO("rlm_sql_mysql: libmysql version: %s", mysql_get_client_info());
197 	}
198 
199 	if (mysql_instance_count == 0) {
200 		if (mysql_library_init(0, NULL, NULL)) {
201 			ERROR("rlm_sql_mysql: libmysql initialisation failed");
202 
203 			return -1;
204 		}
205 	}
206 	mysql_instance_count++;
207 
208 	MEM(driver = config->driver = talloc_zero(config, rlm_sql_mysql_config_t));
209 	talloc_set_destructor(driver, _mod_destructor);
210 
211 	if (cf_section_parse(conf, driver, driver_config) < 0) {
212 		return -1;
213 	}
214 
215 	warnings = fr_str2int(server_warnings_table, driver->warnings_str, -1);
216 	if (warnings < 0) {
217 		ERROR("rlm_sql_mysql: Invalid warnings value \"%s\", must be yes, no, or auto", driver->warnings_str);
218 		return -1;
219 	}
220 	driver->warnings = (rlm_sql_mysql_warnings)warnings;
221 
222 	return 0;
223 }
224 
sql_socket_init(rlm_sql_handle_t * handle,rlm_sql_config_t * config)225 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
226 {
227 	rlm_sql_mysql_conn_t *conn;
228 	rlm_sql_mysql_config_t *driver = config->driver;
229 	unsigned long sql_flags;
230 
231 	MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_mysql_conn_t));
232 	talloc_set_destructor(conn, _sql_socket_destructor);
233 
234 	DEBUG("rlm_sql_mysql: Starting connect to MySQL server");
235 
236 	mysql_init(&(conn->db));
237 
238 	/*
239 	 *	If any of the TLS options are set, configure TLS
240 	 *
241 	 *	According to MySQL docs this function always returns 0, so we won't
242 	 *	know if ssl setup succeeded until mysql_real_connect is called below.
243 	 */
244 	if (driver->tls_ca_file || driver->tls_ca_path ||
245 	    driver->tls_certificate_file || driver->tls_private_key_file) {
246 		mysql_ssl_set(&(conn->db), driver->tls_private_key_file, driver->tls_certificate_file,
247 			      driver->tls_ca_file, driver->tls_ca_path, driver->tls_cipher);
248 	}
249 
250 #if HAVE_TLS_OPTIONS
251 	{
252 		enum mysql_option ssl_mysql_opt;
253 		bool              ssl_mysql_arg = false;
254 		bool              ssl_mode_isset = false;
255 
256 #  if defined(MARIADB_VERSION_ID) || defined(MARIADB_BASE_VERSION)
257 #    if HAVE_TLS_VERIFY_OPTIONS
258 		if (driver->tls_required || driver->tls_check_cert || driver->tls_check_cert_cn) {
259 #    else
260 		if (driver->tls_required) {
261 #    endif
262 			ssl_mode_isset = ssl_mysql_arg = true;
263 			ssl_mysql_opt = MYSQL_OPT_SSL_VERIFY_SERVER_CERT;
264 		}
265 #  else
266 		ssl_mysql_opt = MYSQL_OPT_SSL_MODE;
267 
268 		if (driver->tls_required) {
269 			ssl_mysql_arg = SSL_MODE_REQUIRED;
270 			ssl_mode_isset = true;
271 		}
272 #    if HAVE_TLS_VERIFY_OPTIONS
273 		if (driver->tls_check_cert) {
274 			ssl_mysql_arg = SSL_MODE_VERIFY_CA;
275 			ssl_mode_isset = true;
276 		}
277 		if (driver->tls_check_cert_cn) {
278 			ssl_mysql_arg = SSL_MODE_VERIFY_IDENTITY;
279 			ssl_mode_isset = true;
280 		}
281 #    endif /* MARIADB_VERSION_ID */
282 #  endif
283 
284 		if (ssl_mode_isset) mysql_options(&(conn->db), ssl_mysql_opt, &ssl_mysql_arg);
285 	}
286 #endif
287 
288 #if HAVE_CRL_OPTIONS
289 	if (driver->tls_crl_file) mysql_options(&(conn->db), MYSQL_OPT_SSL_CRL, driver->tls_crl_file);
290 	if (driver->tls_crl_path) mysql_options(&(conn->db), MYSQL_OPT_SSL_CRLPATH, driver->tls_crl_path);
291 #endif
292 
293 	mysql_options(&(conn->db), MYSQL_READ_DEFAULT_GROUP, "freeradius");
294 
295 	/*
296 	 *	We need to know about connection errors, and are capable
297 	 *	of reconnecting automatically.
298 	 */
299 #if MYSQL_VERSION_ID >= 50013
300 	{
301 		int reconnect = 0;
302 		mysql_options(&(conn->db), MYSQL_OPT_RECONNECT, &reconnect);
303 	}
304 #endif
305 
306 #if (MYSQL_VERSION_ID >= 50000)
307 	if (config->query_timeout) {
308 		unsigned int connect_timeout = config->query_timeout;
309 		unsigned int read_timeout = config->query_timeout;
310 		unsigned int write_timeout = config->query_timeout;
311 
312 		/*
313 		 *	The timeout in seconds for each attempt to read from the server.
314 		 *	There are retries if necessary, so the total effective timeout
315 		 *	value is three times the option value.
316 		 */
317 		if (config->query_timeout >= 3) read_timeout /= 3;
318 
319 		/*
320 		 *	The timeout in seconds for each attempt to write to the server.
321 		 *	There is a retry if necessary, so the total effective timeout
322 		 *	value is two times the option value.
323 		 */
324 		if (config->query_timeout >= 2) write_timeout /= 2;
325 
326 		/*
327 		 *	Connect timeout is actually connect timeout (according to the
328 		 *	docs) there are no automatic retries.
329 		 */
330 		mysql_options(&(conn->db), MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
331 		mysql_options(&(conn->db), MYSQL_OPT_READ_TIMEOUT, &read_timeout);
332 		mysql_options(&(conn->db), MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
333 	}
334 #endif
335 
336 #if (MYSQL_VERSION_ID >= 40100)
337 	sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS;
338 #else
339 	sql_flags = CLIENT_FOUND_ROWS;
340 #endif
341 
342 #ifdef CLIENT_MULTI_STATEMENTS
343 	sql_flags |= CLIENT_MULTI_STATEMENTS;
344 #endif
345 	conn->sock = mysql_real_connect(&(conn->db),
346 					config->sql_server,
347 					config->sql_login,
348 					config->sql_password,
349 					config->sql_db,
350 					config->sql_port,
351 					NULL,
352 					sql_flags);
353 	if (!conn->sock) {
354 		ERROR("rlm_sql_mysql: Couldn't connect to MySQL server %s@%s:%s", config->sql_login,
355 		      config->sql_server, config->sql_db);
356 		ERROR("rlm_sql_mysql: MySQL error: %s", mysql_error(&conn->db));
357 
358 		conn->sock = NULL;
359 		return RLM_SQL_ERROR;
360 	}
361 
362 	DEBUG2("rlm_sql_mysql: Connected to database '%s' on %s, server version %s, protocol version %i",
363 	       config->sql_db, mysql_get_host_info(conn->sock),
364 	       mysql_get_server_info(conn->sock), mysql_get_proto_info(conn->sock));
365 
366 	return RLM_SQL_OK;
367 }
368 
369 /** Analyse the last error that occurred on the socket, and determine an action
370  *
371  * @param server Socket from which to extract the server error. May be NULL.
372  * @param client_errno Error from the client.
373  * @return an action for rlm_sql to take.
374  */
375 static sql_rcode_t sql_check_error(MYSQL *server, int client_errno)
376 {
377 	int sql_errno = 0;
378 
379 	/*
380 	 *	The client and server error numbers are in the
381 	 *	same numberspace.
382 	 */
383 	if (server) sql_errno = mysql_errno(server);
384 	if ((sql_errno == 0) && (client_errno != 0)) sql_errno = client_errno;
385 
386 	if (sql_errno > 0) switch (sql_errno) {
387 	case CR_SERVER_GONE_ERROR:
388 	case CR_SERVER_LOST:
389 	case -1:
390 		return RLM_SQL_RECONNECT;
391 
392 	case CR_OUT_OF_MEMORY:
393 	case CR_COMMANDS_OUT_OF_SYNC:
394 	case CR_UNKNOWN_ERROR:
395 	default:
396 		return RLM_SQL_ERROR;
397 
398 	/*
399 	 *	Constraints errors that signify a duplicate, or that we might
400 	 *	want to try an alternative query.
401 	 *
402 	 *	Error constants not found in the 3.23/4.0/4.1 manual page
403 	 *	are checked for.
404 	 *	Other error constants should always be available.
405 	 */
406 	case ER_DUP_UNIQUE:			/* Can't write, because of unique constraint, to table '%s'. */
407 	case ER_DUP_KEY:			/* Can't write; duplicate key in table '%s' */
408 
409 	case ER_DUP_ENTRY:			/* Duplicate entry '%s' for key %d. */
410 	case ER_NO_REFERENCED_ROW:		/* Cannot add or update a child row: a foreign key constraint fails */
411 	case ER_ROW_IS_REFERENCED:		/* Cannot delete or update a parent row: a foreign key constraint fails */
412 #ifdef ER_FOREIGN_DUPLICATE_KEY
413 	case ER_FOREIGN_DUPLICATE_KEY: 		/* Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry. */
414 #endif
415 #ifdef ER_DUP_ENTRY_WITH_KEY_NAME
416 	case ER_DUP_ENTRY_WITH_KEY_NAME:	/* Duplicate entry '%s' for key '%s' */
417 #endif
418 #ifdef ER_NO_REFERENCED_ROW_2
419 	case ER_NO_REFERENCED_ROW_2:
420 #endif
421 #ifdef ER_ROW_IS_REFERENCED_2
422 	case ER_ROW_IS_REFERENCED_2:
423 #endif
424 		return RLM_SQL_ALT_QUERY;
425 
426 	/*
427 	 *	Constraints errors that signify an invalid query
428 	 *	that can never succeed.
429 	 */
430 	case ER_BAD_NULL_ERROR:			/* Column '%s' cannot be null */
431 	case ER_NON_UNIQ_ERROR:			/* Column '%s' in %s is ambiguous */
432 		return RLM_SQL_QUERY_INVALID;
433 
434 	}
435 
436 	return RLM_SQL_OK;
437 }
438 
439 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
440 {
441 	rlm_sql_mysql_conn_t *conn = handle->conn;
442 	sql_rcode_t rcode;
443 	char const *info;
444 
445 	if (!conn->sock) {
446 		ERROR("rlm_sql_mysql: Socket not connected");
447 		return RLM_SQL_RECONNECT;
448 	}
449 
450 	mysql_query(conn->sock, query);
451 	rcode = sql_check_error(conn->sock, 0);
452 	if (rcode != RLM_SQL_OK) {
453 		return rcode;
454 	}
455 
456 	/* Only returns non-null string for INSERTS */
457 	info = mysql_info(conn->sock);
458 	if (info) DEBUG2("rlm_sql_mysql: %s", info);
459 
460 	return RLM_SQL_OK;
461 }
462 
463 static sql_rcode_t sql_store_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
464 {
465 	rlm_sql_mysql_conn_t *conn = handle->conn;
466 	sql_rcode_t rcode;
467 	int ret;
468 
469 	if (!conn->sock) {
470 		ERROR("rlm_sql_mysql: Socket not connected");
471 		return RLM_SQL_RECONNECT;
472 	}
473 
474 retry_store_result:
475 	conn->result = mysql_store_result(conn->sock);
476 	if (!conn->result) {
477 		rcode = sql_check_error(conn->sock, 0);
478 		if (rcode != RLM_SQL_OK) return rcode;
479 #if (MYSQL_VERSION_ID >= 40100)
480 		ret = mysql_next_result(conn->sock);
481 		if (ret == 0) {
482 			/* there are more results */
483 			goto retry_store_result;
484 		} else if (ret > 0) return sql_check_error(NULL, ret);
485 		/* ret == -1 signals no more results */
486 #endif
487 	}
488 	return RLM_SQL_OK;
489 }
490 
491 static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
492 {
493 	int num = 0;
494 	rlm_sql_mysql_conn_t *conn = handle->conn;
495 
496 #if MYSQL_VERSION_ID >= 32224
497 	/*
498 	 *	Count takes a connection handle
499 	 */
500 	if (!(num = mysql_field_count(conn->sock))) {
501 #else
502 	/*
503 	 *	Fields takes a result struct
504 	 */
505 	if (!(num = mysql_num_fields(conn->result))) {
506 #endif
507 		return -1;
508 	}
509 	return num;
510 }
511 
512 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
513 {
514 	sql_rcode_t rcode;
515 
516 	rcode = sql_query(handle, config, query);
517 	if (rcode != RLM_SQL_OK) {
518 		return rcode;
519 	}
520 
521 	rcode = sql_store_result(handle, config);
522 	if (rcode != RLM_SQL_OK) {
523 		return rcode;
524 	}
525 
526 	/* Why? Per http://www.mysql.com/doc/n/o/node_591.html,
527 	 * this cannot return an error.  Perhaps just to complain if no
528 	 * fields are found?
529 	 */
530 	sql_num_fields(handle, config);
531 
532 	return rcode;
533 }
534 
535 static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
536 {
537 	rlm_sql_mysql_conn_t *conn = handle->conn;
538 
539 	if (conn->result) {
540 		return mysql_num_rows(conn->result);
541 	}
542 
543 	return 0;
544 }
545 
546 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
547 {
548 	rlm_sql_mysql_conn_t	*conn = handle->conn;
549 	sql_rcode_t		rcode;
550 	MYSQL_ROW		row;
551 	int			ret;
552 	unsigned int		num_fields, i;
553 	unsigned long		*field_lens;
554 
555 	/*
556 	 *  Check pointer before de-referencing it.
557 	 */
558 	if (!conn->result) {
559 		return RLM_SQL_RECONNECT;
560 	}
561 
562 	TALLOC_FREE(handle->row);		/* Clear previous row set */
563 
564 retry_fetch_row:
565 	row = mysql_fetch_row(conn->result);
566 	if (!row) {
567 		rcode = sql_check_error(conn->sock, 0);
568 		if (rcode != RLM_SQL_OK) return rcode;
569 
570 #if (MYSQL_VERSION_ID >= 40100)
571 		sql_free_result(handle, config);
572 
573 		ret = mysql_next_result(conn->sock);
574 		if (ret == 0) {
575 			/* there are more results */
576 			if ((sql_store_result(handle, config) == 0) && (conn->result != NULL)) {
577 				goto retry_fetch_row;
578 			}
579 		} else if (ret > 0) return sql_check_error(NULL, ret);
580 		/* If ret is -1 then there are no more rows */
581 #endif
582 		return RLM_SQL_NO_MORE_ROWS;
583 	}
584 
585 	num_fields = mysql_num_fields(conn->result);
586 	if (!num_fields) return RLM_SQL_NO_MORE_ROWS;
587 
588 	field_lens = mysql_fetch_lengths(conn->result);
589 
590 	MEM(handle->row = talloc_zero_array(handle, char *, num_fields + 1));
591 	for (i = 0; i < num_fields; i++) {
592 		MEM(handle->row[i] = talloc_bstrndup(handle->row, row[i], field_lens[i]));
593 	}
594 
595 	return RLM_SQL_OK;
596 }
597 
598 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
599 {
600 	rlm_sql_mysql_conn_t *conn = handle->conn;
601 
602 	if (conn->result) {
603 		mysql_free_result(conn->result);
604 		conn->result = NULL;
605 	}
606 	TALLOC_FREE(handle->row);
607 
608 	return RLM_SQL_OK;
609 }
610 
611 /** Retrieves any warnings associated with the last query
612  *
613  * MySQL stores a limited number of warnings associated with the last query
614  * executed. These can be very useful in diagnosing issues, or in some cases
615  * working around bugs in MySQL which causes it to return the wrong error.
616  *
617  * @note Caller should free any memory allocated in ctx (talloc_free_children()).
618  *
619  * @param ctx to allocate temporary error buffers in.
620  * @param out Array of sql_log_entrys to fill.
621  * @param outlen Length of out array.
622  * @param handle rlm_sql connection handle.
623  * @param config rlm_sql config.
624  * @return number of errors written to the sql_log_entry array or -1 on error.
625  */
626 static size_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
627 			   rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
628 {
629 	rlm_sql_mysql_conn_t	*conn = handle->conn;
630 
631 	MYSQL_RES		*result;
632 	MYSQL_ROW		row;
633 	unsigned int		num_fields;
634 	size_t			i = 0;
635 
636 	if (outlen == 0) return 0;
637 
638 	/*
639 	 *	Retrieve any warnings associated with the previous query
640 	 *	that were left lingering on the server.
641 	 */
642 	if (mysql_query(conn->sock, "SHOW WARNINGS") != 0) return -1;
643 	result = mysql_store_result(conn->sock);
644 	if (!result) return -1;
645 
646 	/*
647 	 *	Fields should be [0] = Level, [1] = Code, [2] = Message
648 	 */
649 	num_fields = mysql_field_count(conn->sock);
650 	if (num_fields < 3) {
651 		WARN("rlm_sql_mysql: Failed retrieving warnings, expected 3 fields got %u", num_fields);
652 		mysql_free_result(result);
653 
654 		return -1;
655 	}
656 
657 	while ((row = mysql_fetch_row(result))) {
658 		char *msg = NULL;
659 		log_type_t type;
660 
661 		/*
662 		 *	Translate the MySQL log level into our internal
663 		 *	log levels, so they get colourised correctly.
664 		 */
665 		if (strcasecmp(row[0], "warning") == 0)	type = L_WARN;
666 		else if (strcasecmp(row[0], "note") == 0) type = L_DBG;
667 		else type = L_ERR;
668 
669 		msg = talloc_asprintf(ctx, "%s: %s", row[1], row[2]);
670 		out[i].type = type;
671 		out[i].msg = msg;
672 		if (++i == outlen) break;
673 	}
674 
675 	mysql_free_result(result);
676 
677 	return i;
678 }
679 
680 /** Retrieves any errors associated with the connection handle
681  *
682  * @note Caller should free any memory allocated in ctx (talloc_free_children()).
683  *
684  * @param ctx to allocate temporary error buffers in.
685  * @param out Array of sql_log_entrys to fill.
686  * @param outlen Length of out array.
687  * @param handle rlm_sql connection handle.
688  * @param config rlm_sql config.
689  * @return number of errors written to the sql_log_entry array.
690  */
691 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
692 			rlm_sql_handle_t *handle, rlm_sql_config_t *config)
693 {
694 	rlm_sql_mysql_conn_t	*conn = handle->conn;
695 	rlm_sql_mysql_config_t	*driver = config->driver;
696 	char const		*error;
697 	size_t			i = 0;
698 
699 	rad_assert(conn && conn->sock);
700 	rad_assert(outlen > 0);
701 
702 	error = mysql_error(conn->sock);
703 
704 	/*
705 	 *	Grab the error now in case it gets cleared on the next operation.
706 	 */
707 	if (error && (error[0] != '\0')) {
708 		error = talloc_asprintf(ctx, "ERROR %u (%s): %s", mysql_errno(conn->sock), error,
709 					mysql_sqlstate(conn->sock));
710 	}
711 
712 	/*
713 	 *	Don't attempt to get errors from the server, if the last error
714 	 *	was that the server was unavailable.
715 	 */
716 	if ((outlen > 1) && (sql_check_error(conn->sock, 0) != RLM_SQL_RECONNECT)) {
717 		size_t ret;
718 		unsigned int msgs;
719 
720 		switch (driver->warnings) {
721 		case SERVER_WARNINGS_AUTO:
722 			/*
723 			 *	Check to see if any warnings can be retrieved from the server.
724 			 */
725 			msgs = mysql_warning_count(conn->sock);
726 			if (msgs == 0) {
727 				DEBUG3("rlm_sql_mysql: No additional diagnostic info on server");
728 				break;
729 			}
730 
731 		/* FALL-THROUGH */
732 		case SERVER_WARNINGS_YES:
733 			ret = sql_warnings(ctx, out, outlen - 1, handle, config);
734 			if (ret > 0) i += ret;
735 			break;
736 
737 		case SERVER_WARNINGS_NO:
738 			break;
739 
740 		default:
741 			rad_assert(0);
742 		}
743 	}
744 
745 	if (error) {
746 		out[i].type = L_ERR;
747 		out[i].msg = error;
748 	}
749 	i++;
750 
751 	return i;
752 }
753 
754 /** Finish query
755  *
756  * As a single SQL statement may return multiple results
757  * sets, (for example stored procedures) it is necessary to check
758  * whether more results exist and process them in turn if so.
759  *
760  */
761 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
762 {
763 #if (MYSQL_VERSION_ID >= 40100)
764 	rlm_sql_mysql_conn_t	*conn = handle->conn;
765 	int			ret;
766 	MYSQL_RES		*result;
767 
768 	/*
769 	 *	If there's no result associated with the
770 	 *	connection handle, assume the first result in the
771 	 *	result set hasn't been retrieved.
772 	 *
773 	 *	MySQL docs says there's no performance penalty for
774 	 *	calling mysql_store_result for queries which don't
775 	 *	return results.
776 	 */
777 	if (conn->result == NULL) {
778 		result = mysql_store_result(conn->sock);
779 		if (result) mysql_free_result(result);
780 	/*
781 	 *	...otherwise call sql_free_result to free an
782 	 *	already stored result.
783 	 */
784 	} else {
785 		sql_free_result(handle, config);	/* sql_free_result sets conn->result to NULL */
786 	}
787 
788 	/*
789 	 *	Drain any other results associated with the handle
790 	 *
791 	 *	mysql_next_result advances the result cursor so that
792 	 *	the next call to mysql_store_result will retrieve
793 	 *	the next result from the server.
794 	 *
795 	 *	Unfortunately this really does appear to be the
796 	 *	only way to return the handle to a consistent state.
797 	 */
798 	while (((ret = mysql_next_result(conn->sock)) == 0) &&
799 	       (result = mysql_store_result(conn->sock))) {
800 		mysql_free_result(result);
801 	}
802 	if (ret > 0) return sql_check_error(NULL, ret);
803 #endif
804 	return RLM_SQL_OK;
805 }
806 
807 static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
808 {
809 	rlm_sql_mysql_conn_t *conn = handle->conn;
810 
811 	return mysql_affected_rows(conn->sock);
812 }
813 
814 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, void *arg)
815 {
816 	size_t			inlen;
817 	rlm_sql_handle_t	*handle = talloc_get_type_abort(arg, rlm_sql_handle_t);
818 	rlm_sql_mysql_conn_t	*conn = handle->conn;
819 
820 	/* Check for potential buffer overflow */
821 	inlen = strlen(in);
822 	if ((inlen * 2 + 1) > outlen) return 0;
823 	/* Prevent integer overflow */
824 	if ((inlen * 2 + 1) <= inlen) return 0;
825 
826 	return mysql_real_escape_string(conn->sock, out, in, inlen);
827 }
828 
829 
830 /* Exported to rlm_sql */
831 extern rlm_sql_module_t rlm_sql_mysql;
832 rlm_sql_module_t rlm_sql_mysql = {
833 	.name				= "rlm_sql_mysql",
834 	.flags				= RLM_SQL_RCODE_FLAGS_ALT_QUERY,
835 	.mod_instantiate		= mod_instantiate,
836 	.sql_socket_init		= sql_socket_init,
837 	.sql_query			= sql_query,
838 	.sql_select_query		= sql_select_query,
839 	.sql_store_result		= sql_store_result,
840 	.sql_num_fields			= sql_num_fields,
841 	.sql_num_rows			= sql_num_rows,
842 	.sql_affected_rows		= sql_affected_rows,
843 	.sql_fetch_row			= sql_fetch_row,
844 	.sql_free_result		= sql_free_result,
845 	.sql_error			= sql_error,
846 	.sql_finish_query		= sql_finish_query,
847 	.sql_finish_select_query	= sql_finish_query,
848 	.sql_escape_func		= sql_escape_func
849 };
850