27 RCSID(
"$Id: 4043b4070f030956dbd9e6236ae01db34b3d3ba2 $")
29 #define LOG_PREFIX "sql - mysql"
31 #include <freeradius-devel/server/base.h>
32 #include <freeradius-devel/util/debug.h>
38 #ifdef HAVE_MYSQL_MYSQL_H
39 # include <mysql/errmsg.h>
41 # include <mysql/mysql.h>
43 # include <mysql/mysqld_error.h>
44 #elif defined(HAVE_MYSQL_H)
49 # include <mysqld_error.h>
143 ERROR(
"Invalid warnings value \"%s\", must be yes, no, or auto",
inst->warnings_str);
148 if (
inst->tls_check_cert && !
inst->tls_required) {
149 WARN(
"Implicitly setting tls_required = yes, as tls_check_cert = yes");
150 inst->tls_required =
true;
152 if (
inst->tls_check_cert_cn) {
153 if (!
inst->tls_required) {
154 WARN(
"Implicitly setting tls_required = yes, as check_cert_cn = yes");
155 inst->tls_required =
true;
158 if (!
inst->tls_check_cert) {
159 WARN(
"Implicitly setting check_cert = yes, as check_cert_cn = yes");
160 inst->tls_check_cert =
true;
173 if (mysql_library_init(0, NULL, NULL)) {
174 ERROR(
"libmysql initialisation failed");
179 INFO(
"libmysql version: %s", mysql_get_client_info());
192 if (c->
status == 0)
goto connected;
208 ERROR(
"MySQL error: %s", mysql_error(&c->
db));
213 DEBUG2(
"Connected to database on %s, server version %s, protocol version %i",
214 mysql_get_host_info(c->
sock),
215 mysql_get_server_info(c->
sock), mysql_get_proto_info(c->
sock));
233 ERROR(
"Failed running \"open_query\"");
234 info = mysql_info(sql_conn->
sock);
235 if (info)
ERROR(
"%s", info);
243 result = mysql_store_result(sql_conn->
sock);
244 if (result) mysql_free_result(result);
245 while ((mysql_next_result(sql_conn->
sock) == 0) &&
246 (result = mysql_store_result(sql_conn->
sock))) {
247 mysql_free_result(result);
258 unsigned long sql_flags;
259 enum mysql_option ssl_mysql_opt;
260 unsigned int ssl_mode = 0;
261 bool ssl_mode_isset =
false;
267 DEBUG(
"Starting connect to MySQL server");
277 if (
inst->tls_ca_file ||
inst->tls_ca_path ||
278 inst->tls_certificate_file ||
inst->tls_private_key_file) {
279 mysql_ssl_set(&(c->
db),
inst->tls_private_key_file,
inst->tls_certificate_file,
280 inst->tls_ca_file,
inst->tls_ca_path,
inst->tls_cipher);
283 #ifdef MARIADB_BASE_VERSION
284 if (
inst->tls_required ||
inst->tls_check_cert ||
inst->tls_check_cert_cn) {
285 ssl_mode_isset =
true;
291 ssl_mysql_opt = MYSQL_OPT_SSL_VERIFY_SERVER_CERT;
294 ssl_mysql_opt = MYSQL_OPT_SSL_MODE;
295 if (
inst->tls_required) {
296 ssl_mode = SSL_MODE_REQUIRED;
297 ssl_mode_isset =
true;
299 if (
inst->tls_check_cert) {
300 ssl_mode = SSL_MODE_VERIFY_CA;
301 ssl_mode_isset =
true;
303 if (
inst->tls_check_cert_cn) {
304 ssl_mode = SSL_MODE_VERIFY_IDENTITY;
305 ssl_mode_isset =
true;
308 if (ssl_mode_isset) mysql_options(&(c->
db), ssl_mysql_opt, &ssl_mode);
310 if (
inst->tls_crl_file) mysql_options(&(c->
db), MYSQL_OPT_SSL_CRL,
inst->tls_crl_file);
311 if (
inst->tls_crl_path) mysql_options(&(c->
db), MYSQL_OPT_SSL_CRLPATH,
inst->tls_crl_path);
313 mysql_options(&(c->
db), MYSQL_READ_DEFAULT_GROUP,
"freeradius");
321 mysql_options(&(c->
db), MYSQL_OPT_RECONNECT, &reconnect);
324 sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS;
326 #ifdef CLIENT_MULTI_STATEMENTS
327 sql_flags |= CLIENT_MULTI_STATEMENTS;
330 mysql_options(&c->
db, MYSQL_OPT_NONBLOCK, 0);
337 config->sql_port, NULL, sql_flags);
339 c->
fd = mysql_get_socket(&c->
db);
342 ERROR(
"Could't connect to MySQL server %s@%s:%s",
config->sql_login,
344 ERROR(
"MySQL error: %s", mysql_error(&c->
db));
351 DEBUG2(
"Connected to database '%s' on %s, server version %s, protocol version %i",
353 mysql_get_server_info(c->
sock), mysql_get_proto_info(c->
sock));
362 DEBUG2(
"Connecting to database '%s' on %s:%d, fd %d",
400 if (server) sql_errno = mysql_errno(server);
401 if ((sql_errno == 0) && (client_errno != 0)) sql_errno = client_errno;
403 if (sql_errno > 0)
switch (sql_errno) {
404 case CR_SERVER_GONE_ERROR:
408 case CR_OUT_OF_MEMORY:
409 case CR_COMMANDS_OUT_OF_SYNC:
410 case CR_UNKNOWN_ERROR:
426 case ER_NO_REFERENCED_ROW:
427 case ER_ROW_IS_REFERENCED:
428 #ifdef ER_FOREIGN_DUPLICATE_KEY
429 case ER_FOREIGN_DUPLICATE_KEY:
431 #ifdef ER_DUP_ENTRY_WITH_KEY_NAME
432 case ER_DUP_ENTRY_WITH_KEY_NAME:
434 #ifdef ER_NO_REFERENCED_ROW_2
435 case ER_NO_REFERENCED_ROW_2:
437 #ifdef ER_ROW_IS_REFERENCED_2
438 case ER_ROW_IS_REFERENCED_2:
446 case ER_BAD_NULL_ERROR:
447 case ER_NON_UNIQ_ERROR:
461 conn->
result = mysql_store_result(conn->
sock);
465 ret = mysql_next_result(conn->
sock);
468 goto retry_store_result;
488 unsigned int fields, i;
489 MYSQL_FIELD *field_info;
497 fields = mysql_field_count(conn->
sock);
504 field_info = mysql_fetch_fields(conn->
result);
507 MEM(
names = talloc_array(query_ctx,
char const *, fields));
509 for (i = 0; i < fields; i++)
names[i] = field_info[i].
name;
521 unsigned int num_fields, i;
522 unsigned long *field_lens;
532 TALLOC_FREE(query_ctx->
row);
535 row = mysql_fetch_row(conn->
result);
540 mysql_free_result(conn->
result);
543 ret = mysql_next_result(conn->
sock);
547 goto retry_fetch_row;
549 }
else if (ret > 0) {
560 num_fields = mysql_field_count(conn->
sock);
566 field_lens = mysql_fetch_lengths(conn->
result);
568 MEM(query_ctx->
row = talloc_zero_array(query_ctx,
char *, num_fields + 1));
569 for (i = 0; i < num_fields; i++) {
582 mysql_free_result(conn->
result);
585 TALLOC_FREE(query_ctx->
row);
612 unsigned int num_fields;
615 if (outlen == 0)
return 0;
621 if (mysql_query(conn->
sock,
"SHOW WARNINGS") != 0)
return -1;
622 result = mysql_store_result(conn->
sock);
623 if (!result)
return -1;
628 num_fields = mysql_field_count(conn->
sock);
629 if (num_fields < 3) {
630 WARN(
"Failed retrieving warnings, expected 3 fields got %u", num_fields);
631 mysql_free_result(result);
636 while ((row = mysql_fetch_row(result))) {
651 if (++i == outlen)
break;
654 mysql_free_result(result);
678 if (!query_ctx->
tconn)
return 0;
683 error = mysql_error(conn->
sock);
688 if (error && (error[0] !=
'\0')) {
690 mysql_sqlstate(conn->
sock));
701 switch (
inst->warnings) {
706 msgs = mysql_warning_count(conn->
sock);
708 DEBUG3(
"No additional diagnostic info on server");
715 if (ret > 0) i += ret;
751 if (query_ctx->
treq && !(query_ctx->
treq->state &
778 if (conn->
result == NULL) {
779 result = mysql_store_result(conn->
sock);
780 if (result) mysql_free_result(result);
799 while (((ret = mysql_next_result(conn->
sock)) == 0) &&
800 (result = mysql_store_result(conn->
sock))) {
801 mysql_free_result(result);
812 return mysql_affected_rows(conn->
sock);
823 if ((
inlen * 2 + 1) > outlen)
return 0;
827 return mysql_real_escape_string(&conn->
db,
out,
in,
inlen);
845 ERROR(
"%s - Connection failed: %s", tconn->conn->name,
fr_syserror(fd_errno));
859 char const *log_prefix,
void *uctx)
869 conn_conf, log_prefix, thread->
inst);
871 PERROR(
"Failed allocating state handler for new SQL connection");
905 PERROR(
"Failed inserting FD event");
932 switch (query_ctx->
status) {
936 query_ctx->
tconn = tconn;
951 info = mysql_info(sql_conn->
sock);
953 if (info)
ERROR(
"%s", info);
954 switch (query_ctx->
rcode) {
972 sql_conn->
status = mysql_store_result_start(&sql_conn->
result, sql_conn->
sock);
1022 switch (query_ctx->
status) {
1042 if (sql_conn->
status != 0)
return;
1046 switch (query_ctx->
status) {
1063 info = mysql_info(sql_conn->
sock);
1078 if (!query_ctx->
treq)
return;
1103 query_ctx->
treq = NULL;
1153 inst->config.trunk_conf.conn_conf,
1157 PERROR(
"Failed allocating state handler for SQL escape connection");
1175 .name =
"sql_mysql",
1197 .uses_trunks =
true,
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
#define L(_str)
Helper for initialising arrays of string literals.
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
#define CONF_PARSER_TERMINATOR
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
#define FR_CONF_POINTER(_name, _type, _flags, _res_p)
conf_parser_t which parses a single CONF_PAIR producing a single global result
#define FR_CONF_OFFSET_FLAGS(_name, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Defines a CONF_PAIR to C data type mapping.
@ CONNECTION_STATE_FAILED
Connection has failed.
@ CONNECTION_STATE_CONNECTED
File descriptor is open (ready for writing).
@ CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Holds a complete set of functions for a connection.
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
#define fr_event_fd_insert(...)
void(* fr_event_fd_cb_t)(fr_event_list_t *el, int fd, int flags, void *uctx)
Called when an IO event occurs on a file descriptor.
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
#define unlang_function_repeat_set(_request, _repeat)
Set a new repeat function for an existing function frame.
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
int fr_event_fd_delete(fr_event_list_t *el, int fd, fr_event_filter_t filter)
Remove a file descriptor from the event loop.
Stores all information relating to an event list.
@ L_DBG
Only displayed when debugging is enabled.
int strcasecmp(char *s1, char *s2)
module_instance_t * mi
Instance of the module being instantiated.
Temporary structure to hold arguments for instantiation calls.
static const conf_parser_t config[]
rlm_rcode_t
Return codes indicating the result of the module call.
static int instantiate(module_inst_ctx_t const *mctx)
Prototypes and functions for the SQL module.
fr_sql_query_status_t status
Status of the query.
trunk_connection_t * tconn
Trunk connection this query is being run on.
rlm_sql_t const * inst
Module instance data.
rlm_sql_t const * inst
Module instance for this query.
char const * query_str
Query string to run.
request_t * request
Request this query relates to.
sql_rcode_t
Action to take at end of an SQL query.
@ RLM_SQL_QUERY_INVALID
Query syntax error.
@ RLM_SQL_ALT_QUERY
Key constraint violation, use an alternative query.
@ RLM_SQL_RECONNECT
Stale connection, should reconnect.
@ RLM_SQL_ERROR
General connection/server error.
@ RLM_SQL_NO_MORE_ROWS
No more rows available.
#define RLM_SQL_RCODE_FLAGS_ALT_QUERY
Can distinguish between other errors and those.
rlm_sql_row_t row
Row data from the last query.
sql_rcode_t rcode
Result code.
trunk_request_t * treq
Trunk request for this query.
char const * connect_query
Query executed after establishing new connection.
@ SQL_QUERY_RETURNED
Query has executed.
@ SQL_QUERY_FETCHING_RESULTS
Fetching results from server.
@ SQL_QUERY_FAILED
Failed to submit.
@ SQL_QUERY_SUBMITTED
Submitted for execution.
@ SQL_QUERY_PREPARED
Ready to submit.
@ SQL_QUERY_RESULTS_FETCHED
Results fetched from the server.
static sql_rcode_t sql_check_error(MYSQL *server, int client_errno)
Analyse the last error that occurred on the socket, and determine an action.
static int mod_load(void)
static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
bool tls_check_cert
Verify there's a trust relationship between the server's cert and one of the CAs we have configured.
char const * tls_certificate_file
Public certificate we present to the server.
static void * sql_escape_arg_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, void *uctx)
Allocate the argument used for the SQL escape function.
fr_sql_query_t * query_ctx
Current query running on this connection.
static sql_rcode_t sql_free_result(fr_sql_query_t *, rlm_sql_config_t const *)
static void _sql_connection_close(fr_event_list_t *el, void *h, UNUSED void *uctx)
static fr_table_num_sorted_t const server_warnings_table[]
char const * tls_private_key_file
Private key for the certificate we present to the server.
char const * tls_crl_path
Private key for the certificate we present to the server.
char const * tls_crl_file
Public certificate we present to the server.
int status
returned by the most recent non-blocking function call.
char const * tls_cipher
Colon separated list of TLS ciphers for TLS <= 1.2.
static void _sql_connect_io_notify(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
Callback for I/O events in response to mysql_real_connect_start()
static connection_t * sql_trunk_connection_alloc(trunk_connection_t *tconn, fr_event_list_t *el, connection_conf_t const *conn_conf, char const *log_prefix, void *uctx)
Allocate an SQL trunk connection.
static void sql_conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
char const * tls_ca_path
Directory containing CAs that may be used to validate the servers certificate.
static void sql_trunk_request_demux(UNUSED fr_event_list_t *el, UNUSED trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
static size_t sql_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, void *arg)
static void sql_trunk_request_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
static void sql_escape_arg_free(void *uctx)
static void sql_conn_readable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
int fd
fd for this connection's I/O events.
static conf_parser_t tls_config[]
static void sql_conn_writable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
static void sql_request_cancel(connection_t *conn, void *preq, trunk_cancel_reason_t reason, UNUSED void *uctx)
rlm_sql_driver_t rlm_sql_mysql
MYSQL * sock
Connection details as returned by connection init functions.
static int sql_num_rows(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
MYSQL db
Structure representing connection details.
static void mod_unload(void)
static void sql_request_cancel_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
char const * warnings_str
Whether we always query the server for additional warnings.
static const conf_parser_t driver_config[]
static unlang_action_t sql_select_query_resume(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
static sql_rcode_t sql_store_result(rlm_sql_mysql_conn_t *conn, UNUSED rlm_sql_config_t const *config)
static size_t server_warnings_table_len
static sql_rcode_t sql_finish_query(fr_sql_query_t *query_ctx, rlm_sql_config_t const *config)
Finish query.
static void _sql_connect_query_run(connection_t *conn, UNUSED connection_state_t prev, UNUSED connection_state_t state, void *uctx)
static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, fr_sql_query_t *query_ctx, rlm_sql_config_t const *config)
Retrieves any errors associated with the query context.
static unlang_action_t sql_query_resume(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
connection_t * conn
Generic connection structure for this connection.
bool tls_check_cert_cn
Verify that the CN in the server cert matches the host we passed to mysql_real_connect().
static void sql_trunk_connection_notify(trunk_connection_t *tconn, connection_t *conn, fr_event_list_t *el, trunk_connection_event_t notify_on, UNUSED void *uctx)
static size_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, rlm_sql_mysql_conn_t *conn, UNUSED rlm_sql_config_t const *config)
Retrieves any warnings associated with the last query.
static int sql_affected_rows(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
char const * tls_ca_file
Path to the CA used to validate the server's certificate.
bool tls_required
Require that the connection is encrypted.
MYSQL_RES * result
Result from most recent query.
static void sql_request_fail(request_t *request, void *preq, UNUSED void *rctx, UNUSED trunk_request_state_t state, UNUSED void *uctx)
static int mod_instantiate(module_inst_ctx_t const *mctx)
rlm_sql_mysql_warnings warnings
mysql_warning_count() doesn't appear to work with NDB cluster
void connection_signal_halt(connection_t *conn)
Shuts down a connection ungracefully.
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
void connection_signal_init(connection_t *conn)
Asynchronously signal a halted connection to start.
connection_watch_entry_t * connection_add_watch_post(connection_t *conn, connection_state_t state, connection_watch_t watch, bool oneshot, void const *uctx)
Add a callback to be executed after a state function has been called.
connection_t * connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, connection_funcs_t const *funcs, connection_conf_t const *conf, char const *log_prefix, void const *uctx)
Allocate a new connection.
void connection_signal_connected(connection_t *conn)
Asynchronously signal that the connection is open.
void * data
Module's instance data.
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
module_t common
Common fields for all loadable modules.
module_instance_t * driver_submodule
Driver's submodule.
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
An element in a lexicographically sorted array of name to num mappings.
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
#define talloc_get_type_abort_const
static const char * names[8]
int trunk_connection_pop_cancellation(trunk_request_t **treq_out, trunk_connection_t *tconn)
Pop a cancellation request off a connection's cancellation queue.
void trunk_request_signal_fail(trunk_request_t *treq)
Signal that a trunk request failed.
void trunk_connection_signal_readable(trunk_connection_t *tconn)
Signal that a trunk connection is readable.
trunk_enqueue_t trunk_request_requeue(trunk_request_t *treq)
Re-enqueue a request on the same connection.
void trunk_request_signal_cancel_complete(trunk_request_t *treq)
Signal that a remote server acked our cancellation.
int trunk_connection_pop_request(trunk_request_t **treq_out, trunk_connection_t *tconn)
Pop a request off a connection's pending queue.
void trunk_request_signal_sent(trunk_request_t *treq)
Signal that the request was written to a connection successfully.
void trunk_connection_signal_reconnect(trunk_connection_t *tconn, connection_reason_t reason)
Signal a trunk connection is no longer viable.
void trunk_connection_signal_writable(trunk_connection_t *tconn)
Signal that a trunk connection is writable.
void trunk_request_signal_reapable(trunk_request_t *treq)
Signal that the request was written to a connection successfully, but no response is expected.
Associates request queues with a connection.
trunk_connection_event_t
What type of I/O events the trunk connection is currently interested in receiving.
@ TRUNK_CONN_EVENT_BOTH
Trunk should be notified if a connection is readable or writable.
@ TRUNK_CONN_EVENT_WRITE
Trunk should be notified if a connection is writable.
@ TRUNK_CONN_EVENT_NONE
Don't notify the trunk on connection state changes.
@ TRUNK_CONN_EVENT_READ
Trunk should be notified if a connection is readable.
trunk_cancel_reason_t
Reasons for a request being cancelled.
@ TRUNK_CANCEL_REASON_SIGNAL
Request cancelled due to a signal.
trunk_request_state_t
Used for sanity checks and to simplify freeing.
@ TRUNK_REQUEST_STATE_REAPABLE
Request has been written, needs to persist, but we are not currently waiting for any response.
@ TRUNK_REQUEST_STATE_COMPLETE
The request is complete.
@ TRUNK_REQUEST_STATE_SENT
Was written to a socket. Waiting for a response.
static fr_event_list_t * el
static size_t char fr_sbuff_t size_t inlen
static size_t char ** out