Asterisk - The Open Source Telephony Project  21.4.1
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;
77  AST_LIST_ENTRY(columns) list;
78 };
79 
80 struct tables {
81  char *connection;
82  char *table;
83  unsigned int usegmtime:1;
84  unsigned int allowleapsec:1;
85  AST_LIST_HEAD_NOLOCK(odbc_columns, columns) columns;
86  AST_RWLIST_ENTRY(tables) list;
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;
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 
391  if (AST_RWLIST_RDLOCK(&odbc_tables)) {
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  case SQL_DATETIME:
610  if (ast_strlen_zero(colptr)) {
611  continue;
612  } else {
613  if (datefield) {
614  /*
615  * We've already properly formatted the timestamp so there's no need
616  * to parse it and re-format it.
617  */
618  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
619  LENGTHEN_BUF2(27);
620  ast_str_append(&sql2, 0, "%s{ts '%s'}", separator, colptr);
621  } else {
622  int year = 0, month = 0, day = 0, hour = 0, minute = 0;
623  /* MUST use double for microsecond precision */
624  double second = 0.0;
625  if (strcasecmp(entry->name, "eventdate") == 0) {
626  /*
627  * There doesn't seem to be any reference to 'eventdate' anywhere
628  * other than in this module. It should be considered for removal
629  * at a later date.
630  */
631  struct ast_tm tm;
632  ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
633  year = tm.tm_year + 1900;
634  month = tm.tm_mon + 1;
635  day = tm.tm_mday;
636  hour = tm.tm_hour;
637  minute = tm.tm_min;
638  second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
639  second += (tm.tm_usec / 1000000.0);
640  } else {
641  /*
642  * If we're here, the data to be inserted MAY be a timestamp
643  * but the column is. We parse as much as we can.
644  */
645  int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%lf", &year, &month, &day, &hour, &minute, &second);
646 
647  if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
648  month <= 0 || month > 12 || day < 0 || day > 31 ||
649  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
650  (month == 2 && year % 400 == 0 && day > 29) ||
651  (month == 2 && year % 100 == 0 && day > 28) ||
652  (month == 2 && year % 4 == 0 && day > 29) ||
653  (month == 2 && year % 4 != 0 && day > 28) ||
654  hour > 23 || minute > 59 || ((int)floor(second)) > (tableptr->allowleapsec ? 60 : 59) ||
655  hour < 0 || minute < 0 || ((int)floor(second)) < 0) {
656  ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
657  continue;
658  }
659 
660  if (year > 0 && year < 100) {
661  year += 2000;
662  }
663  }
664 
665  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
666  LENGTHEN_BUF2(27);
667  ast_str_append(&sql2, 0, "%s{ts '%04d-%02d-%02d %02d:%02d:%09.6lf'}", separator, year, month, day, hour, minute, second);
668  }
669  }
670  break;
671  case SQL_INTEGER:
672  {
673  int integer = 0;
674  if (sscanf(colptr, "%30d", &integer) != 1) {
675  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
676  continue;
677  }
678 
679  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
680  LENGTHEN_BUF2(12);
681  ast_str_append(&sql2, 0, "%s%d", separator, integer);
682  }
683  break;
684  case SQL_BIGINT:
685  {
686  long long integer = 0;
687  int ret;
688  if ((ret = sscanf(colptr, "%30lld", &integer)) != 1) {
689  ast_log(LOG_WARNING, "CEL variable %s is not an integer. (%d - '%s')\n", entry->name, ret, colptr);
690  continue;
691  }
692 
693  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
694  LENGTHEN_BUF2(24);
695  ast_str_append(&sql2, 0, "%s%lld", separator, integer);
696  }
697  break;
698  case SQL_SMALLINT:
699  {
700  short integer = 0;
701  if (sscanf(colptr, "%30hd", &integer) != 1) {
702  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
703  continue;
704  }
705 
706  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
707  LENGTHEN_BUF2(7);
708  ast_str_append(&sql2, 0, "%s%d", separator, integer);
709  }
710  break;
711  case SQL_TINYINT:
712  {
713  signed char integer = 0;
714  if (sscanf(colptr, "%30hhd", &integer) != 1) {
715  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
716  continue;
717  }
718 
719  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
720  LENGTHEN_BUF2(4);
721  ast_str_append(&sql2, 0, "%s%d", separator, integer);
722  }
723  break;
724  case SQL_BIT:
725  {
726  signed char integer = 0;
727  if (sscanf(colptr, "%30hhd", &integer) != 1) {
728  ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
729  continue;
730  }
731  if (integer != 0)
732  integer = 1;
733 
734  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
735  LENGTHEN_BUF2(2);
736  ast_str_append(&sql2, 0, "%s%d", separator, integer);
737  }
738  break;
739  case SQL_NUMERIC:
740  case SQL_DECIMAL:
741  {
742  double number = 0.0;
743  if (sscanf(colptr, "%30lf", &number) != 1) {
744  ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
745  continue;
746  }
747 
748  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
749  LENGTHEN_BUF2(entry->decimals + 2);
750  ast_str_append(&sql2, 0, "%s%*.*lf", separator, entry->decimals, entry->radix, number);
751  }
752  break;
753  case SQL_FLOAT:
754  case SQL_REAL:
755  case SQL_DOUBLE:
756  {
757  double number = 0.0;
758  if (sscanf(colptr, "%30lf", &number) != 1) {
759  ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
760  continue;
761  }
762 
763  ast_str_append(&sql, 0, "%s%s", separator, entry->name);
764  LENGTHEN_BUF2(entry->decimals);
765  ast_str_append(&sql2, 0, "%s%lf", separator, number);
766  }
767  break;
768  default:
769  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);
770  continue;
771  }
772  separator = ", ";
773  }
774  }
775 
776  /* Concatenate the two constructed buffers */
777  LENGTHEN_BUF1(ast_str_strlen(sql2));
778  ast_str_append(&sql, 0, ")");
779  ast_str_append(&sql2, 0, ")");
780  ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
781 
782  ast_debug(3, "Executing SQL statement: [%s]\n", ast_str_buffer(sql));
783  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, ast_str_buffer(sql));
784  if (stmt) {
785  SQLRowCount(stmt, &rows);
786  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
787  }
788  if (rows == 0) {
789  ast_log(LOG_WARNING, "Insert failed on '%s:%s'. CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
790  }
791 early_release:
793  }
794  AST_RWLIST_UNLOCK(&odbc_tables);
795 
796  /* Next time, just allocate buffers that are that big to start with. */
797  if (ast_str_strlen(sql) > maxsize) {
798  maxsize = ast_str_strlen(sql);
799  }
800  if (ast_str_strlen(sql2) > maxsize2) {
801  maxsize2 = ast_str_strlen(sql2);
802  }
803 
804  ast_free(sql);
805  ast_free(sql2);
806 }
807 
808 static int unload_module(void)
809 {
810  if (AST_RWLIST_WRLOCK(&odbc_tables)) {
811  ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
812  return -1;
813  }
814 
815  ast_cel_backend_unregister(ODBC_BACKEND_NAME);
816  free_config();
817  AST_RWLIST_UNLOCK(&odbc_tables);
818  AST_RWLIST_HEAD_DESTROY(&odbc_tables);
819 
820  return 0;
821 }
822 
823 static int load_module(void)
824 {
825  AST_RWLIST_HEAD_INIT(&odbc_tables);
826 
827  if (AST_RWLIST_WRLOCK(&odbc_tables)) {
828  ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
830  }
831  load_config();
832  AST_RWLIST_UNLOCK(&odbc_tables);
833  if (ast_cel_backend_register(ODBC_BACKEND_NAME, odbc_log)) {
834  ast_log(LOG_ERROR, "Unable to subscribe to CEL events\n");
835  free_config();
837  }
839 }
840 
841 static int reload(void)
842 {
843  if (AST_RWLIST_WRLOCK(&odbc_tables)) {
844  ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
846  }
847 
848  free_config();
849  load_config();
850  AST_RWLIST_UNLOCK(&odbc_tables);
852 }
853 
854 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "ODBC CEL backend",
855  .support_level = AST_MODULE_SUPPORT_CORE,
856  .load = load_module,
857  .unload = unload_module,
858  .reload = reload,
859  .load_pri = AST_MODPRI_CDR_DRIVER,
860  .requires = "cel,res_odbc",
861 );
SQLHDBC con
Definition: res_odbc.h:47
struct ast_variable * next
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
Helper struct for getting the fields out of a CEL event.
Definition: cel.h:138
#define AST_RWLIST_HEAD_DESTROY(head)
Destroys an rwlist head structure.
Definition: linkedlists.h:667
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:421
int tm_usec
Definition: localtime.h:48
static unsigned char cel_show_user_def
Definition: cel_odbc.c:61
Call Event Logging API.
Time-related functions and macros.
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1781
#define ast_odbc_request_obj(name, check)
Get a ODBC connection object.
Definition: res_odbc.h:120
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:78
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
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_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:52
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:1139
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3326
#define AST_RWLIST_HEAD_INIT(head)
Initializes an rwlist head structure.
Definition: linkedlists.h:639
int tm_year
Definition: localtime.h:41
#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
Number structure.
Definition: app_followme.c:154
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:1113
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
#define CEL_SHOW_USERDEF_DEFAULT
show_user_def is off by default
Definition: cel_odbc.c:58
General Asterisk PBX channel definitions.
ODBC container.
Definition: res_odbc.h:46
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
int tm_mon
Definition: localtime.h:40
uint32_t version
struct ABI version
Definition: cel.h:148
A set of macros to manage forward-linked lists.
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
int tm_mday
Definition: localtime.h:39
ODBC resource manager.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition: cel.c:1769
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
Definition: linkedlists.h:225
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
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: utils.c:2199
Support for dynamic strings.
Definition: strings.h:623
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
int tm_hour
Definition: localtime.h:38
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:143
Structure used to handle boolean flags.
Definition: utils.h:199
int tm_sec
Definition: localtime.h:36
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
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
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
a user-defined event, the event name field should be set
Definition: cel.h:69
Definition: search.h:40
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:151
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
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
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
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:821
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659