diff options
Diffstat (limited to 'cdr')
-rw-r--r-- | cdr/cdr_tds.c | 464 |
1 files changed, 230 insertions, 234 deletions
diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c index 9d322fd27..9688c167c 100644 --- a/cdr/cdr_tds.c +++ b/cdr/cdr_tds.c @@ -66,72 +66,76 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <time.h> #include <math.h> -#include <tds.h> -#include <tdsconvert.h> -#include <ctype.h> - #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 <sqlfront.h> +#include <sybdb.h> #define DATE_FORMAT "%Y/%m/%d %T" -static char *name = "mssql"; +static char *name = "FreeTDS (MSSQL)"; static char *config = "cdr_tds.conf"; -static char *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *charset = NULL, *language = NULL; -static char *table = NULL; - -static int connected = 0; +struct cdr_tds_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(hostname); + AST_STRING_FIELD(database); + AST_STRING_FIELD(username); + AST_STRING_FIELD(password); + AST_STRING_FIELD(table); + AST_STRING_FIELD(charset); + AST_STRING_FIELD(language); + ); + DBPROCESS *dbproc; + unsigned int connected:1; +}; AST_MUTEX_DEFINE_STATIC(tds_lock); -static TDSSOCKET *tds; -static TDSLOGIN *login; -static TDSCONTEXT *context; +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 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 + 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, sizeof(start), cdr->start); + get_date(answer, sizeof(answer), cdr->answer); + get_date(end, sizeof(end), cdr->end); ast_mutex_lock(&tds_lock); - memset(sqlcmd, 0, 2048); + /* Ensure that we are connected */ + if (!settings->connected) { + if (mssql_connect()) { + /* Connect failed */ + goto done; + } + } - 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, + erc = dbfcmd(settings->dbproc, "INSERT INTO %s " "(" "accountcode, " @@ -172,7 +176,7 @@ static int tds_log(struct ast_cdr *cdr) "'%s', " /* amaflags */ "'%s'" /* uniqueid */ ")", - table, + settings->table, accountcode, src, dst, @@ -192,27 +196,26 @@ static int tds_log(struct ast_cdr *cdr) uniqueid ); - do { - if (!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; + } - retried = 1; /* note that we have now tried */ - } + if (dbsqlexec(settings->dbproc) == FAIL) { + ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CDR was logged.\n"); + goto done; + } -#ifdef FREETDS_PRE_0_62 - if (!connected || (tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED)) -#else - if (!connected || (tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED)) -#endif - { - ast_log(LOG_ERROR, "Failed to insert Call Data Record into SQL database.\n"); + /* 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); + } - mssql_disconnect(); /* this is ok even if we are already disconnected */ - } - } while (!connected && !retried); + res = 0; + +done: + ast_mutex_unlock(&tds_lock); ast_free(accountcode); ast_free(src); @@ -225,285 +228,252 @@ static int tds_log(struct ast_cdr *cdr) ast_free(lastdata); ast_free(uniqueid); - ast_mutex_unlock(&tds_lock); - - return res; + return 0; } static char *anti_injection(const char *str, int len) { /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */ - char *buf; char *buf_ptr, *srh_ptr; char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"}; int idx; - if ((buf = ast_malloc(len + 1)) == NULL) - { - ast_log(LOG_ERROR, "cdr_tds: Out of memory error\n"); + if (!(buf = ast_calloc(1, len + 1))) { + ast_log(LOG_ERROR, "Out of memory\n"); return NULL; } - memset(buf, 0, len); buf_ptr = buf; /* Escape single quotes */ - for (; *str && strlen(buf) < len; str++) - { - if (*str == '\'') + for (; *str && strlen(buf) < len; str++) { + if (*str == '\'') { *buf_ptr++ = '\''; + } *buf_ptr++ = *str; } *buf_ptr = '\0'; /* Erase known bad input */ - for (idx=0; *known_bad[idx]; idx++) - { - while((srh_ptr = strcasestr(buf, known_bad[idx]))) - { - memmove(srh_ptr, srh_ptr+strlen(known_bad[idx]), strlen(srh_ptr+strlen(known_bad[idx]))+1); + for (idx = 0; *known_bad[idx]; idx++) { + while ((srh_ptr = strcasestr(buf, known_bad[idx]))) { + memmove(srh_ptr, srh_ptr + strlen(known_bad[idx]), strlen(srh_ptr + strlen(known_bad[idx])) + 1); } } 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)) - { + if (!ast_tvzero(tv)) { + struct ast_tm tm; ast_localtime(&tv, &tm, NULL); - ast_strftime(buf, 80, DATE_FORMAT, &tm); - sprintf(dateField, "'%s'", buf); - } - else - { - strcpy(dateField, "null"); + ast_strftime(dateField, len, "'" DATE_FORMAT "'", &tm); + } else { + ast_copy_string(dateField, "null", len); } } static int mssql_disconnect(void) { - if (tds) { - tds_free_socket(tds); - tds = NULL; - } - - if (context) { - tds_free_context(context); - context = NULL; + if (settings->dbproc) { + dbclose(settings->dbproc); + settings->dbproc = NULL; } - if (login) { - tds_free_login(login); - login = NULL; - } - - connected = 0; + settings->connected = 0; return 0; } 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 (!(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(login, hostname); - tds_set_user(login, dbuser); - tds_set_passwd(login, password); - tds_set_app(login, "TSQL"); - tds_set_library(login, "TDS-Library"); -#ifndef FREETDS_PRE_0_62 - tds_set_client_charset(login, charset); -#endif - tds_set_language(login, language); - tds_set_packet(login, 512); - tds_set_version(login, 7, 0); - -#ifdef FREETDS_0_64 - if (!(context = tds_alloc_context(NULL))) -#else - if (!(context = tds_alloc_context())) -#endif - { - ast_log(LOG_ERROR, "tds_alloc_context() failed.\n"); - goto connect_fail; + + DBSETLAPP(login, "TSQL"); + DBSETLUSER(login, (char *) settings->username); + DBSETLPWD(login, (char *) settings->password); + DBSETLCHARSET(login, (char *) settings->charset); + DBSETLNATLANG(login, (char *) 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 (!(tds = tds_alloc_socket(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(tds, NULL); - connection = tds_read_config_info(tds, login, 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(tds, connection) == TDS_FAIL) - { - ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n"); - 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", dbname); -#ifdef FREETDS_PRE_0_62 - if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED)) -#else - if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED)) -#endif - { - ast_log(LOG_ERROR, "Could not change database (%s)\n", 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); } - connected = 1; + settings->connected = 1; + return 0; -connect_fail: - mssql_disconnect(); +failed: + dbclose(settings->dbproc); + settings->dbproc = NULL; return -1; } static int tds_unload_module(void) { - mssql_disconnect(); + if (settings) { + ast_mutex_lock(&tds_lock); + mssql_disconnect(); + ast_mutex_unlock(&tds_lock); + + ast_string_field_free_memory(settings); + ast_free(settings); + } ast_cdr_unregister(name); - if (hostname) ast_free(hostname); - if (dbname) ast_free(dbname); - if (dbuser) ast_free(dbuser); - if (password) ast_free(password); - if (charset) ast_free(charset); - if (language) ast_free(language); - if (table) ast_free(table); + dbexit(); + + return 0; +} + +static int tds_error_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) +{ + ast_log(LOG_ERROR, "%s (%d)\n", dberrstr, dberr); + + if (oserr != DBNOERR) { + ast_log(LOG_ERROR, "%s (%d)\n", oserrstr, oserr); + } + + 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; } static int tds_load_module(int reload) { - int res = 0; struct ast_config *cfg; - struct ast_variable *var; const char *ptr = NULL; struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; -#ifdef FREETDS_PRE_0_62 - TDS_INT result_type; -#endif cfg = ast_config_load(config, config_flags); if (!cfg) { - ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDR's: %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; } - + + ast_mutex_lock(&tds_lock); + + /* Clear out any existing settings */ + ast_string_field_init(settings, 0); + ptr = ast_variable_retrieve(cfg, "global", "hostname"); if (ptr) { - if (hostname) - ast_free(hostname); - hostname = ast_strdup(ptr); - } else - ast_log(LOG_ERROR, "Database server hostname not specified.\n"); + ast_string_field_set(settings, hostname, ptr); + } else { + ast_log(LOG_ERROR, "Failed to connect: Database server hostname not specified.\n"); + goto failed; + } ptr = ast_variable_retrieve(cfg, "global", "dbname"); if (ptr) { - if (dbname) - ast_free(dbname); - dbname = ast_strdup(ptr); - } else - ast_log(LOG_ERROR, "Database dbname not specified.\n"); + ast_string_field_set(settings, database, ptr); + } else { + ast_log(LOG_ERROR, "Failed to connect: Database dbname not specified.\n"); + goto failed; + } ptr = ast_variable_retrieve(cfg, "global", "user"); if (ptr) { - if (dbuser) - ast_free(dbuser); - dbuser = ast_strdup(ptr); - } else - ast_log(LOG_ERROR, "Database dbuser not specified.\n"); + ast_string_field_set(settings, username, ptr); + } else { + ast_log(LOG_ERROR, "Failed to connect: Database dbuser not specified.\n"); + goto failed; + } ptr = ast_variable_retrieve(cfg, "global", "password"); if (ptr) { - if (password) - ast_free(password); - password = ast_strdup(ptr); - } else - ast_log(LOG_ERROR,"Database password not specified.\n"); + ast_string_field_set(settings, password, ptr); + } else { + ast_log(LOG_ERROR, "Failed to connect: Database password not specified.\n"); + goto failed; + } ptr = ast_variable_retrieve(cfg, "global", "charset"); - if (charset) - ast_free(charset); - if (ptr) - charset = ast_strdup(ptr); - else - charset = ast_strdup("iso_1"); - - if (language) - ast_free(language); + if (ptr) { + ast_string_field_set(settings, charset, ptr); + } else { + ast_string_field_set(settings, charset, "iso_1"); + } + ptr = ast_variable_retrieve(cfg, "global", "language"); - if (ptr) - language = ast_strdup(ptr); - else - language = ast_strdup("us_english"); + if (ptr) { + ast_string_field_set(settings, language, ptr); + } else { + ast_string_field_set(settings, language, "us_english"); + } ptr = ast_variable_retrieve(cfg, "global", "table"); - if (ptr == NULL) { - ast_debug(1, "cdr_tds: table not specified. Assuming cdr\n"); - ptr = "cdr"; + if (ptr) { + ast_string_field_set(settings, table, ptr); + } else { + ast_log(LOG_NOTICE, "Table name not specified, using 'cdr' by default.\n"); + ast_string_field_set(settings, table, "cdr"); + } + + mssql_disconnect(); + + if (mssql_connect()) { + /* We failed to connect (mssql_connect takes care of logging it) */ + goto failed; } - if (table) - ast_free(table); - table = ast_strdup(ptr); + ast_mutex_unlock(&tds_lock); ast_config_destroy(cfg); - ast_mutex_lock(&tds_lock); - mssql_disconnect(); - mssql_connect(); + return 1; + +failed: ast_mutex_unlock(&tds_lock); + ast_config_destroy(cfg); - return res; + return 0; } static int reload(void) @@ -513,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; } @@ -524,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, |