Mon Mar 12 2012 21:27:21

Asterisk developer's documentation


cdr_tds.c File Reference

FreeTDS CDR logger. More...

#include "asterisk.h"
#include "asterisk/config.h"
#include "asterisk/channel.h"
#include "asterisk/cdr.h"
#include "asterisk/module.h"
#include <sqlfront.h>
#include <sybdb.h>
Include dependency graph for cdr_tds.c:

Go to the source code of this file.

Data Structures

struct  cdr_tds_config

Defines

#define DATE_FORMAT   "%Y/%m/%d %T"

Functions

static void __reg_module (void)
static void __unreg_module (void)
static char * anti_injection (const char *, int)
static int execute_and_consume (DBPROCESS *dbproc, const char *fmt,...)
static void get_date (char *, size_t len, struct timeval)
static int load_module (void)
static int mssql_connect (void)
static int mssql_disconnect (void)
static int reload (void)
static int tds_error_handler (DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr)
static int tds_load_module (int reload)
static int tds_log (struct ast_cdr *cdr)
static int tds_message_handler (DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line)
static int tds_unload_module (void)
static int unload_module (void)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "FreeTDS CDR Backend" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, }
static struct ast_module_infoast_module_info = &__mod_info
static const char config [] = "cdr_tds.conf"
static const char name [] = "FreeTDS (MSSQL)"
static struct cdr_tds_configsettings
static ast_mutex_t tds_lock = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP , NULL, 1 }

Detailed Description

FreeTDS CDR logger.

See also

Definition in file cdr_tds.c.


Define Documentation

#define DATE_FORMAT   "%Y/%m/%d %T"
 *
 * Table Structure for `cdr`
 *
 * Created on: 05/20/2004 16:16
 * Last changed on: 07/27/2004 20:01

CREATE TABLE [dbo].[cdr] (
	[accountcode] [varchar] (20) NULL ,
	[src] [varchar] (80) NULL ,
	[dst] [varchar] (80) NULL ,
	[dcontext] [varchar] (80) NULL ,
	[clid] [varchar] (80) NULL ,
	[channel] [varchar] (80) NULL ,
	[dstchannel] [varchar] (80) NULL ,
	[lastapp] [varchar] (80) NULL ,
	[lastdata] [varchar] (80) NULL ,
	[start] [datetime] NULL ,
	[answer] [datetime] NULL ,
	[end] [datetime] NULL ,
	[duration] [int] NULL ,
	[billsec] [int] NULL ,
	[disposition] [varchar] (20) NULL ,
	[amaflags] [varchar] (16) NULL ,
	[uniqueid] [varchar] (32) NULL ,
	[userfield] [varchar] (256) NULL
) ON [PRIMARY]

Definition at line 77 of file cdr_tds.c.

Referenced by get_date().


Function Documentation

static void __reg_module ( void  ) [static]

Definition at line 637 of file cdr_tds.c.

static void __unreg_module ( void  ) [static]

Definition at line 637 of file cdr_tds.c.

static char * anti_injection ( const char *  str,
int  len 
) [static]

Definition at line 304 of file cdr_tds.c.

References ast_calloc, ast_log(), len(), LOG_ERROR, str, and strcasestr().

Referenced by tds_log().

{
   /* 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_calloc(1, len + 1))) {
      ast_log(LOG_ERROR, "Out of memory\n");
      return NULL;
   }

   buf_ptr = buf;

   /* Escape single quotes */
   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);
      }
   }

   return buf;
}
static int execute_and_consume ( DBPROCESS *  dbproc,
const char *  fmt,
  ... 
) [static]

Definition at line 350 of file cdr_tds.c.

References ast_vasprintf, and free.

Referenced by mssql_connect().

