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