Asterisk - The Open Source Telephony Project  21.4.1
cel_tds.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008, 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 CEL logger
20  * http://www.freetds.org/
21  * \ingroup cel_drivers
22  */
23 
24 /*! \verbatim
25  *
26  * Table Structure for `cel`
27  *
28 
29 CREATE TABLE [dbo].[cel] (
30  [accountcode] [varchar] (20) NULL ,
31  [cidname] [varchar] (80) NULL ,
32  [cidnum] [varchar] (80) NULL ,
33  [cidani] [varchar] (80) NULL ,
34  [cidrdnis] [varchar] (80) NULL ,
35  [ciddnid] [varchar] (80) NULL ,
36  [exten] [varchar] (80) NULL ,
37  [context] [varchar] (80) NULL ,
38  [channame] [varchar] (80) NULL ,
39  [appname] [varchar] (80) NULL ,
40  [appdata] [varchar] (80) NULL ,
41  [eventtime] [datetime] NULL ,
42  [eventtype] [varchar] (32) NULL ,
43  [uniqueid] [varchar] (32) NULL ,
44  [linkedid] [varchar] (32) NULL ,
45  [amaflags] [varchar] (16) NULL ,
46  [userfield] [varchar] (32) NULL ,
47  [peer] [varchar] (32) NULL
48 ) ON [PRIMARY]
49 
50 \endverbatim
51 
52 */
53 
54 /*** MODULEINFO
55  <depend>freetds</depend>
56  <support_level>extended</support_level>
57  ***/
58 
59 #include "asterisk.h"
60 
61 #include <time.h>
62 #include <math.h>
63 
64 #include "asterisk/config.h"
65 #include "asterisk/channel.h"
66 #include "asterisk/cel.h"
67 #include "asterisk/module.h"
68 #include "asterisk/logger.h"
69 
70 #include <sqlfront.h>
71 #include <sybdb.h>
72 
73 #ifdef FREETDS_PRE_0_62
74 #warning "You have older TDS, you should upgrade!"
75 #endif
76 
77 #define DATE_FORMAT "%Y/%m/%d %T"
78 
79 #define TDS_BACKEND_NAME "CEL TDS logging backend"
80 
81 static char *config = "cel_tds.conf";
82 
85  AST_STRING_FIELD(connection);
86  AST_STRING_FIELD(database);
87  AST_STRING_FIELD(username);
88  AST_STRING_FIELD(password);
89  AST_STRING_FIELD(table);
90  AST_STRING_FIELD(charset);
91  AST_STRING_FIELD(language);
92  );
93  DBPROCESS *dbproc;
94  unsigned int connected:1;
95 };
96 
97 AST_MUTEX_DEFINE_STATIC(tds_lock);
98 
99 static struct cel_tds_config *settings;
100 
101 static char *anti_injection(const char *, int);
102 static void get_date(char *, size_t len, struct timeval);
103 
104 static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
105  __attribute__((format(printf, 2, 3)));
106 
107 static int mssql_connect(void);
108 static int mssql_disconnect(void);
109 
110 static void tds_log(struct ast_event *event)
111 {
112  char start[80];
113  char *accountcode_ai, *clidnum_ai, *exten_ai, *context_ai, *clid_ai, *channel_ai, *app_ai, *appdata_ai, *uniqueid_ai, *linkedid_ai, *cidani_ai, *cidrdnis_ai, *ciddnid_ai, *peer_ai, *userfield_ai;
114  RETCODE erc;
115  int attempt = 1;
116  struct ast_cel_event_record record = {
118  };
119 
120  if (ast_cel_fill_record(event, &record)) {
121  return;
122  }
123 
124  ast_mutex_lock(&tds_lock);
125 
126  accountcode_ai = anti_injection(record.account_code, 20);
127  clidnum_ai = anti_injection(record.caller_id_num, 80);
128  clid_ai = anti_injection(record.caller_id_name, 80);
129  cidani_ai = anti_injection(record.caller_id_ani, 80);
130  cidrdnis_ai = anti_injection(record.caller_id_rdnis, 80);
131  ciddnid_ai = anti_injection(record.caller_id_dnid, 80);
132  exten_ai = anti_injection(record.extension, 80);
133  context_ai = anti_injection(record.context, 80);
134  channel_ai = anti_injection(record.channel_name, 80);
135  app_ai = anti_injection(record.application_name, 80);
136  appdata_ai = anti_injection(record.application_data, 80);
137  uniqueid_ai = anti_injection(record.unique_id, 32);
138  linkedid_ai = anti_injection(record.linked_id, 32);
139  userfield_ai = anti_injection(record.user_field, 32);
140  peer_ai = anti_injection(record.peer, 32);
141 
142  get_date(start, sizeof(start), record.event_time);
143 
144 retry:
145  /* Ensure that we are connected */
146  if (!settings->connected) {
147  ast_log(LOG_NOTICE, "Attempting to reconnect to %s (Attempt %d)\n", settings->connection, attempt);
148  if (mssql_connect()) {
149  /* Connect failed */
150  if (attempt++ < 3) {
151  goto retry;
152  }
153  goto done;
154  }
155  }
156 
157  erc = dbfcmd(settings->dbproc,
158  "INSERT INTO %s "
159  "("
160  "accountcode,"
161  "cidnum,"
162  "cidname,"
163  "cidani,"
164  "cidrdnis,"
165  "ciddnid,"
166  "exten,"
167  "context,"
168  "channel,"
169  "appname,"
170  "appdata,"
171  "eventtime,"
172  "eventtype,"
173  "amaflags, "
174  "uniqueid,"
175  "linkedid,"
176  "userfield,"
177  "peer"
178  ") "
179  "VALUES "
180  "("
181  "'%s'," /* accountcode */
182  "'%s'," /* clidnum */
183  "'%s'," /* clid */
184  "'%s'," /* cid-ani */
185  "'%s'," /* cid-rdnis */
186  "'%s'," /* cid-dnid */
187  "'%s'," /* exten */
188  "'%s'," /* context */
189  "'%s'," /* channel */
190  "'%s'," /* app */
191  "'%s'," /* appdata */
192  "%s, " /* eventtime */
193  "'%s'," /* eventtype */
194  "'%s'," /* amaflags */
195  "'%s'," /* uniqueid */
196  "'%s'," /* linkedid */
197  "'%s'," /* userfield */
198  "'%s'" /* peer */
199  ")",
200  settings->table, accountcode_ai, clidnum_ai, clid_ai, cidani_ai, cidrdnis_ai,
201  ciddnid_ai, exten_ai, context_ai, channel_ai, app_ai, appdata_ai, start,
202  (record.event_type == AST_CEL_USER_DEFINED)
203  ? record.user_defined_name : record.event_name,
204  ast_channel_amaflags2string(record.amaflag), uniqueid_ai, linkedid_ai,
205  userfield_ai, peer_ai);
206 
207  if (erc == FAIL) {
208  if (attempt++ < 3) {
209  ast_log(LOG_NOTICE, "Failed to build INSERT statement, retrying...\n");
210  mssql_disconnect();
211  goto retry;
212  } else {
213  ast_log(LOG_ERROR, "Failed to build INSERT statement, no CEL was logged.\n");
214  goto done;
215  }
216  }
217 
218  if (dbsqlexec(settings->dbproc) == FAIL) {
219  if (attempt++ < 3) {
220  ast_log(LOG_NOTICE, "Failed to execute INSERT statement, retrying...\n");
221  mssql_disconnect();
222  goto retry;
223  } else {
224  ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CEL was logged.\n");
225  goto done;
226  }
227  }
228 
229  /* Consume any results we might get back (this is more of a sanity check than
230  * anything else, since an INSERT shouldn't return results). */
231  while (dbresults(settings->dbproc) != NO_MORE_RESULTS) {
232  while (dbnextrow(settings->dbproc) != NO_MORE_ROWS);
233  }
234 
235 done:
236  ast_mutex_unlock(&tds_lock);
237 
238  ast_free(accountcode_ai);
239  ast_free(clidnum_ai);
240  ast_free(clid_ai);
241  ast_free(cidani_ai);
242  ast_free(cidrdnis_ai);
243  ast_free(ciddnid_ai);
244  ast_free(exten_ai);
245  ast_free(context_ai);
246  ast_free(channel_ai);
247  ast_free(app_ai);
248  ast_free(appdata_ai);
249  ast_free(uniqueid_ai);
250  ast_free(linkedid_ai);
251  ast_free(userfield_ai);
252  ast_free(peer_ai);
253 
254  return;
255 }
256 
257 static char *anti_injection(const char *str, int len)
258 {
259  /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */
260  char *buf;
261  char *buf_ptr, *srh_ptr;
262  char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
263  int idx;
264 
265  if (!(buf = ast_calloc(1, len + 1))) {
266  ast_log(LOG_ERROR, "Out of memory\n");
267  return NULL;
268  }
269 
270  buf_ptr = buf;
271 
272  /* Escape single quotes */
273  for (; *str && strlen(buf) < len; str++) {
274  if (*str == '\'') {
275  *buf_ptr++ = '\'';
276  }
277  *buf_ptr++ = *str;
278  }
279  *buf_ptr = '\0';
280 
281  /* Erase known bad input */
282  for (idx = 0; *known_bad[idx]; idx++) {
283  while ((srh_ptr = strcasestr(buf, known_bad[idx]))) {
284  memmove(srh_ptr, srh_ptr + strlen(known_bad[idx]), strlen(srh_ptr + strlen(known_bad[idx])) + 1);
285  }
286  }
287  return buf;
288 }
289 
290 static void get_date(char *dateField, size_t len, struct timeval when)
291 {
292  /* To make sure we have date variable if not insert null to SQL */
293  if (!ast_tvzero(when)) {
294  struct ast_tm tm;
295  ast_localtime(&when, &tm, NULL);
296  ast_strftime(dateField, len, "'" DATE_FORMAT "'", &tm);
297  } else {
298  ast_copy_string(dateField, "null", len);
299  }
300 }
301 
302 static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
303 {
304  va_list ap;
305  char *buffer;
306 
307  va_start(ap, fmt);
308  if (ast_vasprintf(&buffer, fmt, ap) < 0) {
309  va_end(ap);
310  return 1;
311  }
312  va_end(ap);
313 
314  if (dbfcmd(dbproc, buffer) == FAIL) {
315  ast_free(buffer);
316  return 1;
317  }
318 
319  ast_free(buffer);
320 
321  if (dbsqlexec(dbproc) == FAIL) {
322  return 1;
323  }
324 
325  /* Consume the result set (we don't really care about the result, though) */
326  while (dbresults(dbproc) != NO_MORE_RESULTS) {
327  while (dbnextrow(dbproc) != NO_MORE_ROWS);
328  }
329 
330  return 0;
331 }
332 
333 static int mssql_disconnect(void)
334 {
335  if (settings->dbproc) {
336  dbclose(settings->dbproc);
337  settings->dbproc = NULL;
338  }
339  settings->connected = 0;
340 
341  return 0;
342 }
343 
344 static int mssql_connect(void)
345 {
346  LOGINREC *login;
347 
348  if ((login = dblogin()) == NULL) {
349  ast_log(LOG_ERROR, "Unable to allocate login structure for db-lib\n");
350  return -1;
351  }
352 
353  DBSETLAPP(login, "TSQL");
354  DBSETLUSER(login, (char *) settings->username);
355  DBSETLPWD(login, (char *) settings->password);
356 
357  if (!ast_strlen_zero(settings->charset)) {
358  DBSETLCHARSET(login, (char *) settings->charset);
359  }
360 
361  if (!ast_strlen_zero(settings->language)) {
362  DBSETLNATLANG(login, (char *) settings->language);
363  }
364 
365  if ((settings->dbproc = dbopen(login, (char *) settings->connection)) == NULL) {
366  ast_log(LOG_ERROR, "Unable to connect to %s\n", settings->connection);
367  dbloginfree(login);
368  return -1;
369  }
370 
371  dbloginfree(login);
372 
373  if (dbuse(settings->dbproc, (char *) settings->database) == FAIL) {
374  ast_log(LOG_ERROR, "Unable to select database %s\n", settings->database);
375  goto failed;
376  }
377 
378  if (execute_and_consume(settings->dbproc, "SELECT 1 FROM [%s]", settings->table)) {
379  ast_log(LOG_ERROR, "Unable to find table '%s'\n", settings->table);
380  goto failed;
381  }
382 
383  settings->connected = 1;
384 
385  return 0;
386 
387 failed:
388  dbclose(settings->dbproc);
389  settings->dbproc = NULL;
390  return -1;
391 }
392 
393 static int tds_unload_module(void)
394 {
395  ast_cel_backend_unregister(TDS_BACKEND_NAME);
396 
397  if (settings) {
398  ast_mutex_lock(&tds_lock);
399  mssql_disconnect();
400  ast_mutex_unlock(&tds_lock);
401 
403  ast_free(settings);
404  }
405 
406  dbexit();
407 
408  return 0;
409 }
410 
411 static int tds_error_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr)
412 {
413  ast_log(LOG_ERROR, "%s (%d)\n", dberrstr, dberr);
414 
415  if (oserr != DBNOERR) {
416  ast_log(LOG_ERROR, "%s (%d)\n", oserrstr, oserr);
417  }
418 
419  return INT_CANCEL;
420 }
421 
422 static int tds_message_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line)
423 {
424  ast_debug(1, "Msg %d, Level %d, State %d, Line %d\n", msgno, severity, msgstate, line);
425  ast_log(LOG_NOTICE, "%s\n", msgtext);
426 
427  return 0;
428 }
429 
430 static int tds_load_module(int reload)
431 {
432  struct ast_config *cfg;
433  const char *ptr = NULL;
434  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
435 
436  cfg = ast_config_load(config, config_flags);
437  if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
438  ast_log(LOG_NOTICE, "Unable to load TDS config for CELs: %s\n", config);
439  return 0;
440  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
441  return 0;
442  }
443 
444  if (!ast_variable_browse(cfg, "global")) {
445  /* nothing configured */
446  ast_config_destroy(cfg);
447  ast_log(LOG_NOTICE, "cel_tds has no global category, nothing to configure.\n");
448  return 0;
449  }
450 
451  ast_mutex_lock(&tds_lock);
452 
453  /* Clear out any existing settings */
454  ast_string_field_init(settings, 0);
455 
456  ptr = ast_variable_retrieve(cfg, "global", "connection");
457  if (ptr) {
458  ast_string_field_set(settings, connection, ptr);
459  } else {
460  ast_log(LOG_ERROR, "Failed to connect: Database connection name not specified.\n");
461  goto failed;
462  }
463 
464  ptr = ast_variable_retrieve(cfg, "global", "dbname");
465  if (ptr) {
466  ast_string_field_set(settings, database, ptr);
467  } else {
468  ast_log(LOG_ERROR, "Failed to connect: Database dbname not specified.\n");
469  goto failed;
470  }
471 
472  ptr = ast_variable_retrieve(cfg, "global", "user");
473  if (ptr) {
474  ast_string_field_set(settings, username, ptr);
475  } else {
476  ast_log(LOG_ERROR, "Failed to connect: Database dbuser not specified.\n");
477  goto failed;
478  }
479 
480  ptr = ast_variable_retrieve(cfg, "global", "password");
481  if (ptr) {
482  ast_string_field_set(settings, password, ptr);
483  } else {
484  ast_log(LOG_ERROR, "Failed to connect: Database password not specified.\n");
485  goto failed;
486  }
487 
488  ptr = ast_variable_retrieve(cfg, "global", "charset");
489  if (ptr) {
490  ast_string_field_set(settings, charset, ptr);
491  }
492 
493  ptr = ast_variable_retrieve(cfg, "global", "language");
494  if (ptr) {
495  ast_string_field_set(settings, language, ptr);
496  }
497 
498  ptr = ast_variable_retrieve(cfg, "global", "table");
499  if (ptr) {
500  ast_string_field_set(settings, table, ptr);
501  } else {
502  ast_log(LOG_NOTICE, "Table name not specified, using 'cel' by default.\n");
503  ast_string_field_set(settings, table, "cel");
504  }
505 
506  mssql_disconnect();
507 
508  if (mssql_connect()) {
509  /* We failed to connect (mssql_connect takes care of logging it) */
510  goto failed;
511  }
512 
513  ast_mutex_unlock(&tds_lock);
514  ast_config_destroy(cfg);
515 
516  return 1;
517 
518 failed:
519  ast_mutex_unlock(&tds_lock);
520  ast_config_destroy(cfg);
521 
522  return 0;
523 }
524 
525 static int reload(void)
526 {
527  return tds_load_module(1);
528 }
529 
530 static int load_module(void)
531 {
532  if (dbinit() == FAIL) {
533  ast_log(LOG_ERROR, "Failed to initialize FreeTDS db-lib\n");
535  }
536 
537  dberrhandle(tds_error_handler);
538  dbmsghandle(tds_message_handler);
539 
540  settings = ast_calloc_with_stringfields(1, struct cel_tds_config, 256);
541 
542  if (!settings) {
543  dbexit();
545  }
546 
547  if (!tds_load_module(0)) {
549  ast_free(settings);
550  settings = NULL;
551  dbexit();
552  ast_log(LOG_WARNING,"cel_tds module had config problems; declining load\n");
554  }
555 
556  /* Register MSSQL CEL handler */
557  if (ast_cel_backend_register(TDS_BACKEND_NAME, tds_log)) {
558  ast_log(LOG_ERROR, "Unable to register MSSQL CEL handling\n");
560  ast_free(settings);
561  settings = NULL;
562  dbexit();
564  }
565 
567 }
568 
569 static int unload_module(void)
570 {
571  return tds_unload_module();
572 }
573 
574 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "FreeTDS CEL Backend",
575  .support_level = AST_MODULE_SUPPORT_EXTENDED,
576  .load = load_module,
577  .unload = unload_module,
578  .reload = reload,
579  .load_pri = AST_MODPRI_CDR_DRIVER,
580  .requires = "cel",
581 );
Helper struct for getting the fields out of a CEL event.
Definition: cel.h:138
An event.
Definition: event.c:81
Asterisk main include file. File version handling, generic pbx functions.
Call Event Logging API.
Time-related functions and macros.
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1781
#define DATE_FORMAT
Definition: cel_tds.c:77
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:117
#define ast_calloc_with_stringfields(n, type, size)
Allocate a structure with embedded stringfields in a single allocation.
Definition: stringfields.h:432
Definition: astman.c:222
const char * ast_channel_amaflags2string(enum ama_flags flags)
Convert the enum representation of an AMA flag to a string representation.
Definition: channel.c:4373
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define ast_vasprintf(ret, fmt, ap)
A wrapper for vasprintf()
Definition: astmm.h:278
static int get_date(char *s, int len)
Gets the current date and time, as formatted string.
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
General Asterisk PBX channel definitions.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
uint32_t version
struct ABI version
Definition: cel.h:148
#define ast_debug(level,...)
Log a DEBUG message.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition: cel.c:1769
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Support for logging to various files, console and syslog Configuration in file logger.conf.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:143
Structure used to handle boolean flags.
Definition: utils.h:199
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
a user-defined event, the event name field should be set
Definition: cel.h:69
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
int ast_cel_fill_record(const struct ast_event *event, struct ast_cel_event_record *r)
Fill in an ast_cel_event_record from a CEL event.
Definition: cel.c:821
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521