00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 #include "asterisk.h"
00034
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00036
00037 #include <sys/types.h>
00038 #include <stdio.h>
00039 #include <stdlib.h>
00040 #include <unistd.h>
00041 #include <string.h>
00042 #include <errno.h>
00043
00044 #include "asterisk/module.h"
00045 #include "asterisk/file.h"
00046 #include "asterisk/logger.h"
00047 #include "asterisk/options.h"
00048 #include "asterisk/channel.h"
00049 #include "asterisk/pbx.h"
00050 #include "asterisk/module.h"
00051 #include "asterisk/config.h"
00052 #include "asterisk/res_odbc.h"
00053 #include "asterisk/app.h"
00054
00055 static char *config = "func_odbc.conf";
00056
00057 enum {
00058 OPT_ESCAPECOMMAS = (1 << 0),
00059 } odbc_option_flags;
00060
00061 struct acf_odbc_query {
00062 AST_LIST_ENTRY(acf_odbc_query) list;
00063 char readhandle[5][30];
00064 char writehandle[5][30];
00065 char sql_read[2048];
00066 char sql_write[2048];
00067 unsigned int flags;
00068 struct ast_custom_function *acf;
00069 };
00070
00071 AST_LIST_HEAD_STATIC(queries, acf_odbc_query);
00072
00073 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
00074 {
00075 int res;
00076 char *sql = data;
00077 SQLHSTMT stmt;
00078
00079 res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
00080 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00081 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
00082 return NULL;
00083 }
00084
00085 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
00086 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00087 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
00088 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00089 return NULL;
00090 }
00091
00092 return stmt;
00093 }
00094
00095
00096
00097
00098 static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const char *value)
00099 {
00100 struct odbc_obj *obj;
00101 struct acf_odbc_query *query;
00102 char *t, buf[2048]="", varname[15];
00103 int i, dsn;
00104 AST_DECLARE_APP_ARGS(values,
00105 AST_APP_ARG(field)[100];
00106 );
00107 AST_DECLARE_APP_ARGS(args,
00108 AST_APP_ARG(field)[100];
00109 );
00110 SQLHSTMT stmt;
00111 SQLLEN rows=0;
00112
00113 AST_LIST_LOCK(&queries);
00114 AST_LIST_TRAVERSE(&queries, query, list) {
00115 if (!strcmp(query->acf->name, cmd)) {
00116 break;
00117 }
00118 }
00119
00120 if (!query) {
00121 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
00122 AST_LIST_UNLOCK(&queries);
00123 return -1;
00124 }
00125
00126
00127 t = value ? ast_strdupa(value) : "";
00128
00129 if (!s || !t) {
00130 ast_log(LOG_ERROR, "Out of memory\n");
00131 AST_LIST_UNLOCK(&queries);
00132 return -1;
00133 }
00134
00135 AST_STANDARD_APP_ARGS(args, s);
00136 for (i = 0; i < args.argc; i++) {
00137 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
00138 pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
00139 }
00140
00141
00142
00143 AST_NONSTANDARD_APP_ARGS(values, t, ',');
00144 for (i = 0; i < values.argc; i++) {
00145 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
00146 pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
00147 }
00148
00149
00150 pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : "");
00151
00152 pbx_substitute_variables_helper(chan, query->sql_write, buf, sizeof(buf) - 1);
00153
00154
00155 for (i = 0; i < args.argc; i++) {
00156 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
00157 pbx_builtin_setvar_helper(chan, varname, NULL);
00158 }
00159
00160 for (i = 0; i < values.argc; i++) {
00161 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
00162 pbx_builtin_setvar_helper(chan, varname, NULL);
00163 }
00164 pbx_builtin_setvar_helper(chan, "VALUE", NULL);
00165
00166 AST_LIST_UNLOCK(&queries);
00167
00168 for (dsn = 0; dsn < 5; dsn++) {
00169 if (!ast_strlen_zero(query->writehandle[dsn])) {
00170 obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
00171 if (obj)
00172 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf);
00173 }
00174 if (stmt)
00175 break;
00176 }
00177
00178 if (stmt) {
00179
00180 SQLRowCount(stmt, &rows);
00181 }
00182
00183
00184
00185
00186
00187 snprintf(varname, sizeof(varname), "%d", (int)rows);
00188 pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
00189
00190 if (stmt)
00191 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00192 if (obj)
00193 ast_odbc_release_obj(obj);
00194
00195 return 0;
00196 }
00197
00198 static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf, size_t len)
00199 {
00200 struct odbc_obj *obj;
00201 struct acf_odbc_query *query;
00202 char sql[2048] = "", varname[15], colnames[2048] = "";
00203 int res, x, buflen = 0, escapecommas, dsn;
00204 AST_DECLARE_APP_ARGS(args,
00205 AST_APP_ARG(field)[100];
00206 );
00207 SQLHSTMT stmt;
00208 SQLSMALLINT colcount=0;
00209 SQLLEN indicator;
00210 SQLSMALLINT collength;
00211
00212 AST_LIST_LOCK(&queries);
00213 AST_LIST_TRAVERSE(&queries, query, list) {
00214 if (!strcmp(query->acf->name, cmd)) {
00215 break;
00216 }
00217 }
00218
00219 if (!query) {
00220 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
00221 AST_LIST_UNLOCK(&queries);
00222 return -1;
00223 }
00224
00225 AST_STANDARD_APP_ARGS(args, s);
00226 for (x = 0; x < args.argc; x++) {
00227 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
00228 pbx_builtin_pushvar_helper(chan, varname, args.field[x]);
00229 }
00230
00231 pbx_substitute_variables_helper(chan, query->sql_read, sql, sizeof(sql) - 1);
00232
00233
00234 for (x = 0; x < args.argc; x++) {
00235 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
00236 pbx_builtin_setvar_helper(chan, varname, NULL);
00237 }
00238
00239
00240 escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS);
00241
00242 AST_LIST_UNLOCK(&queries);
00243
00244 for (dsn = 0; dsn < 5; dsn++) {
00245 if (!ast_strlen_zero(query->writehandle[dsn])) {
00246 obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
00247 if (obj)
00248 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
00249 }
00250 if (stmt)
00251 break;
00252 }
00253
00254 if (!stmt) {
00255 ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql);
00256 if (obj)
00257 ast_odbc_release_obj(obj);
00258 return -1;
00259 }
00260
00261 res = SQLNumResultCols(stmt, &colcount);
00262 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00263 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
00264 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00265 ast_odbc_release_obj(obj);
00266 return -1;
00267 }
00268
00269 *buf = '\0';
00270
00271 res = SQLFetch(stmt);
00272 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00273 int res1 = -1;
00274 if (res == SQL_NO_DATA) {
00275 if (option_verbose > 3) {
00276 ast_verbose(VERBOSE_PREFIX_4 "Found no rows [%s]\n", sql);
00277 }
00278 res1 = 0;
00279 } else if (option_verbose > 3) {
00280 ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql);
00281 }
00282 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00283 ast_odbc_release_obj(obj);
00284 return res1;
00285 }
00286
00287 for (x = 0; x < colcount; x++) {
00288 int i, namelen;
00289 char coldata[256], colname[256];
00290
00291 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, NULL, NULL, NULL);
00292 if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
00293 snprintf(colname, sizeof(colname), "field%d", x);
00294 }
00295
00296 if (!ast_strlen_zero(colnames))
00297 strncat(colnames, ",", sizeof(colnames) - 1);
00298 namelen = strlen(colnames);
00299
00300
00301 for (i = 0; i < sizeof(colname); i++) {
00302 if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) {
00303 colnames[namelen++] = '\\';
00304 }
00305 colnames[namelen++] = colname[i];
00306
00307 if (namelen >= sizeof(colnames) - 2) {
00308 colnames[namelen >= sizeof(colnames) ? sizeof(colnames) - 1 : namelen] = '\0';
00309 break;
00310 }
00311
00312 if (colname[i] == '\0')
00313 break;
00314 }
00315
00316 buflen = strlen(buf);
00317 res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator);
00318 if (indicator == SQL_NULL_DATA) {
00319 coldata[0] = '\0';
00320 res = SQL_SUCCESS;
00321 }
00322
00323 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00324 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
00325 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00326 ast_odbc_release_obj(obj);
00327 return -1;
00328 }
00329
00330
00331 for (i = 0; i < sizeof(coldata); i++) {
00332 if (escapecommas && (coldata[i] == '\\' || coldata[i] == ',')) {
00333 buf[buflen++] = '\\';
00334 }
00335 buf[buflen++] = coldata[i];
00336
00337 if (buflen >= len - 2)
00338 break;
00339
00340 if (coldata[i] == '\0')
00341 break;
00342 }
00343
00344 buf[buflen - 1] = ',';
00345 buf[buflen] = '\0';
00346 }
00347
00348 buf[buflen - 1] = '\0';
00349
00350 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
00351
00352 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00353 ast_odbc_release_obj(obj);
00354 return 0;
00355 }
00356
00357 static int acf_escape(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
00358 {
00359 char *out = buf;
00360
00361 for (; *data && out - buf < len; data++) {
00362 if (*data == '\'') {
00363 *out = '\'';
00364 out++;
00365 }
00366 *out++ = *data;
00367 }
00368 *out = '\0';
00369
00370 return 0;
00371 }
00372
00373 static struct ast_custom_function escape_function = {
00374 .name = "SQL_ESC",
00375 .synopsis = "Escapes single ticks for use in SQL statements",
00376 .syntax = "SQL_ESC(<string>)",
00377 .desc =
00378 "Used in SQL templates to escape data which may contain single ticks (') which\n"
00379 "are otherwise used to delimit data. For example:\n"
00380 "SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n",
00381 .read = acf_escape,
00382 .write = NULL,
00383 };
00384
00385 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
00386 {
00387 const char *tmp;
00388 int i;
00389
00390 if (!cfg || !catg) {
00391 return EINVAL;
00392 }
00393
00394 *query = ast_calloc(1, sizeof(struct acf_odbc_query));
00395 if (! (*query))
00396 return ENOMEM;
00397
00398 if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
00399 char *tmp2 = ast_strdupa(tmp);
00400 AST_DECLARE_APP_ARGS(write,
00401 AST_APP_ARG(dsn)[5];
00402 );
00403 AST_NONSTANDARD_APP_ARGS(write, tmp2, ',');
00404 for (i = 0; i < 5; i++) {
00405 if (!ast_strlen_zero(write.dsn[i]))
00406 ast_copy_string((*query)->writehandle[i], write.dsn[i], sizeof((*query)->writehandle[i]));
00407 }
00408 }
00409
00410 if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
00411 char *tmp2 = ast_strdupa(tmp);
00412 AST_DECLARE_APP_ARGS(read,
00413 AST_APP_ARG(dsn)[5];
00414 );
00415 AST_NONSTANDARD_APP_ARGS(read, tmp2, ',');
00416 for (i = 0; i < 5; i++) {
00417 if (!ast_strlen_zero(read.dsn[i]))
00418 ast_copy_string((*query)->readhandle[i], read.dsn[i], sizeof((*query)->readhandle[i]));
00419 }
00420 } else {
00421
00422 for (i = 0; i < 5; i++) {
00423 if (!ast_strlen_zero((*query)->writehandle[i]))
00424 ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
00425 }
00426 }
00427
00428 if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
00429 ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s. Please use 'readsql' instead.\n", catg);
00430 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
00431 } else if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")))
00432 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
00433
00434 if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) {
00435 free(*query);
00436 ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
00437 return EINVAL;
00438 }
00439
00440 if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
00441 ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s. Please use 'writesql' instead.\n", catg);
00442 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
00443 } else if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")))
00444 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
00445
00446 if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) {
00447 free(*query);
00448 ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
00449 return EINVAL;
00450 }
00451
00452
00453 ast_set_flag((*query), OPT_ESCAPECOMMAS);
00454 if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
00455 if (ast_false(tmp))
00456 ast_clear_flag((*query), OPT_ESCAPECOMMAS);
00457 }
00458
00459 (*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
00460 if (! (*query)->acf) {
00461 free(*query);
00462 return ENOMEM;
00463 }
00464
00465 if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
00466 asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg);
00467 } else {
00468 asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg);
00469 }
00470
00471 if (!((*query)->acf->name)) {
00472 free((*query)->acf);
00473 free(*query);
00474 return ENOMEM;
00475 }
00476
00477 asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
00478
00479 if (!((*query)->acf->syntax)) {
00480 free((char *)(*query)->acf->name);
00481 free((*query)->acf);
00482 free(*query);
00483 return ENOMEM;
00484 }
00485
00486 (*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
00487 if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) {
00488 asprintf((char **)&((*query)->acf->desc),
00489 "Runs the following query, as defined in func_odbc.conf, performing\n"
00490 "substitution of the arguments into the query as specified by ${ARG1},\n"
00491 "${ARG2}, ... ${ARGn}. When setting the function, the values are provided\n"
00492 "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
00493 "\nRead:\n%s\n\nWrite:\n%s\n",
00494 (*query)->sql_read,
00495 (*query)->sql_write);
00496 } else if (!ast_strlen_zero((*query)->sql_read)) {
00497 asprintf((char **)&((*query)->acf->desc),
00498 "Runs the following query, as defined in func_odbc.conf, performing\n"
00499 "substitution of the arguments into the query as specified by ${ARG1},\n"
00500 "${ARG2}, ... ${ARGn}. This function may only be read, not set.\n\nSQL:\n%s\n",
00501 (*query)->sql_read);
00502 } else if (!ast_strlen_zero((*query)->sql_write)) {
00503 asprintf((char **)&((*query)->acf->desc),
00504 "Runs the following query, as defined in func_odbc.conf, performing\n"
00505 "substitution of the arguments into the query as specified by ${ARG1},\n"
00506 "${ARG2}, ... ${ARGn}. The values are provided either in whole as\n"
00507 "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
00508 "This function may only be set.\nSQL:\n%s\n",
00509 (*query)->sql_write);
00510 } else {
00511 free((char *)(*query)->acf->syntax);
00512 free((char *)(*query)->acf->name);
00513 free((*query)->acf);
00514 free(*query);
00515 ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute. Ignoring.\n", catg);
00516 return EINVAL;
00517 }
00518
00519 if (! ((*query)->acf->desc)) {
00520 free((char *)(*query)->acf->syntax);
00521 free((char *)(*query)->acf->name);
00522 free((*query)->acf);
00523 free(*query);
00524 return ENOMEM;
00525 }
00526
00527 if (ast_strlen_zero((*query)->sql_read)) {
00528 (*query)->acf->read = NULL;
00529 } else {
00530 (*query)->acf->read = acf_odbc_read;
00531 }
00532
00533 if (ast_strlen_zero((*query)->sql_write)) {
00534 (*query)->acf->write = NULL;
00535 } else {
00536 (*query)->acf->write = acf_odbc_write;
00537 }
00538
00539 return 0;
00540 }
00541
00542 static int free_acf_query(struct acf_odbc_query *query)
00543 {
00544 if (query) {
00545 if (query->acf) {
00546 if (query->acf->name)
00547 free((char *)query->acf->name);
00548 if (query->acf->syntax)
00549 free((char *)query->acf->syntax);
00550 if (query->acf->desc)
00551 free((char *)query->acf->desc);
00552 free(query->acf);
00553 }
00554 free(query);
00555 }
00556 return 0;
00557 }
00558
00559 static int load_module(void)
00560 {
00561 int res = 0;
00562 struct ast_config *cfg;
00563 char *catg;
00564
00565 AST_LIST_LOCK(&queries);
00566
00567 cfg = ast_config_load(config);
00568 if (!cfg) {
00569 ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
00570 AST_LIST_UNLOCK(&queries);
00571 return AST_MODULE_LOAD_DECLINE;
00572 }
00573
00574 for (catg = ast_category_browse(cfg, NULL);
00575 catg;
00576 catg = ast_category_browse(cfg, catg)) {
00577 struct acf_odbc_query *query = NULL;
00578 int err;
00579
00580 if ((err = init_acf_query(cfg, catg, &query))) {
00581 if (err == ENOMEM)
00582 ast_log(LOG_ERROR, "Out of memory\n");
00583 else if (err == EINVAL)
00584 ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
00585 else
00586 ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
00587 } else {
00588 AST_LIST_INSERT_HEAD(&queries, query, list);
00589 ast_custom_function_register(query->acf);
00590 }
00591 }
00592
00593 ast_config_destroy(cfg);
00594 res |= ast_custom_function_register(&escape_function);
00595
00596 AST_LIST_UNLOCK(&queries);
00597 return res;
00598 }
00599
00600 static int unload_module(void)
00601 {
00602 struct acf_odbc_query *query;
00603 int res = 0;
00604
00605 AST_LIST_LOCK(&queries);
00606 while (!AST_LIST_EMPTY(&queries)) {
00607 query = AST_LIST_REMOVE_HEAD(&queries, list);
00608 ast_custom_function_unregister(query->acf);
00609 free_acf_query(query);
00610 }
00611
00612 res |= ast_custom_function_unregister(&escape_function);
00613
00614
00615 AST_LIST_UNLOCK(&queries);
00616 usleep(1);
00617 AST_LIST_LOCK(&queries);
00618
00619 AST_LIST_UNLOCK(&queries);
00620 return 0;
00621 }
00622
00623 static int reload(void)
00624 {
00625 int res = 0;
00626 struct ast_config *cfg;
00627 struct acf_odbc_query *oldquery;
00628 char *catg;
00629
00630 AST_LIST_LOCK(&queries);
00631
00632 while (!AST_LIST_EMPTY(&queries)) {
00633 oldquery = AST_LIST_REMOVE_HEAD(&queries, list);
00634 ast_custom_function_unregister(oldquery->acf);
00635 free_acf_query(oldquery);
00636 }
00637
00638 cfg = ast_config_load(config);
00639 if (!cfg) {
00640 ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
00641 goto reload_out;
00642 }
00643
00644 for (catg = ast_category_browse(cfg, NULL);
00645 catg;
00646 catg = ast_category_browse(cfg, catg)) {
00647 struct acf_odbc_query *query = NULL;
00648
00649 if (init_acf_query(cfg, catg, &query)) {
00650 ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
00651 } else {
00652 AST_LIST_INSERT_HEAD(&queries, query, list);
00653 ast_custom_function_register(query->acf);
00654 }
00655 }
00656
00657 ast_config_destroy(cfg);
00658 reload_out:
00659 AST_LIST_UNLOCK(&queries);
00660 return res;
00661 }
00662
00663
00664
00665 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",
00666 .load = load_module,
00667 .unload = unload_module,
00668 .reload = reload,
00669 );
00670