Continue merging in changes from resolve-shadow-warnings. funcs/ this time.
[asterisk/asterisk.git] / funcs / func_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (c) 2005, 2006 Tilghman Lesher
5  *
6  * Tilghman Lesher <func_odbc__200508@the-tilghman.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  *
22  * \brief ODBC lookups
23  *
24  * \author Tilghman Lesher <func_odbc__200508@the-tilghman.com>
25  *
26  * \ingroup functions
27  */
28
29 /*** MODULEINFO
30         <depend>unixodbc</depend>
31         <depend>ltdl</depend>
32         <depend>res_odbc</depend>
33  ***/
34
35 #include "asterisk.h"
36
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38
39 #include "asterisk/module.h"
40 #include "asterisk/file.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/config.h"
44 #include "asterisk/res_odbc.h"
45 #include "asterisk/app.h"
46
47 static char *config = "func_odbc.conf";
48
49 enum {
50         OPT_ESCAPECOMMAS =      (1 << 0),
51         OPT_MULTIROW     =      (1 << 1),
52 } odbc_option_flags;
53
54 struct acf_odbc_query {
55         AST_RWLIST_ENTRY(acf_odbc_query) list;
56         char readhandle[5][30];
57         char writehandle[5][30];
58         char sql_read[2048];
59         char sql_write[2048];
60         unsigned int flags;
61         int rowlimit;
62         struct ast_custom_function *acf;
63 };
64
65 static void odbc_datastore_free(void *data);
66
67 struct ast_datastore_info odbc_info = {
68         .type = "FUNC_ODBC",
69         .destroy = odbc_datastore_free,
70 };
71
72 /* For storing each result row */
73 struct odbc_datastore_row {
74         AST_LIST_ENTRY(odbc_datastore_row) list;
75         char data[0];
76 };
77
78 /* For storing each result set */
79 struct odbc_datastore {
80         AST_LIST_HEAD(, odbc_datastore_row);
81         char names[0];
82 };
83
84 AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query);
85
86 static int resultcount = 0;
87
88 static void odbc_datastore_free(void *data)
89 {
90         struct odbc_datastore *result = data;
91         struct odbc_datastore_row *row;
92         AST_LIST_LOCK(result);
93         while ((row = AST_LIST_REMOVE_HEAD(result, list))) {
94                 ast_free(row);
95         }
96         AST_LIST_UNLOCK(result);
97         AST_LIST_HEAD_DESTROY(result);
98         ast_free(result);
99 }
100
101 static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
102 {
103         int res;
104         char *sql = data;
105         SQLHSTMT stmt;
106
107         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
108         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
109                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
110                 return NULL;
111         }
112
113         res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS);
114         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
115                 ast_log(LOG_WARNING, "SQL Exec Direct failed![%s]\n", sql);
116                 SQLCloseCursor(stmt);
117                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
118                 return NULL;
119         }
120
121         return stmt;
122 }
123
124 /*
125  * Master control routine
126  */
127 static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
128 {
129         struct odbc_obj *obj = NULL;
130         struct acf_odbc_query *query;
131         char *t, varname[15];
132         int i, dsn, bogus_chan = 0;
133         AST_DECLARE_APP_ARGS(values,
134                 AST_APP_ARG(field)[100];
135         );
136         AST_DECLARE_APP_ARGS(args,
137                 AST_APP_ARG(field)[100];
138         );
139         SQLHSTMT stmt = NULL;
140         SQLLEN rows=0;
141         struct ast_str *buf = ast_str_create(16);
142
143         if (!buf) {
144                 return -1;
145         }
146
147         AST_RWLIST_RDLOCK(&queries);
148         AST_RWLIST_TRAVERSE(&queries, query, list) {
149                 if (!strcmp(query->acf->name, cmd)) {
150                         break;
151                 }
152         }
153
154         if (!query) {
155                 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
156                 AST_RWLIST_UNLOCK(&queries);
157                 ast_free(buf);
158                 return -1;
159         }
160
161         if (!chan) {
162                 if ((chan = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/func_odbc")))
163                         bogus_chan = 1;
164         }
165
166         if (chan)
167                 ast_autoservice_start(chan);
168
169         ast_str_make_space(&buf, strlen(query->sql_write) * 2);
170
171         /* Parse our arguments */
172         t = value ? ast_strdupa(value) : "";
173
174         if (!s || !t) {
175                 ast_log(LOG_ERROR, "Out of memory\n");
176                 AST_RWLIST_UNLOCK(&queries);
177                 if (chan)
178                         ast_autoservice_stop(chan);
179                 if (bogus_chan)
180                         ast_channel_free(chan);
181                 ast_free(buf);
182                 return -1;
183         }
184
185         AST_STANDARD_APP_ARGS(args, s);
186         for (i = 0; i < args.argc; i++) {
187                 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
188                 pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
189         }
190
191         /* Parse values, just like arguments */
192         AST_STANDARD_APP_ARGS(values, t);
193         for (i = 0; i < values.argc; i++) {
194                 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
195                 pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
196         }
197
198         /* Additionally set the value as a whole (but push an empty string if value is NULL) */
199         pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : "");
200
201         pbx_substitute_variables_helper(chan, query->sql_write, buf->str, buf->len - 1);
202
203         /* Restore prior values */
204         for (i = 0; i < args.argc; i++) {
205                 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
206                 pbx_builtin_setvar_helper(chan, varname, NULL);
207         }
208
209         for (i = 0; i < values.argc; i++) {
210                 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
211                 pbx_builtin_setvar_helper(chan, varname, NULL);
212         }
213         pbx_builtin_setvar_helper(chan, "VALUE", NULL);
214
215         AST_RWLIST_UNLOCK(&queries);
216
217         for (dsn = 0; dsn < 5; dsn++) {
218                 if (!ast_strlen_zero(query->writehandle[dsn])) {
219                         obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
220                         if (obj)
221                                 stmt = ast_odbc_direct_execute(obj, generic_execute, buf);
222                 }
223                 if (stmt)
224                         break;
225         }
226
227         if (stmt) {
228                 /* Rows affected */
229                 SQLRowCount(stmt, &rows);
230         }
231
232         /* Output the affected rows, for all cases.  In the event of failure, we
233          * flag this as -1 rows.  Note that this is different from 0 affected rows
234          * which would be the case if we succeeded in our query, but the values did
235          * not change. */
236         snprintf(varname, sizeof(varname), "%d", (int)rows);
237         pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
238
239         if (stmt) {
240                 SQLCloseCursor(stmt);
241                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
242         }
243         if (obj)
244                 ast_odbc_release_obj(obj);
245
246         if (chan)
247                 ast_autoservice_stop(chan);
248         if (bogus_chan)
249                 ast_channel_free(chan);
250         ast_free(buf);
251
252         return 0;
253 }
254
255 static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len)
256 {
257         struct odbc_obj *obj = NULL;
258         struct acf_odbc_query *query;
259         char varname[15], colnames[2048] = "", rowcount[12] = "-1";
260         int res, x, y, buflen = 0, escapecommas, rowlimit = 1, dsn, bogus_chan = 0;
261         AST_DECLARE_APP_ARGS(args,
262                 AST_APP_ARG(field)[100];
263         );
264         SQLHSTMT stmt = NULL;
265         SQLSMALLINT colcount=0;
266         SQLLEN indicator;
267         SQLSMALLINT collength;
268         struct odbc_datastore *resultset = NULL;
269         struct odbc_datastore_row *row = NULL;
270         struct ast_str *sql = ast_str_create(16);
271
272         if (!sql) {
273                 return -1;
274         }
275
276         AST_RWLIST_RDLOCK(&queries);
277         AST_RWLIST_TRAVERSE(&queries, query, list) {
278                 if (!strcmp(query->acf->name, cmd)) {
279                         break;
280                 }
281         }
282
283         if (!query) {
284                 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
285                 AST_RWLIST_UNLOCK(&queries);
286                 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
287                 ast_free(sql);
288                 return -1;
289         }
290
291         if (!chan) {
292                 if ((chan = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/func_odbc")))
293                         bogus_chan = 1;
294         }
295
296         if (chan)
297                 ast_autoservice_start(chan);
298
299         AST_STANDARD_APP_ARGS(args, s);
300         for (x = 0; x < args.argc; x++) {
301                 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
302                 pbx_builtin_pushvar_helper(chan, varname, args.field[x]);
303         }
304
305         ast_str_make_space(&sql, strlen(query->sql_read) * 2);
306         pbx_substitute_variables_helper(chan, query->sql_read, sql->str, sql->len - 1);
307
308         /* Restore prior values */
309         for (x = 0; x < args.argc; x++) {
310                 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
311                 pbx_builtin_setvar_helper(chan, varname, NULL);
312         }
313
314         /* Save these flags, so we can release the lock */
315         escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS);
316         if (ast_test_flag(query, OPT_MULTIROW)) {
317                 resultset = ast_calloc(1, sizeof(*resultset));
318                 AST_LIST_HEAD_INIT(resultset);
319                 if (query->rowlimit)
320                         rowlimit = query->rowlimit;
321                 else
322                         rowlimit = INT_MAX;
323         }
324         AST_RWLIST_UNLOCK(&queries);
325
326         for (dsn = 0; dsn < 5; dsn++) {
327                 if (!ast_strlen_zero(query->readhandle[dsn])) {
328                         obj = ast_odbc_request_obj(query->readhandle[dsn], 0);
329                         if (obj)
330                                 stmt = ast_odbc_direct_execute(obj, generic_execute, sql->str);
331                 }
332                 if (stmt)
333                         break;
334         }
335
336         if (!stmt) {
337                 ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql->str);
338                 if (obj)
339                         ast_odbc_release_obj(obj);
340                 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
341                 if (chan)
342                         ast_autoservice_stop(chan);
343                 if (bogus_chan)
344                         ast_channel_free(chan);
345                 ast_free(sql);
346                 return -1;
347         }
348
349         res = SQLNumResultCols(stmt, &colcount);
350         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
351                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql->str);
352                 SQLCloseCursor(stmt);
353                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
354                 ast_odbc_release_obj(obj);
355                 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
356                 if (chan)
357                         ast_autoservice_stop(chan);
358                 if (bogus_chan)
359                         ast_channel_free(chan);
360                 ast_free(sql);
361                 return -1;
362         }
363
364         res = SQLFetch(stmt);
365         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
366                 int res1 = -1;
367                 if (res == SQL_NO_DATA) {
368                         ast_verb(4, "Found no rows [%s]\n", sql->str);
369                         res1 = 0;
370                         ast_copy_string(rowcount, "0", sizeof(rowcount));
371                 } else {
372                         ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql->str);
373                 }
374                 SQLCloseCursor(stmt);
375                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
376                 ast_odbc_release_obj(obj);
377                 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
378                 if (chan)
379                         ast_autoservice_stop(chan);
380                 if (bogus_chan)
381                         ast_channel_free(chan);
382                 ast_free(sql);
383                 return res1;
384         }
385
386         for (y = 0; y < rowlimit; y++) {
387                 *buf = '\0';
388                 for (x = 0; x < colcount; x++) {
389                         int i;
390                         char coldata[256];
391
392                         if (y == 0) {
393                                 char colname[256];
394                                 int namelen;
395
396                                 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, NULL, NULL, NULL);
397                                 if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
398                                         snprintf(colname, sizeof(colname), "field%d", x);
399                                 }
400
401                                 if (!ast_strlen_zero(colnames))
402                                         strncat(colnames, ",", sizeof(colnames) - strlen(colnames) - 1);
403                                 namelen = strlen(colnames);
404
405                                 /* Copy data, encoding '\' and ',' for the argument parser */
406                                 for (i = 0; i < sizeof(colname); i++) {
407                                         if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) {
408                                                 colnames[namelen++] = '\\';
409                                         }
410                                         colnames[namelen++] = colname[i];
411
412                                         if (namelen >= sizeof(colnames) - 2) {
413                                                 colnames[namelen >= sizeof(colnames) ? sizeof(colnames) - 1 : namelen] = '\0';
414                                                 break;
415                                         }
416
417                                         if (colname[i] == '\0')
418                                                 break;
419                                 }
420
421                                 if (resultset) {
422                                         void *tmp = ast_realloc(resultset, sizeof(*resultset) + strlen(colnames) + 1);
423                                         if (!tmp) {
424                                                 ast_log(LOG_ERROR, "No space for a new resultset?\n");
425                                                 ast_free(resultset);
426                                                 SQLCloseCursor(stmt);
427                                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
428                                                 ast_odbc_release_obj(obj);
429                                                 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
430                                                 if (chan)
431                                                         ast_autoservice_stop(chan);
432                                                 if (bogus_chan)
433                                                         ast_channel_free(chan);
434                                                 ast_free(sql);
435                                                 return -1;
436                                         }
437                                         resultset = tmp;
438                                         strcpy((char *)resultset + sizeof(*resultset), colnames);
439                                 }
440                         }
441
442                         buflen = strlen(buf);
443                         res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator);
444                         if (indicator == SQL_NULL_DATA) {
445                                 coldata[0] = '\0';
446                                 res = SQL_SUCCESS;
447                         }
448
449                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
450                                 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql->str);
451                                 y = -1;
452                                 goto end_acf_read;
453                         }
454
455                         /* Copy data, encoding '\' and ',' for the argument parser */
456                         for (i = 0; i < sizeof(coldata); i++) {
457                                 if (escapecommas && (coldata[i] == '\\' || coldata[i] == ',')) {
458                                         buf[buflen++] = '\\';
459                                 }
460                                 buf[buflen++] = coldata[i];
461
462                                 if (buflen >= len - 2)
463                                         break;
464
465                                 if (coldata[i] == '\0')
466                                         break;
467                         }
468
469                         buf[buflen - 1] = ',';
470                         buf[buflen] = '\0';
471                 }
472                 /* Trim trailing comma */
473                 buf[buflen - 1] = '\0';
474
475                 if (resultset) {
476                         row = ast_calloc(1, sizeof(*row) + buflen);
477                         if (!row) {
478                                 ast_log(LOG_ERROR, "Unable to allocate space for more rows in this resultset.\n");
479                                 goto end_acf_read;
480                         }
481                         strcpy((char *)row + sizeof(*row), buf);
482                         AST_LIST_INSERT_TAIL(resultset, row, list);
483
484                         /* Get next row */
485                         res = SQLFetch(stmt);
486                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
487                                 if (res != SQL_NO_DATA)
488                                         ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql->str);
489                                 y++;
490                                 break;
491                         }
492                 }
493         }
494
495 end_acf_read:
496         snprintf(rowcount, sizeof(rowcount), "%d", y);
497         pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
498         pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
499         if (resultset) {
500                 int uid;
501                 struct ast_datastore *odbc_store;
502                 uid = ast_atomic_fetchadd_int(&resultcount, +1) + 1;
503                 snprintf(buf, len, "%d", uid);
504                 odbc_store = ast_datastore_alloc(&odbc_info, buf);
505                 if (!odbc_store) {
506                         ast_log(LOG_ERROR, "Rows retrieved, but unable to store it in the channel.  Results fail.\n");
507                         odbc_datastore_free(resultset);
508                         SQLCloseCursor(stmt);
509                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
510                         ast_odbc_release_obj(obj);
511                         if (chan)
512                                 ast_autoservice_stop(chan);
513                         if (bogus_chan)
514                                 ast_channel_free(chan);
515                         ast_free(sql);
516                         return -1;
517                 }
518                 odbc_store->data = resultset;
519                 ast_channel_datastore_add(chan, odbc_store);
520         }
521         SQLCloseCursor(stmt);
522         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
523         ast_odbc_release_obj(obj);
524         if (chan)
525                 ast_autoservice_stop(chan);
526         if (bogus_chan)
527                 ast_channel_free(chan);
528         ast_free(sql);
529         return 0;
530 }
531
532 static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
533 {
534         char *out = buf;
535
536         for (; *data && out - buf < len; data++) {
537                 if (*data == '\'') {
538                         *out = '\'';
539                         out++;
540                 }
541                 *out++ = *data;
542         }
543         *out = '\0';
544
545         return 0;
546 }
547
548 static struct ast_custom_function escape_function = {
549         .name = "SQL_ESC",
550         .synopsis = "Escapes single ticks for use in SQL statements",
551         .syntax = "SQL_ESC(<string>)",
552         .desc =
553 "Used in SQL templates to escape data which may contain single ticks (') which\n"
554 "are otherwise used to delimit data.  For example:\n"
555 "SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n",
556         .read = acf_escape,
557         .write = NULL,
558 };
559
560 static int acf_fetch(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
561 {
562         struct ast_datastore *store;
563         struct odbc_datastore *resultset;
564         struct odbc_datastore_row *row;
565         store = ast_channel_datastore_find(chan, &odbc_info, data);
566         if (!store) {
567                 return -1;
568         }
569         resultset = store->data;
570         AST_LIST_LOCK(resultset);
571         row = AST_LIST_REMOVE_HEAD(resultset, list);
572         AST_LIST_UNLOCK(resultset);
573         if (!row) {
574                 /* Cleanup datastore */
575                 ast_channel_datastore_remove(chan, store);
576                 ast_datastore_free(store);
577                 return -1;
578         }
579         pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", resultset->names);
580         ast_copy_string(buf, row->data, len);
581         ast_free(row);
582         return 0;
583 }
584
585 static struct ast_custom_function fetch_function = {
586         .name = "ODBC_FETCH",
587         .synopsis = "Fetch a row from a multirow query",
588         .syntax = "ODBC_FETCH(<result-id>)",
589         .desc =
590 "For queries which are marked as mode=multirow, the original query returns a\n"
591 "result-id from which results may be fetched.  This function implements the\n"
592 "actual fetch of the results.\n",
593         .read = acf_fetch,
594         .write = NULL,
595 };
596
597 static char *app_odbcfinish = "ODBCFinish";
598 static char *syn_odbcfinish = "Clear the resultset of a successful multirow query";
599 static char *desc_odbcfinish =
600 "ODBCFinish(<result-id>)\n"
601 "  Clears any remaining rows of the specified resultset\n";
602
603
604 static int exec_odbcfinish(struct ast_channel *chan, void *data)
605 {
606         struct ast_datastore *store = ast_channel_datastore_find(chan, &odbc_info, data);
607         if (!store) /* Already freed; no big deal. */
608                 return 0;
609         ast_channel_datastore_remove(chan, store);
610         ast_datastore_free(store);
611         return 0;
612 }
613
614 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
615 {
616         const char *tmp;
617         int i;
618
619         if (!cfg || !catg) {
620                 return EINVAL;
621         }
622
623         *query = ast_calloc(1, sizeof(struct acf_odbc_query));
624         if (! (*query))
625                 return ENOMEM;
626
627         if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
628                 char *tmp2 = ast_strdupa(tmp);
629                 AST_DECLARE_APP_ARGS(writeconf,
630                         AST_APP_ARG(dsn)[5];
631                 );
632                 AST_STANDARD_APP_ARGS(writeconf, tmp2);
633                 for (i = 0; i < 5; i++) {
634                         if (!ast_strlen_zero(writeconf.dsn[i]))
635                                 ast_copy_string((*query)->writehandle[i], writeconf.dsn[i], sizeof((*query)->writehandle[i]));
636                 }
637         }
638
639         if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
640                 char *tmp2 = ast_strdupa(tmp);
641                 AST_DECLARE_APP_ARGS(readconf,
642                         AST_APP_ARG(dsn)[5];
643                 );
644                 AST_STANDARD_APP_ARGS(readconf, tmp2);
645                 for (i = 0; i < 5; i++) {
646                         if (!ast_strlen_zero(readconf.dsn[i]))
647                                 ast_copy_string((*query)->readhandle[i], readconf.dsn[i], sizeof((*query)->readhandle[i]));
648                 }
649         } else {
650                 /* If no separate readhandle, then use the writehandle for reading */
651                 for (i = 0; i < 5; i++) {
652                         if (!ast_strlen_zero((*query)->writehandle[i]))
653                                 ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
654                 }
655         }
656
657         if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")))
658                 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
659         else if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
660                 ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s.  Please use 'readsql' instead.\n", catg);
661                 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
662         }
663
664         if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) {
665                 ast_free(*query);
666                 *query = NULL;
667                 ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
668                 return EINVAL;
669         }
670
671         if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")))
672                 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
673         else if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
674                 ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s.  Please use 'writesql' instead.\n", catg);
675                 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
676         }
677
678         if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) {
679                 ast_free(*query);
680                 *query = NULL;
681                 ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
682                 return EINVAL;
683         }
684
685         /* Allow escaping of embedded commas in fields to be turned off */
686         ast_set_flag((*query), OPT_ESCAPECOMMAS);
687         if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
688                 if (ast_false(tmp))
689                         ast_clear_flag((*query), OPT_ESCAPECOMMAS);
690         }
691
692         if ((tmp = ast_variable_retrieve(cfg, catg, "mode"))) {
693                 if (strcasecmp(tmp, "multirow") == 0)
694                         ast_set_flag((*query), OPT_MULTIROW);
695                 if ((tmp = ast_variable_retrieve(cfg, catg, "rowlimit")))
696                         sscanf(tmp, "%d", &((*query)->rowlimit));
697         }
698
699         (*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
700         if (! (*query)->acf) {
701                 ast_free(*query);
702                 *query = NULL;
703                 return ENOMEM;
704         }
705
706         if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
707                 asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg);
708         } else {
709                 asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg);
710         }
711
712         if (!((*query)->acf->name)) {
713                 ast_free((*query)->acf);
714                 ast_free(*query);
715                 *query = NULL;
716                 return ENOMEM;
717         }
718
719         asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
720
721         if (!((*query)->acf->syntax)) {
722                 ast_free((char *)(*query)->acf->name);
723                 ast_free((*query)->acf);
724                 ast_free(*query);
725                 *query = NULL;
726                 return ENOMEM;
727         }
728
729         (*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
730         if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) {
731                 asprintf((char **)&((*query)->acf->desc),
732                                         "Runs the following query, as defined in func_odbc.conf, performing\n"
733                                         "substitution of the arguments into the query as specified by ${ARG1},\n"
734                                         "${ARG2}, ... ${ARGn}.  When setting the function, the values are provided\n"
735                                         "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
736                                         "\nRead:\n%s\n\nWrite:\n%s\n",
737                                         (*query)->sql_read,
738                                         (*query)->sql_write);
739         } else if (!ast_strlen_zero((*query)->sql_read)) {
740                 asprintf((char **)&((*query)->acf->desc),
741                                         "Runs the following query, as defined in func_odbc.conf, performing\n"
742                                         "substitution of the arguments into the query as specified by ${ARG1},\n"
743                                         "${ARG2}, ... ${ARGn}.  This function may only be read, not set.\n\nSQL:\n%s\n",
744                                         (*query)->sql_read);
745         } else if (!ast_strlen_zero((*query)->sql_write)) {
746                 asprintf((char **)&((*query)->acf->desc),
747                                         "Runs the following query, as defined in func_odbc.conf, performing\n"
748                                         "substitution of the arguments into the query as specified by ${ARG1},\n"
749                                         "${ARG2}, ... ${ARGn}.  The values are provided either in whole as\n"
750                                         "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
751                                         "This function may only be set.\nSQL:\n%s\n",
752                                         (*query)->sql_write);
753         } else {
754                 ast_free((char *)(*query)->acf->syntax);
755                 ast_free((char *)(*query)->acf->name);
756                 ast_free((*query)->acf);
757                 ast_free(*query);
758                 ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute.  Ignoring.\n", catg);
759                 return EINVAL;
760         }
761
762         if (! ((*query)->acf->desc)) {
763                 ast_free((char *)(*query)->acf->syntax);
764                 ast_free((char *)(*query)->acf->name);
765                 ast_free((*query)->acf);
766                 ast_free(*query);
767                 *query = NULL;
768                 return ENOMEM;
769         }
770
771         if (ast_strlen_zero((*query)->sql_read)) {
772                 (*query)->acf->read = NULL;
773         } else {
774                 (*query)->acf->read = acf_odbc_read;
775         }
776
777         if (ast_strlen_zero((*query)->sql_write)) {
778                 (*query)->acf->write = NULL;
779         } else {
780                 (*query)->acf->write = acf_odbc_write;
781         }
782
783         return 0;
784 }
785
786 static int free_acf_query(struct acf_odbc_query *query)
787 {
788         if (query) {
789                 if (query->acf) {
790                         if (query->acf->name)
791                                 ast_free((char *)query->acf->name);
792                         if (query->acf->syntax)
793                                 ast_free((char *)query->acf->syntax);
794                         if (query->acf->desc)
795                                 ast_free((char *)query->acf->desc);
796                         ast_free(query->acf);
797                 }
798                 ast_free(query);
799         }
800         return 0;
801 }
802
803 static int load_module(void)
804 {
805         int res = 0;
806         struct ast_config *cfg;
807         char *catg;
808         struct ast_flags config_flags = { 0 };
809
810         res |= ast_custom_function_register(&fetch_function);
811         res |= ast_register_application(app_odbcfinish, exec_odbcfinish, syn_odbcfinish, desc_odbcfinish);
812         AST_RWLIST_WRLOCK(&queries);
813
814         cfg = ast_config_load(config, config_flags);
815         if (!cfg) {
816                 ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
817                 AST_RWLIST_UNLOCK(&queries);
818                 return AST_MODULE_LOAD_DECLINE;
819         }
820
821         for (catg = ast_category_browse(cfg, NULL);
822              catg;
823              catg = ast_category_browse(cfg, catg)) {
824                 struct acf_odbc_query *query = NULL;
825                 int err;
826
827                 if ((err = init_acf_query(cfg, catg, &query))) {
828                         if (err == ENOMEM)
829                                 ast_log(LOG_ERROR, "Out of memory\n");
830                         else if (err == EINVAL)
831                                 ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
832                         else
833                                 ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
834                 } else {
835                         AST_RWLIST_INSERT_HEAD(&queries, query, list);
836                         ast_custom_function_register(query->acf);
837                 }
838         }
839
840         ast_config_destroy(cfg);
841         res |= ast_custom_function_register(&escape_function);
842
843         AST_RWLIST_UNLOCK(&queries);
844         return res;
845 }
846
847 static int unload_module(void)
848 {
849         struct acf_odbc_query *query;
850         int res = 0;
851
852         AST_RWLIST_WRLOCK(&queries);
853         while (!AST_RWLIST_EMPTY(&queries)) {
854                 query = AST_RWLIST_REMOVE_HEAD(&queries, list);
855                 ast_custom_function_unregister(query->acf);
856                 free_acf_query(query);
857         }
858
859         res |= ast_custom_function_unregister(&escape_function);
860         res |= ast_custom_function_unregister(&fetch_function);
861         res |= ast_unregister_application(app_odbcfinish);
862
863         /* Allow any threads waiting for this lock to pass (avoids a race) */
864         AST_RWLIST_UNLOCK(&queries);
865         usleep(1);
866         AST_RWLIST_WRLOCK(&queries);
867
868         AST_RWLIST_UNLOCK(&queries);
869         return 0;
870 }
871
872 static int reload(void)
873 {
874         int res = 0;
875         struct ast_config *cfg;
876         struct acf_odbc_query *oldquery;
877         char *catg;
878         struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
879
880         cfg = ast_config_load(config, config_flags);
881         if (cfg == CONFIG_STATUS_FILEUNCHANGED)
882                 return 0;
883
884         AST_RWLIST_WRLOCK(&queries);
885
886         while (!AST_RWLIST_EMPTY(&queries)) {
887                 oldquery = AST_RWLIST_REMOVE_HEAD(&queries, list);
888                 ast_custom_function_unregister(oldquery->acf);
889                 free_acf_query(oldquery);
890         }
891
892         if (!cfg) {
893                 ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
894                 goto reload_out;
895         }
896
897         for (catg = ast_category_browse(cfg, NULL);
898              catg;
899              catg = ast_category_browse(cfg, catg)) {
900                 struct acf_odbc_query *query = NULL;
901
902                 if (init_acf_query(cfg, catg, &query)) {
903                         ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
904                 } else {
905                         AST_RWLIST_INSERT_HEAD(&queries, query, list);
906                         ast_custom_function_register(query->acf);
907                 }
908         }
909
910         ast_config_destroy(cfg);
911 reload_out:
912         AST_RWLIST_UNLOCK(&queries);
913         return res;
914 }
915
916 /* XXX need to revise usecount - set if query_lock is set */
917
918 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",
919                 .load = load_module,
920                 .unload = unload_module,
921                 .reload = reload,
922                );
923