The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_sql_unixodbc.c
Go to the documentation of this file.
1/*
2 * sql_unixodbc.c unixODBC rlm_sql driver
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 *
18 * @copyright 2000,2006 The FreeRADIUS server project
19 * @copyright 2000 Dmitri Ageev (d_ageev@ortcc.ru)
20 */
21RCSID("$Id: 74479171b46159d969b5f254c9586de4ac7ec0a2 $")
23
24#define LOG_PREFIX "sql - unixodbc"
25
26#include <freeradius-devel/server/base.h>
27#include <freeradius-devel/util/debug.h>
28
29#include <sqltypes.h>
30#include "rlm_sql.h"
31#include "rlm_sql_trunk.h"
32
33typedef struct {
34 SQLHENV env; /* Environment handle */
35 SQLHDBC dbc; /* Database connection handle */
36 SQLHSTMT stmt; /* Statement handle */
37 SQLSMALLINT colcount; /* Number of columns in last result */
38 rlm_sql_row_t row; /* Results row */
39 SQLLEN *ind; /* Data length / NULL indicators */
40 connection_t *conn; /* Generic connection structure for this connection */
41 rlm_sql_config_t const *config; /* SQL instance configuration */
42 SQLUSMALLINT async_mode; /* What Async mode does this driver support */
43 fr_sql_query_t *query_ctx; /* Current query running on the connection */
44 fr_timer_t *read_ev; /* Timer event for polling reading this connection */
45 fr_timer_t *write_ev; /* Timer event for polling writing this connection */
46 uint select_interval; /* How frequently this connection gets polled for select queries */
47 uint query_interval; /* How frequently this connection gets polled for other queries */
48 uint poll_count; /* How many polls have been done for the current query */
50
52#include <sql.h>
53#include <sqlext.h>
54
55/** Checks the error code to determine if the connection needs to be re-esttablished
56 *
57 * @param ret Return code from a failed unixodbc call.
58 * @param handle_type Type of ODBC handle
59 * @param handle ODBC handle
60 * @return
61 * - #RLM_SQL_OK on success.
62 * - #RLM_SQL_ALT_QUERY if alternate queries should be tried.
63 * - #RLM_SQL_RECONNECT if reconnect is needed.
64 * - #RLM_SQL_ERROR on error.
65 */
66static sql_rcode_t sql_check_error(SQLRETURN ret, SQLSMALLINT handle_type, SQLHANDLE handle)
67{
68 SQLCHAR state[6];
69 SQLCHAR error[256];
70 SQLINTEGER errornum = 0;
71 SQLSMALLINT length = 255;
72 int res = RLM_SQL_ERROR;
73
74 if (SQL_SUCCEEDED(ret)) return 0; /* on success, just return 0 */
75
76 error[0] = state[0] = '\0';
77
78 SQLGetDiagRec(handle_type, handle, 1, state, &errornum, error, sizeof(error), &length);
79
80 if (state[0] == '0') {
81 switch (state[1]) {
82 /* SQLSTATE 01 class contains info and warning messages */
83 case '1':
84 INFO("%s %s", state, error);
86 case '0': /* SQLSTATE 00 class means success */
87 res = RLM_SQL_OK;
88 break;
89
90 /* SQLSTATE 08 class describes various connection errors */
91 case '8':
92 ERROR("SQL down %s %s", state, error);
94 break;
95
96 /* any other SQLSTATE means error */
97 default:
98 ERROR("%s %s", state, error);
99 break;
100 }
101 } else {
102 /* SQLSTATE 23000 is "Integrity constraint violation" - such as duplicate key */
103 if (strcmp((char const *)state, "23000") == 0) {
104 res = RLM_SQL_ALT_QUERY;
105 } else {
106 ERROR("%s %s", state, error);
107 }
108 }
109
110 return res;
111}
112
113static void _sql_connection_close(UNUSED fr_event_list_t *el, void *h, UNUSED void *uctx)
114{
115 rlm_sql_unixodbc_conn_t *c = talloc_get_type_abort(h, rlm_sql_unixodbc_conn_t);
116
117 if (c->stmt) SQLFreeHandle(SQL_HANDLE_STMT, c->stmt);
118
119 if (c->dbc) {
120 SQLDisconnect(c->dbc);
121 SQLFreeHandle(SQL_HANDLE_DBC, c->dbc);
122 }
123
124 if (c->env) SQLFreeHandle(SQL_HANDLE_ENV, c->env);
125 talloc_free(h);
126}
127
129{
130 char buff[256], verbuf[10];
131 SQLRETURN ret;
132 SQLULEN timeout;
133
134 SQLGetInfo(c->dbc, SQL_DRIVER_NAME, buff, sizeof(buff), NULL);
135 SQLGetInfo(c->dbc, SQL_DRIVER_ODBC_VER, verbuf, sizeof(verbuf), NULL);
136 SQLGetInfo(c->dbc, SQL_ASYNC_MODE, &c->async_mode, 0, NULL);
137 switch(c->async_mode) {
138 case SQL_AM_NONE:
139 DEBUG2("Using driver %s, ODBC version %s. Driver does not support async operations", buff, verbuf);
140 break;
141 case SQL_AM_CONNECTION:
142 DEBUG2("Using driver %s, ODBC version %s. Async operation is set per connection", buff, verbuf);
143 ret = SQLSetConnectAttr(c->dbc, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_UINTEGER);
144 sql_check_error(ret, SQL_HANDLE_DBC, c->dbc);
145 break;
146 case SQL_AM_STATEMENT:
147 DEBUG2("Using driver %s, ODBC version %s. Async operation is set per statement", buff, verbuf);
148 break;
149 }
150
151 /* Allocate the stmt handle */
152 ret = SQLAllocHandle(SQL_HANDLE_STMT, c->dbc, &c->stmt);
153 if (sql_check_error(ret, SQL_HANDLE_DBC, c->dbc)) {
154 ERROR("Can't allocate the stmt");
155 _sql_connection_close(NULL, c, NULL);
157 }
158 if (c->async_mode == SQL_AM_STATEMENT) {
159 ret = SQLSetStmtAttr(c->stmt, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, 0);
160 sql_check_error(ret, SQL_HANDLE_STMT, c->stmt);
161 }
162
164 SQLSetStmtAttr(c->stmt, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)timeout, SQL_IS_UINTEGER);
165
167}
168
170{
171 rlm_sql_unixodbc_conn_t *c = talloc_get_type_abort(uctx, rlm_sql_unixodbc_conn_t);
172 SQLRETURN ret;
173
174 ret = SQLConnect(c->dbc,
175 UNCONST(SQLCHAR *, c->config->sql_server), strlen(c->config->sql_server),
176 UNCONST(SQLCHAR *, c->config->sql_login), strlen(c->config->sql_login),
177 UNCONST(SQLCHAR *, c->config->sql_password), strlen(c->config->sql_password));
178
179 if (ret == SQL_STILL_EXECUTING) {
181 false, sql_trunk_connection_init_poll, c) < 0) {
182 ERROR("Unable to insert polling event");
184 }
185 return;
186 }
187
188 if (sql_check_error(ret, SQL_HANDLE_DBC, c->dbc)) {
189 ERROR("Connection failed");
191 }
192
194}
195
196CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
197static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
198{
200 rlm_sql_config_t const *config = &sql->config;
202 SQLRETURN ret;
204
205 MEM(c = talloc_zero(conn, rlm_sql_unixodbc_conn_t));
207 .conn = conn,
208 .config = config,
209 .select_interval = 1000, /* Default starting poll interval - 1ms*/
210 .query_interval = 1000,
211 };
212
213 /* Allocate environment handle and register version */
214 ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &c->env);
215 if (ret == SQL_ERROR) {
216 ERROR("Can't allocate environment handle");
217 error:
218 _sql_connection_close(NULL, c, NULL);
220 }
221
222 ret = SQLSetEnvAttr(c->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3_80, 0);
223 if (sql_check_error(ret, SQL_HANDLE_ENV, c->env)) {
224 ERROR("Can't register ODBC version");
225 goto error;
226 }
227
228 /* Allocate connection handle */
229 ret = SQLAllocHandle(SQL_HANDLE_DBC, c->env, &c->dbc);
230 if (sql_check_error(ret, SQL_HANDLE_ENV, c->env)) {
231 ERROR("Can't allocate connection handle");
232 goto error;
233 }
234
235 /*
236 * Set the login / connection timeout
237 *
238 * Note SQLSetConnectionAttr and SQLSetStmtAttr have an insane parameter passing
239 * model. The 3rd parameter can be an integer, or a pointer to a string
240 * so integers get cast to pointers to match the function signature.
241 */
242 SQLSetConnectAttr(c->dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)timeout, SQL_IS_UINTEGER);
243 SQLSetConnectAttr(c->dbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER)timeout, SQL_IS_UINTEGER);
244
245 /* Set the connection handle to Async */
246 SQLSetConnectAttr(c->dbc, SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON, SQL_IS_UINTEGER);
247
248 /* Connect to the datasource */
249 ret = SQLConnect(c->dbc,
250 UNCONST(SQLCHAR *, config->sql_server), strlen(config->sql_server),
251 UNCONST(SQLCHAR *, config->sql_login), strlen(config->sql_login),
252 UNCONST(SQLCHAR *, config->sql_password), strlen(config->sql_password));
253
254 if (ret == SQL_STILL_EXECUTING) {
255 if (fr_timer_in(c, conn->el->tl, &c->read_ev, fr_time_delta_from_usec(c->query_interval),
256 false, sql_trunk_connection_init_poll, c) < 0) {
257 ERROR("Unable to insert polling event");
258 goto error;
259 }
260 *h = c;
262 }
263
264 if (sql_check_error(ret, SQL_HANDLE_DBC, c->dbc)) {
265 ERROR("Connection failed");
266 goto error;
267 }
268
269 *h = c;
271}
272
274
275CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
277{
278 rlm_sql_unixodbc_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_unixodbc_conn_t);
279 request_t *request;
280 trunk_request_t *treq;
281 fr_sql_query_t *query_ctx;
282 SQLRETURN ret;
283
284 if (trunk_connection_pop_request(&treq, tconn) != 0) return;
285 if (!treq) return;
286
287 query_ctx = talloc_get_type_abort(treq->preq, fr_sql_query_t);
288 request = query_ctx->request;
289
290 switch(query_ctx->status) {
292 ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query_ctx->query_str);
293 ret = SQLExecDirect(sql_conn->stmt, UNCONST(SQLCHAR *, query_ctx->query_str), strlen(query_ctx->query_str));
294 query_ctx->tconn = tconn;
295
296 if (ret == SQL_STILL_EXECUTING) {
297 ROPTIONAL(RDEBUG3, DEBUG3, "Awaiting response");
298 query_ctx->status = SQL_QUERY_SUBMITTED;
299 sql_conn->query_ctx = query_ctx;
300 sql_conn->poll_count = 0;
302 return;
303 }
304
305 query_ctx->rcode = sql_check_error(ret, SQL_HANDLE_STMT, sql_conn->stmt);
306 switch(query_ctx->rcode) {
307 case RLM_SQL_OK:
309 break;
310
311 default:
312 query_ctx->status = SQL_QUERY_FAILED;
315 return;
316 }
317 query_ctx->status = SQL_QUERY_RETURNED;
318 break;
319
320 default:
321 return;
322 }
323
324 ROPTIONAL(RDEBUG3, DEBUG3, "Got immediate response");
326 if (request) unlang_interpret_mark_runnable(request);
327}
328
329CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
330static void sql_request_cancel(connection_t *conn, void *preq, trunk_cancel_reason_t reason,
331 UNUSED void *uctx)
332{
333 fr_sql_query_t *query_ctx = talloc_get_type_abort(preq, fr_sql_query_t);
334 rlm_sql_unixodbc_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_unixodbc_conn_t);
335
336 if (!query_ctx->treq) return;
337 if (reason != TRUNK_CANCEL_REASON_SIGNAL) return;
338 if (sql_conn->query_ctx == query_ctx) sql_conn->query_ctx = NULL;
339}
340
341CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
343 connection_t *conn, UNUSED void *uctx)
344{
345 trunk_request_t *treq;
346 rlm_sql_unixodbc_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_unixodbc_conn_t);
347 SQLRETURN ret;
348 fr_sql_query_t *query_ctx;
349
350 if ((trunk_connection_pop_cancellation(&treq, tconn)) != 0) return;
351 if (!treq) return;
352
353 query_ctx = talloc_get_type_abort(treq->preq, fr_sql_query_t);
354 ret = SQLCancel(sql_conn->stmt);
355 query_ctx->status = SQL_QUERY_CANCELLED;
356 if (ret == SQL_STILL_EXECUTING) {
358 return;
359 }
361}
362
364{
365 rlm_sql_unixodbc_conn_t *c = talloc_get_type_abort(uctx, rlm_sql_unixodbc_conn_t);
366 fr_sql_query_t *query_ctx = c->query_ctx;
367 SQLRETURN ret;
368 trunk_request_t *treq = query_ctx->treq;
369 request_t *request = query_ctx->request;
370
371 switch (query_ctx->status) {
373 ret = SQLExecDirect(c->stmt, UNCONST(SQLCHAR *, query_ctx->query_str), strlen(query_ctx->query_str));
374 c->poll_count++;
375 /* Back off the poll interval, up to half the query timeout */
376 if (c->poll_count > 2) {
377 if (query_ctx->type == SQL_QUERY_SELECT) {
379 } else {
381 }
382 }
383 if (ret == SQL_STILL_EXECUTING) {
384 ROPTIONAL(RDEBUG3, DEBUG3, "Still awaiting response");
385 if (fr_timer_in(c, tl, &c->read_ev,
387 false, sql_trunk_connection_read_poll, c) < 0) {
388 ERROR("Unable to insert polling event");
389 }
390 return;
391 }
392
393 query_ctx->rcode = sql_check_error(ret, SQL_HANDLE_STMT, c->stmt);
394 switch(query_ctx->rcode) {
395 case RLM_SQL_OK:
397 /* If we only polled once, reduce the interval*/
398 if (c->poll_count == 1) {
399 if (query_ctx->type == SQL_QUERY_SELECT) {
400 c->select_interval /= 2;
401 } else {
402 c->query_interval /= 2;
403 }
404 }
405 break;
406
407 default:
408 query_ctx->status = SQL_QUERY_FAILED;
411 return;
412 }
413 break;
414
416 ret = SQLCancel(c->stmt);
417 if (ret == SQL_STILL_EXECUTING) {
418 ROPTIONAL(RDEBUG3, DEBUG3, "Still awaiting response");
420 false, sql_trunk_connection_read_poll, c) < 0) {
421 ERROR("Unable to insert polling event");
422 }
423 return;
424 }
426 return;
427
428 default:
429 return;
430 }
431
432 if (request) unlang_interpret_mark_runnable(request);
433}
434
436{
437 trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
438
440}
441
442/*
443 * UnixODBC doesn't support event driven async, so in this case
444 * we have to resort to polling.
445 *
446 * This "notify" callback sets up the appropriate polling events.
447 */
448CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
450 trunk_connection_event_t notify_on, UNUSED void *uctx)
451{
452 rlm_sql_unixodbc_conn_t *c = talloc_get_type_abort(conn->h, rlm_sql_unixodbc_conn_t);
453 fr_sql_query_t *query_ctx = c->query_ctx;
454 uint poll_interval = (query_ctx && query_ctx->type != SQL_QUERY_SELECT) ? c->query_interval : c->select_interval;
455 switch (notify_on) {
459 return;
460
463 if (c->query_ctx) {
464 if (fr_timer_in(c, el->tl, &c->read_ev, fr_time_delta_from_usec(poll_interval),
465 false, sql_trunk_connection_read_poll, c) < 0) {
466 ERROR("Unable to insert polling event");
467 }
468 }
469 if (notify_on == TRUNK_CONN_EVENT_READ) return;
470
472
475 false, sql_trunk_connection_write_poll, tconn) < 0) {
476 ERROR("Unable to insert polling event");
477 }
478 return;
479 }
480}
481
484
485static unlang_action_t sql_select_query_resume(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
486{
487 fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
488 rlm_sql_unixodbc_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_unixodbc_conn_t);
489 SQLINTEGER i;
490 SQLLEN len;
491 SQLRETURN ret = SQL_STILL_EXECUTING;
492
493 if (query_ctx->rcode != RLM_SQL_OK) RETURN_MODULE_FAIL;
494
495 while (ret == SQL_STILL_EXECUTING) {
496 ret = SQLNumResultCols(conn->stmt, &conn->colcount);
497 }
498 if (sql_check_error(ret, SQL_HANDLE_STMT, conn->stmt)) {
499 query_ctx->rcode = RLM_SQL_ERROR;
501 }
502
503 /* Reserving memory for result */
504 conn->row = talloc_zero_array(conn, char *, conn->colcount + 1); /* Space for pointers */
505 conn->ind = talloc_zero_array(conn, SQLLEN, conn->colcount); /* Space for indicators */
506
507 for (i = 1; i <= conn->colcount; i++) {
508 len = 0;
509 /* SQLColAttribute can in theory run Async */
510 while (SQLColAttribute(conn->stmt, (SQLUSMALLINT) i, SQL_DESC_LENGTH, NULL, 0, NULL, &len) == SQL_STILL_EXECUTING);
511 conn->row[i - 1] = talloc_array(conn->row, char, ++len);
512 SQLBindCol(conn->stmt, i, SQL_C_CHAR, (SQLCHAR *)conn->row[i - 1], len, &conn->ind[i - 1]);
513 }
514
516}
517
518static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
519{
520 rlm_sql_unixodbc_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_unixodbc_conn_t);
521 SQLSMALLINT len, i;
522 SQLRETURN ret;
523 char const **names;
524 char field[128];
525
526 if (conn->colcount == 0) return RLM_SQL_ERROR;
527
528 MEM(names = talloc_array(query_ctx, char const *, conn->colcount));
529
530 for (i = 0; i < conn->colcount; i++) {
531 char *p;
532 ret = SQL_STILL_EXECUTING;
533 while (ret == SQL_STILL_EXECUTING) {
534 ret = SQLColAttribute(conn->stmt, i + 1, SQL_DESC_NAME,
535 field, sizeof(field), &len, NULL);
536 }
537 switch (ret) {
538 case SQL_INVALID_HANDLE:
539 case SQL_ERROR:
540 ERROR("Failed retrieving field name at index %i", i);
541 sql_check_error(ret, SQL_HANDLE_STMT, conn->stmt);
543 return RLM_SQL_ERROR;
544
545 default:
546 break;
547 }
548
549 MEM(p = talloc_array(names, char, (size_t)len + 1));
550 strlcpy(p, field, (size_t)len + 1);
551 names[i] = p;
552 }
553 *out = names;
554
555 return RLM_SQL_OK;
556}
557
558static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
559{
560 fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
561 rlm_sql_unixodbc_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_unixodbc_conn_t);
562 SQLRETURN ret = SQL_STILL_EXECUTING;
563 SQLINTEGER i;
564
565 query_ctx->row = NULL;
566
567 while (ret == SQL_STILL_EXECUTING) {
568 ret = SQLFetch(conn->stmt);
569 }
570 if (ret == SQL_NO_DATA_FOUND) {
571 query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
573 }
574
575 query_ctx->rcode = sql_check_error(ret, SQL_HANDLE_STMT, conn->stmt);
576 if (query_ctx->rcode != RLM_SQL_OK) RETURN_MODULE_FAIL;
577
578 /*
579 * If the field is NULL, then SQLFetch doesn't touch pointer, so set it here
580 */
581 for (i = 0; i < conn->colcount; i++) {
582 if (conn->ind[i] == SQL_NULL_DATA) conn->row[i] = NULL;
583 }
584
585 query_ctx->row = conn->row;
586
587 query_ctx->rcode = RLM_SQL_OK;
589}
590
592{
593 rlm_sql_unixodbc_conn_t *conn = query_ctx->tconn->conn->h;
594
595 TALLOC_FREE(conn->row);
596 TALLOC_FREE(conn->ind);
597 conn->colcount = 0;
598
599 return RLM_SQL_OK;
600}
601
603{
605
606 /*
607 * If the query is not in a state which would return results, then do nothing.
608 */
609 if (query_ctx->treq && !(query_ctx->treq->state &
611
612 /*
613 * If the connection doesn't exist there's nothing to do
614 */
615 if (!query_ctx->tconn || !query_ctx->tconn->conn || !query_ctx->tconn->conn->h) return RLM_SQL_ERROR;
616
617 conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_unixodbc_conn_t);
618
619 TALLOC_FREE(conn->row);
620 conn->colcount = 0;
621 conn->query_ctx = NULL;
622
623 /*
624 * SQL_CLOSE - The cursor (if any) associated with the statement
625 * handle (StatementHandle) is closed and all pending results are
626 * discarded. The application can reopen the cursor by calling
627 * SQLExecute() with the same or different values in the
628 * application variables (if any) that are bound to StatementHandle.
629 * If no cursor has been associated with the statement handle,
630 * this option has no effect (no warning or error is generated).
631 *
632 * So, this call does NOT free the statement at all, it merely
633 * resets it for the next call. This is terrible terrible naming.
634 */
635 SQLFreeStmt(conn->stmt, SQL_CLOSE);
636
637 return RLM_SQL_OK;
638}
639
640/** Retrieves any errors associated with the query context
641 *
642 * @note Caller will free any memory allocated in ctx.
643 *
644 * @param ctx to allocate temporary error buffers in.
645 * @param out Array of #sql_log_entry_t to fill.
646 * @param outlen Length of out array.
647 * @param query_ctx Query context to retrieve error for.
648 * @return number of errors written to the #sql_log_entry_t array.
649 */
650static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], NDEBUG_UNUSED size_t outlen,
651 fr_sql_query_t *query_ctx)
652{
653 rlm_sql_unixodbc_conn_t *conn = query_ctx->tconn->conn->h;
654 SQLCHAR state[256];
655 SQLCHAR errbuff[256];
656 SQLINTEGER errnum = 0;
657 SQLSMALLINT length = 255;
658 size_t i = 0;
659
660 fr_assert(outlen > 2);
661
662 /*
663 * Depending on which handles exist at the time of calling there
664 * may be 1, 2 or 3 handles to check errors on.
665 */
666 errbuff[0] = state[0] = '\0';
667 SQLGetDiagRec(SQL_HANDLE_ENV, conn->env, 1, state, &errnum, errbuff, sizeof(errbuff), &length);
668 if (errnum != 0) {
669 out[i].type = L_ERR;
670 out[i].msg = talloc_typed_asprintf(ctx, "%s: %s", state, errbuff);
671 i++;
672 }
673 if (conn->dbc == SQL_NULL_HANDLE) return i;
674
675 SQLGetDiagRec(SQL_HANDLE_DBC, conn->dbc, 1, state, &errnum, errbuff, sizeof(errbuff), &length);
676 if (errnum != 0) {
677 out[i].type = L_ERR;
678 out[i].msg = talloc_typed_asprintf(ctx, "%s: %s", state, errbuff);
679 i++;
680 }
681 if (conn->stmt == SQL_NULL_HANDLE) return i;
682
683 SQLGetDiagRec(SQL_HANDLE_STMT, conn->stmt, 1, state, &errnum, errbuff, sizeof(errbuff), &length);
684 if (errnum != 0) {
685 out[i].type = L_ERR;
686 out[i].msg = talloc_typed_asprintf(ctx, "%s: %s", state, errbuff);
687 i++;
688 }
689
690 return i;
691}
692
693/*************************************************************************
694 *
695 * Function: sql_affected_rows
696 *
697 * Purpose: Return the number of rows affected by the query (update,
698 * or insert)
699 *
700 *************************************************************************/
702{
703 rlm_sql_unixodbc_conn_t *conn = query_ctx->tconn->conn->h;
704 SQLRETURN ret;
705 SQLLEN affected_rows;
706
707 ret = SQLRowCount(conn->stmt, &affected_rows);
708 if (sql_check_error(ret, SQL_HANDLE_STMT, conn->stmt)) return -1;
709
710 return affected_rows;
711}
712
713
714/* Exported to rlm_sql */
717 .common = {
718 .magic = MODULE_MAGIC_INIT,
719 .name = "sql_unixodbc"
720 },
722 .sql_query_resume = sql_query_resume,
723 .sql_select_query_resume = sql_select_query_resume,
724 .sql_affected_rows = sql_affected_rows,
725 .sql_fields = sql_fields,
726 .sql_fetch_row = sql_fetch_row,
727 .sql_free_result = sql_free_result,
728 .sql_error = sql_error,
729 .sql_finish_query = sql_finish_query,
730 .sql_finish_select_query = sql_finish_query,
731 .trunk_io_funcs = {
732 .connection_alloc = sql_trunk_connection_alloc,
733 .connection_notify = sql_trunk_connection_notify,
734 .request_mux = sql_trunk_request_mux,
735 .request_cancel_mux = sql_request_cancel_mux,
736 .request_cancel = sql_request_cancel,
737 .request_fail = sql_request_fail,
738 }
739};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:167
#define USES_APPLE_DEPRECATED_API
Definition build.h:472
#define RCSID(id)
Definition build.h:485
#define NDEBUG_UNUSED
Definition build.h:328
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:324
#define CC_NO_UBSAN(_sanitize)
Definition build.h:428
#define UNUSED
Definition build.h:317
connection_state_t
Definition connection.h:45
@ CONNECTION_STATE_FAILED
Connection has failed.
Definition connection.h:54
@ CONNECTION_STATE_CONNECTED
File descriptor is open (ready for writing).
Definition connection.h:52
@ CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
Definition connection.h:50
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition connection.h:84
fr_time_delta_t connection_timeout
How long to wait for the connection to open or for shutdown to close the connection.
Definition connection.h:92
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:1418
#define DEBUG3(_fmt,...)
Definition log.h:266
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:528
#define RDEBUG3(fmt,...)
Definition log.h:343
talloc_free(reap)
Stores all information relating to an event list.
Definition event.c:377
@ L_ERR
Error message.
Definition log.h:56
static const conf_parser_t config[]
Definition base.c:183
#define fr_assert(_expr)
Definition rad_assert.h:38
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define DEBUG2(fmt,...)
Definition radclient.h:43
#define INFO(fmt,...)
Definition radict.c:54
#define RETURN_MODULE_OK
Definition rcode.h:57
#define RETURN_MODULE_FAIL
Definition rcode.h:56
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
Prototypes and functions for the SQL module.
fr_sql_query_status_t status
Status of the query.
Definition rlm_sql.h:140
trunk_connection_t * tconn
Trunk connection this query is being run on.
Definition rlm_sql.h:136
fr_sql_query_type_t type
Type of query.
Definition rlm_sql.h:139
char const * query_str
Query string to run.
Definition rlm_sql.h:138
request_t * request
Request this query relates to.
Definition rlm_sql.h:134
sql_rcode_t
Action to take at end of an SQL query.
Definition rlm_sql.h:44
@ 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
@ SQL_QUERY_SELECT
Definition rlm_sql.h:116
fr_time_delta_t query_timeout
How long to allow queries to run for.
Definition rlm_sql.h:96
char const * sql_server
Server to connect to.
Definition rlm_sql.h:74
#define RLM_SQL_RCODE_FLAGS_ALT_QUERY
Can distinguish between other errors and those resulting from a unique key violation.
Definition rlm_sql.h:167
char ** rlm_sql_row_t
Definition rlm_sql.h:59
char const * sql_login
Login credentials to use.
Definition rlm_sql.h:76
rlm_sql_row_t row
Row data from the last query.
Definition rlm_sql.h:142
sql_rcode_t rcode
Result code.
Definition rlm_sql.h:141
char const * sql_password
Login password to use.
Definition rlm_sql.h:77
trunk_request_t * treq
Trunk request for this query.
Definition rlm_sql.h:137
trunk_conf_t trunk_conf
Configuration for trunk connections.
Definition rlm_sql.h:101
@ SQL_QUERY_CANCELLED
A cancellation has been sent to the server.
Definition rlm_sql.h:129
@ SQL_QUERY_RETURNED
Query has executed.
Definition rlm_sql.h:126
@ SQL_QUERY_FAILED
Failed to submit.
Definition rlm_sql.h:123
@ SQL_QUERY_SUBMITTED
Submitted for execution.
Definition rlm_sql.h:125
@ SQL_QUERY_PREPARED
Ready to submit.
Definition rlm_sql.h:124
Definition rlm_sql.h:61
static void sql_request_fail(request_t *request, void *preq, UNUSED void *rctx, UNUSED trunk_request_state_t state, UNUSED void *uctx)
static int affected_rows(PGresult *result)
Return the number of affected rows of the result as an int instead of the string that postgresql prov...
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
rlm_sql_driver_t rlm_sql_unixodbc
static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
static sql_rcode_t sql_finish_query(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
static SQL_TRUNK_CONNECTION_ALLOC void sql_trunk_request_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[], NDEBUG_UNUSED size_t outlen, fr_sql_query_t *query_ctx)
Retrieves any errors associated with the query context.
SQL_QUERY_FAIL static SQL_QUERY_RESUME unlang_action_t sql_select_query_resume(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
static USES_APPLE_DEPRECATED_API sql_rcode_t sql_check_error(SQLRETURN ret, SQLSMALLINT handle_type, SQLHANDLE handle)
Checks the error code to determine if the connection needs to be re-esttablished.
static void sql_request_cancel(connection_t *conn, void *preq, trunk_cancel_reason_t reason, UNUSED void *uctx)
fr_sql_query_t * query_ctx
static void _sql_connection_close(UNUSED fr_event_list_t *el, void *h, UNUSED void *uctx)
static void sql_request_cancel_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
static void sql_trunk_connection_init_poll(fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
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_trunk_connection_read_poll(fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
static sql_rcode_t sql_free_result(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
static connection_state_t sql_trunk_connection_init_stmt(rlm_sql_unixodbc_conn_t *c)
static int sql_affected_rows(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
rlm_sql_config_t const * config
static void sql_trunk_connection_write_poll(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
void connection_signal_connected(connection_t *conn)
Asynchronously signal that the connection is open.
module_flags_t flags
Flags that control how a module starts up and how a module is called.
Definition module.h:228
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:41
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
module_t common
Common fields for all loadable modules.
Definition rlm_sql.h:197
rlm_sql_config_t config
Definition rlm_sql.h:224
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition talloc.c:488
#define talloc_get_type_abort_const
Definition talloc.h:282
static const char * names[8]
Definition time.c:584
static int64_t fr_time_delta_to_sec(fr_time_delta_t delta)
Definition time.h:647
static int64_t fr_time_delta_to_usec(fr_time_delta_t delta)
Definition time.h:632
static fr_time_delta_t fr_time_delta_from_usec(int64_t usec)
Definition time.h:568
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:49
A timer event.
Definition timer.c:75
#define fr_timer_in(...)
Definition timer.h:86
#define FR_TIMER_DISARM(_ev)
Definition timer.h:90
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:3834
void trunk_request_signal_fail(trunk_request_t *treq)
Signal that a trunk request failed.
Definition trunk.c:2131
void trunk_request_signal_cancel_sent(trunk_request_t *treq)
Signal that a remote server has been notified of the cancellation.
Definition trunk.c:2259
void trunk_request_signal_cancel_complete(trunk_request_t *treq)
Signal that a remote server acked our cancellation.
Definition trunk.c:2283
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:3882
void trunk_request_signal_sent(trunk_request_t *treq)
Signal that the request was written to a connection successfully.
Definition trunk.c:2049
void trunk_connection_signal_writable(trunk_connection_t *tconn)
Signal that a trunk connection is writable.
Definition trunk.c:3902
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:2071
Associates request queues with a connection.
Definition trunk.c:133
Wraps a normal request.
Definition trunk.c:99
connection_conf_t const * conn_conf
Connection configuration.
Definition trunk.h:225
trunk_connection_event_t
What type of I/O events the trunk connection is currently interested in receiving.
Definition trunk.h:72
@ TRUNK_CONN_EVENT_BOTH
Trunk should be notified if a connection is readable or writable.
Definition trunk.h:79
@ TRUNK_CONN_EVENT_WRITE
Trunk should be notified if a connection is writable.
Definition trunk.h:77
@ TRUNK_CONN_EVENT_NONE
Don't notify the trunk on connection state changes.
Definition trunk.h:73
@ TRUNK_CONN_EVENT_READ
Trunk should be notified if a connection is readable.
Definition trunk.h:75
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:173
@ TRUNK_REQUEST_STATE_COMPLETE
The request is complete.
Definition trunk.h:182
@ TRUNK_REQUEST_STATE_SENT
Was written to a socket. Waiting for a response.
Definition trunk.h:172
static fr_event_list_t * el
static size_t char ** out
Definition value.h:1012