Asterisk - The Open Source Telephony Project  GIT-master-a24979a
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 
262  if (!ast_strlen_zero(staticvalue)) {
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)))
290  AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
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;
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 );
#define var
Definition: ast_expr2f.c:614
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
static char * table
Definition: cdr_odbc.c:55
Call Event Logging API.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition: cel.c:1728
@ AST_CEL_USER_DEFINED
a user-defined event, the event name field should be set
Definition: cel.h:69
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_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:141
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1740
#define LENGTHEN_BUF2(size)
Definition: cel_odbc.c:362
static int maxsize2
Definition: cel_odbc.c:64
static int maxsize
Definition: cel_odbc.c:64
#define CEL_SHOW_USERDEF_DEFAULT
show_user_def is off by default
Definition: cel_odbc.c:58
static int free_config(void)
Definition: cel_odbc.c:298
static void odbc_log(struct ast_event *event)
Definition: cel_odbc.c:365
static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
Definition: cel_odbc.c:311
#define CONFIG
Definition: cel_odbc.c:53
#define ODBC_BACKEND_NAME
Definition: cel_odbc.c:55
static unsigned char cel_show_user_def
Definition: cel_odbc.c:61
static int load_module(void)
Definition: cel_odbc.c:822
static int unload_module(void)
Definition: cel_odbc.c:807
static int reload(void)
Definition: cel_odbc.c:840
static int load_config(void)
Definition: cel_odbc.c:91
#define LENGTHEN_BUF1(size)
Definition: cel_odbc.c:359
General Asterisk PBX channel definitions.
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3327
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:768
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define ast_verb(level,...)
#define LOG_NOTICE
#define LOG_WARNING
A set of macros to manage forward-linked lists.
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:78
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:52
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:151
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_RWLIST_HEAD_INIT(head)
Initializes an rwlist head structure.
Definition: linkedlists.h:639
#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:333
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:844
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:741
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
#define AST_RWLIST_ENTRY
Definition: linkedlists.h:415
#define AST_RWLIST_HEAD_DESTROY(head)
Destroys an rwlist head structure.
Definition: linkedlists.h:667
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:421
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
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
Asterisk locking-related definitions:
INT32 integer
Definition: lpc10.h:80
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:317
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODPRI_CDR_DRIVER
Definition: module.h:331
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
AST_LIST_HEAD_NOLOCK(contactliststruct, contact)
ODBC resource manager.
int ast_odbc_prepare(struct odbc_obj *obj, SQLHSTMT *stmt, const char *sql)
Prepares a SQL query on a statement.
Definition: res_odbc.c:454
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
Definition: res_odbc.c:804
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:833
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:398
#define ast_odbc_request_obj(name, check)
Get a ODBC connection object.
Definition: res_odbc.h:120
#define NULL
Definition: resample.c:96
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:1117
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:739
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: main/utils.c:2097
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:640
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:1091
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:711
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:406
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
Helper struct for getting the fields out of a CEL event.
Definition: cel.h:136
const char * caller_id_dnid
Definition: cel.h:155
const char * application_data
Definition: cel.h:160
const char * account_code
Definition: cel.h:161
const char * caller_id_rdnis
Definition: cel.h:154
const char * extra
Definition: cel.h:168
const char * extension
Definition: cel.h:156
const char * caller_id_num
Definition: cel.h:152
const char * channel_name
Definition: cel.h:158
const char * linked_id
Definition: cel.h:164
const char * peer_account
Definition: cel.h:162
const char * peer
Definition: cel.h:167
enum ast_cel_event_type event_type
Definition: cel.h:147
const char * unique_id
Definition: cel.h:163
const char * user_defined_name
Definition: cel.h:150
const char * context
Definition: cel.h:157
const char * application_name
Definition: cel.h:159
struct timeval event_time
Definition: cel.h:148
uint32_t version
struct ABI version
Definition: cel.h:146
const char * user_field
Definition: cel.h:166
const char * caller_id_ani
Definition: cel.h:153
const char * caller_id_name
Definition: cel.h:151
const char * event_name
Definition: cel.h:149
An event.
Definition: event.c:81
Structure used to handle boolean flags.
Definition: utils.h:199
Support for dynamic strings.
Definition: strings.h:604
int tm_mday
Definition: localtime.h:39
int tm_sec
Definition: localtime.h:36
int tm_hour
Definition: localtime.h:38
int tm_min
Definition: localtime.h:37
int tm_year
Definition: localtime.h:41
int tm_mon
Definition: localtime.h:40
int tm_usec
Definition: localtime.h:48
Structure for variables, used for configurations and for channel variables.
struct columns::@99 list
char * filtervalue
SQLSMALLINT decimals
SQLSMALLINT radix
char * staticvalue
struct columns::@4 list
SQLINTEGER octetlen
SQLINTEGER size
char * celname
Definition: cel_odbc.c:68
SQLSMALLINT nullable
Definition: search.h:40
Definition: astman.c:222
Number structure.
Definition: app_followme.c:154
ODBC container.
Definition: res_odbc.h:46
SQLHDBC con
Definition: res_odbc.h:47
char * connection
unsigned int usegmtime
char * table
struct tables::@5 list
unsigned int allowleapsec
Definition: cel_odbc.c:84
struct tables::mysql_columns columns
static struct aco_type item
Definition: test_config.c:1463
static struct ast_codec unknown
Time-related functions and macros.