Asterisk - The Open Source Telephony Project  18.5.0
cel_odbc.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008 Digium
5  *
6  * Adapted from cdr_adaptive_odbc:
7  * Tilghman Lesher <tlesher AT digium DOT com>
8  * by Steve Murphy
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20 
21 /*! \file
22  *
23  * \brief ODBC CEL backend
24  *
25  * \author Tilghman Lesher \verbatim <tlesher AT digium DOT com> \endverbatim
26  * \ingroup cel_drivers
27  */
28 
29 /*** MODULEINFO
30  <depend>res_odbc</depend>
31  <depend>generic_odbc</depend>
32  <support_level>core</support_level>
33  ***/
34 
35 #include "asterisk.h"
36 
37 #include <sys/types.h>
38 #include <time.h>
39 #include <math.h>
40 
41 #include <sql.h>
42 #include <sqlext.h>
43 #include <sqltypes.h>
44 
45 #include "asterisk/config.h"
46 #include "asterisk/channel.h"
47 #include "asterisk/lock.h"
48 #include "asterisk/linkedlists.h"
49 #include "asterisk/res_odbc.h"
50 #include "asterisk/cel.h"
51 #include "asterisk/module.h"
52 
53 #define CONFIG "cel_odbc.conf"
54 
55 #define ODBC_BACKEND_NAME "ODBC CEL backend"
56 
57 /*! \brief show_user_def is off by default */
58 #define CEL_SHOW_USERDEF_DEFAULT 0
59 
60 /*! TRUE if we should set the eventtype field to USER_DEFINED on user events. */
61 static unsigned char cel_show_user_def;
62 
63 /* Optimization to reduce number of memory allocations */
64 static int maxsize = 512, maxsize2 = 512;
65 
66 struct columns {
67  char *name;
68  char *celname;
69  char *filtervalue;
70  char *staticvalue;
71  SQLSMALLINT type;
72  SQLINTEGER size;
73  SQLSMALLINT decimals;
74  SQLSMALLINT radix;
75  SQLSMALLINT nullable;
76  SQLINTEGER octetlen;
78 };
79 
80 struct tables {
81  char *connection;
82  char *table;
83  unsigned int usegmtime:1;
84  unsigned int allowleapsec:1;
87 };
88 
90 
91 static int load_config(void)
92 {
93  struct ast_config *cfg;
94  struct ast_variable *var;
95  const char *tmp, *catg;
96  struct tables *tableptr;
97  struct columns *entry;
98  struct odbc_obj *obj;
99  char columnname[80];
100  char connection[40];
101  char table[40];
102  int lenconnection, lentable;
103  SQLLEN sqlptr;
104  int res = 0;
105  SQLHSTMT stmt = NULL;
106  struct ast_flags config_flags = { 0 }; /* Part of our config comes from the database */
107 
108  cfg = ast_config_load(CONFIG, config_flags);
109  if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
110  ast_log(LOG_WARNING, "Unable to load " CONFIG ". No ODBC CEL records!\n");
111  return -1;
112  }
113 
114  /* Process the general category */
116  for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
117  if (!strcasecmp(var->name, "show_user_defined")) {
118  cel_show_user_def = ast_true(var->value) ? 1 : 0;
119  } else {
120  /* Unknown option name. */
121  }
122  }
123 
124  for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
125  if (!strcasecmp(catg, "general")) {
126  continue;
127  }
128  var = ast_variable_browse(cfg, catg);
129  if (!var)
130  continue;
131 
132  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "connection"))) {
133  ast_log(LOG_WARNING, "No connection parameter found in '%s'. Skipping.\n", catg);
134  continue;
135  }
136  ast_copy_string(connection, tmp, sizeof(connection));
137  lenconnection = strlen(connection);
138 
139  /* When loading, we want to be sure we can connect. */
140  obj = ast_odbc_request_obj(connection, 1);
141  if (!obj) {
142  ast_log(LOG_WARNING, "No such connection '%s' in the '%s' section of " CONFIG ". Check res_odbc.conf.\n", connection, catg);
143  continue;
144  }
145 
146  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "table"))) {
147  ast_log(LOG_NOTICE, "No table name found. Assuming 'cel'.\n");
148  tmp = "cel";
149  }
150  ast_copy_string(table, tmp, sizeof(table));
151  lentable = strlen(table);
152 
153  res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
154  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
155  ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", connection);
157  continue;
158  }
159 
160  res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)table, SQL_NTS, (unsigned char *)"%", SQL_NTS);
161  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
162  ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'. Skipping.\n", connection);
164  continue;
165  }
166 
167  tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + lenconnection + 1 + lentable + 1);
168  if (!tableptr) {
169  ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", table, connection);
171  res = -1;
172  break;
173  }
174 
175  tableptr->connection = (char *)tableptr + sizeof(*tableptr);
176  tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
177  ast_copy_string(tableptr->connection, connection, lenconnection + 1);
178  ast_copy_string(tableptr->table, table, lentable + 1);
179 
180  tableptr->usegmtime = 0;
181  if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "usegmtime"))) {
182  tableptr->usegmtime = ast_true(tmp);
183  }
184 
185  tableptr->allowleapsec = 1;
186  if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "allowleapsecond"))) {
187  tableptr->allowleapsec = ast_true(tmp);
188  }
189 
190  ast_verb(3, "Found CEL table %s@%s.\n", tableptr->table, tableptr->connection);
191 
192  /* Check for filters first */
193  for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
194  if (strncmp(var->name, "filter", 6) == 0) {
195  char *celvar = ast_strdupa(var->name + 6);
196  celvar = ast_strip(celvar);
197  ast_verb(3, "Found filter %s for cel variable %s in %s@%s\n", var->value, celvar, tableptr->table, tableptr->connection);
198 
199  entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(celvar) + 1 + strlen(var->value) + 1);
200  if (!entry) {
201  ast_log(LOG_ERROR, "Out of memory creating filter entry for CEL variable '%s' in table '%s' on connection '%s'\n", celvar, table, connection);
202  res = -1;
203  break;
204  }
205 
206  /* NULL column entry means this isn't a column in the database */
207  entry->name = NULL;
208  entry->celname = (char *)entry + sizeof(*entry);
209  entry->filtervalue = (char *)entry + sizeof(*entry) + strlen(celvar) + 1;
210  strcpy(entry->celname, celvar);
211  strcpy(entry->filtervalue, var->value);
212 
213  AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
214  }
215  }
216 
217  while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
218  char *celvar = "", *staticvalue = "";
219 
220  SQLGetData(stmt, 4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
221 
222  /* Is there an alias for this column? */
223 
224  /* NOTE: This seems like a non-optimal parse method, but I'm going
225  * for user configuration readability, rather than fast parsing. We
226  * really don't parse this file all that often, anyway.
227  */
228  for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
229  if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, columnname) == 0) {
230  char *alias = ast_strdupa(var->name + 5);
231  celvar = ast_strip(alias);
232  ast_verb(3, "Found alias %s for column %s in %s@%s\n", celvar, columnname, tableptr->table, tableptr->connection);
233  break;
234  } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, columnname) == 0) {
235  char *item = ast_strdupa(var->name + 6);
236  item = ast_strip(item);
237  if (item[0] == '"' && item[strlen(item) - 1] == '"') {
238  /* Remove surrounding quotes */
239  item[strlen(item) - 1] = '\0';
240  item++;
241  }
242  staticvalue = item;
243  }
244  }
245 
246  entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1 + strlen(celvar) + 1 + strlen(staticvalue) + 1);
247  if (!entry) {
248  ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
249  res = -1;
250  break;
251  }
252  entry->name = (char *)entry + sizeof(*entry);
253  strcpy(entry->name, columnname);
254 
255  if (!ast_strlen_zero(celvar)) {
256  entry->celname = entry->name + strlen(columnname) + 1;
257  strcpy(entry->celname, celvar);
258  } else { /* Point to same place as the column name */
259  entry->celname = (char *)entry + sizeof(*entry);
260  }
261 
263  entry->staticvalue = entry->celname + strlen(entry->celname) + 1;
264  strcpy(entry->staticvalue, staticvalue);
265  }
266 
267  SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
268  SQLGetData(stmt, 7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
269  SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
270  SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
271  SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
272  SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
273 
274  /* Specification states that the octenlen should be the maximum number of bytes
275  * returned in a char or binary column, but it seems that some drivers just set
276  * it to NULL. (Bad Postgres! No biscuit!) */
277  if (entry->octetlen == 0)
278  entry->octetlen = entry->size;
279 
280  ast_verb(10, "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);
281  /* Insert column info into column list */
282  AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
283  res = 0;
284  }
285 
286  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
288 
289  if (AST_LIST_FIRST(&(tableptr->columns)))
291  else
292  ast_free(tableptr);
293  }
294  ast_config_destroy(cfg);
295  return res;
296 }
297 
298 static int free_config(void)
299 {
300  struct tables *table;
301  struct columns *entry;
302  while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
303  while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
304  ast_free(entry);
305  }
306  ast_free(table);
307  }
308  return 0;
309 }
310 
311 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
312 {
313  int res, i;
314  char *sql = data;
315  SQLHSTMT stmt;
316  SQLINTEGER nativeerror = 0, numfields = 0;
317  SQLSMALLINT diagbytes = 0;
318  unsigned char state[10], diagnostic[256];
319 
320  res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
321  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
322  ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
323  return NULL;
324  }
325 
326  res = ast_odbc_prepare(obj, stmt, sql);
327  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
328  ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
329  SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
330  for (i = 0; i < numfields; i++) {
331  SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
332  ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
333  if (i > 10) {
334  ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
335  break;
336  }
337  }
338  SQLFreeHandle (SQL_HANDLE_STMT, stmt);
339  return NULL;
340  }
341 
342  return stmt;
343 }
344 
345 #define LENGTHEN_BUF(size, var_sql) \
346  do { \
347  /* Lengthen buffer, if necessary */ \
348  if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
349  if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
350  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
351  ast_free(sql); \
352  ast_free(sql2); \
353  AST_RWLIST_UNLOCK(&odbc_tables); \
354  return; \
355  } \
356  } \
357  } while (0)
358 
359 #define LENGTHEN_BUF1(size) \
360  LENGTHEN_BUF(size, sql);
361 
362 #define LENGTHEN_BUF2(size) \
363  LENGTHEN_BUF(size, sql2);
364 
365 static void odbc_log(struct ast_event *event)
366 {
367  struct tables *tableptr;
368  struct columns *entry;
369  struct odbc_obj *obj;
370  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
371  char *tmp;
372  char colbuf[1024], *colptr;
373  SQLHSTMT stmt = NULL;
374  SQLLEN rows = 0;
375  struct ast_cel_event_record record = {
377  };
378 
379  if (ast_cel_fill_record(event, &record)) {
380  return;
381  }
382 
383  if (!sql || !sql2) {
384  if (sql)
385  ast_free(sql);
386  if (sql2)
387  ast_free(sql2);
388  return;
389  }
390 
392  ast_log(LOG_ERROR, "Unable to lock table list. Insert CEL(s) failed.\n");
393  ast_free(sql);
394  ast_free(sql2);
395  return;
396  }
397 
398  AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
399  char *separator = "";
400  ast_str_set(&sql, 0, "INSERT INTO %s (", tableptr->table);
401  ast_str_set(&sql2, 0, " VALUES (");
402 
403  /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
404  if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
405  ast_log(LOG_WARNING, "Unable to retrieve database handle for '%s:%s'. CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
406  continue;
407  }
408 
409  AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
410  int datefield = 0;
411  int unknown = 0;
412  if (strcasecmp(entry->celname, "eventtime") == 0) {
413  datefield = 1;
414  }
415 
416  /* Check if we have a similarly named variable */
417  if (entry->staticvalue) {
418  colptr = ast_strdupa(entry->staticvalue);
419  } else if (datefield) {
420  struct timeval date_tv = record.event_time;
421  struct ast_tm tm = { 0, };
422  ast_localtime(&date_tv, &tm, tableptr->usegmtime ? "UTC" : NULL);
423  /* SQL server 2008 added datetime2 and datetimeoffset data types, that
424  are reported to SQLColumns() as SQL_WVARCHAR, according to "Enhanced
425  Date/Time Type Behavior with Previous SQL Server Versions (ODBC)".
426  Here we format the event time with fraction seconds, so these new
427  column types will be set to high-precision event time. However, 'date'
428  and 'time' columns, also newly introduced, reported as SQL_WVARCHAR
429  too, and insertion of the value formatted here into these will fail.
430  This should be ok, however, as nobody is going to store just event
431  date or just time for CDR purposes.
432  */
433  ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S.%6q", &tm);
434  colptr = colbuf;
435  } else {
436  if (strcmp(entry->celname, "userdeftype") == 0) {
437  ast_copy_string(colbuf, record.user_defined_name, sizeof(colbuf));
438  } else if (strcmp(entry->celname, "cid_name") == 0) {
439  ast_copy_string(colbuf, record.caller_id_name, sizeof(colbuf));
440  } else if (strcmp(entry->celname, "cid_num") == 0) {
441  ast_copy_string(colbuf, record.caller_id_num, sizeof(colbuf));
442  } else if (strcmp(entry->celname, "cid_ani") == 0) {
443  ast_copy_string(colbuf, record.caller_id_ani, sizeof(colbuf));
444  } else if (strcmp(entry->celname, "cid_rdnis") == 0) {
445  ast_copy_string(colbuf, record.caller_id_rdnis, sizeof(colbuf));
446  } else if (strcmp(entry->celname, "cid_dnid") == 0) {
447  ast_copy_string(colbuf, record.caller_id_dnid, sizeof(colbuf));
448  } else if (strcmp(entry->celname, "exten") == 0) {
449  ast_copy_string(colbuf, record.extension, sizeof(colbuf));
450  } else if (strcmp(entry->celname, "context") == 0) {
451  ast_copy_string(colbuf, record.context, sizeof(colbuf));
452  } else if (strcmp(entry->celname, "channame") == 0) {
453  ast_copy_string(colbuf, record.channel_name, sizeof(colbuf));
454  } else if (strcmp(entry->celname, "appname") == 0) {
455  ast_copy_string(colbuf, record.application_name, sizeof(colbuf));
456  } else if (strcmp(entry->celname, "appdata") == 0) {
457  ast_copy_string(colbuf, record.application_data, sizeof(colbuf));
458  } else if (strcmp(entry->celname, "accountcode") == 0) {
459  ast_copy_string(colbuf, record.account_code, sizeof(colbuf));
460  } else if (strcmp(entry->celname, "peeraccount") == 0) {
461  ast_copy_string(colbuf, record.peer_account, sizeof(colbuf));
462  } else if (strcmp(entry->celname, "uniqueid") == 0) {
463  ast_copy_string(colbuf, record.unique_id, sizeof(colbuf));
464  } else if (strcmp(entry->celname, "linkedid") == 0) {
465  ast_copy_string(colbuf, record.linked_id, sizeof(colbuf));
466  } else if (strcmp(entry->celname, "userfield") == 0) {
467  ast_copy_string(colbuf, record.user_field, sizeof(colbuf));
468  } else if (strcmp(entry->celname, "peer") == 0) {
469  ast_copy_string(colbuf, record.peer, sizeof(colbuf));
470  } else if (strcmp(entry->celname, "amaflags") == 0) {
471  snprintf(colbuf, sizeof(colbuf), "%u", record.amaflag);
472  } else if (strcmp(entry->celname, "extra") == 0) {
473  ast_copy_string(colbuf, record.extra, sizeof(colbuf));
474  } else if (strcmp(entry->celname, "eventtype") == 0) {
475  snprintf(colbuf, sizeof(colbuf), "%u", record.event_type);
476  } else {
477  colbuf[0] = 0;
478  unknown = 1;
479  }
480  colptr = colbuf;
481  }
482 
483  if (colptr && !unknown) {
484  /* Check first if the column filters this entry. Note that this
485  * is very specifically NOT ast_strlen_zero(), because the filter
486  * could legitimately specify that the field is blank, which is
487  * different from the field being unspecified (NULL). */
488  if (entry->filtervalue && strcasecmp(colptr, entry->filtervalue) != 0) {
489  ast_verb(4, "CEL column '%s' with value '%s' does not match filter of"
490  " '%s'. Cancelling this CEL.\n",
491  entry->celname, colptr, entry->filtervalue);
492  goto early_release;
493  }
494 
495  /* Only a filter? */
496  if (ast_strlen_zero(entry->name))
497  continue;
498 
499  LENGTHEN_BUF1(strlen(entry->name));
500 
501  switch (entry->type) {
502  case SQL_CHAR:
503  case SQL_VARCHAR:
504  case SQL_LONGVARCHAR:
505 #ifdef HAVE_ODBC_WCHAR
506  case SQL_WCHAR:
507  case SQL_WVARCHAR:
508  case SQL_WLONGVARCHAR:
509 #endif
510  case SQL_BINARY:
511  case SQL_VARBINARY:
512  case SQL_LONGVARBINARY:
513  case SQL_GUID:
514  /* For these two field names, get the rendered form, instead of the raw
515  * form (but only when we're dealing with a character-based field).
516  */
517  if (strcasecmp(entry->name, "eventtype") == 0) {
518  const char *event_name;
519 
520  event_name = (!cel_show_user_def
521  && record.event_type == AST_CEL_USER_DEFINED)
522  ? record.user_defined_name : record.event_name;
523  snprintf(colbuf, sizeof(colbuf), "%s", event_name);
524  }
525 
526  /* Truncate too-long fields */
527  if (entry->type != SQL_GUID) {
528  if (strlen(colptr) > entry->octetlen) {
529  colptr[entry->octetlen] = '\0';
530  }
531  }
532 
533  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
534  LENGTHEN_BUF2(strlen(colptr));
535 
536  /* Encode value, with escaping */
537  ast_str_append(&sql2, 0, "%s'", separator);
538  for (tmp = colptr; *tmp; tmp++) {
539  if (*tmp == '\'') {
540  ast_str_append(&sql2, 0, "''");
541  } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
542  ast_str_append(&sql2, 0, "\\\\");
543  } else {
544  ast_str_append(&sql2, 0, "%c", *tmp);
545  }
546  }
547  ast_str_append(&sql2, 0, "'");
548  break;
549  case SQL_TYPE_DATE:
550  if (ast_strlen_zero(colptr)) {
551  continue;
552  } else {
553  int year = 0, month = 0, day = 0;
554  if (strcasecmp(entry->name, "eventdate") == 0) {
555  struct ast_tm tm;
556  ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
557  year = tm.tm_year + 1900;
558  month = tm.tm_mon + 1;
559  day = tm.tm_mday;
560  } else {
561  if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
562  month <= 0 || month > 12 || day < 0 || day > 31 ||
563  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
564  (month == 2 && year % 400 == 0 && day > 29) ||
565  (month == 2 && year % 100 == 0 && day > 28) ||
566  (month == 2 && year % 4 == 0 && day > 29) ||
567  (month == 2 && year % 4 != 0 && day > 28)) {
568  ast_log(LOG_WARNING, "CEL variable %s is not a valid date ('%s').\n", entry->name, colptr);
569  continue;
570  }
571 
572  if (year > 0 && year < 100) {
573  year += 2000;
574  }
575  }
576 
577  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
578  LENGTHEN_BUF2(17);
579  ast_str_append(&sql2, 0, "%s{d '%04d-%02d-%02d'}", separator, year, month, day);
580  }
581  break;
582  case SQL_TYPE_TIME:
583  if (ast_strlen_zero(colptr)) {
584  continue;
585  } else {
586  int hour = 0, minute = 0, second = 0;
587  if (strcasecmp(entry->name, "eventdate") == 0) {
588  struct ast_tm tm;
589  ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
590  hour = tm.tm_hour;
591  minute = tm.tm_min;
592  second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
593  } else {
594  int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
595 
596  if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > (tableptr->allowleapsec ? 60 : 59)) {
597  ast_log(LOG_WARNING, "CEL variable %s is not a valid time ('%s').\n", entry->name, colptr);
598  continue;
599  }
600  }
601 
602  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
603  LENGTHEN_BUF2(15);
604  ast_str_append(&sql2, 0, "%s{t '%02d:%02d:%02d'}", separator, hour, minute, second);
605  }
606  break;
607  case SQL_TYPE_TIMESTAMP:
608  case SQL_TIMESTAMP:
609  if (ast_strlen_zero(colptr)) {
610  continue;
611  } else {
612  if (datefield) {
613  /*
614  * We've already properly formatted the timestamp so there's no need
615  * to parse it and re-format it.
616  */
617  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
618  LENGTHEN_BUF2(27);
619  ast_str_append(&sql2, 0, "%s{ts '%s'}", separator, colptr);
620  } else {
621  int year = 0, month = 0, day = 0, hour = 0, minute = 0;
622  /* MUST use double for microsecond precision */
623  double second = 0.0;
624  if (strcasecmp(entry->name, "eventdate") == 0) {
625  /*
626  * There doesn't seem to be any reference to 'eventdate' anywhere
627  * other than in this module. It should be considered for removal
628  * at a later date.
629  */
630  struct ast_tm tm;
631  ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
632  year = tm.tm_year + 1900;
633  month = tm.tm_mon + 1;
634  day = tm.tm_mday;
635  hour = tm.tm_hour;
636  minute = tm.tm_min;
637  second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
638  second += (tm.tm_usec / 1000000.0);
639  } else {
640  /*
641  * If we're here, the data to be inserted MAY be a timestamp
642  * but the column is. We parse as much as we can.
643  */
644  int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%lf", &year, &month, &day, &hour, &minute, &second);
645 
646  if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
647  month <= 0 || month > 12 || day < 0 || day > 31 ||
648  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
649  (month == 2 && year % 400 == 0 && day > 29) ||
650  (month == 2 && year % 100 == 0 && day > 28) ||
651  (month == 2 && year % 4 == 0 && day > 29) ||
652  (month == 2 && year % 4 != 0 && day > 28) ||
653  hour > 23 || minute > 59 || ((int)floor(second)) > (tableptr->allowleapsec ? 60 : 59) ||
654  hour < 0 || minute < 0 || ((int)floor(second)) < 0) {
655  ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
656  continue;
657  }
658 
659  if (year > 0 && year < 100) {
660  year += 2000;
661  }
662  }
663 
664  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
665  LENGTHEN_BUF2(27);
666  ast_str_append(&sql2, 0, "%s{ts '%04d-%02d-%02d %02d:%02d:%09.6lf'}", separator, year, month, day, hour, minute, second);
667  }
668  }
669  break;
670  case SQL_INTEGER:
671  {
672  int integer = 0;
673  if (sscanf(colptr, "%30d", &integer) != 1) {
674  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
675  continue;
676  }
677 
678  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
679  LENGTHEN_BUF2(12);
680  ast_str_append(&sql2, 0, "%s%d", separator, integer);
681  }
682  break;
683  case SQL_BIGINT:
684  {
685  long long integer = 0;
686  int ret;
687  if ((ret = sscanf(colptr, "%30lld", &integer)) != 1) {
688  ast_log(LOG_WARNING, "CEL variable %s is not an integer. (%d - '%s')\n", entry->name, ret, colptr);
689  continue;
690  }
691 
692  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
693  LENGTHEN_BUF2(24);
694  ast_str_append(&sql2, 0, "%s%lld", separator, integer);
695  }
696  break;
697  case SQL_SMALLINT:
698  {
699  short integer = 0;
700  if (sscanf(colptr, "%30hd", &integer) != 1) {
701  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
702  continue;
703  }
704 
705  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
706  LENGTHEN_BUF2(7);
707  ast_str_append(&sql2, 0, "%s%d", separator, integer);
708  }
709  break;
710  case SQL_TINYINT:
711  {
712  signed char integer = 0;
713  if (sscanf(colptr, "%30hhd", &integer) != 1) {
714  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
715  continue;
716  }
717 
718  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
719  LENGTHEN_BUF2(4);
720  ast_str_append(&sql2, 0, "%s%d", separator, integer);
721  }
722  break;
723  case SQL_BIT:
724  {
725  signed char integer = 0;
726  if (sscanf(colptr, "%30hhd", &integer) != 1) {
727  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
728  continue;
729  }
730  if (integer != 0)
731  integer = 1;
732 
733  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
734  LENGTHEN_BUF2(2);
735  ast_str_append(&sql2, 0, "%s%d", separator, integer);
736  }
737  break;
738  case SQL_NUMERIC:
739  case SQL_DECIMAL:
740  {
741  double number = 0.0;
742  if (sscanf(colptr, "%30lf", &number) != 1) {
743  ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
744  continue;
745  }
746 
747  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
748  LENGTHEN_BUF2(entry->decimals + 2);
749  ast_str_append(&sql2, 0, "%s%*.*lf", separator, entry->decimals, entry->radix, number);
750  }
751  break;
752  case SQL_FLOAT:
753  case SQL_REAL:
754  case SQL_DOUBLE:
755  {
756  double number = 0.0;
757  if (sscanf(colptr, "%30lf", &number) != 1) {
758  ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
759  continue;
760  }
761 
762  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
763  LENGTHEN_BUF2(entry->decimals);
764  ast_str_append(&sql2, 0, "%s%lf", separator, number);
765  }
766  break;
767  default:
768  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);
769  continue;
770  }
771  separator = ", ";
772  }
773  }
774 
775  /* Concatenate the two constructed buffers */
777  ast_str_append(&sql, 0, ")");
778  ast_str_append(&sql2, 0, ")");
779  ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
780 
781  ast_debug(3, "Executing SQL statement: [%s]\n", ast_str_buffer(sql));
783  if (stmt) {
784  SQLRowCount(stmt, &rows);
785  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
786  }
787  if (rows == 0) {
788  ast_log(LOG_WARNING, "Insert failed on '%s:%s'. CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
789  }
790 early_release:
792  }
794 
795  /* Next time, just allocate buffers that are that big to start with. */
796  if (ast_str_strlen(sql) > maxsize) {
797  maxsize = ast_str_strlen(sql);
798  }
799  if (ast_str_strlen(sql2) > maxsize2) {
800  maxsize2 = ast_str_strlen(sql2);
801  }
802 
803  ast_free(sql);
804  ast_free(sql2);
805 }
806 
807 static int unload_module(void)
808 {
810  ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
811  return -1;
812  }
813 
815  free_config();
818 
819  return 0;
820 }
821 
822 static int load_module(void)
823 {
825 
827  ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
829  }
830  load_config();
833  ast_log(LOG_ERROR, "Unable to subscribe to CEL events\n");
834  free_config();
836  }
838 }
839 
840 static int reload(void)
841 {
843  ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
845  }
846 
847  free_config();
848  load_config();
851 }
852 
854  .support_level = AST_MODULE_SUPPORT_CORE,
855  .load = load_module,
856  .unload = unload_module,
857  .reload = reload,
858  .load_pri = AST_MODPRI_CDR_DRIVER,
859  .requires = "cel,res_odbc",
860 );
const char * account_code
Definition: cel.h:161
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
const char * caller_id_name
Definition: cel.h:151
Helper struct for getting the fields out of a CEL event.
Definition: cel.h:136
SQLSMALLINT radix
const char * linked_id
Definition: cel.h:164
#define AST_RWLIST_HEAD_DESTROY(head)
Destroys an rwlist head structure.
Definition: linkedlists.h:666
An event.
Definition: event.c:81
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 tm_usec
Definition: localtime.h:48
#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 SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
Definition: cel_odbc.c:311
static unsigned char cel_show_user_def
Definition: cel_odbc.c:61
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1216
Call Event Logging API.
Time-related functions and macros.
const char * user_defined_name
Definition: cel.h:150
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1740
#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
const char * application_data
Definition: cel.h:160
const char * application_name
Definition: cel.h:159
#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
static void odbc_log(struct ast_event *event)
Definition: cel_odbc.c:365
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 var
Definition: ast_expr2f.c:614
const char * extension
Definition: cel.h:156
#define LENGTHEN_BUF1(size)
Definition: cel_odbc.c:359
Definition: astman.c:222
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
const char * caller_id_num
Definition: cel.h:152
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3328
#define AST_RWLIST_HEAD_INIT(head)
Initializes an rwlist head structure.
Definition: linkedlists.h:638
SQLINTEGER octetlen
#define NULL
Definition: resample.c:96
unsigned int allowleapsec
Definition: cel_odbc.c:84
const char * extra
Definition: cel.h:168
int tm_year
Definition: localtime.h:41
#define ast_verb(level,...)
Definition: logger.h:463
#define ast_strlen_zero(foo)
Definition: strings.h:52
char * celname
Definition: cel_odbc.c:68
Number structure.
Definition: app_followme.c:154
static char * table
Definition: cdr_odbc.c:58
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.
static int free_config(void)
Definition: cel_odbc.c:298
#define CEL_SHOW_USERDEF_DEFAULT
show_user_def is off by default
Definition: cel_odbc.c:58
#define LENGTHEN_BUF2(size)
Definition: cel_odbc.c:362
General Asterisk PBX channel definitions.
SQLINTEGER size
ODBC container.
Definition: res_odbc.h:46
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:219
const char * context
Definition: cel.h:157
int tm_mon
Definition: localtime.h:40
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: extconf.c:1290
uint32_t version
struct ABI version
Definition: cel.h:146
#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
int tm_mday
Definition: localtime.h:39
#define CONFIG
Definition: cel_odbc.c:53
ODBC resource manager.
AST_LIST_HEAD_NOLOCK(contactliststruct, contact)
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition: cel.c:1728
char * connection
enum ast_cel_event_type event_type
Definition: cel.h:147
#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
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
const char * caller_id_rdnis
Definition: cel.h:154
const char * peer
Definition: cel.h:167
#define ODBC_BACKEND_NAME
Definition: cel_odbc.c:55
#define LOG_NOTICE
Definition: logger.h:263
#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
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
#define ast_odbc_request_obj(a, b)
Definition: res_odbc.h:122
static struct ast_codec unknown
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:843
static int reload(void)
Definition: cel_odbc.c:840
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static int unload_module(void)
Definition: cel_odbc.c:807
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
const char * caller_id_ani
Definition: cel.h:153
int tm_hour
Definition: localtime.h:38
const char * user_field
Definition: cel.h:166
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:141
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",)
int tm_sec
Definition: localtime.h:36
const char * peer_account
Definition: cel.h:162
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
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
const char * unique_id
Definition: cel.h:163
struct tables::mysql_columns columns
static int load_config(void)
Definition: cel_odbc.c:91
char * staticvalue
a user-defined event, the event name field should be set
Definition: cel.h:69
Definition: search.h:40
static int load_module(void)
Definition: cel_odbc.c:822
const char * caller_id_dnid
Definition: cel.h:155
const char * channel_name
Definition: cel.h:158
const char * event_name
Definition: cel.h:149
static int maxsize
Definition: cel_odbc.c:64
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
static int maxsize2
Definition: cel_odbc.c:64
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
static int usegmtime
Definition: cdr_csv.c:54
struct timeval event_time
Definition: cel.h:148
INT32 integer
Definition: lpc10.h:80
int tm_min
Definition: localtime.h:37
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:819
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620
unsigned int usegmtime