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