From 13e31ad1ef2a43e0a76fe59d2c9d3a706fdef76d Mon Sep 17 00:00:00 2001 From: seanbright Date: Sat, 28 Jun 2008 21:28:16 +0000 Subject: Merge in changes from my cdr-tds-conversion branch. This changes the internal implementation from using the volatile libtds, to using the db-lib front end. The unintended side effect of this is that we support (at least) versions 0.62 through 0.82 of the FreeTDS distribution without any #ifdef ugliness. (closes issue #12844) Reported by: jcollie git-svn-id: http://svn.digium.com/svn/asterisk/trunk@126226 f38db490-d61c-443f-a65b-d21fe96a405b --- cdr/cdr_tds.c | 376 ++++++++++++++++++++++++++++------------------------------ 1 file changed, 180 insertions(+), 196 deletions(-) (limited to 'cdr') diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c index 235e1a6b8..2f17cac2d 100644 --- a/cdr/cdr_tds.c +++ b/cdr/cdr_tds.c @@ -66,82 +66,76 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include #include -#include -#include -#include - #include "asterisk/config.h" #include "asterisk/channel.h" #include "asterisk/cdr.h" #include "asterisk/module.h" -#ifdef FREETDS_PRE_0_62 -#warning "You have older TDS, you should upgrade!" -#endif +#include +#include #define DATE_FORMAT "%Y/%m/%d %T" -static char *name = "mssql"; +static char *name = "FreeTDS (MSSQL)"; static char *config = "cdr_tds.conf"; struct cdr_tds_config { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(hostname); - AST_STRING_FIELD(dbname); - AST_STRING_FIELD(dbuser); + AST_STRING_FIELD(database); + AST_STRING_FIELD(username); AST_STRING_FIELD(password); AST_STRING_FIELD(table); AST_STRING_FIELD(charset); AST_STRING_FIELD(language); ); - TDSSOCKET *tds; - TDSLOGIN *login; - TDSCONTEXT *context; + DBPROCESS *dbproc; unsigned int connected:1; - ast_mutex_t lock; }; +AST_MUTEX_DEFINE_STATIC(tds_lock); + static struct cdr_tds_config *settings; static char *anti_injection(const char *, int); -static void get_date(char *, struct timeval); +static void get_date(char *, size_t len, struct timeval); static int mssql_connect(void); static int mssql_disconnect(void); -static void cdr_tds_config_destroy(void); - static int tds_log(struct ast_cdr *cdr) { - char sqlcmd[2048], start[80], answer[80], end[80]; + char start[80], answer[80], end[80]; char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid; - int res = 0; - int retried = 0; -#ifdef FREETDS_PRE_0_62 - TDS_INT result_type; -#endif - - ast_mutex_lock(&settings->lock); - - memset(sqlcmd, 0, 2048); + RETCODE erc; + int res = -1; accountcode = anti_injection(cdr->accountcode, 20); - src = anti_injection(cdr->src, 80); - dst = anti_injection(cdr->dst, 80); - dcontext = anti_injection(cdr->dcontext, 80); - clid = anti_injection(cdr->clid, 80); - channel = anti_injection(cdr->channel, 80); - dstchannel = anti_injection(cdr->dstchannel, 80); - lastapp = anti_injection(cdr->lastapp, 80); - lastdata = anti_injection(cdr->lastdata, 80); - uniqueid = anti_injection(cdr->uniqueid, 32); - - get_date(start, cdr->start); - get_date(answer, cdr->answer); - get_date(end, cdr->end); - - sprintf( - sqlcmd, + src = anti_injection(cdr->src, 80); + dst = anti_injection(cdr->dst, 80); + dcontext = anti_injection(cdr->dcontext, 80); + clid = anti_injection(cdr->clid, 80); + channel = anti_injection(cdr->channel, 80); + dstchannel = anti_injection(cdr->dstchannel, 80); + lastapp = anti_injection(cdr->lastapp, 80); + lastdata = anti_injection(cdr->lastdata, 80); + uniqueid = anti_injection(cdr->uniqueid, 32); + + get_date(start, sizeof(start), cdr->start); + get_date(answer, sizeof(answer), cdr->answer); + get_date(end, sizeof(end), cdr->end); + + ast_mutex_lock(&tds_lock); + + /* Ensure that we are connected */ + if (!settings->connected) { + if (mssql_connect()) { + /* Connect failed */ + goto done; + } + } + + erc = dbfcmd(settings->dbproc, "INSERT INTO %s " "(" "accountcode, " @@ -202,27 +196,26 @@ static int tds_log(struct ast_cdr *cdr) uniqueid ); - do { - if (!settings->connected) { - if (mssql_connect()) - ast_log(LOG_ERROR, "Failed to reconnect to SQL database.\n"); - else - ast_log(LOG_WARNING, "Reconnected to SQL database.\n"); + if (erc == FAIL) { + ast_log(LOG_ERROR, "Failed to build INSERT statement, no CDR was logged.\n"); + goto done; + } + + if (dbsqlexec(settings->dbproc) == FAIL) { + ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CDR was logged.\n"); + goto done; + } - retried = 1; /* note that we have now tried */ - } + /* Consume any results we might get back (this is more of a sanity check than + * anything else, since an INSERT shouldn't return results). */ + while (dbresults(settings->dbproc) != NO_MORE_RESULTS) { + while (dbnextrow(settings->dbproc) != NO_MORE_ROWS); + } -#ifdef FREETDS_PRE_0_62 - if (!settings->connected || (tds_submit_query(settings->tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(settings->tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED)) -#else - if (!settings->connected || (tds_submit_query(settings->tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(settings->tds) != TDS_SUCCEED)) -#endif - { - ast_log(LOG_ERROR, "Failed to insert Call Data Record into SQL database.\n"); + res = 0; - mssql_disconnect(); /* this is ok even if we are already disconnected */ - } - } while (!settings->connected && !retried); +done: + ast_mutex_unlock(&tds_lock); ast_free(accountcode); ast_free(src); @@ -235,9 +228,7 @@ static int tds_log(struct ast_cdr *cdr) ast_free(lastdata); ast_free(uniqueid); - ast_mutex_unlock(&settings->lock); - - return res; + return 0; } static char *anti_injection(const char *str, int len) @@ -274,39 +265,23 @@ static char *anti_injection(const char *str, int len) return buf; } -static void get_date(char *dateField, struct timeval tv) +static void get_date(char *dateField, size_t len, struct timeval tv) { - struct ast_tm tm; - char buf[80]; - /* To make sure we have date variable if not insert null to SQL */ if (!ast_tvzero(tv)) { + struct ast_tm tm; ast_localtime(&tv, &tm, NULL); - ast_strftime(buf, 80, DATE_FORMAT, &tm); - sprintf(dateField, "'%s'", buf); + ast_strftime(dateField, len, "'" DATE_FORMAT "'", &tm); } else { - strcpy(dateField, "null"); + ast_copy_string(dateField, "null", len); } } static int mssql_disconnect(void) { - if (!settings) - return 0; - - if (settings->tds) { - tds_free_socket(settings->tds); - settings->tds = NULL; - } - - if (settings->context) { - tds_free_context(settings->context); - settings->context = NULL; - } - - if (settings->login) { - tds_free_login(settings->login); - settings->login = NULL; + if (settings->dbproc) { + dbclose(settings->dbproc); + settings->dbproc = NULL; } settings->connected = 0; @@ -316,107 +291,90 @@ static int mssql_disconnect(void) static int mssql_connect(void) { -#if (defined(FREETDS_0_63) || defined(FREETDS_0_64)) - TDSCONNECTION *connection = NULL; -#else - TDSCONNECTINFO *connection = NULL; -#endif - char query[128]; - - /* Connect to M$SQL Server */ - if (!(settings->login = tds_alloc_login())) { - ast_log(LOG_ERROR, "tds_alloc_login() failed.\n"); + LOGINREC *login; + + if ((login = dblogin()) == NULL) { + ast_log(LOG_ERROR, "Unable to allocate login structure for db-lib\n"); return -1; } - tds_set_server(settings->login, settings->hostname); - tds_set_user(settings->login, settings->dbuser); - tds_set_passwd(settings->login, settings->password); - tds_set_app(settings->login, "TSQL"); - tds_set_library(settings->login, "TDS-Library"); -#ifndef FREETDS_PRE_0_62 - tds_set_client_charset(settings->login, settings->charset); -#endif - tds_set_language(settings->login, settings->language); - tds_set_packet(settings->login, 512); - tds_set_version(settings->login, 7, 0); - -#ifdef FREETDS_0_64 - if (!(settings->context = tds_alloc_context(NULL))) -#else - if (!(settings->context = tds_alloc_context())) -#endif - { - ast_log(LOG_ERROR, "tds_alloc_context() failed.\n"); - goto connect_fail; + DBSETLAPP(login, "TSQL"); + DBSETLUSER(login, settings->username); + DBSETLPWD(login, settings->password); + DBSETLCHARSET(login, settings->charset); + DBSETLNATLANG(login, settings->language); + + if ((settings->dbproc = dbopen(login, (char *) settings->hostname)) == NULL) { + ast_log(LOG_ERROR, "Unable to connect to %s\n", settings->hostname); + dbloginfree(login); + return -1; } - if (!(settings->tds = tds_alloc_socket(settings->context, 512))) { - ast_log(LOG_ERROR, "tds_alloc_socket() failed.\n"); - goto connect_fail; + dbloginfree(login); + + if (dbuse(settings->dbproc, (char *) settings->database) == FAIL) { + ast_log(LOG_ERROR, "Unable to select database %s\n", settings->database); + goto failed; } - tds_set_parent(settings->tds, NULL); - connection = tds_read_config_info(settings->tds, settings->login, settings->context->locale); - if (!connection) { - ast_log(LOG_ERROR, "tds_read_config() failed.\n"); - goto connect_fail; + if (dbfcmd(settings->dbproc, "SELECT 1 FROM [%s]", settings->table) == FAIL) { + ast_log(LOG_ERROR, "Unable to build query while verifying the existence of table '%s'\n", settings->table); + goto failed; } - if (tds_connect(settings->tds, connection) == TDS_FAIL) { - ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n"); - settings->tds = NULL; /* freed by tds_connect() on error */ -#if (defined(FREETDS_0_63) || defined(FREETDS_0_64)) - tds_free_connection(connection); -#else - tds_free_connect(connection); -#endif - connection = NULL; - goto connect_fail; + if (dbsqlexec(settings->dbproc) == FAIL) { + ast_log(LOG_ERROR, "Unable to verify existence of table '%s'\n", settings->table); + goto failed; } -#if (defined(FREETDS_0_63) || defined(FREETDS_0_64)) - tds_free_connection(connection); -#else - tds_free_connect(connection); -#endif - connection = NULL; - - sprintf(query, "USE %s", settings->dbname); -#ifdef FREETDS_PRE_0_62 - if ((tds_submit_query(settings->tds, query) != TDS_SUCCEED) || (tds_process_simple_query(settings->tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED)) -#else - if ((tds_submit_query(settings->tds, query) != TDS_SUCCEED) || (tds_process_simple_query(settings->tds) != TDS_SUCCEED)) -#endif - { - ast_log(LOG_ERROR, "Could not change database (%s)\n", settings->dbname); - goto connect_fail; + + /* Consume the result set (we don't really care about the result, though) */ + while (dbresults(settings->dbproc) != NO_MORE_RESULTS) { + while (dbnextrow(settings->dbproc) != NO_MORE_ROWS); } settings->connected = 1; + return 0; -connect_fail: - mssql_disconnect(); +failed: + dbclose(settings->dbproc); + settings->dbproc = NULL; return -1; } -static void cdr_tds_config_destroy(void) +static int tds_unload_module(void) { - if (settings) - { - ast_mutex_destroy(&settings->lock); + if (settings) { + ast_mutex_lock(&tds_lock); + mssql_disconnect(); + ast_mutex_unlock(&tds_lock); + ast_string_field_free_memory(settings); - ast_free(settings); + ast_free(settings); } + + ast_cdr_unregister(name); + + dbexit(); + + return 0; } -static int tds_unload_module(void) +static int tds_error_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) { - mssql_disconnect(); + ast_log(LOG_ERROR, "%s (%d)\n", dberrstr, dberr); - ast_cdr_unregister(name); + if (oserr != DBNOERR) { + ast_log(LOG_ERROR, "%s (%d)\n", oserrstr, oserr); + } - cdr_tds_config_destroy(); + return INT_CANCEL; +} + +static int tds_message_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) +{ + ast_debug(1, "Msg %d, Level %d, State %d, Line %d\n", msgno, severity, msgstate, line); + ast_log(LOG_NOTICE, "%s\n", msgtext); return 0; } @@ -424,66 +382,57 @@ static int tds_unload_module(void) static int tds_load_module(int reload) { struct ast_config *cfg; - struct ast_variable *var; const char *ptr = NULL; struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; cfg = ast_config_load(config, config_flags); if (!cfg) { - ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDRs: %s\n", config); + ast_log(LOG_NOTICE, "Unable to load TDS config for CDRs: %s\n", config); return 0; } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) return 0; - var = ast_variable_browse(cfg, "global"); - if (!var) /* nothing configured */ { + if (!ast_variable_browse(cfg, "global")) { + /* nothing configured */ ast_config_destroy(cfg); return 0; } - if (reload) { - ast_string_field_init(settings, 0); - } else { - settings = ast_calloc(1, sizeof(*settings)); - - if (!settings || ast_string_field_init(settings, 256)) { - if (settings) { - ast_free(settings); - settings = NULL; - } - ast_config_destroy(cfg); - return 0; - } + ast_mutex_lock(&tds_lock); - ast_mutex_init(&settings->lock); - } + /* Clear out any existing settings */ + ast_string_field_init(settings, 0); ptr = ast_variable_retrieve(cfg, "global", "hostname"); if (ptr) { ast_string_field_set(settings, hostname, ptr); } else { - ast_log(LOG_ERROR, "Database server hostname not specified.\n"); + ast_log(LOG_ERROR, "Failed to connect: Database server hostname not specified.\n"); + goto failed; } ptr = ast_variable_retrieve(cfg, "global", "dbname"); if (ptr) { - ast_string_field_set(settings, dbname, ptr); + ast_string_field_set(settings, database, ptr); } else { - ast_log(LOG_ERROR, "Database dbname not specified.\n"); + ast_log(LOG_ERROR, "Failed to connect: Database dbname not specified.\n"); + goto failed; } ptr = ast_variable_retrieve(cfg, "global", "user"); if (ptr) { - ast_string_field_set(settings, dbuser, ptr); + ast_string_field_set(settings, username, ptr); } else { - ast_log(LOG_ERROR, "Database dbuser not specified.\n"); + ast_log(LOG_ERROR, "Failed to connect: Database dbuser not specified.\n"); + goto failed; } ptr = ast_variable_retrieve(cfg, "global", "password"); if (ptr) { ast_string_field_set(settings, password, ptr); } else { - ast_log(LOG_ERROR, "Database password not specified.\n"); + ast_log(LOG_ERROR, "Failed to connect: Database password not specified.\n"); + goto failed; } ptr = ast_variable_retrieve(cfg, "global", "charset"); @@ -503,19 +452,28 @@ static int tds_load_module(int reload) ptr = ast_variable_retrieve(cfg, "global", "table"); if (ptr) { ast_string_field_set(settings, table, ptr); - } else { - ast_debug(1, "Table not specified. Assuming 'cdr'\n"); + } else { + ast_log(LOG_NOTICE, "Table name not specified, using 'cdr' by default.\n"); ast_string_field_set(settings, table, "cdr"); } - ast_config_destroy(cfg); - - ast_mutex_lock(&settings->lock); mssql_disconnect(); - mssql_connect(); - ast_mutex_unlock(&settings->lock); + + if (mssql_connect()) { + /* We failed to connect (mssql_connect takes care of logging it) */ + goto failed; + } + + ast_mutex_unlock(&tds_lock); + ast_config_destroy(cfg); return 1; + +failed: + ast_mutex_unlock(&tds_lock); + ast_config_destroy(cfg); + + return 0; } static int reload(void) @@ -525,9 +483,35 @@ static int reload(void) static int load_module(void) { - if (!tds_load_module(0)) + if (dbinit() == FAIL) { + ast_log(LOG_ERROR, "Failed to initialize FreeTDS db-lib\n"); return AST_MODULE_LOAD_DECLINE; + } + + dberrhandle(tds_error_handler); + dbmsghandle(tds_message_handler); + + settings = ast_calloc(1, sizeof(*settings)); + + if (!settings || ast_string_field_init(settings, 256)) { + if (settings) { + ast_free(settings); + settings = NULL; + } + dbexit(); + return AST_MODULE_LOAD_DECLINE; + } + + if (!tds_load_module(0)) { + ast_string_field_free_memory(settings); + ast_free(settings); + settings = NULL; + dbexit(); + return AST_MODULE_LOAD_DECLINE; + } + ast_cdr_register(name, ast_module_info->description, tds_log); + return AST_MODULE_LOAD_SUCCESS; } @@ -536,7 +520,7 @@ static int unload_module(void) return tds_unload_module(); } -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MSSQL CDR Backend", +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "FreeTDS CDR Backend", .load = load_module, .unload = unload_module, .reload = reload, -- cgit v1.2.3