cdr_pgsql.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2003 - 2005
  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. /*! \file
  23. *
  24. * \brief PostgreSQL CDR logger
  25. *
  26. * \author Matthew D. Hardeman <mhardemn@papersoft.com>
  27. *
  28. * See also
  29. * \arg \ref Config_cdr
  30. * \arg http://www.postgresql.org/
  31. * \ingroup cdr_drivers
  32. */
  33. #include <sys/types.h>
  34. #include <stdio.h>
  35. #include <string.h>
  36. #include <stdlib.h>
  37. #include <unistd.h>
  38. #include <time.h>
  39. #include <libpq-fe.h>
  40. #include "asterisk.h"
  41. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  42. #include "asterisk/config.h"
  43. #include "asterisk/options.h"
  44. #include "asterisk/channel.h"
  45. #include "asterisk/cdr.h"
  46. #include "asterisk/module.h"
  47. #include "asterisk/logger.h"
  48. #include "asterisk.h"
  49. #define DATE_FORMAT "%Y-%m-%d %T"
  50. static char *desc = "PostgreSQL CDR Backend";
  51. static char *name = "pgsql";
  52. static char *config = "cdr_pgsql.conf";
  53. static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbsock = NULL, *pgdbport = NULL, *table = NULL;
  54. static int connected = 0;
  55. AST_MUTEX_DEFINE_STATIC(pgsql_lock);
  56. PGconn *conn;
  57. PGresult *result;
  58. static int pgsql_log(struct ast_cdr *cdr)
  59. {
  60. struct tm tm;
  61. char sqlcmd[2048] = "", timestr[128];
  62. char *pgerror;
  63. ast_mutex_lock(&pgsql_lock);
  64. localtime_r(&cdr->start.tv_sec,&tm);
  65. strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
  66. if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
  67. conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
  68. if (PQstatus(conn) != CONNECTION_BAD) {
  69. connected = 1;
  70. } else {
  71. pgerror = PQerrorMessage(conn);
  72. ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
  73. ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
  74. }
  75. }
  76. if (connected) {
  77. char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL;
  78. char *uniqueid=NULL, *userfield=NULL;
  79. /* Maximum space needed would be if all characters needed to be escaped, plus a trailing NULL */
  80. if ((clid = alloca(strlen(cdr->clid) * 2 + 1)) != NULL)
  81. PQescapeString(clid, cdr->clid, strlen(cdr->clid));
  82. if ((dcontext = alloca(strlen(cdr->dcontext) * 2 + 1)) != NULL)
  83. PQescapeString(dcontext, cdr->dcontext, strlen(cdr->dcontext));
  84. if ((channel = alloca(strlen(cdr->channel) * 2 + 1)) != NULL)
  85. PQescapeString(channel, cdr->channel, strlen(cdr->channel));
  86. if ((dstchannel = alloca(strlen(cdr->dstchannel) * 2 + 1)) != NULL)
  87. PQescapeString(dstchannel, cdr->dstchannel, strlen(cdr->dstchannel));
  88. if ((lastapp = alloca(strlen(cdr->lastapp) * 2 + 1)) != NULL)
  89. PQescapeString(lastapp, cdr->lastapp, strlen(cdr->lastapp));
  90. if ((lastdata = alloca(strlen(cdr->lastdata) * 2 + 1)) != NULL)
  91. PQescapeString(lastdata, cdr->lastdata, strlen(cdr->lastdata));
  92. if ((uniqueid = alloca(strlen(cdr->uniqueid) * 2 + 1)) != NULL)
  93. PQescapeString(uniqueid, cdr->uniqueid, strlen(cdr->uniqueid));
  94. if ((userfield = alloca(strlen(cdr->userfield) * 2 + 1)) != NULL)
  95. PQescapeString(userfield, cdr->userfield, strlen(cdr->userfield));
  96. /* Check for all alloca failures above at once */
  97. if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid) || (!userfield)) {
  98. ast_log(LOG_ERROR, "cdr_pgsql: Out of memory error (insert fails)\n");
  99. ast_mutex_unlock(&pgsql_lock);
  100. return -1;
  101. }
  102. ast_log(LOG_DEBUG,"cdr_pgsql: inserting a CDR record.\n");
  103. snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s (calldate,clid,src,dst,dcontext,channel,dstchannel,"
  104. "lastapp,lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) VALUES"
  105. " ('%s','%s','%s','%s','%s', '%s','%s','%s','%s',%d,%d,'%s',%d,'%s','%s','%s')",
  106. table,timestr,clid,cdr->src, cdr->dst, dcontext,channel, dstchannel, lastapp, lastdata,
  107. cdr->duration,cdr->billsec,ast_cdr_disp2str(cdr->disposition),cdr->amaflags, cdr->accountcode, uniqueid, userfield);
  108. ast_log(LOG_DEBUG,"cdr_pgsql: SQL command executed: %s\n",sqlcmd);
  109. /* Test to be sure we're still connected... */
  110. /* If we're connected, and connection is working, good. */
  111. /* Otherwise, attempt reconnect. If it fails... sorry... */
  112. if (PQstatus(conn) == CONNECTION_OK) {
  113. connected = 1;
  114. } else {
  115. ast_log(LOG_ERROR, "cdr_pgsql: Connection was lost... attempting to reconnect.\n");
  116. PQreset(conn);
  117. if (PQstatus(conn) == CONNECTION_OK) {
  118. ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
  119. connected = 1;
  120. } else {
  121. pgerror = PQerrorMessage(conn);
  122. ast_log(LOG_ERROR, "cdr_pgsql: Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
  123. ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
  124. connected = 0;
  125. ast_mutex_unlock(&pgsql_lock);
  126. return -1;
  127. }
  128. }
  129. result = PQexec(conn, sqlcmd);
  130. if ( PQresultStatus(result) != PGRES_COMMAND_OK) {
  131. pgerror = PQresultErrorMessage(result);
  132. ast_log(LOG_ERROR,"cdr_pgsql: Failed to insert call detail record into database!\n");
  133. ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
  134. ast_log(LOG_ERROR,"cdr_pgsql: Connection may have been lost... attempting to reconnect.\n");
  135. PQreset(conn);
  136. if (PQstatus(conn) == CONNECTION_OK) {
  137. ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
  138. connected = 1;
  139. result = PQexec(conn, sqlcmd);
  140. if ( PQresultStatus(result) != PGRES_COMMAND_OK)
  141. {
  142. pgerror = PQresultErrorMessage(result);
  143. ast_log(LOG_ERROR,"cdr_pgsql: HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
  144. ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
  145. }
  146. }
  147. ast_mutex_unlock(&pgsql_lock);
  148. return -1;
  149. }
  150. }
  151. ast_mutex_unlock(&pgsql_lock);
  152. return 0;
  153. }
  154. char *description(void)
  155. {
  156. return desc;
  157. }
  158. static int my_unload_module(void)
  159. {
  160. if (conn)
  161. PQfinish(conn);
  162. if (pghostname)
  163. free(pghostname);
  164. if (pgdbname)
  165. free(pgdbname);
  166. if (pgdbuser)
  167. free(pgdbuser);
  168. if (pgdbsock)
  169. free(pgdbsock);
  170. if (pgpassword)
  171. free(pgpassword);
  172. if (pgdbport)
  173. free(pgdbport);
  174. if (table)
  175. free(table);
  176. ast_cdr_unregister(name);
  177. return 0;
  178. }
  179. static int process_my_load_module(struct ast_config *cfg)
  180. {
  181. int res;
  182. struct ast_variable *var;
  183. char *pgerror;
  184. char *tmp;
  185. var = ast_variable_browse(cfg, "global");
  186. if (!var) {
  187. /* nothing configured */
  188. return 0;
  189. }
  190. tmp = ast_variable_retrieve(cfg,"global","hostname");
  191. if (tmp == NULL) {
  192. ast_log(LOG_WARNING,"PostgreSQL server hostname not specified. Assuming localhost\n");
  193. tmp = "localhost";
  194. }
  195. pghostname = strdup(tmp);
  196. if (pghostname == NULL) {
  197. ast_log(LOG_ERROR,"Out of memory error.\n");
  198. return -1;
  199. }
  200. tmp = ast_variable_retrieve(cfg,"global","dbname");
  201. if (tmp == NULL) {
  202. ast_log(LOG_WARNING,"PostgreSQL database not specified. Assuming asterisk\n");
  203. tmp = "asteriskcdrdb";
  204. }
  205. pgdbname = strdup(tmp);
  206. if (pgdbname == NULL) {
  207. ast_log(LOG_ERROR,"Out of memory error.\n");
  208. return -1;
  209. }
  210. tmp = ast_variable_retrieve(cfg,"global","user");
  211. if (tmp == NULL) {
  212. ast_log(LOG_WARNING,"PostgreSQL database user not specified. Assuming root\n");
  213. tmp = "root";
  214. }
  215. pgdbuser = strdup(tmp);
  216. if (pgdbuser == NULL) {
  217. ast_log(LOG_ERROR,"Out of memory error.\n");
  218. return -1;
  219. }
  220. tmp = ast_variable_retrieve(cfg,"global","password");
  221. if (tmp == NULL) {
  222. ast_log(LOG_WARNING,"PostgreSQL database password not specified. Assuming blank\n");
  223. tmp = "";
  224. }
  225. pgpassword = strdup(tmp);
  226. if (pgpassword == NULL) {
  227. ast_log(LOG_ERROR,"Out of memory error.\n");
  228. return -1;
  229. }
  230. tmp = ast_variable_retrieve(cfg,"global","port");
  231. if (tmp == NULL) {
  232. ast_log(LOG_WARNING,"PostgreSQL database port not specified. Using default 5432.\n");
  233. tmp = "5432";
  234. }
  235. pgdbport = strdup(tmp);
  236. if (pgdbport == NULL) {
  237. ast_log(LOG_ERROR,"Out of memory error.\n");
  238. return -1;
  239. }
  240. tmp = ast_variable_retrieve(cfg,"global","table");
  241. if (tmp == NULL) {
  242. ast_log(LOG_WARNING,"CDR table not specified. Assuming cdr\n");
  243. tmp = "cdr";
  244. }
  245. table = strdup(tmp);
  246. if (table == NULL) {
  247. ast_log(LOG_ERROR,"Out of memory error.\n");
  248. return -1;
  249. }
  250. ast_log(LOG_DEBUG,"cdr_pgsql: got hostname of %s\n",pghostname);
  251. ast_log(LOG_DEBUG,"cdr_pgsql: got port of %s\n",pgdbport);
  252. if (pgdbsock)
  253. ast_log(LOG_DEBUG,"cdr_pgsql: got sock file of %s\n",pgdbsock);
  254. ast_log(LOG_DEBUG,"cdr_pgsql: got user of %s\n",pgdbuser);
  255. ast_log(LOG_DEBUG,"cdr_pgsql: got dbname of %s\n",pgdbname);
  256. ast_log(LOG_DEBUG,"cdr_pgsql: got password of %s\n",pgpassword);
  257. ast_log(LOG_DEBUG,"cdr_pgsql: got sql table name of %s\n",table);
  258. conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
  259. if (PQstatus(conn) != CONNECTION_BAD) {
  260. ast_log(LOG_DEBUG,"Successfully connected to PostgreSQL database.\n");
  261. connected = 1;
  262. } else {
  263. pgerror = PQerrorMessage(conn);
  264. ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
  265. ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
  266. connected = 0;
  267. }
  268. res = ast_cdr_register(name, desc, pgsql_log);
  269. if (res) {
  270. ast_log(LOG_ERROR, "Unable to register PGSQL CDR handling\n");
  271. }
  272. return res;
  273. }
  274. static int my_load_module(void)
  275. {
  276. struct ast_config *cfg;
  277. int res;
  278. cfg = ast_config_load(config);
  279. if (!cfg) {
  280. ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
  281. return 0;
  282. }
  283. res = process_my_load_module(cfg);
  284. ast_config_destroy(cfg);
  285. return res;
  286. }
  287. int load_module(void)
  288. {
  289. return my_load_module();
  290. }
  291. int unload_module(void)
  292. {
  293. return my_unload_module();
  294. }
  295. int reload(void)
  296. {
  297. my_unload_module();
  298. return my_load_module();
  299. }
  300. int usecount(void)
  301. {
  302. /* To be able to unload the module */
  303. if ( ast_mutex_trylock(&pgsql_lock) ) {
  304. return 1;
  305. } else {
  306. ast_mutex_unlock(&pgsql_lock);
  307. return 0;
  308. }
  309. }
  310. char *key()
  311. {
  312. return ASTERISK_GPL_KEY;
  313. }