Asterisk - The Open Source Telephony Project GIT-master-4c84066
Loading...
Searching...
No Matches
Data Structures | Macros | Functions | Variables
cel_odbc.c File Reference

ODBC CEL backend. More...

#include "asterisk.h"
#include <sys/types.h>
#include <time.h>
#include <math.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
#include "asterisk/config.h"
#include "asterisk/channel.h"
#include "asterisk/lock.h"
#include "asterisk/linkedlists.h"
#include "asterisk/res_odbc.h"
#include "asterisk/cel.h"
#include "asterisk/module.h"
Include dependency graph for cel_odbc.c:

Go to the source code of this file.

Data Structures

struct  columns
 
struct  tables::odbc_columns
 
struct  odbc_tables
 
struct  tables
 

Macros

#define CEL_SHOW_USERDEF_DEFAULT   0
 show_user_def is off by default
 
#define CONFIG   "cel_odbc.conf"
 
#define LENGTHEN_BUF(size, var_sql)
 
#define LENGTHEN_BUF1(size)    LENGTHEN_BUF(size, sql);
 
#define LENGTHEN_BUF2(size)    LENGTHEN_BUF(size, sql2);
 
#define ODBC_BACKEND_NAME   "ODBC CEL backend"
 

Functions

static void __reg_module (void)
 
static void __unreg_module (void)
 
struct ast_moduleAST_MODULE_SELF_SYM (void)
 
static int free_config (void)
 
static int load_config (void)
 
static int load_module (void)
 
static void odbc_log (struct ast_event *event)
 
static int reload (void)
 
static int unload_module (void)
 

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "ODBC CEL backend" , .key = ASTERISK_GPL_KEY , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, .requires = "cel,res_odbc", }
 
static const struct ast_module_infoast_module_info = &__mod_info
 
static unsigned char cel_show_user_def
 
static int maxsize = 512
 
static int maxsize2 = 512
 
static struct odbc_tables odbc_tables = AST_RWLIST_HEAD_INIT_VALUE
 

Detailed Description

ODBC CEL backend.

Author
Tilghman Lesher
<tlesher AT digium DOT com> 

Definition in file cel_odbc.c.

Macro Definition Documentation

◆ CEL_SHOW_USERDEF_DEFAULT

#define CEL_SHOW_USERDEF_DEFAULT   0

show_user_def is off by default

Definition at line 58 of file cel_odbc.c.

◆ CONFIG

#define CONFIG   "cel_odbc.conf"

Definition at line 53 of file cel_odbc.c.

◆ LENGTHEN_BUF

#define LENGTHEN_BUF (   size,
  var_sql 
)

Definition at line 311 of file cel_odbc.c.

312 { \
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)
#define LOG_ERROR
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
#define ast_str_make_space(buf, new_len)
Definition strings.h:828
size_t attribute_pure ast_str_size(const struct ast_str *buf)
Returns the current maximum length (without reallocation) of the current buffer.
Definition strings.h:742

◆ LENGTHEN_BUF1

#define LENGTHEN_BUF1 (   size)     LENGTHEN_BUF(size, sql);

Definition at line 325 of file cel_odbc.c.

◆ LENGTHEN_BUF2

#define LENGTHEN_BUF2 (   size)     LENGTHEN_BUF(size, sql2);

Definition at line 328 of file cel_odbc.c.

◆ ODBC_BACKEND_NAME

#define ODBC_BACKEND_NAME   "ODBC CEL backend"

Definition at line 55 of file cel_odbc.c.

Function Documentation

◆ __reg_module()

static void __reg_module ( void  )
static

Definition at line 836 of file cel_odbc.c.

◆ __unreg_module()

static void __unreg_module ( void  )
static

Definition at line 836 of file cel_odbc.c.

◆ AST_MODULE_SELF_SYM()

struct ast_module * AST_MODULE_SELF_SYM ( void  )

Definition at line 836 of file cel_odbc.c.

◆ free_config()

static int free_config ( void  )
static

Definition at line 298 of file cel_odbc.c.

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}
#define ast_free(a)
Definition astmm.h:180
#define AST_RWLIST_REMOVE_HEAD
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
struct columns::@5 list
struct tables::mysql_columns columns

References ast_free, AST_LIST_REMOVE_HEAD, AST_RWLIST_REMOVE_HEAD, tables::columns, columns::list, and tables::table.

Referenced by load_module(), reload(), and unload_module().

◆ load_config()

static int load_config ( void  )
static

Definition at line 91 of file cel_odbc.c.

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}
#define var
Definition ast_expr2f.c:605
#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
#define CEL_SHOW_USERDEF_DEFAULT
show_user_def is off by default
Definition cel_odbc.c:58
#define CONFIG
Definition cel_odbc.c:53
static unsigned char cel_show_user_def
Definition cel_odbc.c:61
#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_verb(level,...)
#define LOG_NOTICE
#define LOG_WARNING
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_RWLIST_INSERT_TAIL
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
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
#define ast_odbc_request_obj(name, check)
Get a ODBC connection object.
Definition res_odbc.h:120
#define NULL
Definition resample.c:96
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
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
Structure used to handle boolean flags.
Definition utils.h:220
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
char * filtervalue
SQLSMALLINT decimals
SQLSMALLINT radix
char * staticvalue
SQLINTEGER octetlen
SQLINTEGER size
char * celname
Definition cel_odbc.c:68
SQLSMALLINT nullable
ODBC container.
Definition res_odbc.h:46
SQLHDBC con
Definition res_odbc.h:47
char * connection
unsigned int usegmtime
unsigned int allowleapsec
Definition cel_odbc.c:84
static struct aco_type item

