Merge "rtp_engine.h: Remove extraneous semicolons."
[asterisk/asterisk.git] / cel / cel_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008 Digium
5  *
6  * Adapted from cdr_adaptive_odbc:
7  * Tilghman Lesher <tlesher AT digium DOT com>
8  * by Steve Murphy
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief ODBC CEL backend
24  *
25  * \author Tilghman Lesher \verbatim <tlesher AT digium DOT com> \endverbatim
26  * \ingroup cel_drivers
27  */
28
29 /*** MODULEINFO
30         <depend>res_odbc</depend>
31         <support_level>core</support_level>
32  ***/
33
34 #include "asterisk.h"
35
36 ASTERISK_REGISTER_FILE()
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
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         return res;
295 }
296
297 static int free_config(void)
298 {
299         struct tables *table;
300         struct columns *entry;
301         while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
302                 while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
303                         ast_free(entry);
304                 }
305                 ast_free(table);
306         }
307         return 0;
308 }
309
310 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
311 {
312         int res, i;
313         char *sql = data;
314         SQLHSTMT stmt;
315         SQLINTEGER nativeerror = 0, numfields = 0;
316         SQLSMALLINT diagbytes = 0;
317         unsigned char state[10], diagnostic[256];
318
319         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
320         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
321                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
322                 return NULL;
323         }
324
325         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
326         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
327                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
328                 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
329                 for (i = 0; i < numfields; i++) {
330                         SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
331                         ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
332                         if (i > 10) {
333                                 ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
334                                 break;
335                         }
336                 }
337                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
338                 return NULL;
339         }
340
341         return stmt;
342 }
343
344 #define LENGTHEN_BUF(size, var_sql)                                                                                                             \
345                         do {                                                                                                                            \
346                                 /* Lengthen buffer, if necessary */                                                             \
347                                 if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) {               \
348                                         if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
349                                                 ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CEL '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
350                                                 ast_free(sql);                                                                                  \
351                                                 ast_free(sql2);                                                                                 \
352                                                 AST_RWLIST_UNLOCK(&odbc_tables);                                                \
353                                                 return;                                                                                                 \
354                                         }                                                                                                                       \
355                                 }                                                                                                                               \
356                         } while (0)
357
358 #define LENGTHEN_BUF1(size) \
359         LENGTHEN_BUF(size, sql);
360
361 #define LENGTHEN_BUF2(size) \
362         LENGTHEN_BUF(size, sql2);
363
364 static void odbc_log(struct ast_event *event)
365 {
366         struct tables *tableptr;
367         struct columns *entry;
368         struct odbc_obj *obj;
369         struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
370         char *tmp;
371         char colbuf[1024], *colptr;
372         SQLHSTMT stmt = NULL;
373         SQLLEN rows = 0;
374         struct ast_cel_event_record record = {
375                 .version = AST_CEL_EVENT_RECORD_VERSION,
376         };
377
378         if (ast_cel_fill_record(event, &record)) {
379                 return;
380         }
381
382         if (!sql || !sql2) {
383                 if (sql)
384                         ast_free(sql);
385                 if (sql2)
386                         ast_free(sql2);
387                 return;
388         }
389
390         if (AST_RWLIST_RDLOCK(&odbc_tables)) {
391                 ast_log(LOG_ERROR, "Unable to lock table list.  Insert CEL(s) failed.\n");
392                 ast_free(sql);
393                 ast_free(sql2);
394                 return;
395         }
396
397         AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
398                 char *separator = "";
399                 ast_str_set(&sql, 0, "INSERT INTO %s (", tableptr->table);
400                 ast_str_set(&sql2, 0, " VALUES (");
401
402                 /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
403                 if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
404                         ast_log(LOG_WARNING, "Unable to retrieve database handle for '%s:%s'.  CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
405                         continue;
406                 }
407
408                 AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
409                         int datefield = 0;
410                         int unknown = 0;
411                         if (strcasecmp(entry->celname, "eventtime") == 0) {
412                                 datefield = 1;
413                         }
414
415                         /* Check if we have a similarly named variable */
416                         if (entry->staticvalue) {
417                                 colptr = ast_strdupa(entry->staticvalue);
418                         } else if (datefield) {
419                                 struct timeval date_tv = record.event_time;
420                                 struct ast_tm tm = { 0, };
421                                 ast_localtime(&date_tv, &tm, tableptr->usegmtime ? "UTC" : NULL);
422                                 /* SQL server 2008 added datetime2 and datetimeoffset data types, that
423                                    are reported to SQLColumns() as SQL_WVARCHAR, according to "Enhanced
424                                    Date/Time Type Behavior with Previous SQL Server Versions (ODBC)".
425                                    Here we format the event time with fraction seconds, so these new
426                                    column types will be set to high-precision event time. However, 'date'
427                                    and 'time' columns, also newly introduced, reported as SQL_WVARCHAR
428                                    too, and insertion of the value formatted here into these will fail.
429                                    This should be ok, however, as nobody is going to store just event
430                                    date or just time for CDR purposes.
431                                  */
432                                 ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S.%6q", &tm);
433                                 colptr = colbuf;
434                         } else {
435                                 if (strcmp(entry->celname, "userdeftype") == 0) {
436                                         ast_copy_string(colbuf, record.user_defined_name, sizeof(colbuf));
437                                 } else if (strcmp(entry->celname, "cid_name") == 0) {
438                                         ast_copy_string(colbuf, record.caller_id_name, sizeof(colbuf));
439                                 } else if (strcmp(entry->celname, "cid_num") == 0) {
440                                         ast_copy_string(colbuf, record.caller_id_num, sizeof(colbuf));
441                                 } else if (strcmp(entry->celname, "cid_ani") == 0) {
442                                         ast_copy_string(colbuf, record.caller_id_ani, sizeof(colbuf));
443                                 } else if (strcmp(entry->celname, "cid_rdnis") == 0) {
444                                         ast_copy_string(colbuf, record.caller_id_rdnis, sizeof(colbuf));
445                                 } else if (strcmp(entry->celname, "cid_dnid") == 0) {
446                                         ast_copy_string(colbuf, record.caller_id_dnid, sizeof(colbuf));
447                                 } else if (strcmp(entry->celname, "exten") == 0) {
448                                         ast_copy_string(colbuf, record.extension, sizeof(colbuf));
449                                 } else if (strcmp(entry->celname, "context") == 0) {
450                                         ast_copy_string(colbuf, record.context, sizeof(colbuf));
451                                 } else if (strcmp(entry->celname, "channame") == 0) {
452                                         ast_copy_string(colbuf, record.channel_name, sizeof(colbuf));
453                                 } else if (strcmp(entry->celname, "appname") == 0) {
454                                         ast_copy_string(colbuf, record.application_name, sizeof(colbuf));
455                                 } else if (strcmp(entry->celname, "appdata") == 0) {
456                                         ast_copy_string(colbuf, record.application_data, sizeof(colbuf));
457                                 } else if (strcmp(entry->celname, "accountcode") == 0) {
458                                         ast_copy_string(colbuf, record.account_code, sizeof(colbuf));
459                                 } else if (strcmp(entry->celname, "peeraccount") == 0) {
460                                         ast_copy_string(colbuf, record.peer_account, sizeof(colbuf));
461                                 } else if (strcmp(entry->celname, "uniqueid") == 0) {
462                                         ast_copy_string(colbuf, record.unique_id, sizeof(colbuf));
463                                 } else if (strcmp(entry->celname, "linkedid") == 0) {
464                                         ast_copy_string(colbuf, record.linked_id, sizeof(colbuf));
465                                 } else if (strcmp(entry->celname, "userfield") == 0) {
466                                         ast_copy_string(colbuf, record.user_field, sizeof(colbuf));
467                                 } else if (strcmp(entry->celname, "peer") == 0) {
468                                         ast_copy_string(colbuf, record.peer, sizeof(colbuf));
469                                 } else if (strcmp(entry->celname, "amaflags") == 0) {
470                                         snprintf(colbuf, sizeof(colbuf), "%u", record.amaflag);
471                                 } else if (strcmp(entry->celname, "extra") == 0) {
472                                         ast_copy_string(colbuf, record.extra, sizeof(colbuf));
473                                 } else if (strcmp(entry->celname, "eventtype") == 0) {
474                                         snprintf(colbuf, sizeof(colbuf), "%u", record.event_type);
475                                 } else {
476                                         colbuf[0] = 0;
477                                         unknown = 1;
478                                 }
479                                 colptr = colbuf;
480                         }
481
482                         if (colptr && !unknown) {
483                                 /* Check first if the column filters this entry.  Note that this
484                                  * is very specifically NOT ast_strlen_zero(), because the filter
485                                  * could legitimately specify that the field is blank, which is
486                                  * different from the field being unspecified (NULL). */
487                                 if (entry->filtervalue && strcasecmp(colptr, entry->filtervalue) != 0) {
488                                         ast_verb(4, "CEL column '%s' with value '%s' does not match filter of"
489                                                 " '%s'.  Cancelling this CEL.\n",
490                                                 entry->celname, colptr, entry->filtervalue);
491                                         goto early_release;
492                                 }
493
494                                 /* Only a filter? */
495                                 if (ast_strlen_zero(entry->name))
496                                         continue;
497
498                                 LENGTHEN_BUF1(strlen(entry->name));
499
500                                 switch (entry->type) {
501                                 case SQL_CHAR:
502                                 case SQL_VARCHAR:
503                                 case SQL_LONGVARCHAR:
504 #ifdef HAVE_ODBC_WCHAR
505                                 case SQL_WCHAR:
506                                 case SQL_WVARCHAR:
507                                 case SQL_WLONGVARCHAR:
508 #endif
509                                 case SQL_BINARY:
510                                 case SQL_VARBINARY:
511                                 case SQL_LONGVARBINARY:
512                                 case SQL_GUID:
513                                         /* For these two field names, get the rendered form, instead of the raw
514                                          * form (but only when we're dealing with a character-based field).
515                                          */
516                                         if (strcasecmp(entry->name, "eventtype") == 0) {
517                                                 const char *event_name;
518
519                                                 event_name = (!cel_show_user_def
520                                                         && record.event_type == AST_CEL_USER_DEFINED)
521                                                         ? record.user_defined_name : record.event_name;
522                                                 snprintf(colbuf, sizeof(colbuf), "%s", event_name);
523                                         }
524
525                                         /* Truncate too-long fields */
526                                         if (entry->type != SQL_GUID) {
527                                                 if (strlen(colptr) > entry->octetlen) {
528                                                         colptr[entry->octetlen] = '\0';
529                                                 }
530                                         }
531
532                                         ast_str_append(&sql, 0, "%s%s", separator, entry->name);
533                                         LENGTHEN_BUF2(strlen(colptr));
534
535                                         /* Encode value, with escaping */
536                                         ast_str_append(&sql2, 0, "%s'", separator);
537                                         for (tmp = colptr; *tmp; tmp++) {
538                                                 if (*tmp == '\'') {
539                                                         ast_str_append(&sql2, 0, "''");
540                                                 } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
541                                                         ast_str_append(&sql2, 0, "\\\\");
542                                                 } else {
543                                                         ast_str_append(&sql2, 0, "%c", *tmp);
544                                                 }
545                                         }
546                                         ast_str_append(&sql2, 0, "'");
547                                         break;
548                                 case SQL_TYPE_DATE:
549                                         if (ast_strlen_zero(colptr)) {
550                                                 continue;
551                                         } else {
552                                                 int year = 0, month = 0, day = 0;
553                                                 if (strcasecmp(entry->name, "eventdate") == 0) {
554                                                         struct ast_tm tm;
555                                                         ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
556                                                         year = tm.tm_year + 1900;
557                                                         month = tm.tm_mon + 1;
558                                                         day = tm.tm_mday;
559                                                 } else {
560                                                         if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
561                                                                 month <= 0 || month > 12 || day < 0 || day > 31 ||
562                                                                 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
563                                                                 (month == 2 && year % 400 == 0 && day > 29) ||
564                                                                 (month == 2 && year % 100 == 0 && day > 28) ||
565                                                                 (month == 2 && year % 4 == 0 && day > 29) ||
566                                                                 (month == 2 && year % 4 != 0 && day > 28)) {
567                                                                 ast_log(LOG_WARNING, "CEL variable %s is not a valid date ('%s').\n", entry->name, colptr);
568                                                                 continue;
569                                                         }
570
571                                                         if (year > 0 && year < 100) {
572                                                                 year += 2000;
573                                                         }
574                                                 }
575
576                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
577                                                 LENGTHEN_BUF2(17);
578                                                 ast_str_append(&sql2, 0, "%s{d '%04d-%02d-%02d'}", separator, year, month, day);
579                                         }
580                                         break;
581                                 case SQL_TYPE_TIME:
582                                         if (ast_strlen_zero(colptr)) {
583                                                 continue;
584                                         } else {
585                                                 int hour = 0, minute = 0, second = 0;
586                                                 if (strcasecmp(entry->name, "eventdate") == 0) {
587                                                         struct ast_tm tm;
588                                                         ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
589                                                         hour = tm.tm_hour;
590                                                         minute = tm.tm_min;
591                                                         second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
592                                                 } else {
593                                                         int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
594
595                                                         if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > (tableptr->allowleapsec ? 60 : 59)) {
596                                                                 ast_log(LOG_WARNING, "CEL variable %s is not a valid time ('%s').\n", entry->name, colptr);
597                                                                 continue;
598                                                         }
599                                                 }
600
601                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
602                                                 LENGTHEN_BUF2(15);
603                                                 ast_str_append(&sql2, 0, "%s{t '%02d:%02d:%02d'}", separator, hour, minute, second);
604                                         }
605                                         break;
606                                 case SQL_TYPE_TIMESTAMP:
607                                 case SQL_TIMESTAMP:
608                                         if (ast_strlen_zero(colptr)) {
609                                                 continue;
610                                         } else {
611                                                 int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, fraction = 0;
612                                                 if (strcasecmp(entry->name, "eventdate") == 0) {
613                                                         struct ast_tm tm;
614                                                         ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
615                                                         year = tm.tm_year + 1900;
616                                                         month = tm.tm_mon + 1;
617                                                         day = tm.tm_mday;
618                                                         hour = tm.tm_hour;
619                                                         minute = tm.tm_min;
620                                                         second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
621                                                         fraction = tm.tm_usec;
622                                                 } else {
623                                                         int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%2d.%6d", &year, &month, &day, &hour, &minute, &second, &fraction);
624
625                                                         if ((count != 3 && count != 5 && count != 6 && count != 7) || year <= 0 ||
626                                                                 month <= 0 || month > 12 || day < 0 || day > 31 ||
627                                                                 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
628                                                                 (month == 2 && year % 400 == 0 && day > 29) ||
629                                                                 (month == 2 && year % 100 == 0 && day > 28) ||
630                                                                 (month == 2 && year % 4 == 0 && day > 29) ||
631                                                                 (month == 2 && year % 4 != 0 && day > 28) ||
632                                                                 hour > 23 || minute > 59 || second > (tableptr->allowleapsec ? 60 : 59) || hour < 0 || minute < 0 || second < 0 || fraction < 0) {
633                                                                 ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
634                                                                 continue;
635                                                         }
636
637                                                         if (year > 0 && year < 100) {
638                                                                 year += 2000;
639                                                         }
640                                                 }
641
642                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
643                                                 LENGTHEN_BUF2(27);
644                                                 ast_str_append(&sql2, 0, "%s{ts '%04d-%02d-%02d %02d:%02d:%02d.%d'}", separator, year, month, day, hour, minute, second, fraction);
645                                         }
646                                         break;
647                                 case SQL_INTEGER:
648                                         {
649                                                 int integer = 0;
650                                                 if (sscanf(colptr, "%30d", &integer) != 1) {
651                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
652                                                         continue;
653                                                 }
654
655                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
656                                                 LENGTHEN_BUF2(12);
657                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
658                                         }
659                                         break;
660                                 case SQL_BIGINT:
661                                         {
662                                                 long long integer = 0;
663                                                 int ret;
664                                                 if ((ret = sscanf(colptr, "%30lld", &integer)) != 1) {
665                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer. (%d - '%s')\n", entry->name, ret, colptr);
666                                                         continue;
667                                                 }
668
669                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
670                                                 LENGTHEN_BUF2(24);
671                                                 ast_str_append(&sql2, 0, "%s%lld", separator, integer);
672                                         }
673                                         break;
674                                 case SQL_SMALLINT:
675                                         {
676                                                 short integer = 0;
677                                                 if (sscanf(colptr, "%30hd", &integer) != 1) {
678                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
679                                                         continue;
680                                                 }
681
682                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
683                                                 LENGTHEN_BUF2(7);
684                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
685                                         }
686                                         break;
687                                 case SQL_TINYINT:
688                                         {
689                                                 signed char integer = 0;
690                                                 if (sscanf(colptr, "%30hhd", &integer) != 1) {
691                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
692                                                         continue;
693                                                 }
694
695                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
696                                                 LENGTHEN_BUF2(4);
697                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
698                                         }
699                                         break;
700                                 case SQL_BIT:
701                                         {
702                                                 signed char integer = 0;
703                                                 if (sscanf(colptr, "%30hhd", &integer) != 1) {
704                                                         ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
705                                                         continue;
706                                                 }
707                                                 if (integer != 0)
708                                                         integer = 1;
709
710                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
711                                                 LENGTHEN_BUF2(2);
712                                                 ast_str_append(&sql2, 0, "%s%d", separator, integer);
713                                         }
714                                         break;
715                                 case SQL_NUMERIC:
716                                 case SQL_DECIMAL:
717                                         {
718                                                 double number = 0.0;
719                                                 if (sscanf(colptr, "%30lf", &number) != 1) {
720                                                         ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
721                                                         continue;
722                                                 }
723
724                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
725                                                 LENGTHEN_BUF2(entry->decimals + 2);
726                                                 ast_str_append(&sql2, 0, "%s%*.*lf", separator, entry->decimals, entry->radix, number);
727                                         }
728                                         break;
729                                 case SQL_FLOAT:
730                                 case SQL_REAL:
731                                 case SQL_DOUBLE:
732                                         {
733                                                 double number = 0.0;
734                                                 if (sscanf(colptr, "%30lf", &number) != 1) {
735                                                         ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
736                                                         continue;
737                                                 }
738
739                                                 ast_str_append(&sql, 0, "%s%s", separator, entry->name);
740                                                 LENGTHEN_BUF2(entry->decimals);
741                                                 ast_str_append(&sql2, 0, "%s%lf", separator, number);
742                                         }
743                                         break;
744                                 default:
745                                         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);
746                                         continue;
747                                 }
748                                 separator = ", ";
749                         }
750                 }
751
752                 /* Concatenate the two constructed buffers */
753                 LENGTHEN_BUF1(ast_str_strlen(sql2));
754                 ast_str_append(&sql, 0, ")");
755                 ast_str_append(&sql2, 0, ")");
756                 ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
757
758                 ast_debug(3, "Executing SQL statement: [%s]\n", ast_str_buffer(sql));
759                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, ast_str_buffer(sql));
760                 if (stmt) {
761                         SQLRowCount(stmt, &rows);
762                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
763                 }
764                 if (rows == 0) {
765                         ast_log(LOG_WARNING, "Insert failed on '%s:%s'.  CEL failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
766                 }
767 early_release:
768                 ast_odbc_release_obj(obj);
769         }
770         AST_RWLIST_UNLOCK(&odbc_tables);
771
772         /* Next time, just allocate buffers that are that big to start with. */
773         if (ast_str_strlen(sql) > maxsize) {
774                 maxsize = ast_str_strlen(sql);
775         }
776         if (ast_str_strlen(sql2) > maxsize2) {
777                 maxsize2 = ast_str_strlen(sql2);
778         }
779
780         ast_free(sql);
781         ast_free(sql2);
782 }
783
784 static int unload_module(void)
785 {
786         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
787                 ast_log(LOG_ERROR, "Unable to lock column list.  Unload failed.\n");
788                 return -1;
789         }
790
791         ast_cel_backend_unregister(ODBC_BACKEND_NAME);
792         free_config();
793         AST_RWLIST_UNLOCK(&odbc_tables);
794         AST_RWLIST_HEAD_DESTROY(&odbc_tables);
795         
796         return 0;
797 }
798
799 static int load_module(void)
800 {
801         AST_RWLIST_HEAD_INIT(&odbc_tables);
802
803         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
804                 ast_log(LOG_ERROR, "Unable to lock column list.  Load failed.\n");
805                 return AST_MODULE_LOAD_FAILURE;
806         }
807         load_config();
808         AST_RWLIST_UNLOCK(&odbc_tables);
809         if (ast_cel_backend_register(ODBC_BACKEND_NAME, odbc_log)) {
810                 ast_log(LOG_ERROR, "Unable to subscribe to CEL events\n");
811                 return AST_MODULE_LOAD_FAILURE;
812         }
813         return AST_MODULE_LOAD_SUCCESS;
814 }
815
816 static int reload(void)
817 {
818         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
819                 ast_log(LOG_ERROR, "Unable to lock column list.  Reload failed.\n");
820                 return AST_MODULE_LOAD_FAILURE;
821         }
822
823         free_config();
824         load_config();
825         AST_RWLIST_UNLOCK(&odbc_tables);
826         return AST_MODULE_LOAD_SUCCESS;
827 }
828
829 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, ODBC_BACKEND_NAME,
830         .support_level = AST_MODULE_SUPPORT_CORE,
831         .load = load_module,
832         .unload = unload_module,
833         .reload = reload,
834         .load_pri = AST_MODPRI_CDR_DRIVER,
835 );
836