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