Merge config updates (bug #3406)
[asterisk/asterisk.git] / cdr / cdr_tds.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * FreeTDS CDR logger
5  *
6  * This program is free software, distributed under the terms of
7  * the GNU General Public License.
8  *
9  *
10  * Table Structure for `cdr`
11  *
12  * Created on: 05/20/2004 16:16
13  * Last changed on: 07/27/2004 20:01
14
15 CREATE TABLE [dbo].[cdr] (
16         [accountcode] [varchar] (20) NULL ,
17         [src] [varchar] (80) NULL ,
18         [dst] [varchar] (80) NULL ,
19         [dcontext] [varchar] (80) NULL ,
20         [clid] [varchar] (80) NULL ,
21         [channel] [varchar] (80) NULL ,
22         [dstchannel] [varchar] (80) NULL ,
23         [lastapp] [varchar] (80) NULL ,
24         [lastdata] [varchar] (80) NULL ,
25         [start] [datetime] NULL ,
26         [answer] [datetime] NULL ,
27         [end] [datetime] NULL ,
28         [duration] [int] NULL ,
29         [billsec] [int] NULL ,
30         [disposition] [varchar] (20) NULL ,
31         [amaflags] [varchar] (16) NULL ,
32         [uniqueid] [varchar] (32) NULL
33 ) ON [PRIMARY]
34
35 */
36
37 #include <sys/types.h>
38 #include <asterisk/config.h>
39 #include <asterisk/options.h>
40 #include <asterisk/channel.h>
41 #include <asterisk/cdr.h>
42 #include <asterisk/module.h>
43 #include <asterisk/logger.h>
44 #include "../asterisk.h"
45
46 #include <stdio.h>
47 #include <string.h>
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <time.h>
51 #include <math.h>
52
53 #include <tds.h>
54 #include <tdsconvert.h>
55 #include <ctype.h>
56
57 #if !defined(TDS_INT_EXIT) 
58 #define TDS_PRE_0_62
59 #warning "You have older TDS, you should upgrade!"
60 #endif
61
62 #define DATE_FORMAT "%Y/%m/%d %T"
63
64 static char *desc = "MSSQL CDR Backend";
65 static char *name = "mssql";
66 static char *config = "cdr_tds.conf";
67
68 AST_MUTEX_DEFINE_STATIC(tds_lock);
69
70 static TDSSOCKET *tds;
71 static TDSLOGIN *login;
72 static TDSCONTEXT *context;
73
74 static char *stristr(const char*, const char*);
75 static char *anti_injection(const char *, int);
76 static void get_date(char *, struct timeval);
77
78 static int tds_log(struct ast_cdr *cdr)
79 {
80         char sqlcmd[2048], start[80], answer[80], end[80];
81         char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid;
82         int res = 0;
83 #ifdef TDS_PRE_0_62
84         TDS_INT result_type;
85 #endif
86
87         ast_mutex_lock(&tds_lock);
88
89         memset(sqlcmd, 0, 2048);
90
91         accountcode = anti_injection(cdr->accountcode, 20);
92         src = anti_injection(cdr->src, 80);
93         dst = anti_injection(cdr->dst, 80);
94         dcontext = anti_injection(cdr->dcontext, 80);
95         clid = anti_injection(cdr->clid, 80);
96         channel = anti_injection(cdr->channel, 80);
97         dstchannel = anti_injection(cdr->dstchannel, 80);
98         lastapp = anti_injection(cdr->lastapp, 80);
99         lastdata = anti_injection(cdr->lastdata, 80);
100         uniqueid = anti_injection(cdr->uniqueid, 32);
101
102         get_date(start, cdr->start);
103         get_date(answer, cdr->answer);
104         get_date(end, cdr->end);
105
106         sprintf(
107                 sqlcmd,
108                 "INSERT INTO cdr "
109                 "("
110                         "accountcode, "
111                         "src, "
112                         "dst, "
113                         "dcontext, "
114                         "clid, "
115                         "channel, "
116                         "dstchannel, "
117                         "lastapp, "
118                         "lastdata, "
119                         "start, "
120                         "answer, "
121                         "[end], "
122                         "duration, "
123                         "billsec, "
124                         "disposition, "
125                         "amaflags, "
126                         "uniqueid"
127                 ") "
128                 "VALUES "
129                 "("
130                         "'%s', "        /* accountcode */
131                         "'%s', "        /* src */
132                         "'%s', "        /* dst */
133                         "'%s', "        /* dcontext */
134                         "'%s', "        /* clid */
135                         "'%s', "        /* channel */
136                         "'%s', "        /* dstchannel */
137                         "'%s', "        /* lastapp */
138                         "'%s', "        /* lastdata */
139                         "%s, "          /* start */
140                         "%s, "          /* answer */
141                         "%s, "          /* end */
142                         "%i, "          /* duration */
143                         "%i, "          /* billsec */
144                         "'%s', "        /* disposition */
145                         "'%s', "        /* amaflags */
146                         "'%s'"          /* uniqueid */
147                 ")",
148                 accountcode,
149                 src,
150                 dst,
151                 dcontext,
152                 clid,
153                 channel,
154                 dstchannel,
155                 lastapp,
156                 lastdata,
157                 start,
158                 answer,
159                 end,
160                 cdr->duration,
161                 cdr->billsec,
162                 ast_cdr_disp2str(cdr->disposition),
163                 ast_cdr_flags2str(cdr->amaflags),
164                 uniqueid
165         );
166
167 #ifdef TDS_PRE_0_62
168         if ((tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
169 #else
170         if ((tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
171 #endif
172         {
173                 ast_log(LOG_ERROR, "Failed to insert record into database.\n");
174
175                 res = -1;
176         }
177
178         free(accountcode);
179         free(src);
180         free(dst);
181         free(dcontext);
182         free(clid);
183         free(channel);
184         free(dstchannel);
185         free(lastapp);
186         free(lastdata);
187         free(uniqueid);
188
189         ast_mutex_unlock(&tds_lock);
190
191         return res;
192 }
193
194 /* Return the offset of one string within another.
195    Copyright (C) 1994, 1996, 1997, 2000, 2001 Free Software Foundation, Inc.
196    This file is part of the GNU C Library.
197
198    The GNU C Library is free software; you can redistribute it and/or
199    modify it under the terms of the GNU Lesser General Public
200    License as published by the Free Software Foundation; either
201    version 2.1 of the License, or (at your option) any later version.
202
203    The GNU C Library is distributed in the hope that it will be useful,
204    but WITHOUT ANY WARRANTY; without even the implied warranty of
205    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
206    Lesser General Public License for more details.
207
208    You should have received a copy of the GNU Lesser General Public
209    License along with the GNU C Library; if not, write to the Free
210    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
211    02111-1307 USA.  */
212
213 /*
214  * My personal strstr() implementation that beats most other algorithms.
215  * Until someone tells me otherwise, I assume that this is the
216  * fastest implementation of strstr() in C.
217  * I deliberately chose not to comment it.  You should have at least
218  * as much fun trying to understand it, as I had to write it :-).
219  *
220  * Stephen R. van den Berg, berg@pool.informatik.rwth-aachen.de */
221
222 static char *
223 stristr (phaystack, pneedle)
224      const char *phaystack;
225      const char *pneedle;
226 {
227   typedef unsigned chartype;
228
229   const unsigned char *haystack, *needle;
230   chartype b;
231   const unsigned char *rneedle;
232
233   haystack = (const unsigned char *) phaystack;
234
235   if ((b = toupper(*(needle = (const unsigned char *) pneedle))))
236     {
237       chartype c;
238       haystack--;               /* possible ANSI violation */
239
240       {
241         chartype a;
242         do
243           if (!(a = toupper(*++haystack)))
244             goto ret0;
245         while (a != b);
246       }
247
248       if (!(c = toupper(*++needle)))
249         goto foundneedle;
250       ++needle;
251       goto jin;
252
253       for (;;)
254         {
255           {
256             chartype a;
257             if (0)
258             jin:{
259                 if ((a = toupper(*++haystack)) == c)
260                   goto crest;
261               }
262             else
263               a = toupper(*++haystack);
264             do
265               {
266                 for (; a != b; a = toupper(*++haystack))
267                   {
268                     if (!a)
269                       goto ret0;
270                     if ((a = toupper(*++haystack)) == b)
271                       break;
272                     if (!a)
273                       goto ret0;
274                   }
275               }
276             while ((a = toupper(*++haystack)) != c);
277           }
278         crest:
279           {
280             chartype a;
281             {
282               const unsigned char *rhaystack;
283               if (toupper(*(rhaystack = haystack-- + 1)) == (a = toupper(*(rneedle = needle))))
284                 do
285                   {
286                     if (!a)
287                       goto foundneedle;
288                     if (toupper(*++rhaystack) != (a = toupper(*++needle)))
289                       break;
290                     if (!a)
291                       goto foundneedle;
292                   }
293                 while (toupper(*++rhaystack) == (a = toupper(*++needle)));
294               needle = rneedle; /* took the register-poor aproach */
295             }
296             if (!a)
297               break;
298           }
299         }
300     }
301 foundneedle:
302   return (char *) haystack;
303 ret0:
304   return 0;
305 }
306
307 static char *anti_injection(const char *str, int len)
308 {
309         /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */
310
311         char *buf;
312         char *buf_ptr, *srh_ptr;
313         char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
314         int idx;
315
316         if ((buf = malloc(len + 1)) == NULL)
317         {
318                 ast_log(LOG_ERROR, "cdr_tds:  Out of memory error\n");
319                 return NULL;
320         }
321         memset(buf, 0, len);
322
323         buf_ptr = buf;
324
325         /* Escape single quotes */
326         for (; *str && strlen(buf) < len; str++)
327         {
328                 if (*str == '\'')
329                         *buf_ptr++ = '\'';
330                 *buf_ptr++ = *str;
331         }
332         *buf_ptr = '\0';
333
334         /* Erase known bad input */
335         for (idx=0; *known_bad[idx]; idx++)
336         {
337                 while((srh_ptr = stristr(buf, known_bad[idx]))) /* fix me! */
338                 {
339                         memmove(srh_ptr, srh_ptr+strlen(known_bad[idx]), strlen(srh_ptr+strlen(known_bad[idx]))+1);
340                 }
341         }
342
343         return buf;
344 }
345
346 static void get_date(char *dateField, struct timeval tv)
347 {
348         struct tm tm;
349         time_t t;
350         char buf[80];
351
352         /* To make sure we have date variable if not insert null to SQL */
353         if (tv.tv_sec && tv.tv_usec)
354         {
355                 t = tv.tv_sec;
356                 localtime_r(&t, &tm);
357                 strftime(buf, 80, DATE_FORMAT, &tm);
358                 sprintf(dateField, "'%s'", buf);
359         }
360         else
361         {
362                 strcpy(dateField, "null");
363         }
364 }
365
366 char *description(void)
367 {
368         return desc;
369 }
370
371 int unload_module(void)
372 {
373         tds_free_socket(tds);
374         tds_free_login(login);
375         tds_free_context(context);
376
377         ast_cdr_unregister(name);
378
379         return 0;
380 }
381
382 int load_module(void)
383 {
384         TDSCONNECTINFO *connection;
385         int res = 0;
386         struct ast_config *cfg;
387         struct ast_variable *var;
388         char query[1024], *ptr = NULL;
389         char *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *charset = NULL, *language = NULL;
390 #ifdef TDS_PRE_0_62
391         TDS_INT result_type;
392 #endif
393
394         cfg = ast_config_load(config);
395         if (!cfg)
396         {
397                 ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDR's: %s\n", config);
398                 return 0;
399         }
400
401         var = ast_variable_browse(cfg, "global");
402         if (!var) /* nothing configured */
403                 return 0;
404
405         ptr = ast_variable_retrieve(cfg, "global", "hostname");
406         if (ptr)
407         {
408                 hostname = strdupa(ptr);
409         }
410         else
411         {
412                 ast_log(LOG_ERROR,"Database server hostname not specified.\n");
413         }
414
415         ptr = ast_variable_retrieve(cfg, "global", "dbname");
416         if (ptr)
417         {
418                 dbname = strdupa(ptr);
419         }
420         else
421         {
422                 ast_log(LOG_ERROR,"Database dbname not specified.\n");
423         }
424
425         ptr = ast_variable_retrieve(cfg, "global", "user");
426         if (ptr)
427         {
428                 dbuser = strdupa(ptr);
429         }
430         else
431         {
432                 ast_log(LOG_ERROR,"Database dbuser not specified.\n");
433         }
434
435         ptr = ast_variable_retrieve(cfg, "global", "password");
436         if (ptr)
437         {
438                 password = strdupa(ptr);
439         }
440         else
441         {
442                 ast_log(LOG_ERROR,"Database password not specified.\n");
443         }
444
445         ptr = ast_variable_retrieve(cfg, "global", "charset");
446         if (ptr)
447         {
448                 charset = strdupa(ptr);
449         }
450         else
451         {
452                 charset = strdupa("iso_1");
453         }
454
455         ptr = ast_variable_retrieve(cfg, "global", "language");
456         if (ptr)
457         {
458                 language = strdupa(ptr);
459         }
460         else
461         {
462                 language = strdupa("us_english");
463         }
464
465         ast_config_destroy(cfg);
466
467         /* Connect to M$SQL Server */
468         if (!(login = tds_alloc_login()))
469         {
470                 ast_log(LOG_ERROR, "tds_alloc_login() failed.\n");
471                 res = -1;
472         }
473         else
474         {
475                 tds_set_server(login, hostname);
476                 tds_set_user(login, dbuser);
477                 tds_set_passwd(login, password);
478                 tds_set_app(login, "TSQL");
479                 tds_set_library(login, "TDS-Library");
480 #ifndef TDS_PRE_0_62
481                 tds_set_client_charset(login, charset);
482 #endif
483                 tds_set_language(login, language);
484                 tds_set_packet(login, 512);
485                 tds_set_version(login, 7, 0);
486
487                 context = tds_alloc_context();
488                 tds = tds_alloc_socket(context, 512);
489
490                 tds_set_parent(tds, NULL);
491                 connection = tds_read_config_info(NULL, login, context->locale);
492                 if (!connection || tds_connect(tds, connection) == TDS_FAIL)
493                 {
494                         ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n");
495                         res = -1;
496                 }
497                 tds_free_connect(connection);
498
499                 if (!res)
500                 {
501                         memset(query, 0, sizeof(query));
502                         sprintf(query, "USE %s", dbname);
503 #ifdef TDS_PRE_0_62
504                         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
505 #else
506                         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
507 #endif
508                         {
509                                 ast_log(LOG_ERROR, "Could not change database (%s)\n", dbname);
510                                 res = -1;
511                         }
512                         else
513                         {
514                                 /* Register MSSQL CDR handler */
515                                 res = ast_cdr_register(name, desc, tds_log);
516                                 if (res)
517                                 {
518                                         ast_log(LOG_ERROR, "Unable to register MSSQL CDR handling\n");
519                                 }
520                         }
521                 }
522         }
523         return res;
524 }
525
526 int reload(void)
527 {
528         return 0;
529 }
530
531 int usecount(void)
532 {
533         return 0;
534 }
535
536 char *key()
537 {
538         return ASTERISK_GPL_KEY;
539 }