Ad MSSQL CDR support (bug #1859)
[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/19/2004 13: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 #define DATE_FORMAT "%Y/%m/%d %T"
58
59 static char *desc = "MSSQL CDR Backend";
60 static char *name = "mssql";
61 static char *config = "cdr_tds.conf";
62
63 AST_MUTEX_DEFINE_STATIC(tds_lock);
64
65 static TDSSOCKET *tds;
66 static TDSLOGIN *login;
67 static TDSCONTEXT *context;
68
69 char *stristr(const char*, const char*);
70 char *anti_injection(const char *, int);
71
72 static int tds_log(struct ast_cdr *cdr)
73 {
74         struct tm tm;
75         time_t t;
76         char sqlcmd[2048], start[80], answer[80], end[80];
77         char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid;
78         int res = 0;
79
80         ast_mutex_lock(&tds_lock);
81
82         memset(sqlcmd, 0, 2048);
83
84         accountcode = anti_injection(cdr->accountcode, 20);
85         src = anti_injection(cdr->src, 80);
86         dst = anti_injection(cdr->dst, 80);
87         dcontext = anti_injection(cdr->dcontext, 80);
88         clid = anti_injection(cdr->clid, 80);
89         channel = anti_injection(cdr->channel, 80);
90         dstchannel = anti_injection(cdr->dstchannel, 80);
91         lastapp = anti_injection(cdr->lastapp, 80);
92         lastdata = anti_injection(cdr->lastdata, 80);
93         uniqueid = anti_injection(cdr->uniqueid, 32);
94
95         t = cdr->start.tv_sec;
96         localtime_r(&t, &tm);
97         strftime(start, 80, DATE_FORMAT, &tm);
98         t = cdr->answer.tv_sec;
99         localtime_r(&t, &tm);
100         strftime(answer, 80, DATE_FORMAT, &tm);
101         t = cdr->end.tv_sec;
102         localtime_r(&t, &tm);
103         strftime(end, 80, DATE_FORMAT, &tm);
104
105         sprintf(sqlcmd, "INSERT INTO cdr (accountcode, src, dst, dcontext, clid, channel, dstchannel, lastapp, lastdata, start, answer, [end], duration, billsec, disposition, amaflags, uniqueid) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, %i, '%s', '%s', '%s')", accountcode, src, dst, dcontext, clid, channel, dstchannel, lastapp, lastdata, start, answer, end, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid);
106
107         if ((tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
108         {
109                 ast_log(LOG_ERROR, "Failed to insert record into database.\n");
110
111                 res = -1;
112         }
113
114         free(accountcode);
115         free(src);
116         free(dst);
117         free(dcontext);
118         free(clid);
119         free(channel);
120         free(dstchannel);
121         free(lastapp);
122         free(lastdata);
123         free(uniqueid);
124
125         ast_mutex_unlock(&tds_lock);
126
127         return res;
128 }
129
130 /* Return the offset of one string within another.
131    Copyright (C) 1994, 1996, 1997, 2000, 2001 Free Software Foundation, Inc.
132    This file is part of the GNU C Library.
133
134    The GNU C Library is free software; you can redistribute it and/or
135    modify it under the terms of the GNU Lesser General Public
136    License as published by the Free Software Foundation; either
137    version 2.1 of the License, or (at your option) any later version.
138
139    The GNU C Library is distributed in the hope that it will be useful,
140    but WITHOUT ANY WARRANTY; without even the implied warranty of
141    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
142    Lesser General Public License for more details.
143
144    You should have received a copy of the GNU Lesser General Public
145    License along with the GNU C Library; if not, write to the Free
146    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
147    02111-1307 USA.  */
148
149 /*
150  * My personal strstr() implementation that beats most other algorithms.
151  * Until someone tells me otherwise, I assume that this is the
152  * fastest implementation of strstr() in C.
153  * I deliberately chose not to comment it.  You should have at least
154  * as much fun trying to understand it, as I had to write it :-).
155  *
156  * Stephen R. van den Berg, berg@pool.informatik.rwth-aachen.de */
157
158 char *
159 stristr (phaystack, pneedle)
160      const char *phaystack;
161      const char *pneedle;
162 {
163   typedef unsigned chartype;
164
165   const unsigned char *haystack, *needle;
166   chartype b;
167   const unsigned char *rneedle;
168
169   haystack = (const unsigned char *) phaystack;
170
171   if ((b = toupper(*(needle = (const unsigned char *) pneedle))))
172     {
173       chartype c;
174       haystack--;               /* possible ANSI violation */
175
176       {
177         chartype a;
178         do
179           if (!(a = toupper(*++haystack)))
180             goto ret0;
181         while (a != b);
182       }
183
184       if (!(c = toupper(*++needle)))
185         goto foundneedle;
186       ++needle;
187       goto jin;
188
189       for (;;)
190         {
191           {
192             chartype a;
193             if (0)
194             jin:{
195                 if ((a = toupper(*++haystack)) == c)
196                   goto crest;
197               }
198             else
199               a = toupper(*++haystack);
200             do
201               {
202                 for (; a != b; a = toupper(*++haystack))
203                   {
204                     if (!a)
205                       goto ret0;
206                     if ((a = toupper(*++haystack)) == b)
207                       break;
208                     if (!a)
209                       goto ret0;
210                   }
211               }
212             while ((a = toupper(*++haystack)) != c);
213           }
214         crest:
215           {
216             chartype a;
217             {
218               const unsigned char *rhaystack;
219               if (toupper(*(rhaystack = haystack-- + 1)) == (a = toupper(*(rneedle = needle))))
220                 do
221                   {
222                     if (!a)
223                       goto foundneedle;
224                     if (toupper(*++rhaystack) != (a = toupper(*++needle)))
225                       break;
226                     if (!a)
227                       goto foundneedle;
228                   }
229                 while (toupper(*++rhaystack) == (a = toupper(*++needle)));
230               needle = rneedle; /* took the register-poor aproach */
231             }
232             if (!a)
233               break;
234           }
235         }
236     }
237 foundneedle:
238   return (char *) haystack;
239 ret0:
240   return 0;
241 }
242
243 char *anti_injection(const char *str, int len)
244 {
245         /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */
246
247         char *buf;
248         char *buf_ptr, *srh_ptr;
249         char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
250         int idx;
251
252         if ((buf = malloc(len + 1)) == NULL)
253         {
254                 ast_log(LOG_ERROR, "cdr_tds:  Out of memory error\n");
255                 return NULL;
256         }
257         memset(buf, 0, len);
258
259         buf_ptr = buf;
260
261         /* Escape single quotes */
262         for (; *str && strlen(buf) < len; str++)
263         {
264                 if (*str == '\'')
265                         *buf_ptr++ = '\'';
266                 *buf_ptr++ = *str;
267         }
268         *buf_ptr = '\0';
269
270         /* Erase known bad input */
271         for (idx=0; *known_bad[idx]; idx++)
272         {
273                 while(srh_ptr = stristr(buf, known_bad[idx])) /* fix me! */
274                 {
275                         memmove(srh_ptr, srh_ptr+strlen(known_bad[idx]), strlen(srh_ptr+strlen(known_bad[idx]))+1);
276                 }
277         }
278
279         return buf;
280 }
281
282 char *description(void)
283 {
284         return desc;
285 }
286
287 int unload_module(void)
288 {
289         tds_free_socket(tds);
290         tds_free_login(login);
291         tds_free_context(context);
292
293         ast_cdr_unregister(name);
294
295         return 0;
296 }
297
298 int load_module(void)
299 {
300         TDSCONNECTINFO *connection;
301         int res = 0;
302         struct ast_config *cfg;
303         struct ast_variable *var;
304         char query[1024], *ptr = NULL;
305         char *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *charset = NULL, *language = NULL;
306
307         cfg = ast_load(config);
308         if (!cfg)
309         {
310                 ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDR's: %s\n", config);
311                 return 0;
312         }
313
314         var = ast_variable_browse(cfg, "global");
315         if (!var) /* nothing configured */
316                 return 0;
317
318         ptr = ast_variable_retrieve(cfg, "global", "hostname");
319         if (ptr)
320         {
321                 hostname = strdupa(ptr);
322         }
323         else
324         {
325                 ast_log(LOG_ERROR,"Database server hostname not specified.\n");
326         }
327
328         ptr = ast_variable_retrieve(cfg, "global", "dbname");
329         if (ptr)
330         {
331                 dbname = strdupa(ptr);
332         }
333         else
334         {
335                 ast_log(LOG_ERROR,"Database dbname not specified.\n");
336         }
337
338         ptr = ast_variable_retrieve(cfg, "global", "user");
339         if (ptr)
340         {
341                 dbuser = strdupa(ptr);
342         }
343         else
344         {
345                 ast_log(LOG_ERROR,"Database dbuser not specified.\n");
346         }
347
348         ptr = ast_variable_retrieve(cfg, "global", "password");
349         if (ptr)
350         {
351                 password = strdupa(ptr);
352         }
353         else
354         {
355                 ast_log(LOG_ERROR,"Database password not specified.\n");
356         }
357
358         ptr = ast_variable_retrieve(cfg, "global", "charset");
359         if (ptr)
360         {
361                 charset = strdupa(ptr);
362         }
363         else
364         {
365                 charset = strdupa("iso_1");
366         }
367
368         ptr = ast_variable_retrieve(cfg, "global", "language");
369         if (ptr)
370         {
371                 language = strdupa(ptr);
372         }
373         else
374         {
375                 language = strdupa("us_english");
376         }
377
378         ast_destroy(cfg);
379
380         /* Connect to M$SQL Server */
381         if (!(login = tds_alloc_login()))
382         {
383                 ast_log(LOG_ERROR, "tds_alloc_login() failed.\n");
384                 res = -1;
385         }
386         else
387         {
388                 tds_set_server(login, hostname);
389                 tds_set_user(login, dbuser);
390                 tds_set_passwd(login, password);
391                 tds_set_app(login, "TSQL");
392                 tds_set_library(login, "TDS-Library");
393                 tds_set_client_charset(login, charset);
394                 tds_set_language(login, language);
395                 tds_set_packet(login, 512);
396                 tds_set_version(login, 7, 0);
397
398                 context = tds_alloc_context();
399                 tds = tds_alloc_socket(context, 512);
400
401                 tds_set_parent(tds, NULL);
402                 connection = tds_read_config_info(NULL, login, context->locale);
403                 if (!connection || tds_connect(tds, connection) == TDS_FAIL)
404                 {
405                         ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n");
406                         res = -1;
407                 }
408                 tds_free_connect(connection);
409
410                 if (!res)
411                 {
412                         memset(query, 0, sizeof(query));
413                         sprintf(query, "USE %s", dbname);
414                         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
415                         {
416                                 ast_log(LOG_ERROR, "Could not change database (%s)\n", dbname);
417                                 res = -1;
418                         }
419                         else
420                         {
421                                 /* Register MSSQL CDR handler */
422                                 res = ast_cdr_register(name, desc, tds_log);
423                                 if (res)
424                                 {
425                                         ast_log(LOG_ERROR, "Unable to register MSSQL CDR handling\n");
426                                 }
427                         }
428                 }
429         }
430         return res;
431 }
432
433 int reload(void)
434 {
435         return 0;
436 }
437
438 int usecount(void)
439 {
440         return 0;
441 }
442
443 char *key()
444 {
445         return ASTERISK_GPL_KEY;
446 }