All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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: 2932e61d65e98e34ede0b62bb0ccc1de4028690f $
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: 2932e61d65e98e34ede0b62bb0ccc1de4028690f $")
28 
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/rad_assert.h>
31 
32 #include <sys/stat.h>
33 
34 #include <ctpublic.h>
35 
36 #include "rlm_sql.h"
37 
38 typedef struct rlm_sql_freetds_conn {
39  CS_CONTEXT *context; //!< Structure FreeTDS uses to avoid creating globals.
40  CS_CONNECTION *db; //!< Handle specifying a single connection to the database.
41  CS_COMMAND *command; //!< A prepared statement.
42  char **results; //!< Result strings from statement execution.
43  char *error; //!< The last error string created by one of the call backs.
44  bool established; //!< Set to false once the connection has been properly established.
46 
47 #define MAX_DATASTR_LEN 256
48 
49 /** Client-Library error handler
50  *
51  * Callback for any errors raised by the Client-Library. Will overwrite any previous errors associated
52  * with a connection.
53  *
54  * @param context The FreeTDS library context.
55  * @param conn DB connection handle.
56  * @param emsgp Pointer to the error structure.
57  * @return CS_SUCCEED
58  */
59 static CS_RETCODE CS_PUBLIC clientmsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_CLIENTMSG *emsgp)
60 {
61  rlm_sql_freetds_conn_t *this = NULL;
62  int len = 0;
63 
64  /*
65  * Not actually an error, but the client wanted to tell us something...
66  */
67  if (emsgp->severity == CS_SV_INFORM) {
68  INFO("rlm_sql_freetds: %s", emsgp->msgstring);
69 
70  return CS_SUCCEED;
71  }
72 
73  if ((cs_config(context, CS_GET, CS_USERDATA, &this, sizeof(this), &len) != CS_SUCCEED) || !this) {
74  ERROR("rlm_sql_freetds: failed retrieving context userdata");
75 
76  return CS_SUCCEED;
77  }
78 
79  if (this->error) TALLOC_FREE(this->error);
80 
81  this->error = talloc_typed_asprintf(this, "client error: severity(%ld), number(%ld), origin(%ld), layer(%ld): %s",
82  (long)CS_SEVERITY(emsgp->severity), (long)CS_NUMBER(emsgp->msgnumber),
83  (long)CS_ORIGIN(emsgp->msgnumber), (long)CS_LAYER(emsgp->msgnumber),
84  emsgp->msgstring);
85 
86  if (emsgp->osstringlen > 0) {
87  this->error = talloc_asprintf_append(this->error, ". os error: number(%ld): %s",
88  (long)emsgp->osnumber, emsgp->osstring);
89  }
90 
91  return CS_SUCCEED;
92 }
93 
94 /** Client error handler
95  *
96  * Callback for any errors raised by the client. Will overwrite any previous errors associated
97  * with a connection.
98  *
99  * @param context The FreeTDS library context.
100  * @param emsgp Pointer to the error structure.
101  * @return CS_SUCCEED
102  */
103 static CS_RETCODE CS_PUBLIC csmsg_callback(CS_CONTEXT *context, CS_CLIENTMSG *emsgp)
104 {
105  rlm_sql_freetds_conn_t *this = NULL;
106  int len = 0;
107 
108  /*
109  * Not actually an error, but the client wanted to tell us something...
110  */
111  if (emsgp->severity == CS_SV_INFORM) {
112  INFO("rlm_sql_freetds: %s", emsgp->msgstring);
113 
114  return CS_SUCCEED;
115  }
116 
117  if ((cs_config(context, CS_GET, CS_USERDATA, &this, sizeof(this), &len) != CS_SUCCEED) || !this) {
118  ERROR("rlm_sql_freetds: failed retrieving context userdata");
119 
120  return CS_SUCCEED;
121  }
122 
123  if (this->error) TALLOC_FREE(this->error);
124 
125  this->error = talloc_typed_asprintf(this, "cs error: severity(%ld), number(%ld), origin(%ld), layer(%ld): %s",
126  (long)CS_SEVERITY(emsgp->severity), (long)CS_NUMBER(emsgp->msgnumber),
127  (long)CS_ORIGIN(emsgp->msgnumber), (long)CS_LAYER(emsgp->msgnumber),
128  emsgp->msgstring);
129 
130  if (emsgp->osstringlen > 0) {
131  this->error = talloc_asprintf_append(this->error, ". os error: number(%ld): %s",
132  (long)emsgp->osnumber, emsgp->osstring);
133  }
134 
135  return CS_SUCCEED;
136 }
137 
138 /** Server error handler
139  *
140  * Callback for any messages sent back from the server.
141  *
142  * There's no standard categorisation of messages sent back from the server, so we don't know they're errors,
143  * the only thing we can do is write them to the long as informational messages.
144  *
145  * @param context The FreeTDS library context.
146  * @param conn DB connection handle.
147  * @param msgp Pointer to the error structure.
148  * @return CS_SUCCEED
149  */
150 static CS_RETCODE CS_PUBLIC servermsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_SERVERMSG *msgp)
151 {
152  rlm_sql_freetds_conn_t *this = NULL;
153  int len = 0;
154 
155  if ((cs_config(context, CS_GET, CS_USERDATA, &this, sizeof(this), &len) != CS_SUCCEED) || !this) {
156  ERROR("rlm_sql_freetds: failed retrieving context userdata");
157 
158  return CS_SUCCEED;
159  }
160 
161  /*
162  * Because apparently there are no standard severity levels *brilliant*
163  */
164  if (this->established) {
165  INFO("rlm_sql_freetds: server msg from \"%s\": severity(%ld), number(%ld), origin(%ld), "
166  "layer(%ld), procedure \"%s\": %s",
167  (msgp->svrnlen > 0) ? msgp->svrname : "unknown",
168  (long)msgp->msgnumber, (long)msgp->severity, (long)msgp->state, (long)msgp->line,
169  (msgp->proclen > 0) ? msgp->proc : "none", msgp->text);
170  } else {
171  if (this->error) TALLOC_FREE(this->error);
172 
173  this->error = talloc_typed_asprintf(this, "Server msg from \"%s\": severity(%ld), number(%ld), "
174  "origin(%ld), layer(%ld), procedure \"%s\": %s",
175  (msgp->svrnlen > 0) ? msgp->svrname : "unknown",
176  (long)msgp->msgnumber, (long)msgp->severity, (long)msgp->state,
177  (long)msgp->line,
178  (msgp->proclen > 0) ? msgp->proc : "none", msgp->text);
179  }
180 
181  return CS_SUCCEED;
182 }
183 
184 /*************************************************************************
185  *
186  * Function: sql_query
187  *
188  * Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
189  * the database.
190  *
191  *************************************************************************/
192 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
193 {
194  rlm_sql_freetds_conn_t *conn = handle->conn;
195 
196  CS_RETCODE results_ret;
197  CS_INT result_type;
198 
199  if (ct_cmd_alloc(conn->db, &conn->command) != CS_SUCCEED) {
200  ERROR("rlm_sql_freetds: Unable to allocate command structure (ct_cmd_alloc())");
201 
202  return RLM_SQL_ERROR;
203  }
204 
205  if (ct_command(conn->command, CS_LANG_CMD, query, CS_NULLTERM, CS_UNUSED) != CS_SUCCEED) {
206  ERROR("rlm_sql_freetds: Unable to initialise command structure (ct_command())");
207 
208  return RLM_SQL_ERROR;
209  }
210 
211  if (ct_send(conn->command) != CS_SUCCEED) {
212  ERROR("rlm_sql_freetds: Unable to send command (ct_send())");
213 
214  return RLM_SQL_ERROR;
215  }
216 
217  /*
218  * We'll make three calls to ct_results, first to get a success indicator, secondly to get a
219  * done indicator, and thirdly to get a "nothing left to handle" status.
220  */
221 
222  /*
223  * First call to ct_results, we need returncode CS_SUCCEED and result_type CS_CMD_SUCCEED.
224  */
225  if ((results_ret = ct_results(conn->command, &result_type)) == CS_SUCCEED) {
226  if (result_type != CS_CMD_SUCCEED) {
227  if (result_type == CS_ROW_RESULT) {
228  ERROR("rlm_sql_freetds: sql_query processed a query returning rows. "
229  "Use sql_select_query instead!");
230  }
231  ERROR("rlm_sql_freetds: Result failure or unexpected result type from query");
232 
233  return RLM_SQL_ERROR;
234  }
235  } else {
236  switch (results_ret) {
237  case CS_FAIL: /* Serious failure, freetds requires us to cancel and maybe even close db */
238  ERROR("rlm_sql_freetds: Failure retrieving query results");
239 
240  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
241  INFO("rlm_sql_freetds: Cleaning up");
242 
243  return RLM_SQL_RECONNECT;
244  }
245  conn->command = NULL;
246 
247  return RLM_SQL_ERROR;
248  default:
249  ERROR("rlm_sql_freetds: Unexpected return value from ct_results()");
250 
251  return RLM_SQL_ERROR;
252  }
253  }
254 
255  /*
256  * Second call to ct_results, we need returncode CS_SUCCEED
257  * and result_type CS_CMD_DONE.
258  */
259  if ((results_ret = ct_results(conn->command, &result_type)) == CS_SUCCEED) {
260  if (result_type != CS_CMD_DONE) {
261  ERROR("rlm_sql_freetds: Result failure or unexpected result type from query");
262 
263  return RLM_SQL_ERROR;
264  }
265  } else {
266  switch (results_ret) {
267  case CS_FAIL: /* Serious failure, freetds requires us to cancel and maybe even close db */
268  ERROR("rlm_sql_freetds: Failure retrieving query results");
269  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
270 
271  conn->command = NULL;
272  return RLM_SQL_ERROR;
273 
274  default:
275  ERROR("rlm_sql_freetds: Unexpected return value from ct_results()");
276 
277  return RLM_SQL_ERROR;
278  }
279  }
280 
281  /*
282  * Third call to ct_results, we need returncode CS_END_RESULTS result_type will be ignored.
283  */
284  results_ret = ct_results(conn->command, &result_type);
285  switch (results_ret) {
286  case CS_FAIL: /* Serious failure, freetds requires us to cancel and maybe even close db */
287  ERROR("rlm_sql_freetds: Failure retrieving query results");
288  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) return RLM_SQL_RECONNECT;
289  conn->command = NULL;
290 
291  return RLM_SQL_ERROR;
292 
293  case CS_END_RESULTS: /* This is where we want to end up */
294  break;
295 
296  default:
297  ERROR("rlm_sql_freetds: Unexpected return value from ct_results()");
298 
299  return RLM_SQL_ERROR;
300  }
301 
302  return RLM_SQL_OK;
303 }
304 
305 /*************************************************************************
306  *
307  * Function: sql_num_fields
308  *
309  * Purpose: database specific num_fields function. Returns number
310  * of columns from query
311  *
312  *************************************************************************/
314 {
315  rlm_sql_freetds_conn_t *conn = handle->conn;
316  int num = 0;
317 
318  if (ct_res_info(conn->command, CS_NUMDATA, (CS_INT *)&num, CS_UNUSED, NULL) != CS_SUCCEED) {
319  ERROR("rlm_sql_freetds: Error retrieving column count");
320 
321  return RLM_SQL_ERROR;
322  }
323 
324  return num;
325 }
326 
327 /*************************************************************************
328  *
329  * Function: sql_fields
330  *
331  * Purpose: Return name of regular result columns.
332  *
333  *************************************************************************/
334 static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
335 {
336  rlm_sql_freetds_conn_t *conn = handle->conn;
337  CS_DATAFMT datafmt;
338  int fields, i;
339  char const **names;
340 
341  /* Get number of elements in row result */
342  if (ct_res_info(conn->command, CS_NUMDATA, (CS_INT *)&fields, CS_UNUSED, NULL) != CS_SUCCEED) {
343  ERROR("rlm_sql_freetds: sql_fields() Error retrieving column count");
344 
345  return RLM_SQL_ERROR;
346  }
347 
348  if (fields <= 0) return RLM_SQL_ERROR;
349 
350  MEM(names = talloc_array(handle, char const *, fields));
351 
352  for (i = 0; i < fields; i++) {
353  int col = i + 1;
354  char *p;
355 
356  /*
357  ** Get the column description. ct_describe() fills the
358  ** datafmt parameter with a description of the column.
359  */
360  if (ct_describe(conn->command, col, &datafmt) != CS_SUCCEED) {
361  ERROR("rlm_sql_freetds: sql_fields() Problems with ct_describe(), column %d", col);
362  talloc_free(names);
363  return RLM_SQL_ERROR;
364  }
365 
366  if (datafmt.namelen > 0) {
367  MEM(p = talloc_array(names, char, (size_t)datafmt.namelen + 1));
368  strlcpy(p, datafmt.name, (size_t)datafmt.namelen + 1);
369  names[i] = p;
370  }
371  }
372 
373  *out = names;
374 
375  return RLM_SQL_OK;
376 }
377 
378 /** Retrieves any errors associated with the connection handle
379  *
380  * @note Caller will free any memory allocated in ctx.
381  *
382  * @param ctx to allocate temporary error buffers in.
383  * @param out Array of sql_log_entrys to fill.
384  * @param outlen Length of out array.
385  * @param handle rlm_sql connection handle.
386  * @param config rlm_sql config.
387  * @return number of errors written to the #sql_log_entry_t array.
388  */
389 static size_t sql_error(UNUSED TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
390  rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
391 {
392  rlm_sql_freetds_conn_t *conn = handle->conn;
393 
394  rad_assert(conn && conn->db);
395  rad_assert(outlen > 0);
396 
397  if (!conn->error) return 0;
398 
399  out[0].type = L_ERR;
400  out[0].msg = conn->error;
401 
402  return 1;
403 }
404 
406 {
407  rlm_sql_freetds_conn_t *conn = handle->conn;
408 
409  ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
410  if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
411  ERROR("rlm_sql_freetds: freeing command structure failed");
412 
413  return RLM_SQL_ERROR;
414  }
415  conn->command = NULL;
416 
417  TALLOC_FREE(conn->results);
418 
419  return RLM_SQL_OK;
420 
421 }
422 
423 /** Execute a query when we expected a result set
424  *
425  * @note Only the first row from queries returning several rows will be returned by this function,
426  * consecutive rows will be discarded.
427  *
428  */
429 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
430 {
431  rlm_sql_freetds_conn_t *conn = handle->conn;
432 
433  CS_RETCODE results_ret;
434  CS_INT result_type;
435  CS_DATAFMT descriptor;
436 
437  int colcount,i;
438  char **rowdata;
439 
440  if (!conn->db) {
441  ERROR("rlm_sql_freetds: socket not connected");
442 
443  return RLM_SQL_ERROR;
444  }
445 
446  if (ct_cmd_alloc(conn->db, &conn->command) != CS_SUCCEED) {
447  ERROR("rlm_sql_freetds: unable to allocate command structure (ct_cmd_alloc())");
448 
449  return RLM_SQL_ERROR;
450  }
451 
452  if (ct_command(conn->command, CS_LANG_CMD, query, CS_NULLTERM, CS_UNUSED) != CS_SUCCEED) {
453  ERROR("rlm_sql_freetds: unable to initiate command structure (ct_command()");
454 
455  return RLM_SQL_ERROR;
456  }
457 
458  if (ct_send(conn->command) != CS_SUCCEED) {
459  ERROR("rlm_sql_freetds: unable to send command (ct_send())");
460  return RLM_SQL_ERROR;
461  }
462 
463  results_ret = ct_results(conn->command, &result_type);
464  switch (results_ret) {
465  case CS_SUCCEED:
466  switch (result_type) {
467  case CS_ROW_RESULT:
468 
469  /*
470  * Set up a target buffer for the results data, and associate the buffer with the results,
471  * but the actual fetching takes place in sql_fetch_row.
472  * The layer above MUST call sql_fetch_row and/or sql_finish_select_query
473  * or this socket will be unusable and may cause segfaults
474  * if reused later on.
475  */
476 
477  /*
478  * Set up the DATAFMT structure that describes our target array
479  * and tells freetds what we want future ct_fetch calls to do.
480  */
481  descriptor.datatype = CS_CHAR_TYPE; /* The target buffer is a string */
482  descriptor.format = CS_FMT_NULLTERM; /* Null termination please */
483  descriptor.maxlength = MAX_DATASTR_LEN; /* The string arrays are this large */
484  descriptor.count = 1; /* Fetch one row of data */
485  descriptor.locale = NULL; /* Don't do NLS stuff */
486 
487  colcount = sql_num_fields(handle, config); /* Get number of elements in row result */
488 
489  rowdata = talloc_zero_array(conn, char *, colcount + 1); /* Space for pointers */
490  rowdata[colcount] = NULL;
491 
492  for (i = 0; i < colcount; i++) {
493  /* Space to hold the result data */
494  rowdata[i] = talloc_array(rowdata, char, MAX_DATASTR_LEN + 1);
495 
496  /* Associate the target buffer with the data */
497  if (ct_bind(conn->command, i + 1, &descriptor, rowdata[i], NULL, NULL) != CS_SUCCEED) {
498  talloc_free(rowdata);
499 
500  ERROR("rlm_sql_freetds: ct_bind() failed)");
501 
502  return RLM_SQL_ERROR;
503  }
504 
505  }
506 
507  rowdata[i] = NULL; /* Terminate the array */
508  conn->results = rowdata;
509  break;
510 
511  case CS_CMD_SUCCEED:
512  case CS_CMD_DONE:
513  ERROR("rlm_sql_freetds: query returned no data");
514  break;
515 
516  default:
517 
518  ERROR("rlm_sql_freetds: unexpected result type from query");
519  sql_finish_select_query(handle, config);
520 
521  return RLM_SQL_ERROR;
522  }
523  break;
524 
525  case CS_FAIL:
526 
527  /*
528  * Serious failure, freetds requires us to cancel the results and maybe even close the db.
529  */
530 
531  ERROR("rlm_sql_freetds: failure retrieving query results");
532 
533  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
534  ERROR("rlm_sql_freetds: cleaning up");
535 
536  return RLM_SQL_RECONNECT;
537  }
538  conn->command = NULL;
539 
540  return RLM_SQL_ERROR;
541 
542  default:
543  ERROR("rlm_sql_freetds: unexpected return value from ct_results()");
544 
545  return RLM_SQL_ERROR;
546  }
547 
548  return RLM_SQL_OK;
549 }
550 
552 {
553  rlm_sql_freetds_conn_t *conn = handle->conn;
554  int num;
555 
556  if (ct_res_info(conn->command, CS_ROW_COUNT, (CS_INT *)&num, CS_UNUSED, NULL) != CS_SUCCEED) {
557  ERROR("rlm_sql_freetds: error retrieving row count");
558 
559  return RLM_SQL_ERROR;
560  }
561 
562  return num;
563 }
564 
566 {
567  rlm_sql_freetds_conn_t *conn = handle->conn;
568  CS_INT ret, count;
569 
570  *out = NULL;
571  handle->row = NULL;
572 
573  ret = ct_fetch(conn->command, CS_UNUSED, CS_UNUSED, CS_UNUSED, &count);
574  switch (ret) {
575  case CS_FAIL:
576  /*
577  * Serious failure, freetds requires us to cancel the results and maybe even close the db.
578  */
579  ERROR("rlm_sql_freetds: failure fetching row data");
580  if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
581  ERROR("rlm_sql_freetds: cleaning up");
582  } else {
583  conn->command = NULL;
584  }
585 
586  return RLM_SQL_RECONNECT;
587 
588  case CS_END_DATA:
589  return RLM_SQL_OK;
590 
591  case CS_SUCCEED:
592  *out = handle->row = conn->results;
593 
594  return RLM_SQL_OK;
595 
596  case CS_ROW_FAIL:
597  ERROR("rlm_sql_freetds: recoverable failure fetching row data");
598 
599  return RLM_SQL_RECONNECT;
600 
601  default:
602  ERROR("rlm_sql_freetds: unexpected returncode from ct_fetch");
603 
604  return RLM_SQL_ERROR;
605  }
606 }
607 
609 {
610 
611  /*
612  * Not implemented, never called from rlm_sql anyway result buffer is freed in the
613  * finish_query functions.
614  */
615  return RLM_SQL_OK;
616 
617 }
618 
620 {
621  rlm_sql_freetds_conn_t *conn = handle->conn;
622 
623  ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
624  if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
625  ERROR("rlm_sql_freetds: freeing command structure failed");
626 
627  return RLM_SQL_ERROR;
628  }
629  conn->command = NULL;
630 
631  return RLM_SQL_OK;
632 }
633 
635 {
636  return sql_num_rows(handle, config);
637 }
638 
639 
641 {
642  DEBUG2("rlm_sql_freetds: socket destructor called, closing socket");
643 
644  if (conn->command) {
645  ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
646  if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
647  ERROR("rlm_sql_freetds: freeing command structure failed");
648 
649  return RLM_SQL_ERROR;
650  }
651  }
652 
653  if (conn->db) {
654  /*
655  * We first try gracefully closing the connection (which informs the server)
656  * Then if that fails we force the connection closure.
657  *
658  * Sybase docs says this may fail because of pending results, but we
659  * should not have any pending results at this point, so something else must
660  * of gone wrong.
661  */
662  if (ct_close(conn->db, CS_UNUSED) != CS_SUCCEED) {
663  ct_close(conn->db, CS_FORCE_CLOSE);
664  }
665 
666  ct_con_drop(conn->db);
667  }
668 
669  if (conn->context) {
670  ct_exit(conn->context, CS_UNUSED);
671  cs_ctx_drop(conn->context);
672  }
673 
674  return RLM_SQL_OK;
675 }
676 
677 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config, struct timeval const *timeout)
678 {
680  unsigned int timeout_ms = FR_TIMEVAL_TO_MS(timeout);
681 
682  MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_freetds_conn_t));
683  talloc_set_destructor(conn, _sql_socket_destructor);
684 
685  /*
686  * Allocate a CS context structure. This should really only be done once, but because of
687  * the db pooling design of rlm_sql, we'll have to go with one context per db
688  */
689  if (cs_ctx_alloc(CS_VERSION_100, &conn->context) != CS_SUCCEED) {
690  ERROR("rlm_sql_freetds: unable to allocate CS context structure (cs_ctx_alloc())");
691 
692  goto error;
693  }
694 
695  /*
696  * Initialize ctlib
697  */
698  if (ct_init(conn->context, CS_VERSION_100) != CS_SUCCEED) {
699  ERROR("rlm_sql_freetds: unable to initialize Client-Library");
700 
701  goto error;
702  }
703 
704  if (ct_config(conn->context, CS_GET, CS_LOGIN_TIMEOUT, (CS_VOID *)&timeout_ms, CS_UNUSED, NULL) != CS_SUCCEED) {
705  ERROR("rlm_sql_freetds: Setting connection timeout failed");
706 
707  goto error;
708  }
709 
710  /*
711  * Install callback functions for error-handling
712  */
713  if (cs_config(conn->context, CS_SET, CS_MESSAGE_CB, (CS_VOID *)csmsg_callback, CS_UNUSED, NULL) != CS_SUCCEED) {
714  ERROR("rlm_sql_freetds: unable to install CS Library error callback");
715 
716  goto error;
717  }
718 
719  if (cs_config(conn->context, CS_SET, CS_USERDATA,
720  (CS_VOID *)&handle->conn, sizeof(handle->conn), NULL) != CS_SUCCEED) {
721  ERROR("rlm_sql_freetds: unable to set userdata pointer");
722 
723  goto error;
724  }
725 
726  if (ct_callback(conn->context, NULL, CS_SET, CS_CLIENTMSG_CB, (CS_VOID *)clientmsg_callback) != CS_SUCCEED) {
727  ERROR("rlm_sql_freetds: unable to install client message callback");
728 
729  goto error;
730  }
731 
732  if (ct_callback(conn->context, NULL, CS_SET, CS_SERVERMSG_CB, (CS_VOID *)servermsg_callback) != CS_SUCCEED) {
733  ERROR("rlm_sql_freetds: unable to install server message callback");
734 
735  goto error;
736  }
737 
738  /*
739  * Allocate a ctlib db structure
740  */
741  if (ct_con_alloc(conn->context, &conn->db) != CS_SUCCEED) {
742  ERROR("rlm_sql_freetds: unable to allocate db structure");
743 
744  goto error;
745  }
746 
747  /*
748  * Set User and Password properties for the db
749  */
750  {
751  CS_VOID *login, *password;
752  CS_CHAR *server;
753  char database[128];
754 
755  memcpy(&login, &config->sql_login, sizeof(login));
756  if (ct_con_props(conn->db, CS_SET, CS_USERNAME, login, strlen(config->sql_login), NULL) != CS_SUCCEED) {
757  ERROR("rlm_sql_freetds: unable to set username for db");
758 
759  goto error;
760  }
761 
762  memcpy(&password, &config->sql_password, sizeof(password));
763  if (ct_con_props(conn->db, CS_SET, CS_PASSWORD,
764  password, strlen(config->sql_password), NULL) != CS_SUCCEED) {
765  ERROR("rlm_sql_freetds: unable to set password for db");
766 
767  goto error;
768  }
769 
770  /*
771  * Connect to the database
772  */
773  memcpy(&server, &config->sql_server, sizeof(server));
774  if (ct_connect(conn->db, server, strlen(config->sql_server)) != CS_SUCCEED) {
775  ERROR("rlm_sql_freetds: unable to establish db to symbolic servername %s",
776  config->sql_server);
777 
778  goto error;
779  }
780 
781  /*
782  * There doesn't appear to be a way to set the database with the API, so use an
783  * sql statement when we first open the connection.
784  */
785  snprintf(database, sizeof(database), "USE %s;", config->sql_db);
786  if (sql_query(handle, config, database) != RLM_SQL_OK) {
787  goto error;
788  }
789 
790  sql_finish_query(handle, config);
791  }
792 
793  return RLM_SQL_OK;
794 
795 error:
796  if (conn->context) {
797  sql_log_entry_t error;
798 
799  if (sql_error(NULL, &error, 1, handle, config) > 0) ERROR("rlm_sql_freetds: %s", error.msg);
800  }
801 
802  return RLM_SQL_ERROR;
803 }
804 
805 /* Exported to rlm_sql */
807 rlm_sql_module_t rlm_sql_freetds = {
808  .name = "rlm_sql_freetds",
809  .sql_socket_init = sql_socket_init,
810  .sql_query = sql_query,
811  .sql_select_query = sql_select_query,
812  .sql_num_fields = sql_num_fields,
813  .sql_num_rows = sql_num_rows,
814  .sql_fields = sql_fields,
815  .sql_affected_rows = sql_affected_rows,
816  .sql_fetch_row = sql_fetch_row,
817  .sql_free_result = sql_free_result,
818  .sql_error = sql_error,
819  .sql_finish_query = sql_finish_query,
820  .sql_finish_select_query = sql_finish_select_query
821 };
static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
Execute a query when we expected a result set.
General connection/server error.
Definition: rlm_sql.h:46
static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
static sql_rcode_t sql_free_result(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
Prototypes and functions for the SQL module.
char ** rlm_sql_row_t
Definition: rlm_sql.h:59
log_type_t type
Type of log entry L_ERR, L_WARN, L_INFO, L_DBG etc..
Definition: rlm_sql.h:62
#define MEM(x)
Definition: radiusd.h:396
#define INFO(fmt,...)
Definition: log.h:143
static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
char const * msg
Log message.
Definition: rlm_sql.h:63
#define UNUSED
Definition: libradius.h:134
Error message.
Definition: log.h:36
char const * sql_server
Server to connect to.
Definition: rlm_sql.h:85
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
static float timeout
Definition: radclient.c:43
bool established
Set to false once the connection has been properly established.
static sql_rcode_t sql_fetch_row(rlm_sql_row_t *out, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
#define rad_assert(expr)
Definition: rad_assert.h:38
Stale connection, should reconnect.
Definition: rlm_sql.h:48
char const * name
Definition: rlm_sql.h:191
sql_rcode_t
Definition: rlm_sql.h:44
#define DEBUG2(fmt,...)
Definition: log.h:176
Definition: rlm_sql.h:61
CS_CONNECTION * db
Handle specifying a single connection to the database.
CS_CONTEXT * context
Structure FreeTDS uses to avoid creating globals.
Success.
Definition: rlm_sql.h:47
static size_t sql_error(UNUSED TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
Retrieves any errors associated with the connection handle.
char * talloc_typed_asprintf(void const *t, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition: missing.c:611
char const * sql_password
Login password to use.
Definition: rlm_sql.h:88
void * conn
Database specific connection handle.
Definition: rlm_sql.h:153
char * error
The last error string created by one of the call backs.
static CS_RETCODE CS_PUBLIC csmsg_callback(CS_CONTEXT *context, CS_CLIENTMSG *emsgp)
Client error handler.
rlm_sql_row_t row
Row data from the last query.
Definition: rlm_sql.h:154
struct rlm_sql_freetds_conn rlm_sql_freetds_conn_t
static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
char const * sql_login
Login credentials to use.
Definition: rlm_sql.h:87
char ** results
Result strings from statement execution.
static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
static CS_RETCODE CS_PUBLIC servermsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_SERVERMSG *msgp)
Server error handler.
char const * sql_db
Database to run queries against.
Definition: rlm_sql.h:89
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
#define MAX_DATASTR_LEN
static CS_RETCODE CS_PUBLIC clientmsg_callback(CS_CONTEXT *context, UNUSED CS_CONNECTION *conn, CS_CLIENTMSG *emsgp)
Client-Library error handler.
#define FR_TIMEVAL_TO_MS(_x)
Definition: conffile.h:235
CS_COMMAND * command
A prepared statement.
#define RCSID(id)
Definition: build.h:135
static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config, struct timeval const *timeout)
#define ERROR(fmt,...)
Definition: log.h:145
rlm_sql_module_t rlm_sql_freetds
static int _sql_socket_destructor(rlm_sql_freetds_conn_t *conn)