References tables::allowleapsec, ast_calloc, ast_category_browse(), ast_config_destroy(), ast_config_load, ast_copy_string(), ast_free, AST_LIST_FIRST, AST_LIST_INSERT_TAIL, ast_log, ast_odbc_release_obj(), ast_odbc_request_obj, AST_RWLIST_INSERT_TAIL, ast_strdupa, ast_strip(), ast_strlen_zero(), ast_true(), ast_variable_browse(), ast_variable_retrieve(), ast_verb, cel_show_user_def, CEL_SHOW_USERDEF_DEFAULT, columns::celname, tables::columns, odbc_obj::con, CONFIG, CONFIG_STATUS_FILEINVALID, tables::connection, columns::decimals, columns::filtervalue, item, LOG_ERROR, LOG_NOTICE, LOG_WARNING, columns::name, ast_variable::next, NULL, columns::nullable, columns::octetlen, columns::radix, columns::size, columns::staticvalue, tables::table, columns::type, tables::usegmtime, and var.

Referenced by load_module(), and reload().

◆ load_module()

static int load_module ( void  )
static

Definition at line 798 of file cel_odbc.c.

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}
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition cel.c:1836
static int free_config(void)
Definition cel_odbc.c:298
static void odbc_log(struct ast_event *event)
Definition cel_odbc.c:331
#define ODBC_BACKEND_NAME
Definition cel_odbc.c:55
static int load_config(void)
Definition cel_odbc.c:91
#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_RWLIST_HEAD_INIT(head)
Initializes an rwlist head structure.
@ 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

References ast_cel_backend_register(), ast_log, AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, AST_RWLIST_HEAD_INIT, AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, free_config(), load_config(), LOG_ERROR, ODBC_BACKEND_NAME, and odbc_log().

◆ odbc_log()

static void odbc_log ( struct ast_event event)
static

Definition at line 331 of file cel_odbc.c.

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}
@ 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
#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 LENGTHEN_BUF1(size)
Definition cel_odbc.c:325
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition linkedlists.h:78
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries 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
INT32 integer
Definition lpc10.h:80
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
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
#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
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
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
Number structure.
static struct ast_codec unknown

References ast_cel_event_record::account_code, tables::allowleapsec, ast_cel_event_record::amaflag, ast_cel_event_record::application_data, ast_cel_event_record::application_name, AST_CEL_EVENT_RECORD_VERSION, ast_cel_fill_record(), AST_CEL_USER_DEFINED, ast_copy_string(), ast_debug, ast_free, AST_LIST_TRAVERSE, ast_localtime(), ast_log, ast_odbc_backslash_is_escape(), ast_odbc_execute_sql(), ast_odbc_release_obj(), ast_odbc_request_obj, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK, ast_str_append(), ast_str_buffer(), ast_str_create, ast_str_set(), ast_str_strlen(), ast_strdupa, ast_strftime(), ast_strlen_zero(), ast_verb, ast_cel_event_record::caller_id_ani, ast_cel_event_record::caller_id_dnid, ast_cel_event_record::caller_id_name, ast_cel_event_record::caller_id_num, ast_cel_event_record::caller_id_rdnis, cel_show_user_def, columns::celname, ast_cel_event_record::channel_name, tables::columns, odbc_obj::con, tables::connection, ast_cel_event_record::context, columns::decimals, ast_cel_event_record::event_name, ast_cel_event_record::event_time, ast_cel_event_record::event_type, ast_cel_event_record::extension, ast_cel_event_record::extra, columns::filtervalue, LENGTHEN_BUF1, LENGTHEN_BUF2, ast_cel_event_record::linked_id, LOG_ERROR, LOG_WARNING, maxsize, maxsize2, columns::name, NULL, columns::octetlen, ast_cel_event_record::peer, ast_cel_event_record::peer_account, columns::radix, columns::staticvalue, tables::table, ast_tm::tm_hour, ast_tm::tm_mday, ast_tm::tm_min, ast_tm::tm_mon, ast_tm::tm_sec, ast_tm::tm_usec, ast_tm::tm_year, columns::type, ast_cel_event_record::unique_id, unknown, tables::usegmtime, ast_cel_event_record::user_defined_name, ast_cel_event_record::user_field, and ast_cel_event_record::version.

Referenced by load_module().

◆ reload()

static int reload ( void  )
static

Definition at line 816 of file cel_odbc.c.

817{
819 ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
821 }
822
823 free_config();
824 load_config();
827}

References ast_log, AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, free_config(), load_config(), and LOG_ERROR.

◆ unload_module()

static int unload_module ( void  )
static

Definition at line 783 of file cel_odbc.c.

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}
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition cel.c:1824
#define AST_RWLIST_HEAD_DESTROY(head)
Destroys an rwlist head structure.

References ast_cel_backend_unregister(), ast_log, AST_RWLIST_HEAD_DESTROY, AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, free_config(), LOG_ERROR, and ODBC_BACKEND_NAME.

Variable Documentation

◆ __mod_info

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "ODBC CEL backend" , .key = ASTERISK_GPL_KEY , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, .requires = "cel,res_odbc", }
static

Definition at line 836 of file cel_odbc.c.

◆ ast_module_info

const struct ast_module_info* ast_module_info = &__mod_info
static

Definition at line 836 of file cel_odbc.c.

◆ cel_show_user_def

unsigned char cel_show_user_def
static

TRUE if we should set the eventtype field to USER_DEFINED on user events.

Definition at line 61 of file cel_odbc.c.

Referenced by load_config(), and odbc_log().

◆ maxsize

int maxsize = 512
static

Definition at line 64 of file cel_odbc.c.

Referenced by odbc_log().

◆ maxsize2

int maxsize2 = 512
static

Definition at line 64 of file cel_odbc.c.

Referenced by odbc_log().

◆ odbc_tables