Add a massive set of changes for converting to use the ast_debug() macro.
[asterisk/asterisk.git] / cdr / cdr_tds.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2004 - 2006, Digium, Inc.
5  *
6  * See http://www.asterisk.org for more information about
7  * the Asterisk project. Please do not directly contact
8  * any of the maintainers of this project for assistance;
9  * the project provides a web site, mailing lists and IRC
10  * channels for your use.
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License Version 2. See the LICENSE file
14  * at the top of the source tree.
15  */
16
17 /*! \file
18  *
19  * \brief FreeTDS CDR logger
20  *
21  * See also
22  * \arg \ref Config_cdr
23  * \arg http://www.freetds.org/
24  * \ingroup cdr_drivers
25  */
26
27 /*! \verbatim
28  *
29  * Table Structure for `cdr`
30  *
31  * Created on: 05/20/2004 16:16
32  * Last changed on: 07/27/2004 20:01
33
34 CREATE TABLE [dbo].[cdr] (
35         [accountcode] [varchar] (20) NULL ,
36         [src] [varchar] (80) NULL ,
37         [dst] [varchar] (80) NULL ,
38         [dcontext] [varchar] (80) NULL ,
39         [clid] [varchar] (80) NULL ,
40         [channel] [varchar] (80) NULL ,
41         [dstchannel] [varchar] (80) NULL ,
42         [lastapp] [varchar] (80) NULL ,
43         [lastdata] [varchar] (80) NULL ,
44         [start] [datetime] NULL ,
45         [answer] [datetime] NULL ,
46         [end] [datetime] NULL ,
47         [duration] [int] NULL ,
48         [billsec] [int] NULL ,
49         [disposition] [varchar] (20) NULL ,
50         [amaflags] [varchar] (16) NULL ,
51         [uniqueid] [varchar] (32) NULL
52 ) ON [PRIMARY]
53
54 \endverbatim
55
56 */
57
58 /*** MODULEINFO
59         <depend>freetds</depend>
60  ***/
61
62 #include "asterisk.h"
63
64 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
65
66 #include <sys/types.h>
67 #include <stdio.h>
68 #include <string.h>
69 #include <stdlib.h>
70 #include <unistd.h>
71 #include <time.h>
72 #include <math.h>
73
74 #include <tds.h>
75 #include <tdsconvert.h>
76 #include <ctype.h>
77
78 #include "asterisk/config.h"
79 #include "asterisk/options.h"
80 #include "asterisk/channel.h"
81 #include "asterisk/cdr.h"
82 #include "asterisk/module.h"
83 #include "asterisk/logger.h"
84
85 #ifdef FREETDS_PRE_0_62
86 #warning "You have older TDS, you should upgrade!"
87 #endif
88
89 #define DATE_FORMAT "%Y/%m/%d %T"
90
91 static char *name = "mssql";
92 static char *config = "cdr_tds.conf";
93
94 static char *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *charset = NULL, *language = NULL;
95 static char *table = NULL;
96
97 static int connected = 0;
98
99 AST_MUTEX_DEFINE_STATIC(tds_lock);
100
101 static TDSSOCKET *tds;
102 static TDSLOGIN *login;
103 static TDSCONTEXT *context;
104
105 static char *anti_injection(const char *, int);
106 static void get_date(char *, struct timeval);
107
108 static int mssql_connect(void);
109 static int mssql_disconnect(void);
110
111 static int tds_log(struct ast_cdr *cdr)
112 {
113         char sqlcmd[2048], start[80], answer[80], end[80];
114         char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid;
115         int res = 0;
116         int retried = 0;
117 #ifdef FREETDS_PRE_0_62
118         TDS_INT result_type;
119 #endif
120
121         ast_mutex_lock(&tds_lock);
122
123         memset(sqlcmd, 0, 2048);
124
125         accountcode = anti_injection(cdr->accountcode, 20);
126         src = anti_injection(cdr->src, 80);
127         dst = anti_injection(cdr->dst, 80);
128         dcontext = anti_injection(cdr->dcontext, 80);
129         clid = anti_injection(cdr->clid, 80);
130         channel = anti_injection(cdr->channel, 80);
131         dstchannel = anti_injection(cdr->dstchannel, 80);
132         lastapp = anti_injection(cdr->lastapp, 80);
133         lastdata = anti_injection(cdr->lastdata, 80);
134         uniqueid = anti_injection(cdr->uniqueid, 32);
135
136         get_date(start, cdr->start);
137         get_date(answer, cdr->answer);
138         get_date(end, cdr->end);
139
140         sprintf(
141                 sqlcmd,
142                 "INSERT INTO %s "
143                 "("
144                         "accountcode, "
145                         "src, "
146                         "dst, "
147                         "dcontext, "
148                         "clid, "
149                         "channel, "
150                         "dstchannel, "
151                         "lastapp, "
152                         "lastdata, "
153                         "start, "
154                         "answer, "
155                         "[end], "
156                         "duration, "
157                         "billsec, "
158                         "disposition, "
159                         "amaflags, "
160                         "uniqueid"
161                 ") "
162                 "VALUES "
163                 "("
164                         "'%s', "        /* accountcode */
165                         "'%s', "        /* src */
166                         "'%s', "        /* dst */
167                         "'%s', "        /* dcontext */
168                         "'%s', "        /* clid */
169                         "'%s', "        /* channel */
170                         "'%s', "        /* dstchannel */
171                         "'%s', "        /* lastapp */
172                         "'%s', "        /* lastdata */
173                         "%s, "          /* start */
174                         "%s, "          /* answer */
175                         "%s, "          /* end */
176                         "%ld, "         /* duration */
177                         "%ld, "         /* billsec */
178                         "'%s', "        /* disposition */
179                         "'%s', "        /* amaflags */
180                         "'%s'"          /* uniqueid */
181                 ")",
182                 table,
183                 accountcode,
184                 src,
185                 dst,
186                 dcontext,
187                 clid,
188                 channel,
189                 dstchannel,
190                 lastapp,
191                 lastdata,
192                 start,
193                 answer,
194                 end,
195                 cdr->duration,
196                 cdr->billsec,
197                 ast_cdr_disp2str(cdr->disposition),
198                 ast_cdr_flags2str(cdr->amaflags),
199                 uniqueid
200         );
201
202         do {
203                 if (!connected) {
204                         if (mssql_connect())
205                                 ast_log(LOG_ERROR, "Failed to reconnect to SQL database.\n");
206                         else
207                                 ast_log(LOG_WARNING, "Reconnected to SQL database.\n");
208
209                         retried = 1;    /* note that we have now tried */
210                 }
211
212 #ifdef FREETDS_PRE_0_62
213                 if (!connected || (tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
214 #else
215                 if (!connected || (tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
216 #endif
217                 {
218                         ast_log(LOG_ERROR, "Failed to insert Call Data Record into SQL database.\n");
219
220                         mssql_disconnect();     /* this is ok even if we are already disconnected */
221                 }
222         } while (!connected && !retried);
223
224         ast_free(accountcode);
225         ast_free(src);
226         ast_free(dst);
227         ast_free(dcontext);
228         ast_free(clid);
229         ast_free(channel);
230         ast_free(dstchannel);
231         ast_free(lastapp);
232         ast_free(lastdata);
233         ast_free(uniqueid);
234
235         ast_mutex_unlock(&tds_lock);
236
237         return res;
238 }
239
240 static char *anti_injection(const char *str, int len)
241 {
242         /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */
243
244         char *buf;
245         char *buf_ptr, *srh_ptr;
246         char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
247         int idx;
248
249         if ((buf = ast_malloc(len + 1)) == NULL)
250         {
251                 ast_log(LOG_ERROR, "cdr_tds:  Out of memory error\n");
252                 return NULL;
253         }
254         memset(buf, 0, len);
255
256         buf_ptr = buf;
257
258         /* Escape single quotes */
259         for (; *str && strlen(buf) < len; str++)
260         {
261                 if (*str == '\'')
262                         *buf_ptr++ = '\'';
263                 *buf_ptr++ = *str;
264         }
265         *buf_ptr = '\0';
266
267         /* Erase known bad input */
268         for (idx=0; *known_bad[idx]; idx++)
269         {
270                 while((srh_ptr = strcasestr(buf, known_bad[idx])))
271                 {
272                         memmove(srh_ptr, srh_ptr+strlen(known_bad[idx]), strlen(srh_ptr+strlen(known_bad[idx]))+1);
273                 }
274         }
275
276         return buf;
277 }
278
279 static void get_date(char *dateField, struct timeval tv)
280 {
281         struct tm tm;
282         time_t t;
283         char buf[80];
284
285         /* To make sure we have date variable if not insert null to SQL */
286         if (!ast_tvzero(tv))
287         {
288                 t = tv.tv_sec;
289                 localtime_r(&t, &tm);
290                 strftime(buf, 80, DATE_FORMAT, &tm);
291                 sprintf(dateField, "'%s'", buf);
292         }
293         else
294         {
295                 strcpy(dateField, "null");
296         }
297 }
298
299 static int mssql_disconnect(void)
300 {
301         if (tds) {
302                 tds_free_socket(tds);
303                 tds = NULL;
304         }
305
306         if (context) {
307                 tds_free_context(context);
308                 context = NULL;
309         }
310
311         if (login) {
312                 tds_free_login(login);
313                 login = NULL;
314         }
315
316         connected = 0;
317
318         return 0;
319 }
320
321 static int mssql_connect(void)
322 {
323 #if (defined(FREETDS_0_63) || defined(FREETDS_0_64))
324         TDSCONNECTION *connection = NULL;
325 #else
326         TDSCONNECTINFO *connection = NULL;
327 #endif
328         char query[128];
329
330         /* Connect to M$SQL Server */
331         if (!(login = tds_alloc_login()))
332         {
333                 ast_log(LOG_ERROR, "tds_alloc_login() failed.\n");
334                 return -1;
335         }
336         
337         tds_set_server(login, hostname);
338         tds_set_user(login, dbuser);
339         tds_set_passwd(login, password);
340         tds_set_app(login, "TSQL");
341         tds_set_library(login, "TDS-Library");
342 #ifndef FREETDS_PRE_0_62
343         tds_set_client_charset(login, charset);
344 #endif
345         tds_set_language(login, language);
346         tds_set_packet(login, 512);
347         tds_set_version(login, 7, 0);
348
349 #ifdef FREETDS_0_64
350         if (!(context = tds_alloc_context(NULL)))
351 #else
352         if (!(context = tds_alloc_context()))
353 #endif
354         {
355                 ast_log(LOG_ERROR, "tds_alloc_context() failed.\n");
356                 goto connect_fail;
357         }
358
359         if (!(tds = tds_alloc_socket(context, 512))) {
360                 ast_log(LOG_ERROR, "tds_alloc_socket() failed.\n");
361                 goto connect_fail;
362         }
363
364         tds_set_parent(tds, NULL);
365         connection = tds_read_config_info(tds, login, context->locale);
366         if (!connection)
367         {
368                 ast_log(LOG_ERROR, "tds_read_config() failed.\n");
369                 goto connect_fail;
370         }
371
372         if (tds_connect(tds, connection) == TDS_FAIL)
373         {
374                 ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n");
375                 tds = NULL;     /* freed by tds_connect() on error */
376 #if (defined(FREETDS_0_63) || defined(FREETDS_0_64))
377                 tds_free_connection(connection);
378 #else
379                 tds_free_connect(connection);
380 #endif
381                 connection = NULL;
382                 goto connect_fail;
383         }
384 #if (defined(FREETDS_0_63) || defined(FREETDS_0_64))
385         tds_free_connection(connection);
386 #else
387         tds_free_connect(connection);
388 #endif
389         connection = NULL;
390
391         sprintf(query, "USE %s", dbname);
392 #ifdef FREETDS_PRE_0_62
393         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
394 #else
395         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
396 #endif
397         {
398                 ast_log(LOG_ERROR, "Could not change database (%s)\n", dbname);
399                 goto connect_fail;
400         }
401
402         connected = 1;
403         return 0;
404
405 connect_fail:
406         mssql_disconnect();
407         return -1;
408 }
409
410 static int tds_unload_module(void)
411 {
412         mssql_disconnect();
413
414         ast_cdr_unregister(name);
415
416         if (hostname) ast_free(hostname);
417         if (dbname) ast_free(dbname);
418         if (dbuser) ast_free(dbuser);
419         if (password) ast_free(password);
420         if (charset) ast_free(charset);
421         if (language) ast_free(language);
422         if (table) ast_free(table);
423
424         return 0;
425 }
426
427 static int tds_load_module(void)
428 {
429         int res = 0;
430         struct ast_config *cfg;
431         struct ast_variable *var;
432         const char *ptr = NULL;
433 #ifdef FREETDS_PRE_0_62
434         TDS_INT result_type;
435 #endif
436
437         cfg = ast_config_load(config);
438         if (!cfg) {
439                 ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDR's: %s\n", config);
440                 return 0;
441         }
442
443         var = ast_variable_browse(cfg, "global");
444         if (!var) /* nothing configured */
445                 return 0;
446
447         ptr = ast_variable_retrieve(cfg, "global", "hostname");
448         if (ptr)
449                 hostname = strdup(ptr);
450         else
451                 ast_log(LOG_ERROR,"Database server hostname not specified.\n");
452
453         ptr = ast_variable_retrieve(cfg, "global", "dbname");
454         if (ptr)
455                 dbname = strdup(ptr);
456         else
457                 ast_log(LOG_ERROR,"Database dbname not specified.\n");
458
459         ptr = ast_variable_retrieve(cfg, "global", "user");
460         if (ptr)
461                 dbuser = strdup(ptr);
462         else
463                 ast_log(LOG_ERROR,"Database dbuser not specified.\n");
464
465         ptr = ast_variable_retrieve(cfg, "global", "password");
466         if (ptr)
467                 password = strdup(ptr);
468         else
469                 ast_log(LOG_ERROR,"Database password not specified.\n");
470
471         ptr = ast_variable_retrieve(cfg, "global", "charset");
472         if (ptr)
473                 charset = strdup(ptr);
474         else
475                 charset = strdup("iso_1");
476
477         ptr = ast_variable_retrieve(cfg, "global", "language");
478         if (ptr)
479                 language = strdup(ptr);
480         else
481                 language = strdup("us_english");
482
483         ptr = ast_variable_retrieve(cfg,"global","table");
484         if (ptr == NULL) {
485                 ast_debug(1,"cdr_tds: table not specified.  Assuming cdr\n");
486                 ptr = "cdr";
487         }
488         table = strdup(ptr);
489
490         ast_config_destroy(cfg);
491
492         mssql_connect();
493
494         /* Register MSSQL CDR handler */
495         res = ast_cdr_register(name, ast_module_info->description, tds_log);
496         if (res)
497         {
498                 ast_log(LOG_ERROR, "Unable to register MSSQL CDR handling\n");
499         }
500
501         return res;
502 }
503
504 static int reload(void)
505 {
506         tds_unload_module();
507         return tds_load_module();
508 }
509
510 static int load_module(void)
511 {
512         if(!tds_load_module())
513                 return AST_MODULE_LOAD_DECLINE;
514         else 
515                 return AST_MODULE_LOAD_SUCCESS;
516 }
517
518 static int unload_module(void)
519 {
520         return tds_unload_module();
521 }
522
523 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MSSQL CDR Backend",
524                 .load = load_module,
525                 .unload = unload_module,
526                 .reload = reload,
527                );