{
   va_list ap;
   char *buffer;

   va_start(ap, fmt);
   if (ast_vasprintf(&buffer, fmt, ap) < 0) {
      va_end(ap);
      return 1;
   }
   va_end(ap);

   if (dbfcmd(dbproc, buffer) == FAIL) {
      free(buffer);
      return 1;
   }

   free(buffer);

   if (dbsqlexec(dbproc) == FAIL) {
      return 1;
   }

   /* Consume the result set (we don't really care about the result, though) */
   while (dbresults(dbproc) != NO_MORE_RESULTS) {
      while (dbnextrow(dbproc) != NO_MORE_ROWS);
   }

   return 0;
}
static void get_date ( char *  dateField,
size_t  len,
struct timeval  when 
) [static]

Definition at line 338 of file cdr_tds.c.

References ast_copy_string(), ast_localtime(), ast_strftime(), ast_tvzero(), and DATE_FORMAT.

Referenced by tds_log().

{
   /* To make sure we have date variable if not insert null to SQL */
   if (!ast_tvzero(when)) {
      struct ast_tm tm;
      ast_localtime(&when, &tm, NULL);
      ast_strftime(dateField, len, "'" DATE_FORMAT "'", &tm);
   } else {
      ast_copy_string(dateField, "null", len);
   }
}
static int mssql_connect ( void  ) [static]

Definition at line 393 of file cdr_tds.c.

References ast_log(), cdr_tds_config::charset, cdr_tds_config::connected, cdr_tds_config::database, cdr_tds_config::dbproc, execute_and_consume(), cdr_tds_config::has_userfield, cdr_tds_config::hostname, cdr_tds_config::language, LOG_ERROR, LOG_NOTICE, cdr_tds_config::password, cdr_tds_config::table, and cdr_tds_config::username.

Referenced by tds_load_module(), and tds_log().

{
   LOGINREC *login;

   if ((login = dblogin()) == NULL) {
      ast_log(LOG_ERROR, "Unable to allocate login structure for db-lib\n");
      return -1;
   }

   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;
   }

   dbloginfree(login);

   if (dbuse(settings->dbproc, (char *) settings->database) == FAIL) {
      ast_log(LOG_ERROR, "Unable to select database %s\n", settings->database);
      goto failed;
   }

   if (execute_and_consume(settings->dbproc, "SELECT 1 FROM [%s] WHERE 1 = 0", settings->table)) {
      ast_log(LOG_ERROR, "Unable to find table '%s'\n", settings->table);
      goto failed;
   }

   /* Check to see if we have a userfield column in the table */
   if (execute_and_consume(settings->dbproc, "SELECT userfield FROM [%s] WHERE 1 = 0", settings->table)) {
      ast_log(LOG_NOTICE, "Unable to find 'userfield' column in table '%s'\n", settings->table);
      settings->has_userfield = 0;
   } else {
      settings->has_userfield = 1;
   }

   settings->connected = 1;

   return 0;

failed:
   dbclose(settings->dbproc);
   settings->dbproc = NULL;
   return -1;
}
static int mssql_disconnect ( void  ) [static]

Definition at line 381 of file cdr_tds.c.

References cdr_tds_config::connected, and cdr_tds_config::dbproc.

Referenced by tds_load_module(), tds_log(), and tds_unload_module().

{
   if (settings->dbproc) {
      dbclose(settings->dbproc);
      settings->dbproc = NULL;
   }

   settings->connected = 0;

   return 0;
}
static int reload ( void  ) [static]

Definition at line 592 of file cdr_tds.c.

References tds_load_module().

{
   return tds_load_module(1);
}
static int tds_error_handler ( DBPROCESS *  dbproc,
int  severity,
int  dberr,
int  oserr,
char *  dberrstr,
char *  oserrstr 
) [static]

Definition at line 462 of file cdr_tds.c.

References ast_log(), and LOG_ERROR.

Referenced by load_module().

