Asterisk - The Open Source Telephony Project GIT-master-a358458
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"
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. */
61static unsigned char cel_show_user_def;
62
63/* Optimization to reduce number of memory allocations */
64static int maxsize = 512, maxsize2 = 512;
65
66struct 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
80struct tables {
81 char *connection;
82 char *table;
83 unsigned int usegmtime:1;
84 unsigned int allowleapsec:1;
87};
88
90
91static 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);
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 }
295 return res;
296}
297
298static int free_config(void)
299{
300 struct tables *table;
301 struct columns *entry;
303 while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
305 }
307 }
308 return 0;
309}
310
311static 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
365static 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 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 */
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));
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 }
791early_release:
793 }
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
808static int unload_module(void)
809{
811 ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
812 return -1;
813 }
814
816 free_config();
819
820 return 0;
821}
822
823static int load_module(void)
824{
826
828 ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
830 }
831 load_config();
834 ast_log(LOG_ERROR, "Unable to subscribe to CEL events\n");
835 free_config();
837 }
839}
840
841static int reload(void)
842{
844 ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
846 }
847
848 free_config();
849 load_config();
852}
853
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",
#define var
Definition: ast_expr2f.c:605
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:1769
@ 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:821
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:143
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1781
#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:823
static int unload_module(void)
Definition: cel_odbc.c:808
static int reload(void)
Definition: cel_odbc.c:841
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:3326
#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:783
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_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_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
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
int ast_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
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
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:1139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199
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:659
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
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
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:138
const char * caller_id_dnid
Definition: cel.h:157
const char * application_data
Definition: cel.h:162
const char * account_code
Definition: cel.h:163
const char * caller_id_rdnis
Definition: cel.h:156
const char * extra
Definition: cel.h:170
const char * extension
Definition: cel.h:158
const char * caller_id_num
Definition: cel.h:154
const char * channel_name
Definition: cel.h:160
const char * linked_id
Definition: cel.h:166
const char * peer_account
Definition: cel.h:164
const char * peer
Definition: cel.h:169
enum ast_cel_event_type event_type
Definition: cel.h:149
const char * unique_id
Definition: cel.h:165
const char * user_defined_name
Definition: cel.h:152
const char * context
Definition: cel.h:159
const char * application_name
Definition: cel.h:161
struct timeval event_time
Definition: cel.h:150
uint32_t version
struct ABI version
Definition: cel.h:148
const char * user_field
Definition: cel.h:168
const char * caller_id_ani
Definition: cel.h:155
const char * caller_id_name
Definition: cel.h:153
const char * event_name
Definition: cel.h:151
An event.
Definition: event.c:81
Structure used to handle boolean flags.
Definition: utils.h:199
Support for dynamic strings.
Definition: strings.h:623
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.
char * filtervalue
SQLSMALLINT decimals
SQLSMALLINT radix
char * staticvalue
struct columns::@4 list
SQLINTEGER octetlen
SQLINTEGER size
char * celname
Definition: cel_odbc.c:68
struct columns::@108 list
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.