cdr_mysql.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * James Sharp <jsharp@psychoses.org>
  5. *
  6. * Modified August 2003
  7. * Tilghman Lesher <asterisk__cdr__cdr_mysql__200308@the-tilghman.com>
  8. *
  9. * Modified August 6, 2005
  10. * Joseph Benden <joe@thrallingpenguin.com>
  11. * Added mysql connection timeout parameter
  12. * Added an automatic reconnect as to not lose a cdr record
  13. * Cleaned up the original code to match the coding guidelines
  14. *
  15. * Modified Juli 2006
  16. * Martin Portmann <map@infinitum.ch>
  17. * Added mysql ssl support
  18. *
  19. * See http://www.asterisk.org for more information about
  20. * the Asterisk project. Please do not directly contact
  21. * any of the maintainers of this project for assistance;
  22. * the project provides a web site, mailing lists and IRC
  23. * channels for your use.
  24. *
  25. * This program is free software, distributed under the terms of
  26. * the GNU General Public License Version 2. See the LICENSE file
  27. * at the top of the source tree.
  28. */
  29. /*!
  30. * \file
  31. * \brief MySQL CDR backend
  32. * \ingroup cdr_drivers
  33. */
  34. /*** MODULEINFO
  35. <depend>mysqlclient</depend>
  36. <defaultenabled>no</defaultenabled>
  37. <support_level>deprecated</support_level>
  38. <replacement>cdr_adaptive_odbc</replacement>
  39. ***/
  40. #include "asterisk.h"
  41. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  42. #include <mysql/mysql.h>
  43. #include <mysql/errmsg.h>
  44. #include "asterisk/config.h"
  45. #include "asterisk/options.h"
  46. #include "asterisk/channel.h"
  47. #include "asterisk/cdr.h"
  48. #include "asterisk/module.h"
  49. #include "asterisk/logger.h"
  50. #include "asterisk/cli.h"
  51. #include "asterisk/strings.h"
  52. #include "asterisk/linkedlists.h"
  53. #include "asterisk/threadstorage.h"
  54. #define DATE_FORMAT "%Y-%m-%d %T"
  55. #ifndef MYSQL_PORT
  56. # ifdef MARIADB_PORT
  57. # define MYSQL_PORT MARIADB_PORT
  58. # else
  59. # define MYSQL_PORT 3306
  60. # endif
  61. #endif
  62. AST_THREADSTORAGE(sql1_buf);
  63. AST_THREADSTORAGE(sql2_buf);
  64. AST_THREADSTORAGE(escape_buf);
  65. static const char desc[] = "MySQL CDR Backend";
  66. static const char name[] = "mysql";
  67. static const char config[] = "cdr_mysql.conf";
  68. static struct ast_str *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *dbsock = NULL, *dbtable = NULL, *dbcharset = NULL, *cdrzone = NULL;
  69. static struct ast_str *ssl_ca = NULL, *ssl_cert = NULL, *ssl_key = NULL;
  70. static int dbport = 0;
  71. static int connected = 0;
  72. static time_t connect_time = 0;
  73. static int records = 0;
  74. static int totalrecords = 0;
  75. static int timeout = 0;
  76. static int calldate_compat = 0;
  77. AST_MUTEX_DEFINE_STATIC(mysql_lock);
  78. struct unload_string {
  79. AST_LIST_ENTRY(unload_string) entry;
  80. struct ast_str *str;
  81. };
  82. static AST_LIST_HEAD_STATIC(unload_strings, unload_string);
  83. struct column {
  84. char *name;
  85. char *cdrname;
  86. char *staticvalue;
  87. char *type;
  88. AST_LIST_ENTRY(column) list;
  89. };
  90. /* Protected with mysql_lock */
  91. static AST_RWLIST_HEAD_STATIC(columns, column);
  92. static MYSQL mysql = { { NULL }, };
  93. static char *handle_cli_cdr_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  94. {
  95. switch (cmd) {
  96. case CLI_INIT:
  97. e->command = "cdr mysql status";
  98. e->usage =
  99. "Usage: cdr mysql status\n"
  100. " Shows current connection status for cdr_mysql\n";
  101. return NULL;
  102. case CLI_GENERATE:
  103. return NULL;
  104. }
  105. if (a->argc != 3)
  106. return CLI_SHOWUSAGE;
  107. if (connected) {
  108. char status[256], status2[100] = "";
  109. int ctime = time(NULL) - connect_time;
  110. if (dbport)
  111. snprintf(status, 255, "Connected to %s@%s, port %d", ast_str_buffer(dbname), ast_str_buffer(hostname), dbport);
  112. else if (dbsock)
  113. snprintf(status, 255, "Connected to %s on socket file %s", ast_str_buffer(dbname), S_OR(ast_str_buffer(dbsock), "default"));
  114. else
  115. snprintf(status, 255, "Connected to %s@%s", ast_str_buffer(dbname), ast_str_buffer(hostname));
  116. if (ast_str_strlen(dbuser))
  117. snprintf(status2, 99, " with username %s", ast_str_buffer(dbuser));
  118. if (ast_str_strlen(dbtable))
  119. snprintf(status2, 99, " using table %s", ast_str_buffer(dbtable));
  120. if (ctime > 31536000) {
  121. ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 31536000, (ctime % 31536000) / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
  122. } else if (ctime > 86400) {
  123. ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
  124. } else if (ctime > 3600) {
  125. ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 3600, (ctime % 3600) / 60, ctime % 60);
  126. } else if (ctime > 60) {
  127. ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, ctime % 60);
  128. } else {
  129. ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
  130. }
  131. if (records == totalrecords)
  132. ast_cli(a->fd, " Wrote %d records since last restart.\n", totalrecords);
  133. else
  134. ast_cli(a->fd, " Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
  135. } else {
  136. ast_cli(a->fd, "Not currently connected to a MySQL server.\n");
  137. }
  138. return CLI_SUCCESS;
  139. }
  140. static struct ast_cli_entry cdr_mysql_status_cli[] = {
  141. AST_CLI_DEFINE(handle_cli_cdr_mysql_status, "Show connection status of cdr_mysql"),
  142. };
  143. static void configure_connection_charset(void)
  144. {
  145. if (ast_str_strlen(dbcharset)) {
  146. const char *charset = ast_str_buffer(dbcharset);
  147. if (mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, charset)) {
  148. ast_log(LOG_WARNING, "Failed to set connection charset. Data inserted might be invalid.\n");
  149. }
  150. }
  151. }
  152. static int mysql_log(struct ast_cdr *cdr)
  153. {
  154. struct ast_str *sql1 = ast_str_thread_get(&sql1_buf, 1024), *sql2 = ast_str_thread_get(&sql2_buf, 1024);
  155. int retries = 5;
  156. #if MYSQL_VERSION_ID >= 50013
  157. my_bool my_bool_true = 1;
  158. #endif
  159. if (!sql1 || !sql2) {
  160. ast_log(LOG_ERROR, "Memory error\n");
  161. return -1;
  162. }
  163. ast_mutex_lock(&mysql_lock);
  164. db_reconnect:
  165. if ((!connected) && (hostname || dbsock) && dbuser && password && dbname && dbtable ) {
  166. /* Attempt to connect */
  167. mysql_init(&mysql);
  168. /* Add option to quickly timeout the connection */
  169. if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
  170. ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
  171. }
  172. #if MYSQL_VERSION_ID >= 50013
  173. /* Add option for automatic reconnection */
  174. if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
  175. ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
  176. }
  177. #endif
  178. if (ssl_ca || ssl_cert || ssl_key) {
  179. mysql_ssl_set(&mysql, ssl_key ? ast_str_buffer(ssl_key) : NULL, ssl_cert ? ast_str_buffer(ssl_cert) : NULL, ssl_ca ? ast_str_buffer(ssl_ca) : NULL, NULL, NULL);
  180. }
  181. configure_connection_charset();
  182. if (mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL, ssl_ca ? CLIENT_SSL : 0)) {
  183. connected = 1;
  184. connect_time = time(NULL);
  185. records = 0;
  186. } else {
  187. ast_log(LOG_ERROR, "Cannot connect to database server %s: (%d) %s\n", ast_str_buffer(hostname), mysql_errno(&mysql), mysql_error(&mysql));
  188. connected = 0;
  189. }
  190. } else {
  191. /* Long connection - ping the server */
  192. int error;
  193. if ((error = mysql_ping(&mysql))) {
  194. connected = 0;
  195. records = 0;
  196. switch (mysql_errno(&mysql)) {
  197. case CR_SERVER_GONE_ERROR:
  198. case CR_SERVER_LOST:
  199. ast_log(LOG_ERROR, "Server has gone away. Attempting to reconnect.\n");
  200. break;
  201. default:
  202. ast_log(LOG_ERROR, "Unknown connection error: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
  203. }
  204. retries--;
  205. if (retries) {
  206. goto db_reconnect;
  207. } else {
  208. ast_log(LOG_ERROR, "Retried to connect five times, giving up.\n");
  209. }
  210. }
  211. }
  212. if (connected) {
  213. int column_count = 0;
  214. char *cdrname;
  215. char workspace[2048], *value = NULL;
  216. struct column *entry;
  217. struct ast_str *escape = ast_str_thread_get(&escape_buf, 16);
  218. ast_str_set(&sql1, 0, "INSERT INTO %s (", AS_OR(dbtable, "cdr"));
  219. ast_str_set(&sql2, 0, ") VALUES (");
  220. AST_RWLIST_RDLOCK(&columns);
  221. AST_RWLIST_TRAVERSE(&columns, entry, list) {
  222. if (!strcmp(entry->name, "calldate")) {
  223. /*!\note
  224. * For some dumb reason, "calldate" used to be formulated using
  225. * the datetime the record was posted, rather than the start
  226. * time of the call. If someone really wants the old compatible
  227. * behavior, it's provided here.
  228. */
  229. if (calldate_compat) {
  230. struct timeval tv = ast_tvnow();
  231. struct ast_tm tm;
  232. char timestr[128];
  233. ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
  234. ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
  235. value = ast_strdupa(timestr);
  236. cdrname = "calldate";
  237. } else {
  238. cdrname = "start";
  239. }
  240. } else {
  241. cdrname = entry->cdrname;
  242. }
  243. /* Construct SQL */
  244. /* Need the type and value to determine if we want the raw value or not */
  245. if (entry->staticvalue) {
  246. value = ast_strdupa(entry->staticvalue);
  247. } else if ((!strcmp(cdrname, "disposition") ||
  248. !strcmp(cdrname, "amaflags")) &&
  249. (strstr(entry->type, "int") ||
  250. strstr(entry->type, "dec") ||
  251. strstr(entry->type, "float") ||
  252. strstr(entry->type, "double") ||
  253. strstr(entry->type, "real") ||
  254. strstr(entry->type, "numeric") ||
  255. strstr(entry->type, "fixed"))) {
  256. ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1);
  257. } else if (!strcmp(cdrname, "start") || !strcmp(cdrname, "answer") ||
  258. !strcmp(cdrname, "end")) {
  259. struct ast_tm tm;
  260. char timestr[128];
  261. ast_localtime(&cdr->start, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
  262. ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
  263. value = ast_strdupa(timestr);
  264. } else if (!strcmp(cdrname, "calldate")) {
  265. /* Skip calldate - the value has already been dup'd */
  266. } else {
  267. ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0);
  268. }
  269. if (value) {
  270. size_t valsz;
  271. if (column_count++) {
  272. ast_str_append(&sql1, 0, ",");
  273. ast_str_append(&sql2, 0, ",");
  274. }
  275. if (!strcasecmp(cdrname, "billsec") &&
  276. (strstr(entry->type, "float") ||
  277. strstr(entry->type, "double") ||
  278. strstr(entry->type, "decimal") ||
  279. strstr(entry->type, "numeric") ||
  280. strstr(entry->type, "real"))) {
  281. if (!ast_tvzero(cdr->answer)) {
  282. snprintf(workspace, sizeof(workspace), "%lf",
  283. (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
  284. } else {
  285. ast_copy_string(workspace, "0", sizeof(workspace));
  286. }
  287. if (!ast_strlen_zero(workspace)) {
  288. value = workspace;
  289. }
  290. }
  291. if (!strcasecmp(cdrname, "duration") &&
  292. (strstr(entry->type, "float") ||
  293. strstr(entry->type, "double") ||
  294. strstr(entry->type, "decimal") ||
  295. strstr(entry->type, "numeric") ||
  296. strstr(entry->type, "real"))) {
  297. snprintf(workspace, sizeof(workspace), "%lf",
  298. (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
  299. if (!ast_strlen_zero(workspace)) {
  300. value = workspace;
  301. }
  302. }
  303. ast_str_make_space(&escape, (valsz = strlen(value)) * 2 + 1);
  304. mysql_real_escape_string(&mysql, ast_str_buffer(escape), value, valsz);
  305. ast_str_append(&sql1, 0, "`%s`", entry->name);
  306. ast_str_append(&sql2, 0, "'%s'", ast_str_buffer(escape));
  307. }
  308. }
  309. AST_RWLIST_UNLOCK(&columns);
  310. ast_debug(1, "Inserting a CDR record.\n");
  311. ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
  312. ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
  313. if (mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1))) {
  314. ast_log(LOG_ERROR, "Failed to insert into database: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
  315. mysql_close(&mysql);
  316. connected = 0;
  317. } else {
  318. records++;
  319. totalrecords++;
  320. }
  321. }
  322. ast_mutex_unlock(&mysql_lock);
  323. return 0;
  324. }
  325. static void free_strings(void)
  326. {
  327. struct unload_string *us;
  328. AST_LIST_LOCK(&unload_strings);
  329. while ((us = AST_LIST_REMOVE_HEAD(&unload_strings, entry))) {
  330. ast_free(us->str);
  331. ast_free(us);
  332. }
  333. AST_LIST_UNLOCK(&unload_strings);
  334. }
  335. static int my_unload_module(int reload)
  336. {
  337. struct column *entry;
  338. ast_cli_unregister_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
  339. if (connected) {
  340. mysql_close(&mysql);
  341. connected = 0;
  342. records = 0;
  343. }
  344. free_strings();
  345. if (!reload) {
  346. AST_RWLIST_WRLOCK(&columns);
  347. }
  348. while ((entry = AST_RWLIST_REMOVE_HEAD(&columns, list))) {
  349. ast_free(entry);
  350. }
  351. if (!reload) {
  352. AST_RWLIST_UNLOCK(&columns);
  353. }
  354. dbport = 0;
  355. if (reload) {
  356. return ast_cdr_backend_suspend(name);
  357. } else {
  358. return ast_cdr_unregister(name);
  359. }
  360. }
  361. static int my_load_config_string(struct ast_config *cfg, const char *category, const char *variable, struct ast_str **field, const char *def)
  362. {
  363. struct unload_string *us;
  364. const char *tmp;
  365. if (!(us = ast_calloc(1, sizeof(*us))))
  366. return -1;
  367. if (!(*field = ast_str_create(16))) {
  368. ast_free(us);
  369. return -1;
  370. }
  371. tmp = ast_variable_retrieve(cfg, category, variable);
  372. ast_str_set(field, 0, "%s", tmp ? tmp : def);
  373. us->str = *field;
  374. AST_LIST_LOCK(&unload_strings);
  375. AST_LIST_INSERT_HEAD(&unload_strings, us, entry);
  376. AST_LIST_UNLOCK(&unload_strings);
  377. return 0;
  378. }
  379. static int my_load_config_number(struct ast_config *cfg, const char *category, const char *variable, int *field, int def)
  380. {
  381. const char *tmp;
  382. tmp = ast_variable_retrieve(cfg, category, variable);
  383. if (!tmp || sscanf(tmp, "%30d", field) < 1)
  384. *field = def;
  385. return 0;
  386. }
  387. static int my_load_module(int reload)
  388. {
  389. int res;
  390. struct ast_config *cfg;
  391. struct ast_variable *var;
  392. /* CONFIG_STATUS_FILEUNCHANGED is impossible when config_flags is always 0,
  393. * and it has to be zero, so a reload can be sent to tell the driver to
  394. * rescan the table layout. */
  395. struct ast_flags config_flags = { 0 };
  396. struct column *entry;
  397. char *temp;
  398. struct ast_str *compat;
  399. MYSQL_ROW row;
  400. MYSQL_RES *result;
  401. char sqldesc[128];
  402. #if MYSQL_VERSION_ID >= 50013
  403. my_bool my_bool_true = 1;
  404. #endif
  405. /* Cannot use a conditionally different flag, because the table layout may
  406. * have changed, which is not detectable by config file change detection,
  407. * but should still cause the configuration to be re-parsed. */
  408. cfg = ast_config_load(config, config_flags);
  409. if (cfg == CONFIG_STATUS_FILEMISSING) {
  410. ast_log(LOG_WARNING, "Unable to load config for mysql CDR's: %s\n", config);
  411. return AST_MODULE_LOAD_SUCCESS;
  412. } else if (cfg == CONFIG_STATUS_FILEINVALID) {
  413. ast_log(LOG_ERROR, "Unable to load configuration file '%s'\n", config);
  414. return AST_MODULE_LOAD_DECLINE;
  415. }
  416. if (reload) {
  417. AST_RWLIST_WRLOCK(&columns);
  418. my_unload_module(1);
  419. }
  420. var = ast_variable_browse(cfg, "global");
  421. if (!var) {
  422. /* nothing configured */
  423. if (reload) {
  424. AST_RWLIST_UNLOCK(&columns);
  425. }
  426. ast_config_destroy(cfg);
  427. return AST_MODULE_LOAD_SUCCESS;
  428. }
  429. res = 0;
  430. res |= my_load_config_string(cfg, "global", "hostname", &hostname, "localhost");
  431. res |= my_load_config_string(cfg, "global", "dbname", &dbname, "astriskcdrdb");
  432. res |= my_load_config_string(cfg, "global", "user", &dbuser, "root");
  433. res |= my_load_config_string(cfg, "global", "sock", &dbsock, "");
  434. res |= my_load_config_string(cfg, "global", "table", &dbtable, "cdr");
  435. res |= my_load_config_string(cfg, "global", "password", &password, "");
  436. res |= my_load_config_string(cfg, "global", "charset", &dbcharset, "");
  437. res |= my_load_config_string(cfg, "global", "ssl_ca", &ssl_ca, "");
  438. res |= my_load_config_string(cfg, "global", "ssl_cert", &ssl_cert, "");
  439. res |= my_load_config_string(cfg, "global", "ssl_key", &ssl_key, "");
  440. res |= my_load_config_number(cfg, "global", "port", &dbport, MYSQL_PORT);
  441. res |= my_load_config_number(cfg, "global", "timeout", &timeout, 0);
  442. res |= my_load_config_string(cfg, "global", "compat", &compat, "no");
  443. res |= my_load_config_string(cfg, "global", "cdrzone", &cdrzone, "");
  444. if (ast_str_strlen(cdrzone) == 0) {
  445. for (; var; var = var->next) {
  446. if (!strcasecmp(var->name, "usegmtime") && ast_true(var->value)) {
  447. ast_str_set(&cdrzone, 0, "UTC");
  448. }
  449. }
  450. }
  451. if (ast_true(ast_str_buffer(compat))) {
  452. calldate_compat = 1;
  453. } else {
  454. calldate_compat = 0;
  455. }
  456. if (res < 0) {
  457. if (reload) {
  458. AST_RWLIST_UNLOCK(&columns);
  459. }
  460. ast_config_destroy(cfg);
  461. free_strings();
  462. return AST_MODULE_LOAD_DECLINE;
  463. }
  464. /* Check for any aliases */
  465. if (!reload) {
  466. /* Lock, if not already */
  467. AST_RWLIST_WRLOCK(&columns);
  468. }
  469. while ((entry = AST_LIST_REMOVE_HEAD(&columns, list))) {
  470. ast_free(entry);
  471. }
  472. ast_debug(1, "Got hostname of %s\n", ast_str_buffer(hostname));
  473. ast_debug(1, "Got port of %d\n", dbport);
  474. ast_debug(1, "Got a timeout of %d\n", timeout);
  475. if (ast_str_strlen(dbsock)) {
  476. ast_debug(1, "Got sock file of %s\n", ast_str_buffer(dbsock));
  477. }
  478. ast_debug(1, "Got user of %s\n", ast_str_buffer(dbuser));
  479. ast_debug(1, "Got dbname of %s\n", ast_str_buffer(dbname));
  480. ast_debug(1, "Got password of %s\n", ast_str_buffer(password));
  481. ast_debug(1, "%sunning in calldate compatibility mode\n", calldate_compat ? "R" : "Not r");
  482. ast_debug(1, "Dates and times are localized to %s\n", S_OR(ast_str_buffer(cdrzone), "local timezone"));
  483. if (ast_str_strlen(dbcharset)) {
  484. ast_debug(1, "Got DB charset of %s\n", ast_str_buffer(dbcharset));
  485. }
  486. mysql_init(&mysql);
  487. if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
  488. ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
  489. }
  490. #if MYSQL_VERSION_ID >= 50013
  491. /* Add option for automatic reconnection */
  492. if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
  493. ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
  494. }
  495. #endif
  496. if ((ssl_ca && ast_str_strlen(ssl_ca)) || (ssl_cert && ast_str_strlen(ssl_cert)) || (ssl_key && ast_str_strlen(ssl_key))) {
  497. mysql_ssl_set(&mysql,
  498. ssl_key ? ast_str_buffer(ssl_key) : NULL,
  499. ssl_cert ? ast_str_buffer(ssl_cert) : NULL,
  500. ssl_ca ? ast_str_buffer(ssl_ca) : NULL,
  501. NULL, NULL);
  502. }
  503. temp = dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL;
  504. configure_connection_charset();
  505. if (!mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, temp, ssl_ca && ast_str_strlen(ssl_ca) ? CLIENT_SSL : 0)) {
  506. ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n", ast_str_buffer(dbname), ast_str_buffer(hostname));
  507. connected = 0;
  508. records = 0;
  509. } else {
  510. ast_debug(1, "Successfully connected to MySQL database.\n");
  511. connected = 1;
  512. records = 0;
  513. connect_time = time(NULL);
  514. /* Get table description */
  515. snprintf(sqldesc, sizeof(sqldesc), "DESC %s", dbtable ? ast_str_buffer(dbtable) : "cdr");
  516. if (mysql_query(&mysql, sqldesc)) {
  517. ast_log(LOG_ERROR, "Unable to query table description!! Logging disabled.\n");
  518. mysql_close(&mysql);
  519. connected = 0;
  520. AST_RWLIST_UNLOCK(&columns);
  521. ast_config_destroy(cfg);
  522. free_strings();
  523. return AST_MODULE_LOAD_DECLINE;
  524. }
  525. if (!(result = mysql_store_result(&mysql))) {
  526. ast_log(LOG_ERROR, "Unable to query table description!! Logging disabled.\n");
  527. mysql_close(&mysql);
  528. connected = 0;
  529. AST_RWLIST_UNLOCK(&columns);
  530. ast_config_destroy(cfg);
  531. free_strings();
  532. return AST_MODULE_LOAD_DECLINE;
  533. }
  534. while ((row = mysql_fetch_row(result))) {
  535. struct column *entry;
  536. char *cdrvar = "", *staticvalue = "";
  537. ast_debug(1, "Got a field '%s' of type '%s'\n", row[0], row[1]);
  538. /* Check for an alias or a static value */
  539. for (var = ast_variable_browse(cfg, "columns"); var; var = var->next) {
  540. if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, row[0]) == 0 ) {
  541. char *alias = ast_strdupa(var->name + 5);
  542. cdrvar = ast_strip(alias);
  543. ast_verb(3, "Found alias %s for column %s\n", cdrvar, row[0]);
  544. break;
  545. } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, row[0]) == 0) {
  546. char *item = ast_strdupa(var->name + 6);
  547. item = ast_strip(item);
  548. if (item[0] == '"' && item[strlen(item) - 1] == '"') {
  549. /* Remove surrounding quotes */
  550. item[strlen(item) - 1] = '\0';
  551. item++;
  552. }
  553. staticvalue = item;
  554. }
  555. }
  556. entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(row[0]) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1 + strlen(row[1]) + 1);
  557. if (!entry) {
  558. ast_log(LOG_ERROR, "Out of memory creating entry for column '%s'\n", row[0]);
  559. res = -1;
  560. break;
  561. }
  562. entry->name = (char *)entry + sizeof(*entry);
  563. strcpy(entry->name, row[0]);
  564. if (!ast_strlen_zero(cdrvar)) {
  565. entry->cdrname = entry->name + strlen(row[0]) + 1;
  566. strcpy(entry->cdrname, cdrvar);
  567. } else { /* Point to same place as the column name */
  568. entry->cdrname = (char *)entry + sizeof(*entry);
  569. }
  570. if (!ast_strlen_zero(staticvalue)) {
  571. entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
  572. strcpy(entry->staticvalue, staticvalue);
  573. ast_debug(1, "staticvalue length: %d\n", (int) strlen(staticvalue) );
  574. entry->type = entry->staticvalue + strlen(entry->staticvalue) + 1;
  575. } else {
  576. entry->type = entry->cdrname + strlen(entry->cdrname) + 1;
  577. }
  578. strcpy(entry->type, row[1]);
  579. ast_debug(1, "Entry name '%s'\n", entry->name);
  580. ast_debug(1, " cdrname '%s'\n", entry->cdrname);
  581. ast_debug(1, " static '%s'\n", entry->staticvalue);
  582. ast_debug(1, " type '%s'\n", entry->type);
  583. AST_LIST_INSERT_TAIL(&columns, entry, list);
  584. }
  585. mysql_free_result(result);
  586. }
  587. AST_RWLIST_UNLOCK(&columns);
  588. ast_config_destroy(cfg);
  589. if (res < 0) {
  590. my_unload_module(0);
  591. return AST_MODULE_LOAD_DECLINE;
  592. }
  593. if (!reload) {
  594. res = ast_cdr_register(name, desc, mysql_log);
  595. } else {
  596. res = ast_cdr_backend_unsuspend(name);
  597. }
  598. if (res) {
  599. ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
  600. } else {
  601. res = ast_cli_register_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
  602. }
  603. if (res) {
  604. my_unload_module(0);
  605. return AST_MODULE_LOAD_DECLINE;
  606. }
  607. return AST_MODULE_LOAD_SUCCESS;
  608. }
  609. static int load_module(void)
  610. {
  611. return my_load_module(0);
  612. }
  613. static int unload_module(void)
  614. {
  615. return my_unload_module(0);
  616. }
  617. static int reload(void)
  618. {
  619. int ret;
  620. ast_mutex_lock(&mysql_lock);
  621. ret = my_load_module(1);
  622. ast_mutex_unlock(&mysql_lock);
  623. return ret;
  624. }
  625. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MySQL CDR Backend",
  626. .support_level = AST_MODULE_SUPPORT_DEPRECATED,
  627. .load = load_module,
  628. .unload = unload_module,
  629. .reload = reload,
  630. );