SQL driver for freetds-0.64
Lv Zheng
lv.zheng at soliton.com.cn
Fri Jun 8 04:37:07 CEST 2007
Hello, all
I created an sql module using FreeTDS-0.64.
I've tested the module against Microsft SQL
Server 2000 developer version, sql accounting,
checksimul and sqlcounter can work with this
module.
Embedded solutions may require this module
to connect SQL servers, having not unixODBC
installed.
The patch is developed in FreeRADIUS-1.1.2
environment, it can also be applied to
FreeRADIUS-1.1.6. Hopes this will be useful
for someone.
Best regards,
Lv Zheng
Index: freeradius-1.1.2/src/modules/rlm_sql/drivers/rlm_sql_freetds/configure.in
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ freeradius-1.1.2/src/modules/rlm_sql/drivers/rlm_sql_freetds/configure.in 2007-04-26 06:23:25.000000000 +0800
@@ -0,0 +1,83 @@
+AC_INIT(sql_freetds.c)
+AC_REVISION($Revision: 1.1 $)
+AC_DEFUN(modname,[rlm_sql_freetds])
+
+fail=
+SMART_LIBS=
+SMART_CLFAGS=
+if test x$with_[]modname != xno; then
+
+ AC_PROG_CC
+
+ dnl extra argument: --with-rlm-sql-freetds-lib-dir
+ rlm_sql_freetds_lib_dir=
+ AC_ARG_WITH(rlm-sql-freetds-lib-dir,
+ [ --with-rlm-sql-freetds-lib-dir=DIR Directory for FreeTDS library files []],
+ [ case "$withval" in
+ no)
+ AC_MSG_ERROR(Need rlm-sql-freetds-lib-dir)
+ ;;
+ yes)
+ ;;
+ *)
+ rlm_sql_freetds_lib_dir="$withval"
+ ;;
+ esac ]
+ )
+
+ dnl extra argument: --with-rlm-sql-freetds-include-dir
+ rlm_sql_freetds_include_dir=
+ AC_ARG_WITH(rlm-sql-freetds-include-dir,
+ [ --with-rlm-sql-freetds-include-dir=DIR Directory for FreeTDS include files []],
+ [ case "$withval" in
+ no)
+ AC_MSG_ERROR(Need rlm-sql-freetds-include-dir)
+ ;;
+ yes)
+ ;;
+ *)
+ rlm_sql_freetds_include_dir="$withval"
+ ;;
+ esac ]
+ )
+
+ smart_try_dir="$rlm_sql_freetds_include_dir /usr/include/ /usr/local/freetds/include/"
+ AC_SMART_CHECK_INCLUDE(tds.h)
+ if test "x$ac_cv_header_tds_h" != "xyes"; then
+ fail="$fail tds.h"
+ fi
+
+ smart_try_dir="$rlm_sql_freetds_lib_dir /usr/lib /usr/local/freetds/lib"
+ AC_SMART_CHECK_LIB(tds, tds_connect)
+ if test "x$ac_cv_lib_tds_tds_connect" != "xyes"; then
+ fail="$fail libtds"
+ fi
+
+ targetname=modname
+else
+ targetname=
+ echo \*\*\* module modname is disabled.
+fi
+
+if test x"$fail" != x""; then
+ if test x"${enable_strict_dependencies}" = x"yes"; then
+ AC_MSG_ERROR([set --without-]modname[ to disable it explicitly.])
+ else
+ AC_MSG_WARN([silently not building ]modname[.])
+ AC_MSG_WARN([FAILURE: ]modname[ requires: $fail.])
+ if test x"$headersuggestion" != x; then
+ AC_MSG_WARN([$headersuggestion])
+ fi
+ if test x"$libsuggestion" != x; then
+ AC_MSG_WARN([$libsuggestion])
+ fi
+ targetname=""
+ fi
+fi
+
+freetds_ldflags=$SMART_LIBS
+freetds_cflags=$SMART_CFLAGS
+AC_SUBST(freetds_ldflags)
+AC_SUBST(freetds_cflags)
+AC_SUBST(targetname)
+AC_OUTPUT(Makile)
Index: freeradius-1.1.2/src/modules/rlm_sql/stable
===================================================================
--- freeradius-1.1.2.orig/src/modules/rlm_sql/stable 2007-04-26 05:29:09.000000000 +0800
+++ freeradius-1.1.2/src/modules/rlm_sql/stable 2007-04-26 06:23:25.000000000 +0800
@@ -3,3 +3,4 @@
rlm_sql_postgresql
rlm_sql_oracle
rlm_sql_unixodbc
+rlm_sql_freetds
Index: freeradius-1.1.2/src/modules/rlm_sql/drivers/rlm_sql_freetds/Makefile.in
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ freeradius-1.1.2/src/modules/rlm_sql/drivers/rlm_sql_freetds/Makefile.in 2007-04-26 06:23:25.000000000 +0800
@@ -0,0 +1,8 @@
+include ../../../../../Make.inc
+
+TARGET = @targetname@
+SRCS = sql_freetds.c
+RLM_SQL_CFLAGS = @freetds_cflags@ $(INCLTDL)
+RLM_SQL_LIBS = @freetds_ldflags@
+
+include ../rules.mak
Index: freeradius-1.1.2/src/modules/rlm_sql/drivers/rlm_sql_freetds/sql_freetds.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ freeradius-1.1.2/src/modules/rlm_sql/drivers/rlm_sql_freetds/sql_freetds.c 2007-04-26 06:24:31.000000000 +0800
@@ -0,0 +1,535 @@
+/**************************************************************************
+ * sql_freetds.c FreeTDS rlm_sql driver *
+ * *
+ * Some pieces of code were adopted from FreeTDS project. *
+ * FreeTDS home page - http://www.freetds.org/ *
+ * *
+ * Dmitri Ageev <d_ageev at ortcc.ru> *
+ **************************************************************************/
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "radiusd.h"
+
+#include <tds.h>
+#include "rlm_sql.h"
+
+typedef struct rlm_sql_freetds_sock {
+ TDSSOCKET *tds_socket;
+ TDSLOGIN *tds_login;
+ TDSCONTEXT *tds_context;
+ char **row;
+ void *conn;
+} rlm_sql_freetds_sock;;
+
+#include <tds.h>
+#include <tdsconvert.h>
+
+struct main_config_t *rad_mainconfig = NULL;
+
+static int sql_use_database(TDSSOCKET *tds, const char *name);
+static int sql_close(SQLSOCK * sqlsocket, SQL_CONFIG *config);
+
+/*************************************************************************
+ *
+ * Function: sql_destroy_socket
+ *
+ * Purpose: Free socket and private connection data
+ *
+ *************************************************************************/
+static int sql_destroy_socket(SQLSOCK *sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+
+ if (tds_socket) {
+ sql_close(sqlsocket, config);
+ free(tds_socket);
+ tds_socket = NULL;
+ }
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_init_socket
+ *
+ * Purpose: Establish connection to the db
+ *
+ *************************************************************************/
+static int sql_init_socket(SQLSOCK *sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket;
+ TDSCONNECTION *connection;
+
+ if (!sqlsocket->conn) {
+ sqlsocket->conn = (rlm_sql_freetds_sock *)malloc(sizeof(rlm_sql_freetds_sock));
+ if (!sqlsocket->conn) {
+ return -1;
+ }
+ memset(sqlsocket->conn, 0, sizeof (rlm_sql_freetds_sock));
+ }
+ tds_socket = sqlsocket->conn;
+ memset(tds_socket, 0, sizeof(*tds_socket));
+
+ radlog(L_DBG, "tds: Starting connect to TDS server for #%d",
+ sqlsocket->id);
+
+ tds_socket->tds_login = tds_alloc_login();
+ if (!tds_socket->tds_login) {
+ radlog(L_ERR, "tds: tds_alloc_login() failed.");
+ sql_close(sqlsocket, config);
+ return -1;
+ }
+ tds_set_version(tds_socket->tds_login, 8, 0);
+ tds_set_passwd(tds_socket->tds_login, config->sql_password);
+ tds_set_user(tds_socket->tds_login, config->sql_login);
+ tds_set_server(tds_socket->tds_login, config->sql_server);
+ tds_set_port(tds_socket->tds_login, atoi(config->sql_port));
+ tds_set_app(tds_socket->tds_login, "radius");
+ /*
+ tds_set_host(tds_socket->tds_login, "myhost");
+ tds_set_library(tds_socket->tds_login, "TDS-Library");
+ tds_set_client_charset(tds_socket->tds_login, "ISO-8859-1");
+ tds_set_language(tds_socket->tds_login, "us_english");
+ */
+
+ tds_socket->tds_context = tds_alloc_context(NULL);
+ tds_socket->tds_socket = tds_alloc_socket(tds_socket->tds_context, 512);
+ tds_set_parent(tds_socket->tds_socket, NULL);
+ connection = tds_read_config_info(NULL, tds_socket->tds_login, tds_socket->tds_context->locale);
+
+ if (!connection || tds_connect(tds_socket->tds_socket, connection) == TDS_FAIL) {
+ if (connection) {
+ tds_free_socket(tds_socket->tds_socket);
+ tds_socket->tds_socket = NULL;
+ tds_free_connection(connection);
+ }
+ radlog(L_ERR, "tds: Couldn't connect socket to TDS server %s@%s",
+ config->sql_login, config->sql_server);
+ sql_close(sqlsocket, config);
+ return -1;
+ }
+ tds_free_connection(connection);
+ if (sql_use_database(tds_socket->tds_socket, config->sql_db) != 0) {
+ radlog(L_ERR, "tds: Couldn't use TDS database %s",
+ config->sql_db);
+ sql_close(sqlsocket, config);
+ return -1;
+ }
+ return 0;
+}
+
+static int sql_process_result(TDSSOCKET * tds)
+{
+ int rc;
+ int result_type;
+
+ while ((rc = tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_RESULTS)) == TDS_SUCCEED) {
+ switch (result_type) {
+ case TDS_DONE_RESULT:
+ case TDS_DONEPROC_RESULT:
+ case TDS_DONEINPROC_RESULT:
+ /* ignore possible spurious result (TDS7+ send it) */
+ case TDS_STATUS_RESULT:
+ break;
+ default:
+ radlog(L_ERR, "tds: query should not return results");
+ return -1;
+ }
+ }
+ if (rc == TDS_FAIL) {
+ radlog(L_ERR, "tds: tds_process_tokens() returned TDS_FAIL");
+ return -1;
+ } else if (rc != TDS_NO_MORE_RESULTS) {
+ radlog(L_ERR, "tds: tds_process_tokens() unexpected return");
+ return -1;
+ }
+ return 0;
+}
+
+static int sql_use_database(TDSSOCKET *tds, const char *name)
+{
+ char query[512];
+
+ strcpy(query, "use ");
+ if (name[0] == '[' && name[strlen(name)-1] == ']')
+ strcat(query, name);
+ else
+ tds_quote_id(tds, query + 4, name, -1);
+ if (tds_submit_query(tds, query) != TDS_SUCCEED) {
+ radlog(L_ERR, "tds: tds_submit_query() failed");
+ return -1;
+ }
+ if (tds_process_simple_query(tds) != TDS_SUCCEED) {
+ return -1;
+ }
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_query
+ *
+ * Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
+ * the database.
+ *
+ *************************************************************************/
+static int sql_query(SQLSOCK * sqlsocket, SQL_CONFIG *config, char *querystr)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+
+ if (config->tracefile)
+ radlog(L_DBG, "query: %s", querystr);
+ if (tds_socket->tds_socket == NULL) {
+ radlog(L_ERR, "tds: Socket not connected");
+ return SQL_DOWN;
+ }
+
+ if (tds_submit_query(tds_socket->tds_socket, querystr) != TDS_SUCCEED) {
+ radlog(L_ERR, "tds: tds_submit_query() failed");
+ return -1;
+ }
+ if (tds_process_simple_query(tds_socket->tds_socket) != TDS_SUCCEED) {
+ return -1;
+ }
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_num_fields
+ *
+ * Purpose: database specific num_fields function. Returns number
+ * of columns from query
+ *
+ *************************************************************************/
+static int sql_num_fields(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ TDSRESULTINFO *result_info;
+
+ /* Get information about the resulting set */
+ result_info = tds_socket->tds_socket->res_info;
+ if (result_info == NULL) {
+ radlog(L_ERR, "tds: Can't get information about the resulting set");
+ return -1;
+ }
+ return result_info->num_cols;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_free_result
+ *
+ * Purpose: database specific free_result. Frees memory allocated
+ * for a result set
+ *
+ *************************************************************************/
+static int sql_free_result(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ int column, numfileds = sql_num_fields(sqlsocket, config);
+
+ /* Freeing reserved memory */
+ if (tds_socket->row != NULL) {
+ for (column=0; column < numfileds; column++) {
+ if (tds_socket->row[column] != NULL) {
+ free(tds_socket->row[column]);
+ tds_socket->row[column] = NULL;
+ }
+ }
+ free(tds_socket->row);
+ tds_socket->row = NULL;
+ }
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_store_result
+ *
+ * Purpose: database specific function.
+ * Reserve memory for the result set of a query.
+ *
+ *************************************************************************/
+static int sql_store_result(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ TDSCOLUMN **columns;
+ int numfields, column;
+
+ /* Check if memory were allocated */
+ if (tds_socket->row != NULL)
+ sql_free_result(sqlsocket, config);
+
+ /* Getting amount of result fields */
+ numfields = sql_num_fields(sqlsocket, config);
+ if (numfields < 0) return -1;
+
+ /* Get information about the column set */
+ columns = tds_socket->tds_socket->res_info->columns;
+ if (columns == NULL) {
+ radlog(L_ERR, "tds: Can't get information about the column set");
+ return -1;
+ }
+
+ /* Reserving memory for a result set */
+ tds_socket->row = (char **) malloc((numfields+1)*sizeof(char *));
+ if (tds_socket->row == NULL) {
+ radlog(L_ERR, "tds: Can't allocate the memory");
+ return -1;
+ }
+
+ for (column = 0; column < numfields; column++)
+ tds_socket->row[column] = NULL;
+ tds_socket->row[numfields] = NULL;
+
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_select_query
+ *
+ * Purpose: Issue a select query to the database
+ *
+ *************************************************************************/
+static int sql_select_query(SQLSOCK *sqlsocket, SQL_CONFIG *config,
+ char *querystr)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ TDS_INT result_type;
+ int rc;
+
+ rc = tds_submit_query(tds_socket->tds_socket, querystr);
+ if (rc != TDS_SUCCEED) {
+ radlog(L_ERR, "tds: tds_submit_query() failed");
+ return -1;
+ }
+
+ if (tds_process_tokens(tds_socket->tds_socket, &result_type, NULL, TDS_TOKEN_RESULTS) != TDS_SUCCEED) {
+ radlog(L_ERR, "tds: tds_process_tokens() failed");
+ return -1;
+ }
+ if (result_type != TDS_ROWFMT_RESULT) {
+ radlog(L_ERR, "tds: expected row fmt() failed");
+ return -1;
+ }
+ if (tds_process_tokens(tds_socket->tds_socket, &result_type, NULL, TDS_TOKEN_RESULTS) != TDS_SUCCEED) {
+ radlog(L_ERR, "tds: tds_process_tokens() failed");
+ return -1;
+ }
+ if (result_type != TDS_ROW_RESULT) {
+ /* no result */
+ return -1;
+ }
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_fetch_row
+ *
+ * Purpose: database specific fetch_row. Returns a SQL_ROW struct
+ * with all the data for the query in 'sqlsocket->row'. Returns
+ * 0 on success, -1 on failure, SQL_DOWN if 'database is down'
+ *
+ *************************************************************************/
+static int sql_fetch_row(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ TDSRESULTINFO *result_info;
+ TDSCOLUMN **columns;
+ int numfields, column, ret, rc, result_type;
+ CONV_RESULT cr;
+
+ sqlsocket->row = NULL;
+
+ /* Alocating the memory */
+ if (sql_store_result(sqlsocket, config) < 0) return 0;
+
+ /* Getting amount of result fields */
+ numfields = sql_num_fields(sqlsocket, config);
+ if (numfields < 0)
+ return 0;
+ /* Get information about the resulting set */
+ result_info = tds_socket->tds_socket->res_info;
+ if (result_info == NULL) {
+ radlog(L_ERR, "tds: Can't get information about the resulting set");
+ return -1;
+ }
+ /* Get information about the column set */
+ columns = result_info->columns;
+ if (columns == NULL) {
+ radlog(L_ERR, "tds: Can't get information about the column set");
+ return -1;
+ }
+
+ while ((rc = tds_process_tokens(tds_socket->tds_socket, &result_type, NULL,
+ TDS_STOPAT_ROWFMT|TDS_RETURN_DONE|
+ TDS_RETURN_ROW|TDS_RETURN_COMPUTE)) == TDS_SUCCEED) {
+ if (result_type == TDS_ROW_RESULT || result_type == TDS_COMPUTE_RESULT) {
+ /* Converting the fields to a CHAR data type */
+ for (column = 0; column < numfields; column++) {
+ TDSCOLUMN *curcol = tds_socket->tds_socket->current_results->columns[column];
+ TDS_CHAR *src = (TDS_CHAR *) tds_socket->tds_socket->current_results->current_row + curcol->column_offset;
+ int conv_type = tds_get_conversion_type(curcol->column_type, curcol->column_size);
+
+ if (is_blob_type(columns[column]->column_type)) {
+ TDSBLOB *blob = (TDSBLOB *) src;
+ src = blob->textvalue;
+ }
+ if (src && curcol->column_cur_size >= 0) {
+ ret = tds_convert(tds_socket->tds_context, conv_type, src,
+ curcol->column_cur_size,
+ SYBVARCHAR, &cr);
+ if (ret < 0) {
+ radlog(L_ERR, "tds: Error converting");
+ return -1;
+ } else {
+ tds_socket->row[column] = cr.c;
+ }
+ }
+ }
+ sqlsocket->row = tds_socket->row;
+ return 0;
+ }
+ }
+ if (rc != TDS_NO_MORE_RESULTS && rc != TDS_SUCCEED) {
+ radlog(L_ERR, "tds: tds_process_tokens() unexpected return");
+ return -1;
+ }
+ return -1;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_affected_rows
+ *
+ * Purpose: Return the number of rows affected by the query (update,
+ * or insert)
+ *
+ *************************************************************************/
+static int sql_affected_rows(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ return tds_socket->tds_socket->rows_affected;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_num_rows
+ *
+ * Purpose: database specific num_rows. Returns number of rows in
+ * query
+ *
+ *************************************************************************/
+static int sql_num_rows(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ return sql_affected_rows(sqlsocket, config);
+}
+
+/*************************************************************************
+ *
+ * Function: sql_error
+ *
+ * Purpose: database specific error. Returns error associated with
+ * connection
+ *
+ *************************************************************************/
+static const char *sql_error(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+
+ if (tds_socket == NULL || tds_socket->tds_socket == NULL) {
+ return "tds: no connection to db";
+ }
+ return NULL;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_close
+ *
+ * Purpose: database specific close. Closes an open database
+ * connection and cleans up any open handles.
+ *
+ *************************************************************************/
+static int sql_close(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+ if (tds_socket) {
+ if (tds_socket->tds_socket) {
+ tds_free_socket(tds_socket->tds_socket);
+ tds_socket->tds_socket = NULL;
+ }
+ if (tds_socket->tds_login) {
+ tds_free_login(tds_socket->tds_login);
+ tds_socket->tds_login = NULL;
+ }
+ if (tds_socket->tds_context) {
+ tds_free_context(tds_socket->tds_context);
+ tds_socket->tds_context = NULL;
+ }
+ }
+ return 0;
+}
+
+/*************************************************************************
+ *
+ * Function: sql_finish_query
+ *
+ * Purpose: End the query, such as freeing memory
+ *
+ *************************************************************************/
+static int sql_finish_query(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+
+ return sql_process_result(tds_socket->tds_socket);
+}
+
+/*************************************************************************
+ *
+ * Function: sql_finish_select_query
+ *
+ * Purpose: End the select query, such as freeing memory or result
+ *
+ *************************************************************************/
+static int sql_finish_select_query(SQLSOCK * sqlsocket, SQL_CONFIG *config)
+{
+ rlm_sql_freetds_sock *tds_socket = sqlsocket->conn;
+
+ sql_free_result(sqlsocket, config);
+ /* make sure the current statement is complete */
+ if (tds_socket->tds_socket->state == TDS_PENDING) {
+ /* send 'cancel' packet */
+ tds_send_cancel(tds_socket->tds_socket);
+ /* process 'cancel' packet */
+ tds_process_cancel(tds_socket->tds_socket);
+ }
+ return 0;
+}
+
+/* Exported to rlm_sql */
+rlm_sql_module_t rlm_sql_freetds = {
+ "rlm_sql_freetds",
+ sql_init_socket,
+ sql_destroy_socket,
+ sql_query,
+ sql_select_query,
+ sql_store_result,
+ sql_num_fields,
+ sql_num_rows,
+ sql_fetch_row,
+ sql_free_result,
+ sql_error,
+ sql_close,
+ sql_finish_query,
+ sql_finish_select_query,
+ sql_affected_rows
+};
+
=============================================
Soliton Network Systems (Shanghai) Co., Ltd.
EMAIL: lv.zheng at soliton.com.cn
=============================================
More information about the Freeradius-Devel
mailing list