Asterisk - The Open Source Telephony Project  18.5.0
cdr_adaptive_odbc.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Tilghman Lesher
5  *
6  * Tilghman Lesher <[email protected]>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*!
20  * \file
21  * \brief Adaptive ODBC CDR backend
22  *
23  * \author Tilghman Lesher <[email protected]>
24  * \ingroup cdr_drivers
25  */
26 
27 /*! \li \ref cdr_adaptive_odbc.c uses the configuration file \ref cdr_adaptive_odbc.conf
28  * \addtogroup configuration_file Configuration Files
29  */
30 
31 /*!
32  * \page cdr_adaptive_odbc.conf cdr_adaptive_odbc.conf
33  * \verbinclude cdr_adaptive_odbc.conf.sample
34  */
35 
36 /*** MODULEINFO
37  <depend>res_odbc</depend>
38  <depend>generic_odbc</depend>
39  <support_level>core</support_level>
40  ***/
41 
42 #include "asterisk.h"
43 
44 #include <sys/types.h>
45 #include <time.h>
46 
47 #include <sql.h>
48 #include <sqlext.h>
49 #include <sqltypes.h>
50 
51 #include "asterisk/config.h"
52 #include "asterisk/channel.h"
53 #include "asterisk/lock.h"
54 #include "asterisk/linkedlists.h"
55 #include "asterisk/res_odbc.h"
56 #include "asterisk/cdr.h"
57 #include "asterisk/module.h"
58 
59 #define CONFIG "cdr_adaptive_odbc.conf"
60 
61 static const char name[] = "Adaptive ODBC";
62 /* Optimization to reduce number of memory allocations */
63 static int maxsize = 512, maxsize2 = 512;
64 
65 struct columns {
66  char *name;
67  char *cdrname;
68  char *filtervalue;
69  char *staticvalue;
70  SQLSMALLINT type;
71  SQLINTEGER size;
72  SQLSMALLINT decimals;
73  SQLSMALLINT radix;
74  SQLSMALLINT nullable;
75  SQLINTEGER octetlen;
77  unsigned int negatefiltervalue:1;
78 };
79 
80 struct tables {
81  char *connection;
82  char *table;
83  char *schema;
85  unsigned int usegmtime:1;
88 };
89 
91 
92 static int load_config(void)
93 {
94  struct ast_config *cfg;
95  struct ast_variable *var;
96  const char *tmp, *catg;
97  struct tables *tableptr;
98  struct columns *entry;
99  struct odbc_obj *obj;
100  char columnname[80];
101  char connection[40];
102  char table[40];
103  char schema[40];
104  char quoted_identifiers;
105  int lenconnection, lentable, lenschema, usegmtime = 0;
106  SQLLEN sqlptr;
107  int res = 0;
108  SQLHSTMT stmt = NULL;
109  struct ast_flags config_flags = { 0 }; /* Part of our config comes from the database */
110 
111  cfg = ast_config_load(CONFIG, config_flags);
112  if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
113  ast_log(LOG_WARNING, "Unable to load " CONFIG ". No adaptive ODBC CDRs.\n");
114  return -1;
115  }
116 
117  for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
118  var = ast_variable_browse(cfg, catg);
119  if (!var)
120  continue;
121 
122  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "connection"))) {
123  ast_log(LOG_WARNING, "No connection parameter found in '%s'. Skipping.\n", catg);
124  continue;
125  }
126  ast_copy_string(connection, tmp, sizeof(connection));
127  lenconnection = strlen(connection);
128 
129  if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "usegmtime"))) {
130  usegmtime = ast_true(tmp);
131  }
132 
133  /* When loading, we want to be sure we can connect. */
134  obj = ast_odbc_request_obj(connection, 1);
135  if (!obj) {
136  ast_log(LOG_WARNING, "No such connection '%s' in the '%s' section of " CONFIG ". Check res_odbc.conf.\n", connection, catg);
137  continue;
138  }
139 
140  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "table"))) {
141  ast_log(LOG_NOTICE, "No table name found. Assuming 'cdr'.\n");
142  tmp = "cdr";
143  }
144  ast_copy_string(table, tmp, sizeof(table));
145  lentable = strlen(table);
146 
147  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "schema"))) {
148  tmp = "";
149  }
150  ast_copy_string(schema, tmp, sizeof(schema));
151  lenschema = strlen(schema);
152 
153  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "quoted_identifiers"))) {
154  tmp = "";
155  }
156  quoted_identifiers = tmp[0];
157  if (strlen(tmp) > 1) {
158  ast_log(LOG_ERROR, "The quoted_identifiers setting only accepts a single character,"
159  " while a value of '%s' was provided. This option has been disabled as a result.\n", tmp);
160  quoted_identifiers = '\0';
161  }
162 
163  res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
164  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
165  ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", connection);
167  continue;
168  }
169 
170  res = SQLColumns(stmt, NULL, 0, lenschema == 0 ? NULL : (unsigned char *)schema, SQL_NTS, (unsigned char *)table, SQL_NTS, (unsigned char *)"%", SQL_NTS);
171  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
172  ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'. Skipping.\n", connection);
173  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
175  continue;
176  }
177 
178  tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + lenconnection + 1 + lentable + 1 + lenschema + 1 + 1);
179  if (!tableptr) {
180  ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'%s%s%s\n", table, connection,
181  lenschema ? " (schema '" : "", lenschema ? schema : "", lenschema ? "')" : "");
182  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
184  res = -1;
185  break;
186  }
187 
188  tableptr->usegmtime = usegmtime;
189  tableptr->connection = (char *)tableptr + sizeof(*tableptr);
190  tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
191  tableptr->schema = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1 + lentable + 1;
192  ast_copy_string(tableptr->connection, connection, lenconnection + 1);
193  ast_copy_string(tableptr->table, table, lentable + 1);
194  ast_copy_string(tableptr->schema, schema, lenschema + 1);
195  tableptr->quoted_identifiers = quoted_identifiers;
196 
197  ast_verb(3, "Found adaptive CDR table %[email protected]%s.\n", tableptr->table, tableptr->connection);
198 
199  /* Check for filters first */
200  for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
201  if (strncmp(var->name, "filter", 6) == 0) {
202  int negate = 0;
203  char *cdrvar = ast_strdupa(var->name + 6);
204  cdrvar = ast_strip(cdrvar);
205  if (cdrvar[strlen(cdrvar) - 1] == '!') {
206  negate = 1;
207  cdrvar[strlen(cdrvar) - 1] = '\0';
208  ast_trim_blanks(cdrvar);
209  }
210 
211  ast_verb(3, "Found filter %s'%s' for CDR variable %s in %[email protected]%s\n", negate ? "!" : "", var->value, cdrvar, tableptr->table, tableptr->connection);
212 
213  entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(cdrvar) + 1 + strlen(var->value) + 1);
214  if (!entry) {
215  ast_log(LOG_ERROR, "Out of memory creating filter entry for CDR variable '%s' in table '%s' on connection '%s'\n", cdrvar, table, connection);
216  res = -1;
217  break;
218  }
219 
220  /* NULL column entry means this isn't a column in the database */
221  entry->name = NULL;
222  entry->cdrname = (char *)entry + sizeof(*entry);
223  entry->filtervalue = (char *)entry + sizeof(*entry) + strlen(cdrvar) + 1;
224  strcpy(entry->cdrname, cdrvar);
225  strcpy(entry->filtervalue, var->value);
226  entry->negatefiltervalue = negate;
227 
228  AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
229  }
230  }
231 
232  while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
233  char *cdrvar = "", *staticvalue = "";
234 
235  SQLGetData(stmt, 4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
236 
237  /* Is there an alias for this column? */
238 
239  /* NOTE: This seems like a non-optimal parse method, but I'm going
240  * for user configuration readability, rather than fast parsing. We
241  * really don't parse this file all that often, anyway.
242  */
243  for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
244  if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, columnname) == 0) {
245  char *alias = ast_strdupa(var->name + 5);
246  cdrvar = ast_strip(alias);
247  ast_verb(3, "Found alias %s for column %s in %[email protected]%s\n", cdrvar, columnname, tableptr->table, tableptr->connection);
248  break;
249  } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, columnname) == 0) {
250  char *item = ast_strdupa(var->name + 6);
251  item = ast_strip(item);
252  if (item[0] == '"' && item[strlen(item) - 1] == '"') {
253  /* Remove surrounding quotes */
254  item[strlen(item) - 1] = '\0';
255  item++;
256  }
257  staticvalue = item;
258  }
259  }
260 
261  entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1);
262  if (!entry) {
263  ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
264  res = -1;
265  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
266  break;
267  }
268  entry->name = (char *)entry + sizeof(*entry);
269  strcpy(entry->name, columnname);
270 
271  if (!ast_strlen_zero(cdrvar)) {
272  entry->cdrname = entry->name + strlen(columnname) + 1;
273  strcpy(entry->cdrname, cdrvar);
274  } else { /* Point to same place as the column name */
275  entry->cdrname = (char *)entry + sizeof(*entry);
276  }
277 
279  entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
280  strcpy(entry->staticvalue, staticvalue);
281  }
282 
283  SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
284  SQLGetData(stmt, 7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
285  SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
286  SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
287  SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
288  SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
289 
290  /* Specification states that the octenlen should be the maximum number of bytes
291  * returned in a char or binary column, but it seems that some drivers just set
292  * it to NULL. (Bad Postgres! No biscuit!) */
293  if (entry->octetlen == 0)
294  entry->octetlen = entry->size;
295 
296  ast_verb(4, "Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, (long) entry->size, (long) entry->octetlen, entry->decimals, entry->radix);
297  /* Insert column info into column list */
298  AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
299  res = 0;
300  }
301 
302  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
304 
305  if (AST_LIST_FIRST(&(tableptr->columns)))
307  else
308  ast_free(tableptr);
309  }
310  ast_config_destroy(cfg);
311  return res;
312 }
313 
314 static int free_config(void)
315 {
316  struct tables *table;
317  struct columns *entry;
318  while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
319  while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
320  ast_free(entry);
321  }
322  ast_free(table);
323  }
324  return 0;
325 }
326 
327 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
328 {
329  int res, i;
330  SQLHSTMT stmt;
331  SQLINTEGER nativeerror = 0, numfields = 0;
332  SQLSMALLINT diagbytes = 0;
333  unsigned char state[10], diagnostic[256];
334 
335  res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
336  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
337  ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
338  return NULL;
339  }
340 
341  res = ast_odbc_prepare(obj, stmt, data);
342  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
343  ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *) data);
344  SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
345  for (i = 0; i < numfields; i++) {
346  SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
347  ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
348  if (i > 10) {
349  ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
350  break;
351  }
352  }
353  SQLFreeHandle (SQL_HANDLE_STMT, stmt);
354  return NULL;
355  }
356 
357  return stmt;
358 }
359 
360 #define LENGTHEN_BUF(size, var_sql) \
361  do { \
362  /* Lengthen buffer, if necessary */ \
363  if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
364  if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
365  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
366  ast_free(sql); \
367  ast_free(sql2); \
368  AST_RWLIST_UNLOCK(&odbc_tables); \
369  return -1; \
370  } \
371  } \
372  } while (0)
373 
374 #define LENGTHEN_BUF1(size) \
375  LENGTHEN_BUF(size, sql);
376 #define LENGTHEN_BUF2(size) \
377  LENGTHEN_BUF(size, sql2);
378 
379 static int odbc_log(struct ast_cdr *cdr)
380 {
381  struct tables *tableptr;
382  struct columns *entry;
383  struct odbc_obj *obj;
384  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
385  char *tmp;
386  char colbuf[1024], *colptr;
387  SQLHSTMT stmt = NULL;
388  SQLLEN rows = 0;
389  char *separator;
390  int quoted = 0;
391 
392  if (!sql || !sql2) {
393  if (sql)
394  ast_free(sql);
395  if (sql2)
396  ast_free(sql2);
397  return -1;
398  }
399 
401  ast_log(LOG_ERROR, "Unable to lock table list. Insert CDR(s) failed.\n");
402  ast_free(sql);
403  ast_free(sql2);
404  return -1;
405  }
406 
407  AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
408  separator = "";
409 
410  quoted = 0;
411  if (tableptr->quoted_identifiers != '\0'){
412  quoted = 1;
413  }
414 
415  if (ast_strlen_zero(tableptr->schema)) {
416  if (quoted) {
417  ast_str_set(&sql, 0, "INSERT INTO %c%s%c (",
418  tableptr->quoted_identifiers, tableptr->table, tableptr->quoted_identifiers );
419  }else{
420  ast_str_set(&sql, 0, "INSERT INTO %s (", tableptr->table);
421  }
422  } else {
423  if (quoted) {
424  ast_str_set(&sql, 0, "INSERT INTO %c%s%c.%c%s%c (",
425  tableptr->quoted_identifiers, tableptr->schema, tableptr->quoted_identifiers,
426  tableptr->quoted_identifiers, tableptr->table, tableptr->quoted_identifiers);
427  }else{
428  ast_str_set(&sql, 0, "INSERT INTO %s.%s (", tableptr->schema, tableptr->table);
429  }
430  }
431  ast_str_set(&sql2, 0, " VALUES (");
432 
433  /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
434  if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
435  ast_log(LOG_WARNING, "cdr_adaptive_odbc: Unable to retrieve database handle for '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
436  continue;
437  }
438 
439  AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
440  int datefield = 0;
441  if (strcasecmp(entry->cdrname, "start") == 0) {
442  datefield = 1;
443  } else if (strcasecmp(entry->cdrname, "answer") == 0) {
444  datefield = 2;
445  } else if (strcasecmp(entry->cdrname, "end") == 0) {
446  datefield = 3;
447  }
448 
449  /* Check if we have a similarly named variable */
450  if (entry->staticvalue) {
451  colptr = ast_strdupa(entry->staticvalue);
452  } else if (datefield && tableptr->usegmtime) {
453  struct timeval date_tv = (datefield == 1) ? cdr->start : (datefield == 2) ? cdr->answer : cdr->end;
454  struct ast_tm tm = { 0, };
455  ast_localtime(&date_tv, &tm, "UTC");
456  ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S", &tm);
457  colptr = colbuf;
458  } else {
459  ast_cdr_format_var(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), datefield ? 0 : 1);
460  }
461 
462  if (colptr) {
463  /* Check first if the column filters this entry. Note that this
464  * is very specifically NOT ast_strlen_zero(), because the filter
465  * could legitimately specify that the field is blank, which is
466  * different from the field being unspecified (NULL). */
467  if ((entry->filtervalue && !entry->negatefiltervalue && strcasecmp(colptr, entry->filtervalue) != 0) ||
468  (entry->filtervalue && entry->negatefiltervalue && strcasecmp(colptr, entry->filtervalue) == 0)) {
469  ast_verb(4, "CDR column '%s' with value '%s' does not match filter of"
470  " %s'%s'. Cancelling this CDR.\n",
471  entry->cdrname, colptr, entry->negatefiltervalue ? "!" : "", entry->filtervalue);
472  goto early_release;
473  }
474 
475  /* Only a filter? */
476  if (ast_strlen_zero(entry->name))
477  continue;
478 
479  LENGTHEN_BUF1(strlen(entry->name));
480 
481  switch (entry->type) {
482  case SQL_CHAR:
483  case SQL_VARCHAR:
484  case SQL_LONGVARCHAR:
485 #ifdef HAVE_ODBC_WCHAR
486  case SQL_WCHAR:
487  case SQL_WVARCHAR:
488  case SQL_WLONGVARCHAR:
489 #endif
490  case SQL_BINARY:
491  case SQL_VARBINARY:
492  case SQL_LONGVARBINARY:
493  case SQL_GUID:
494  /* For these two field names, get the rendered form, instead of the raw
495  * form (but only when we're dealing with a character-based field).
496  */
497  if (strcasecmp(entry->name, "disposition") == 0) {
498  ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0);
499  } else if (strcasecmp(entry->name, "amaflags") == 0) {
500  ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0);
501  }
502 
503  /* Truncate too-long fields */
504  if (entry->type != SQL_GUID) {
505  if (strlen(colptr) > entry->octetlen) {
506  colptr[entry->octetlen] = '\0';
507  }
508  }
509 
510  LENGTHEN_BUF2(strlen(colptr));
511 
512  /* Encode value, with escaping */
513  ast_str_append(&sql2, 0, "%s'", separator);
514  for (tmp = colptr; *tmp; tmp++) {
515  if (*tmp == '\'') {
516  ast_str_append(&sql2, 0, "''");
517  } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
518  ast_str_append(&sql2, 0, "\\\\");
519  } else {
520  ast_str_append(&sql2, 0, "%c", *tmp);
521  }
522  }
523  ast_str_append(&sql2, 0, "'");
524  break;
525  case SQL_TYPE_DATE:
526  if (ast_strlen_zero(colptr)) {
527  continue;
528  } else {
529  int year = 0, month = 0, day = 0;
530  if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
531  month <= 0 || month > 12 || day < 0 || day > 31 ||
532  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
533  (month == 2 && year % 400 == 0 && day > 29) ||
534  (month == 2 && year % 100 == 0 && day > 28) ||
535  (month == 2 && year % 4 == 0 && day > 29) ||
536  (month == 2 && year % 4 != 0 && day > 28)) {
537  ast_log(LOG_WARNING, "CDR variable %s is not a valid date ('%s').\n", entry->name, colptr);
538  continue;
539  }
540 
541  if (year > 0 && year < 100) {
542  year += 2000;
543  }
544 
545  LENGTHEN_BUF2(17);
546  ast_str_append(&sql2, 0, "%s{ d '%04d-%02d-%02d' }", separator, year, month, day);
547  }
548  break;
549  case SQL_TYPE_TIME:
550  if (ast_strlen_zero(colptr)) {
551  continue;
552  } else {
553  int hour = 0, minute = 0, second = 0;
554  int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
555 
556  if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
557  ast_log(LOG_WARNING, "CDR variable %s is not a valid time ('%s').\n", entry->name, colptr);
558  continue;
559  }
560 
561  LENGTHEN_BUF2(15);
562  ast_str_append(&sql2, 0, "%s{ t '%02d:%02d:%02d' }", separator, hour, minute, second);
563  }
564  break;
565  case SQL_TYPE_TIMESTAMP:
566  case SQL_TIMESTAMP:
567  if (ast_strlen_zero(colptr)) {
568  continue;
569  } else {
570  int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
571  int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second);
572 
573  if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
574  month <= 0 || month > 12 || day < 0 || day > 31 ||
575  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
576  (month == 2 && year % 400 == 0 && day > 29) ||
577  (month == 2 && year % 100 == 0 && day > 28) ||
578  (month == 2 && year % 4 == 0 && day > 29) ||
579  (month == 2 && year % 4 != 0 && day > 28) ||
580  hour > 23 || minute > 59 || second > 59 || hour < 0 || minute < 0 || second < 0) {
581  ast_log(LOG_WARNING, "CDR variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
582  continue;
583  }
584 
585  if (year > 0 && year < 100) {
586  year += 2000;
587  }
588 
589  LENGTHEN_BUF2(26);
590  ast_str_append(&sql2, 0, "%s{ ts '%04d-%02d-%02d %02d:%02d:%02d' }", separator, year, month, day, hour, minute, second);
591  }
592  break;
593  case SQL_INTEGER:
594  if (ast_strlen_zero(colptr)) {
595  continue;
596  } else {
597  int integer = 0;
598  if (sscanf(colptr, "%30d", &integer) != 1) {
599  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
600  continue;
601  }
602 
603  LENGTHEN_BUF2(12);
604  ast_str_append(&sql2, 0, "%s%d", separator, integer);
605  }
606  break;
607  case SQL_BIGINT:
608  if (ast_strlen_zero(colptr)) {
609  continue;
610  } else {
611  long long integer = 0;
612  if (sscanf(colptr, "%30lld", &integer) != 1) {
613  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
614  continue;
615  }
616 
617  LENGTHEN_BUF2(24);
618  ast_str_append(&sql2, 0, "%s%lld", separator, integer);
619  }
620  break;
621  case SQL_SMALLINT:
622  if (ast_strlen_zero(colptr)) {
623  continue;
624  } else {
625  short integer = 0;
626  if (sscanf(colptr, "%30hd", &integer) != 1) {
627  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
628  continue;
629  }
630 
631  LENGTHEN_BUF2(6);
632  ast_str_append(&sql2, 0, "%s%d", separator, integer);
633  }
634  break;
635  case SQL_TINYINT:
636  if (ast_strlen_zero(colptr)) {
637  continue;
638  } else {
639  signed char integer = 0;
640  if (sscanf(colptr, "%30hhd", &integer) != 1) {
641  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
642  continue;
643  }
644 
645  LENGTHEN_BUF2(4);
646  ast_str_append(&sql2, 0, "%s%d", separator, integer);
647  }
648  break;
649  case SQL_BIT:
650  if (ast_strlen_zero(colptr)) {
651  continue;
652  } else {
653  signed char integer = 0;
654  if (sscanf(colptr, "%30hhd", &integer) != 1) {
655  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
656  continue;
657  }
658  if (integer != 0)
659  integer = 1;
660 
661  LENGTHEN_BUF2(2);
662  ast_str_append(&sql2, 0, "%s%d", separator, integer);
663  }
664  break;
665  case SQL_NUMERIC:
666  case SQL_DECIMAL:
667  if (ast_strlen_zero(colptr)) {
668  continue;
669  } else {
670  double number = 0.0;
671 
672  if (!strcasecmp(entry->cdrname, "billsec")) {
673  if (!ast_tvzero(cdr->answer)) {
674  snprintf(colbuf, sizeof(colbuf), "%lf",
675  (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
676  } else {
677  ast_copy_string(colbuf, "0", sizeof(colbuf));
678  }
679  } else if (!strcasecmp(entry->cdrname, "duration")) {
680  snprintf(colbuf, sizeof(colbuf), "%lf",
681  (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
682 
683  if (!ast_strlen_zero(colbuf)) {
684  colptr = colbuf;
685  }
686  }
687 
688  if (sscanf(colptr, "%30lf", &number) != 1) {
689  ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
690  continue;
691  }
692 
693  LENGTHEN_BUF2(entry->decimals);
694  ast_str_append(&sql2, 0, "%s%*.*lf", separator, entry->decimals, entry->radix, number);
695  }
696  break;
697  case SQL_FLOAT:
698  case SQL_REAL:
699  case SQL_DOUBLE:
700  if (ast_strlen_zero(colptr)) {
701  continue;
702  } else {
703  double number = 0.0;
704 
705  if (!strcasecmp(entry->cdrname, "billsec")) {
706  if (!ast_tvzero(cdr->answer)) {
707  snprintf(colbuf, sizeof(colbuf), "%lf",
708  (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
709  } else {
710  ast_copy_string(colbuf, "0", sizeof(colbuf));
711  }
712  } else if (!strcasecmp(entry->cdrname, "duration")) {
713  snprintf(colbuf, sizeof(colbuf), "%lf",
714  (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
715 
716  if (!ast_strlen_zero(colbuf)) {
717  colptr = colbuf;
718  }
719  }
720 
721  if (sscanf(colptr, "%30lf", &number) != 1) {
722  ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
723  continue;
724  }
725 
726  LENGTHEN_BUF2(entry->decimals);
727  ast_str_append(&sql2, 0, "%s%lf", separator, number);
728  }
729  break;
730  default:
731  ast_log(LOG_WARNING, "Column type %d (field '%s:%s:%s') is unsupported at this time.\n", entry->type, tableptr->connection, tableptr->table, entry->name);
732  continue;
733  }
734  if (quoted) {
735  ast_str_append(&sql, 0, "%s%c%s%c", separator, tableptr->quoted_identifiers, entry->name, tableptr->quoted_identifiers);
736  } else {
737  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
738  }
739  separator = ", ";
740  } else if (entry->filtervalue
741  && ((!entry->negatefiltervalue && entry->filtervalue[0] != '\0')
742  || (entry->negatefiltervalue && entry->filtervalue[0] == '\0'))) {
743  ast_log(AST_LOG_WARNING, "CDR column '%s' was not set and does not match filter of"
744  " %s'%s'. Cancelling this CDR.\n",
745  entry->cdrname, entry->negatefiltervalue ? "!" : "",
746  entry->filtervalue);
747  goto early_release;
748  }
749  }
750 
751  /* Concatenate the two constructed buffers */
753  ast_str_append(&sql, 0, ")");
754  ast_str_append(&sql2, 0, ")");
755  ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
756 
757  ast_debug(3, "Executing [%s]\n", ast_str_buffer(sql));
758 
760  if (stmt) {
761  SQLRowCount(stmt, &rows);
762  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
763  }
764  if (rows == 0) {
765  ast_log(LOG_WARNING, "cdr_adaptive_odbc: Insert failed on '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
766  }
767 early_release:
769  }
771 
772  /* Next time, just allocate buffers that are that big to start with. */
773  if (ast_str_strlen(sql) > maxsize) {
774  maxsize = ast_str_strlen(sql);
775  }
776  if (ast_str_strlen(sql2) > maxsize2) {
777  maxsize2 = ast_str_strlen(sql2);
778  }
779 
780  ast_free(sql);
781  ast_free(sql2);
782  return 0;
783 }
784 
785 static int unload_module(void)
786 {
787  if (ast_cdr_unregister(name)) {
788  return -1;
789  }
790 
793  ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
794  return -1;
795  }
796 
797  free_config();
799  return 0;
800 }
801 
802 static int load_module(void)
803 {
805  ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
806  return 0;
807  }
808 
809  load_config();
812  return 0;
813 }
814 
815 static int reload(void)
816 {
818  ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
819  return -1;
820  }
821 
822  free_config();
823  load_config();
825  return 0;
826 }
827 
828 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Adaptive ODBC CDR backend",
829  .support_level = AST_MODULE_SUPPORT_CORE,
830  .load = load_module,
831  .unload = unload_module,
832  .reload = reload,
833  .load_pri = AST_MODPRI_CDR_DRIVER,
834  .requires = "cdr,res_odbc",
835 );
const char * description
Definition: module.h:352
SQLHDBC con
Definition: res_odbc.h:47
struct ast_variable * next
struct columns::@8 list
int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
Checks if the database natively supports backslash as an escape character.
Definition: res_odbc.c:842
SQLSMALLINT radix
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:420
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:2988
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized...
Definition: linkedlists.h:332
char * filtervalue
static int load_module(void)
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1216
Time-related functions and macros.
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:51
#define LOG_WARNING
Definition: logger.h:274
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
#define CONFIG_STATUS_FILEINVALID
SQLSMALLINT nullable
int ast_odbc_prepare(struct odbc_obj *obj, SQLHSTMT *stmt, const char *sql)
Prepares a SQL query on a statement.
Definition: res_odbc.c:463
static int tmp()
Definition: bt_open.c:389
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
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
Structure for variables, used for configurations and for channel variables.
#define AST_LOG_WARNING
Definition: logger.h:279
#define var
Definition: ast_expr2f.c:614
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:108
SQLSMALLINT type
static int reload(void)
#define LENGTHEN_BUF2(size)
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1091
static struct aco_type item
Definition: test_config.c:1463
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3328
SQLINTEGER octetlen
#define NULL
Definition: resample.c:96
#define ast_verb(level,...)
Definition: logger.h:463
#define ast_strlen_zero(foo)
Definition: strings.h:52
Number structure.
Definition: app_followme.c:154
static char * table
Definition: cdr_odbc.c:58
Call Detail Record API.
void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw)
Format a CDR variable from an already posted CDR.
Definition: cdr.c:3050
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1065
Configuration File Parser.
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:77
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
SQLSMALLINT decimals
#define ast_config_load(filename, flags)
Load a config file.
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:2943
General Asterisk PBX channel definitions.
static int maxsize2
SQLINTEGER size
ODBC container.
Definition: res_odbc.h:46
static int unload_module(void)
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:219
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: extconf.c:1290
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
A set of macros to manage forward-linked lists.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:832
ODBC resource manager.
AST_LIST_HEAD_NOLOCK(contactliststruct, contact)
char * cdrname
char * connection
struct timeval answer
Definition: cdr.h:296
Responsible for call detail data.
Definition: cdr.h:276
#define LOG_ERROR
Definition: logger.h:285
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:730
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: main/utils.c:1951
static int free_config(void)
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
char * schema
#define LOG_NOTICE
Definition: logger.h:263
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:182
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:409
static struct columns columns
struct timeval start
Definition: cdr.h:294
static const char name[]
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
#define CONFIG
#define ast_odbc_request_obj(a, b)
Definition: res_odbc.h:122
static int maxsize
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:843
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
static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
Structure used to handle boolean flags.
Definition: utils.h:199
char * table
#define AST_RWLIST_ENTRY
Definition: linkedlists.h:414
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:694
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:688
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:740
struct timeval end
Definition: cdr.h:298
SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT(*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
Prepares, executes, and returns the resulting statement handle.
Definition: res_odbc.c:407
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
static int odbc_log(struct ast_cdr *cdr)
struct tables::mysql_columns columns
char * staticvalue
Definition: search.h:40
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
Definition: time.h:78
#define LENGTHEN_BUF1(size)
static char * schema
Definition: cel_pgsql.c:72
char quoted_identifiers
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
Definition: res_odbc.c:813
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
unsigned int negatefiltervalue
Asterisk module definitions.
static int usegmtime
Definition: cdr_csv.c:54
INT32 integer
Definition: lpc10.h:80
static int load_config(void)
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620
unsigned int usegmtime