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