res_pjsip: Symmetric transports
[asterisk/asterisk.git] / cel / cel_odbc.c
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         <support_level>core</support_level>
32  ***/
33
34 #include "asterisk.h"
35
36 #include <sys/types.h>
37 #include <time.h>
38
39 #include <sql.h>
40 #include <sqlext.h>
41 #include <sqltypes.h>
42
43 #include "asterisk/config.h"
44 #include "asterisk/channel.h"
45 #include "asterisk/lock.h"
46 #include "asterisk/linkedlists.h"
47 #include "asterisk/res_odbc.h"
48 #include "asterisk/cel.h"
49 #include "asterisk/module.h"
50
51 #define CONFIG  "cel_odbc.conf"
52
53 #define ODBC_BACKEND_NAME "ODBC CEL backend"
54
55 /*! \brief show_user_def is off by default */
56 #define CEL_SHOW_USERDEF_DEFAULT        0
57
58 /*! TRUE if we should set the eventtype field to USER_DEFINED on user events. */
59 static unsigned char cel_show_user_def;
60
61 /* Optimization to reduce number of memory allocations */
62 static int maxsize = 512, maxsize2 = 512;
63
64 struct columns {
65         char *name;
66         char *celname;
67         char *filtervalue;
68         char *staticvalue;
69         SQLSMALLINT type;
70         SQLINTEGER size;
71         SQLSMALLINT decimals;
72         SQLSMALLINT radix;
73         SQLSMALLINT nullable;
74         SQLINTEGER octetlen;
75         AST_LIST_ENTRY(columns) list;
76 };
77
78 struct tables {
79         char *connection;
80         char *table;
81         unsigned int usegmtime:1;
82         unsigned int allowleapsec:1;
83         AST_LIST_HEAD_NOLOCK(odbc_columns, columns) columns;
84         AST_RWLIST_ENTRY(tables) list;
85 };
86
87 static AST_RWLIST_HEAD_STATIC(odbc_tables, tables);
88
89 static int load_config(void)
90 {
91         struct ast_config *cfg;
92         struct ast_variable *var;
93         const char *tmp, *catg;
94         struct tables *tableptr;
95         struct columns *entry;
96         struct odbc_obj *obj;
97         char columnname[80];
98         char connection[40];
99         char table[40];
100         int lenconnection, lentable;
101         SQLLEN sqlptr;
102         int res = 0;
103         SQLHSTMT stmt = NULL;
104         struct ast_flags config_flags = { 0 }; /* Part of our config comes from the database */
105
106         cfg = ast_config_load(CONFIG, config_flags);
107         if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
108                 ast_log(LOG_WARNING, "Unable to load " CONFIG ".  No ODBC CEL records!\n");
109                 return -1;
110         }
111
112         /* Process the general category */
113         cel_show_user_def = CEL_SHOW_USERDEF_DEFAULT;
114         for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
115                 if (!strcasecmp(var->name, "show_user_defined")) {
116                         cel_show_user_def = ast_true(var->value) ? 1 : 0;
117                 } else {
118                         /* Unknown option name. */
119                 }
120         }
121
122         for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
123                 if (!strcasecmp(catg, "general")) {
124                         continue;
125                 }
126                 var = ast_variable_browse(cfg, catg);
127                 if (!var)
128                         continue;
129
130                 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "connection"))) {
131                         ast_log(LOG_WARNING, "No connection parameter found in '%s'.  Skipping.\n", catg);
132                         continue;
133                 }
134                 ast_copy_string(connection, tmp, sizeof(connection));
135                 lenconnection = strlen(connection);
136
137                 /* When loading, we want to be sure we can connect. */
138                 obj = ast_odbc_request_obj(connection, 1);
139                 if (!obj) {
140                         ast_log(LOG_WARNING, "No such connection '%s' in the '%s' section of " CONFIG ".  Check res_odbc.conf.\n", connection, catg);
141                         continue;
142                 }
143
144                 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "table"))) {
145                         ast_log(LOG_NOTICE, "No table name found.  Assuming 'cel'.\n");
146                         tmp = "cel";
147                 }
148                 ast_copy_string(table, tmp, sizeof(table));
149                 lentable = strlen(table);
150
151                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
152                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
153                         ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", connection);
154                         ast_odbc_release_obj(obj);
155                         continue;
156                 }
157
158                 res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)table, SQL_NTS, (unsigned char *)"%", SQL_NTS);
159                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
160                         ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'.  Skipping.\n", connection);
161                         ast_odbc_release_obj(obj);
162                         continue;
163                 }
164
165                 tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + lenconnection + 1 + lentable + 1);
166                 if (!tableptr) {
167                         ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", table, connection);
168                         ast_odbc_release_obj(obj);
169                         res = -1;
170                         break;
171                 }
172
173                 tableptr->connection = (char *)tableptr + sizeof(*tableptr);
174                 tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
175                 ast_copy_string(tableptr->connection, connection, lenconnection + 1);
176                 ast_copy_string(tableptr->table, table, lentable + 1);
177
178                 tableptr->usegmtime = 0;
179                 if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "usegmtime"))) {
180                         tableptr->usegmtime = ast_true(tmp);
181                 }
182
183                 tableptr->allowleapsec = 1;
184                 if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "allowleapsecond"))) {
185                         tableptr->allowleapsec = ast_true(tmp);
186                 }
187
188                 ast_verb(3, "Found CEL table %s@%s.\n", tableptr->table, tableptr->connection);
189
190                 /* Check for filters first */
191                 for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
192                         if (strncmp(var->name, "filter", 6) == 0) {
193                                 char *celvar = ast_strdupa(var->name + 6);
194                                 celvar = ast_strip(celvar);
195                                 ast_verb(3, "Found filter %s for cel variable %s in %s@%s\n", var->value, celvar, tableptr->table, tableptr->connection);
196
197                                 entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(celvar) + 1 + strlen(var->value) + 1);
198                                 if (!entry) {
199                                         ast_log(LOG_ERROR, "Out of memory creating filter entry for CEL variable '%s' in table '%s' on connection '%s'\n", celvar, table, connection);
200                                         res = -1;
201                                         break;
202                                 }
203
204                                 /* NULL column entry means this isn't a column in the database */
205                                 entry->name = NULL;
206                                 entry->celname = (char *)entry + sizeof(*entry);
207                                 entry->filtervalue = (char *)entry + sizeof(*entry) + strlen(celvar) + 1;
208                                 strcpy(entry->celname, celvar);
209                                 strcpy(entry->filtervalue, var->value);
210
211                                 AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
212                         }
213                 }
214
215                 while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
216                         char *celvar = "", *staticvalue = "";
217
218                         SQLGetData(stmt,  4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
219
220                         /* Is there an alias for this column? */
221
222                         /* NOTE: This seems like a non-optimal parse method, but I'm going
223                          * for user configuration readability, rather than fast parsing. We
224                          * really don't parse this file all that often, anyway.
225                          */
226                         for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
227                                 if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, columnname) == 0) {
228                                         char *alias = ast_strdupa(var->name + 5);
229                                         celvar = ast_strip(alias);
230                                         ast_verb(3, "Found alias %s for column %s in %s@%s\n", celvar, columnname, tableptr->table, tableptr->connection);
231                                         break;
232                                 } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, columnname) == 0) {
233                                         char *item = ast_strdupa(var->name + 6);
234                                         item = ast_strip(item);
235                                         if (item[0] == '"' && item[strlen(item) - 1] == '"') {
236                                                 /* Remove surrounding quotes */
237                                                 item[strlen(item) - 1] = '\0';
238                                                 item++;
239                                         }
240                                         staticvalue = item;
241                                 }
242                         }
243
244                         entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1 + strlen(celvar) + 1 + strlen(staticvalue) + 1);
245                         if (!entry) {
246                                 ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
247                                 res = -1;
248                                 break;
249                         }
250                         entry->name = (char *)entry + sizeof(*entry);
251                         strcpy(entry->name, columnname);
252
253                         if (!ast_strlen_zero(celvar)) {
254                                 entry->celname = entry->name + strlen(columnname) + 1;
255                                 strcpy(entry->celname, celvar);
256                         } else { /* Point to same place as the column name */
257                                 entry->celname = (char *)entry + sizeof(*entry);
258                         }
259
260                         if (!ast_strlen_zero(staticvalue)) {
261                                 entry->staticvalue = entry->celname + strlen(entry->celname) + 1;
262                                 strcpy(entry->staticvalue, staticvalue);
263                         }
264
265                         SQLGetData(stmt,  5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
266                         SQLGetData(stmt,  7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
267                         SQLGetData(stmt,  9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
268                         SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
269                         SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
270                         SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
271
272                         /* Specification states that the octenlen should be the maximum number of bytes
273                          * returned in a char or binary column, but it seems that some drivers just set
274                          * it to NULL. (Bad Postgres! No biscuit!) */
275                         if (entry->octetlen == 0)
276                                 entry->octetlen = entry->size;
277
278                         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);
279                         /* Insert column info into column list */
280                         AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
281                         res = 0;
282                 }
283
284                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
285                 ast_odbc_release_obj(obj);
286
287                 if (AST_LIST_FIRST(&(tableptr->columns)))
288                         AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
289                 else
290                         ast_free(tableptr);
291         }
292         ast_config_destroy(cfg);
293         return res;
294 }
295
296 static int free_config(void)
297 {
298         struct tables *table;
299         struct columns *entry;
300         while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
301                 while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
302                         ast_free(entry);
303                 }
304                 ast_free(table);
305         }
306         return 0;
307 }
308
309 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
310 {
311         int res, i;
312         char *sql = data;
313         SQLHSTMT stmt;
314         SQLINTEGER nativeerror = 0, numfields = 0;
315         SQLSMALLINT diagbytes = 0;
316         unsigned char state[10], diagnostic[256];
317
318         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
319         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
320                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
321                 return NULL;
322         }
323
324         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
325         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
326                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
327                 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
328                 for (i = 0; i < numfields; i++) {
329                         SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
330                         ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
331                         if (i > 10) {
332                                 ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
333                                 break;
334                         }
335                 }
336                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
337                 return NULL;
338         }
339
340         return stmt;
341 }
342
343 #define LENGTHEN_BUF(size, var_sql)                                                                                                             \
344                         do {                                                                                                                            \
345                                 /* Lengthen buffer, if necessary */                                                             \
346                                 if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) {               \
347                                         if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
348                                                 ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CEL '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
349                                                 ast_free(sql);                                                                                  \
350                                                 ast_free(sql2);                                                                                 \
351                                                 AST_RWLIST_UNLOCK(&odbc_tables);                                                \
352                                                 return;                                                                                                 \
353                                         }                                                                                                                       \
354                                 }                                                                                                                               \
355                         } while (0)
356
357 #define LENGTHEN_BUF1(size) \
358         LENGTHEN_BUF(size, sql);
359
360 #define LENGTHEN_BUF2(size) \
361         LENGTHEN_BUF(size, sql2);
362
363 static void odbc_log(struct ast_event *event)
364 {
365         struct tables *tableptr;
366         struct columns *entry;
367         struct odbc_obj *obj;
368         struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
369         char *tmp;
370         char colbuf[1024], *colptr;
371         SQLHSTMT stmt = NULL;
372         SQLLEN rows = 0;
373         struct ast_cel_event_record record = {
374                 .version = AST_CEL_EVENT_RECORD_VERSION,
375         };
376
377         if (ast_cel_fill_record(event, &record)) {
378                 return;
379         }
380
381         if (!sql || !sql2) {
382                 if (sql)
383                         ast_free(sql);
384                 if (sql2)
385                         ast_free(sql2);
386                 return;
387         }
388
389         if (AST_RWLIST_RDLOCK(&odbc_tables)) {
390                 ast_log(LOG_ERROR, "Unable to lock table list.  Insert CEL(s) failed.\n");
391                 ast_free(sql);
392                 ast_free(sql2);
393                 return;
394         }
395
396         AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
397                 char *separator = "";
398                 ast_str_set(&sql, 0, "INSERT INTO %s (", tableptr->table);
399                 ast_str_set(&sql2, 0, " VALUES (");
400
401                 /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
402                 if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
403                         ast_log(LOG_WARNING, "Unable to retrieve database handle for '%s:%s'.  CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
404                         continue;
405                 }
406
407                 AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
408                         int datefield = 0;
409                         int unknown = 0;
410                         if (strcasecmp(entry->celname, "eventtime") == 0) {
411                                 datefield = 1;
412                         }
413
414                         /* Check if we have a similarly named variable */
415                         if (entry->staticvalue) {
416                                 colptr = ast_strdupa(entry->staticvalue);
417                         } else if (datefield) {
418                                 struct timeval date_tv = record.event_time;
419                                 struct ast_tm tm = { 0, };
420                                 ast_localtime(&date_tv, &tm, tableptr->usegmtime ? "UTC" : NULL);
421                                 /* SQL server 2008 added datetime2 and datetimeoffset data types, that
422                                    are reported to SQLColumns() as SQL_WVARCHAR, according to "Enhanced
423                                    Date/Time Type Behavior with Previous SQL Server Versions (ODBC)".
424                                    Here we format the event time with fraction seconds, so these new
425                                    column types will be set to high-precision event time. However, 'date'
426                                    and 'time' columns, also newly introduced, reported as SQL_WVARCHAR
427                                    too, and insertion of the value formatted here into these will fail.
428                                    This should be ok, however, as nobody is going to store just event
429                                    date or just time for CDR purposes.
430                                  */
431                                 ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S.%6q", &tm);
432                                 colptr = colbuf;
433                         } else {
434                                 if (strcmp(entry->celname, "userdeftype") == 0) {
435                                         ast_copy_string(colbuf, record.user_defined_name, sizeof(colbuf));
436                                 } else if (strcmp(entry->celname, "cid_name") == 0) {
437                                         ast_copy_string(colbuf, record.caller_id_name, sizeof(colbuf));
438                                 } else if (strcmp(entry->celname, "cid_num") == 0) {
439                                         ast_copy_string(colbuf, record.caller_id_num, sizeof(colbuf));
440                                 } else if (strcmp(entry->celname, "cid_ani") == 0) {
441                                         ast_copy_string(colbuf, record.caller_id_ani, sizeof(colbuf));
442                                 } else if (strcmp(entry->celname, "cid_rdnis") == 0) {
443                                         ast_copy_string(colbuf, record.caller_id_rdnis, sizeof(colbuf));
444                                 } else if (strcmp(entry->celname, "cid_dnid") == 0) {
445                                         ast_copy_string(colbuf, record.caller_id_dnid, sizeof(colbuf));
446                                 } else if (strcmp(entry->celname, "exten") == 0) {
447                                         ast_copy_string(colbuf, record.extension, sizeof(colbuf));
448                                 } else if (strcmp(entry->celname, "context") == 0) {
449                                         ast_copy_string(colbuf, record.context, sizeof(colbuf));
450                                 } else if (strcmp(entry->celname, "channame") == 0) {
451                                         ast_copy_string(colbuf, record.channel_name, sizeof(colbuf));
452                                 } else if (strcmp(entry->celname, "appname") == 0) {
453                                         ast_copy_string(colbuf, record.application_name, sizeof(colbuf));
454                                 } else if (strcmp(entry->celname, "appdata") == 0) {
455                                         ast_copy_string(colbuf, record.application_data, sizeof(colbuf));
456                                 } else if (strcmp(entry->celname, "accountcode") == 0) {
457                                         ast_copy_string(colbuf, record.account_code, sizeof(colbuf));
458                                 } else if (strcmp(entry->celname, "peeraccount") == 0) {
459                                         ast_copy_string(colbuf, record.peer_account, sizeof(colbuf));
460                                 } else if (strcmp(entry->celname, "uniqueid") == 0) {
461                                         ast_copy_string(colbuf, record.unique_id, sizeof(colbuf));
462                                 } else if (strcmp(entry->celname, "linkedid") == 0) {
463                                         ast_copy_string(colbuf, record.linked_id, sizeof(colbuf));
464                                 } else if (strcmp(entry->celname, "userfield") == 0) {
465                                         ast_copy_string(colbuf, record.user_field, sizeof(colbuf));
466                                 } else if (strcmp(entry->celname, "peer") == 0) {
467                                         ast_copy_string(colbuf, record.peer, sizeof(colbuf));
468                                 } else if (strcmp(entry->celname, "amaflags") == 0) {
469                                         snprintf(colbuf, sizeof(colbuf), "%u", record.amaflag);
470                                 } else if (strcmp(entry->celname, "extra") == 0) {
471                                         ast_copy_string(colbuf, record.extra, sizeof(colbuf));
472                                 } else if (strcmp(entry->celname, "eventtype") == 0) {
473                                         snprintf(colbuf, sizeof(colbuf), "%u", record.event_type);
474                                 } else {
475                                         colbuf[0] = 0;
476                                         unknown = 1;
477                                 }
478                                 colptr = colbuf;
479                         }
480
481                         if (colptr && !unknown) {
482                                 /* Check first if the column filters this entry.  Note that this
483                                  * is very specifically NOT ast_strlen_zero(), because the filter
484                                  * could legitimately specify that the field is blank, which is
485                                  * different from the field being unspecified (NULL). */
486                                 if (entry->filtervalue && strcasecmp(colptr, entry->filtervalue) != 0) {
487                                         ast_verb(4, "CEL column '%s' with value '%s' does not match filter of"
488                                                 " '%s'.  Cancelling this CEL.\n",
489                                                 entry->celname, colptr, entry->filtervalue);
490                                         goto early_release;
491                                 }
492
493                                 /* Only a filter? */
494                                 if (ast_strlen_zero(entry->name))
495                                         continue;
496
497                                 LENGTHEN_BUF1(strlen(entry->name));
498
499                                 switch (entry->type) {
500                                 case SQL_CHAR:
501                                 case SQL_VARCHAR:
502                                 case SQL_LONGVARCHAR:
503 #ifdef HAVE_ODBC_WCHAR
504                                 case SQL_WCHAR:
505                                 case SQL_WVARCHAR:
506                                 case SQL_WLONGVARCHAR:
507 #endif
508                                 case SQL_BINARY:
509                                 case SQL_VARBINARY:
510                                 case SQL_LONGVARBINARY:
511                                 case SQL_GUID:
512                                         /* For these two field names, get the rendered form, instead of the raw
513                                          * form (but only when we're dealing with a character-based field).
514                                          */
515                                         if (strcasecmp(entry->name, "eventtype") == 0) {
516                                                 const char *event_name;
517
518                                                 event_name = (!cel_show_user_def
519                                                         && record.event_type == AST_CEL_USER_DEFINED)
520                                                         ? record.user_defined_name : record.event_name;
521                                                 snprintf(colbuf, sizeof(colbuf), "%s", event_name);
522                                         }
523
524                                         /* Truncate too-long fields */
525                                         if (entry->type != SQL_GUID) {
526                                                 if (strlen(colptr) > entry->octetlen) {
527                                                         colptr[entry->octetlen] = '\0';
528                                                 }
529                                         }
530
531                                         ast_str_append(&sql, 0, "%s%s", separator, entry->name);
532                                         LENGTHEN_BUF2(strlen(colptr));
533
534                                         /* Encode value, with escaping */
535                                         ast_str_append(&sql2, 0, "%s'", separator);
536                                         for (tmp = colptr; *tmp; tmp++) {
537                                                 if (*tmp == '\'') {
538                                                         ast_str_append(&sql2, 0, "''");
539                                                 } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
540                                                         ast_str_append(&sql2, 0, "\\\\");
541                                                 } else {
542                                                         ast_str_append(&sql2, 0, "%c", *tmp);
543                                                 }
544                                         }
545                                         ast_str_append(&sql2, 0, "'");
546                                         break;
547                                 case SQL_TYPE_DATE:
548                                         if (ast_strlen_zero(colptr)) {
549                                                 continue;
550                                         } else {
551                                                 int year = 0, month = 0, day = 0;
552                                                 if (strcasecmp(entry->name, "eventdate") == 0) {
553                                                         struct ast_tm tm;
554                                                         ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
555                                                         year = tm.tm_year + 1900;
556                                                         month = tm.tm_mon + 1;
557                                                         day = tm.tm_mday;
558                                                 } else {
559                                                         if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
560                                                                 month <= 0 || month > 12 || day < 0 || day > 31 ||
561                                                                 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
562                                                                 (month == 2 && year % 400 == 0 && day > 29) ||
563                                                                 (month == 2 && year % 100 == 0 && day > 28) ||
564                                                                 (month == 2 && year % 4 == 0 && day > 29) ||
565                                                                 (month == 2 && year % 4 != 0 && day > 28)) {
566                                                                 ast_log(LOG_WARNING, "CEL variable %s is not a valid date ('%s').\n", entry->name, colptr);
567                                                                 continue;
568                                                         }
569
570                                                         if (year > 0 && year < 100) {
571                                                                 year += 2000;
572                                                         }
573                                                 }
574
575                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
576                                                 LENGTHEN_BUF2(17);
577                                                 ast_str_append(&sql2, 0, "%s{d '%04d-%02d-%02d'}", separator, year, month, day);
578                                         }
579                                         break;
580                                 case SQL_TYPE_TIME:
581                                         if (ast_strlen_zero(colptr)) {
582                                                 continue;
583                                         } else {
584                                                 int hour = 0, minute = 0, second = 0;
585                                                 if (strcasecmp(entry->name, "eventdate") == 0) {
586                                                         struct ast_tm tm;
587                                                         ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
588                                                         hour = tm.tm_hour;
589                                                         minute = tm.tm_min;
590                                                         second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
591                                                 } else {
592                                                         int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
593
594                                                         if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > (tableptr->allowleapsec ? 60 : 59)) {
595                                                                 ast_log(LOG_WARNING, "CEL variable %s is not a valid time ('%s').\n", entry->name, colptr);
596                                                                 continue;
597                                                         }
598                                                 }
599
600                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
601                                                 LENGTHEN_BUF2(15);
602                                                 ast_str_append(&sql2, 0, "%s{t '%02d:%02d:%02d'}", separator, hour, minute, second);
603                                         }
604                                         break;
605                                 case SQL_TYPE_TIMESTAMP:
606                                 case SQL_TIMESTAMP:
607                                         if (ast_strlen_zero(colptr)) {
608                                                 continue;
609                                         } else {
610                                                 int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, fraction = 0;
611                                                 if (strcasecmp(entry->name, "eventdate") == 0) {
612                                                         struct ast_tm tm;
613                                                         ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
614                                                         year = tm.tm_year + 1900;
615                                                         month = tm.tm_mon + 1;
616                                                         day = tm.tm_mday;
617                                                         hour = tm.tm_hour;
618                                                         minute = tm.tm_min;
619                                                         second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
620                                                         fraction = tm.tm_usec;
621                                                 } else {
622                                                         int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%2d.%6d", &year, &month, &day, &hour, &minute, &second, &fraction);
623
624                                                         if ((count != 3 && count != 5 && count != 6 && count != 7) || year <= 0 ||
625                                                                 month <= 0 || month > 12 || day < 0 || day > 31 ||
626                                                                 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
627                                                                 (month == 2 && year % 400 == 0 && day > 29) ||
628                                                                 (month == 2 && year % 100 == 0 && day > 28) ||
629                                                                 (month == 2 && year % 4 == 0 && day > 29) ||
630                                                                 (month == 2 && year % 4 != 0 && day > 28) ||
631                                                                 hour > 23 || minute > 59 || second > (tableptr->allowleapsec ? 60 : 59) || hour < 0 || minute < 0 || second < 0 || fraction < 0) {
632                                                                 ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
633                                                                 continue;
634                                                         }
635
636                                                         if (year > 0 && year < 100) {
637                                                                 year += 2000;
638                                                         }
639                                                 }
640
641                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
642                                                 LENGTHEN_BUF2(27);
643                                                 ast_str_append(&sql2, 0, "%s{ts '%04d-%02d-%02d %02d:%02d:%02d.%d'}", separator, year, month, day, hour, minute, second, fraction);
644                                         }
645                                         break;
646                                 case SQL_INTEGER:
647                                         {
648                                                 int integer = 0;
649                                                 if (sscanf(colptr, "%30d", &integer) != 1) {
650                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
651                                                         continue;
652                                                 }
653
654                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
655                                                 LENGTHEN_BUF2(12);
656                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
657                                         }
658                                         break;
659                                 case SQL_BIGINT:
660                                         {
661                                                 long long integer = 0;
662                                                 int ret;
663                                                 if ((ret = sscanf(colptr, "%30lld", &integer)) != 1) {
664                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer. (%d - '%s')\n", entry->name, ret, colptr);
665                                                         continue;
666                                                 }
667
668                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
669                                                 LENGTHEN_BUF2(24);
670                                                 ast_str_append(&sql2, 0, "%s%lld", separator, integer);
671                                         }
672                                         break;
673                                 case SQL_SMALLINT:
674                                         {
675                                                 short integer = 0;
676                                                 if (sscanf(colptr, "%30hd", &integer) != 1) {
677                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
678                                                         continue;
679                                                 }
680
681                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
682                                                 LENGTHEN_BUF2(7);
683                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
684                                         }
685                                         break;
686                                 case SQL_TINYINT:
687                                         {
688                                                 signed char integer = 0;
689                                                 if (sscanf(colptr, "%30hhd", &integer) != 1) {
690                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
691                                                         continue;
692                                                 }
693
694                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
695                                                 LENGTHEN_BUF2(4);
696                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
697                                         }
698                                         break;
699                                 case SQL_BIT:
700                                         {
701                                                 signed char integer = 0;
702                                                 if (sscanf(colptr, "%30hhd", &integer) != 1) {
703                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
704                                                         continue;
705                                                 }
706                                                 if (integer != 0)
707                                                         integer = 1;
708
709                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
710                                                 LENGTHEN_BUF2(2);
711                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
712                                         }
713                                         break;
714                                 case SQL_NUMERIC:
715                                 case SQL_DECIMAL:
716                                         {
717                                                 double number = 0.0;
718                                                 if (sscanf(colptr, "%30lf", &number) != 1) {
719                                                         ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
720                                                         continue;
721                                                 }
722
723                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
724                                                 LENGTHEN_BUF2(entry->decimals + 2);
725                                                 ast_str_append(&sql2, 0, "%s%*.*lf", separator, entry->decimals, entry->radix, number);
726                                         }
727                                         break;
728                                 case SQL_FLOAT:
729                                 case SQL_REAL:
730                                 case SQL_DOUBLE:
731                                         {
732                                                 double number = 0.0;
733                                                 if (sscanf(colptr, "%30lf", &number) != 1) {
734                                                         ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
735                                                         continue;
736                                                 }
737
738                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
739                                                 LENGTHEN_BUF2(entry->decimals);
740                                                 ast_str_append(&sql2, 0, "%s%lf", separator, number);
741                                         }
742                                         break;
743                                 default:
744                                         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);
745                                         continue;
746                                 }
747                                 separator = ", ";
748                         }
749                 }
750
751                 /* Concatenate the two constructed buffers */
752                 LENGTHEN_BUF1(ast_str_strlen(sql2));
753                 ast_str_append(&sql, 0, ")");
754                 ast_str_append(&sql2, 0, ")");
755                 ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
756
757                 ast_debug(3, "Executing SQL statement: [%s]\n", ast_str_buffer(sql));
758                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, ast_str_buffer(sql));
759                 if (stmt) {
760                         SQLRowCount(stmt, &rows);
761                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
762                 }
763                 if (rows == 0) {
764                         ast_log(LOG_WARNING, "Insert failed on '%s:%s'.  CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
765                 }
766 early_release:
767                 ast_odbc_release_obj(obj);
768         }
769         AST_RWLIST_UNLOCK(&odbc_tables);
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
783 static int unload_module(void)
784 {
785         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
786                 ast_log(LOG_ERROR, "Unable to lock column list.  Unload failed.\n");
787                 return -1;
788         }
789
790         ast_cel_backend_unregister(ODBC_BACKEND_NAME);
791         free_config();
792         AST_RWLIST_UNLOCK(&odbc_tables);
793         AST_RWLIST_HEAD_DESTROY(&odbc_tables);
794         
795         return 0;
796 }
797
798 static int load_module(void)
799 {
800         AST_RWLIST_HEAD_INIT(&odbc_tables);
801
802         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
803                 ast_log(LOG_ERROR, "Unable to lock column list.  Load failed.\n");
804                 return AST_MODULE_LOAD_FAILURE;
805         }
806         load_config();
807         AST_RWLIST_UNLOCK(&odbc_tables);
808         if (ast_cel_backend_register(ODBC_BACKEND_NAME, odbc_log)) {
809                 ast_log(LOG_ERROR, "Unable to subscribe to CEL events\n");
810                 return AST_MODULE_LOAD_FAILURE;
811         }
812         return AST_MODULE_LOAD_SUCCESS;
813 }
814
815 static int reload(void)
816 {
817         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
818                 ast_log(LOG_ERROR, "Unable to lock column list.  Reload failed.\n");
819                 return AST_MODULE_LOAD_FAILURE;
820         }
821
822         free_config();
823         load_config();
824         AST_RWLIST_UNLOCK(&odbc_tables);
825         return AST_MODULE_LOAD_SUCCESS;
826 }
827
828 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, ODBC_BACKEND_NAME,
829         .support_level = AST_MODULE_SUPPORT_CORE,
830         .load = load_module,
831         .unload = unload_module,
832         .reload = reload,
833         .load_pri = AST_MODPRI_CDR_DRIVER,
834 );
835