The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 6c52371f05fcdb3875a248e6ce463b08a4b53f64 $
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 
27 RCSID("$Id: 6c52371f05fcdb3875a248e6ce463b08a4b53f64 $")
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 
41 typedef 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  */
66 static 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  */
110 static 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  */
157 static 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  *************************************************************************/
199 static 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  *************************************************************************/
333 static 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  * @param config rlm_sql config.
386  * @return number of errors written to the #sql_log_entry_t array.
387  */
388 static size_t sql_error(UNUSED TALLOC_CTX *ctx, sql_log_entry_t out[], NDEBUG_UNUSED size_t outlen,
389  fr_sql_query_t *query_ctx)
390 {
391  rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
392 
393  fr_assert(outlen > 0);
394 
395  if (!conn->error) return 0;
396 
397  out[0].type = L_ERR;
398  out[0].msg = conn->error;
399 
400  return 1;
401 }
402 
404 {
405  rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
406 
407  ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
408  if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
409  ERROR("freeing command structure failed");
410 
411  return RLM_SQL_ERROR;
412  }
413  conn->command = NULL;
414  conn->nulls = false;
415 
416  TALLOC_FREE(conn->results);
417 
418  return RLM_SQL_OK;
419 
420 }
421 
422 /** Execute a query when we expected a result set
423  *
424  */
425 static sql_rcode_t sql_select_query(request_t *request, rlm_sql_freetds_conn_t *conn, char const *query)
426 {
427  CS_RETCODE results_ret;
428  CS_INT result_type;
429  CS_DATAFMT descriptor;
430  int i;
431  char **rowdata;
432 
433  if (!conn->db) {
434  ROPTIONAL(RERROR, ERROR, "socket not connected");
435  return RLM_SQL_ERROR;
436  }
437 
438  if (ct_cmd_alloc(conn->db, &conn->command) != CS_SUCCEED) {
439  ROPTIONAL(RERROR, ERROR, "unable to allocate command structure (ct_cmd_alloc())");
440  return RLM_SQL_ERROR;
441  }
442 
443  if (ct_command(conn->command, CS_LANG_CMD, query, CS_NULLTERM, CS_UNUSED) != CS_SUCCEED) {
444  ROPTIONAL(RERROR, ERROR, "unable to initiate command structure (ct_command()");
445  return RLM_SQL_ERROR;
446  }
447 
448  if (ct_send(conn->command) != CS_SUCCEED) {
449  ROPTIONAL(RERROR, ERROR, "unable to send command (ct_send())");
450  return RLM_SQL_ERROR;
451  }
452 
453  results_ret = ct_results(conn->command, &result_type);
454  switch (results_ret) {
455  case CS_SUCCEED:
456  switch (result_type) {
457  case CS_ROW_RESULT:
458 
459  /*
460  * Set up a target buffer for the results data, and associate the buffer with the results,
461  * but the actual fetching takes place in sql_fetch_row.
462  * The layer above MUST call sql_fetch_row and/or sql_finish_select_query
463  * or this socket will be unusable and may cause segfaults
464  * if reused later on.
465  */
466 
467  /*
468  * Set up the DATAFMT structure that describes our target array
469  * and tells freetds what we want future ct_fetch calls to do.
470  */
471  descriptor.datatype = CS_CHAR_TYPE; /* The target buffer is a string */
472  descriptor.format = CS_FMT_NULLTERM; /* Null termination please */
473  descriptor.maxlength = MAX_DATASTR_LEN; /* The string arrays are this large */
474  descriptor.count = 1; /* Fetch one row of data */
475  descriptor.locale = NULL; /* Don't do NLS stuff */
476 
477  if (ct_res_info(conn->command, CS_NUMDATA, &conn->colcount, CS_UNUSED, NULL) != CS_SUCCEED) {
478  ROPTIONAL(RERROR, ERROR, "Error retrieving column count");
479  return RLM_SQL_ERROR;
480  }
481 
482  rowdata = talloc_zero_array(conn, char *, conn->colcount + 1); /* Space for pointers */
483  conn->ind = talloc_zero_array(conn, CS_SMALLINT, conn->colcount);
484 
485  for (i = 0; i < conn->colcount; i++) {
486  /* Space to hold the result data */
487  rowdata[i] = talloc_zero_array(rowdata, char, MAX_DATASTR_LEN + 1);
488 
489  /* Associate the target buffer with the data */
490  if (ct_bind(conn->command, i + 1, &descriptor, rowdata[i], NULL, &conn->ind[i]) != CS_SUCCEED) {
491  talloc_free(rowdata);
492  talloc_free(conn->ind);
493 
494  ROPTIONAL(RERROR, ERROR, "ct_bind() failed)");
495  return RLM_SQL_ERROR;
496  }
497  }
498 
499  rowdata[i] = NULL; /* Terminate the array */
500  conn->results = rowdata;
501  break;
502 
503  case CS_CMD_SUCCEED:
504  case CS_CMD_DONE:
505  ROPTIONAL(RWARN, WARN, "query returned no data");
506  break;
507 
508  default:
509  ROPTIONAL(RERROR, ERROR, "unexpected result type from query");
510  return RLM_SQL_ERROR;
511  }
512  break;
513 
514  case CS_FAIL:
515  /*
516  * Serious failure, freetds requires us to cancel the results and maybe even close the db.
517  */
518 
519  ROPTIONAL(RERROR, ERROR, "failure retrieving query results");
520 
521  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
522  conn->command = NULL;
523 
524  return RLM_SQL_ERROR;
525 
526  default:
527  ROPTIONAL(RERROR, ERROR, "unexpected return value from ct_results()");
528 
529  return RLM_SQL_ERROR;
530  }
531 
532  return RLM_SQL_OK;
533 }
534 
535 CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
536 static void sql_trunk_request_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn,
537  connection_t *conn, UNUSED void *uctx)
538 {
539  rlm_sql_freetds_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_freetds_conn_t);
540  trunk_request_t *treq;
541  request_t *request;
542  fr_sql_query_t *query_ctx;
543 
544  if (trunk_connection_pop_request(&treq, tconn) != 0) return;
545  if (!treq) return;
546 
547  query_ctx = talloc_get_type_abort(treq->preq, fr_sql_query_t);
548  request = query_ctx->request;
549  query_ctx->tconn = tconn;
550 
551  ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query_ctx->query_str);
552 
553  switch (query_ctx->type) {
554  case SQL_QUERY_SELECT:
555  query_ctx->rcode = sql_select_query(request, sql_conn, query_ctx->query_str);
556  break;
557  case SQL_QUERY_OTHER:
558  query_ctx->rcode = sql_query(request, sql_conn, query_ctx->query_str);
559  break;
560  }
561 
562  switch (query_ctx->rcode) {
563  case RLM_SQL_OK:
564  case RLM_SQL_ALT_QUERY:
565  break;
566 
567  default:
570  return;
571  }
572 
573  query_ctx->status = SQL_QUERY_RETURNED;
575  if (request) unlang_interpret_mark_runnable(request);
576 }
577 
579 {
580  rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
581 
582  return (conn->rows_affected);
583 }
584 
585 static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
586 {
587  fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
588  rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
589  CS_INT ret, count;
590  int i;
591 
592  if (conn->nulls) TALLOC_FREE(query_ctx->row);
593  query_ctx->row = NULL;
594 
595  ret = ct_fetch(conn->command, CS_UNUSED, CS_UNUSED, CS_UNUSED, &count);
596  switch (ret) {
597  case CS_FAIL:
598  /*
599  * Serious failure, freetds requires us to cancel the results and maybe even close the db.
600  */
601  ROPTIONAL(RERROR, ERROR, "failure fetching row data");
602  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
603  ROPTIONAL(RERROR, ERROR, "cleaning up");
604  } else {
605  conn->command = NULL;
606  }
607 
608  query_ctx->rcode = RLM_SQL_RECONNECT;
610 
611  case CS_END_DATA:
612  query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
614 
615  case CS_SUCCEED:
616  /*
617  * NULL values are indicated by -1 in the corresponding "indicator"
618  * However, the buffer still exists, so if we have any NULL
619  * returns, we need to copy the results with the NULL
620  * fields left at zero to match behaviour of other drivers.
621  */
622  conn->nulls = false;
623  for (i = 0; i < conn->colcount; i++) {
624  if (conn->ind[i] < 0) {
625  conn->nulls = true;
626  break;
627  }
628  }
629 
630  if (conn->nulls) {
631  query_ctx->row = talloc_zero_array(query_ctx, char *, conn->colcount + 1);
632  for (i = 0; i < conn->colcount; i++) {
633  if (conn->ind[i] < 0) continue;
634  query_ctx->row[i] = talloc_strdup(query_ctx->row, conn->results[i]);
635  }
636  } else {
637  query_ctx->row = conn->results;
638  }
639 
640  query_ctx->rcode = RLM_SQL_OK;
642 
643  case CS_ROW_FAIL:
644  ROPTIONAL(RERROR, ERROR, "recoverable failure fetching row data");
645 
646  query_ctx->rcode = RLM_SQL_RECONNECT;
648 
649  default:
650  ROPTIONAL(RERROR, ERROR, "unexpected returncode from ct_fetch");
651 
652  query_ctx->rcode = RLM_SQL_ERROR;
654  }
655 }
656 
658 {
659  rlm_sql_freetds_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_freetds_conn_t);
660 
661  ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
662  if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
663  ERROR("freeing command structure failed");
664 
665  return RLM_SQL_ERROR;
666  }
667  conn->command = NULL;
668  conn->rows_affected = -1;
669 
670  return RLM_SQL_OK;
671 }
672 
674 {
675  rlm_sql_freetds_conn_t *c = talloc_get_type_abort(h, rlm_sql_freetds_conn_t);
676 
677  DEBUG2("socket destructor called, closing socket");
678 
679  if (c->command) {
680  ct_cancel(NULL, c->command, CS_CANCEL_ALL);
681  if (ct_cmd_drop(c->command) != CS_SUCCEED) {
682  ERROR("freeing command structure failed");
683  return;
684  }
685  }
686 
687  if (c->db) {
688  /*
689  * We first try gracefully closing the connection (which informs the server)
690  * Then if that fails we force the connection closure.
691  *
692  * Sybase docs says this may fail because of pending results, but we
693  * should not have any pending results at this point, so something else must
694  * of gone wrong.
695  */
696  if (ct_close(c->db, CS_UNUSED) != CS_SUCCEED) ct_close(c->db, CS_FORCE_CLOSE);
697 
698  ct_con_drop(c->db);
699  }
700 
701  if (c->context) {
702  ct_exit(c->context, CS_UNUSED);
703  cs_ctx_drop(c->context);
704  }
705 
706  talloc_free(h);
707 }
708 
709 CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
710 static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
711 {
713  rlm_sql_config_t const *config = &sql->config;
715  unsigned int timeout_ms = fr_time_delta_to_msec(config->trunk_conf.conn_conf->connection_timeout);
716  char database[128];
717 
718  MEM(c = talloc_zero(conn, rlm_sql_freetds_conn_t));
719 
720  /*
721  * Allocate a CS context structure. This should really only be done once, but because of
722  * the db pooling design of rlm_sql, we'll have to go with one context per db
723  */
724  if (cs_ctx_alloc(CS_VERSION_100, &c->context) != CS_SUCCEED) {
725  ERROR("unable to allocate CS context structure (cs_ctx_alloc())");
726  error:
727  if (c->error) ERROR("%s", c->error);
729  }
730 
731  /*
732  * Initialize ctlib
733  */
734  if (ct_init(c->context, CS_VERSION_100) != CS_SUCCEED) {
735  ERROR("unable to initialize Client-Library");
736  goto error;
737  }
738 
739  if (ct_config(c->context, CS_SET, CS_LOGIN_TIMEOUT, (CS_VOID *)&timeout_ms, CS_UNUSED, NULL) != CS_SUCCEED) {
740  ERROR("Setting connection timeout failed");
741  goto error;
742  }
743 
744  /*
745  * Install callback functions for error-handling
746  */
747  if (cs_config(c->context, CS_SET, CS_MESSAGE_CB, (CS_VOID *)csmsg_callback, CS_UNUSED, NULL) != CS_SUCCEED) {
748  ERROR("unable to install CS Library error callback");
749  goto error;
750  }
751 
752  if (cs_config(c->context, CS_SET, CS_USERDATA, (CS_VOID *)&c, sizeof(c), NULL) != CS_SUCCEED) {
753  ERROR("unable to set userdata pointer");
754  goto error;
755  }
756 
757  if (ct_callback(c->context, NULL, CS_SET, CS_CLIENTMSG_CB, (CS_VOID *)clientmsg_callback) != CS_SUCCEED) {
758  ERROR("unable to install client message callback");
759  goto error;
760  }
761 
762  if (ct_callback(c->context, NULL, CS_SET, CS_SERVERMSG_CB, (CS_VOID *)servermsg_callback) != CS_SUCCEED) {
763  ERROR("unable to install server message callback");
764  goto error;
765  }
766 
767  /*
768  * Allocate a ctlib db structure
769  */
770  if (ct_con_alloc(c->context, &c->db) != CS_SUCCEED) {
771  ERROR("unable to allocate db structure");
772  goto error;
773  }
774 
775  /*
776  * Set User and Password properties for the db
777  */
778  if (ct_con_props(c->db, CS_SET, CS_USERNAME,
779  UNCONST(CS_VOID *, config->sql_login), strlen(config->sql_login), NULL) != CS_SUCCEED) {
780  ERROR("unable to set username for db");
781  goto error;
782  }
783 
784  if (ct_con_props(c->db, CS_SET, CS_PASSWORD,
785  UNCONST(CS_VOID *, config->sql_password), strlen(config->sql_password), NULL) != CS_SUCCEED) {
786  ERROR("unable to set password for db");
787  goto error;
788  }
789 
790  /*
791  * Connect to the database
792  */
793  if (ct_connect(c->db, UNCONST(CS_CHAR *, config->sql_server), strlen(config->sql_server)) != CS_SUCCEED) {
794  ERROR("unable to establish db to symbolic servername %s",
795  config->sql_server);
796  goto error;
797  }
798 
799  /*
800  * There doesn't appear to be a way to set the database with the API, so use an
801  * sql statement when we first open the connection.
802  */
803  snprintf(database, sizeof(database), "USE %s;", config->sql_db);
804  if (sql_query(NULL, c, database) != RLM_SQL_OK) goto error;
805 
806  *h = c;
808 }
809 
811 
813 
814 static void sql_request_fail(request_t *request, void *preq, UNUSED void *rctx,
816 {
817  fr_sql_query_t *query_ctx = talloc_get_type_abort(preq, fr_sql_query_t);
818 
819  query_ctx->treq = NULL;
820  if (query_ctx->rcode == RLM_SQL_OK) query_ctx->rcode = RLM_SQL_ERROR;
821  if (request) unlang_interpret_mark_runnable(request);
822 }
823 
824 /* Exported to rlm_sql */
827  .common = {
828  .magic = MODULE_MAGIC_INIT,
829  .name = "sql_freetds"
830  },
832  .sql_query_resume = sql_query_resume,
833  .sql_select_query_resume = sql_query_resume,
834  .sql_fields = sql_fields,
835  .sql_affected_rows = sql_affected_rows,
836  .sql_fetch_row = sql_fetch_row,
837  .sql_error = sql_error,
838  .sql_finish_query = sql_finish_query,
839  .sql_finish_select_query = sql_finish_select_query,
840  .uses_trunks = true,
841  .trunk_io_funcs = {
842  .connection_alloc = sql_trunk_connection_alloc,
843  .request_mux = sql_trunk_request_mux,
844  .request_fail = sql_request_fail
845  }
846 };
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:165
#define RCSID(id)
Definition: build.h:481
#define NDEBUG_UNUSED
Definition: build.h:324
#define UNUSED
Definition: build.h:313
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
fr_dcursor_eval_t void const * uctx
Definition: dcursor.h:546
#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 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
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:146
trunk_connection_t * tconn
Trunk connection this query is being run on.
Definition: rlm_sql.h:142
fr_sql_query_type_t type
Type of query.
Definition: rlm_sql.h:145
char const * query_str
Query string to run.
Definition: rlm_sql.h:144
request_t * request
Request this query relates to.
Definition: rlm_sql.h:139
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:121
@ SQL_QUERY_OTHER
Definition: rlm_sql.h:122
#define RLM_SQL_RCODE_FLAGS_ALT_QUERY
Can distinguish between other errors and those.
Definition: rlm_sql.h:172
rlm_sql_row_t row
Row data from the last query.
Definition: rlm_sql.h:148
sql_rcode_t rcode
Result code.
Definition: rlm_sql.h:147
trunk_request_t * treq
Trunk request for this query.
Definition: rlm_sql.h:143
@ SQL_QUERY_RETURNED
Query has executed.
Definition: rlm_sql.h:131
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 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.
CC_NO_UBSAN(function)
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
Definition: rlm_sql_trunk.h:56
#define SQL_TRUNK_CONNECTION_ALLOC
Allocate an SQL trunk connection.
Definition: rlm_sql_trunk.h:35
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
Definition: connection.c:1167
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:689
return count
Definition: module.c:163
RETURN_MODULE_FAIL
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:204
rlm_sql_config_t config
Definition: rlm_sql.h:238
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:617
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:2120
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:3861
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:2065
Associates request queues with a connection.
Definition: trunk.c:131
Wraps a normal request.
Definition: trunk.c:97
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