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