{
   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_load_module ( int  reload) [static]

Definition at line 481 of file cdr_tds.c.

References ast_config_destroy(), ast_config_load, ast_log(), ast_mutex_lock, ast_mutex_unlock, ast_string_field_init, ast_string_field_set, ast_true(), ast_variable_browse(), ast_variable_retrieve(), charset, CONFIG_FLAG_FILEUNCHANGED, CONFIG_STATUS_FILEINVALID, CONFIG_STATUS_FILEUNCHANGED, hostname, language, LOG_ERROR, LOG_NOTICE, mssql_connect(), mssql_disconnect(), table, and tds_lock.

Referenced by load_module(), and reload().

{
   struct ast_config *cfg;
   const char *ptr = NULL;
   struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };

   cfg = ast_config_load(config, config_flags);
   if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
      ast_log(LOG_NOTICE, "Unable to load TDS config for CDRs: %s\n", config);
      return 0;
   } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
      return 0;

   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);

   /* 'connection' is the new preferred configuration option */
   ptr = ast_variable_retrieve(cfg, "global", "connection");
   if (ptr) {
      ast_string_field_set(settings, hostname, ptr);
   } else {
      /* But we keep 'hostname' for backwards compatibility */
      ptr = ast_variable_retrieve(cfg, "global", "hostname");
      if (ptr) {
         ast_string_field_set(settings, hostname, ptr);
      } else {
         ast_log(LOG_ERROR, "Failed to connect: Database server connection not specified.\n");
         goto failed;
      }
   }

   ptr = ast_variable_retrieve(cfg, "global", "dbname");
   if (ptr) {
      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) {
      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) {
      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 (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) {
      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) {
      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");
   }

   ptr = ast_variable_retrieve(cfg, "global", "hrtime");
   if (ptr && ast_true(ptr)) {
      ast_string_field_set(settings, hrtime, ptr);
   } else {
      ast_log(LOG_NOTICE, "High Resolution Time not found, using integers for billsec and duration fields by default.\n");
   }

   mssql_disconnect();

   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 tds_log ( struct ast_cdr cdr) [static]

Definition at line 111 of file cdr_tds.c.

References accountcode, anti_injection(), ast_cdr_disp2str(), ast_cdr_flags2str(), ast_free, ast_log(), AST_MAX_USER_FIELD, ast_mutex_lock, ast_mutex_unlock, ast_tvdiff_us(), ast_tvzero(), cdr_tds_config::connected, cdr_tds_config::dbproc, get_date(), cdr_tds_config::has_userfield, cdr_tds_config::hostname, cdr_tds_config::hrtime, LOG_ERROR, LOG_NOTICE, mssql_connect(), mssql_disconnect(), cdr_tds_config::table, and tds_lock.

Referenced by load_module().

{
   char start[80], answer[80], end[80];
   char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid, *userfield = NULL;
   RETCODE erc;
   int res = -1;
   int attempt = 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);

   if (settings->has_userfield) {
      userfield = anti_injection(cdr->userfield, AST_MAX_USER_FIELD);
   }

retry:
   /* Ensure that we are connected */
   if (!settings->connected) {
      ast_log(LOG_NOTICE, "Attempting to reconnect to %s (Attempt %d)\n", settings->hostname, attempt);
      if (mssql_connect()) {
         /* Connect failed */
         if (attempt++ < 3) {
            goto retry;
         }
         goto done;
      }
   }

   if (settings->has_userfield) {
      if (settings->hrtime) {
         double hrbillsec = 0.0;
         double hrduration;

         if (!ast_tvzero(cdr->answer)) {
            hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
         }
         hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);

         erc = dbfcmd(settings->dbproc,
                "INSERT INTO %s "
                "("
                "accountcode, src, dst, dcontext, clid, channel, "
                "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
                "billsec, disposition, amaflags, uniqueid, userfield"
                ") "
                "VALUES "
                "("
                "'%s', '%s', '%s', '%s', '%s', '%s', "
                "'%s', '%s', '%s', %s, %s, %s, %lf, "
                "%lf, '%s', '%s', '%s', '%s'"
                ")",
                settings->table,
                accountcode, src, dst, dcontext, clid, channel,
                dstchannel, lastapp, lastdata, start, answer, end, hrduration,
                hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
                userfield
         );
      } else {
         erc = dbfcmd(settings->dbproc,
                "INSERT INTO %s "
                "("
                "accountcode, src, dst, dcontext, clid, channel, "
                "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
                "billsec, disposition, amaflags, uniqueid, userfield"
                ") "
                "VALUES "
                "("
                "'%s', '%s', '%s', '%s', '%s', '%s', "
                "'%s', '%s', '%s', %s, %s, %s, %ld, "
                "%ld, '%s', '%s', '%s', '%s'"
                ")",
                settings->table,
                accountcode, src, dst, dcontext, clid, channel,
                dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
                cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
                userfield
         );
      }
   } else {
      if (settings->hrtime) {
         double hrbillsec = 0.0;
         double hrduration;

         if (!ast_tvzero(cdr->answer)) {
            hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
         }
         hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);

         erc = dbfcmd(settings->dbproc,
                "INSERT INTO %s "
                "("
                "accountcode, src, dst, dcontext, clid, channel, "
                "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
                "billsec, disposition, amaflags, uniqueid"
                ") "
                "VALUES "
                "("
                "'%s', '%s', '%s', '%s', '%s', '%s', "
                "'%s', '%s', '%s', %s, %s, %s, %lf, "
                "%lf, '%s', '%s', '%s'"
                ")",
                settings->table,
                accountcode, src, dst, dcontext, clid, channel,
                dstchannel, lastapp, lastdata, start, answer, end, hrduration,
                hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
         );
      } else {
         erc = dbfcmd(settings->dbproc,
                "INSERT INTO %s "
                "("
                "accountcode, src, dst, dcontext, clid, channel, "
                "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
                "billsec, disposition, amaflags, uniqueid"
                ") "
                "VALUES "
                "("
                "'%s', '%s', '%s', '%s', '%s', '%s', "
                "'%s', '%s', '%s', %s, %s, %s, %ld, "
                "%ld, '%s', '%s', '%s'"
                ")",
                settings->table,
                accountcode, src, dst, dcontext, clid, channel,
                dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
                cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
         );
      }
   }

   if (erc == FAIL) {
      if (attempt++ < 3) {
         ast_log(LOG_NOTICE, "Failed to build INSERT statement, retrying...\n");
         mssql_disconnect();
         goto retry;
      } else {
         ast_log(LOG_ERROR, "Failed to build INSERT statement, no CDR was logged.\n");
         goto done;
      }
   }

   if (dbsqlexec(settings->dbproc) == FAIL) {
      if (attempt++ < 3) {
         ast_log(LOG_NOTICE, "Failed to execute INSERT statement, retrying...\n");
         mssql_disconnect();
         goto retry;
      } else {
         ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CDR was logged.\n");
         goto done;
      }
   }

   /* 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);
   }

   res = 0;

done:
   ast_mutex_unlock(&tds_lock);

   ast_free(accountcode);
   ast_free(src);
   ast_free(dst);
   ast_free(dcontext);
   ast_free(clid);
   ast_free(channel);
   ast_free(dstchannel);
   ast_free(lastapp);
   ast_free(lastdata);
   ast_free(uniqueid);

   if (userfield) {
      ast_free(userfield);
   }

   return res;
}
static int tds_message_handler ( DBPROCESS *  dbproc,
DBINT  msgno,
int  msgstate,
int  severity,
char *  msgtext,
char *  srvname,
char *  procname,
int  line 
) [static]

Definition at line 473 of file cdr_tds.c.

References ast_debug, ast_log(), and LOG_NOTICE.

Referenced by load_module().

{
   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 unload_module ( void  ) [static]

Definition at line 627 of file cdr_tds.c.

References tds_unload_module().

{
   return tds_unload_module();
}

Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "FreeTDS CDR Backend" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, } [static]

Definition at line 637 of file cdr_tds.c.

Definition at line 637 of file cdr_tds.c.

const char config[] = "cdr_tds.conf" [static]

Definition at line 80 of file cdr_tds.c.

const char name[] = "FreeTDS (MSSQL)" [static]

Definition at line 79 of file cdr_tds.c.

struct cdr_tds_config* settings [static]

Definition at line 100 of file cdr_tds.c.

ast_mutex_t tds_lock = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP , NULL, 1 } [static]

Definition at line 98 of file cdr_tds.c.

Referenced by tds_load_module(), tds_log(), and tds_unload_module().