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