00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065 #include "asterisk.h"
00066
00067 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 371590 $")
00068
00069 #include "asterisk/config.h"
00070 #include "asterisk/channel.h"
00071 #include "asterisk/cdr.h"
00072 #include "asterisk/module.h"
00073
00074 #include <sqlfront.h>
00075 #include <sybdb.h>
00076
00077 #define DATE_FORMAT "%Y/%m/%d %T"
00078
00079 static const char name[] = "FreeTDS (MSSQL)";
00080 static const char config[] = "cdr_tds.conf";
00081
00082 struct cdr_tds_config {
00083 AST_DECLARE_STRING_FIELDS(
00084 AST_STRING_FIELD(hostname);
00085 AST_STRING_FIELD(database);
00086 AST_STRING_FIELD(username);
00087 AST_STRING_FIELD(password);
00088 AST_STRING_FIELD(table);
00089 AST_STRING_FIELD(charset);
00090 AST_STRING_FIELD(language);
00091 AST_STRING_FIELD(hrtime);
00092 );
00093 DBPROCESS *dbproc;
00094 unsigned int connected:1;
00095 unsigned int has_userfield:1;
00096 };
00097
00098 AST_MUTEX_DEFINE_STATIC(tds_lock);
00099
00100 static struct cdr_tds_config *settings;
00101
00102 static char *anti_injection(const char *, int);
00103 static void get_date(char *, size_t len, struct timeval);
00104
00105 static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
00106 __attribute__((format(printf, 2, 3)));
00107
00108 static int mssql_connect(void);
00109 static int mssql_disconnect(void);
00110
00111 static int tds_log(struct ast_cdr *cdr)
00112 {
00113 char start[80], answer[80], end[80];
00114 char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid, *userfield = NULL;
00115 RETCODE erc;
00116 int res = -1;
00117 int attempt = 1;
00118
00119 accountcode = anti_injection(cdr->accountcode, 20);
00120 src = anti_injection(cdr->src, 80);
00121 dst = anti_injection(cdr->dst, 80);
00122 dcontext = anti_injection(cdr->dcontext, 80);
00123 clid = anti_injection(cdr->clid, 80);
00124 channel = anti_injection(cdr->channel, 80);
00125 dstchannel = anti_injection(cdr->dstchannel, 80);
00126 lastapp = anti_injection(cdr->lastapp, 80);
00127 lastdata = anti_injection(cdr->lastdata, 80);
00128 uniqueid = anti_injection(cdr->uniqueid, 32);
00129
00130 get_date(start, sizeof(start), cdr->start);
00131 get_date(answer, sizeof(answer), cdr->answer);
00132 get_date(end, sizeof(end), cdr->end);
00133
00134 ast_mutex_lock(&tds_lock);
00135
00136 if (settings->has_userfield) {
00137 userfield = anti_injection(cdr->userfield, AST_MAX_USER_FIELD);
00138 }
00139
00140 retry:
00141
00142 if (!settings->connected) {
00143 ast_log(LOG_NOTICE, "Attempting to reconnect to %s (Attempt %d)\n", settings->hostname, attempt);
00144 if (mssql_connect()) {
00145
00146 if (attempt++ < 3) {
00147 goto retry;
00148 }
00149 goto done;
00150 }
00151 }
00152
00153 if (settings->has_userfield) {
00154 if (settings->hrtime) {
00155 double hrbillsec = 0.0;
00156 double hrduration;
00157
00158 if (!ast_tvzero(cdr->answer)) {
00159 hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
00160 }
00161 hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
00162
00163 erc = dbfcmd(settings->dbproc,
00164 "INSERT INTO %s "
00165 "("
00166 "accountcode, src, dst, dcontext, clid, channel, "
00167 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
00168 "billsec, disposition, amaflags, uniqueid, userfield"
00169 ") "
00170 "VALUES "
00171 "("
00172 "'%s', '%s', '%s', '%s', '%s', '%s', "
00173 "'%s', '%s', '%s', %s, %s, %s, %lf, "
00174 "%lf, '%s', '%s', '%s', '%s'"
00175 ")",
00176 settings->table,
00177 accountcode, src, dst, dcontext, clid, channel,
00178 dstchannel, lastapp, lastdata, start, answer, end, hrduration,
00179 hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
00180 userfield
00181 );
00182 } else {
00183 erc = dbfcmd(settings->dbproc,
00184 "INSERT INTO %s "
00185 "("
00186 "accountcode, src, dst, dcontext, clid, channel, "
00187 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
00188 "billsec, disposition, amaflags, uniqueid, userfield"
00189 ") "
00190 "VALUES "
00191 "("
00192 "'%s', '%s', '%s', '%s', '%s', '%s', "
00193 "'%s', '%s', '%s', %s, %s, %s, %ld, "
00194 "%ld, '%s', '%s', '%s', '%s'"
00195 ")",
00196 settings->table,
00197 accountcode, src, dst, dcontext, clid, channel,
00198 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
00199 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
00200 userfield
00201 );
00202 }
00203 } else {
00204 if (settings->hrtime) {
00205 double hrbillsec = 0.0;
00206 double hrduration;
00207
00208 if (!ast_tvzero(cdr->answer)) {
00209 hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
00210 }
00211 hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
00212
00213 erc = dbfcmd(settings->dbproc,
00214 "INSERT INTO %s "
00215 "("
00216 "accountcode, src, dst, dcontext, clid, channel, "
00217 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
00218 "billsec, disposition, amaflags, uniqueid"
00219 ") "
00220 "VALUES "
00221 "("
00222 "'%s', '%s', '%s', '%s', '%s', '%s', "
00223 "'%s', '%s', '%s', %s, %s, %s, %lf, "
00224 "%lf, '%s', '%s', '%s'"
00225 ")",
00226 settings->table,
00227 accountcode, src, dst, dcontext, clid, channel,
00228 dstchannel, lastapp, lastdata, start, answer, end, hrduration,
00229 hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
00230 );
00231 } else {
00232 erc = dbfcmd(settings->dbproc,
00233 "INSERT INTO %s "
00234 "("
00235 "accountcode, src, dst, dcontext, clid, channel, "
00236 "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
00237 "billsec, disposition, amaflags, uniqueid"
00238 ") "
00239 "VALUES "
00240 "("
00241 "'%s', '%s', '%s', '%s', '%s', '%s', "
00242 "'%s', '%s', '%s', %s, %s, %s, %ld, "
00243 "%ld, '%s', '%s', '%s'"
00244 ")",
00245 settings->table,
00246 accountcode, src, dst, dcontext, clid, channel,
00247 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
00248 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
00249 );
00250 }
00251 }
00252
00253 if (erc == FAIL) {
00254 if (attempt++ < 3) {
00255 ast_log(LOG_NOTICE, "Failed to build INSERT statement, retrying...\n");
00256 mssql_disconnect();
00257 goto retry;
00258 } else {
00259 ast_log(LOG_ERROR, "Failed to build INSERT statement, no CDR was logged.\n");
00260 goto done;
00261 }
00262 }
00263
00264 if (dbsqlexec(settings->dbproc) == FAIL) {
00265 if (attempt++ < 3) {
00266 ast_log(LOG_NOTICE, "Failed to execute INSERT statement, retrying...\n");
00267 mssql_disconnect();
00268 goto retry;
00269 } else {
00270 ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CDR was logged.\n");
00271 goto done;
00272 }
00273 }
00274
00275
00276
00277 while (dbresults(settings->dbproc) != NO_MORE_RESULTS) {
00278 while (dbnextrow(settings->dbproc) != NO_MORE_ROWS);
00279 }
00280
00281 res = 0;
00282
00283 done:
00284 ast_mutex_unlock(&tds_lock);
00285
00286 ast_free(accountcode);
00287 ast_free(src);
00288 ast_free(dst);
00289 ast_free(dcontext);
00290 ast_free(clid);
00291 ast_free(channel);
00292 ast_free(dstchannel);
00293 ast_free(lastapp);
00294 ast_free(lastdata);
00295 ast_free(uniqueid);
00296
00297 if (userfield) {
00298 ast_free(userfield);
00299 }
00300
00301 return res;
00302 }
00303
00304 static char *anti_injection(const char *str, int len)
00305 {
00306
00307 char *buf;
00308 char *buf_ptr, *srh_ptr;
00309 char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
00310 int idx;
00311
00312 if (!(buf = ast_calloc(1, len + 1))) {
00313 ast_log(LOG_ERROR, "Out of memory\n");
00314 return NULL;
00315 }
00316
00317 buf_ptr = buf;
00318
00319
00320 for (; *str && strlen(buf) < len; str++) {
00321 if (*str == '\'') {
00322 *buf_ptr++ = '\'';
00323 }
00324 *buf_ptr++ = *str;
00325 }
00326 *buf_ptr = '\0';
00327
00328
00329 for (idx = 0; *known_bad[idx]; idx++) {
00330 while ((srh_ptr = strcasestr(buf, known_bad[idx]))) {
00331 memmove(srh_ptr, srh_ptr + strlen(known_bad[idx]), strlen(srh_ptr + strlen(known_bad[idx])) + 1);
00332 }
00333 }
00334
00335 return buf;
00336 }
00337
00338 static void get_date(char *dateField, size_t len, struct timeval when)
00339 {
00340
00341 if (!ast_tvzero(when)) {
00342 struct ast_tm tm;
00343 ast_localtime(&when, &tm, NULL);
00344 ast_strftime(dateField, len, "'" DATE_FORMAT "'", &tm);
00345 } else {
00346 ast_copy_string(dateField, "null", len);
00347 }
00348 }
00349
00350 static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
00351 {
00352 va_list ap;
00353 char *buffer;
00354
00355 va_start(ap, fmt);
00356 if (ast_vasprintf(&buffer, fmt, ap) < 0) {
00357 va_end(ap);
00358 return 1;
00359 }
00360 va_end(ap);
00361
00362 if (dbfcmd(dbproc, buffer) == FAIL) {
00363 ast_free(buffer);
00364 return 1;
00365 }
00366
00367 ast_free(buffer);
00368
00369 if (dbsqlexec(dbproc) == FAIL) {
00370 return 1;
00371 }
00372
00373
00374 while (dbresults(dbproc) != NO_MORE_RESULTS) {
00375 while (dbnextrow(dbproc) != NO_MORE_ROWS);
00376 }
00377
00378 return 0;
00379 }
00380
00381 static int mssql_disconnect(void)
00382 {
00383 if (settings->dbproc) {
00384 dbclose(settings->dbproc);
00385 settings->dbproc = NULL;
00386 }
00387
00388 settings->connected = 0;
00389
00390 return 0;
00391 }
00392
00393 static int mssql_connect(void)
00394 {
00395 LOGINREC *login;
00396
00397 if ((login = dblogin()) == NULL) {
00398 ast_log(LOG_ERROR, "Unable to allocate login structure for db-lib\n");
00399 return -1;
00400 }
00401
00402 DBSETLAPP(login, "TSQL");
00403 DBSETLUSER(login, (char *) settings->username);
00404 DBSETLPWD(login, (char *) settings->password);
00405 DBSETLCHARSET(login, (char *) settings->charset);
00406 DBSETLNATLANG(login, (char *) settings->language);
00407
00408 if ((settings->dbproc = dbopen(login, (char *) settings->hostname)) == NULL) {
00409 ast_log(LOG_ERROR, "Unable to connect to %s\n", settings->hostname);
00410 dbloginfree(login);
00411 return -1;
00412 }
00413
00414 dbloginfree(login);
00415
00416 if (dbuse(settings->dbproc, (char *) settings->database) == FAIL) {
00417 ast_log(LOG_ERROR, "Unable to select database %s\n", settings->database);
00418 goto failed;
00419 }
00420
00421 if (execute_and_consume(settings->dbproc, "SELECT 1 FROM [%s] WHERE 1 = 0", settings->table)) {
00422 ast_log(LOG_ERROR, "Unable to find table '%s'\n", settings->table);
00423 goto failed;
00424 }
00425
00426
00427 if (execute_and_consume(settings->dbproc, "SELECT userfield FROM [%s] WHERE 1 = 0", settings->table)) {
00428 ast_log(LOG_NOTICE, "Unable to find 'userfield' column in table '%s'\n", settings->table);
00429 settings->has_userfield = 0;
00430 } else {
00431 settings->has_userfield = 1;
00432 }
00433
00434 settings->connected = 1;
00435
00436 return 0;
00437
00438 failed:
00439 dbclose(settings->dbproc);
00440 settings->dbproc = NULL;
00441 return -1;
00442 }
00443
00444 static int tds_unload_module(void)
00445 {
00446 if (settings) {
00447 ast_mutex_lock(&tds_lock);
00448 mssql_disconnect();
00449 ast_mutex_unlock(&tds_lock);
00450
00451 ast_string_field_free_memory(settings);
00452 ast_free(settings);
00453 }
00454
00455 ast_cdr_unregister(name);
00456
00457 dbexit();
00458
00459 return 0;
00460 }
00461
00462 static int tds_error_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr)
00463 {
00464 ast_log(LOG_ERROR, "%s (%d)\n", dberrstr, dberr);
00465
00466 if (oserr != DBNOERR) {
00467 ast_log(LOG_ERROR, "%s (%d)\n", oserrstr, oserr);
00468 }
00469
00470 return INT_CANCEL;
00471 }
00472
00473 static int tds_message_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line)
00474 {
00475 ast_debug(1, "Msg %d, Level %d, State %d, Line %d\n", msgno, severity, msgstate, line);
00476 ast_log(LOG_NOTICE, "%s\n", msgtext);
00477
00478 return 0;
00479 }
00480
00481 static int tds_load_module(int reload)
00482 {
00483 struct ast_config *cfg;
00484 const char *ptr = NULL;
00485 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00486
00487 cfg = ast_config_load(config, config_flags);
00488 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
00489 ast_log(LOG_NOTICE, "Unable to load TDS config for CDRs: %s\n", config);
00490 return 0;
00491 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
00492 return 0;
00493
00494 if (!ast_variable_browse(cfg, "global")) {
00495
00496 ast_config_destroy(cfg);
00497 return 0;
00498 }
00499
00500 ast_mutex_lock(&tds_lock);
00501
00502
00503 ast_string_field_init(settings, 0);
00504
00505
00506 ptr = ast_variable_retrieve(cfg, "global", "connection");
00507 if (ptr) {
00508 ast_string_field_set(settings, hostname, ptr);
00509 } else {
00510
00511 ptr = ast_variable_retrieve(cfg, "global", "hostname");
00512 if (ptr) {
00513 ast_string_field_set(settings, hostname, ptr);
00514 } else {
00515 ast_log(LOG_ERROR, "Failed to connect: Database server connection not specified.\n");
00516 goto failed;
00517 }
00518 }
00519
00520 ptr = ast_variable_retrieve(cfg, "global", "dbname");
00521 if (ptr) {
00522 ast_string_field_set(settings, database, ptr);
00523 } else {
00524 ast_log(LOG_ERROR, "Failed to connect: Database dbname not specified.\n");
00525 goto failed;
00526 }
00527
00528 ptr = ast_variable_retrieve(cfg, "global", "user");
00529 if (ptr) {
00530 ast_string_field_set(settings, username, ptr);
00531 } else {
00532 ast_log(LOG_ERROR, "Failed to connect: Database dbuser not specified.\n");
00533 goto failed;
00534 }
00535
00536 ptr = ast_variable_retrieve(cfg, "global", "password");
00537 if (ptr) {
00538 ast_string_field_set(settings, password, ptr);
00539 } else {
00540 ast_log(LOG_ERROR, "Failed to connect: Database password not specified.\n");
00541 goto failed;
00542 }
00543
00544 ptr = ast_variable_retrieve(cfg, "global", "charset");
00545 if (ptr) {
00546 ast_string_field_set(settings, charset, ptr);
00547 } else {
00548 ast_string_field_set(settings, charset, "iso_1");
00549 }
00550
00551 ptr = ast_variable_retrieve(cfg, "global", "language");
00552 if (ptr) {
00553 ast_string_field_set(settings, language, ptr);
00554 } else {
00555 ast_string_field_set(settings, language, "us_english");
00556 }
00557
00558 ptr = ast_variable_retrieve(cfg, "global", "table");
00559 if (ptr) {
00560 ast_string_field_set(settings, table, ptr);
00561 } else {
00562 ast_log(LOG_NOTICE, "Table name not specified, using 'cdr' by default.\n");
00563 ast_string_field_set(settings, table, "cdr");
00564 }
00565
00566 ptr = ast_variable_retrieve(cfg, "global", "hrtime");
00567 if (ptr && ast_true(ptr)) {
00568 ast_string_field_set(settings, hrtime, ptr);
00569 } else {
00570 ast_log(LOG_NOTICE, "High Resolution Time not found, using integers for billsec and duration fields by default.\n");
00571 }
00572
00573 mssql_disconnect();
00574
00575 if (mssql_connect()) {
00576
00577 goto failed;
00578 }
00579
00580 ast_mutex_unlock(&tds_lock);
00581 ast_config_destroy(cfg);
00582
00583 return 1;
00584
00585 failed:
00586 ast_mutex_unlock(&tds_lock);
00587 ast_config_destroy(cfg);
00588
00589 return 0;
00590 }
00591
00592 static int reload(void)
00593 {
00594 return tds_load_module(1);
00595 }
00596
00597 static int load_module(void)
00598 {
00599 if (dbinit() == FAIL) {
00600 ast_log(LOG_ERROR, "Failed to initialize FreeTDS db-lib\n");
00601 return AST_MODULE_LOAD_DECLINE;
00602 }
00603
00604 dberrhandle(tds_error_handler);
00605 dbmsghandle(tds_message_handler);
00606
00607 settings = ast_calloc_with_stringfields(1, struct cdr_tds_config, 256);
00608
00609 if (!settings) {
00610 dbexit();
00611 return AST_MODULE_LOAD_DECLINE;
00612 }
00613
00614 if (!tds_load_module(0)) {
00615 ast_string_field_free_memory(settings);
00616 ast_free(settings);
00617 settings = NULL;
00618 dbexit();
00619 return AST_MODULE_LOAD_DECLINE;
00620 }
00621
00622 ast_cdr_register(name, ast_module_info->description, tds_log);
00623
00624 return AST_MODULE_LOAD_SUCCESS;
00625 }
00626
00627 static int unload_module(void)
00628 {
00629 return tds_unload_module();
00630 }
00631
00632 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "FreeTDS CDR Backend",
00633 .load = load_module,
00634 .unload = unload_module,
00635 .reload = reload,
00636 .load_pri = AST_MODPRI_CDR_DRIVER,
00637 );