SQL driver for freetds-0.64
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@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@soliton.com.cn =============================================
Lv Zheng wrote:
I created an sql module using FreeTDS-0.64.
It seems to me it is derivative work from the module rlm_sql_freetds of Dmitri Ageev to be found in the CVS history.
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 module rlm_sql_freetds was in older versions of FreeRADIUS (0.8.x) but was removed in 2003. I don't know whether Alan and the others want to put it back in or not.
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.
Before someone can use it, we have to know what is the license of the new version of the module. Please state it clearly because the sources doesn't contain any license information. -- Nicolas Baradakis
Nicolas Baradakis wrote:
Lv Zheng wrote:
I created an sql module using FreeTDS-0.64.
It seems to me it is derivative work from the module rlm_sql_freetds of Dmitri Ageev to be found in the CVS history.
Yes, the idea, name, comments of APIs are stolen shamelessly from the original one. :) The FreeTDS APIs are not stable, so the codes are different from details.
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 module rlm_sql_freetds was in older versions of FreeRADIUS (0.8.x) but was removed in 2003. I don't know whether Alan and the others want to put it back in or not.
Yes, I referenced the older distribution from 0.8.x.
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.
Before someone can use it, we have to know what is the license of the new version of the module. Please state it clearly because the sources doesn't contain any license information.
I digged into FreeTDS / FreeRADIUS mailing lists, but did not find any clue of what was the license of the old version. Since FreeTDS library is released under LGPL, I'd like to release this new version under LGPL. Best regards, Lv Zheng
Lv Zheng wrote:
Yes, the idea, name, comments of APIs are stolen shamelessly from the original one. :) The FreeTDS APIs are not stable, so the codes are different from details.
The original rlm_sql_freetds module was deleted because the FreeTDS developers indicated that the API was not stable, and should not be used. There are other, better, alternatives to getting the same result. Many people have connected FreeRADIUS to the Microsoft SQL server without using rlm_sql_freetds. Alan DeKok.
participants (3)
-
Alan Dekok -
Lv Zheng -
Nicolas Baradakis