cdr_odbc.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2003-2005, Digium, Inc.
  5. *
  6. * Brian K. West <brian@bkw.org>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*! \file
  19. *
  20. * \brief ODBC CDR Backend
  21. *
  22. * \author Brian K. West <brian@bkw.org>
  23. *
  24. * See also:
  25. * \arg http://www.unixodbc.org
  26. * \arg \ref Config_cdr
  27. * \ingroup cdr_drivers
  28. */
  29. #include <sys/types.h>
  30. #include <stdio.h>
  31. #include <string.h>
  32. #include <stdlib.h>
  33. #include <unistd.h>
  34. #include <time.h>
  35. #ifndef __CYGWIN__
  36. #include <sql.h>
  37. #include <sqlext.h>
  38. #include <sqltypes.h>
  39. #else
  40. #include <windows.h>
  41. #include <w32api/sql.h>
  42. #include <w32api/sqlext.h>
  43. #include <w32api/sqltypes.h>
  44. #endif
  45. #include "asterisk.h"
  46. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  47. #include "asterisk/config.h"
  48. #include "asterisk/options.h"
  49. #include "asterisk/channel.h"
  50. #include "asterisk/cdr.h"
  51. #include "asterisk/module.h"
  52. #include "asterisk/logger.h"
  53. #define DATE_FORMAT "%Y-%m-%d %T"
  54. static char *desc = "ODBC CDR Backend";
  55. static char *name = "ODBC";
  56. static char *config = "cdr_odbc.conf";
  57. static char *dsn = NULL, *username = NULL, *password = NULL, *table = NULL;
  58. static int loguniqueid = 0;
  59. static int usegmtime = 0;
  60. static int dispositionstring = 0;
  61. static int connected = 0;
  62. AST_MUTEX_DEFINE_STATIC(odbc_lock);
  63. static int odbc_do_query(void);
  64. static int odbc_init(void);
  65. static SQLHENV ODBC_env = SQL_NULL_HANDLE; /* global ODBC Environment */
  66. static SQLHDBC ODBC_con; /* global ODBC Connection Handle */
  67. static SQLHSTMT ODBC_stmt; /* global ODBC Statement Handle */
  68. static int odbc_log(struct ast_cdr *cdr)
  69. {
  70. SQLINTEGER ODBC_err;
  71. short int ODBC_mlen;
  72. int ODBC_res;
  73. char ODBC_msg[200], ODBC_stat[10];
  74. char sqlcmd[2048] = "", timestr[128];
  75. int res = 0;
  76. struct tm tm;
  77. if (usegmtime)
  78. gmtime_r(&cdr->start.tv_sec,&tm);
  79. else
  80. localtime_r(&cdr->start.tv_sec,&tm);
  81. ast_mutex_lock(&odbc_lock);
  82. strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
  83. memset(sqlcmd,0,2048);
  84. if (loguniqueid) {
  85. snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s "
  86. "(calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp,"
  87. "lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) "
  88. "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", table);
  89. } else {
  90. snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s "
  91. "(calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp,lastdata,"
  92. "duration,billsec,disposition,amaflags,accountcode) "
  93. "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", table);
  94. }
  95. if (!connected) {
  96. res = odbc_init();
  97. if (res < 0) {
  98. connected = 0;
  99. ast_mutex_unlock(&odbc_lock);
  100. return 0;
  101. }
  102. }
  103. ODBC_res = SQLAllocHandle(SQL_HANDLE_STMT, ODBC_con, &ODBC_stmt);
  104. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  105. if (option_verbose > 10)
  106. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Failure in AllocStatement %d\n", ODBC_res);
  107. SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
  108. SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
  109. connected = 0;
  110. ast_mutex_unlock(&odbc_lock);
  111. return 0;
  112. }
  113. /* We really should only have to do this once. But for some
  114. strange reason if I don't it blows holes in memory like
  115. like a shotgun. So we just do this so its safe. */
  116. ODBC_res = SQLPrepare(ODBC_stmt, sqlcmd, SQL_NTS);
  117. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  118. if (option_verbose > 10)
  119. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in PREPARE %d\n", ODBC_res);
  120. SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
  121. SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
  122. connected = 0;
  123. ast_mutex_unlock(&odbc_lock);
  124. return 0;
  125. }
  126. SQLBindParameter(ODBC_stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(timestr), 0, &timestr, 0, NULL);
  127. SQLBindParameter(ODBC_stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->clid), 0, cdr->clid, 0, NULL);
  128. SQLBindParameter(ODBC_stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->src), 0, cdr->src, 0, NULL);
  129. SQLBindParameter(ODBC_stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dst), 0, cdr->dst, 0, NULL);
  130. SQLBindParameter(ODBC_stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dcontext), 0, cdr->dcontext, 0, NULL);
  131. SQLBindParameter(ODBC_stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->channel), 0, cdr->channel, 0, NULL);
  132. SQLBindParameter(ODBC_stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dstchannel), 0, cdr->dstchannel, 0, NULL);
  133. SQLBindParameter(ODBC_stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastapp), 0, cdr->lastapp, 0, NULL);
  134. SQLBindParameter(ODBC_stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastdata), 0, cdr->lastdata, 0, NULL);
  135. SQLBindParameter(ODBC_stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->duration, 0, NULL);
  136. SQLBindParameter(ODBC_stmt, 11, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL);
  137. if (dispositionstring)
  138. SQLBindParameter(ODBC_stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL);
  139. else
  140. SQLBindParameter(ODBC_stmt, 12, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->disposition, 0, NULL);
  141. SQLBindParameter(ODBC_stmt, 13, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->amaflags, 0, NULL);
  142. SQLBindParameter(ODBC_stmt, 14, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->accountcode), 0, cdr->accountcode, 0, NULL);
  143. if (loguniqueid) {
  144. SQLBindParameter(ODBC_stmt, 15, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->uniqueid), 0, cdr->uniqueid, 0, NULL);
  145. SQLBindParameter(ODBC_stmt, 16, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->userfield), 0, cdr->userfield, 0, NULL);
  146. }
  147. if (connected) {
  148. res = odbc_do_query();
  149. if (res < 0) {
  150. if (option_verbose > 10)
  151. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n");
  152. res = odbc_init();
  153. if (option_verbose > 10)
  154. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Reconnecting to dsn %s\n", dsn);
  155. if (res < 0) {
  156. if (option_verbose > 10)
  157. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: %s has gone away!\n", dsn);
  158. connected = 0;
  159. } else {
  160. if (option_verbose > 10)
  161. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Trying Query again!\n");
  162. res = odbc_do_query();
  163. if (res < 0) {
  164. if (option_verbose > 10)
  165. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n");
  166. }
  167. }
  168. }
  169. } else {
  170. if (option_verbose > 10)
  171. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n");
  172. }
  173. SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
  174. ast_mutex_unlock(&odbc_lock);
  175. return 0;
  176. }
  177. char *description(void)
  178. {
  179. return desc;
  180. }
  181. static int odbc_unload_module(void)
  182. {
  183. ast_mutex_lock(&odbc_lock);
  184. if (connected) {
  185. if (option_verbose > 10)
  186. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Disconnecting from %s\n", dsn);
  187. SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
  188. SQLDisconnect(ODBC_con);
  189. SQLFreeHandle(SQL_HANDLE_DBC, ODBC_con);
  190. SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
  191. connected = 0;
  192. }
  193. if (dsn) {
  194. if (option_verbose > 10)
  195. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free dsn\n");
  196. free(dsn);
  197. }
  198. if (username) {
  199. if (option_verbose > 10)
  200. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free username\n");
  201. free(username);
  202. }
  203. if (password) {
  204. if (option_verbose > 10)
  205. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free password\n");
  206. free(password);
  207. }
  208. if (table) {
  209. if (option_verbose > 10)
  210. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free table\n");
  211. free(table);
  212. }
  213. ast_cdr_unregister(name);
  214. ast_mutex_unlock(&odbc_lock);
  215. return 0;
  216. }
  217. static int odbc_load_module(void)
  218. {
  219. int res = 0;
  220. struct ast_config *cfg;
  221. struct ast_variable *var;
  222. char *tmp;
  223. ast_mutex_lock(&odbc_lock);
  224. cfg = ast_config_load(config);
  225. if (!cfg) {
  226. ast_log(LOG_WARNING, "cdr_odbc: Unable to load config for ODBC CDR's: %s\n", config);
  227. goto out;
  228. }
  229. var = ast_variable_browse(cfg, "global");
  230. if (!var) {
  231. /* nothing configured */
  232. goto out;
  233. }
  234. tmp = ast_variable_retrieve(cfg,"global","dsn");
  235. if (tmp == NULL) {
  236. ast_log(LOG_WARNING,"cdr_odbc: dsn not specified. Assuming asteriskdb\n");
  237. tmp = "asteriskdb";
  238. }
  239. dsn = strdup(tmp);
  240. if (dsn == NULL) {
  241. ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
  242. res = -1;
  243. goto out;
  244. }
  245. tmp = ast_variable_retrieve(cfg,"global","dispositionstring");
  246. if (tmp) {
  247. dispositionstring = ast_true(tmp);
  248. } else {
  249. dispositionstring = 0;
  250. }
  251. tmp = ast_variable_retrieve(cfg,"global","username");
  252. if (tmp) {
  253. username = strdup(tmp);
  254. if (username == NULL) {
  255. ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
  256. res = -1;
  257. goto out;
  258. }
  259. }
  260. tmp = ast_variable_retrieve(cfg,"global","password");
  261. if (tmp) {
  262. password = strdup(tmp);
  263. if (password == NULL) {
  264. ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
  265. res = -1;
  266. goto out;
  267. }
  268. }
  269. tmp = ast_variable_retrieve(cfg,"global","loguniqueid");
  270. if (tmp) {
  271. loguniqueid = ast_true(tmp);
  272. if (loguniqueid) {
  273. ast_log(LOG_DEBUG,"cdr_odbc: Logging uniqueid\n");
  274. } else {
  275. ast_log(LOG_DEBUG,"cdr_odbc: Not logging uniqueid\n");
  276. }
  277. } else {
  278. ast_log(LOG_DEBUG,"cdr_odbc: Not logging uniqueid\n");
  279. loguniqueid = 0;
  280. }
  281. tmp = ast_variable_retrieve(cfg,"global","usegmtime");
  282. if (tmp) {
  283. usegmtime = ast_true(tmp);
  284. if (usegmtime) {
  285. ast_log(LOG_DEBUG,"cdr_odbc: Logging in GMT\n");
  286. } else {
  287. ast_log(LOG_DEBUG,"cdr_odbc: Not logging in GMT\n");
  288. }
  289. } else {
  290. ast_log(LOG_DEBUG,"cdr_odbc: Not logging in GMT\n");
  291. usegmtime = 0;
  292. }
  293. tmp = ast_variable_retrieve(cfg,"global","table");
  294. if (tmp == NULL) {
  295. ast_log(LOG_WARNING,"cdr_odbc: table not specified. Assuming cdr\n");
  296. tmp = "cdr";
  297. }
  298. table = strdup(tmp);
  299. if (table == NULL) {
  300. ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
  301. res = -1;
  302. goto out;
  303. }
  304. ast_config_destroy(cfg);
  305. if (option_verbose > 2) {
  306. ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: dsn is %s\n",dsn);
  307. if (username)
  308. {
  309. ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: username is %s\n",username);
  310. ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: password is [secret]\n");
  311. }
  312. else
  313. ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: retreiving username and password from odbc config\n");
  314. ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: table is %s\n",table);
  315. }
  316. res = odbc_init();
  317. if (res < 0) {
  318. ast_log(LOG_ERROR, "cdr_odbc: Unable to connect to datasource: %s\n", dsn);
  319. if (option_verbose > 2) {
  320. ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: Unable to connect to datasource: %s\n", dsn);
  321. }
  322. }
  323. res = ast_cdr_register(name, desc, odbc_log);
  324. if (res) {
  325. ast_log(LOG_ERROR, "cdr_odbc: Unable to register ODBC CDR handling\n");
  326. }
  327. out:
  328. ast_mutex_unlock(&odbc_lock);
  329. return res;
  330. }
  331. static int odbc_do_query(void)
  332. {
  333. SQLINTEGER ODBC_err;
  334. int ODBC_res;
  335. short int ODBC_mlen;
  336. char ODBC_msg[200], ODBC_stat[10];
  337. ODBC_res = SQLExecute(ODBC_stmt);
  338. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  339. if (option_verbose > 10)
  340. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in Query %d\n", ODBC_res);
  341. SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
  342. SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
  343. connected = 0;
  344. return -1;
  345. } else {
  346. if (option_verbose > 10)
  347. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query Successful!\n");
  348. connected = 1;
  349. }
  350. return 0;
  351. }
  352. static int odbc_init(void)
  353. {
  354. SQLINTEGER ODBC_err;
  355. short int ODBC_mlen;
  356. int ODBC_res;
  357. char ODBC_msg[200], ODBC_stat[10];
  358. if (ODBC_env == SQL_NULL_HANDLE || connected == 0) {
  359. ODBC_res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ODBC_env);
  360. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  361. if (option_verbose > 10)
  362. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error AllocHandle\n");
  363. connected = 0;
  364. return -1;
  365. }
  366. ODBC_res = SQLSetEnvAttr(ODBC_env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
  367. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  368. if (option_verbose > 10)
  369. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SetEnv\n");
  370. SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
  371. connected = 0;
  372. return -1;
  373. }
  374. ODBC_res = SQLAllocHandle(SQL_HANDLE_DBC, ODBC_env, &ODBC_con);
  375. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  376. if (option_verbose > 10)
  377. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error AllocHDB %d\n", ODBC_res);
  378. SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
  379. connected = 0;
  380. return -1;
  381. }
  382. SQLSetConnectAttr(ODBC_con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)10, 0);
  383. }
  384. /* Note that the username and password could be NULL here, but that is allowed in ODBC.
  385. In this case, the default username and password will be used from odbc.conf */
  386. ODBC_res = SQLConnect(ODBC_con, (SQLCHAR*)dsn, SQL_NTS, (SQLCHAR*)username, SQL_NTS, (SQLCHAR*)password, SQL_NTS);
  387. if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
  388. if (option_verbose > 10)
  389. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SQLConnect %d\n", ODBC_res);
  390. SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
  391. SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
  392. connected = 0;
  393. return -1;
  394. } else {
  395. if (option_verbose > 10)
  396. ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Connected to %s\n", dsn);
  397. connected = 1;
  398. }
  399. return 0;
  400. }
  401. int load_module(void)
  402. {
  403. return odbc_load_module();
  404. }
  405. int unload_module(void)
  406. {
  407. return odbc_unload_module();
  408. }
  409. int reload(void)
  410. {
  411. odbc_unload_module();
  412. return odbc_load_module();
  413. }
  414. int usecount(void)
  415. {
  416. /* Simplistic use count */
  417. if (ast_mutex_trylock(&odbc_lock)) {
  418. return 1;
  419. } else {
  420. ast_mutex_unlock(&odbc_lock);
  421. return 0;
  422. }
  423. }
  424. char *key()
  425. {
  426. return ASTERISK_GPL_KEY;
  427. }