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