Merge anthm's ODBC sanity check fix (bug #3529)
[asterisk/asterisk.git] / res / res_config_odbc.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * res_config_odbc.c <odbc+odbc plugin for portable configuration engine >
9  * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
10  */
11
12 #include <asterisk/file.h>
13 #include <asterisk/logger.h>
14 #include <asterisk/channel.h>
15 #include <asterisk/pbx.h>
16 #include <asterisk/config.h>
17 #include <asterisk/module.h>
18 #include <asterisk/lock.h>
19 #include <asterisk/options.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <asterisk/res_odbc.h>
24 #include <asterisk/utils.h>
25
26 static char *tdesc = "ODBC Configuration";
27 STANDARD_LOCAL_USER;
28
29 LOCAL_USER_DECL;
30
31 static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap)
32 {
33         odbc_obj *obj;
34         SQLHSTMT stmt;
35         char sql[1024];
36         char coltitle[256];
37         char rowdata[2048];
38         char *op;
39         const char *newparam, *newval;
40         char *stringp;
41         char *chunk;
42         SQLSMALLINT collen;
43         int res;
44         int x;
45         struct ast_variable *var=NULL, *prev=NULL;
46         SQLLEN rowcount=0;
47         SQLULEN colsize;
48         SQLSMALLINT colcount=0;
49         SQLSMALLINT datatype;
50         SQLSMALLINT decimaldigits;
51         SQLSMALLINT nullable;
52         va_list aq;
53         
54         va_copy(aq, ap);
55         
56         
57         if (!table)
58                 return NULL;
59
60         obj = fetch_odbc_obj(database, 0);
61         if (!obj)
62                 return NULL;
63
64         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
65         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
66                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
67                 return NULL;
68         }
69
70         newparam = va_arg(aq, const char *);
71         if (!newparam)  {
72                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
73                 return NULL;
74         }
75         newval = va_arg(aq, const char *);
76         if (!strchr(newparam, ' ')) op = " ="; else op = "";
77         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?", table, newparam, op);
78         while((newparam = va_arg(aq, const char *))) {
79                 if (!strchr(newparam, ' ')) op = " ="; else op = "";
80                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?", newparam, op);
81                 newval = va_arg(aq, const char *);
82         }
83         va_end(aq);
84         res = SQLPrepare(stmt, sql, SQL_NTS);
85         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
86                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
87                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
88                 return NULL;
89         }
90         
91         /* Now bind the parameters */
92         x = 1;
93
94         while((newparam = va_arg(ap, const char *))) {
95                 newval = va_arg(ap, const char *);
96                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
97         }
98         
99         res = odbc_smart_execute(obj, stmt);
100
101         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
102                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
103                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
104                 return NULL;
105         }
106
107         res = SQLRowCount(stmt, &rowcount);
108         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
109                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
110                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
111                 return NULL;
112         }
113
114         res = SQLNumResultCols(stmt, &colcount);
115         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
116                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
117                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
118                 return NULL;
119         }
120
121         if (rowcount) {
122                 res = SQLFetch(stmt);
123                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
124                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
125                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
126                         return NULL;
127                 }
128                 for (x=0;x<colcount;x++) {
129                         rowdata[0] = '\0';
130                         collen = sizeof(coltitle);
131                         res = SQLDescribeCol(stmt, x + 1, coltitle, sizeof(coltitle), &collen, 
132                                                 &datatype, &colsize, &decimaldigits, &nullable);
133                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
134                                 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
135                                 if (var)
136                                         ast_variables_destroy(var);
137                                 return NULL;
138                         }
139                         res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
140                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
141                                 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
142                                 if (var)
143                                         ast_variables_destroy(var);
144                                 return NULL;
145                         }
146                         stringp = rowdata;
147                         while(stringp) {
148                                 chunk = strsep(&stringp, ";");
149                                 if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
150                                         if (prev) {
151                                                 prev->next = ast_variable_new(coltitle, chunk);
152                                                 if (prev->next)
153                                                         prev = prev->next;
154                                         } else 
155                                                 prev = var = ast_variable_new(coltitle, chunk);
156                                         
157                                 }
158                         }
159                 }
160         }
161
162
163         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
164         return var;
165 }
166
167 static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap)
168 {
169         odbc_obj *obj;
170         SQLHSTMT stmt;
171         char sql[1024];
172         char coltitle[256];
173         char rowdata[2048];
174         const char *initfield=NULL;
175         char *op;
176         const char *newparam, *newval;
177         char *stringp;
178         char *chunk;
179         SQLSMALLINT collen;
180         int res;
181         int x;
182         struct ast_variable *var=NULL;
183         struct ast_config *cfg=NULL;
184         struct ast_category *cat=NULL;
185         struct ast_realloca ra;
186         SQLLEN rowcount=0;
187         SQLULEN colsize;
188         SQLSMALLINT colcount=0;
189         SQLSMALLINT datatype;
190         SQLSMALLINT decimaldigits;
191         SQLSMALLINT nullable;
192
193         va_list aq;
194         va_copy(aq, ap);
195         
196         
197         if (!table)
198                 return NULL;
199         memset(&ra, 0, sizeof(ra));
200
201         obj = fetch_odbc_obj(database, 0);
202         if (!obj)
203                 return NULL;
204
205         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
206         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
207                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
208                 return NULL;
209         }
210
211         newparam = va_arg(aq, const char *);
212         if (!newparam)  {
213                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
214                 return NULL;
215         }
216         initfield = ast_strdupa(newparam);
217         if (initfield && (op = strchr(initfield, ' '))) 
218                 *op = '\0';
219         newval = va_arg(aq, const char *);
220         if (!strchr(newparam, ' ')) op = " ="; else op = "";
221         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?", table, newparam, op);
222         while((newparam = va_arg(aq, const char *))) {
223                 if (!strchr(newparam, ' ')) op = " ="; else op = "";
224                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?", newparam, op);
225                 newval = va_arg(aq, const char *);
226         }
227         if (initfield)
228                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
229         va_end(aq);
230         res = SQLPrepare(stmt, sql, SQL_NTS);
231         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
232                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
233                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
234                 return NULL;
235         }
236         
237         /* Now bind the parameters */
238         x = 1;
239
240         while((newparam = va_arg(ap, const char *))) {
241                 newval = va_arg(ap, const char *);
242                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
243         }
244                 
245         res = odbc_smart_execute(obj, stmt);
246
247         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
248                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
249                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
250                 return NULL;
251         }
252
253         res = SQLRowCount(stmt, &rowcount);
254         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
255                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
256                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
257                 return NULL;
258         }
259
260         res = SQLNumResultCols(stmt, &colcount);
261         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
262                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
263                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
264                 return NULL;
265         }
266
267         cfg = ast_config_new();
268         if (!cfg) {
269                 ast_log(LOG_WARNING, "Out of memory!\n");
270                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
271                 return NULL;
272         }
273
274         while (rowcount--) {
275                 var = NULL;
276                 res = SQLFetch(stmt);
277                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
278                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
279                         continue;
280                 }
281                 cat = ast_category_new("");
282                 if (!cat) {
283                         ast_log(LOG_WARNING, "Out of memory!\n");
284                         continue;
285                 }
286                 for (x=0;x<colcount;x++) {
287                         rowdata[0] = '\0';
288                         collen = sizeof(coltitle);
289                         res = SQLDescribeCol(stmt, x + 1, coltitle, sizeof(coltitle), &collen, 
290                                                 &datatype, &colsize, &decimaldigits, &nullable);
291                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
292                                 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
293                                 ast_category_destroy(cat);
294                                 continue;
295                         }
296                         res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
297                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
298                                 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
299                                 ast_category_destroy(cat);
300                                 continue;
301                         }
302                         stringp = rowdata;
303                         while(stringp) {
304                                 chunk = strsep(&stringp, ";");
305                                 if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
306                                         if (initfield && !strcmp(initfield, coltitle))
307                                                 ast_category_rename(cat, chunk);
308                                         var = ast_variable_new(coltitle, chunk);
309                                         ast_variable_append(cat, var);
310                                 }
311                         }
312                 }
313                 ast_category_append(cfg, cat);
314         }
315
316         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
317         return cfg;
318 }
319
320 static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
321 {
322         odbc_obj *obj;
323         SQLHSTMT stmt;
324         char sql[256];
325         SQLLEN rowcount=0;
326         const char *newparam, *newval;
327         int res;
328         int x;
329         va_list aq;
330         
331         va_copy(aq, ap);
332         
333         if (!table)
334                 return -1;
335
336         obj = fetch_odbc_obj (database, 0);
337         if (!obj)
338                 return -1;
339
340         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
341         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
342                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
343                 return -1;
344         }
345
346         newparam = va_arg(aq, const char *);
347         if (!newparam)  {
348                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
349                 return -1;
350         }
351         newval = va_arg(aq, const char *);
352         snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam);
353         while((newparam = va_arg(aq, const char *))) {
354                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam);
355                 newval = va_arg(aq, const char *);
356         }
357         va_end(aq);
358         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
359         
360         res = SQLPrepare(stmt, sql, SQL_NTS);
361         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
362                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
363                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
364                 return -1;
365         }
366         
367         /* Now bind the parameters */
368         x = 1;
369
370         while((newparam = va_arg(ap, const char *))) {
371                 newval = va_arg(ap, const char *);
372                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
373         }
374                 
375         SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(lookup), 0, (void *)lookup, 0, NULL);
376
377         res = odbc_smart_execute(obj, stmt);
378
379         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
380                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
381                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
382                 return -1;
383         }
384
385         res = SQLRowCount(stmt, &rowcount);
386         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
387
388         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
389                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
390                 return -1;
391         }
392
393         if (rowcount) 
394                 return 0;
395         return -1;
396 }
397
398 static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg)
399 {
400         struct ast_variable *new_v;
401         struct ast_category *cur_cat;
402         int res = 0;
403         odbc_obj *obj;
404         SQLINTEGER err=0, commented=0, cat_metric=0, var_metric=0, last_cat_metric=0;
405         SQLBIGINT id;
406         char sql[255] = "", filename[128], category[128], var_name[128], var_val[512];
407         SQLSMALLINT rowcount=0;
408         SQLHSTMT stmt;
409         char last[128] = "";
410
411         if (!file || !strcmp (file, "res_config_odbc.conf"))
412                 return NULL;            /* cant configure myself with myself ! */
413
414         obj = fetch_odbc_obj(database, 0);
415         if (!obj)
416                 return NULL;
417
418         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
419
420         SQLBindCol (stmt, 1, SQL_C_ULONG, &id, sizeof (id), &err);
421         SQLBindCol (stmt, 2, SQL_C_ULONG, &cat_metric, sizeof (cat_metric), &err);
422         SQLBindCol (stmt, 3, SQL_C_ULONG, &var_metric, sizeof (var_metric), &err);
423         SQLBindCol (stmt, 4, SQL_C_ULONG, &commented, sizeof (commented), &err);
424         SQLBindCol (stmt, 5, SQL_C_CHAR, &filename, sizeof (filename), &err);
425         SQLBindCol (stmt, 6, SQL_C_CHAR, &category, sizeof (category), &err);
426         SQLBindCol (stmt, 7, SQL_C_CHAR, &var_name, sizeof (var_name), &err);
427         SQLBindCol (stmt, 8, SQL_C_CHAR, &var_val, sizeof (var_val), &err);
428         
429         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE filename='%s' and commented=0 ORDER BY filename,cat_metric desc,var_metric asc,category,var_name,var_val,id", table, file);
430
431         res = odbc_smart_direct_execute(obj, stmt, sql);
432         
433         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
434                 ast_log (LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
435                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
436                 return NULL;
437         }
438
439         res = SQLNumResultCols (stmt, &rowcount);
440
441         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
442                 ast_log (LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
443                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
444                 return NULL;
445         }
446
447         if (!rowcount) {
448                 ast_log (LOG_NOTICE, "found nothing\n");
449                 return cfg;
450         }
451
452         cur_cat = ast_config_get_current_category(cfg);
453
454         while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
455                 if (!strcmp (var_name, "#include")) {
456                         if (!ast_config_internal_load(var_val, cfg)) {
457                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
458                                 return NULL;
459                         }
460                         continue;
461                 } 
462                 if (strcmp(last, category) || last_cat_metric != cat_metric) {
463                         cur_cat = ast_category_new(category);
464                         if (!cur_cat) {
465                                 ast_log(LOG_WARNING, "Out of memory!\n");
466                                 break;
467                         }
468                         strcpy(last, category);
469                         last_cat_metric = cat_metric;
470                         ast_category_append(cfg, cur_cat);
471                 }
472
473                 new_v = ast_variable_new(var_name, var_val);
474                 ast_variable_append(cur_cat, new_v);
475         }
476
477         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
478         return cfg;
479 }
480
481 static struct ast_config_engine odbc_engine = {
482         .name = "odbc",
483         .load_func = config_odbc,
484         .realtime_func = realtime_odbc,
485         .realtime_multi_func = realtime_multi_odbc,
486         .update_func = update_odbc
487 };
488
489 int unload_module (void)
490 {
491         ast_config_engine_deregister(&odbc_engine);
492         if (option_verbose)
493                 ast_verbose("res_config_odbc unloaded.\n");
494         STANDARD_HANGUP_LOCALUSERS;
495         return 0;
496 }
497
498 int load_module (void)
499 {
500         ast_config_engine_register(&odbc_engine);
501         if (option_verbose)
502                 ast_verbose("res_config_odbc loaded.\n");
503         return 0;
504 }
505
506 char *description (void)
507 {
508         return tdesc;
509 }
510
511 int usecount (void)
512 {
513         /* never unload a config module */
514         return 1;
515 }
516
517 char *key ()
518 {
519         return ASTERISK_GPL_KEY;
520 }