37 #include <sys/types.h>
53 #define CONFIG "cel_odbc.conf"
55 #define ODBC_BACKEND_NAME "ODBC CEL backend"
58 #define CEL_SHOW_USERDEF_DEFAULT 0
64 static int maxsize = 512, maxsize2 = 512;
83 unsigned int usegmtime:1;
84 unsigned int allowleapsec:1;
86 AST_RWLIST_ENTRY(
tables) list;
91 static
int load_config(
void)
95 const char *tmp, *catg;
96 struct tables *tableptr;
97 struct columns *
entry;
102 int lenconnection, lentable;
105 SQLHSTMT stmt = NULL;
109 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
110 ast_log(LOG_WARNING,
"Unable to load " CONFIG
". No ODBC CEL records!\n");
116 for (var = ast_variable_browse(cfg,
"general"); var; var = var->
next) {
117 if (!strcasecmp(var->
name,
"show_user_defined")) {
125 if (!strcasecmp(catg,
"general")) {
128 var = ast_variable_browse(cfg, catg);
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);
137 lenconnection = strlen(connection);
142 ast_log(LOG_WARNING,
"No such connection '%s' in the '%s' section of " CONFIG
". Check res_odbc.conf.\n", connection, catg);
146 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"table"))) {
147 ast_log(LOG_NOTICE,
"No table name found. Assuming 'cel'.\n");
151 lentable = strlen(table);
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);
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);
167 tableptr =
ast_calloc(
sizeof(
char),
sizeof(*tableptr) + lenconnection + 1 + lentable + 1);
169 ast_log(LOG_ERROR,
"Out of memory creating entry for table '%s' on connection '%s'\n", table, connection);
175 tableptr->connection = (
char *)tableptr +
sizeof(*tableptr);
176 tableptr->table = (
char *)tableptr +
sizeof(*tableptr) + lenconnection + 1;
180 tableptr->usegmtime = 0;
181 if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"usegmtime"))) {
182 tableptr->usegmtime =
ast_true(tmp);
185 tableptr->allowleapsec = 1;
186 if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"allowleapsecond"))) {
187 tableptr->allowleapsec =
ast_true(tmp);
190 ast_verb(3,
"Found CEL table %s@%s.\n", tableptr->table, tableptr->connection);
193 for (var = ast_variable_browse(cfg, catg); var; var = var->
next) {
194 if (strncmp(var->
name,
"filter", 6) == 0) {
197 ast_verb(3,
"Found filter %s for cel variable %s in %s@%s\n", var->
value, celvar, tableptr->table, tableptr->connection);
199 entry =
ast_calloc(
sizeof(
char),
sizeof(*entry) + strlen(celvar) + 1 + strlen(var->
value) + 1);
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);
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);
217 while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
218 char *celvar =
"", *staticvalue =
"";
220 SQLGetData(stmt, 4, SQL_C_CHAR, columnname,
sizeof(columnname), &sqlptr);
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) {
232 ast_verb(3,
"Found alias %s for column %s in %s@%s\n", celvar, columnname, tableptr->table, tableptr->connection);
234 }
else if (strncmp(var->
name,
"static", 6) == 0 && strcasecmp(var->
value, columnname) == 0) {
237 if (item[0] ==
'"' && item[strlen(item) - 1] ==
'"') {
239 item[strlen(item) - 1] =
'\0';
246 entry =
ast_calloc(
sizeof(
char),
sizeof(*entry) + strlen(columnname) + 1 + strlen(celvar) + 1 + strlen(staticvalue) + 1);
248 ast_log(LOG_ERROR,
"Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
252 entry->name = (
char *)entry +
sizeof(*entry);
253 strcpy(entry->name, columnname);
255 if (!ast_strlen_zero(celvar)) {
256 entry->celname = entry->name + strlen(columnname) + 1;
257 strcpy(entry->celname, celvar);
259 entry->celname = (
char *)entry +
sizeof(*entry);
262 if (!ast_strlen_zero(staticvalue)) {
263 entry->staticvalue = entry->celname + strlen(entry->celname) + 1;
264 strcpy(entry->staticvalue, staticvalue);
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);
277 if (entry->octetlen == 0)
278 entry->octetlen = entry->size;
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);
286 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
290 AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
298 static int free_config(
void)
300 struct tables *table;
301 struct columns *entry;
302 while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
311 static SQLHSTMT generic_prepare(
struct odbc_obj *obj,
void *data)
316 SQLINTEGER nativeerror = 0, numfields = 0;
317 SQLSMALLINT diagbytes = 0;
318 unsigned char state[10], diagnostic[256];
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");
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);
334 ast_log(LOG_WARNING,
"Oh, that was good. There are really %d diagnostics?\n", (
int)numfields);
338 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
345 #define LENGTHEN_BUF(size, var_sql) \
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); \
353 AST_RWLIST_UNLOCK(&odbc_tables); \
359 #define LENGTHEN_BUF1(size) \
360 LENGTHEN_BUF(size, sql);
362 #define LENGTHEN_BUF2(size) \
363 LENGTHEN_BUF(size, sql2);
367 struct tables *tableptr;
368 struct columns *entry;
372 char colbuf[1024], *colptr;
373 SQLHSTMT stmt = NULL;
392 ast_log(LOG_ERROR,
"Unable to lock table list. Insert CEL(s) failed.\n");
399 char *separator =
"";
400 ast_str_set(&sql, 0,
"INSERT INTO %s (", tableptr->table);
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));
412 if (strcasecmp(entry->celname,
"eventtime") == 0) {
417 if (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);
433 ast_strftime(colbuf,
sizeof(colbuf),
"%Y-%m-%d %H:%M:%S.%6q", &tm);
436 if (strcmp(entry->celname,
"userdeftype") == 0) {
438 }
else if (strcmp(entry->celname,
"cid_name") == 0) {
440 }
else if (strcmp(entry->celname,
"cid_num") == 0) {
442 }
else if (strcmp(entry->celname,
"cid_ani") == 0) {
444 }
else if (strcmp(entry->celname,
"cid_rdnis") == 0) {
446 }
else if (strcmp(entry->celname,
"cid_dnid") == 0) {
448 }
else if (strcmp(entry->celname,
"exten") == 0) {
450 }
else if (strcmp(entry->celname,
"context") == 0) {
452 }
else if (strcmp(entry->celname,
"channame") == 0) {
454 }
else if (strcmp(entry->celname,
"appname") == 0) {
456 }
else if (strcmp(entry->celname,
"appdata") == 0) {
458 }
else if (strcmp(entry->celname,
"accountcode") == 0) {
460 }
else if (strcmp(entry->celname,
"peeraccount") == 0) {
462 }
else if (strcmp(entry->celname,
"uniqueid") == 0) {
464 }
else if (strcmp(entry->celname,
"linkedid") == 0) {
466 }
else if (strcmp(entry->celname,
"userfield") == 0) {
468 }
else if (strcmp(entry->celname,
"peer") == 0) {
470 }
else if (strcmp(entry->celname,
"amaflags") == 0) {
471 snprintf(colbuf,
sizeof(colbuf),
"%u", record.amaflag);
472 }
else if (strcmp(entry->celname,
"extra") == 0) {
474 }
else if (strcmp(entry->celname,
"eventtype") == 0) {
475 snprintf(colbuf,
sizeof(colbuf),
"%u", record.event_type);
483 if (colptr && !unknown) {
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);
496 if (ast_strlen_zero(entry->name))
499 LENGTHEN_BUF1(strlen(entry->name));
501 switch (entry->type) {
504 case SQL_LONGVARCHAR:
505 #ifdef HAVE_ODBC_WCHAR
508 case SQL_WLONGVARCHAR:
512 case SQL_LONGVARBINARY:
517 if (strcasecmp(entry->name,
"eventtype") == 0) {
518 const char *event_name;
522 ? record.user_defined_name : record.event_name;
523 snprintf(colbuf,
sizeof(colbuf),
"%s", event_name);
527 if (entry->type != SQL_GUID) {
528 if (strlen(colptr) > entry->octetlen) {
529 colptr[entry->octetlen] =
'\0';
534 LENGTHEN_BUF2(strlen(colptr));
538 for (tmp = colptr; *tmp; tmp++) {
550 if (ast_strlen_zero(colptr)) {
553 int year = 0, month = 0, day = 0;
554 if (strcasecmp(entry->name,
"eventdate") == 0) {
556 ast_localtime(&record.event_time, &tm, tableptr->usegmtime ?
"UTC" : NULL);
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);
572 if (year > 0 && year < 100) {
579 ast_str_append(&sql2, 0,
"%s{d '%04d-%02d-%02d'}", separator, year, month, day);
583 if (ast_strlen_zero(colptr)) {
586 int hour = 0, minute = 0, second = 0;
587 if (strcasecmp(entry->name,
"eventdate") == 0) {
589 ast_localtime(&record.event_time, &tm, tableptr->usegmtime ?
"UTC" : NULL);
592 second = (tableptr->allowleapsec || tm.
tm_sec < 60) ? tm.
tm_sec : 59;
594 int count = sscanf(colptr,
"%2d:%2d:%2d", &hour, &minute, &second);
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);
604 ast_str_append(&sql2, 0,
"%s{t '%02d:%02d:%02d'}", separator, hour, minute, second);
607 case SQL_TYPE_TIMESTAMP:
610 if (ast_strlen_zero(colptr)) {
622 int year = 0, month = 0, day = 0, hour = 0, minute = 0;
625 if (strcasecmp(entry->name,
"eventdate") == 0) {
632 ast_localtime(&record.event_time, &tm, tableptr->usegmtime ?
"UTC" : NULL);
638 second = (tableptr->allowleapsec || tm.
tm_sec < 60) ? tm.
tm_sec : 59;
639 second += (tm.
tm_usec / 1000000.0);
645 int count = sscanf(colptr,
"%4d-%2d-%2d %2d:%2d:%lf", &year, &month, &day, &hour, &minute, &second);
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);
660 if (year > 0 && year < 100) {
667 ast_str_append(&sql2, 0,
"%s{ts '%04d-%02d-%02d %02d:%02d:%09.6lf'}", separator, year, month, day, hour, minute, second);
674 if (sscanf(colptr,
"%30d", &integer) != 1) {
675 ast_log(LOG_WARNING,
"CEL variable %s is not an integer.\n", entry->name);
686 long long integer = 0;
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);
701 if (sscanf(colptr,
"%30hd", &integer) != 1) {
702 ast_log(LOG_WARNING,
"CEL variable %s is not an integer.\n", entry->name);
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);
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);
743 if (sscanf(colptr,
"%30lf", &number) != 1) {
744 ast_log(LOG_WARNING,
"CEL variable %s is not an numeric type.\n", entry->name);
749 LENGTHEN_BUF2(entry->decimals + 2);
750 ast_str_append(&sql2, 0,
"%s%*.*lf", separator, entry->decimals, entry->radix, number);
758 if (sscanf(colptr,
"%30lf", &number) != 1) {
759 ast_log(LOG_WARNING,
"CEL variable %s is not an numeric type.\n", entry->name);
764 LENGTHEN_BUF2(entry->decimals);
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);
785 SQLRowCount(stmt, &rows);
786 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
789 ast_log(LOG_WARNING,
"Insert failed on '%s:%s'. CEL failed: %s\n", tableptr->connection, tableptr->table,
ast_str_buffer(sql));
808 static int unload_module(
void)
811 ast_log(LOG_ERROR,
"Unable to lock column list. Unload failed.\n");
823 static int load_module(
void)
828 ast_log(LOG_ERROR,
"Unable to lock column list. Load failed.\n");
834 ast_log(LOG_ERROR,
"Unable to subscribe to CEL events\n");
841 static int reload(
void)
844 ast_log(LOG_ERROR,
"Unable to lock column list. Reload failed.\n");
854 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER,
"ODBC CEL backend",
855 .support_level = AST_MODULE_SUPPORT_CORE,
857 .unload = unload_module,
860 .requires =
"cel,res_odbc",
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.
Helper struct for getting the fields out of a CEL event.
#define AST_RWLIST_HEAD_DESTROY(head)
Destroys an rwlist head structure.
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.
static unsigned char cel_show_user_def
Time-related functions and macros.
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
#define ast_odbc_request_obj(name, check)
Get a ODBC connection object.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
int ast_odbc_prepare(struct odbc_obj *obj, SQLHSTMT *stmt, const char *sql)
Prepares a SQL query on a statement.
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Structure for variables, used for configurations and for channel variables.
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
#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...
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
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
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
uint32_t version
struct ABI version
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.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
#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_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
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".
Support for dynamic strings.
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define ast_calloc(num, len)
A wrapper for calloc()
Module has failed to load, may be in an inconsistent state.
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...
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Structure used to handle boolean flags.
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
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.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
a user-defined event, the event name field should be set
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
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.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.