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