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