cdr_pgsql.c 10 KB

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