The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_sql_freetds.c
Go to the documentation of this file.
1 /*
2 * This program is 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: ed4de9c4164a97ec5ccf853635b3da01252391f2 $
19 * @file rlm_sql.c
20 * @brief Implements FreeTDS rlm_sql driver.
21 *
22 * @copyright 2013 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 * @copyright 2000,2006 The FreeRADIUS server project
24 * @copyright 2000 Mattias Sjostrom (mattias@nogui.se)
25 */
26
27RCSID("$Id: ed4de9c4164a97ec5ccf853635b3da01252391f2 $")
28
29#define LOG_PREFIX "sql - freetds"
30
31#include <freeradius-devel/server/base.h>
32#include <freeradius-devel/util/debug.h>
33
34#include <sys/stat.h>
35
36#include <ctpublic.h>
37
38#include "rlm_sql.h"
39#include "rlm_sql_trunk.h"
40
41typedef struct {
42 CS_CONTEXT *context; //!< Structure FreeTDS uses to avoid creating globals.
43 CS_CONNECTION *db; //!< Handle specifying a single connection to the database.
44 CS_COMMAND *command; //!< A prepared statement.
45 int colcount; //!< How many columns are in the current result set.
46 bool nulls; //!< Were there any NULL values in the last row.
47 char **results; //!< Result strings from statement execution.
48 CS_SMALLINT *ind; //!< Indicators of data length / NULL.
49 char *error; //!< The last error string created by one of the call backs.
50 bool established; //!< Set to false once the connection has been properly established.
51 CS_INT rows_affected; //!< Rows affected by last INSERT / UPDATE / DELETE.
53
54#define MAX_DATASTR_LEN 256
55
56/** Client-Library error handler
57 *
58 * Callback for any errors raised by the Client-Library. Will overwrite any previous errors associated
59 * with a connection.
60 *
61 * @param context The FreeTDS library context.
62 * @param conn DB connection handle.
63 * @param emsgp Pointer to the error structure.
64 * @return CS_SUCCEED
65 */
66static CS_RETCODE CS_PUBLIC clientmsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_CLIENTMSG *emsgp)
67{
68 rlm_sql_freetds_conn_t *this = NULL;
69 int len = 0;
70
71 /*
72 * Not actually an error, but the client wanted to tell us something...
73 */
74 if (emsgp->severity == CS_SV_INFORM) {
75 INFO("%s", emsgp->msgstring);
76
77 return CS_SUCCEED;
78 }
79
80 if ((cs_config(context, CS_GET, CS_USERDATA, &this, sizeof(this), &len) != CS_SUCCEED) || !this) {
81 ERROR("failed retrieving context userdata");
82
83 return CS_SUCCEED;
84 }
85
86 if (this->error) TALLOC_FREE(this->error);
87
88 this->error = talloc_typed_asprintf(this, "client error: severity(%ld), number(%ld), origin(%ld), layer(%ld): %s",
89 (long)CS_SEVERITY(emsgp->severity), (long)CS_NUMBER(emsgp->msgnumber),
90 (long)CS_ORIGIN(emsgp->msgnumber), (long)CS_LAYER(emsgp->msgnumber),
91 emsgp->msgstring);
92
93 if (emsgp->osstringlen > 0) {
94 this->error = talloc_asprintf_append(this->error, ". os error: number(%ld): %s",
95 (long)emsgp->osnumber, emsgp->osstring);
96 }
97
98 return CS_SUCCEED;
99}
100
101/** Client error handler
102 *
103 * Callback for any errors raised by the client. Will overwrite any previous errors associated
104 * with a connection.
105 *
106 * @param context The FreeTDS library context.
107 * @param emsgp Pointer to the error structure.
108 * @return CS_SUCCEED
109 */
110static CS_RETCODE CS_PUBLIC csmsg_callback(CS_CONTEXT *context, CS_CLIENTMSG *emsgp)
111{
112 rlm_sql_freetds_conn_t *this = NULL;
113 int len = 0;
114
115 /*
116 * Not actually an error, but the client wanted to tell us something...
117 */
118 if (emsgp->severity == CS_SV_INFORM) {
119 INFO("%s", emsgp->msgstring);
120
121 return CS_SUCCEED;
122 }
123
124 if ((cs_config(context, CS_GET, CS_USERDATA, &this, sizeof(this), &len) != CS_SUCCEED) || !this) {
125 ERROR("failed retrieving context userdata");
126
127 return CS_SUCCEED;
128 }
129
130 if (this->error) TALLOC_FREE(this->error);
131
132 this->error = talloc_typed_asprintf(this, "cs error: severity(%ld), number(%ld), origin(%ld), layer(%ld): %s",
133 (long)CS_SEVERITY(emsgp->severity), (long)CS_NUMBER(emsgp->msgnumber),
134 (long)CS_ORIGIN(emsgp->msgnumber), (long)CS_LAYER(emsgp->msgnumber),
135 emsgp->msgstring);
136
137 if (emsgp->osstringlen > 0) {
138 this->error = talloc_asprintf_append(this->error, ". os error: number(%ld): %s",
139 (long)emsgp->osnumber, emsgp->osstring);
140 }
141
142 return CS_SUCCEED;
143}
144
145/** Server error handler
146 *
147 * Callback for any messages sent back from the server.
148 *
149 * There's no standard categorisation of messages sent back from the server, so we don't know they're errors,
150 * the only thing we can do is write them to the long as informational messages.
151 *
152 * @param context The FreeTDS library context.
153 * @param conn DB connection handle.
154 * @param msgp Pointer to the error structure.
155 * @return CS_SUCCEED
156 */
157static CS_RETCODE CS_PUBLIC servermsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_SERVERMSG *msgp)
158{
159 rlm_sql_freetds_conn_t *this = NULL;
160 int len = 0;
161
162 if ((cs_config(context, CS_GET, CS_USERDATA, &this, sizeof(this), &len) != CS_SUCCEED) || !this) {
163 ERROR("failed retrieving context userdata");
164
165 return CS_SUCCEED;
166 }
167
168 /*
169 * Because apparently there are no standard severity levels *brilliant*
170 */
171 if (this->established) {
172 INFO("server msg from \"%s\": severity(%ld), number(%ld), origin(%ld), "
173 "layer(%ld), procedure \"%s\": %s",
174 (msgp->svrnlen > 0) ? msgp->svrname : "unknown",
175 (long)msgp->msgnumber, (long)msgp->severity, (long)msgp->state, (long)msgp->line,
176 (msgp->proclen > 0) ? msgp->proc : "none", msgp->text);
177 } else {
178 if (this->error) TALLOC_FREE(this->error);
179
180 this->error = talloc_typed_asprintf(this, "Server msg from \"%s\": severity(%ld), number(%ld), "
181 "origin(%ld), layer(%ld), procedure \"%s\": %s",
182 (msgp->svrnlen > 0) ? msgp->svrname : "unknown",
183 (long)msgp->msgnumber, (long)msgp->severity, (long)msgp->state,
184 (long)msgp->line,
185 (msgp->proclen > 0) ? msgp->proc : "none", msgp->text);
186 }
187
188 return CS_SUCCEED;
189}
190
191/*************************************************************************
192 *
193 * Function: sql_query
194 *
195 * Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
196 * the database.
197 *
198 *************************************************************************/
199static sql_rcode_t sql_query(request_t *request, rlm_sql_freetds_conn_t *conn, char const *query)
200{
201 CS_RETCODE results_ret;
202 CS_INT result_type;
203
204 /*
205 * Reset rows_affected in case the query fails.
206 * Prevents accidentally returning the rows_affected from a previous query.
207 */
208 conn->rows_affected = -1;
209
210 if (ct_cmd_alloc(conn->db, &conn->command) != CS_SUCCEED) {
211 ROPTIONAL(RERROR, ERROR, "Unable to allocate command structure (ct_cmd_alloc())");
212 return RLM_SQL_ERROR;
213 }
214
215 if (ct_command(conn->command, CS_LANG_CMD, query, CS_NULLTERM, CS_UNUSED) != CS_SUCCEED) {
216 ROPTIONAL(RERROR, ERROR, "Unable to initialise command structure (ct_command())");
217 return RLM_SQL_ERROR;
218 }
219
220 if (ct_send(conn->command) != CS_SUCCEED) {
221 ROPTIONAL(RERROR, ERROR, "Unable to send command (ct_send())");
222 return RLM_SQL_ERROR;
223 }
224
225 /*
226 * We'll make three calls to ct_results, first to get a success indicator, secondly to get a
227 * done indicator, and thirdly to get a "nothing left to handle" status.
228 */
229
230 /*
231 * First call to ct_results, we need returncode CS_SUCCEED and result_type CS_CMD_SUCCEED.
232 */
233 switch(ct_results(conn->command, &result_type)) {
234 case CS_SUCCEED:
235 switch (result_type) {
236 case CS_CMD_SUCCEED:
237 break;
238 case CS_ROW_RESULT:
239 ROPTIONAL(RERROR, ERROR, "sql_query processed a query returning rows. "
240 "Use sql_select_query instead!");
241 break;
242 case CS_CMD_FAIL:
243 /*
244 * If ct_send succeeded and ct_results gives CS_CMD_FAIL,
245 * provided the queries are sane, this will be a key constraint
246 * conflict.
247 * Either way, this is a reasonable cause to go to the alternate query.
248 */
249 return RLM_SQL_ALT_QUERY;
250 default:
251 ROPTIONAL(RERROR, ERROR, "Result failure or unexpected result type from query, (%d)", result_type);
252 return RLM_SQL_ERROR;
253 }
254 break;
255
256 case CS_FAIL: /* Serious failure, freetds requires us to cancel and maybe even close db */
257 ROPTIONAL(RERROR, ERROR, "Failure retrieving query results");
258
259 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
260 conn->command = NULL;
261 return RLM_SQL_ERROR;
262
263 default:
264 ROPTIONAL(RERROR, ERROR, "Unexpected return value from ct_results()");
265 return RLM_SQL_ERROR;
266 }
267
268 /*
269 * Retrieve the number of rows affected - the later calls
270 * to ct_results end up resetting the underlying counter so we
271 * no longer have access to this.
272 */
273 if (ct_res_info(conn->command, CS_ROW_COUNT, &conn->rows_affected, CS_UNUSED, NULL) != CS_SUCCEED) {
274 ROPTIONAL(RERROR, ERROR, "rlm_sql_freetds: error retrieving row count");
275 return RLM_SQL_ERROR;
276 }
277
278 /*
279 * Second call to ct_results, we need returncode CS_SUCCEED
280 * and result_type CS_CMD_DONE.
281 */
282 if ((results_ret = ct_results(conn->command, &result_type)) == CS_SUCCEED) {
283 if (result_type != CS_CMD_DONE) {
284 ROPTIONAL(RERROR, ERROR, "Result failure or unexpected result type from query");
285 return RLM_SQL_ERROR;
286 }
287 } else {
288 switch (results_ret) {
289 case CS_FAIL: /* Serious failure, freetds requires us to cancel and maybe even close db */
290 ROPTIONAL(RERROR, ERROR, "Failure retrieving query results");
291 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
292
293 conn->command = NULL;
294 return RLM_SQL_ERROR;
295
296 default:
297 ROPTIONAL(RERROR, ERROR, "Unexpected return value from ct_results()");
298 return RLM_SQL_ERROR;
299 }
300 }
301
302 /*
303 * Third call to ct_results, we need returncode CS_END_RESULTS result_type will be ignored.
304 */
305 results_ret = ct_results(conn->command, &result_type);
306 switch (results_ret) {
307 case CS_FAIL: /* Serious failure, freetds requires us to cancel and maybe even close db */
308 ROPTIONAL(RERROR, ERROR, "Failure retrieving query results");
309 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
310 conn->command = NULL;
311
312 return RLM_SQL_ERROR;
313
314 case CS_END_RESULTS: /* This is where we want to end up */
315 break;
316
317 default:
318 ROPTIONAL(RERROR, ERROR, "Unexpected return value from ct_results()");
319
320 return RLM_SQL_ERROR;
321 }
322
323 return RLM_SQL_OK;
324}
325
326/*************************************************************************
327 *
328 * Function: sql_fields
329 *
330 * Purpose: Return name of regular result columns.
331 *
332 *************************************************************************/
333static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
334{
335 rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
336 CS_DATAFMT datafmt;
337 int fields, i;
338 char const **names;
339
340 /* Get number of elements in row result */
341 if (ct_res_info(conn->command, CS_NUMDATA, (CS_INT *)&fields, CS_UNUSED, NULL) != CS_SUCCEED) {
342 ERROR("sql_fields() Error retrieving column count");
343
344 return RLM_SQL_ERROR;
345 }
346
347 if (fields <= 0) return RLM_SQL_ERROR;
348
349 MEM(names = talloc_array(query_ctx, char const *, fields));
350
351 for (i = 0; i < fields; i++) {
352 int col = i + 1;
353 char *p;
354
355 /*
356 ** Get the column description. ct_describe() fills the
357 ** datafmt parameter with a description of the column.
358 */
359 if (ct_describe(conn->command, col, &datafmt) != CS_SUCCEED) {
360 ERROR("sql_fields() Problems with ct_describe(), column %d", col);
362 return RLM_SQL_ERROR;
363 }
364
365 if (datafmt.namelen > 0) {
366 MEM(p = talloc_array(names, char, (size_t)datafmt.namelen + 1));
367 strlcpy(p, datafmt.name, (size_t)datafmt.namelen + 1);
368 names[i] = p;
369 }
370 }
371
372 *out = names;
373
374 return RLM_SQL_OK;
375}
376
377/** Retrieves any errors associated with the query context
378 *
379 * @note Caller will free any memory allocated in ctx.
380 *
381 * @param ctx to allocate temporary error buffers in.
382 * @param out Array of sql_log_entrys to fill.
383 * @param outlen Length of out array.
384 * @param query_ctx Query context to retrieve error for.
385 * @return number of errors written to the #sql_log_entry_t array.
386 */
387static size_t sql_error(UNUSED TALLOC_CTX *ctx, sql_log_entry_t out[], NDEBUG_UNUSED size_t outlen,
388 fr_sql_query_t *query_ctx)
389{
390 rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
391
392 fr_assert(outlen > 0);
393
394 if (!conn->error) return 0;
395
396 out[0].type = L_ERR;
397 out[0].msg = conn->error;
398
399 return 1;
400}
401
403{
404 rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
405
406 ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
407 if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
408 ERROR("freeing command structure failed");
409
410 return RLM_SQL_ERROR;
411 }
412 conn->command = NULL;
413 conn->nulls = false;
414
415 TALLOC_FREE(conn->results);
416
417 return RLM_SQL_OK;
418
419}
420
421/** Execute a query when we expected a result set
422 *
423 */
424static sql_rcode_t sql_select_query(request_t *request, rlm_sql_freetds_conn_t *conn, char const *query)
425{
426 CS_RETCODE results_ret;
427 CS_INT result_type;
428 CS_DATAFMT descriptor;
429 int i;
430 char **rowdata;
431
432 if (!conn->db) {
433 ROPTIONAL(RERROR, ERROR, "socket not connected");
434 return RLM_SQL_ERROR;
435 }
436
437 if (ct_cmd_alloc(conn->db, &conn->command) != CS_SUCCEED) {
438 ROPTIONAL(RERROR, ERROR, "unable to allocate command structure (ct_cmd_alloc())");
439 return RLM_SQL_ERROR;
440 }
441
442 if (ct_command(conn->command, CS_LANG_CMD, query, CS_NULLTERM, CS_UNUSED) != CS_SUCCEED) {
443 ROPTIONAL(RERROR, ERROR, "unable to initiate command structure (ct_command()");
444 return RLM_SQL_ERROR;
445 }
446
447 if (ct_send(conn->command) != CS_SUCCEED) {
448 ROPTIONAL(RERROR, ERROR, "unable to send command (ct_send())");
449 return RLM_SQL_ERROR;
450 }
451
452 results_ret = ct_results(conn->command, &result_type);
453 switch (results_ret) {
454 case CS_SUCCEED:
455 switch (result_type) {
456 case CS_ROW_RESULT:
457
458 /*
459 * Set up a target buffer for the results data, and associate the buffer with the results,
460 * but the actual fetching takes place in sql_fetch_row.
461 * The layer above MUST call sql_fetch_row and/or sql_finish_select_query
462 * or this socket will be unusable and may cause segfaults
463 * if reused later on.
464 */
465
466 /*
467 * Set up the DATAFMT structure that describes our target array
468 * and tells freetds what we want future ct_fetch calls to do.
469 */
470 descriptor.datatype = CS_CHAR_TYPE; /* The target buffer is a string */
471 descriptor.format = CS_FMT_NULLTERM; /* Null termination please */
472 descriptor.maxlength = MAX_DATASTR_LEN; /* The string arrays are this large */
473 descriptor.count = 1; /* Fetch one row of data */
474 descriptor.locale = NULL; /* Don't do NLS stuff */
475
476 if (ct_res_info(conn->command, CS_NUMDATA, &conn->colcount, CS_UNUSED, NULL) != CS_SUCCEED) {
477 ROPTIONAL(RERROR, ERROR, "Error retrieving column count");
478 return RLM_SQL_ERROR;
479 }
480
481 rowdata = talloc_zero_array(conn, char *, conn->colcount + 1); /* Space for pointers */
482 conn->ind = talloc_zero_array(conn, CS_SMALLINT, conn->colcount);
483
484 for (i = 0; i < conn->colcount; i++) {
485 /* Space to hold the result data */
486 rowdata[i] = talloc_zero_array(rowdata, char, MAX_DATASTR_LEN + 1);
487
488 /* Associate the target buffer with the data */
489 if (ct_bind(conn->command, i + 1, &descriptor, rowdata[i], NULL, &conn->ind[i]) != CS_SUCCEED) {
490 talloc_free(rowdata);
491 talloc_free(conn->ind);
492
493 ROPTIONAL(RERROR, ERROR, "ct_bind() failed)");
494 return RLM_SQL_ERROR;
495 }
496 }
497
498 rowdata[i] = NULL; /* Terminate the array */
499 conn->results = rowdata;
500 break;
501
502 case CS_CMD_SUCCEED:
503 case CS_CMD_DONE:
504 ROPTIONAL(RWARN, WARN, "query returned no data");
505 break;
506
507 default:
508 ROPTIONAL(RERROR, ERROR, "unexpected result type from query");
509 return RLM_SQL_ERROR;
510 }
511 break;
512
513 case CS_FAIL:
514 /*
515 * Serious failure, freetds requires us to cancel the results and maybe even close the db.
516 */
517
518 ROPTIONAL(RERROR, ERROR, "failure retrieving query results");
519
520 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
521 conn->command = NULL;
522
523 return RLM_SQL_ERROR;
524
525 default:
526 ROPTIONAL(RERROR, ERROR, "unexpected return value from ct_results()");
527
528 return RLM_SQL_ERROR;
529 }
530
531 return RLM_SQL_OK;
532}
533
534CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
536 connection_t *conn, UNUSED void *uctx)
537{
538 rlm_sql_freetds_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_freetds_conn_t);
539 trunk_request_t *treq;
540 request_t *request;
541 fr_sql_query_t *query_ctx;
542
543 if (trunk_connection_pop_request(&treq, tconn) != 0) return;
544 if (!treq) return;
545
546 query_ctx = talloc_get_type_abort(treq->preq, fr_sql_query_t);
547 request = query_ctx->request;
548 query_ctx->tconn = tconn;
549
550 ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query_ctx->query_str);
551
552 switch (query_ctx->type) {
553 case SQL_QUERY_SELECT:
554 query_ctx->rcode = sql_select_query(request, sql_conn, query_ctx->query_str);
555 break;
556 case SQL_QUERY_OTHER:
557 query_ctx->rcode = sql_query(request, sql_conn, query_ctx->query_str);
558 break;
559 }
560
561 switch (query_ctx->rcode) {
562 case RLM_SQL_OK:
564 break;
565
566 default:
569 return;
570 }
571
572 query_ctx->status = SQL_QUERY_RETURNED;
574 if (request) unlang_interpret_mark_runnable(request);
575}
576
578{
579 rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
580
581 return (conn->rows_affected);
582}
583
584static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
585{
586 fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
587 rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
588 CS_INT ret, count;
589 int i;
590
591 if (conn->nulls) TALLOC_FREE(query_ctx->row);
592 query_ctx->row = NULL;
593
594 ret = ct_fetch(conn->command, CS_UNUSED, CS_UNUSED, CS_UNUSED, &count);
595 switch (ret) {
596 case CS_FAIL:
597 /*
598 * Serious failure, freetds requires us to cancel the results and maybe even close the db.
599 */
600 ROPTIONAL(RERROR, ERROR, "failure fetching row data");
601 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
602 ROPTIONAL(RERROR, ERROR, "cleaning up");
603 } else {
604 conn->command = NULL;
605 }
606
607 query_ctx->rcode = RLM_SQL_RECONNECT;
609
610 case CS_END_DATA:
611 query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
613
614 case CS_SUCCEED:
615 /*
616 * NULL values are indicated by -1 in the corresponding "indicator"
617 * However, the buffer still exists, so if we have any NULL
618 * returns, we need to copy the results with the NULL
619 * fields left at zero to match behaviour of other drivers.
620 */
621 conn->nulls = false;
622 for (i = 0; i < conn->colcount; i++) {
623 if (conn->ind[i] < 0) {
624 conn->nulls = true;
625 break;
626 }
627 }
628
629 if (conn->nulls) {
630 query_ctx->row = talloc_zero_array(query_ctx, char *, conn->colcount + 1);
631 for (i = 0; i < conn->colcount; i++) {
632 if (conn->ind[i] < 0) continue;
633 query_ctx->row[i] = talloc_strdup(query_ctx->row, conn->results[i]);
634 }
635 } else {
636 query_ctx->row = conn->results;
637 }
638
639 query_ctx->rcode = RLM_SQL_OK;
641
642 case CS_ROW_FAIL:
643 ROPTIONAL(RERROR, ERROR, "recoverable failure fetching row data");
644
645 query_ctx->rcode = RLM_SQL_RECONNECT;
647
648 default:
649 ROPTIONAL(RERROR, ERROR, "unexpected returncode from ct_fetch");
650
651 query_ctx->rcode = RLM_SQL_ERROR;
653 }
654}
655
657{
658 rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
659
660 ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
661 if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
662 ERROR("freeing command structure failed");
663
664 return RLM_SQL_ERROR;
665 }
666 conn->command = NULL;
667 conn->rows_affected = -1;
668
669 return RLM_SQL_OK;
670}
671
672static void _sql_connection_close(UNUSED fr_event_list_t *el, void *h, UNUSED void *uctx)
673{
674 rlm_sql_freetds_conn_t *c = talloc_get_type_abort(h, rlm_sql_freetds_conn_t);
675
676 DEBUG2("socket destructor called, closing socket");
677
678 if (c->command) {
679 ct_cancel(NULL, c->command, CS_CANCEL_ALL);
680 if (ct_cmd_drop(c->command) != CS_SUCCEED) {
681 ERROR("freeing command structure failed");
682 return;
683 }
684 }
685
686 if (c->db) {
687 /*
688 * We first try gracefully closing the connection (which informs the server)
689 * Then if that fails we force the connection closure.
690 *
691 * Sybase docs says this may fail because of pending results, but we
692 * should not have any pending results at this point, so something else must
693 * of gone wrong.
694 */
695 if (ct_close(c->db, CS_UNUSED) != CS_SUCCEED) ct_close(c->db, CS_FORCE_CLOSE);
696
697 ct_con_drop(c->db);
698 }
699
700 if (c->context) {
701 ct_exit(c->context, CS_UNUSED);
702 cs_ctx_drop(c->context);
703 }
704
705 talloc_free(h);
706}
707
708CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
709static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
710{
712 rlm_sql_config_t const *config = &sql->config;
714 unsigned int timeout_ms = fr_time_delta_to_msec(config->trunk_conf.conn_conf->connection_timeout);
715 char database[128];
716
717 MEM(c = talloc_zero(conn, rlm_sql_freetds_conn_t));
718
719 /*
720 * Allocate a CS context structure. This should really only be done once, but because of
721 * the db pooling design of rlm_sql, we'll have to go with one context per db
722 */
723 if (cs_ctx_alloc(CS_VERSION_100, &c->context) != CS_SUCCEED) {
724 ERROR("unable to allocate CS context structure (cs_ctx_alloc())");
725 error:
726 if (c->error) ERROR("%s", c->error);
728 }
729
730 /*
731 * Initialize ctlib
732 */
733 if (ct_init(c->context, CS_VERSION_100) != CS_SUCCEED) {
734 ERROR("unable to initialize Client-Library");
735 goto error;
736 }
737
738 if (ct_config(c->context, CS_SET, CS_LOGIN_TIMEOUT, (CS_VOID *)&timeout_ms, CS_UNUSED, NULL) != CS_SUCCEED) {
739 ERROR("Setting connection timeout failed");
740 goto error;
741 }
742
743 /*
744 * Install callback functions for error-handling
745 */
746 if (cs_config(c->context, CS_SET, CS_MESSAGE_CB, (CS_VOID *)csmsg_callback, CS_UNUSED, NULL) != CS_SUCCEED) {
747 ERROR("unable to install CS Library error callback");
748 goto error;
749 }
750
751 if (cs_config(c->context, CS_SET, CS_USERDATA, (CS_VOID *)&c, sizeof(c), NULL) != CS_SUCCEED) {
752 ERROR("unable to set userdata pointer");
753 goto error;
754 }
755
756 if (ct_callback(c->context, NULL, CS_SET, CS_CLIENTMSG_CB, (CS_VOID *)clientmsg_callback) != CS_SUCCEED) {
757 ERROR("unable to install client message callback");
758 goto error;
759 }
760
761 if (ct_callback(c->context, NULL, CS_SET, CS_SERVERMSG_CB, (CS_VOID *)servermsg_callback) != CS_SUCCEED) {
762 ERROR("unable to install server message callback");
763 goto error;
764 }
765
766 /*
767 * Allocate a ctlib db structure
768 */
769 if (ct_con_alloc(c->context, &c->db) != CS_SUCCEED) {
770 ERROR("unable to allocate db structure");
771 goto error;
772 }
773
774 /*
775 * Set User and Password properties for the db
776 */
777 if (ct_con_props(c->db, CS_SET, CS_USERNAME,
778 UNCONST(CS_VOID *, config->sql_login), strlen(config->sql_login), NULL) != CS_SUCCEED) {
779 ERROR("unable to set username for db");
780 goto error;
781 }
782
783 if (ct_con_props(c->db, CS_SET, CS_PASSWORD,
784 UNCONST(CS_VOID *, config->sql_password), strlen(config->sql_password), NULL) != CS_SUCCEED) {
785 ERROR("unable to set password for db");
786 goto error;
787 }
788
789 /*
790 * Connect to the database
791 */
792 if (ct_connect(c->db, UNCONST(CS_CHAR *, config->sql_server), strlen(config->sql_server)) != CS_SUCCEED) {
793 ERROR("unable to establish db to symbolic servername %s",
794 config->sql_server);
795 goto error;
796 }
797
798 /*
799 * There doesn't appear to be a way to set the database with the API, so use an
800 * sql statement when we first open the connection.
801 */
802 snprintf(database, sizeof(database), "USE %s;", config->sql_db);
803 if (sql_query(NULL, c, database) != RLM_SQL_OK) goto error;
804
805 *h = c;
807}
808
810
812
813static void sql_request_fail(request_t *request, void *preq, UNUSED void *rctx,
814 UNUSED trunk_request_state_t state, UNUSED void *uctx)
815{
816 fr_sql_query_t *query_ctx = talloc_get_type_abort(preq, fr_sql_query_t);
817
818 query_ctx->treq = NULL;
819 if (query_ctx->rcode == RLM_SQL_OK) query_ctx->rcode = RLM_SQL_ERROR;
820 if (request) unlang_interpret_mark_runnable(request);
821}
822
823/* Exported to rlm_sql */
826 .common = {
827 .magic = MODULE_MAGIC_INIT,
828 .name = "sql_freetds"
829 },
831 .sql_query_resume = sql_query_resume,
832 .sql_select_query_resume = sql_query_resume,
833 .sql_fields = sql_fields,
834 .sql_affected_rows = sql_affected_rows,
835 .sql_fetch_row = sql_fetch_row,
836 .sql_error = sql_error,
837 .sql_finish_query = sql_finish_query,
838 .sql_finish_select_query = sql_finish_select_query,
839 .trunk_io_funcs = {
840 .connection_alloc = sql_trunk_connection_alloc,
841 .request_mux = sql_trunk_request_mux,
842 .request_fail = sql_request_fail
843 }
844};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int context
Definition radmin.c:71
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:167
#define RCSID(id)
Definition build.h:483
#define NDEBUG_UNUSED
Definition build.h:326
#define CC_NO_UBSAN(_sanitize)
Definition build.h:426
#define UNUSED
Definition build.h:315
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_FAILED
Connection is being reconnected because it failed.
Definition connection.h:84
#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:1359
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:528
#define RWARN(fmt,...)
Definition log.h:297
#define RERROR(fmt,...)
Definition log.h:298
talloc_free(reap)
Stores all information relating to an event list.
Definition event.c:411
@ 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 WARN(fmt,...)
Definition radclient.h:47
#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:138
trunk_connection_t * tconn
Trunk connection this query is being run on.
Definition rlm_sql.h:134
fr_sql_query_type_t type
Type of query.
Definition rlm_sql.h:137
char const * query_str
Query string to run.
Definition rlm_sql.h:136
request_t * request
Request this query relates to.
Definition rlm_sql.h:132
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:114
@ SQL_QUERY_OTHER
Definition rlm_sql.h:115
#define RLM_SQL_RCODE_FLAGS_ALT_QUERY
Can distinguish between other errors and those resulting from a unique key violation.
Definition rlm_sql.h:164
rlm_sql_row_t row
Row data from the last query.
Definition rlm_sql.h:140
sql_rcode_t rcode
Result code.
Definition rlm_sql.h:139
trunk_request_t * treq
Trunk request for this query.
Definition rlm_sql.h:135
@ SQL_QUERY_RETURNED
Query has executed.
Definition rlm_sql.h:124
Definition rlm_sql.h:61
static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
static size_t sql_error(UNUSED 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.
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)
SQL_TRUNK_CONNECTION_ALLOC static SQL_QUERY_RESUME void sql_request_fail(request_t *request, void *preq, UNUSED void *rctx, UNUSED trunk_request_state_t state, UNUSED void *uctx)
CS_SMALLINT * ind
Indicators of data length / NULL.
char ** results
Result strings from statement execution.
static sql_rcode_t sql_query(request_t *request, rlm_sql_freetds_conn_t *conn, char const *query)
static CS_RETCODE CS_PUBLIC servermsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_SERVERMSG *msgp)
Server error handler.
static CS_RETCODE CS_PUBLIC csmsg_callback(CS_CONTEXT *context, CS_CLIENTMSG *emsgp)
Client error handler.
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)
static sql_rcode_t sql_finish_select_query(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
CS_CONNECTION * db
Handle specifying a single connection to the database.
#define MAX_DATASTR_LEN
static void _sql_connection_close(UNUSED fr_event_list_t *el, void *h, UNUSED void *uctx)
CS_COMMAND * command
A prepared statement.
static CS_RETCODE CS_PUBLIC clientmsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_CLIENTMSG *emsgp)
Client-Library error handler.
CS_INT rows_affected
Rows affected by last INSERT / UPDATE / DELETE.
bool established
Set to false once the connection has been properly established.
int colcount
How many columns are in the current result set.
static int sql_affected_rows(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
static sql_rcode_t sql_select_query(request_t *request, rlm_sql_freetds_conn_t *conn, char const *query)
Execute a query when we expected a result set.
CS_CONTEXT * context
Structure FreeTDS uses to avoid creating globals.
bool nulls
Were there any NULL values in the last row.
char * error
The last error string created by one of the call backs.
rlm_sql_driver_t rlm_sql_freetds
Macros to reduce boilerplate in trunk SQL drivers.
#define SQL_QUERY_RESUME
#define SQL_TRUNK_CONNECTION_ALLOC
Allocate an SQL trunk connection.
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
module_flags_t flags
Flags that control how a module starts up and how a module is called.
Definition module.h:227
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
return count
Definition module.c:163
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:194
rlm_sql_config_t config
Definition rlm_sql.h:221
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition talloc.c:492
#define talloc_get_type_abort_const
Definition talloc.h:282
static const char * names[8]
Definition time.c:621
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition time.h:637
void trunk_request_signal_fail(trunk_request_t *treq)
Signal that a trunk request failed.
Definition trunk.c:2132
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:3883
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:2072
Associates request queues with a connection.
Definition trunk.c:134
Wraps a normal request.
Definition trunk.c:100
trunk_request_state_t
Used for sanity checks and to simplify freeing.
Definition trunk.h:161
static fr_event_list_t * el
static size_t char ** out
Definition value.h:997