The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
rlm_sql_mysql.c
Go to the documentation of this file.
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: 10a4d35182f6e8d4dfc8f4108a1b5947b41e0003 $
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@freeradius.org)
26 */
27RCSID("$Id: 10a4d35182f6e8d4dfc8f4108a1b5947b41e0003 $")
28
29#define LOG_PREFIX log_prefix
30
31#include <freeradius-devel/server/base.h>
32#include <freeradius-devel/util/debug.h>
33
34#include <sys/stat.h>
35
36#include "config.h"
37
38#ifdef HAVE_MYSQL_MYSQL_H
39# include <mysql/errmsg.h>
40DIAG_OFF(strict-prototypes) /* Seen with homebrew mysql client 5.7.13 */
41# include <mysql/mysql.h>
42DIAG_ON(strict-prototypes)
43# include <mysql/mysqld_error.h>
44#elif defined(HAVE_MYSQL_H)
45# include <errmsg.h>
46DIAG_OFF(strict-prototypes) /* Seen with homebrew mysql client 5.7.13 */
47# include <mysql.h>
48DIAG_ON(strict-prototypes)
49# include <mysqld_error.h>
50#endif
51
52#include "rlm_sql.h"
53#include "rlm_sql_trunk.h"
54
60
62 { L("auto"), SERVER_WARNINGS_AUTO },
63 { L("no"), SERVER_WARNINGS_NO },
64 { L("yes"), SERVER_WARNINGS_YES }
65};
67
68typedef struct {
69 MYSQL db; //!< Structure representing connection details.
70 MYSQL *sock; //!< Connection details as returned by connection init functions.
71 MYSQL_RES *result; //!< Result from most recent query.
72 connection_t *conn; //!< Generic connection structure for this connection.
73 int fd; //!< Our fd for this connection's I/O events.
74 int client_fd; //!< Socket the client library last reported, for detecting fd changes.
75 fr_sql_query_t *query_ctx; //!< Current query running on this connection.
76 int status; //!< returned by the most recent non-blocking function call.
78
79typedef struct {
80 char const *tls_ca_file; //!< Path to the CA used to validate the server's certificate.
81 char const *tls_ca_path; //!< Directory containing CAs that may be used to validate the
82 //!< servers certificate.
83 char const *tls_certificate_file; //!< Public certificate we present to the server.
84 char const *tls_private_key_file; //!< Private key for the certificate we present to the server.
85
86 char const *tls_crl_file; //!< Public certificate we present to the server.
87 char const *tls_crl_path; //!< Private key for the certificate we present to the server.
88
89 char const *tls_cipher; //!< Colon separated list of TLS ciphers for TLS <= 1.2.
90
91 bool tls_required; //!< Require that the connection is encrypted.
92 bool tls_check_cert; //!< Verify there's a trust relationship between the server's
93 ///< cert and one of the CAs we have configured.
94 bool tls_check_cert_cn; //!< Verify that the CN in the server cert matches the host
95 ///< we passed to mysql_real_connect().
96
97 char const *warnings_str; //!< Whether we always query the server for additional warnings.
98 rlm_sql_mysql_warnings warnings; //!< mysql_warning_count() doesn't
99 //!< appear to work with NDB cluster
100
101 char const *character_set; //!< Character set to use on connections.
103
104typedef struct {
105 MYSQL db; //!< Our copy of the connection details - holds the flags needed
106 ///< by mysql_real_escape_string()
107 bool ready; //!< Has the db structure been populated.
108 connection_t *conn; //!< Connection used to fetch server status.
110
114 { FR_CONF_OFFSET_FLAGS("certificate_file", CONF_FLAG_FILE_READABLE, rlm_sql_mysql_t, tls_certificate_file) },
115 { FR_CONF_OFFSET_FLAGS("private_key_file", CONF_FLAG_FILE_READABLE, rlm_sql_mysql_t, tls_private_key_file) },
116 { FR_CONF_OFFSET_FLAGS("crl_file", CONF_FLAG_FILE_READABLE, rlm_sql_mysql_t, tls_crl_file) },
117 { FR_CONF_OFFSET_FLAGS("crl_path", CONF_FLAG_FILE_READABLE, rlm_sql_mysql_t, tls_crl_path) },
118 /*
119 * MySQL Specific TLS attributes
120 */
121 { FR_CONF_OFFSET("cipher", rlm_sql_mysql_t, tls_cipher) },
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 { FR_CONF_OFFSET("tls_required", rlm_sql_mysql_t, tls_required) },
132 { FR_CONF_OFFSET("check_cert", rlm_sql_mysql_t, tls_check_cert) },
133 { FR_CONF_OFFSET("check_cert_cn", rlm_sql_mysql_t, tls_check_cert_cn) },
135};
136
137static const conf_parser_t driver_config[] = {
138 { FR_CONF_POINTER("tls", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) tls_config },
139
140 { FR_CONF_OFFSET("warnings", rlm_sql_mysql_t, warnings_str), .dflt = "auto" },
141 { FR_CONF_OFFSET("character_set", rlm_sql_mysql_t, character_set) },
143};
144
145/* Prototypes */
147
148static int mod_instantiate(module_inst_ctx_t const *mctx)
149{
150 rlm_sql_mysql_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_sql_mysql_t);
151 int warnings;
152 char const *log_prefix = mctx->mi->name;
153
154 warnings = fr_table_value_by_str(server_warnings_table, inst->warnings_str, -1);
155 if (warnings < 0) {
156 ERROR("Invalid warnings value \"%s\", must be yes, no, or auto", inst->warnings_str);
157 return -1;
158 }
159 inst->warnings = (rlm_sql_mysql_warnings)warnings;
160
161 if (inst->tls_check_cert && !inst->tls_required) {
162 WARN("Implicitly setting tls_required = yes, as tls_check_cert = yes");
163 inst->tls_required = true;
164 }
165 if (inst->tls_check_cert_cn) {
166 if (!inst->tls_required) {
167 WARN("Implicitly setting tls_required = yes, as check_cert_cn = yes");
168 inst->tls_required = true;
169 }
170
171 if (!inst->tls_check_cert) {
172 WARN("Implicitly setting check_cert = yes, as check_cert_cn = yes");
173 inst->tls_check_cert = true;
174 }
175 }
176 return 0;
177}
178
179static void mod_unload(void)
180{
181 mysql_library_end();
182}
183
184static int mod_load(void)
185{
186 char const *log_prefix = "rlm_sql_mysql";
187 if (mysql_library_init(0, NULL, NULL)) {
188 ERROR("libmysql initialisation failed");
189
190 return -1;
191 }
192
193 INFO("libmysql version: %s", mysql_get_client_info());
194
195 return 0;
196}
197
198/** Stop tracking our fd for a connection.
199 *
200 * Removes the event and closes our descriptor. Safe to call repeatedly.
201 */
203{
204 if (c->fd < 0) return;
205
207 close(c->fd);
208 c->fd = -1;
209 c->client_fd = -1;
210}
211
212/** Track the socket actually being used by the client library.
213 *
214 * The client library owns its socket and potentially both closes and replaces
215 * it without telling us during connection setup, meaning events are still
216 * registered on the wrong fd.
217 *
218 * Create a dup() of the fd which gets used in the event list to avoid this
219 * issue.
220 *
221 * Call after each mysql_real_connect_* call to ensure the correct fd is in
222 * the event list.
223 *
224 * @param[in] c connection to update.
225 * @param[in] el event list the old event is registered in, may be NULL if
226 * nothing is registered yet.
227 * @return
228 * - 0 if the descriptor is unchanged or was updated.
229 * - -1 on error or if the library closed closed the connection.
230 */
232{
233 char const *log_prefix = c->conn->name;
234 int client_fd = mysql_get_socket(&c->db);
235
236 if (client_fd == c->client_fd) return 0; /* Still the same socket */
237
239
240 /*
241 * The library has finished with the socket entirely, which is what a
242 * failed connect leaves behind.
243 */
244 if (client_fd < 0){
245 ERROR("MySQL error: %s", mysql_error(&c->db));
246 return -1;
247 }
248
249 c->fd = dup(client_fd);
250 if (unlikely(c->fd < 0)) {
251 ERROR("Failed duplicating MySQL socket %d: %s", client_fd, fr_syserror(errno));
252 return -1;
253 }
254 c->client_fd = client_fd;
255
256 return 0;
257}
258
259#ifdef HAVE_MARIADB_OPT_SOCKET_CALLBACK
260/** Called by the client library when it is about to close, or has just created, a socket
261 *
262 * The handle is still valid when MARIADB_SOCKET_CLOSING arrives, so this is the one
263 * point at which the event can be removed without racing the number being reused.
264 * Only the closing notification is of interest; the new socket is picked up by
265 * sql_mysql_fd_track() on return from the call that created it.
266 */
267static void _sql_socket_event(void *uctx, my_socket handle, enum enum_mariadb_socket_event event)
268{
269 rlm_sql_mysql_conn_t *c = talloc_get_type_abort(uctx, rlm_sql_mysql_conn_t);
270
271 if (event != MARIADB_SOCKET_CLOSING) return;
272 if (handle != c->client_fd) return;
273
274 sql_mysql_fd_release(c, c->conn->el);
275}
276#endif
277
278/** Callback for I/O events in response to mysql_real_connect_start()
279 */
280static void _sql_connect_io_notify(fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
281{
282 rlm_sql_mysql_conn_t *c = talloc_get_type_abort(uctx, rlm_sql_mysql_conn_t);
283 char const *log_prefix = c->conn->name;
284
285 if (c->status == 0) goto connected;
286 c->status = mysql_real_connect_cont(&c->sock, &c->db, c->status);
287
288 /*
289 * The library may have closed the socket we were waiting on and moved
290 * to the next address, so re-check before doing anything with it.
291 */
292 if (unlikely(sql_mysql_fd_track(c, el) < 0)) {
294 return;
295 }
296
297 /*
298 * If status is not zero, we're still waiting for something.
299 * The event will be fired again when that happens.
300 */
301 if (c->status != 0) {
302 if (unlikely(c->fd < 0)) {
303 ERROR("MySQL is waiting on a socket it has already closed");
305 return;
306 }
307 (void) fr_event_fd_insert(c, NULL, c->conn->el, c->fd,
308 c->status & MYSQL_WAIT_READ ? _sql_connect_io_notify : NULL,
309 c->status & MYSQL_WAIT_WRITE ? _sql_connect_io_notify : NULL, NULL, c);
310 return;
311 }
312
313connected:
314 /*
315 * Pause any notifications until we're actually ready
316 * to operate on the connection.
317 *
318 * Deleting by fd is safe here only because the fd is ours: see
319 * sql_mysql_fd_track().
320 */
321 if (c->fd >= 0) fr_event_fd_delete(el, c->fd, FR_EVENT_FILTER_IO);
322
323 if (!c->sock) {
324 ERROR("MySQL error: %s", mysql_error(&c->db));
326 return;
327 }
328
329 DEBUG2("Connected to database on %s, server version %s, protocol version %i",
330 mysql_get_host_info(c->sock),
331 mysql_get_server_info(c->sock), mysql_get_proto_info(c->sock));
332
334}
335
337 UNUSED connection_state_t state, void *uctx)
338{
340 rlm_sql_mysql_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_mysql_conn_t);
341 char const *log_prefix = conn->name;
342 int ret;
343 MYSQL_RES *result;
344
345 DEBUG2("Executing \"%s\"", sql->config.connect_query);
346
347 ret = mysql_real_query(sql_conn->sock, sql->config.connect_query, strlen(sql->config.connect_query));
348 if (ret != 0) {
349 char const *info;
350 ERROR("Failed running \"open_query\"");
351 info = mysql_info(sql_conn->sock);
352 if (info) ERROR("%s", info);
354 return;
355 }
356
357 /*
358 * These queries should not return any results - but let's be safe
359 */
360 result = mysql_store_result(sql_conn->sock);
361 if (result) mysql_free_result(result);
362 while ((mysql_next_result(sql_conn->sock) == 0) &&
363 (result = mysql_store_result(sql_conn->sock))) {
364 mysql_free_result(result);
365 }
366}
367
368CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
369static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
370{
372 rlm_sql_mysql_t const *inst = talloc_get_type_abort(sql->driver_submodule->data, rlm_sql_mysql_t);
373 char const *log_prefix = conn->name;
375 rlm_sql_config_t const *config = &sql->config;
376
377 unsigned long sql_flags;
378
379 MEM(c = talloc_zero(conn, rlm_sql_mysql_conn_t));
380 c->conn = conn;
381 c->fd = -1;
382 c->client_fd = -1;
383
384 DEBUG("Starting connect to MySQL server");
385
386 mysql_init(&c->db);
387
388#ifdef HAVE_MARIADB_OPT_SOCKET_CALLBACK
389 /*
390 * Get notifications when the library is going to close a socket.
391 */
392 mysql_optionsv(&c->db, MARIADB_OPT_SOCKET_CALLBACK, (void *)_sql_socket_event, (void *)c);
393#endif
394
395 /*
396 * If any of the TLS options are set, configure TLS
397 *
398 * According to MySQL docs this function always returns 0, so we won't
399 * know if ssl setup succeeded until mysql_real_connect is called below.
400 */
401 if (inst->tls_ca_file || inst->tls_ca_path ||
402 inst->tls_certificate_file || inst->tls_private_key_file) {
403 mysql_ssl_set(&(c->db), inst->tls_private_key_file, inst->tls_certificate_file,
404 inst->tls_ca_file, inst->tls_ca_path, inst->tls_cipher);
405 }
406
407 if (inst->tls_required || inst->tls_check_cert || inst->tls_check_cert_cn) {
408 unsigned int ssl_mode = true;
409 /**
410 * For MariaDB, It should be true as can be seen in
411 * https://github.com/MariaDB/server/blob/mariadb-5.5.68/sql-common/client.c#L4338
412 */
413 mysql_optionsv(&(c->db), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &ssl_mode);
414 }
415
416 if (inst->tls_crl_file) mysql_optionsv(&(c->db), MYSQL_OPT_SSL_CRL, inst->tls_crl_file);
417 if (inst->tls_crl_path) mysql_optionsv(&(c->db), MYSQL_OPT_SSL_CRLPATH, inst->tls_crl_path);
418
419 mysql_optionsv(&(c->db), MYSQL_READ_DEFAULT_GROUP, "freeradius");
420
421 if (inst->character_set) mysql_optionsv(&(c->db), MYSQL_SET_CHARSET_NAME, inst->character_set);
422
423 sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS;
424
425#ifdef CLIENT_MULTI_STATEMENTS
426 sql_flags |= CLIENT_MULTI_STATEMENTS;
427#endif
428
429 mysql_optionsv(&c->db, MYSQL_OPT_NONBLOCK, 0);
430
431 c->status = mysql_real_connect_start(&c->sock, &c->db,
432 config->sql_server,
433 config->sql_login,
434 config->sql_password,
435 config->sql_db,
436 config->sql_port, NULL, sql_flags);
437
438 if ((mysql_get_socket(&c->db) < 0) || (sql_mysql_fd_track(c, NULL) < 0)) {
439 ERROR("Could't connect to MySQL server %s@%s:%s", config->sql_login,
440 config->sql_server, config->sql_db);
441 ERROR("MySQL error: %s", mysql_error(&c->db));
442 error:
443 talloc_free(c);
445 }
446
447 if (c->status == 0) {
448 DEBUG2("Connected to database '%s' on %s, server version %s, protocol version %i",
449 config->sql_db, mysql_get_host_info(c->sock),
450 mysql_get_server_info(c->sock), mysql_get_proto_info(c->sock));
451 goto finish;
452 }
453
454 if (fr_event_fd_insert(c, NULL, c->conn->el, c->fd,
455 c->status & MYSQL_WAIT_READ ? _sql_connect_io_notify : NULL,
456 c->status & MYSQL_WAIT_WRITE ? _sql_connect_io_notify : NULL, NULL, c) != 0) goto error;
457
458 DEBUG2("Connecting to database '%s' on %s:%d, fd %d",
459 config->sql_db, config->sql_server, config->sql_port, c->fd);
460
461finish:
462 *h = c;
463
465 _sql_connect_query_run, true, sql);
466
468}
469
470static void _sql_connection_close(fr_event_list_t *el, void *h, UNUSED void *uctx)
471{
472 rlm_sql_mysql_conn_t *c = talloc_get_type_abort(h, rlm_sql_mysql_conn_t);
473
474 /*
475 * Clean up event and our fd before closing.
476 */
478
479 mysql_close(&c->db);
480 c->query_ctx = NULL;
481 talloc_free(h);
482}
483
484/** Analyse the last error that occurred on the socket, and determine an action
485 *
486 * @param server Socket from which to extract the server error. May be NULL.
487 * @param client_errno Error from the client.
488 * @return an action for #rlm_sql_t to take.
489 */
490static sql_rcode_t sql_check_error(MYSQL *server, int client_errno)
491{
492 int sql_errno = 0;
493
494 /*
495 * The client and server error numbers are in the
496 * same numberspace.
497 */
498 if (server) sql_errno = mysql_errno(server);
499 if ((sql_errno == 0) && (client_errno != 0)) sql_errno = client_errno;
500
501 if (sql_errno > 0) switch (sql_errno) {
502 case CR_SERVER_GONE_ERROR:
503 case CR_SERVER_LOST:
504 return RLM_SQL_RECONNECT;
505
506 case CR_OUT_OF_MEMORY:
507 case CR_COMMANDS_OUT_OF_SYNC:
508 case CR_UNKNOWN_ERROR:
509 default:
510 return RLM_SQL_ERROR;
511
512 /*
513 * Constraints errors that signify a duplicate, or that we might
514 * want to try an alternative query.
515 *
516 * Error constants not found in the 3.23/4.0/4.1 manual page
517 * are checked for.
518 * Other error constants should always be available.
519 */
520 case ER_DUP_UNIQUE: /* Can't write, because of unique constraint, to table '%s'. */
521 case ER_DUP_KEY: /* Can't write; duplicate key in table '%s' */
522
523 case ER_DUP_ENTRY: /* Duplicate entry '%s' for key %d. */
524 case ER_NO_REFERENCED_ROW: /* Cannot add or update a child row: a foreign key constraint fails */
525 case ER_ROW_IS_REFERENCED: /* Cannot delete or update a parent row: a foreign key constraint fails */
526#ifdef ER_FOREIGN_DUPLICATE_KEY
527 case ER_FOREIGN_DUPLICATE_KEY: /* Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry. */
528#endif
529#ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
530 case ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO: /* Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in table '%s', key '%s' */
531#endif
532#ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
533 case ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO: /* Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in a child table */
534#endif
535#ifdef ER_DUP_ENTRY_WITH_KEY_NAME
536 case ER_DUP_ENTRY_WITH_KEY_NAME: /* Duplicate entry '%s' for key '%s' */
537#endif
538#ifdef ER_NO_REFERENCED_ROW_2
539 case ER_NO_REFERENCED_ROW_2:
540#endif
541#ifdef ER_ROW_IS_REFERENCED_2
542 case ER_ROW_IS_REFERENCED_2:
543#endif
544 return RLM_SQL_ALT_QUERY;
545
546 /*
547 * Constraints errors that signify an invalid query
548 * that can never succeed.
549 */
550 case ER_BAD_NULL_ERROR: /* Column '%s' cannot be null */
551 case ER_NON_UNIQ_ERROR: /* Column '%s' in %s is ambiguous */
553
554 /*
555 * Constraints errors that signify no data returned.
556 *
557 * This is considered OK as the caller may look for the next result set.
558 */
559 case ER_SP_FETCH_NO_DATA:
560 return RLM_SQL_OK;
561
562 }
563
564 return RLM_SQL_OK;
565}
566
568{
569 sql_rcode_t rcode;
570 int ret;
571
572retry_store_result:
573 conn->result = mysql_store_result(conn->sock);
574 if (!conn->result) {
575 rcode = sql_check_error(conn->sock, 0);
576 if (rcode != RLM_SQL_OK) return rcode;
577 ret = mysql_next_result(conn->sock);
578 if (ret == 0) {
579 /* there are more results */
580 goto retry_store_result;
581 } else if (ret > 0) return sql_check_error(NULL, ret);
582 /* ret == -1 signals no more results */
583 }
584 return RLM_SQL_OK;
585}
586
588{
589 rlm_sql_mysql_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
590
591 if (conn->result) return mysql_num_rows(conn->result);
592
593 return 0;
594}
595
596static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
597{
598 rlm_sql_mysql_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
599
600 unsigned int fields, i;
601 MYSQL_FIELD *field_info;
602 char const **names;
603
604 /*
605 * Use our internal function to abstract out the API call.
606 * Different versions of SQL use different functions,
607 * and some don't like NULL pointers.
608 */
609 fields = mysql_field_count(conn->sock);
610 if (fields == 0) return RLM_SQL_ERROR;
611
612 /*
613 * https://bugs.mysql.com/bug.php?id=32318
614 * Hints that we don't have to free field_info.
615 */
616 field_info = mysql_fetch_fields(conn->result);
617 if (!field_info) return RLM_SQL_ERROR;
618
619 MEM(names = talloc_array(query_ctx, char const *, fields));
620
621 for (i = 0; i < fields; i++) names[i] = field_info[i].name;
622 *out = names;
623
624 return RLM_SQL_OK;
625}
626
627static unlang_action_t sql_fetch_row(unlang_result_t *p_result, UNUSED request_t *request, void *uctx)
628{
629 fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
630 rlm_sql_mysql_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
631 MYSQL_ROW row;
632 int ret;
633 unsigned int num_fields, i;
634 unsigned long *field_lens;
635
636 /*
637 * Check pointer before de-referencing it.
638 * Lack of conn->result is either an error, or no result returned.
639 */
640 if (!conn->result) {
641 query_ctx->rcode = sql_check_error(conn->sock, 0);
642 if (query_ctx->rcode == RLM_SQL_OK) {
643 query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
645 }
647 }
648
649 TALLOC_FREE(query_ctx->row); /* Clear previous row set */
650
651retry_fetch_row:
652 row = mysql_fetch_row(conn->result);
653 if (!row) {
654 query_ctx->rcode = sql_check_error(conn->sock, 0);
655 if (query_ctx->rcode != RLM_SQL_OK) RETURN_UNLANG_FAIL;
656
657 mysql_free_result(conn->result);
658 conn->result = NULL;
659
660 ret = mysql_next_result(conn->sock);
661 if (ret == 0) {
662 /* there are more results */
663 if ((sql_store_result(conn, &query_ctx->inst->config) == 0) && (conn->result != NULL)) {
664 goto retry_fetch_row;
665 }
666 } else if (ret > 0) {
667 query_ctx->rcode = sql_check_error(NULL, ret);
668 if (query_ctx->rcode == RLM_SQL_OK) RETURN_UNLANG_OK;
670 }
671 /* If ret is -1 then there are no more rows */
672
673 query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
675 }
676
677 num_fields = mysql_field_count(conn->sock);
678 if (!num_fields) {
679 query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
681 }
682
683 field_lens = mysql_fetch_lengths(conn->result);
684
685 MEM(query_ctx->row = talloc_zero_array(query_ctx, char *, num_fields + 1));
686 for (i = 0; i < num_fields; i++) {
687 if (!row[i]) continue;
688 MEM(query_ctx->row[i] = talloc_bstrndup(query_ctx->row, row[i], field_lens[i]));
689 }
690
691 query_ctx->rcode = RLM_SQL_OK;
693}
694
696{
697 rlm_sql_mysql_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
698
699 if (conn->result) {
700 mysql_free_result(conn->result);
701 conn->result = NULL;
702 }
703 TALLOC_FREE(query_ctx->row);
704
705 return RLM_SQL_OK;
706}
707
708/** Retrieves any warnings associated with the last query
709 *
710 * MySQL stores a limited number of warnings associated with the last query
711 * executed. These can be very useful in diagnosing issues, or in some cases
712 * working around bugs in MySQL which causes it to return the wrong error.
713 *
714 * @note Caller should free any memory allocated in ctx (talloc_free_children()).
715 *
716 * @param ctx to allocate temporary error buffers in.
717 * @param out Array of sql_log_entrys to fill.
718 * @param outlen Length of out array.
719 * @param conn MySQL connection the query was run on.
720 * @return
721 * - Number of errors written to the #sql_log_entry_t array.
722 * - -1 on failure.
723 */
724static ssize_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
726{
727 MYSQL_RES *result;
728 MYSQL_ROW row;
729 unsigned int num_fields;
730 size_t i = 0;
731 char const *log_prefix = conn->conn->name;
732
733 if (outlen == 0) return 0;
734
735 /*
736 * Retrieve any warnings associated with the previous query
737 * that were left lingering on the server.
738 */
739 if (mysql_query(conn->sock, "SHOW WARNINGS") != 0) return -1;
740 result = mysql_store_result(conn->sock);
741 if (!result) return -1;
742
743 /*
744 * Fields should be [0] = Level, [1] = Code, [2] = Message
745 */
746 num_fields = mysql_field_count(conn->sock);
747 if (num_fields < 3) {
748 WARN("Failed retrieving warnings, expected 3 fields got %u", num_fields);
749 mysql_free_result(result);
750
751 return -1;
752 }
753
754 while ((row = mysql_fetch_row(result))) {
755 char *msg = NULL;
757
758 /*
759 * Translate the MySQL log level into our internal
760 * log levels, so they get colourised correctly.
761 */
762 if (strcasecmp(row[0], "warning") == 0) type = L_WARN;
763 else if (strcasecmp(row[0], "note") == 0) type = L_DBG;
764 else type = L_ERR;
765
766 msg = talloc_typed_asprintf(ctx, "%s: %s", row[1], row[2]);
767 out[i].type = type;
768 out[i].msg = msg;
769 if (++i == outlen) break;
770 }
771
772 mysql_free_result(result);
773
774 return i;
775}
776
777/** Retrieves any errors associated with the query context
778 *
779 * @note Caller should free any memory allocated in ctx (talloc_free_children()).
780 *
781 * @param ctx to allocate temporary error buffers in.
782 * @param out Array of sql_log_entrys to fill.
783 * @param outlen Length of out array.
784 * @param query_ctx Query context to retrieve error for.
785 * @return number of errors written to the #sql_log_entry_t array.
786 */
787static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
788 fr_sql_query_t *query_ctx)
789{
792 char const *error;
793 size_t i = 0;
794 char const *log_prefix;
795
796 if (!query_ctx->tconn) return 0;
797 conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
798 log_prefix = conn->conn->name;
799
800 fr_assert(outlen > 0);
801
802 error = mysql_error(conn->sock);
803
804 /*
805 * Grab the error now in case it gets cleared on the next operation.
806 */
807 if (error && (error[0] != '\0')) {
808 error = talloc_typed_asprintf(ctx, "ERROR %u (%s): %s", mysql_errno(conn->sock), error,
809 mysql_sqlstate(conn->sock));
810 } else {
811 error = NULL;
812 }
813
814 /*
815 * Don't attempt to get errors from the server, if the last error
816 * was that the server was unavailable.
817 */
818 if ((outlen > 1) && (sql_check_error(conn->sock, 0) != RLM_SQL_RECONNECT)) {
819 ssize_t ret;
820 unsigned int msgs;
821
822 switch (inst->warnings) {
824 /*
825 * Check to see if any warnings can be retrieved from the server.
826 */
827 msgs = mysql_warning_count(conn->sock);
828 if (msgs == 0) {
829 DEBUG3("No additional diagnostic info on server");
830 break;
831 }
832
835 ret = sql_warnings(ctx, out, outlen - 1, conn);
836 if (ret > 0) i += ret;
837 break;
838
840 break;
841
842 default:
843 fr_assert(0);
844 }
845 }
846
847 if (error) {
848 out[i].type = L_ERR;
849 out[i].msg = error;
850 i++;
851 }
852
853 return i;
854}
855
856/** Finish query
857 *
858 * As a single SQL statement may return multiple results
859 * sets, (for example stored procedures) it is necessary to check
860 * whether more results exist and process them in turn if so.
861 *
862 */
864{
866 int ret;
867 MYSQL_RES *result;
868
869 /*
870 * If the query is not in a state which would return results, then do nothing.
871 */
872 if (query_ctx->treq && !(query_ctx->treq->state &
874
875 /*
876 * If the connection doesn't exist there's nothing to do
877 */
878 if (!query_ctx->tconn || !query_ctx->tconn->conn || !query_ctx->tconn->conn->h) return RLM_SQL_ERROR;
879
880 conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
881
882 /*
883 * If the connection is not active, then all that we can do is free any stored results
884 */
885 if (query_ctx->tconn->conn->state != CONNECTION_STATE_CONNECTED) {
886 sql_free_result(query_ctx, config);
887 return RLM_SQL_OK;
888 }
889
890 /*
891 * If there's no result associated with the
892 * connection handle, assume the first result in the
893 * result set hasn't been retrieved.
894 *
895 * MySQL docs says there's no performance penalty for
896 * calling mysql_store_result for queries which don't
897 * return results.
898 */
899 if (conn->result == NULL) {
900 result = mysql_store_result(conn->sock);
901 if (result) mysql_free_result(result);
902 /*
903 * ...otherwise call sql_free_result to free an
904 * already stored result.
905 */
906 } else {
907 sql_free_result(query_ctx, config); /* sql_free_result sets conn->result to NULL */
908 }
909
910 /*
911 * Drain any other results associated with the handle
912 *
913 * mysql_next_result advances the result cursor so that
914 * the next call to mysql_store_result will retrieve
915 * the next result from the server.
916 *
917 * Unfortunately this really does appear to be the
918 * only way to return the handle to a consistent state.
919 */
920 while (((ret = mysql_next_result(conn->sock)) == 0) &&
921 (result = mysql_store_result(conn->sock))) {
922 mysql_free_result(result);
923 }
924 if (ret > 0) return sql_check_error(NULL, ret);
925
926 return RLM_SQL_OK;
927}
928
930{
931 rlm_sql_mysql_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_mysql_conn_t);
932
933 return mysql_affected_rows(conn->sock);
934}
935
936static int sql_escape_func(fr_value_box_t *vb, void *arg)
937{
938 rlm_sql_mysql_esc_ctx_t *esc_ctx = talloc_get_type_abort(arg, rlm_sql_mysql_esc_ctx_t);
939 char *out;
940 size_t inlen = vb->vb_length;
941 unsigned long real_len;
942
943 if (!esc_ctx->ready) {
944 fr_strerror_const("Connection flags not available for escaping");
945 return -1;
946 }
947
948 /* Prevent integer overflow on (inlen * 2 + 1) */
949 if (inlen > (SIZE_MAX - 1) / 2) {
950 fr_strerror_const("Input too large to escape");
951 return -1;
952 }
953
954 MEM(out = talloc_array(vb, char, inlen * 2 + 1));
955 real_len = mysql_real_escape_string(&esc_ctx->db, out, vb->vb_strvalue, inlen);
956
957 if ((size_t)real_len + 1 < inlen * 2 + 1) MEM(out = talloc_realloc(vb, out, char, real_len + 1));
959
960 return 0;
961}
962
964
965#undef LOG_PREFIX
966#define LOG_PREFIX "rlm_sql_mysql"
967
969
970#undef LOG_PREFIX
971#define LOG_PREFIX log_prefix
972
973CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
975 connection_t *conn, UNUSED void *uctx)
976{
977 rlm_sql_mysql_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_mysql_conn_t);
978 char const *log_prefix = conn->name;
979 request_t *request;
980 trunk_request_t *treq;
981 fr_sql_query_t *query_ctx;
982 char const *info;
983 int err;
984
985 if (trunk_connection_pop_request(&treq, tconn) != 0) return;
986 if (!treq) return;
987
988 query_ctx = talloc_get_type_abort(treq->preq, fr_sql_query_t);
989 request = query_ctx->request;
990
991 /*
992 * Each of the MariaDB async "start" calls returns a non-zero value
993 * if they are waiting on I/O.
994 * A return value of zero means that the operation completed.
995 */
996
997 switch (query_ctx->status) {
999 ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query_ctx->query_str);
1000 sql_conn->status = mysql_real_query_start(&err, sql_conn->sock, query_ctx->query_str, strlen(query_ctx->query_str));
1001 query_ctx->tconn = tconn;
1002
1003 if (sql_conn->status) {
1004 ROPTIONAL(RDEBUG3, DEBUG3, "Waiting for IO");
1005 query_ctx->status = SQL_QUERY_SUBMITTED;
1006 sql_conn->query_ctx = query_ctx;
1008 return;
1009 }
1010
1011 if (err) {
1012 /*
1013 * Need to check what kind of error this is - it may
1014 * be a unique key conflict, we run the next query.
1015 */
1016 info = mysql_info(sql_conn->sock);
1017 query_ctx->rcode = sql_check_error(sql_conn->sock, 0);
1018 if (info) ERROR("%s", info);
1019 switch (query_ctx->rcode) {
1020 case RLM_SQL_OK:
1021 case RLM_SQL_ALT_QUERY:
1022 break;
1023
1024 default:
1025 query_ctx->status = SQL_QUERY_FAILED;
1027 if (request) unlang_interpret_mark_runnable(request);
1028 return;
1029 }
1030 } else {
1031 query_ctx->rcode = RLM_SQL_OK;
1032 }
1033 query_ctx->status = SQL_QUERY_RETURNED;
1034
1035 break;
1036
1037 case SQL_QUERY_RETURNED:
1038 ROPTIONAL(RDEBUG2, DEBUG2, "Fetching results");
1039 fr_assert(query_ctx->tconn == tconn);
1040 sql_conn->status = mysql_store_result_start(&sql_conn->result, sql_conn->sock);
1041
1042 if (sql_conn->status) {
1043 ROPTIONAL(RDEBUG3, DEBUG3, "Waiting for IO");
1045 sql_conn->query_ctx = query_ctx;
1047 return;
1048 }
1049 query_ctx->status = SQL_QUERY_RESULTS_FETCHED;
1050 query_ctx->rcode = RLM_SQL_OK;
1051
1052 break;
1053
1054 default:
1055 /*
1056 * The request outstanding on this connection returned
1057 * immediately, so we are not actually waiting for I/O.
1058 */
1059 return;
1060 }
1061
1062 /*
1063 * The current request is not waiting for I/O so the request can run
1064 */
1065 ROPTIONAL(RDEBUG3, DEBUG3, "Got immediate response");
1067 if (request) unlang_interpret_mark_runnable(request);
1068}
1069
1070CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
1072 connection_t *conn, UNUSED void *uctx)
1073{
1074 rlm_sql_mysql_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_mysql_conn_t);
1075 char const *log_prefix = conn->name;
1076 fr_sql_query_t *query_ctx;
1077 char const *info;
1078 int err = 0;
1079 request_t *request;
1080
1081 /*
1082 * Lookup the outstanding SQL query for this connection.
1083 * There will only ever be one per tconn.
1084 */
1085 query_ctx = sql_conn->query_ctx;
1086
1087 /*
1088 * No outstanding query on this connection.
1089 * Should not happen, but added for safety.
1090 */
1091 if (unlikely(!query_ctx)) return;
1092
1093 switch (query_ctx->status) {
1095 sql_conn->status = mysql_real_query_cont(&err, sql_conn->sock, sql_conn->status);
1096 break;
1097
1099 sql_conn->status = mysql_store_result_cont(&sql_conn->result, sql_conn->sock, sql_conn->status);
1100 break;
1101
1102 default:
1103 /*
1104 * The request outstanding on this connection returned
1105 * immediately, so we are not actually waiting for I/O.
1106 */
1107 return;
1108 }
1109
1110 /*
1111 * Are we still waiting for any further I/O?
1112 */
1113 if (sql_conn->status != 0) return;
1114
1115 sql_conn->query_ctx = NULL;
1116
1117 switch (query_ctx->status) {
1119 query_ctx->status = SQL_QUERY_RETURNED;
1120 break;
1121
1123 query_ctx->status = SQL_QUERY_RESULTS_FETCHED;
1124 break;
1125
1126 default:
1127 fr_assert(0);
1128 }
1129
1130 request = query_ctx->request;
1131 if (request) unlang_interpret_mark_runnable(request);
1132
1133 if (err) {
1134 info = mysql_info(sql_conn->sock);
1135 query_ctx->rcode = sql_check_error(sql_conn->sock, 0);
1136 if (info) ROPTIONAL(RERROR, ERROR, "%s", info);
1137 return;
1138 }
1139
1140 query_ctx->rcode = RLM_SQL_OK;
1141}
1142
1143CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
1144static void sql_request_cancel(connection_t *conn, void *preq, trunk_cancel_reason_t reason,
1145 UNUSED void *uctx)
1146{
1147 fr_sql_query_t *query_ctx = talloc_get_type_abort(preq, fr_sql_query_t);
1148 rlm_sql_mysql_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_mysql_conn_t);
1149
1150 if (reason != TRUNK_CANCEL_REASON_SIGNAL) return;
1151
1152 /*
1153 * Prevent any further queries being enqueued on the trunk connection
1154 * since the cancellation mux will close the connection.
1155 */
1156 if (query_ctx->tconn) trunk_connection_signal_inactive(query_ctx->tconn);
1157
1158 if (!query_ctx->treq) return;
1159 if (sql_conn->query_ctx == query_ctx) sql_conn->query_ctx = NULL;
1160}
1161
1162CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
1164 connection_t *conn, UNUSED void *uctx)
1165{
1166 trunk_request_t *treq;
1167
1168 /*
1169 * The MariaDB non-blocking API doesn't have any cancellation functions -
1170 * rather you are expected to close the connection.
1171 */
1172 if ((trunk_connection_pop_cancellation(&treq, tconn)) == 0) {
1175 }
1176}
1177
1180
1182{
1183 fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
1184
1185 if (query_ctx->rcode != RLM_SQL_OK) RETURN_UNLANG_FAIL;
1186
1187 if (query_ctx->status == SQL_QUERY_RETURNED) {
1188 trunk_request_requeue(query_ctx->treq);
1189
1191 query_ctx->rcode = RLM_SQL_ERROR;
1193 }
1194
1195 return UNLANG_ACTION_YIELD;
1196 }
1197
1199}
1200
1201/** Capture our copy of the connection details for escaping purposes.
1202 *
1203 * After an "escape" connection has been started and again after it is
1204 * established.
1205 * Then close the connection. mysql_real_escape_string only looks at one
1206 * flag inside the MYSQL structure, so no need to keep an open connection.
1207 */
1209 UNUSED connection_state_t state, void *uctx)
1210{
1211 rlm_sql_mysql_esc_ctx_t *esc_ctx = talloc_get_type_abort(uctx, rlm_sql_mysql_esc_ctx_t);
1212 rlm_sql_mysql_conn_t *c = talloc_get_type_abort(conn->h, rlm_sql_mysql_conn_t);
1213
1214 esc_ctx->db = c->db;
1215 esc_ctx->ready = true;
1216 if (conn->state == CONNECTION_STATE_CONNECTED) connection_signal_halt(conn);
1217}
1218
1219
1220/** Allocate the argument used for the SQL escape function
1221 *
1222 * In this case, a dedicated connection is made to fetch server flags used by
1223 * the escape function, though no packets ever flow after the connection is made.
1224 * Once the server flags have been retrieved, the connection is closed, by a
1225 * watch function.
1226 */
1227static void *sql_escape_arg_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, void *uctx)
1228{
1229 rlm_sql_t const *inst = talloc_get_type_abort(uctx, rlm_sql_t);
1230 rlm_sql_mysql_esc_ctx_t *esc_ctx;
1231 char const *log_prefix = inst->name;
1232
1233 MEM(esc_ctx = talloc_zero(ctx, rlm_sql_mysql_esc_ctx_t));
1234 esc_ctx->conn = connection_alloc(ctx, el,
1236 .init = _sql_connection_init,
1237 .close = _sql_connection_close,
1238 },
1239 inst->config.trunk_conf.conn_conf,
1240 inst->name, inst);
1241
1242 if (!esc_ctx->conn) {
1243 PERROR("Failed allocating state handler for SQL escape connection");
1244 talloc_free(esc_ctx);
1245 return NULL;
1246 }
1247
1248 /*
1249 * The watch is run both after entering CONNECTING and CONNECTED
1250 * The first will capture the configured character set. The second will
1251 * capture status returned by the server.
1252 */
1254 _sql_escape_post_conn, true, esc_ctx);
1256 _sql_escape_post_conn, true, esc_ctx);
1257
1258 connection_signal_init(esc_ctx->conn);
1259 return esc_ctx;
1260}
1261
1262static void sql_escape_arg_free(void *uctx)
1263{
1264 rlm_sql_mysql_esc_ctx_t *esc_ctx = talloc_get_type_abort(uctx, rlm_sql_mysql_esc_ctx_t);
1265 connection_signal_halt(esc_ctx->conn);
1266}
1267
1268/* Exported to rlm_sql */
1271 .common = {
1272 .name = "sql_mysql",
1273 .magic = MODULE_MAGIC_INIT,
1274 .inst_size = sizeof(rlm_sql_mysql_t),
1275 .onload = mod_load,
1276 .unload = mod_unload,
1278 .instantiate = mod_instantiate
1279 },
1281 .sql_query_resume = sql_query_resume,
1282 .sql_select_query_resume = sql_select_query_resume,
1283 .sql_num_rows = sql_num_rows,
1284 .sql_affected_rows = sql_affected_rows,
1285 .sql_fields = sql_fields,
1286 .sql_fetch_row = sql_fetch_row,
1287 .sql_free_result = sql_free_result,
1288 .sql_error = sql_error,
1289 .sql_finish_query = sql_finish_query,
1290 .sql_finish_select_query = sql_finish_query,
1291 .sql_escape_func = sql_escape_func,
1292 .sql_escape_arg_alloc = sql_escape_arg_alloc,
1293 .sql_escape_arg_free = sql_escape_arg_free,
1294 .trunk_io_funcs = {
1295 .connection_alloc = sql_trunk_connection_alloc,
1296 .connection_notify = sql_trunk_connection_notify,
1297 .request_mux = sql_trunk_request_mux,
1298 .request_demux = sql_trunk_request_demux,
1299 .request_cancel_mux = sql_request_cancel_mux,
1300 .request_cancel = sql_request_cancel,
1301 .request_fail = sql_request_fail,
1302 }
1303};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:41
log_entry msg
Definition acutest.h:794
#define RCSID(id)
Definition build.h:560
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:228
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:391
#define DIAG_ON(_x)
Definition build.h:535
#define CC_NO_UBSAN(_sanitize)
Definition build.h:503
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
#define NUM_ELEMENTS(_t)
Definition build.h:406
#define DIAG_OFF(_x)
Definition build.h:534
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:673
#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
Definition cf_parse.h:280
conf_parser_flags_t flags
Flags which control parsing behaviour.
Definition cf_parse.h:616
#define FR_CONF_POINTER(_name, _type, _flags, _res_p)
conf_parser_t which parses a single CONF_PAIR producing a single global result
Definition cf_parse.h:334
#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
Definition cf_parse.h:268
@ CONF_FLAG_FILE_READABLE
File matching value must exist, and must be readable.
Definition cf_parse.h:439
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:427
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:610
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
#define DEBUG(fmt,...)
Definition dhcpclient.c:38
static fr_slen_t err
Definition dict.h:904
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
#define fr_event_fd_insert(...)
Definition event.h:247
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition event.h:83
#define unlang_function_repeat_set(_request, _repeat)
Set a new repeat function for an existing function frame.
Definition function.h:108
talloc_free(hp)
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:2008
#define PERROR(_fmt,...)
Definition log.h:233
#define DEBUG3(_fmt,...)
Definition log.h:271
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:545
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RERROR(fmt,...)
Definition log.h:315
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.
Definition event.c:1203
Stores all information relating to an event list.
Definition event.c:377
fr_log_type_t
Definition log.h:51
@ L_WARN
Warning.
Definition log.h:54
@ L_ERR
Error message.
Definition log.h:53
@ L_DBG
Only displayed when debugging is enabled.
Definition log.h:56
long int ssize_t
int strcasecmp(char *s1, char *s2)
Definition missing.c:65
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
static const conf_parser_t config[]
Definition base.c:162
#define fr_assert(_expr)
Definition rad_assert.h:37
#define RDEBUG2(fmt,...)
#define DEBUG2(fmt,...)
#define WARN(fmt,...)
#define INFO(fmt,...)
Definition radict.c:63
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_OK
Definition rcode.h:64
static char const * name
Prototypes and functions for the SQL module.
fr_sql_query_status_t status
Status of the query.
Definition rlm_sql.h:144
trunk_connection_t * tconn
Trunk connection this query is being run on.
Definition rlm_sql.h:140
rlm_sql_t const * inst
Module instance for this query.
Definition rlm_sql.h:137
char const * query_str
Query string to run.
Definition rlm_sql.h:142
request_t * request
Request this query relates to.
Definition rlm_sql.h:138
sql_rcode_t
Action to take at end of an SQL query.
Definition rlm_sql.h:44
@ RLM_SQL_QUERY_INVALID
Query syntax error.
Definition rlm_sql.h:45
@ RLM_SQL_ALT_QUERY
Key constraint violation, use an alternative query.
Definition rlm_sql.h:49
@ RLM_SQL_RECONNECT
Stale connection, should reconnect.
Definition rlm_sql.h:48
@ RLM_SQL_ERROR
General connection/server error.
Definition rlm_sql.h:46
@ RLM_SQL_OK
Success.
Definition rlm_sql.h:47
@ RLM_SQL_NO_MORE_ROWS
No more rows available.
Definition rlm_sql.h:50
#define RLM_SQL_RCODE_FLAGS_ALT_QUERY
Can distinguish between other errors and those resulting from a unique key violation.
Definition rlm_sql.h:171
rlm_sql_row_t row
Row data from the last query.
Definition rlm_sql.h:146
sql_rcode_t rcode
Result code.
Definition rlm_sql.h:145
trunk_request_t * treq
Trunk request for this query.
Definition rlm_sql.h:141
char const * connect_query
Query executed after establishing new connection.
Definition rlm_sql.h:98
@ SQL_QUERY_RETURNED
Query has executed.
Definition rlm_sql.h:130
@ SQL_QUERY_FETCHING_RESULTS
Fetching results from server.
Definition rlm_sql.h:131
@ SQL_QUERY_FAILED
Failed to submit.
Definition rlm_sql.h:127
@ SQL_QUERY_SUBMITTED
Submitted for execution.
Definition rlm_sql.h:129
@ SQL_QUERY_PREPARED
Ready to submit.
Definition rlm_sql.h:128
@ SQL_QUERY_RESULTS_FETCHED
Results fetched from the server.
Definition rlm_sql.h:132
Definition rlm_sql.h:61
static void sql_trunk_connection_notify(UNUSED trunk_connection_t *tconn, connection_t *conn, fr_event_list_t *el, trunk_connection_event_t notify_on, UNUSED void *uctx)
static void sql_request_fail(request_t *request, void *preq, UNUSED void *rctx, UNUSED trunk_request_state_t state, UNUSED void *uctx)
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.
fr_sql_query_t * query_ctx
Current query running on this connection.
int client_fd
Socket the client library last reported, for detecting fd changes.
static sql_rcode_t sql_free_result(fr_sql_query_t *, rlm_sql_config_t const *)
SQL_QUERY_FAIL static SQL_QUERY_RESUME unlang_action_t sql_select_query_resume(unlang_result_t *p_result, UNUSED request_t *request, void *uctx)
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.
static unlang_action_t sql_fetch_row(unlang_result_t *p_result, UNUSED request_t *request, void *uctx)
int status
returned by the most recent non-blocking function call.
static ssize_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, rlm_sql_mysql_conn_t *conn)
Retrieves any warnings associated with the last query.
char const * tls_cipher
Colon separated list of TLS ciphers for TLS <= 1.2.
MYSQL db
Our copy of the connection details - holds the flags needed by mysql_real_escape_string()
char const * tls_ca_path
Directory containing CAs that may be used to validate the servers certificate.
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.
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 void sql_trunk_request_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
rlm_sql_mysql_warnings
@ SERVER_WARNINGS_AUTO
@ SERVER_WARNINGS_NO
@ SERVER_WARNINGS_YES
static void sql_escape_arg_free(void *uctx)
int fd
Our fd for this connection's I/O events.
static conf_parser_t tls_config[]
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
connection_t * conn
Connection used to fetch server status.
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)
static void _sql_connect_io_notify(fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
Callback for I/O events in response to mysql_real_connect_start()
MYSQL db
Structure representing connection details.
static void sql_mysql_fd_release(rlm_sql_mysql_conn_t *c, fr_event_list_t *el)
Stop tracking our fd for a connection.
static void mod_unload(void)
static void _sql_escape_post_conn(connection_t *conn, UNUSED connection_state_t prev, UNUSED connection_state_t state, void *uctx)
Capture our copy of the connection details for escaping purposes.
static void sql_request_cancel_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, fr_sql_query_t *query_ctx)
Retrieves any errors associated with the query context.
char const * character_set
Character set to use on connections.
char const * warnings_str
Whether we always query the server for additional warnings.
bool ready
Has the db structure been populated.
static const conf_parser_t driver_config[]
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)
connection_t * conn
Generic connection structure for this connection.
static int sql_escape_func(fr_value_box_t *vb, void *arg)
bool tls_check_cert_cn
Verify that the CN in the server cert matches the host we passed to mysql_real_connect().
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 int mod_instantiate(module_inst_ctx_t const *mctx)
static int sql_mysql_fd_track(rlm_sql_mysql_conn_t *c, fr_event_list_t *el)
Track the socket actually being used by the client library.
rlm_sql_mysql_warnings warnings
mysql_warning_count() doesn't appear to work with NDB cluster
Macros to reduce boilerplate in trunk SQL drivers.
#define SQL_QUERY_RESUME
#define SQL_TRUNK_CONNECTION_ALLOC
Allocate an SQL trunk connection.
#define SQL_QUERY_FAIL
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_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.
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.
Definition connection.c:542
void connection_signal_connected(connection_t *conn)
Asynchronously signal that the connection is open.
connection_state_t
Definition connection.h:47
@ CONNECTION_STATE_FAILED
Connection has failed.
Definition connection.h:56
@ CONNECTION_STATE_CONNECTED
File descriptor is open (ready for writing).
Definition connection.h:54
@ CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
Definition connection.h:52
@ CONNECTION_EXPIRED
Connection is being reconnected because it's at the end of its life.
Definition connection.h:90
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition connection.h:89
Holds a complete set of functions for a connection.
Definition connection.h:199
char const * name
Instance name e.g. user_database.
Definition module.h:359
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:295
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
module_t common
Common fields for all loadable modules.
Definition rlm_sql.h:201
module_instance_t * driver_submodule
Driver's submodule.
Definition rlm_sql.h:234
rlm_sql_config_t config
Definition rlm_sql.h:228
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
Definition table.h:685
An element in a lexicographically sorted array of name to num mappings.
Definition table.h:49
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition talloc.c:546
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition talloc.c:618
#define talloc_get_type_abort_const
Definition talloc.h:117
static const char * names[8]
Definition time.c:614
int trunk_connection_pop_cancellation(trunk_request_t **treq_out, trunk_connection_t *tconn)
Pop a cancellation request off a connection's cancellation queue.
Definition trunk.c:3931
void trunk_request_signal_fail(trunk_request_t *treq)
Signal that a trunk request failed.
Definition trunk.c:2196
trunk_enqueue_t trunk_request_requeue(trunk_request_t *treq)
Re-enqueue a request on the same connection.
Definition trunk.c:2746
void trunk_request_signal_cancel_complete(trunk_request_t *treq)
Signal that a remote server acked our cancellation.
Definition trunk.c:2348
int trunk_connection_pop_request(trunk_request_t **treq_out, trunk_connection_t *tconn)
Pop a request off a connection's pending queue.
Definition trunk.c:3979
void trunk_connection_signal_inactive(trunk_connection_t *tconn)
Signal a trunk connection cannot accept more requests.
Definition trunk.c:4033
void trunk_request_signal_sent(trunk_request_t *treq)
Signal that the request was written to a connection successfully.
Definition trunk.c:2114
void trunk_request_signal_reapable(trunk_request_t *treq)
Signal that the request was written to a connection successfully, but no response is expected.
Definition trunk.c:2136
Associates request queues with a connection.
Definition trunk.c:137
Wraps a normal request.
Definition trunk.c:99
#define TRUNK_NOTIFY_FUNC(_name, _type)
Helper macro for building generic trunk notify callback.
Definition trunk.h:970
trunk_cancel_reason_t
Reasons for a request being cancelled.
Definition trunk.h:55
@ TRUNK_CANCEL_REASON_SIGNAL
Request cancelled due to a signal.
Definition trunk.h:57
@ TRUNK_REQUEST_STATE_REAPABLE
Request has been written, needs to persist, but we are not currently waiting for any response.
Definition trunk.h:183
@ TRUNK_REQUEST_STATE_COMPLETE
The request is complete.
Definition trunk.h:192
@ TRUNK_REQUEST_STATE_SENT
Was written to a socket. Waiting for a response.
Definition trunk.h:182
static fr_event_list_t * el
#define fr_strerror_const(_msg)
Definition strerror.h:223
void fr_value_box_strdup_shallow_replace(fr_value_box_t *vb, char const *src, ssize_t len)
Free the existing buffer (if talloced) associated with the valuebox, and replace it with a new one.
Definition value.c:4774
static fr_sbuff_err_t char ** out
Definition value.h:1062