Asterisk - The Open Source Telephony Project GIT-master-4c84066
Loading...
Searching...
No Matches
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 (!SQL_SUCCEEDED(res)) {
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 (!SQL_SUCCEEDED(res)) {
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;
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#define LENGTHEN_BUF(size, var_sql) \
312 do { \
313 /* Lengthen buffer, if necessary */ \
314 if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
315 if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
316 ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
317 ast_free(sql); \
318 ast_free(sql2); \
319 AST_RWLIST_UNLOCK(&odbc_tables); \
320 return; \
321 } \
322 } \
323 } while (0)
324
325#define LENGTHEN_BUF1(size) \
326 LENGTHEN_BUF(size, sql);
327
328#define LENGTHEN_BUF2(size) \
329 LENGTHEN_BUF(size, sql2);
330
331static void odbc_log(struct ast_event *event)
332{
333 struct tables *tableptr;
334 struct columns *entry;
335 struct odbc_obj *obj;
336 struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
337 char *tmp;
338 char colbuf[1024], *colptr;
339 SQLHSTMT stmt = NULL;
340 int res;
341 struct ast_cel_event_record record = {
343 };
344
345 if (ast_cel_fill_record(event, &record)) {
346 return;
347 }
348
349 if (!sql || !sql2) {
350 if (sql)
351 ast_free(sql);
352 if (sql2)
353 ast_free(sql2);
354 return;
355 }
356
358 ast_log(LOG_ERROR, "Unable to lock table list. Insert CEL(s) failed.\n");
359 ast_free(sql);
360 ast_free(sql2);
361 return;
362 }
363
364 AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
365 char *separator = "";
366 ast_str_set(&sql, 0, "INSERT INTO %s (", tableptr->table);
367 ast_str_set(&sql2, 0, " VALUES (");
368
369 /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
370 if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
371 ast_log(LOG_WARNING, "Unable to retrieve database handle for '%s:%s'. CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
372 continue;
373 }
374
375 AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
376 int datefield = 0;
377 int unknown = 0;
378 if (strcasecmp(entry->celname, "eventtime") == 0) {
379 datefield = 1;
380 }
381
382 /* Check if we have a similarly named variable */
383 if (entry->staticvalue) {
384 colptr = ast_strdupa(entry->staticvalue);
385 } else if (datefield) {
386 struct timeval date_tv = record.event_time;
387 struct ast_tm tm = { 0, };
388 ast_localtime(&date_tv, &tm, tableptr->usegmtime ? "UTC" : NULL);
389 /* SQL server 2008 added datetime2 and datetimeoffset data types, that
390 are reported to SQLColumns() as SQL_WVARCHAR, according to "Enhanced
391 Date/Time Type Behavior with Previous SQL Server Versions (ODBC)".
392 Here we format the event time with fraction seconds, so these new
393 column types will be set to high-precision event time. However, 'date'
394 and 'time' columns, also newly introduced, reported as SQL_WVARCHAR
395 too, and insertion of the value formatted here into these will fail.
396 This should be ok, however, as nobody is going to store just event
397 date or just time for CDR purposes.
398 */
399 ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S.%6q", &tm);
400 colptr = colbuf;
401 } else {
402 if (strcmp(entry->celname, "userdeftype") == 0) {
403 ast_copy_string(colbuf, record.user_defined_name, sizeof(colbuf));
404 } else if (strcmp(entry->celname, "cid_name") == 0) {
405 ast_copy_string(colbuf, record.caller_id_name, sizeof(colbuf));
406 } else if (strcmp(entry->celname, "cid_num") == 0) {
407 ast_copy_string(colbuf, record.caller_id_num, sizeof(colbuf));
408 } else if (strcmp(entry->celname, "cid_ani") == 0) {
409 ast_copy_string(colbuf, record.caller_id_ani, sizeof(colbuf));
410 } else if (strcmp(entry->celname, "cid_rdnis") == 0) {
411 ast_copy_string(colbuf, record.caller_id_rdnis, sizeof(colbuf));
412 } else if (strcmp(entry->celname, "cid_dnid") == 0) {
413 ast_copy_string(colbuf, record.caller_id_dnid, sizeof(colbuf));
414 } else if (strcmp(entry->celname, "exten") == 0) {
415 ast_copy_string(colbuf, record.extension, sizeof(colbuf));
416 } else if (strcmp(entry->celname, "context") == 0) {
417 ast_copy_string(colbuf, record.context, sizeof(colbuf));
418 } else if (strcmp(entry->celname, "channame") == 0) {
419 ast_copy_string(colbuf, record.channel_name, sizeof(colbuf));
420 } else if (strcmp(entry->celname, "appname") == 0) {
421 ast_copy_string(colbuf, record.application_name, sizeof(colbuf));
422 } else if (strcmp(entry->celname, "appdata") == 0) {
423 ast_copy_string(colbuf, record.application_data, sizeof(colbuf));
424 } else if (strcmp(entry->celname, "accountcode") == 0) {
425 ast_copy_string(colbuf, record.account_code, sizeof(colbuf));
426 } else if (strcmp(entry->celname, "peeraccount") == 0) {
427 ast_copy_string(colbuf, record.peer_account, sizeof(colbuf));
428 } else if (strcmp(entry->celname, "uniqueid") == 0) {
429 ast_copy_string(colbuf, record.unique_id, sizeof(colbuf));
430 } else if (strcmp(entry->celname, "linkedid") == 0) {
431 ast_copy_string(colbuf, record.linked_id, sizeof(colbuf));
432 } else if (strcmp(entry->celname, "userfield") == 0) {
433 ast_copy_string(colbuf, record.user_field, sizeof(colbuf));
434 } else if (strcmp(entry->celname, "peer") == 0) {
435 ast_copy_string(colbuf, record.peer, sizeof(colbuf));
436 } else if (strcmp(entry->celname, "amaflags") == 0) {
437 snprintf(colbuf, sizeof(colbuf), "%u", record.amaflag);
438 } else if (strcmp(entry->celname, "extra") == 0) {
439 ast_copy_string(colbuf, record.extra, sizeof(colbuf));
440 } else if (strcmp(entry->celname, "eventtype") == 0) {
441 snprintf(colbuf, sizeof(colbuf), "%u", record.event_type);
442 } else {
443 colbuf[0] = 0;
444 unknown = 1;
445 }
446 colptr = colbuf;
447 }
448
449 if (colptr && !unknown) {
450 /* Check first if the column filters this entry. Note that this
451 * is very specifically NOT ast_strlen_zero(), because the filter
452 * could legitimately specify that the field is blank, which is
453 * different from the field being unspecified (NULL). */
454 if (entry->filtervalue && strcasecmp(colptr, entry->filtervalue) != 0) {
455 ast_verb(4, "CEL column '%s' with value '%s' does not match filter of"
456 " '%s'. Cancelling this CEL.\n",
457 entry->celname, colptr, entry->filtervalue);
458 goto early_release;
459 }
460
461 /* Only a filter? */
462 if (ast_strlen_zero(entry->name))
463 continue;
464
465 LENGTHEN_BUF1(strlen(entry->name));
466
467 switch (entry->type) {
468 case SQL_CHAR:
469 case SQL_VARCHAR:
470 case SQL_LONGVARCHAR:
471#ifdef HAVE_ODBC_WCHAR
472 case SQL_WCHAR:
473 case SQL_WVARCHAR:
474 case SQL_WLONGVARCHAR:
475#endif
476 case SQL_BINARY:
477 case SQL_VARBINARY:
478 case SQL_LONGVARBINARY:
479 case SQL_GUID:
480 /* For these two field names, get the rendered form, instead of the raw
481 * form (but only when we're dealing with a character-based field).
482 */
483 if (strcasecmp(entry->name, "eventtype") == 0) {
484 const char *event_name;
485
486 event_name = (!cel_show_user_def
487 && record.event_type == AST_CEL_USER_DEFINED)
488 ? record.user_defined_name : record.event_name;
489 snprintf(colbuf, sizeof(colbuf), "%s", event_name);
490 }
491
492 /* Truncate too-long fields */
493 if (entry->type != SQL_GUID) {
494 if (strlen(colptr) > entry->octetlen) {
495 colptr[entry->octetlen] = '\0';
496 }
497 }
498
499 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
500 LENGTHEN_BUF2(strlen(colptr));
501
502 /* Encode value, with escaping */
503 ast_str_append(&sql2, 0, "%s'", separator);
504 for (tmp = colptr; *tmp; tmp++) {
505 if (*tmp == '\'') {
506 ast_str_append(&sql2, 0, "''");
507 } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
508 ast_str_append(&sql2, 0, "\\\\");
509 } else {
510 ast_str_append(&sql2, 0, "%c", *tmp);
511 }
512 }
513 ast_str_append(&sql2, 0, "'");
514 break;
515 case SQL_TYPE_DATE:
516 if (ast_strlen_zero(colptr)) {
517 continue;
518 } else {
519 int year = 0, month = 0, day = 0;
520 if (strcasecmp(entry->name, "eventdate") == 0) {
521 struct ast_tm tm;
522 ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
523 year = tm.tm_year + 1900;
524 month = tm.tm_mon + 1;
525 day = tm.tm_mday;
526 } else {
527 if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
528 month <= 0 || month > 12 || day < 0 || day > 31 ||
529 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
530 (month == 2 && year % 400 == 0 && day > 29) ||
531 (month == 2 && year % 100 == 0 && day > 28) ||
532 (month == 2 && year % 4 == 0 && day > 29) ||
533 (month == 2 && year % 4 != 0 && day > 28)) {
534 ast_log(LOG_WARNING, "CEL variable %s is not a valid date ('%s').\n", entry->name, colptr);
535 continue;
536 }
537
538 if (year > 0 && year < 100) {
539 year += 2000;
540 }
541 }
542
543 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
544 LENGTHEN_BUF2(17);
545 ast_str_append(&sql2, 0, "%s{d '%04d-%02d-%02d'}", separator, year, month, day);
546 }
547 break;
548 case SQL_TYPE_TIME:
549 if (ast_strlen_zero(colptr)) {
550 continue;
551 } else {
552 int hour = 0, minute = 0, second = 0;
553 if (strcasecmp(entry->name, "eventdate") == 0) {
554 struct ast_tm tm;
555 ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
556 hour = tm.tm_hour;
557 minute = tm.tm_min;
558 second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
559 } else {
560 int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
561
562 if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > (tableptr->allowleapsec ? 60 : 59)) {
563 ast_log(LOG_WARNING, "CEL variable %s is not a valid time ('%s').\n", entry->name, colptr);
564 continue;
565 }
566 }
567
568 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
569 LENGTHEN_BUF2(15);
570 ast_str_append(&sql2, 0, "%s{t '%02d:%02d:%02d'}", separator, hour, minute, second);
571 }
572 break;
573 case SQL_TYPE_TIMESTAMP:
574 case SQL_TIMESTAMP:
575 case SQL_DATETIME:
576 if (ast_strlen_zero(colptr)) {
577 continue;
578 } else {
579 if (datefield) {
580 /*
581 * We've already properly formatted the timestamp so there's no need
582 * to parse it and re-format it.
583 */
584 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
585 LENGTHEN_BUF2(27);
586 ast_str_append(&sql2, 0, "%s{ts '%s'}", separator, colptr);
587 } else {
588 int year = 0, month = 0, day = 0, hour = 0, minute = 0;
589 /* MUST use double for microsecond precision */
590 double second = 0.0;
591 if (strcasecmp(entry->name, "eventdate") == 0) {
592 /*
593 * There doesn't seem to be any reference to 'eventdate' anywhere
594 * other than in this module. It should be considered for removal
595 * at a later date.
596 */
597 struct ast_tm tm;
598 ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
599 year = tm.tm_year + 1900;
600 month = tm.tm_mon + 1;
601 day = tm.tm_mday;
602 hour = tm.tm_hour;
603 minute = tm.tm_min;
604 second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
605 second += (tm.tm_usec / 1000000.0);
606 } else {
607 /*
608 * If we're here, the data to be inserted MAY be a timestamp
609 * but the column is. We parse as much as we can.
610 */
611 int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%lf", &year, &month, &day, &hour, &minute, &second);
612
613 if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
614 month <= 0 || month > 12 || day < 0 || day > 31 ||
615 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
616 (month == 2 && year % 400 == 0 && day > 29) ||
617 (month == 2 && year % 100 == 0 && day > 28) ||
618 (month == 2 && year % 4 == 0 && day > 29) ||
619 (month == 2 && year % 4 != 0 && day > 28) ||
620 hour > 23 || minute > 59 || ((int)floor(second)) > (tableptr->allowleapsec ? 60 : 59) ||
621 hour < 0 || minute < 0 || ((int)floor(second)) < 0) {
622 ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
623 continue;
624 }
625
626 if (year > 0 && year < 100) {
627 year += 2000;
628 }
629 }
630
631 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
632 LENGTHEN_BUF2(27);
633 ast_str_append(&sql2, 0, "%s{ts '%04d-%02d-%02d %02d:%02d:%09.6lf'}", separator, year, month, day, hour, minute, second);
634 }
635 }
636 break;
637 case SQL_INTEGER:
638 {
639 int integer = 0;
640 if (sscanf(colptr, "%30d", &integer) != 1) {
641 ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
642 continue;
643 }
644
645 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
646 LENGTHEN_BUF2(12);
647 ast_str_append(&sql2, 0, "%s%d", separator, integer);
648 }
649 break;
650 case SQL_BIGINT:
651 {
652 long long integer = 0;
653 int ret;
654 if ((ret = sscanf(colptr, "%30lld", &integer)) != 1) {
655 ast_log(LOG_WARNING, "CEL variable %s is not an integer. (%d - '%s')\n", entry->name, ret, colptr);
656 continue;
657 }
658
659 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
660 LENGTHEN_BUF2(24);
661 ast_str_append(&sql2, 0, "%s%lld", separator, integer);
662 }
663 break;
664 case SQL_SMALLINT:
665 {
666 short integer = 0;
667 if (sscanf(colptr, "%30hd", &integer) != 1) {
668 ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
669 continue;
670 }
671
672 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
673 LENGTHEN_BUF2(7);
674 ast_str_append(&sql2, 0, "%s%d", separator, integer);
675 }
676 break;
677 case SQL_TINYINT:
678 {
679 signed char integer = 0;
680 if (sscanf(colptr, "%30hhd", &integer) != 1) {
681 ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
682 continue;
683 }
684
685 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
686 LENGTHEN_BUF2(4);
687 ast_str_append(&sql2, 0, "%s%d", separator, integer);
688 }
689 break;
690 case SQL_BIT:
691 {
692 signed char integer = 0;
693 if (sscanf(colptr, "%30hhd", &integer) != 1) {
694 ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
695 continue;
696 }
697 if (integer != 0)
698 integer = 1;
699
700 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
701 LENGTHEN_BUF2(2);
702 ast_str_append(&sql2, 0, "%s%d", separator, integer);
703 }
704 break;
705 case SQL_NUMERIC:
706 case SQL_DECIMAL:
707 {
708 double number = 0.0;
709 if (sscanf(colptr, "%30lf", &number) != 1) {
710 ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
711 continue;
712 }
713
714 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
715 LENGTHEN_BUF2(entry->decimals + 2);
716 ast_str_append(&sql2, 0, "%s%*.*lf", separator, entry->decimals, entry->radix, number);
717 }
718 break;
719 case SQL_FLOAT:
720 case SQL_REAL:
721 case SQL_DOUBLE:
722 {
723 double number = 0.0;
724 if (sscanf(colptr, "%30lf", &number) != 1) {
725 ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
726 continue;
727 }
728
729 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
730 LENGTHEN_BUF2(entry->decimals);
731 ast_str_append(&sql2, 0, "%s%lf", separator, number);
732 }
733 break;
734 default:
735 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);
736 continue;
737 }
738 separator = ", ";
739 }
740 }
741
742 /* Concatenate the two constructed buffers */
744 ast_str_append(&sql, 0, ")");
745 ast_str_append(&sql2, 0, ")");
746 ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
747
748 ast_debug(3, "Executing SQL statement: [%s]\n", ast_str_buffer(sql));
749
750 /* There is no benefit to prepared statements here as each generated
751 INSERT statement is unique. Just execute our query directly. */
752 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
753 if (!SQL_SUCCEEDED(res)) {
754 ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n",
755 tableptr->connection);
756 goto early_release;
757 }
758
759 res = ast_odbc_execute_sql(obj, stmt, ast_str_buffer(sql));
760 if (!SQL_SUCCEEDED(res)) {
761 ast_log(LOG_WARNING, "Insert failed on '%s:%s'. CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
762 }
763
764 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
765
766early_release:
768 }
770
771 /* Next time, just allocate buffers that are that big to start with. */
772 if (ast_str_strlen(sql) > maxsize) {
773 maxsize = ast_str_strlen(sql);
774 }
775 if (ast_str_strlen(sql2) > maxsize2) {
776 maxsize2 = ast_str_strlen(sql2);
777 }
778
779 ast_free(sql);
780 ast_free(sql2);
781}
782
783static int unload_module(void)
784{
786 ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
787 return -1;
788 }
789
791 free_config();
794
795 return 0;
796}
797
798static int load_module(void)
799{
801
803 ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
805 }
806 load_config();
809 ast_log(LOG_ERROR, "Unable to subscribe to CEL events\n");
810 free_config();
812 }
814}
815
816static int reload(void)
817{
819 ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
821 }
822
823 free_config();
824 load_config();
827}
828
830 .support_level = AST_MODULE_SUPPORT_CORE,
831 .load = load_module,
832 .unload = unload_module,
833 .reload = reload,
834 .load_pri = AST_MODPRI_CDR_DRIVER,
835 .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
Call Event Logging API.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition cel.c:1824
@ AST_CEL_USER_DEFINED
a user-defined event, the event name field should be set
Definition cel.h:70
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:870
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition cel.h:162
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition cel.c:1836
#define LENGTHEN_BUF2(size)
Definition cel_odbc.c:328
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:331
#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:798
static int unload_module(void)
Definition cel_odbc.c:783
static int reload(void)
Definition cel_odbc.c:816
static int load_config(void)
Definition cel_odbc.c:91
#define LENGTHEN_BUF1(size)
Definition cel_odbc.c:325
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:3324
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition extconf.c:1287
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition extconf.c:1213
#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).
#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.
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_RWLIST_HEAD_INIT(head)
Initializes an rwlist head structure.
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized.
#define AST_RWLIST_REMOVE_HEAD
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_RWLIST_INSERT_TAIL
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
#define AST_RWLIST_ENTRY
#define AST_RWLIST_HEAD_DESTROY(head)
Destroys an rwlist head structure.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
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:331
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition module.h:557
@ AST_MODPRI_CDR_DRIVER
Definition module.h:345
@ 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.
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
Definition res_odbc.c:828
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:884
SQLRETURN ast_odbc_execute_sql(struct odbc_obj *obj, SQLHSTMT *stmt, const char *sql)
Execute a unprepared SQL query.
Definition res_odbc.c:474
#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
size_t attribute_pure ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition strings.h:730
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition utils.c:2233
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
char *attribute_pure ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition strings.h:761
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:157
const char * caller_id_dnid
Definition cel.h:176
const char * application_data
Definition cel.h:181
const char * account_code
Definition cel.h:182
const char * caller_id_rdnis
Definition cel.h:175
const char * extra
Definition cel.h:190
const char * extension
Definition cel.h:177
const char * caller_id_num
Definition cel.h:173
const char * channel_name
Definition cel.h:179
const char * linked_id
Definition cel.h:185
const char * peer_account
Definition cel.h:183
const char * peer
Definition cel.h:189
enum ast_cel_event_type event_type
Definition cel.h:168
const char * unique_id
Definition cel.h:184
const char * user_defined_name
Definition cel.h:171
const char * context
Definition cel.h:178
const char * application_name
Definition cel.h:180
struct timeval event_time
Definition cel.h:169
uint32_t version
struct ABI version
Definition cel.h:167
const char * user_field
Definition cel.h:188
const char * caller_id_ani
Definition cel.h:174
const char * caller_id_name
Definition cel.h:172
const char * event_name
Definition cel.h:170
An event.
Definition event.c:81
Structure used to handle boolean flags.
Definition utils.h:220
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.
struct ast_variable * next
char * filtervalue
SQLSMALLINT decimals
SQLSMALLINT radix
char * staticvalue
struct columns::@5 list
SQLINTEGER octetlen
SQLINTEGER size
char * celname
Definition cel_odbc.c:68
struct columns::@109 list
SQLSMALLINT nullable
Number structure.
ODBC container.
Definition res_odbc.h:46
SQLHDBC con
Definition res_odbc.h:47
char * connection
unsigned int usegmtime
struct tables::@110 list
unsigned int allowleapsec
Definition cel_odbc.c:84
struct tables::mysql_columns columns
static struct aco_type item
static struct ast_codec unknown
Time-related functions and macros.