/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ /** * $Id$ * @file rlm_sql_mysql.c * @brief MySQL driver. * * @copyright 2014-2015 Arran Cudbard-Bell * @copyright 2000-2007,2015 The FreeRADIUS server project * @copyright 2000 Mike Machado * @copyright 2000 Alan DeKok */ RCSID("$Id$") #include #include #include #include "config.h" #ifdef HAVE_MYSQL_MYSQL_H # include # include # include # include #elif defined(HAVE_MYSQL_H) # include # include # include # include #endif #if (MYSQL_VERSION_ID >= 50555) && (MYSQL_VERSION_ID < 50600) # define HAVE_TLS_OPTIONS 1 # define HAVE_CRL_OPTIONS 0 # define HAVE_TLS_VERIFY_OPTIONS 0 #elif (MYSQL_VERSION_ID >= 50636) && (MYSQL_VERSION_ID < 50700) # define HAVE_TLS_OPTIONS 1 # define HAVE_CRL_OPTIONS 1 # define HAVE_TLS_VERIFY_OPTIONS 0 #elif MYSQL_VERSION_ID >= 50700 # define HAVE_TLS_OPTIONS 1 # define HAVE_CRL_OPTIONS 1 # define HAVE_TLS_VERIFY_OPTIONS 1 #endif #include "rlm_sql.h" static int mysql_instance_count = 0; typedef enum { SERVER_WARNINGS_AUTO = 0, SERVER_WARNINGS_YES, SERVER_WARNINGS_NO } rlm_sql_mysql_warnings; static const FR_NAME_NUMBER server_warnings_table[] = { { "auto", SERVER_WARNINGS_AUTO }, { "yes", SERVER_WARNINGS_YES }, { "no", SERVER_WARNINGS_NO }, { NULL, 0 } }; typedef struct rlm_sql_mysql_conn { MYSQL db; MYSQL *sock; MYSQL_RES *result; } rlm_sql_mysql_conn_t; typedef struct rlm_sql_mysql_config { char const *tls_ca_file; //!< Path to the CA used to validate the server's certificate. char const *tls_ca_path; //!< Directory containing CAs that may be used to validate the //!< servers certificate. char const *tls_certificate_file; //!< Public certificate we present to the server. char const *tls_private_key_file; //!< Private key for the certificate we present to the server. char const *tls_crl_file; //!< Public certificate we present to the server. char const *tls_crl_path; //!< Private key for the certificate we present to the server. char const *tls_cipher; //!< Colon separated list of TLS ciphers for TLS <= 1.2. bool tls_required; //!< Require that the connection is encrypted. bool tls_check_cert; //!< Verify there's a trust relationship between the server's ///< cert and one of the CAs we have configured. bool tls_check_cert_cn; //!< Verify that the CN in the server cert matches the host ///< we passed to mysql_real_connect(). char const *warnings_str; //!< Whether we always query the server for additional warnings. rlm_sql_mysql_warnings warnings; //!< mysql_warning_count() doesn't //!< appear to work with NDB cluster } rlm_sql_mysql_config_t; static CONF_PARSER tls_config[] = { { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_file), NULL }, { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_path), NULL }, { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_certificate_file), NULL }, { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_private_key_file), NULL }, #if HAVE_CRL_OPTIONS { "crl_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_crl_file), NULL }, { "crl_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_crl_path), NULL }, #endif /* * MySQL Specific TLS attributes */ { "cipher", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, tls_cipher), NULL }, /* * The closest thing we have to these options in other modules is * in rlm_rest. rlm_ldap has its own bizarre option set. * * There, the options can be toggled independently, here they can't * but for consistency we break them out anyway, and warn if the user * has provided an invalid list of flags. */ #if HAVE_TLS_OPTIONS { "tls_required", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_mysql_config_t, tls_required), "no" }, # if HAVE_TLS_VERIFY_OPTIONS { "check_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_mysql_config_t, tls_check_cert), "no" }, { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_mysql_config_t, tls_check_cert_cn), "no" }, # endif #endif CONF_PARSER_TERMINATOR }; static const CONF_PARSER driver_config[] = { { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config }, { "warnings", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, warnings_str), "auto" }, CONF_PARSER_TERMINATOR }; /* Prototypes */ static sql_rcode_t sql_free_result(rlm_sql_handle_t*, rlm_sql_config_t*); static int _sql_socket_destructor(rlm_sql_mysql_conn_t *conn) { DEBUG2("rlm_sql_mysql: Socket destructor called, closing socket"); if (conn->sock){ mysql_close(conn->sock); } return 0; } static int _mod_destructor(UNUSED rlm_sql_mysql_config_t *driver) { if (--mysql_instance_count == 0) mysql_library_end(); #if HAVE_TLS_VERIFY_OPTIONS if (driver->tls_check_cert && !driver->tls_required) { WARN("Implicitly setting tls_required = yes, as tls_check_cert = yes"); driver->tls_required = true; } if (driver->tls_check_cert_cn) { if (!driver->tls_required) { WARN("Implicitly setting tls_required = yes, as check_cert_cn = yes"); driver->tls_required = true; } if (!driver->tls_check_cert) { WARN("Implicitly setting check_cert = yes, as check_cert_cn = yes"); driver->tls_check_cert = true; } } #endif return 0; } static int mod_instantiate(CONF_SECTION *conf, rlm_sql_config_t *config) { rlm_sql_mysql_config_t *driver; int warnings; static bool version_done = false; if (!version_done) { version_done = true; INFO("rlm_sql_mysql: libmysql version: %s", mysql_get_client_info()); } if (mysql_instance_count == 0) { if (mysql_library_init(0, NULL, NULL)) { ERROR("rlm_sql_mysql: libmysql initialisation failed"); return -1; } } mysql_instance_count++; MEM(driver = config->driver = talloc_zero(config, rlm_sql_mysql_config_t)); talloc_set_destructor(driver, _mod_destructor); if (cf_section_parse(conf, driver, driver_config) < 0) { return -1; } warnings = fr_str2int(server_warnings_table, driver->warnings_str, -1); if (warnings < 0) { ERROR("rlm_sql_mysql: Invalid warnings value \"%s\", must be yes, no, or auto", driver->warnings_str); return -1; } driver->warnings = (rlm_sql_mysql_warnings)warnings; return 0; } static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn; rlm_sql_mysql_config_t *driver = config->driver; unsigned long sql_flags; MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_mysql_conn_t)); talloc_set_destructor(conn, _sql_socket_destructor); DEBUG("rlm_sql_mysql: Starting connect to MySQL server"); mysql_init(&(conn->db)); /* * If any of the TLS options are set, configure TLS * * According to MySQL docs this function always returns 0, so we won't * know if ssl setup succeeded until mysql_real_connect is called below. */ if (driver->tls_ca_file || driver->tls_ca_path || driver->tls_certificate_file || driver->tls_private_key_file) { mysql_ssl_set(&(conn->db), driver->tls_private_key_file, driver->tls_certificate_file, driver->tls_ca_file, driver->tls_ca_path, driver->tls_cipher); } #if HAVE_TLS_OPTIONS { enum mysql_option ssl_mysql_opt; unsigned int ssl_mysql_arg; bool ssl_mode_isset = false; # if defined(MARIADB_VERSION_ID) || defined(MARIADB_BASE_VERSION) # if HAVE_TLS_VERIFY_OPTIONS if (driver->tls_required || driver->tls_check_cert || driver->tls_check_cert_cn) { # else if (driver->tls_required) { # endif ssl_mode_isset = true; # if defined(MARIADB_VERSION_ID) || defined(MARIADB_BASE_VERSION) /** * For MariaDB, It should be true as can be seen in * https://github.com/MariaDB/server/blob/mariadb-5.5.68/sql-common/client.c#L4338 */ ssl_mysql_arg = true; #else ssl_mysql_arg = SSL_MODE_REQUIRED; #endif ssl_mysql_opt = MYSQL_OPT_SSL_VERIFY_SERVER_CERT; } # else ssl_mysql_opt = MYSQL_OPT_SSL_MODE; if (driver->tls_required) { ssl_mysql_arg = SSL_MODE_REQUIRED; ssl_mode_isset = true; } # if HAVE_TLS_VERIFY_OPTIONS if (driver->tls_check_cert) { ssl_mysql_arg = SSL_MODE_VERIFY_CA; ssl_mode_isset = true; } if (driver->tls_check_cert_cn) { ssl_mysql_arg = SSL_MODE_VERIFY_IDENTITY; ssl_mode_isset = true; } # endif /* MARIADB_VERSION_ID */ # endif if (ssl_mode_isset) mysql_options(&(conn->db), ssl_mysql_opt, &ssl_mysql_arg); } #endif #if HAVE_CRL_OPTIONS if (driver->tls_crl_file) mysql_options(&(conn->db), MYSQL_OPT_SSL_CRL, driver->tls_crl_file); if (driver->tls_crl_path) mysql_options(&(conn->db), MYSQL_OPT_SSL_CRLPATH, driver->tls_crl_path); #endif mysql_options(&(conn->db), MYSQL_READ_DEFAULT_GROUP, "freeradius"); /* * We need to know about connection errors, and are capable * of reconnecting automatically. */ #if MYSQL_VERSION_ID >= 50013 { int reconnect = 0; mysql_options(&(conn->db), MYSQL_OPT_RECONNECT, &reconnect); } #endif #if (MYSQL_VERSION_ID >= 50000) if (config->query_timeout) { unsigned int connect_timeout = config->query_timeout; unsigned int read_timeout = config->query_timeout; unsigned int write_timeout = config->query_timeout; /* * The timeout in seconds for each attempt to read from the server. * There are retries if necessary, so the total effective timeout * value is three times the option value. */ if (config->query_timeout >= 3) read_timeout /= 3; /* * The timeout in seconds for each attempt to write to the server. * There is a retry if necessary, so the total effective timeout * value is two times the option value. */ if (config->query_timeout >= 2) write_timeout /= 2; /* * Connect timeout is actually connect timeout (according to the * docs) there are no automatic retries. */ mysql_options(&(conn->db), MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout); mysql_options(&(conn->db), MYSQL_OPT_READ_TIMEOUT, &read_timeout); mysql_options(&(conn->db), MYSQL_OPT_WRITE_TIMEOUT, &write_timeout); } #endif #if (MYSQL_VERSION_ID >= 40100) sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS; #else sql_flags = CLIENT_FOUND_ROWS; #endif #ifdef CLIENT_MULTI_STATEMENTS sql_flags |= CLIENT_MULTI_STATEMENTS; #endif conn->sock = mysql_real_connect(&(conn->db), config->sql_server, config->sql_login, config->sql_password, config->sql_db, config->sql_port, NULL, sql_flags); if (!conn->sock) { ERROR("rlm_sql_mysql: Couldn't connect to MySQL server %s@%s:%s", config->sql_login, config->sql_server, config->sql_db); ERROR("rlm_sql_mysql: MySQL error: %s", mysql_error(&conn->db)); conn->sock = NULL; return RLM_SQL_ERROR; } DEBUG2("rlm_sql_mysql: Connected to database '%s' on %s, server version %s, protocol version %i", config->sql_db, mysql_get_host_info(conn->sock), mysql_get_server_info(conn->sock), mysql_get_proto_info(conn->sock)); return RLM_SQL_OK; } /** Analyse the last error that occurred on the socket, and determine an action * * @param server Socket from which to extract the server error. May be NULL. * @param client_errno Error from the client. * @return an action for rlm_sql to take. */ static sql_rcode_t sql_check_error(MYSQL *server, int client_errno) { int sql_errno = 0; /* * The client and server error numbers are in the * same numberspace. */ if (server) sql_errno = mysql_errno(server); if ((sql_errno == 0) && (client_errno != 0)) sql_errno = client_errno; if (sql_errno > 0) switch (sql_errno) { case CR_SERVER_GONE_ERROR: case CR_SERVER_LOST: case -1: return RLM_SQL_RECONNECT; case CR_OUT_OF_MEMORY: case CR_COMMANDS_OUT_OF_SYNC: case CR_UNKNOWN_ERROR: default: return RLM_SQL_ERROR; /* * Constraints errors that signify a duplicate, or that we might * want to try an alternative query. * * Error constants not found in the 3.23/4.0/4.1 manual page * are checked for. * Other error constants should always be available. */ case ER_DUP_UNIQUE: /* Can't write, because of unique constraint, to table '%s'. */ case ER_DUP_KEY: /* Can't write; duplicate key in table '%s' */ case ER_DUP_ENTRY: /* Duplicate entry '%s' for key %d. */ case ER_NO_REFERENCED_ROW: /* Cannot add or update a child row: a foreign key constraint fails */ case ER_ROW_IS_REFERENCED: /* Cannot delete or update a parent row: a foreign key constraint fails */ #ifdef ER_FOREIGN_DUPLICATE_KEY case ER_FOREIGN_DUPLICATE_KEY: /* Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry. */ #endif #ifdef ER_DUP_ENTRY_WITH_KEY_NAME case ER_DUP_ENTRY_WITH_KEY_NAME: /* Duplicate entry '%s' for key '%s' */ #endif #ifdef ER_NO_REFERENCED_ROW_2 case ER_NO_REFERENCED_ROW_2: #endif #ifdef ER_ROW_IS_REFERENCED_2 case ER_ROW_IS_REFERENCED_2: #endif return RLM_SQL_ALT_QUERY; /* * Constraints errors that signify an invalid query * that can never succeed. */ case ER_BAD_NULL_ERROR: /* Column '%s' cannot be null */ case ER_NON_UNIQ_ERROR: /* Column '%s' in %s is ambiguous */ return RLM_SQL_QUERY_INVALID; } return RLM_SQL_OK; } static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query) { rlm_sql_mysql_conn_t *conn = handle->conn; sql_rcode_t rcode; char const *info; if (!conn->sock) { ERROR("rlm_sql_mysql: Socket not connected"); return RLM_SQL_RECONNECT; } mysql_query(conn->sock, query); rcode = sql_check_error(conn->sock, 0); if (rcode != RLM_SQL_OK) { return rcode; } /* Only returns non-null string for INSERTS */ info = mysql_info(conn->sock); if (info) DEBUG2("rlm_sql_mysql: %s", info); return RLM_SQL_OK; } static sql_rcode_t sql_store_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; sql_rcode_t rcode; int ret; if (!conn->sock) { ERROR("rlm_sql_mysql: Socket not connected"); return RLM_SQL_RECONNECT; } retry_store_result: conn->result = mysql_store_result(conn->sock); if (!conn->result) { rcode = sql_check_error(conn->sock, 0); if (rcode != RLM_SQL_OK) return rcode; #if (MYSQL_VERSION_ID >= 40100) ret = mysql_next_result(conn->sock); if (ret == 0) { /* there are more results */ goto retry_store_result; } else if (ret > 0) return sql_check_error(NULL, ret); /* ret == -1 signals no more results */ #endif } return RLM_SQL_OK; } static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) { int num = 0; rlm_sql_mysql_conn_t *conn = handle->conn; #if MYSQL_VERSION_ID >= 32224 /* * Count takes a connection handle */ if (!(num = mysql_field_count(conn->sock))) { #else /* * Fields takes a result struct */ if (!(num = mysql_num_fields(conn->result))) { #endif return -1; } return num; } static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query) { sql_rcode_t rcode; rcode = sql_query(handle, config, query); if (rcode != RLM_SQL_OK) { return rcode; } rcode = sql_store_result(handle, config); if (rcode != RLM_SQL_OK) { return rcode; } /* Why? Per http://www.mysql.com/doc/n/o/node_591.html, * this cannot return an error. Perhaps just to complain if no * fields are found? */ sql_num_fields(handle, config); return rcode; } static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; if (conn->result) { return mysql_num_rows(conn->result); } return 0; } static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; sql_rcode_t rcode; MYSQL_ROW row; int ret; unsigned int num_fields, i; unsigned long *field_lens; /* * Check pointer before de-referencing it. */ if (!conn->result) { return RLM_SQL_RECONNECT; } TALLOC_FREE(handle->row); /* Clear previous row set */ retry_fetch_row: row = mysql_fetch_row(conn->result); if (!row) { rcode = sql_check_error(conn->sock, 0); if (rcode != RLM_SQL_OK) return rcode; #if (MYSQL_VERSION_ID >= 40100) sql_free_result(handle, config); ret = mysql_next_result(conn->sock); if (ret == 0) { /* there are more results */ if ((sql_store_result(handle, config) == 0) && (conn->result != NULL)) { goto retry_fetch_row; } } else if (ret > 0) return sql_check_error(NULL, ret); /* If ret is -1 then there are no more rows */ #endif return RLM_SQL_NO_MORE_ROWS; } num_fields = mysql_num_fields(conn->result); if (!num_fields) return RLM_SQL_NO_MORE_ROWS; field_lens = mysql_fetch_lengths(conn->result); MEM(handle->row = talloc_zero_array(handle, char *, num_fields + 1)); for (i = 0; i < num_fields; i++) { MEM(handle->row[i] = talloc_bstrndup(handle->row, row[i], field_lens[i])); } return RLM_SQL_OK; } static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; if (conn->result) { mysql_free_result(conn->result); conn->result = NULL; } TALLOC_FREE(handle->row); return RLM_SQL_OK; } /** Retrieves any warnings associated with the last query * * MySQL stores a limited number of warnings associated with the last query * executed. These can be very useful in diagnosing issues, or in some cases * working around bugs in MySQL which causes it to return the wrong error. * * @note Caller should free any memory allocated in ctx (talloc_free_children()). * * @param ctx to allocate temporary error buffers in. * @param out Array of sql_log_entrys to fill. * @param outlen Length of out array. * @param handle rlm_sql connection handle. * @param config rlm_sql config. * @return number of errors written to the sql_log_entry array or -1 on error. */ static size_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; MYSQL_RES *result; MYSQL_ROW row; unsigned int num_fields; size_t i = 0; if (outlen == 0) return 0; /* * Retrieve any warnings associated with the previous query * that were left lingering on the server. */ if (mysql_query(conn->sock, "SHOW WARNINGS") != 0) return -1; result = mysql_store_result(conn->sock); if (!result) return -1; /* * Fields should be [0] = Level, [1] = Code, [2] = Message */ num_fields = mysql_field_count(conn->sock); if (num_fields < 3) { WARN("rlm_sql_mysql: Failed retrieving warnings, expected 3 fields got %u", num_fields); mysql_free_result(result); return -1; } while ((row = mysql_fetch_row(result))) { char *msg = NULL; log_type_t type; /* * Translate the MySQL log level into our internal * log levels, so they get colourised correctly. */ if (strcasecmp(row[0], "warning") == 0) type = L_WARN; else if (strcasecmp(row[0], "note") == 0) type = L_DBG; else type = L_ERR; msg = talloc_asprintf(ctx, "%s: %s", row[1], row[2]); out[i].type = type; out[i].msg = msg; if (++i == outlen) break; } mysql_free_result(result); return i; } /** Retrieves any errors associated with the connection handle * * @note Caller should free any memory allocated in ctx (talloc_free_children()). * * @param ctx to allocate temporary error buffers in. * @param out Array of sql_log_entrys to fill. * @param outlen Length of out array. * @param handle rlm_sql connection handle. * @param config rlm_sql config. * @return number of errors written to the sql_log_entry array. */ static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, rlm_sql_handle_t *handle, rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; rlm_sql_mysql_config_t *driver = config->driver; char const *error; size_t i = 0; rad_assert(conn && conn->sock); rad_assert(outlen > 0); error = mysql_error(conn->sock); /* * Grab the error now in case it gets cleared on the next operation. */ if (error && (error[0] != '\0')) { error = talloc_asprintf(ctx, "ERROR %u (%s): %s", mysql_errno(conn->sock), error, mysql_sqlstate(conn->sock)); } /* * Don't attempt to get errors from the server, if the last error * was that the server was unavailable. */ if ((outlen > 1) && (sql_check_error(conn->sock, 0) != RLM_SQL_RECONNECT)) { size_t ret; unsigned int msgs; switch (driver->warnings) { case SERVER_WARNINGS_AUTO: /* * Check to see if any warnings can be retrieved from the server. */ msgs = mysql_warning_count(conn->sock); if (msgs == 0) { DEBUG3("rlm_sql_mysql: No additional diagnostic info on server"); break; } /* FALL-THROUGH */ case SERVER_WARNINGS_YES: ret = sql_warnings(ctx, out, outlen - 1, handle, config); if (ret > 0) i += ret; break; case SERVER_WARNINGS_NO: break; default: rad_assert(0); } } if (error) { out[i].type = L_ERR; out[i].msg = error; } i++; return i; } /** Finish query * * As a single SQL statement may return multiple results * sets, (for example stored procedures) it is necessary to check * whether more results exist and process them in turn if so. * */ static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config) { #if (MYSQL_VERSION_ID >= 40100) rlm_sql_mysql_conn_t *conn = handle->conn; int ret; MYSQL_RES *result; /* * If there's no result associated with the * connection handle, assume the first result in the * result set hasn't been retrieved. * * MySQL docs says there's no performance penalty for * calling mysql_store_result for queries which don't * return results. */ if (conn->result == NULL) { result = mysql_store_result(conn->sock); if (result) mysql_free_result(result); /* * ...otherwise call sql_free_result to free an * already stored result. */ } else { sql_free_result(handle, config); /* sql_free_result sets conn->result to NULL */ } /* * Drain any other results associated with the handle * * mysql_next_result advances the result cursor so that * the next call to mysql_store_result will retrieve * the next result from the server. * * Unfortunately this really does appear to be the * only way to return the handle to a consistent state. */ while (((ret = mysql_next_result(conn->sock)) == 0) && (result = mysql_store_result(conn->sock))) { mysql_free_result(result); } if (ret > 0) return sql_check_error(NULL, ret); #endif return RLM_SQL_OK; } static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) { rlm_sql_mysql_conn_t *conn = handle->conn; return mysql_affected_rows(conn->sock); } static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, void *arg) { size_t inlen; rlm_sql_handle_t *handle = talloc_get_type_abort(arg, rlm_sql_handle_t); rlm_sql_mysql_conn_t *conn = handle->conn; /* Check for potential buffer overflow */ inlen = strlen(in); if ((inlen * 2 + 1) > outlen) return 0; /* Prevent integer overflow */ if ((inlen * 2 + 1) <= inlen) return 0; return mysql_real_escape_string(conn->sock, out, in, inlen); } /* Exported to rlm_sql */ extern rlm_sql_module_t rlm_sql_mysql; rlm_sql_module_t rlm_sql_mysql = { .name = "rlm_sql_mysql", .flags = RLM_SQL_RCODE_FLAGS_ALT_QUERY, .mod_instantiate = mod_instantiate, .sql_socket_init = sql_socket_init, .sql_query = sql_query, .sql_select_query = sql_select_query, .sql_store_result = sql_store_result, .sql_num_fields = sql_num_fields, .sql_num_rows = sql_num_rows, .sql_affected_rows = sql_affected_rows, .sql_fetch_row = sql_fetch_row, .sql_free_result = sql_free_result, .sql_error = sql_error, .sql_finish_query = sql_finish_query, .sql_finish_select_query = sql_finish_query, .sql_escape_func = sql_escape_func };