more doxygenification (issue #5513)
[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  */
25
26 /*! \verbatim
27  *
28  * Table Structure for `cdr`
29  *
30  * Created on: 05/20/2004 16:16
31  * Last changed on: 07/27/2004 20:01
32
33 CREATE TABLE [dbo].[cdr] (
34         [accountcode] [varchar] (20) NULL ,
35         [src] [varchar] (80) NULL ,
36         [dst] [varchar] (80) NULL ,
37         [dcontext] [varchar] (80) NULL ,
38         [clid] [varchar] (80) NULL ,
39         [channel] [varchar] (80) NULL ,
40         [dstchannel] [varchar] (80) NULL ,
41         [lastapp] [varchar] (80) NULL ,
42         [lastdata] [varchar] (80) NULL ,
43         [start] [datetime] NULL ,
44         [answer] [datetime] NULL ,
45         [end] [datetime] NULL ,
46         [duration] [int] NULL ,
47         [billsec] [int] NULL ,
48         [disposition] [varchar] (20) NULL ,
49         [amaflags] [varchar] (16) NULL ,
50         [uniqueid] [varchar] (32) NULL
51 ) ON [PRIMARY]
52
53 \endverbatim
54
55 */
56
57 #include <sys/types.h>
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <unistd.h>
62 #include <time.h>
63 #include <math.h>
64
65 #include <tds.h>
66 #include <tdsconvert.h>
67 #include <ctype.h>
68
69 #include "asterisk.h"
70
71 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
72
73 #include "asterisk/config.h"
74 #include "asterisk/options.h"
75 #include "asterisk/channel.h"
76 #include "asterisk/cdr.h"
77 #include "asterisk/module.h"
78 #include "asterisk/logger.h"
79
80 #if !defined(TDS_INT_EXIT) 
81 #define TDS_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 TDS_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 TDS_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         TDSCONNECTINFO *connection = NULL;
438         char query[128];
439
440         /* Connect to M$SQL Server */
441         if (!(login = tds_alloc_login()))
442         {
443                 ast_log(LOG_ERROR, "tds_alloc_login() failed.\n");
444                 return -1;
445         }
446         
447         tds_set_server(login, hostname);
448         tds_set_user(login, dbuser);
449         tds_set_passwd(login, password);
450         tds_set_app(login, "TSQL");
451         tds_set_library(login, "TDS-Library");
452 #ifndef TDS_PRE_0_62
453         tds_set_client_charset(login, charset);
454 #endif
455         tds_set_language(login, language);
456         tds_set_packet(login, 512);
457         tds_set_version(login, 7, 0);
458
459         if (!(context = tds_alloc_context()))
460         {
461                 ast_log(LOG_ERROR, "tds_alloc_context() failed.\n");
462                 goto connect_fail;
463         }
464
465         if (!(tds = tds_alloc_socket(context, 512))) {
466                 ast_log(LOG_ERROR, "tds_alloc_socket() failed.\n");
467                 goto connect_fail;
468         }
469
470         tds_set_parent(tds, NULL);
471         connection = tds_read_config_info(tds, login, context->locale);
472         if (!connection)
473         {
474                 ast_log(LOG_ERROR, "tds_read_config() failed.\n");
475                 goto connect_fail;
476         }
477
478         if (tds_connect(tds, connection) == TDS_FAIL)
479         {
480                 ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n");
481                 tds = NULL;     /* freed by tds_connect() on error */
482                 tds_free_connect(connection);
483                 connection = NULL;
484                 goto connect_fail;
485         }
486         tds_free_connect(connection);
487         connection = NULL;
488
489         sprintf(query, "USE %s", dbname);
490 #ifdef TDS_PRE_0_62
491         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
492 #else
493         if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
494 #endif
495         {
496                 ast_log(LOG_ERROR, "Could not change database (%s)\n", dbname);
497                 goto connect_fail;
498         }
499
500         connected = 1;
501         return 0;
502
503 connect_fail:
504         mssql_disconnect();
505         return -1;
506 }
507
508 static int tds_unload_module(void)
509 {
510         mssql_disconnect();
511
512         ast_cdr_unregister(name);
513
514         if (hostname) free(hostname);
515         if (dbname) free(dbname);
516         if (dbuser) free(dbuser);
517         if (password) free(password);
518         if (charset) free(charset);
519         if (language) free(language);
520
521         return 0;
522 }
523
524 static int tds_load_module(void)
525 {
526         int res = 0;
527         struct ast_config *cfg;
528         struct ast_variable *var;
529         char *ptr = NULL;
530 #ifdef TDS_PRE_0_62
531         TDS_INT result_type;
532 #endif
533
534         cfg = ast_config_load(config);
535         if (!cfg) {
536                 ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDR's: %s\n", config);
537                 return 0;
538         }
539
540         var = ast_variable_browse(cfg, "global");
541         if (!var) /* nothing configured */
542                 return 0;
543
544         ptr = ast_variable_retrieve(cfg, "global", "hostname");
545         if (ptr)
546                 hostname = strdup(ptr);
547         else
548                 ast_log(LOG_ERROR,"Database server hostname not specified.\n");
549
550         ptr = ast_variable_retrieve(cfg, "global", "dbname");
551         if (ptr)
552                 dbname = strdup(ptr);
553         else
554                 ast_log(LOG_ERROR,"Database dbname not specified.\n");
555
556         ptr = ast_variable_retrieve(cfg, "global", "user");
557         if (ptr)
558                 dbuser = strdup(ptr);
559         else
560                 ast_log(LOG_ERROR,"Database dbuser not specified.\n");
561
562         ptr = ast_variable_retrieve(cfg, "global", "password");
563         if (ptr)
564                 password = strdup(ptr);
565         else
566                 ast_log(LOG_ERROR,"Database password not specified.\n");
567
568         ptr = ast_variable_retrieve(cfg, "global", "charset");
569         if (ptr)
570                 charset = strdup(ptr);
571         else
572                 charset = strdup("iso_1");
573
574         ptr = ast_variable_retrieve(cfg, "global", "language");
575         if (ptr)
576                 language = strdup(ptr);
577         else
578                 language = strdup("us_english");
579
580         ast_config_destroy(cfg);
581
582         mssql_connect();
583
584         /* Register MSSQL CDR handler */
585         res = ast_cdr_register(name, desc, tds_log);
586         if (res)
587         {
588                 ast_log(LOG_ERROR, "Unable to register MSSQL CDR handling\n");
589         }
590
591         return res;
592 }
593
594 int reload(void)
595 {
596         tds_unload_module();
597         return tds_load_module();
598 }
599
600 int load_module(void)
601 {
602         return tds_load_module();
603 }
604
605 int unload_module(void)
606 {
607         return tds_unload_module();
608 }
609
610 int usecount(void)
611 {
612         /* Simplistic use count */
613         if (ast_mutex_trylock(&tds_lock)) {
614                 return 1;
615         } else {
616                 ast_mutex_unlock(&tds_lock);
617                 return 0;
618         }
619 }
620
621 char *key()
622 {
623         return ASTERISK_GPL_KEY;
624 }