cdr_pgsql.c 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. static ast_mutex_t pgsql_lock = AST_MUTEX_INITIALIZER;
  38. PGconn *conn;
  39. PGresult *result;
  40. static int pgsql_log(struct ast_cdr *cdr)
  41. {
  42. struct tm tm;
  43. struct timeval tv;
  44. char sqlcmd[2048], timestr[128];
  45. time_t t;
  46. char *pgerror;
  47. ast_mutex_lock(&pgsql_lock);
  48. memset(sqlcmd,0,2048);
  49. gettimeofday(&tv,NULL);
  50. t = tv.tv_sec;
  51. localtime_r(&t,&tm);
  52. strftime(timestr,128,DATE_FORMAT,&tm);
  53. if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
  54. conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
  55. if (PQstatus(conn) != CONNECTION_BAD) {
  56. connected = 1;
  57. } else {
  58. pgerror = PQerrorMessage(conn);
  59. ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
  60. ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
  61. }
  62. }
  63. if (connected) {
  64. char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL;
  65. char *uniqueid=NULL, *userfield=NULL;
  66. /* Maximum space needed would be if all characters needed to be escaped, plus a trailing NULL */
  67. if ((clid = alloca(strlen(cdr->clid) * 2 + 1)) != NULL)
  68. PQescapeString(clid, cdr->clid, strlen(cdr->clid));
  69. if ((dcontext = alloca(strlen(cdr->dcontext) * 2 + 1)) != NULL)
  70. PQescapeString(dcontext, cdr->dcontext, strlen(cdr->dcontext));
  71. if ((channel = alloca(strlen(cdr->channel) * 2 + 1)) != NULL)
  72. PQescapeString(channel, cdr->channel, strlen(cdr->channel));
  73. if ((dstchannel = alloca(strlen(cdr->dstchannel) * 2 + 1)) != NULL)
  74. PQescapeString(dstchannel, cdr->dstchannel, strlen(cdr->dstchannel));
  75. if ((lastapp = alloca(strlen(cdr->lastapp) * 2 + 1)) != NULL)
  76. PQescapeString(lastapp, cdr->lastapp, strlen(cdr->lastapp));
  77. if ((lastdata = alloca(strlen(cdr->lastdata) * 2 + 1)) != NULL)
  78. PQescapeString(lastdata, cdr->lastdata, strlen(cdr->lastdata));
  79. if ((uniqueid = alloca(strlen(cdr->uniqueid) * 2 + 1)) != NULL)
  80. PQescapeString(uniqueid, cdr->uniqueid, strlen(cdr->uniqueid));
  81. if ((userfield = alloca(strlen(cdr->userfield) * 2 + 1)) != NULL)
  82. PQescapeString(userfield, cdr->userfield, strlen(cdr->userfield));
  83. /* Check for all alloca failures above at once */
  84. if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid) || (!userfield)) {
  85. ast_log(LOG_ERROR, "cdr_pgsql: Out of memory error (insert fails)\n");
  86. ast_mutex_unlock(&pgsql_lock);
  87. return -1;
  88. }
  89. ast_log(LOG_DEBUG,"cdr_pgsql: inserting a CDR record.\n");
  90. sprintf(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);
  91. ast_log(LOG_DEBUG,"cdr_pgsql: SQL command executed: %s\n",sqlcmd);
  92. /* Test to be sure we're still connected... */
  93. /* If we're connected, and connection is working, good. */
  94. /* Otherwise, attempt reconnect. If it fails... sorry... */
  95. if (PQstatus(conn) == CONNECTION_OK) {
  96. connected = 1;
  97. } else {
  98. ast_log(LOG_ERROR, "cdr_pgsql: Connection was lost... attempting to reconnect.\n");
  99. PQreset(conn);
  100. if (PQstatus(conn) == CONNECTION_OK) {
  101. ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
  102. connected = 1;
  103. } else {
  104. pgerror = PQerrorMessage(conn);
  105. ast_log(LOG_ERROR, "cdr_pgsql: Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
  106. ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
  107. connected = 0;
  108. ast_mutex_unlock(&pgsql_lock);
  109. return -1;
  110. }
  111. }
  112. result = PQexec(conn, sqlcmd);
  113. if ( PQresultStatus(result) != PGRES_COMMAND_OK) {
  114. pgerror = PQresultErrorMessage(result);
  115. ast_log(LOG_ERROR,"cdr_pgsql: Failed to insert call detail record into database!\n");
  116. ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
  117. ast_mutex_unlock(&pgsql_lock);
  118. return -1;
  119. }
  120. }
  121. ast_mutex_unlock(&pgsql_lock);
  122. return 0;
  123. }
  124. char *description(void)
  125. {
  126. return desc;
  127. }
  128. static int my_unload_module(void)
  129. {
  130. PQfinish(conn);
  131. connected = 0;
  132. if (pghostname && hostname_alloc) {
  133. free(pghostname);
  134. pghostname = NULL;
  135. hostname_alloc = 0;
  136. }
  137. if (pgdbname && dbname_alloc) {
  138. free(pgdbname);
  139. pgdbname = NULL;
  140. dbname_alloc = 0;
  141. }
  142. if (pgdbuser && dbuser_alloc) {
  143. free(pgdbuser);
  144. pgdbuser = NULL;
  145. dbuser_alloc = 0;
  146. }
  147. if (pgdbsock && dbsock_alloc) {
  148. free(pgdbsock);
  149. pgdbsock = NULL;
  150. dbsock_alloc = 0;
  151. }
  152. if (pgpassword && password_alloc) {
  153. free(pgpassword);
  154. pgpassword = NULL;
  155. password_alloc = 0;
  156. }
  157. if (pgdbport && dbport_alloc) {
  158. free(pgdbport);
  159. pgdbport = NULL;
  160. dbport_alloc = 0;
  161. }
  162. ast_cdr_unregister(name);
  163. return 0;
  164. }
  165. static int my_load_module(void)
  166. {
  167. int res;
  168. struct ast_config *cfg;
  169. struct ast_variable *var;
  170. char *pgerror;
  171. char *tmp;
  172. cfg = ast_load(config);
  173. if (!cfg) {
  174. ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
  175. return 0;
  176. }
  177. var = ast_variable_browse(cfg, "global");
  178. if (!var) {
  179. /* nothing configured */
  180. return 0;
  181. }
  182. tmp = ast_variable_retrieve(cfg,"global","hostname");
  183. if (tmp) {
  184. pghostname = malloc(strlen(tmp) + 1);
  185. if (pghostname != NULL) {
  186. hostname_alloc = 1;
  187. strcpy(pghostname,tmp);
  188. } else {
  189. ast_log(LOG_ERROR,"Out of memory error.\n");
  190. return -1;
  191. }
  192. } else {
  193. ast_log(LOG_WARNING,"PostgreSQL server hostname not specified. Assuming localhost\n");
  194. pghostname = "localhost";
  195. }
  196. tmp = ast_variable_retrieve(cfg,"global","dbname");
  197. if (tmp) {
  198. pgdbname = malloc(strlen(tmp) + 1);
  199. if (pgdbname != NULL) {
  200. dbname_alloc = 1;
  201. strcpy(pgdbname,tmp);
  202. } else {
  203. ast_log(LOG_ERROR,"Out of memory error.\n");
  204. return -1;
  205. }
  206. } else {
  207. ast_log(LOG_WARNING,"PostgreSQL database not specified. Assuming asterisk\n");
  208. pgdbname = "asteriskcdrdb";
  209. }
  210. tmp = ast_variable_retrieve(cfg,"global","user");
  211. if (tmp) {
  212. pgdbuser = malloc(strlen(tmp) + 1);
  213. if (pgdbuser != NULL) {
  214. dbuser_alloc = 1;
  215. strcpy(pgdbuser,tmp);
  216. } else {
  217. ast_log(LOG_ERROR,"Out of memory error.\n");
  218. return -1;
  219. }
  220. } else {
  221. ast_log(LOG_WARNING,"PostgreSQL database user not specified. Assuming root\n");
  222. pgdbuser = "root";
  223. }
  224. tmp = ast_variable_retrieve(cfg,"global","password");
  225. if (tmp) {
  226. pgpassword = malloc(strlen(tmp) + 1);
  227. if (pgpassword != NULL) {
  228. password_alloc = 1;
  229. strcpy(pgpassword,tmp);
  230. } else {
  231. ast_log(LOG_ERROR,"Out of memory error.\n");
  232. return -1;
  233. }
  234. } else {
  235. ast_log(LOG_WARNING,"PostgreSQL database password not specified. Assuming blank\n");
  236. pgpassword = "";
  237. }
  238. tmp = ast_variable_retrieve(cfg,"global","port");
  239. if (tmp) {
  240. pgdbport = malloc(strlen(tmp) + 1);
  241. if (pgdbport != NULL) {
  242. dbport_alloc = 1;
  243. strcpy(pgdbport,tmp);
  244. } else {
  245. ast_log(LOG_ERROR,"Out of memory error.\n");
  246. return -1;
  247. }
  248. } else {
  249. ast_log(LOG_WARNING,"PostgreSQL database port not specified. Using default 5432.\n");
  250. pgdbport = "5432";
  251. }
  252. ast_destroy(cfg);
  253. ast_log(LOG_DEBUG,"cdr_pgsql: got hostname of %s\n",pghostname);
  254. ast_log(LOG_DEBUG,"cdr_pgsql: got port of %s\n",pgdbport);
  255. if (pgdbsock)
  256. ast_log(LOG_DEBUG,"cdr_pgsql: got sock file of %s\n",pgdbsock);
  257. ast_log(LOG_DEBUG,"cdr_pgsql: got user of %s\n",pgdbuser);
  258. ast_log(LOG_DEBUG,"cdr_pgsql: got dbname of %s\n",pgdbname);
  259. ast_log(LOG_DEBUG,"cdr_pgsql: got password of %s\n",pgpassword);
  260. conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
  261. if (PQstatus(conn) != CONNECTION_BAD) {
  262. ast_log(LOG_DEBUG,"Successfully connected to PostgreSQL database.\n");
  263. connected = 1;
  264. } else {
  265. pgerror = PQerrorMessage(conn);
  266. ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
  267. ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
  268. connected = 0;
  269. }
  270. res = ast_cdr_register(name, desc, pgsql_log);
  271. if (res) {
  272. ast_log(LOG_ERROR, "Unable to register PGSQL CDR handling\n");
  273. }
  274. return res;
  275. }
  276. int load_module(void)
  277. {
  278. return my_load_module();
  279. }
  280. int unload_module(void)
  281. {
  282. return my_unload_module();
  283. }
  284. int reload(void)
  285. {
  286. my_unload_module();
  287. return my_load_module();
  288. }
  289. int usecount(void)
  290. {
  291. return connected;
  292. }
  293. char *key()
  294. {
  295. return ASTERISK_GPL_KEY;
  296. }