BSD portability enhancements (bug #234)
[asterisk/asterisk.git] / cdr / cdr_mysql.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * MySQL CDR logger 
5  * 
6  * James Sharp <jsharp@psychoses.org>
7  *
8  * Modified August 2003
9  * Tilghman Lesher <asterisk__cdr__cdr_mysql__200308@the-tilghman.com>
10  *
11  * This program is free software, distributed under the terms of
12  * the GNU General Public License.
13  *
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
25 #include <stdio.h>
26 #include <string.h>
27
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <time.h>
31
32 #include <mysql.h>
33 #include <errmsg.h>
34
35 #define DATE_FORMAT "%Y-%m-%d %T"
36
37 static char *desc = "MySQL CDR Backend";
38 static char *name = "mysql";
39 static char *config = "cdr_mysql.conf";
40 static char *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *dbsock = NULL;
41 static int hostname_alloc = 0, dbname_alloc = 0, dbuser_alloc = 0, password_alloc = 0, dbsock_alloc = 0;
42 static int dbport = 0;
43 static int connected = 0;
44
45 static ast_mutex_t mysql_lock = AST_MUTEX_INITIALIZER;
46
47 static MYSQL mysql;
48
49 static int mysql_log(struct ast_cdr *cdr)
50 {
51         struct tm tm;
52         struct timeval tv;
53         char sqlcmd[2048], timestr[128];
54         time_t t;
55
56         ast_mutex_lock(&mysql_lock);
57
58         memset(sqlcmd,0,2048);
59
60         gettimeofday(&tv,NULL);
61         t = tv.tv_sec;
62         localtime_r(&t,&tm);
63         strftime(timestr,128,DATE_FORMAT,&tm);
64
65         if ((!connected) && (hostname || dbsock) && dbuser && password && dbname) {
66                 /* Attempt to connect */
67                 mysql_init(&mysql);
68                 if (mysql_real_connect(&mysql, hostname, dbuser, password, dbname, dbport, dbsock, 0)) {
69                         connected = 1;
70                 } else {
71                         ast_log(LOG_ERROR, "cdr_mysql: cannot connect to database server %s.  Call will not be logged\n", hostname);
72                 }
73         } else {
74                 /* Long connection - ping the server */
75                 int error;
76                 if ((error = mysql_ping(&mysql))) {
77                         connected = 0;
78                         switch (error) {
79                                 case CR_SERVER_GONE_ERROR:
80                                         ast_log(LOG_ERROR, "cdr_mysql: Server has gone away\n");
81                                         break;
82                                 default:
83                                         ast_log(LOG_ERROR, "cdr_mysql: Unknown connection error\n");
84                         }
85                 }
86         }
87
88         if (connected) {
89                 char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL;
90 #ifdef MYSQL_LOGUNIQUEID
91                 char *uniqueid=NULL;
92 #endif
93
94                 /* Maximum space needed would be if all characters needed to be escaped, plus a trailing NULL */
95                 if ((clid = alloca(strlen(cdr->clid) * 2 + 1)) != NULL)
96                         mysql_real_escape_string(&mysql, clid, cdr->clid, strlen(cdr->clid));
97                 if ((dcontext = alloca(strlen(cdr->dcontext) * 2 + 1)) != NULL)
98                         mysql_real_escape_string(&mysql, dcontext, cdr->dcontext, strlen(cdr->dcontext));
99                 if ((channel = alloca(strlen(cdr->channel) * 2 + 1)) != NULL)
100                         mysql_real_escape_string(&mysql, channel, cdr->channel, strlen(cdr->channel));
101                 if ((dstchannel = alloca(strlen(cdr->dstchannel) * 2 + 1)) != NULL)
102                         mysql_real_escape_string(&mysql, dstchannel, cdr->dstchannel, strlen(cdr->dstchannel));
103                 if ((lastapp = alloca(strlen(cdr->lastapp) * 2 + 1)) != NULL)
104                         mysql_real_escape_string(&mysql, lastapp, cdr->lastapp, strlen(cdr->lastapp));
105                 if ((lastdata = alloca(strlen(cdr->lastdata) * 2 + 1)) != NULL)
106                         mysql_real_escape_string(&mysql, lastdata, cdr->lastdata, strlen(cdr->lastdata));
107 #ifdef MYSQL_LOGUNIQUEID
108                 if ((uniqueid = alloca(strlen(cdr->uniqueid) * 2 + 1)) != NULL)
109                         mysql_real_escape_string(&mysql, uniqueid, cdr->uniqueid, strlen(cdr->uniqueid));
110 #endif
111
112                 /* Check for all alloca failures above at once */
113 #ifdef MYSQL_LOGUNIQUEID
114                 if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid)) {
115 #else
116                 if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata)) {
117 #endif
118                         ast_log(LOG_ERROR, "cdr_mysql:  Out of memory error (insert fails)\n");
119                         ast_mutex_unlock(&mysql_lock);
120                         return -1;
121                 }
122
123                 ast_log(LOG_DEBUG,"cdr_mysql: inserting a CDR record.\n");
124
125 #ifdef MYSQL_LOGUNIQUEID
126                 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);
127 #else
128                 sprintf(sqlcmd,"INSERT INTO cdr (calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp,lastdata,duration,billsec,disposition,amaflags,accountcode) VALUES ('%s','%s','%s','%s','%s', '%s','%s','%s','%s',%i,%i,'%s',%i,'%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);
129 #endif  
130                 ast_log(LOG_DEBUG,"cdr_mysql: SQL command as follows:  %s\n",sqlcmd);
131         
132                 if (mysql_real_query(&mysql,sqlcmd,strlen(sqlcmd))) {
133                         ast_log(LOG_ERROR,"Failed to insert into database.");
134                         ast_mutex_unlock(&mysql_lock);
135                         return -1;
136                 }
137         }
138         ast_mutex_unlock(&mysql_lock);
139         return 0;
140 }
141
142 char *description(void)
143 {
144         return desc;
145 }
146
147 static int my_unload_module(void)
148
149         if (connected) {
150                 mysql_close(&mysql);
151                 connected = 0;
152         }
153         if (hostname && hostname_alloc) {
154                 free(hostname);
155                 hostname = NULL;
156                 hostname_alloc = 0;
157         }
158         if (dbname && dbname_alloc) {
159                 free(dbname);
160                 dbname = NULL;
161                 dbname_alloc = 0;
162         }
163         if (dbuser && dbuser_alloc) {
164                 free(dbuser);
165                 dbuser = NULL;
166                 dbuser_alloc = 0;
167         }
168         if (dbsock && dbsock_alloc) {
169                 free(dbsock);
170                 dbsock = NULL;
171                 dbsock_alloc = 0;
172         }
173         if (password && password_alloc) {
174                 free(password);
175                 password = NULL;
176                 password_alloc = 0;
177         }
178         dbport = 0;
179         ast_cdr_unregister(name);
180         return 0;
181 }
182
183 static int my_load_module(void)
184 {
185         int res;
186         struct ast_config *cfg;
187         struct ast_variable *var;
188         char *tmp;
189
190         cfg = ast_load(config);
191         if (!cfg) {
192                 ast_log(LOG_WARNING, "Unable to load config for mysql CDR's: %s\n", config);
193                 return 0;
194         }
195         
196         var = ast_variable_browse(cfg, "global");
197         if (!var) {
198                 /* nothing configured */
199                 return 0;
200         }
201
202         tmp = ast_variable_retrieve(cfg,"global","hostname");
203         if (tmp) {
204                 hostname = malloc(strlen(tmp) + 1);
205                 if (hostname != NULL) {
206                         hostname_alloc = 1;
207                         strcpy(hostname,tmp);
208                 } else {
209                         ast_log(LOG_ERROR,"Out of memory error.\n");
210                         return -1;
211                 }
212         } else {
213                 ast_log(LOG_WARNING,"MySQL server hostname not specified.  Assuming localhost\n");
214                 hostname = "localhost";
215         }
216
217         tmp = ast_variable_retrieve(cfg,"global","dbname");
218         if (tmp) {
219                 dbname = malloc(strlen(tmp) + 1);
220                 if (dbname != NULL) {
221                         dbname_alloc = 1;
222                         strcpy(dbname,tmp);
223                 } else {
224                         ast_log(LOG_ERROR,"Out of memory error.\n");
225                         return -1;
226                 }
227         } else {
228                 ast_log(LOG_WARNING,"MySQL database not specified.  Assuming asteriskcdrdb\n");
229                 dbname = "asteriskcdrdb";
230         }
231
232         tmp = ast_variable_retrieve(cfg,"global","user");
233         if (tmp) {
234                 dbuser = malloc(strlen(tmp) + 1);
235                 if (dbuser != NULL) {
236                         dbuser_alloc = 1;
237                         strcpy(dbuser,tmp);
238                 } else {
239                         ast_log(LOG_ERROR,"Out of memory error.\n");
240                         return -1;
241                 }
242         } else {
243                 ast_log(LOG_WARNING,"MySQL database user not specified.  Assuming root\n");
244                 dbuser = "root";
245         }
246
247         tmp = ast_variable_retrieve(cfg,"global","sock");
248         if (tmp) {
249                 dbsock = malloc(strlen(tmp) + 1);
250                 if (dbsock != NULL) {
251                         dbsock_alloc = 1;
252                         strcpy(dbsock,tmp);
253                 } else {
254                         ast_log(LOG_ERROR,"Out of memory error.\n");
255                         return -1;
256                 }
257         } else {
258                 ast_log(LOG_WARNING,"MySQL database sock file not specified.  Using default\n");
259                 dbsock = NULL;
260         }
261
262         tmp = ast_variable_retrieve(cfg,"global","password");
263         if (tmp) {
264                 password = malloc(strlen(tmp) + 1);
265                 if (password != NULL) {
266                         password_alloc = 1;
267                         strcpy(password,tmp);
268                 } else {
269                         ast_log(LOG_ERROR,"Out of memory error.\n");
270                         return -1;
271                 }
272         } else {
273                 ast_log(LOG_WARNING,"MySQL database password not specified.  Assuming blank\n");
274                 password = "";
275         }
276
277         tmp = ast_variable_retrieve(cfg,"global","port");
278         if (tmp) {
279                 if (sscanf(tmp,"%d",&dbport) < 1) {
280                         ast_log(LOG_WARNING,"Invalid MySQL port number.  Using default\n");
281                         dbport = 0;
282                 }
283         }
284
285         ast_destroy(cfg);
286
287         ast_log(LOG_DEBUG,"cdr_mysql: got hostname of %s\n",hostname);
288         ast_log(LOG_DEBUG,"cdr_mysql: got port of %d\n",dbport);
289         if (dbsock)
290                 ast_log(LOG_DEBUG,"cdr_mysql: got sock file of %s\n",dbsock);
291         ast_log(LOG_DEBUG,"cdr_mysql: got user of %s\n",dbuser);
292         ast_log(LOG_DEBUG,"cdr_mysql: got dbname of %s\n",dbname);
293         ast_log(LOG_DEBUG,"cdr_mysql: got password of %s\n",password);
294
295         mysql_init(&mysql);
296
297         if (!mysql_real_connect(&mysql, hostname, dbuser, password, dbname, dbport, dbsock, 0)) {
298                 ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n", dbname, hostname);
299                 connected = 0;
300         } else {
301                 ast_log(LOG_DEBUG,"Successfully connected to MySQL database.\n");
302                 connected = 1;
303         }
304
305         res = ast_cdr_register(name, desc, mysql_log);
306         if (res) {
307                 ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
308         }
309         return res;
310 }
311
312 int load_module(void)
313 {
314         return my_load_module();
315 }
316
317 int unload_module(void)
318 {
319         return my_unload_module();
320 }
321
322 int reload(void)
323 {
324         my_unload_module();
325         return my_load_module();
326 }
327
328 int usecount(void)
329 {
330         return connected;
331 }
332
333 char *key()
334 {
335         return ASTERISK_GPL_KEY;
336 }