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: 9e62e6230c722e0d690241ded5b897192597c787 $
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: 9e62e6230c722e0d690241ded5b897192597c787 $")
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.
45 } rlm_sql_freetds_conn_t;
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_CUCCEED
58 */
clientmsg_callback(CS_CONTEXT * context,UNUSED CS_CONNECTION * conn,CS_CLIENTMSG * emsgp)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 */
csmsg_callback(CS_CONTEXT * context,CS_CLIENTMSG * emsgp)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 */
servermsg_callback(CS_CONTEXT * context,UNUSED CS_CONNECTION * conn,CS_SERVERMSG * msgp)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 *************************************************************************/
sql_query(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config,char const * query)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 *************************************************************************/
sql_num_fields(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)313 static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
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 /** Retrieves any errors associated with the connection handle
328 *
329 * @note Caller will free any memory allocated in ctx.
330 *
331 * @param ctx to allocate temporary error buffers in.
332 * @param out Array of sql_log_entrys to fill.
333 * @param outlen Length of out array.
334 * @param handle rlm_sql connection handle.
335 * @param config rlm_sql config.
336 * @return number of errors written to the sql_log_entry array.
337 */
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)338 static size_t sql_error(UNUSED TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
339 rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
340 {
341 rlm_sql_freetds_conn_t *conn = handle->conn;
342
343 rad_assert(conn && conn->db);
344 rad_assert(outlen > 0);
345
346 if (!conn->error) return 0;
347
348 out[0].type = L_ERR;
349 out[0].msg = conn->error;
350
351 return 1;
352 }
353
sql_finish_select_query(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)354 static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
355 {
356 rlm_sql_freetds_conn_t *conn = handle->conn;
357
358 ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
359 if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
360 ERROR("rlm_sql_freetds: freeing command structure failed");
361
362 return RLM_SQL_ERROR;
363 }
364 conn->command = NULL;
365
366 TALLOC_FREE(conn->results);
367
368 return RLM_SQL_OK;
369
370 }
371
372 /** Execute a query when we expected a result set
373 *
374 * @note Only the first row from queries returning several rows will be returned by this function,
375 * consecutive rows will be discarded.
376 *
377 */
sql_select_query(rlm_sql_handle_t * handle,rlm_sql_config_t * config,char const * query)378 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
379 {
380 rlm_sql_freetds_conn_t *conn = handle->conn;
381
382 CS_RETCODE results_ret;
383 CS_INT result_type;
384 CS_DATAFMT descriptor;
385
386 int colcount,i;
387 char **rowdata;
388
389 if (!conn->db) {
390 ERROR("rlm_sql_freetds: socket not connected");
391
392 return RLM_SQL_ERROR;
393 }
394
395 if (ct_cmd_alloc(conn->db, &conn->command) != CS_SUCCEED) {
396 ERROR("rlm_sql_freetds: unable to allocate command structure (ct_cmd_alloc())");
397
398 return RLM_SQL_ERROR;
399 }
400
401 if (ct_command(conn->command, CS_LANG_CMD, query, CS_NULLTERM, CS_UNUSED) != CS_SUCCEED) {
402 ERROR("rlm_sql_freetds: unable to initiate command structure (ct_command()");
403
404 return RLM_SQL_ERROR;
405 }
406
407 if (ct_send(conn->command) != CS_SUCCEED) {
408 ERROR("rlm_sql_freetds: unable to send command (ct_send())");
409 return RLM_SQL_ERROR;
410 }
411
412 results_ret = ct_results(conn->command, &result_type);
413 switch (results_ret) {
414 case CS_SUCCEED:
415 switch (result_type) {
416 case CS_ROW_RESULT:
417
418 /*
419 * Set up a target buffer for the results data, and associate the buffer with the results,
420 * but the actual fetching takes place in sql_fetch_row.
421 * The layer above MUST call sql_fetch_row and/or sql_finish_select_query
422 * or this socket will be unusable and may cause segfaults
423 * if reused later on.
424 */
425
426 /*
427 * Set up the DATAFMT structure that describes our target array
428 * and tells freetds what we want future ct_fetch calls to do.
429 */
430 descriptor.datatype = CS_CHAR_TYPE; /* The target buffer is a string */
431 descriptor.format = CS_FMT_NULLTERM; /* Null termination please */
432 descriptor.maxlength = MAX_DATASTR_LEN; /* The string arrays are this large */
433 descriptor.count = 1; /* Fetch one row of data */
434 descriptor.locale = NULL; /* Don't do NLS stuff */
435
436 colcount = sql_num_fields(handle, config); /* Get number of elements in row result */
437
438 rowdata = talloc_zero_array(conn, char *, colcount + 1); /* Space for pointers */
439 rowdata[colcount] = NULL;
440
441 for (i = 0; i < colcount; i++) {
442 /* Space to hold the result data */
443 rowdata[i] = talloc_array(rowdata, char, MAX_DATASTR_LEN + 1);
444
445 /* Associate the target buffer with the data */
446 if (ct_bind(conn->command, i + 1, &descriptor, rowdata[i], NULL, NULL) != CS_SUCCEED) {
447 talloc_free(rowdata);
448
449 ERROR("rlm_sql_freetds: ct_bind() failed)");
450
451 return RLM_SQL_ERROR;
452 }
453
454 }
455
456 rowdata[i] = NULL; /* Terminate the array */
457 conn->results = rowdata;
458 break;
459
460 case CS_CMD_SUCCEED:
461 case CS_CMD_DONE:
462 ERROR("rlm_sql_freetds: query returned no data");
463 break;
464
465 default:
466
467 ERROR("rlm_sql_freetds: unexpected result type from query");
468 sql_finish_select_query(handle, config);
469
470 return RLM_SQL_ERROR;
471 }
472 break;
473
474 case CS_FAIL:
475
476 /*
477 * Serious failure, freetds requires us to cancel the results and maybe even close the db.
478 */
479
480 ERROR("rlm_sql_freetds: failure retrieving query results");
481
482 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
483 ERROR("rlm_sql_freetds: cleaning up");
484
485 return RLM_SQL_RECONNECT;
486 }
487 conn->command = NULL;
488
489 return RLM_SQL_ERROR;
490
491 default:
492 ERROR("rlm_sql_freetds: unexpected return value from ct_results()");
493
494 return RLM_SQL_ERROR;
495 }
496
497 return RLM_SQL_OK;
498 }
499
sql_num_rows(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)500 static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
501 {
502 rlm_sql_freetds_conn_t *conn = handle->conn;
503 CS_INT num;
504
505 if (ct_res_info(conn->command, CS_ROW_COUNT, &num, CS_UNUSED, NULL) != CS_SUCCEED) {
506 ERROR("rlm_sql_freetds: error retrieving row count");
507
508 return RLM_SQL_ERROR;
509 }
510
511 return num;
512 }
513
sql_fetch_row(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)514 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
515 {
516 rlm_sql_freetds_conn_t *conn = handle->conn;
517 CS_INT ret, count;
518
519 handle->row = NULL;
520
521 ret = ct_fetch(conn->command, CS_UNUSED, CS_UNUSED, CS_UNUSED, &count);
522 switch (ret) {
523 case CS_FAIL:
524 /*
525 * Serious failure, freetds requires us to cancel the results and maybe even close the db.
526 */
527 ERROR("rlm_sql_freetds: failure fetching row data");
528 if (ct_cancel(NULL, conn->command, CS_CANCEL_ALL) == CS_FAIL) {
529 ERROR("rlm_sql_freetds: cleaning up");
530 } else {
531 conn->command = NULL;
532 }
533
534 return RLM_SQL_RECONNECT;
535
536 case CS_END_DATA:
537 return RLM_SQL_NO_MORE_ROWS;
538
539 case CS_SUCCEED:
540 handle->row = conn->results;
541
542 return RLM_SQL_OK;
543
544 case CS_ROW_FAIL:
545 ERROR("rlm_sql_freetds: recoverable failure fetching row data");
546
547 return RLM_SQL_RECONNECT;
548
549 default:
550 ERROR("rlm_sql_freetds: unexpected returncode from ct_fetch");
551
552 return RLM_SQL_ERROR;
553 }
554 }
555
sql_free_result(UNUSED rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)556 static sql_rcode_t sql_free_result(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
557 {
558
559 /*
560 * Not implemented, never called from rlm_sql anyway result buffer is freed in the
561 * finish_query functions.
562 */
563 return RLM_SQL_OK;
564
565 }
566
sql_finish_query(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)567 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
568 {
569 rlm_sql_freetds_conn_t *conn = handle->conn;
570
571 ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
572 if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
573 ERROR("rlm_sql_freetds: freeing command structure failed");
574
575 return RLM_SQL_ERROR;
576 }
577 conn->command = NULL;
578
579 return RLM_SQL_OK;
580 }
581
sql_affected_rows(rlm_sql_handle_t * handle,rlm_sql_config_t * config)582 static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
583 {
584 return sql_num_rows(handle, config);
585 }
586
587
_sql_socket_destructor(rlm_sql_freetds_conn_t * conn)588 static int _sql_socket_destructor(rlm_sql_freetds_conn_t *conn)
589 {
590 DEBUG2("rlm_sql_freetds: socket destructor called, closing socket");
591
592 if (conn->command) {
593 ct_cancel(NULL, conn->command, CS_CANCEL_ALL);
594 if (ct_cmd_drop(conn->command) != CS_SUCCEED) {
595 ERROR("rlm_sql_freetds: freeing command structure failed");
596
597 return RLM_SQL_ERROR;
598 }
599 }
600
601 if (conn->db) {
602 /*
603 * We first try gracefully closing the connection (which informs the server)
604 * Then if that fails we force the connection closure.
605 *
606 * Sybase docs says this may fail because of pending results, but we
607 * should not have any pending results at this point, so something else must
608 * of gone wrong.
609 */
610 if (ct_close(conn->db, CS_UNUSED) != CS_SUCCEED) {
611 ct_close(conn->db, CS_FORCE_CLOSE);
612 }
613
614 ct_con_drop(conn->db);
615 }
616
617 if (conn->context) {
618 ct_exit(conn->context, CS_UNUSED);
619 cs_ctx_drop(conn->context);
620 }
621
622 return RLM_SQL_OK;
623 }
624
sql_socket_init(rlm_sql_handle_t * handle,rlm_sql_config_t * config)625 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
626 {
627 rlm_sql_freetds_conn_t *conn;
628
629 MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_freetds_conn_t));
630 talloc_set_destructor(conn, _sql_socket_destructor);
631
632 /*
633 * Allocate a CS context structure. This should really only be done once, but because of
634 * the db pooling design of rlm_sql, we'll have to go with one context per db
635 */
636 if (cs_ctx_alloc(CS_VERSION_100, &conn->context) != CS_SUCCEED) {
637 ERROR("rlm_sql_freetds: unable to allocate CS context structure (cs_ctx_alloc())");
638
639 goto error;
640 }
641
642 /*
643 * Initialize ctlib
644 */
645 if (ct_init(conn->context, CS_VERSION_100) != CS_SUCCEED) {
646 ERROR("rlm_sql_freetds: unable to initialize Client-Library");
647
648 goto error;
649 }
650
651 /*
652 * Install callback functions for error-handling
653 */
654 if (cs_config(conn->context, CS_SET, CS_MESSAGE_CB, (CS_VOID *)csmsg_callback, CS_UNUSED, NULL) != CS_SUCCEED) {
655 ERROR("rlm_sql_freetds: unable to install CS Library error callback");
656
657 goto error;
658 }
659
660 if (cs_config(conn->context, CS_SET, CS_USERDATA,
661 (CS_VOID *)&handle->conn, sizeof(handle->conn), NULL) != CS_SUCCEED) {
662 ERROR("rlm_sql_freetds: unable to set userdata pointer");
663
664 goto error;
665 }
666
667 if (ct_callback(conn->context, NULL, CS_SET, CS_CLIENTMSG_CB, (CS_VOID *)clientmsg_callback) != CS_SUCCEED) {
668 ERROR("rlm_sql_freetds: unable to install client message callback");
669
670 goto error;
671 }
672
673 if (ct_callback(conn->context, NULL, CS_SET, CS_SERVERMSG_CB, (CS_VOID *)servermsg_callback) != CS_SUCCEED) {
674 ERROR("rlm_sql_freetds: unable to install server message callback");
675
676 goto error;
677 }
678
679 /*
680 * Allocate a ctlib db structure
681 */
682 if (ct_con_alloc(conn->context, &conn->db) != CS_SUCCEED) {
683 ERROR("rlm_sql_freetds: unable to allocate db structure");
684
685 goto error;
686 }
687
688 /*
689 * Set User and Password properties for the db
690 */
691 {
692 CS_VOID *login, *password;
693 CS_CHAR *server;
694 char database[128];
695
696 memcpy(&login, &config->sql_login, sizeof(login));
697 if (ct_con_props(conn->db, CS_SET, CS_USERNAME, login, strlen(config->sql_login), NULL) != CS_SUCCEED) {
698 ERROR("rlm_sql_freetds: unable to set username for db");
699
700 goto error;
701 }
702
703 memcpy(&password, &config->sql_password, sizeof(password));
704 if (ct_con_props(conn->db, CS_SET, CS_PASSWORD,
705 password, strlen(config->sql_password), NULL) != CS_SUCCEED) {
706 ERROR("rlm_sql_freetds: unable to set password for db");
707
708 goto error;
709 }
710
711 /*
712 * Connect to the database
713 */
714 memcpy(&server, &config->sql_server, sizeof(server));
715 if (ct_connect(conn->db, server, strlen(config->sql_server)) != CS_SUCCEED) {
716 ERROR("rlm_sql_freetds: unable to establish db to symbolic servername %s",
717 config->sql_server);
718
719 goto error;
720 }
721
722 /*
723 * There doesn't appear to be a way to set the database with the API, so use an
724 * sql statement when we first open the connection.
725 */
726 snprintf(database, sizeof(database), "USE %s;", config->sql_db);
727 if (sql_query(handle, config, database) != RLM_SQL_OK) {
728 goto error;
729 }
730
731 sql_finish_query(handle, config);
732 }
733
734 return RLM_SQL_OK;
735
736 error:
737 if (conn->context) {
738 sql_log_entry_t error;
739
740 if (sql_error(NULL, &error, 1, handle, config) > 0) ERROR("rlm_sql_freetds: %s", error.msg);
741 }
742
743 return RLM_SQL_ERROR;
744 }
745
746 /* Exported to rlm_sql */
747 extern rlm_sql_module_t rlm_sql_freetds;
748 rlm_sql_module_t rlm_sql_freetds = {
749 .name = "rlm_sql_freetds",
750 .sql_socket_init = sql_socket_init,
751 .sql_query = sql_query,
752 .sql_select_query = sql_select_query,
753 .sql_num_fields = sql_num_fields,
754 .sql_num_rows = sql_num_rows,
755 .sql_affected_rows = sql_affected_rows,
756 .sql_fetch_row = sql_fetch_row,
757 .sql_free_result = sql_free_result,
758 .sql_error = sql_error,
759 .sql_finish_query = sql_finish_query,
760 .sql_finish_select_query = sql_finish_select_query
761 };
762