fix a minor typo from brian
[asterisk/asterisk.git] / cdr / cdr_odbc.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * ODBC CDR Backend
5  * 
6  * Brian K. West <brian@bkw.org>
7  *
8  * This program is free software, distributed under the terms of
9  * the GNU General Public License.
10  *
11  * Copyright (c) 2003 Digium, Inc.
12  *
13  */
14
15 #include <sys/types.h>
16 #include <asterisk/config.h>
17 #include <asterisk/options.h>
18 #include <asterisk/channel.h>
19 #include <asterisk/cdr.h>
20 #include <asterisk/module.h>
21 #include <asterisk/logger.h>
22 #include "../asterisk.h"
23
24 #include <stdio.h>
25 #include <string.h>
26
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <time.h>
30
31 #include <sql.h>
32 #include <sqlext.h>
33 #include <sqltypes.h>
34
35 #define DATE_FORMAT "%Y-%m-%d %T"
36
37 static char *desc = "ODBC CDR Backend";
38 static char *name = "ODBC";
39 static char *config = "cdr_odbc.conf";
40 static char *dsn = NULL, *username = NULL, *password = NULL, *loguniqueid = NULL;
41 static int dsn_alloc = 0, username_alloc = 0, password_alloc = 0;
42 static int connected = 0;
43
44 static ast_mutex_t odbc_lock = AST_MUTEX_INITIALIZER;
45
46 static int odbc_do_query(char *sqlcmd);
47 static int odbc_init(void);
48 static size_t escape_string(char *to, const char *from, size_t length);
49
50 static SQLHENV  ODBC_env = SQL_NULL_HANDLE;     /* global ODBC Environment */
51 static int      ODBC_res;                       /* global ODBC Result of Functions */
52 static SQLHDBC  ODBC_con;                       /* global ODBC Connection Handle */
53 static SQLHSTMT ODBC_stmt;                      /* global ODBC Statement Handle */
54
55 static int odbc_log(struct ast_cdr *cdr)
56 {
57         int res;
58         struct tm tm;
59         struct timeval tv;
60         time_t t;
61         char sqlcmd[2048], timestr[128];
62         char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL, *uniqueid=NULL;
63         
64         ast_mutex_lock(&odbc_lock);
65
66         gettimeofday(&tv,NULL);
67         t = tv.tv_sec;
68         localtime_r(&t,&tm);
69         strftime(timestr,128,DATE_FORMAT,&tm);
70
71         memset(sqlcmd,0,2048);
72
73         if((clid = alloca(strlen(cdr->clid) * 2 + 1)) != NULL)
74                 escape_string(clid, cdr->clid, strlen(cdr->clid));
75         if((dcontext = alloca(strlen(cdr->dcontext) * 2 + 1)) != NULL)
76                 escape_string(dcontext, cdr->dcontext, strlen(cdr->dcontext));
77         if((channel = alloca(strlen(cdr->channel) * 2 + 1)) != NULL)
78                 escape_string(channel, cdr->channel, strlen(cdr->channel));
79         if((dstchannel = alloca(strlen(cdr->dstchannel) * 2 + 1)) != NULL)
80                 escape_string(dstchannel, cdr->dstchannel, strlen(cdr->dstchannel));
81         if((lastapp = alloca(strlen(cdr->lastapp) * 2 + 1)) != NULL)
82                 escape_string(lastapp, cdr->lastapp, strlen(cdr->lastapp));
83         if((lastdata = alloca(strlen(cdr->lastdata) * 2 + 1)) != NULL)
84                 escape_string(lastdata, cdr->lastdata, strlen(cdr->lastdata));
85         if((uniqueid = alloca(strlen(cdr->uniqueid) * 2 + 1)) != NULL)
86                 escape_string(uniqueid, cdr->uniqueid, strlen(cdr->uniqueid));
87
88         if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid))
89         {
90                 ast_log(LOG_ERROR, "cdr_odbc:  Out of memory error (insert fails)\n");
91                 ast_mutex_unlock(&odbc_lock);
92                 return -1;
93         }
94
95         if((strcmp(loguniqueid, "1") == 0) || (strcmp(loguniqueid, "yes") == 0))
96         {
97                 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,%i,%i,'%s','%s')", timestr, clid, cdr->src, cdr->dst, dcontext, channel, dstchannel, lastapp, lastdata, cdr->duration, cdr->billsec, cdr->disposition, cdr->amaflags, cdr->accountcode, uniqueid);
98         }
99         else
100         {
101                 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,%i,%i,'%s')", timestr, clid, cdr->src, cdr->dst, dcontext, channel, dstchannel, lastapp, lastdata, cdr->duration, cdr->billsec, cdr->disposition, cdr->amaflags, cdr->accountcode);
102         }
103
104         if(connected)
105         {
106                 res = odbc_do_query(sqlcmd);
107                 if(res < 0)
108                 {
109                         if(option_verbose > 3)          
110                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n");
111                         res = odbc_init();
112                         if(option_verbose > 3)
113                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Reconnecting to dsn %s\n", dsn);
114                         if(res < 0)
115                         {
116                                 if(option_verbose > 3)
117                                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: %s has gone away!\n", dsn);
118                                 connected = 0;
119                         }
120                         else
121                         {
122                                 if(option_verbose > 3)
123                                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Trying Query again!\n");
124                                 res = odbc_do_query(sqlcmd);
125                                 if(res < 0)
126                                 {
127                                         if(option_verbose > 3)
128                                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n");
129                                 }
130                         }
131                 }
132         }
133         else
134         {
135                 if(option_verbose > 3)
136                          ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Reconnecting to dsn %s\n", dsn);
137                 res = odbc_init();
138                 if(res < 0)
139                 {
140                         if(option_verbose > 3)
141                         {
142                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: %s has gone away!\n", dsn);
143                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Call not logged!\n");
144                         }
145                 }
146                 else
147                 {
148                         if(option_verbose > 3)
149                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Trying Query again!\n");
150                         res = odbc_do_query(sqlcmd);
151                         if(res < 0)
152                         {
153                                 if(option_verbose > 3)
154                                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n");
155                         }
156                 }
157         }
158         ast_mutex_unlock(&odbc_lock);
159         return 0;
160 }
161
162 char *description(void)
163 {
164         return desc;
165 }
166
167 static int odbc_unload_module(void)
168 {
169         if (connected)
170         {
171                 if(option_verbose > 3)
172                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Disconnecting from %s\n", dsn);
173                 SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
174                 SQLDisconnect(ODBC_con);
175                 SQLFreeHandle(SQL_HANDLE_DBC, ODBC_con);
176                 SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
177                 connected = 0;
178         }
179         if (dsn && dsn_alloc)
180         {
181                 if(option_verbose > 3)
182                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free dsn\n");
183                 free(dsn);
184                 dsn = NULL;
185                 dsn_alloc = 0;
186         }
187         if (username && username_alloc)
188         {
189                 if(option_verbose > 3)
190                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free username\n");
191                 free(username);
192                 username = NULL;
193                 username_alloc = 0;
194         }
195         if (password && password_alloc)
196         {
197                 if(option_verbose > 3)
198                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free password\n");
199                 free(password);
200                 password = NULL;
201                 password_alloc = 0;
202         }
203         ast_cdr_unregister(name);
204         return 0;
205 }
206
207 static int odbc_load_module(void)
208 {
209         int res;
210         struct ast_config *cfg;
211         struct ast_variable *var;
212         char *tmp;
213
214         cfg = ast_load(config);
215         if (!cfg)
216         {
217                 ast_log(LOG_WARNING, "cdr_odbc: Unable to load config for ODBC CDR's: %s\n", config);
218                 return 0;
219         }
220         
221         var = ast_variable_browse(cfg, "global");
222         if (!var) {
223                 /* nothing configured */
224                 return 0;
225         }
226
227         tmp = ast_variable_retrieve(cfg,"global","dsn");
228         if (tmp)
229         {
230                 dsn = malloc(strlen(tmp) + 1);
231                 if (dsn != NULL)
232                 {
233                         dsn_alloc = 1;
234                         strcpy(dsn,tmp);
235                 }
236                 else
237                 {
238                         ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
239                         return -1;
240                 }
241         }
242         else
243         {
244                 ast_log(LOG_WARNING,"cdr_odbc: dsn not specified.  Assuming asteriskdb\n");
245                 dsn = "asteriskdb";
246         }
247
248         tmp = ast_variable_retrieve(cfg,"global","username");
249         if (tmp)
250         {
251                 username = malloc(strlen(tmp) + 1);
252                 if (username != NULL)
253                 {
254                         username_alloc = 1;
255                         strcpy(username,tmp);
256                 }
257                 else
258                 {
259                         ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
260                         return -1;
261                 }
262         }
263         else
264         {
265                 ast_log(LOG_WARNING,"cdr_odbc: username not specified.  Assuming root\n");
266                 username = "root";
267         }
268
269         tmp = ast_variable_retrieve(cfg,"global","password");
270         if (tmp)
271         {
272                 password = malloc(strlen(tmp) + 1);
273                 if (password != NULL)
274                 {
275                         password_alloc = 1;
276                         strcpy(password,tmp);
277                 }
278                 else
279                 {
280                         ast_log(LOG_ERROR,"cdr_odbc: Out of memory error.\n");
281                         return -1;
282                 }
283         }
284         else
285         {
286                 ast_log(LOG_WARNING,"cdr_odbc: database password not specified.  Assuming blank\n");
287                 password = "";
288         }
289
290         tmp = ast_variable_retrieve(cfg,"global","loguniqueid");
291         if (tmp)
292         {
293                 loguniqueid = malloc(strlen(tmp) + 1);
294                 if (loguniqueid != NULL)
295                 {
296                         strcpy(loguniqueid,tmp);
297                         ast_log(LOG_WARNING,"cdr_odbc: Logging uniqueid\n");
298                 }
299                 else
300                 {
301                         ast_log(LOG_ERROR,"cdr_odbc: Not logging uniqueid\n");
302                 }
303         }
304         else
305         {
306                 ast_log(LOG_WARNING,"cdr_odbc: Not logging uniqueid\n");
307                 loguniqueid = NULL;
308         }
309
310         ast_destroy(cfg);
311         if(option_verbose > 3)
312         {
313                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: dsn is %s\n",dsn);
314                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: username is %s\n",username);
315                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: password is [secret]\n");
316
317         }
318         
319         res = odbc_init();
320         if(res < 0)
321         {
322                 ast_log(LOG_ERROR, "cdr_odbc: Unable to connect to datasource: %s\n", dsn);
323                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Unable to connect to datasource: %s\n", dsn);
324         }
325
326         res = ast_cdr_register(name, desc, odbc_log);
327         if (res)
328         {
329                 ast_log(LOG_ERROR, "cdr_odbc: Unable to register ODBC CDR handling\n");
330         }
331         return res;
332 }
333
334 static int odbc_do_query(char *sqlcmd)
335 {
336         long int ODBC_err;
337         short int ODBC_mlen;
338         char ODBC_msg[200], ODBC_stat[10];
339
340         ODBC_res = SQLAllocHandle(SQL_HANDLE_STMT, ODBC_con, &ODBC_stmt);
341
342         if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
343         {
344                 if(option_verbose > 3)
345                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Failure in AllocStatement %d\n", ODBC_res);
346                 SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
347                 SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);      
348                 connected = 0;
349                 return -1;
350         }
351
352         ODBC_res = SQLPrepare(ODBC_stmt, sqlcmd, SQL_NTS);
353
354         if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
355         {
356                 if(option_verbose > 3)
357                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in PREPARE %d\n", ODBC_res);
358                 SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
359                 SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
360                 return -1;
361         }
362
363         ODBC_res = SQLExecute(ODBC_stmt);
364
365         if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
366         {
367                 if(option_verbose > 3)
368                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in Query %d\n", ODBC_res);
369                 SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
370                 SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt);
371                 connected = 0;
372                 return -1;
373         }
374         else
375         {
376                 if(option_verbose > 3)
377                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query Successful!\n");
378                 connected = 1;
379         }
380         return 0;
381 }
382
383 static int odbc_init(void)
384 {
385         long int ODBC_err;
386         short int ODBC_mlen;
387         char ODBC_msg[200], ODBC_stat[10];
388
389         if ( ODBC_env == SQL_NULL_HANDLE || connected == 0)
390         {
391                 ODBC_res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ODBC_env);
392
393                 if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
394                 {
395                         if(option_verbose > 3)
396                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error AllocHandle\n");
397                         connected = 0;
398                         return -1;
399                 }
400
401                 ODBC_res = SQLSetEnvAttr(ODBC_env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
402
403                 if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
404                 {
405                         if(option_verbose > 3)
406                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SetEnv\n");
407                         SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
408                         connected = 0;
409                         return -1;
410                 }
411
412                 ODBC_res = SQLAllocHandle(SQL_HANDLE_DBC, ODBC_env, &ODBC_con);
413
414                 if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
415                 {
416                         if(option_verbose > 3)
417                                 ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error AllocHDB %d\n", ODBC_res);
418                         SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
419                         connected = 0;
420                         return -1;
421                 }
422
423                 SQLSetConnectAttr(ODBC_con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)10, 0);    
424         }
425
426         ODBC_res = SQLConnect(ODBC_con, (SQLCHAR*)dsn, SQL_NTS, (SQLCHAR*)username, SQL_NTS, (SQLCHAR*)password, SQL_NTS);
427
428         if((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO))
429         {
430                 if(option_verbose > 3)
431                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SQLConnect %d\n", ODBC_res);
432                 SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen);
433                 SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env);
434                 connected = 0;
435                 return -1;
436         }
437         else
438         {
439                 if(option_verbose > 3)
440                         ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Connected to %s\n", dsn);
441                 connected = 1;
442         }
443
444         return 0;
445 }
446
447 static size_t escape_string(char *to, const char *from, size_t length)
448 {
449         const char *source = from;
450         char *target = to;
451         unsigned int remaining = length;
452         while (remaining > 0) {
453                 switch (*source) {
454                         case '\\':
455                                 *target = '\\';
456                                 target++;
457                                 *target = '\\';
458                                 break;
459                         case '\'':
460                                 *target = '\\';
461                                 target++;
462                                 *target = '\'';
463                                 break;
464                          case '"':
465                                 *target = '\\';
466                                 target++;
467                                 *target = '"';
468                                 break;
469                         default:
470                                 *target = *source;
471                         }
472                 source++;
473                 target++;
474                 remaining--;
475         }
476
477         *target = '\0';
478  
479         return target - to;
480 }
481
482 int load_module(void)
483 {
484         return odbc_load_module();
485 }
486
487 int unload_module(void)
488 {
489         return odbc_unload_module();
490 }
491
492 int reload(void)
493 {
494         odbc_unload_module();
495         return odbc_load_module();
496 }
497
498 int usecount(void)
499 {
500         return connected;
501 }
502
503 char *key()
504 {
505         return ASTERISK_GPL_KEY;
506 }