72 #define DATE_FORMAT "%Y/%m/%d %T"
74 static const char name[] =
"FreeTDS (MSSQL)";
75 static const char config[] =
"cdr_tds.conf";
89 unsigned int connected:1;
90 unsigned int has_userfield:1;
93 AST_MUTEX_DEFINE_STATIC(tds_lock);
97 static char *anti_injection(
const char *,
int);
98 static void get_date(
char *,
size_t len,
struct timeval);
100 static int execute_and_consume(DBPROCESS *dbproc,
const char *fmt, ...)
101 __attribute__((format(printf, 2, 3)));
103 static
int mssql_connect(
void);
104 static
int mssql_disconnect(
void);
106 static
int tds_log(struct
ast_cdr *cdr)
108 char start[80], answer[80], end[80];
109 char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid, *userfield = NULL;
114 accountcode = anti_injection(cdr->accountcode, 20);
115 src = anti_injection(cdr->src, 80);
116 dst = anti_injection(cdr->dst, 80);
117 dcontext = anti_injection(cdr->dcontext, 80);
118 clid = anti_injection(cdr->clid, 80);
119 channel = anti_injection(cdr->channel, 80);
120 dstchannel = anti_injection(cdr->dstchannel, 80);
121 lastapp = anti_injection(cdr->lastapp, 80);
122 lastdata = anti_injection(cdr->lastdata, 80);
123 uniqueid = anti_injection(cdr->uniqueid, 32);
125 get_date(start,
sizeof(start), cdr->start);
126 get_date(answer,
sizeof(answer), cdr->answer);
127 get_date(end,
sizeof(end), cdr->end);
129 ast_mutex_lock(&tds_lock);
131 if (settings->has_userfield) {
137 if (!settings->connected) {
138 ast_log(LOG_NOTICE,
"Attempting to reconnect to %s (Attempt %d)\n", settings->hostname, attempt);
139 if (mssql_connect()) {
148 if (settings->has_userfield) {
149 if (settings->hrtime) {
150 double hrbillsec = 0.0;
154 hrbillsec = (double)(
ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
156 hrduration = (double)(
ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
158 erc = dbfcmd(settings->dbproc,
161 "accountcode, src, dst, dcontext, clid, channel, "
162 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
163 "billsec, disposition, amaflags, uniqueid, userfield"
167 "'%s', '%s', '%s', '%s', '%s', '%s', "
168 "'%s', '%s', '%s', %s, %s, %s, %lf, "
169 "%lf, '%s', '%s', '%s', '%s'"
172 accountcode, src, dst, dcontext, clid, channel,
173 dstchannel, lastapp, lastdata, start, answer, end, hrduration,
178 erc = dbfcmd(settings->dbproc,
181 "accountcode, src, dst, dcontext, clid, channel, "
182 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
183 "billsec, disposition, amaflags, uniqueid, userfield"
187 "'%s', '%s', '%s', '%s', '%s', '%s', "
188 "'%s', '%s', '%s', %s, %s, %s, %ld, "
189 "%ld, '%s', '%s', '%s', '%s'"
192 accountcode, src, dst, dcontext, clid, channel,
193 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
199 if (settings->hrtime) {
200 double hrbillsec = 0.0;
204 hrbillsec = (double)(
ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
206 hrduration = (double)(
ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
208 erc = dbfcmd(settings->dbproc,
211 "accountcode, src, dst, dcontext, clid, channel, "
212 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
213 "billsec, disposition, amaflags, uniqueid"
217 "'%s', '%s', '%s', '%s', '%s', '%s', "
218 "'%s', '%s', '%s', %s, %s, %s, %lf, "
219 "%lf, '%s', '%s', '%s'"
222 accountcode, src, dst, dcontext, clid, channel,
223 dstchannel, lastapp, lastdata, start, answer, end, hrduration,
227 erc = dbfcmd(settings->dbproc,
230 "accountcode, src, dst, dcontext, clid, channel, "
231 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
232 "billsec, disposition, amaflags, uniqueid"
236 "'%s', '%s', '%s', '%s', '%s', '%s', "
237 "'%s', '%s', '%s', %s, %s, %s, %ld, "
238 "%ld, '%s', '%s', '%s'"
241 accountcode, src, dst, dcontext, clid, channel,
242 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
250 ast_log(LOG_NOTICE,
"Failed to build INSERT statement, retrying...\n");
254 ast_log(LOG_ERROR,
"Failed to build INSERT statement, no CDR was logged.\n");
259 if (dbsqlexec(settings->dbproc) == FAIL) {
261 ast_log(LOG_NOTICE,
"Failed to execute INSERT statement, retrying...\n");
265 ast_log(LOG_ERROR,
"Failed to execute INSERT statement, no CDR was logged.\n");
272 while (dbresults(settings->dbproc) != NO_MORE_RESULTS) {
273 while (dbnextrow(settings->dbproc) != NO_MORE_ROWS);
279 ast_mutex_unlock(&tds_lock);
281 ast_free(accountcode);
287 ast_free(dstchannel);
299 static char *anti_injection(
const char *str,
int len)
303 char *buf_ptr, *srh_ptr;
304 char *known_bad[] = {
"select",
"insert",
"update",
"delete",
"drop",
";",
"--",
"\0"};
308 ast_log(LOG_ERROR,
"Out of memory\n");
315 for (; *str && strlen(buf) < len; str++) {
324 for (idx = 0; *known_bad[idx]; idx++) {
325 while ((srh_ptr = strcasestr(buf, known_bad[idx]))) {
326 memmove(srh_ptr, srh_ptr + strlen(known_bad[idx]), strlen(srh_ptr + strlen(known_bad[idx])) + 1);
333 static void get_date(
char *dateField,
size_t len,
struct timeval when)
345 static int execute_and_consume(DBPROCESS *dbproc,
const char *fmt, ...)
357 if (dbfcmd(dbproc, buffer) == FAIL) {
364 if (dbsqlexec(dbproc) == FAIL) {
369 while (dbresults(dbproc) != NO_MORE_RESULTS) {
370 while (dbnextrow(dbproc) != NO_MORE_ROWS);
376 static int mssql_disconnect(
void)
378 if (settings->dbproc) {
379 dbclose(settings->dbproc);
380 settings->dbproc = NULL;
383 settings->connected = 0;
388 static int mssql_connect(
void)
392 if ((login = dblogin()) == NULL) {
393 ast_log(LOG_ERROR,
"Unable to allocate login structure for db-lib\n");
397 DBSETLAPP(login,
"TSQL");
398 DBSETLUSER(login, (
char *) settings->username);
399 DBSETLPWD(login, (
char *) settings->password);
400 DBSETLCHARSET(login, (
char *) settings->charset);
401 DBSETLNATLANG(login, (
char *) settings->language);
403 if ((settings->dbproc = dbopen(login, (
char *) settings->hostname)) == NULL) {
404 ast_log(LOG_ERROR,
"Unable to connect to %s\n", settings->hostname);
411 if (dbuse(settings->dbproc, (
char *) settings->database) == FAIL) {
412 ast_log(LOG_ERROR,
"Unable to select database %s\n", settings->database);
416 if (execute_and_consume(settings->dbproc,
"SELECT 1 FROM [%s] WHERE 1 = 0", settings->table)) {
417 ast_log(LOG_ERROR,
"Unable to find table '%s'\n", settings->table);
422 if (execute_and_consume(settings->dbproc,
"SELECT userfield FROM [%s] WHERE 1 = 0", settings->table)) {
423 ast_log(LOG_NOTICE,
"Unable to find 'userfield' column in table '%s'\n", settings->table);
424 settings->has_userfield = 0;
426 settings->has_userfield = 1;
429 settings->connected = 1;
434 dbclose(settings->dbproc);
435 settings->dbproc = NULL;
439 static int tds_unload_module(
void)
446 ast_mutex_lock(&tds_lock);
448 ast_mutex_unlock(&tds_lock);
459 static int tds_error_handler(DBPROCESS *dbproc,
int severity,
int dberr,
int oserr,
char *dberrstr,
char *oserrstr)
461 ast_log(LOG_ERROR,
"%s (%d)\n", dberrstr, dberr);
463 if (oserr != DBNOERR) {
464 ast_log(LOG_ERROR,
"%s (%d)\n", oserrstr, oserr);
470 static int tds_message_handler(DBPROCESS *dbproc, DBINT msgno,
int msgstate,
int severity,
char *msgtext,
char *srvname,
char *procname,
int line)
472 ast_debug(1,
"Msg %d, Level %d, State %d, Line %d\n", msgno, severity, msgstate, line);
473 ast_log(LOG_NOTICE,
"%s\n", msgtext);
478 static int tds_load_module(
int reload)
481 const char *ptr = NULL;
485 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
486 ast_log(LOG_NOTICE,
"Unable to load TDS config for CDRs: %s\n", config);
488 }
else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
491 if (!ast_variable_browse(cfg,
"global")) {
497 ast_mutex_lock(&tds_lock);
503 ptr = ast_variable_retrieve(cfg,
"global",
"connection");
508 ptr = ast_variable_retrieve(cfg,
"global",
"hostname");
512 ast_log(LOG_ERROR,
"Failed to connect: Database server connection not specified.\n");
517 ptr = ast_variable_retrieve(cfg,
"global",
"dbname");
521 ast_log(LOG_ERROR,
"Failed to connect: Database dbname not specified.\n");
525 ptr = ast_variable_retrieve(cfg,
"global",
"user");
529 ast_log(LOG_ERROR,
"Failed to connect: Database dbuser not specified.\n");
533 ptr = ast_variable_retrieve(cfg,
"global",
"password");
537 ast_log(LOG_ERROR,
"Failed to connect: Database password not specified.\n");
541 ptr = ast_variable_retrieve(cfg,
"global",
"charset");
548 ptr = ast_variable_retrieve(cfg,
"global",
"language");
555 ptr = ast_variable_retrieve(cfg,
"global",
"table");
559 ast_log(LOG_NOTICE,
"Table name not specified, using 'cdr' by default.\n");
563 ptr = ast_variable_retrieve(cfg,
"global",
"hrtime");
567 ast_log(LOG_NOTICE,
"High Resolution Time not found, using integers for billsec and duration fields by default.\n");
572 if (mssql_connect()) {
577 ast_mutex_unlock(&tds_lock);
583 ast_mutex_unlock(&tds_lock);
589 static int reload(
void)
591 return tds_load_module(1);
594 static int load_module(
void)
596 if (dbinit() == FAIL) {
597 ast_log(LOG_ERROR,
"Failed to initialize FreeTDS db-lib\n");
601 dberrhandle(tds_error_handler);
602 dbmsghandle(tds_message_handler);
611 if (!tds_load_module(0)) {
624 static int unload_module(
void)
626 return tds_unload_module();
629 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER,
"FreeTDS CDR Backend",
630 .support_level = AST_MODULE_SUPPORT_EXTENDED,
632 .unload = unload_module,
Asterisk main include file. File version handling, generic pbx functions.
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
#define ast_calloc_with_stringfields(n, type, size)
Allocate a structure with embedded stringfields in a single allocation.
const char * ast_channel_amaflags2string(enum ama_flags flags)
Convert the enum representation of an AMA flag to a string representation.
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
#define ast_vasprintf(ret, fmt, ap)
A wrapper for vasprintf()
static int get_date(char *s, int len)
Gets the current date and time, as formatted string.
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
General Asterisk PBX channel definitions.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
#define AST_STRING_FIELD(name)
Declare a string field.
#define ast_debug(level,...)
Log a DEBUG message.
Responsible for call detail data.
const char * ast_cdr_disp2str(int disposition)
Disposition to a string.
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
#define ast_calloc(num, len)
A wrapper for calloc()
Module has failed to load, may be in an inconsistent state.
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Structure used to handle boolean flags.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
#define AST_MAX_USER_FIELD