cdr_pgsql.c 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2003 - 2012
  5. *
  6. * Matthew D. Hardeman <mhardemn@papersoft.com>
  7. * Adapted from the MySQL CDR logger originally by James Sharp
  8. *
  9. * Modified September 2003
  10. * Matthew D. Hardeman <mhardemn@papersoft.com>
  11. *
  12. * See http://www.asterisk.org for more information about
  13. * the Asterisk project. Please do not directly contact
  14. * any of the maintainers of this project for assistance;
  15. * the project provides a web site, mailing lists and IRC
  16. * channels for your use.
  17. *
  18. * This program is free software, distributed under the terms of
  19. * the GNU General Public License Version 2. See the LICENSE file
  20. * at the top of the source tree.
  21. */
  22. /*!
  23. * \file
  24. * \brief PostgreSQL CDR logger
  25. *
  26. * \author Matthew D. Hardeman <mhardemn@papersoft.com>
  27. * PostgreSQL http://www.postgresql.org/
  28. *
  29. * See also
  30. * \arg \ref Config_cdr
  31. * PostgreSQL http://www.postgresql.org/
  32. * \ingroup cdr_drivers
  33. */
  34. /*! \li \ref cdr_pgsql.c uses the configuration file \ref cdr_pgsql.conf
  35. * \addtogroup configuration_file Configuration Files
  36. */
  37. /*!
  38. * \page cdr_pgsql.conf cdr_pgsql.conf
  39. * \verbinclude cdr_pgsql.conf.sample
  40. */
  41. /*** MODULEINFO
  42. <depend>pgsql</depend>
  43. <support_level>extended</support_level>
  44. ***/
  45. #include "asterisk.h"
  46. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  47. #include <libpq-fe.h>
  48. #include "asterisk/config.h"
  49. #include "asterisk/channel.h"
  50. #include "asterisk/cdr.h"
  51. #include "asterisk/cli.h"
  52. #include "asterisk/module.h"
  53. #define DATE_FORMAT "'%Y-%m-%d %T'"
  54. static const char name[] = "pgsql";
  55. static const char config[] = "cdr_pgsql.conf";
  56. static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL, *encoding = NULL, *tz = NULL;
  57. static int connected = 0;
  58. static int maxsize = 512, maxsize2 = 512;
  59. static time_t connect_time = 0;
  60. static int totalrecords = 0;
  61. static int records;
  62. static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
  63. static struct ast_cli_entry cdr_pgsql_status_cli[] = {
  64. AST_CLI_DEFINE(handle_cdr_pgsql_status, "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)"),
  65. };
  66. AST_MUTEX_DEFINE_STATIC(pgsql_lock);
  67. static PGconn *conn = NULL;
  68. struct columns {
  69. char *name;
  70. char *type;
  71. int len;
  72. unsigned int notnull:1;
  73. unsigned int hasdefault:1;
  74. AST_RWLIST_ENTRY(columns) list;
  75. };
  76. static AST_RWLIST_HEAD_STATIC(psql_columns, columns);
  77. #define LENGTHEN_BUF1(size) \
  78. do { \
  79. /* Lengthen buffer, if necessary */ \
  80. if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
  81. if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) { \
  82. ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR failed.\n"); \
  83. ast_free(sql); \
  84. ast_free(sql2); \
  85. AST_RWLIST_UNLOCK(&psql_columns); \
  86. ast_mutex_unlock(&pgsql_lock); \
  87. return -1; \
  88. } \
  89. } \
  90. } while (0)
  91. #define LENGTHEN_BUF2(size) \
  92. do { \
  93. if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) { \
  94. if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) { \
  95. ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR failed.\n"); \
  96. ast_free(sql); \
  97. ast_free(sql2); \
  98. AST_RWLIST_UNLOCK(&psql_columns); \
  99. ast_mutex_unlock(&pgsql_lock); \
  100. return -1; \
  101. } \
  102. } \
  103. } while (0)
  104. /*! \brief Handle the CLI command cdr show pgsql status */
  105. static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  106. {
  107. switch (cmd) {
  108. case CLI_INIT:
  109. e->command = "cdr show pgsql status";
  110. e->usage =
  111. "Usage: cdr show pgsql status\n"
  112. " Shows current connection status for cdr_pgsql\n";
  113. return NULL;
  114. case CLI_GENERATE:
  115. return NULL;
  116. }
  117. if (a->argc != 3)
  118. return CLI_SHOWUSAGE;
  119. if (connected) {
  120. char status[256], status2[100] = "";
  121. int ctime = time(NULL) - connect_time;
  122. if (pgdbport) {
  123. snprintf(status, 255, "Connected to %s@%s, port %s", pgdbname, pghostname, pgdbport);
  124. } else {
  125. snprintf(status, 255, "Connected to %s@%s", pgdbname, pghostname);
  126. }
  127. if (pgdbuser && *pgdbuser) {
  128. snprintf(status2, 99, " with username %s", pgdbuser);
  129. }
  130. if (table && *table) {
  131. snprintf(status2, 99, " using table %s", table);
  132. }
  133. if (ctime > 31536000) {
  134. 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);
  135. } else if (ctime > 86400) {
  136. 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);
  137. } else if (ctime > 3600) {
  138. ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 3600, (ctime % 3600) / 60, ctime % 60);
  139. } else if (ctime > 60) {
  140. ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, ctime % 60);
  141. } else {
  142. ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
  143. }
  144. if (records == totalrecords) {
  145. ast_cli(a->fd, " Wrote %d records since last restart.\n", totalrecords);
  146. } else {
  147. ast_cli(a->fd, " Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
  148. }
  149. } else {
  150. ast_cli(a->fd, "Not currently connected to a PgSQL server.\n");
  151. }
  152. return CLI_SUCCESS;
  153. }
  154. static int pgsql_log(struct ast_cdr *cdr)
  155. {
  156. struct ast_tm tm;
  157. char *pgerror;
  158. PGresult *result;
  159. ast_mutex_lock(&pgsql_lock);
  160. if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
  161. conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
  162. if (PQstatus(conn) != CONNECTION_BAD) {
  163. connected = 1;
  164. connect_time = time(NULL);
  165. records = 0;
  166. if (PQsetClientEncoding(conn, encoding)) {
  167. #ifdef HAVE_PGSQL_pg_encoding_to_char
  168. ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
  169. #else
  170. ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
  171. #endif
  172. }
  173. } else {
  174. pgerror = PQerrorMessage(conn);
  175. ast_log(LOG_ERROR, "Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
  176. ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
  177. PQfinish(conn);
  178. conn = NULL;
  179. }
  180. }
  181. if (connected) {
  182. struct columns *cur;
  183. struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
  184. char buf[257], escapebuf[513], *value;
  185. int first = 1;
  186. if (!sql || !sql2) {
  187. ast_free(sql);
  188. ast_free(sql2);
  189. return -1;
  190. }
  191. ast_str_set(&sql, 0, "INSERT INTO %s (", table);
  192. ast_str_set(&sql2, 0, " VALUES (");
  193. AST_RWLIST_RDLOCK(&psql_columns);
  194. AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
  195. /* For fields not set, simply skip them */
  196. ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
  197. if (strcmp(cur->name, "calldate") == 0 && !value) {
  198. ast_cdr_format_var(cdr, "start", &value, buf, sizeof(buf), 0);
  199. }
  200. if (!value) {
  201. if (cur->notnull && !cur->hasdefault) {
  202. /* Field is NOT NULL (but no default), must include it anyway */
  203. LENGTHEN_BUF1(strlen(cur->name) + 2);
  204. ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
  205. LENGTHEN_BUF2(3);
  206. ast_str_append(&sql2, 0, "%s''", first ? "" : ",");
  207. first = 0;
  208. }
  209. continue;
  210. }
  211. LENGTHEN_BUF1(strlen(cur->name) + 2);
  212. ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
  213. if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
  214. if (strncmp(cur->type, "int", 3) == 0) {
  215. LENGTHEN_BUF2(13);
  216. ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->start.tv_sec);
  217. } else if (strncmp(cur->type, "float", 5) == 0) {
  218. LENGTHEN_BUF2(31);
  219. ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
  220. } else {
  221. /* char, hopefully */
  222. LENGTHEN_BUF2(31);
  223. ast_localtime(&cdr->start, &tm, tz);
  224. ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
  225. ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
  226. }
  227. } else if (strcmp(cur->name, "answer") == 0) {
  228. if (strncmp(cur->type, "int", 3) == 0) {
  229. LENGTHEN_BUF2(13);
  230. ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->answer.tv_sec);
  231. } else if (strncmp(cur->type, "float", 5) == 0) {
  232. LENGTHEN_BUF2(31);
  233. ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
  234. } else {
  235. /* char, hopefully */
  236. LENGTHEN_BUF2(31);
  237. ast_localtime(&cdr->answer, &tm, tz);
  238. ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
  239. ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
  240. }
  241. } else if (strcmp(cur->name, "end") == 0) {
  242. if (strncmp(cur->type, "int", 3) == 0) {
  243. LENGTHEN_BUF2(13);
  244. ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->end.tv_sec);
  245. } else if (strncmp(cur->type, "float", 5) == 0) {
  246. LENGTHEN_BUF2(31);
  247. ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
  248. } else {
  249. /* char, hopefully */
  250. LENGTHEN_BUF2(31);
  251. ast_localtime(&cdr->end, &tm, tz);
  252. ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
  253. ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
  254. }
  255. } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
  256. if (cur->type[0] == 'i') {
  257. /* Get integer, no need to escape anything */
  258. ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
  259. LENGTHEN_BUF2(13);
  260. ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
  261. } else if (strncmp(cur->type, "float", 5) == 0) {
  262. struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
  263. LENGTHEN_BUF2(31);
  264. ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
  265. } else {
  266. /* Char field, probably */
  267. struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
  268. LENGTHEN_BUF2(31);
  269. ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
  270. }
  271. } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
  272. if (strncmp(cur->type, "int", 3) == 0) {
  273. /* Integer, no need to escape anything */
  274. ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 1);
  275. LENGTHEN_BUF2(13);
  276. ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
  277. } else {
  278. /* Although this is a char field, there are no special characters in the values for these fields */
  279. ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
  280. LENGTHEN_BUF2(31);
  281. ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
  282. }
  283. } else {
  284. /* Arbitrary field, could be anything */
  285. ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
  286. if (strncmp(cur->type, "int", 3) == 0) {
  287. long long whatever;
  288. if (value && sscanf(value, "%30lld", &whatever) == 1) {
  289. LENGTHEN_BUF2(26);
  290. ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", whatever);
  291. } else {
  292. LENGTHEN_BUF2(2);
  293. ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
  294. }
  295. } else if (strncmp(cur->type, "float", 5) == 0) {
  296. long double whatever;
  297. if (value && sscanf(value, "%30Lf", &whatever) == 1) {
  298. LENGTHEN_BUF2(51);
  299. ast_str_append(&sql2, 0, "%s%30Lf", first ? "" : ",", whatever);
  300. } else {
  301. LENGTHEN_BUF2(2);
  302. ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
  303. }
  304. /* XXX Might want to handle dates, times, and other misc fields here XXX */
  305. } else {
  306. if (value)
  307. PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
  308. else
  309. escapebuf[0] = '\0';
  310. LENGTHEN_BUF2(strlen(escapebuf) + 3);
  311. ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", escapebuf);
  312. }
  313. }
  314. first = 0;
  315. }
  316. LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
  317. AST_RWLIST_UNLOCK(&psql_columns);
  318. ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
  319. ast_verb(11, "[%s]\n", ast_str_buffer(sql));
  320. ast_debug(2, "inserting a CDR record.\n");
  321. /* Test to be sure we're still connected... */
  322. /* If we're connected, and connection is working, good. */
  323. /* Otherwise, attempt reconnect. If it fails... sorry... */
  324. if (PQstatus(conn) == CONNECTION_OK) {
  325. connected = 1;
  326. } else {
  327. ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
  328. PQreset(conn);
  329. if (PQstatus(conn) == CONNECTION_OK) {
  330. ast_log(LOG_ERROR, "Connection reestablished.\n");
  331. connected = 1;
  332. connect_time = time(NULL);
  333. records = 0;
  334. } else {
  335. pgerror = PQerrorMessage(conn);
  336. ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
  337. ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
  338. PQfinish(conn);
  339. conn = NULL;
  340. connected = 0;
  341. ast_mutex_unlock(&pgsql_lock);
  342. ast_free(sql);
  343. ast_free(sql2);
  344. return -1;
  345. }
  346. }
  347. result = PQexec(conn, ast_str_buffer(sql));
  348. if (PQresultStatus(result) != PGRES_COMMAND_OK) {
  349. pgerror = PQresultErrorMessage(result);
  350. ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
  351. ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
  352. ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
  353. PQreset(conn);
  354. if (PQstatus(conn) == CONNECTION_OK) {
  355. ast_log(LOG_ERROR, "Connection reestablished.\n");
  356. connected = 1;
  357. connect_time = time(NULL);
  358. records = 0;
  359. PQclear(result);
  360. result = PQexec(conn, ast_str_buffer(sql));
  361. if (PQresultStatus(result) != PGRES_COMMAND_OK) {
  362. pgerror = PQresultErrorMessage(result);
  363. ast_log(LOG_ERROR, "HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
  364. ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
  365. } else {
  366. /* Second try worked out ok */
  367. totalrecords++;
  368. records++;
  369. ast_mutex_unlock(&pgsql_lock);
  370. PQclear(result);
  371. return 0;
  372. }
  373. }
  374. ast_mutex_unlock(&pgsql_lock);
  375. PQclear(result);
  376. ast_free(sql);
  377. ast_free(sql2);
  378. return -1;
  379. } else {
  380. totalrecords++;
  381. records++;
  382. }
  383. PQclear(result);
  384. ast_free(sql);
  385. ast_free(sql2);
  386. }
  387. ast_mutex_unlock(&pgsql_lock);
  388. return 0;
  389. }
  390. /* This function should be called without holding the pgsql_columns lock */
  391. static void empty_columns(void)
  392. {
  393. struct columns *current;
  394. AST_RWLIST_WRLOCK(&psql_columns);
  395. while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
  396. ast_free(current);
  397. }
  398. AST_RWLIST_UNLOCK(&psql_columns);
  399. }
  400. static int unload_module(void)
  401. {
  402. if (ast_cdr_unregister(name)) {
  403. return -1;
  404. }
  405. ast_cli_unregister_multiple(cdr_pgsql_status_cli, ARRAY_LEN(cdr_pgsql_status_cli));
  406. PQfinish(conn);
  407. ast_free(pghostname);
  408. ast_free(pgdbname);
  409. ast_free(pgdbuser);
  410. ast_free(pgpassword);
  411. ast_free(pgdbport);
  412. ast_free(table);
  413. ast_free(encoding);
  414. ast_free(tz);
  415. empty_columns();
  416. return 0;
  417. }
  418. static int config_module(int reload)
  419. {
  420. char *pgerror;
  421. struct columns *cur;
  422. PGresult *result;
  423. const char *tmp;
  424. struct ast_config *cfg;
  425. struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
  426. if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
  427. ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
  428. return -1;
  429. } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
  430. return 0;
  431. }
  432. ast_mutex_lock(&pgsql_lock);
  433. if (!ast_variable_browse(cfg, "global")) {
  434. ast_config_destroy(cfg);
  435. ast_mutex_unlock(&pgsql_lock);
  436. ast_log(LOG_NOTICE, "cdr_pgsql configuration contains no global section, skipping module %s.\n",
  437. reload ? "reload" : "load");
  438. return -1;
  439. }
  440. if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
  441. ast_log(LOG_WARNING, "PostgreSQL server hostname not specified. Assuming unix socket connection\n");
  442. tmp = ""; /* connect via UNIX-socket by default */
  443. }
  444. ast_free(pghostname);
  445. if (!(pghostname = ast_strdup(tmp))) {
  446. ast_config_destroy(cfg);
  447. ast_mutex_unlock(&pgsql_lock);
  448. return -1;
  449. }
  450. if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
  451. ast_log(LOG_WARNING, "PostgreSQL database not specified. Assuming asterisk\n");
  452. tmp = "asteriskcdrdb";
  453. }
  454. ast_free(pgdbname);
  455. if (!(pgdbname = ast_strdup(tmp))) {
  456. ast_config_destroy(cfg);
  457. ast_mutex_unlock(&pgsql_lock);
  458. return -1;
  459. }
  460. if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
  461. ast_log(LOG_WARNING, "PostgreSQL database user not specified. Assuming asterisk\n");
  462. tmp = "asterisk";
  463. }
  464. ast_free(pgdbuser);
  465. if (!(pgdbuser = ast_strdup(tmp))) {
  466. ast_config_destroy(cfg);
  467. ast_mutex_unlock(&pgsql_lock);
  468. return -1;
  469. }
  470. if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
  471. ast_log(LOG_WARNING, "PostgreSQL database password not specified. Assuming blank\n");
  472. tmp = "";
  473. }
  474. ast_free(pgpassword);
  475. if (!(pgpassword = ast_strdup(tmp))) {
  476. ast_config_destroy(cfg);
  477. ast_mutex_unlock(&pgsql_lock);
  478. return -1;
  479. }
  480. if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
  481. ast_log(LOG_WARNING, "PostgreSQL database port not specified. Using default 5432.\n");
  482. tmp = "5432";
  483. }
  484. ast_free(pgdbport);
  485. if (!(pgdbport = ast_strdup(tmp))) {
  486. ast_config_destroy(cfg);
  487. ast_mutex_unlock(&pgsql_lock);
  488. return -1;
  489. }
  490. if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
  491. ast_log(LOG_WARNING, "CDR table not specified. Assuming cdr\n");
  492. tmp = "cdr";
  493. }
  494. ast_free(table);
  495. if (!(table = ast_strdup(tmp))) {
  496. ast_config_destroy(cfg);
  497. ast_mutex_unlock(&pgsql_lock);
  498. return -1;
  499. }
  500. if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
  501. ast_log(LOG_WARNING, "Encoding not specified. Assuming LATIN9\n");
  502. tmp = "LATIN9";
  503. }
  504. ast_free(encoding);
  505. if (!(encoding = ast_strdup(tmp))) {
  506. ast_config_destroy(cfg);
  507. ast_mutex_unlock(&pgsql_lock);
  508. return -1;
  509. }
  510. if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
  511. tmp = "";
  512. }
  513. ast_free(tz);
  514. tz = NULL;
  515. if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
  516. ast_config_destroy(cfg);
  517. ast_mutex_unlock(&pgsql_lock);
  518. return -1;
  519. }
  520. if (option_debug) {
  521. if (ast_strlen_zero(pghostname)) {
  522. ast_debug(1, "using default unix socket\n");
  523. } else {
  524. ast_debug(1, "got hostname of %s\n", pghostname);
  525. }
  526. ast_debug(1, "got port of %s\n", pgdbport);
  527. ast_debug(1, "got user of %s\n", pgdbuser);
  528. ast_debug(1, "got dbname of %s\n", pgdbname);
  529. ast_debug(1, "got password of %s\n", pgpassword);
  530. ast_debug(1, "got sql table name of %s\n", table);
  531. ast_debug(1, "got encoding of %s\n", encoding);
  532. ast_debug(1, "got timezone of %s\n", tz);
  533. }
  534. conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
  535. if (PQstatus(conn) != CONNECTION_BAD) {
  536. char sqlcmd[768];
  537. char *fname, *ftype, *flen, *fnotnull, *fdef;
  538. int i, rows, version;
  539. ast_debug(1, "Successfully connected to PostgreSQL database.\n");
  540. connected = 1;
  541. connect_time = time(NULL);
  542. records = 0;
  543. if (PQsetClientEncoding(conn, encoding)) {
  544. #ifdef HAVE_PGSQL_pg_encoding_to_char
  545. ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
  546. #else
  547. ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
  548. #endif
  549. }
  550. version = PQserverVersion(conn);
  551. if (version >= 70300) {
  552. char *schemaname, *tablename;
  553. if (strchr(table, '.')) {
  554. schemaname = ast_strdupa(table);
  555. tablename = strchr(schemaname, '.');
  556. *tablename++ = '\0';
  557. } else {
  558. schemaname = "";
  559. tablename = table;
  560. }
  561. /* Escape special characters in schemaname */
  562. if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
  563. char *tmp = schemaname, *ptr;
  564. ptr = schemaname = ast_alloca(strlen(tmp) * 2 + 1);
  565. for (; *tmp; tmp++) {
  566. if (strchr("\\'", *tmp)) {
  567. *ptr++ = *tmp;
  568. }
  569. *ptr++ = *tmp;
  570. }
  571. *ptr = '\0';
  572. }
  573. /* Escape special characters in tablename */
  574. if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
  575. char *tmp = tablename, *ptr;
  576. ptr = tablename = ast_alloca(strlen(tmp) * 2 + 1);
  577. for (; *tmp; tmp++) {
  578. if (strchr("\\'", *tmp)) {
  579. *ptr++ = *tmp;
  580. }
  581. *ptr++ = *tmp;
  582. }
  583. *ptr = '\0';
  584. }
  585. snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
  586. tablename,
  587. ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
  588. } else {
  589. snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", table);
  590. }
  591. /* Query the columns */
  592. result = PQexec(conn, sqlcmd);
  593. if (PQresultStatus(result) != PGRES_TUPLES_OK) {
  594. pgerror = PQresultErrorMessage(result);
  595. ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
  596. PQclear(result);
  597. unload_module();
  598. ast_mutex_unlock(&pgsql_lock);
  599. return AST_MODULE_LOAD_DECLINE;
  600. }
  601. rows = PQntuples(result);
  602. if (rows == 0) {
  603. ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
  604. PQclear(result);
  605. unload_module();
  606. ast_mutex_unlock(&pgsql_lock);
  607. return AST_MODULE_LOAD_DECLINE;
  608. }
  609. /* Clear out the columns list. */
  610. empty_columns();
  611. for (i = 0; i < rows; i++) {
  612. fname = PQgetvalue(result, i, 0);
  613. ftype = PQgetvalue(result, i, 1);
  614. flen = PQgetvalue(result, i, 2);
  615. fnotnull = PQgetvalue(result, i, 3);
  616. fdef = PQgetvalue(result, i, 4);
  617. if (atoi(flen) == -1) {
  618. /* For varchar columns, the maximum length is encoded in a different field */
  619. flen = PQgetvalue(result, i, 5);
  620. }
  621. ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
  622. cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
  623. if (cur) {
  624. sscanf(flen, "%30d", &cur->len);
  625. cur->name = (char *)cur + sizeof(*cur);
  626. cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
  627. strcpy(cur->name, fname);
  628. strcpy(cur->type, ftype);
  629. if (*fnotnull == 't') {
  630. cur->notnull = 1;
  631. } else {
  632. cur->notnull = 0;
  633. }
  634. if (!ast_strlen_zero(fdef)) {
  635. cur->hasdefault = 1;
  636. } else {
  637. cur->hasdefault = 0;
  638. }
  639. AST_RWLIST_WRLOCK(&psql_columns);
  640. AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
  641. AST_RWLIST_UNLOCK(&psql_columns);
  642. }
  643. }
  644. PQclear(result);
  645. } else {
  646. pgerror = PQerrorMessage(conn);
  647. ast_log(LOG_ERROR, "Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
  648. ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
  649. connected = 0;
  650. }
  651. ast_config_destroy(cfg);
  652. ast_mutex_unlock(&pgsql_lock);
  653. return 0;
  654. }
  655. static int load_module(void)
  656. {
  657. ast_cli_register_multiple(cdr_pgsql_status_cli, sizeof(cdr_pgsql_status_cli) / sizeof(struct ast_cli_entry));
  658. if (config_module(0)) {
  659. return AST_MODULE_LOAD_DECLINE;
  660. }
  661. return ast_cdr_register(name, ast_module_info->description, pgsql_log)
  662. ? AST_MODULE_LOAD_DECLINE : 0;
  663. }
  664. static int reload(void)
  665. {
  666. return config_module(1);
  667. }
  668. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
  669. .load = load_module,
  670. .unload = unload_module,
  671. .reload = reload,
  672. .load_pri = AST_MODPRI_CDR_DRIVER,
  673. );