Rename mysql.conf to app_mysql.conf, make module support both names
[asterisk/asterisk.git] / addons / app_mysql.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2004, Constantine Filin and Christos Ricudis
5  *
6  * Christos Ricudis <ricudis@itc.auth.gr>
7  * Constantine Filin <cf@intermedia.net>
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*!
21  * \file
22  * \brief MYSQL dialplan application
23  * \ingroup applications
24  */
25
26 /*** MODULEINFO
27         <depend>mysqlclient</depend>
28         <defaultenabled>no</defaultenabled>
29  ***/
30
31 #include "asterisk.h"
32
33 #include <mysql/mysql.h>
34
35 #include "asterisk/file.h"
36 #include "asterisk/logger.h"
37 #include "asterisk/channel.h"
38 #include "asterisk/pbx.h"
39 #include "asterisk/module.h"
40 #include "asterisk/linkedlists.h"
41 #include "asterisk/chanvars.h"
42 #include "asterisk/lock.h"
43 #include "asterisk/options.h"
44 #include "asterisk/app.h"
45 #include "asterisk/config.h"
46
47 #define EXTRA_LOG 0
48
49 enum { NULLSTRING, NULLVALUE, EMPTYSTRING } nullvalue = NULLSTRING;
50
51 static const char app[] = "MYSQL";
52
53 static const char synopsis[] = "Do several mySQLy things";
54
55 static const char descrip[] =
56 "MYSQL():  Do several mySQLy things\n"
57 "Syntax:\n"
58 "  MYSQL(Set timeout <num>)\n"
59 "    Set the connection timeout, in seconds.\n"
60 "  MYSQL(Connect connid dhhost dbuser dbpass dbname)\n"
61 "    Connects to a database.  Arguments contain standard MySQL parameters\n"
62 "    passed to function mysql_real_connect.  Connection identifer returned\n"
63 "    in ${connid}\n"
64 "  MYSQL(Query resultid ${connid} query-string)\n"
65 "    Executes standard MySQL query contained in query-string using established\n"
66 "    connection identified by ${connid}. Result of query is stored in ${resultid}.\n"
67 "  MYSQL(Nextresult resultid ${connid}\n"
68 "    If last query returned more than one result set, it stores the next\n"
69 "    result set in ${resultid}. It's useful with stored procedures\n"
70 "  MYSQL(Fetch fetchid ${resultid} var1 var2 ... varN)\n"
71 "    Fetches a single row from a result set contained in ${result_identifier}.\n"
72 "    Assigns returned fields to ${var1} ... ${varn}.  ${fetchid} is set TRUE\n"
73 "    if additional rows exist in result set.\n"
74 "  MYSQL(Clear ${resultid})\n"
75 "    Frees memory and datastructures associated with result set.\n"
76 "  MYSQL(Disconnect ${connid})\n"
77 "    Disconnects from named connection to MySQL.\n"
78 "  On exit, always returns 0. Sets MYSQL_STATUS to 0 on success and -1 on error.\n";
79
80 /*
81 EXAMPLES OF USE :
82
83 exten => s,2,MYSQL(Connect connid localhost asterisk mypass credit)
84 exten => s,3,MYSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
85 exten => s,4,MYSQL(Fetch fetchid ${resultid} datavar1 datavar2)
86 exten => s,5,GotoIf(${fetchid}?6:8)
87 exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
88 exten => s,7,Goto(s,4)
89 exten => s,8,MYSQL(Clear ${resultid})
90 exten => s,9,MYSQL(Disconnect ${connid})
91 */
92
93 AST_MUTEX_DEFINE_STATIC(_mysql_mutex);
94
95 #define MYSQL_CONFIG "app_mysql.conf"
96 #define MYSQL_CONFIG_OLD "mysql.conf"
97 #define AST_MYSQL_ID_DUMMY   0
98 #define AST_MYSQL_ID_CONNID  1
99 #define AST_MYSQL_ID_RESID   2
100 #define AST_MYSQL_ID_FETCHID 3
101
102 static int autoclear = 0;
103
104 static void mysql_ds_destroy(void *data);
105 static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
106
107 static struct ast_datastore_info mysql_ds_info = {
108         .type = "APP_ADDON_SQL_MYSQL",
109         .destroy = mysql_ds_destroy,
110         .chan_fixup = mysql_ds_fixup,
111 };
112
113 struct ast_MYSQL_id {
114         struct ast_channel *owner;
115         int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
116         int identifier;
117         void *data;
118         AST_LIST_ENTRY(ast_MYSQL_id) entries;
119 } *ast_MYSQL_id;
120
121 AST_LIST_HEAD(MYSQLidshead,ast_MYSQL_id) _mysql_ids_head;
122
123 static void mysql_ds_destroy(void *data)
124 {
125         /* Destroy any IDs owned by the channel */
126         struct ast_MYSQL_id *i;
127         if (AST_LIST_LOCK(&_mysql_ids_head)) {
128                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
129         } else {
130                 AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
131                         if (i->owner == data) {
132                                 AST_LIST_REMOVE_CURRENT(entries);
133                                 if (i->identifier_type == AST_MYSQL_ID_CONNID) {
134                                         /* Drop connection */
135                                         mysql_close(i->data);
136                                 } else if (i->identifier_type == AST_MYSQL_ID_RESID) {
137                                         /* Drop result */
138                                         mysql_free_result(i->data);
139                                 }
140                                 ast_free(i);
141                         }
142                 }
143                 AST_LIST_TRAVERSE_SAFE_END
144                 AST_LIST_UNLOCK(&_mysql_ids_head);
145         }
146 }
147
148 static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
149 {
150         /* Destroy any IDs owned by the channel */
151         struct ast_MYSQL_id *i;
152         if (AST_LIST_LOCK(&_mysql_ids_head)) {
153                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
154         } else {
155                 AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
156                         if (i->owner == data) {
157                                 AST_LIST_REMOVE_CURRENT(entries);
158                                 if (i->identifier_type == AST_MYSQL_ID_CONNID) {
159                                         /* Drop connection */
160                                         mysql_close(i->data);
161                                 } else if (i->identifier_type == AST_MYSQL_ID_RESID) {
162                                         /* Drop result */
163                                         mysql_free_result(i->data);
164                                 }
165                                 ast_free(i);
166                         }
167                 }
168                 AST_LIST_TRAVERSE_SAFE_END
169                 AST_LIST_UNLOCK(&_mysql_ids_head);
170         }
171 }
172
173 /* helpful procs */
174 static void *find_identifier(int identifier, int identifier_type)
175 {
176         struct MYSQLidshead *headp = &_mysql_ids_head;
177         struct ast_MYSQL_id *i;
178         void *res=NULL;
179         int found=0;
180
181         if (AST_LIST_LOCK(headp)) {
182                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
183         } else {
184                 AST_LIST_TRAVERSE(headp, i, entries) {
185                         if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
186                                 found = 1;
187                                 res = i->data;
188                                 break;
189                         }
190                 }
191                 if (!found) {
192                         ast_log(LOG_WARNING, "Identifier %d, identifier_type %d not found in identifier list\n", identifier, identifier_type);
193                 }
194                 AST_LIST_UNLOCK(headp);
195         }
196
197         return res;
198 }
199
200 static int add_identifier(struct ast_channel *chan, int identifier_type, void *data)
201 {
202         struct ast_MYSQL_id *i = NULL, *j = NULL;
203         struct MYSQLidshead *headp = &_mysql_ids_head;
204         int maxidentifier = 0;
205
206         if (AST_LIST_LOCK(headp)) {
207                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
208                 return -1;
209         } else {
210                 i = malloc(sizeof(*i));
211                 AST_LIST_TRAVERSE(headp, j, entries) {
212                         if (j->identifier > maxidentifier) {
213                                 maxidentifier = j->identifier;
214                         }
215                 }
216                 i->identifier = maxidentifier + 1;
217                 i->identifier_type = identifier_type;
218                 i->data = data;
219                 i->owner = chan;
220                 AST_LIST_INSERT_HEAD(headp, i, entries);
221                 AST_LIST_UNLOCK(headp);
222         }
223         return i->identifier;
224 }
225
226 static int del_identifier(int identifier, int identifier_type)
227 {
228         struct ast_MYSQL_id *i;
229         struct MYSQLidshead *headp = &_mysql_ids_head;
230         int found = 0;
231
232         if (AST_LIST_LOCK(headp)) {
233                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
234         } else {
235                 AST_LIST_TRAVERSE(headp, i, entries) {
236                         if ((i->identifier == identifier) &&
237                             (i->identifier_type == identifier_type)) {
238                                 AST_LIST_REMOVE(headp, i, entries);
239                                 free(i);
240                                 found = 1;
241                                 break;
242                         }
243                 }
244                 AST_LIST_UNLOCK(headp);
245         }
246
247         if (found == 0) {
248                 ast_log(LOG_WARNING, "Could not find identifier %d, identifier_type %d in list to delete\n", identifier, identifier_type);
249                 return -1;
250         } else {
251                 return 0;
252         }
253 }
254
255 static int set_asterisk_int(struct ast_channel *chan, char *varname, int id)
256 {
257         if (id >= 0) {
258                 char s[12] = "";
259                 snprintf(s, sizeof(s), "%d", id);
260                 ast_debug(5, "MYSQL: setting var '%s' to value '%s'\n", varname, s);
261                 pbx_builtin_setvar_helper(chan, varname, s);
262         }
263         return id;
264 }
265
266 static int add_identifier_and_set_asterisk_int(struct ast_channel *chan, char *varname, int identifier_type, void *data)
267 {
268         return set_asterisk_int(chan, varname, add_identifier(chan, identifier_type, data));
269 }
270
271 static int safe_scan_int(char **data, char *delim, int def)
272 {
273         char *end;
274         int res = def;
275         char *s = strsep(data, delim);
276         if (s) {
277                 res = strtol(s, &end, 10);
278                 if (*end)
279                         res = def;  /* not an integer */
280         }
281         return res;
282 }
283
284 static int aMYSQL_set(struct ast_channel *chan, char *data)
285 {
286         char *var, *tmp;
287         AST_DECLARE_APP_ARGS(args,
288                 AST_APP_ARG(set);
289                 AST_APP_ARG(variable);
290                 AST_APP_ARG(value);
291         );
292
293         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
294
295         if (args.argc == 3) {
296                 var = alloca(6 + strlen(args.variable) + 1);
297                 sprintf(var, "MYSQL_%s", args.variable);
298
299                 /* Make the parameter case-insensitive */
300                 for (tmp = var + 6; *tmp; tmp++)
301                         *tmp = toupper(*tmp);
302
303                 pbx_builtin_setvar_helper(chan, var, args.value);
304         }
305         return 0;
306 }
307
308 /* MYSQL operations */
309 static int aMYSQL_connect(struct ast_channel *chan, char *data)
310 {
311         AST_DECLARE_APP_ARGS(args,
312                 AST_APP_ARG(connect);
313                 AST_APP_ARG(connid);
314                 AST_APP_ARG(dbhost);
315                 AST_APP_ARG(dbuser);
316                 AST_APP_ARG(dbpass);
317                 AST_APP_ARG(dbname);
318         );
319         MYSQL *mysql;
320         int timeout;
321         const char *ctimeout;
322
323         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
324
325         if (args.argc != 6) {
326                 ast_log(LOG_WARNING, "MYSQL_connect is missing some arguments\n");
327                 return -1;
328         }
329
330         if (!(mysql = mysql_init(NULL))) {
331                 ast_log(LOG_WARNING, "mysql_init returned NULL\n");
332                 return -1;
333         }
334
335         ctimeout = pbx_builtin_getvar_helper(chan, "MYSQL_TIMEOUT");
336         if (ctimeout && sscanf(ctimeout, "%d", &timeout) == 1) {
337                 mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
338         }
339
340         if (! mysql_real_connect(mysql, args.dbhost, args.dbuser, args.dbpass, args.dbname, 0, NULL,
341 #ifdef CLIENT_MULTI_STATEMENTS
342                         CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS
343 #elif defined(CLIENT_MULTI_QUERIES)
344                         CLIENT_MULTI_QUERIES
345 #else
346                         0
347 #endif
348                 )) {
349                 ast_log(LOG_WARNING, "mysql_real_connect(mysql,%s,%s,dbpass,%s,...) failed(%d): %s\n",
350                                 args.dbhost, args.dbuser, args.dbname, mysql_errno(mysql), mysql_error(mysql));
351                 return -1;
352         }
353
354         add_identifier_and_set_asterisk_int(chan, args.connid, AST_MYSQL_ID_CONNID, mysql);
355         return 0;
356 }
357
358 static int aMYSQL_query(struct ast_channel *chan, char *data)
359 {
360         AST_DECLARE_APP_ARGS(args,
361                 AST_APP_ARG(query);
362                 AST_APP_ARG(resultid);
363                 AST_APP_ARG(connid);
364                 AST_APP_ARG(sql);
365         );
366         MYSQL       *mysql;
367         MYSQL_RES   *mysqlres;
368         int connid;
369         int mysql_query_res;
370
371         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
372
373         if (args.argc != 4 || (connid = atoi(args.connid)) == 0) {
374                 ast_log(LOG_WARNING, "missing some arguments\n");
375                 return -1;
376         }
377
378         if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
379                 ast_log(LOG_WARNING, "Invalid connection identifier %s passed in aMYSQL_query\n", args.connid);
380                 return -1;
381         }
382
383         if ((mysql_query_res = mysql_query(mysql, args.sql)) != 0) {
384                 ast_log(LOG_WARNING, "aMYSQL_query: mysql_query failed. Error: %s\n", mysql_error(mysql));
385                 return -1;
386         }
387
388         if ((mysqlres = mysql_store_result(mysql))) {
389                 add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
390                 return 0;
391         } else if (!mysql_field_count(mysql)) {
392                 return 0;
393         } else
394                 ast_log(LOG_WARNING, "mysql_store_result() failed on query %s\n", args.sql);
395
396         return -1;
397 }
398
399 static int aMYSQL_nextresult(struct ast_channel *chan, char *data)
400 {
401         MYSQL       *mysql;
402         MYSQL_RES   *mysqlres;
403         AST_DECLARE_APP_ARGS(args,
404                 AST_APP_ARG(nextresult);
405                 AST_APP_ARG(resultid);
406                 AST_APP_ARG(connid);
407         );
408         int connid = -1;
409
410         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
411         sscanf(args.connid, "%d", &connid);
412
413         if (args.argc != 3 || connid <= 0) {
414                 ast_log(LOG_WARNING, "missing some arguments\n");
415                 return -1;
416         }
417
418         if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
419                 ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_query\n", connid);
420                 return -1;
421         }
422
423         if (mysql_more_results(mysql)) {
424                 mysql_next_result(mysql);
425                 if ((mysqlres = mysql_store_result(mysql))) {
426                         add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
427                         return 0;
428                 } else if (!mysql_field_count(mysql)) {
429                         return 0;
430                 } else
431                         ast_log(LOG_WARNING, "mysql_store_result() failed on storing next_result\n");
432         } else
433                 ast_log(LOG_WARNING, "mysql_more_results() result set has no more results\n");
434
435         return 0;
436 }
437
438
439 static int aMYSQL_fetch(struct ast_channel *chan, char *data)
440 {
441         MYSQL_RES *mysqlres;
442         MYSQL_ROW mysqlrow;
443         AST_DECLARE_APP_ARGS(args,
444                 AST_APP_ARG(fetch);
445                 AST_APP_ARG(resultvar);
446                 AST_APP_ARG(fetchid);
447                 AST_APP_ARG(vars);
448         );
449         char *s5, *parse;
450         int resultid = -1, numFields, j;
451
452         parse = ast_strdupa(data);
453         AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
454         sscanf(args.fetchid, "%d", &resultid);
455
456         if (args.resultvar && (resultid >= 0) ) {
457                 if ((mysqlres = find_identifier(resultid, AST_MYSQL_ID_RESID)) != NULL) {
458                         /* Grab the next row */
459                         if ((mysqlrow = mysql_fetch_row(mysqlres)) != NULL) {
460                                 numFields = mysql_num_fields(mysqlres);
461                                 for (j = 0; j < numFields; j++) {
462                                         s5 = strsep(&args.vars, " ");
463                                         if (s5 == NULL) {
464                                                 ast_log(LOG_WARNING, "ast_MYSQL_fetch: More fields (%d) than variables (%d)\n", numFields, j);
465                                                 break;
466                                         }
467
468                                         pbx_builtin_setvar_helper(chan, s5, mysqlrow[j] ? mysqlrow[j] :
469                                                 nullvalue == NULLSTRING ? "NULL" :
470                                                 nullvalue == EMPTYSTRING ? "" :
471                                                 NULL);
472                                 }
473                                 ast_debug(5, "ast_MYSQL_fetch: numFields=%d\n", numFields);
474                                 set_asterisk_int(chan, args.resultvar, 1); /* try more rows */
475                         } else {
476                                 ast_debug(5, "ast_MYSQL_fetch : EOF\n");
477                                 set_asterisk_int(chan, args.resultvar, 0); /* no more rows */
478                         }
479                         return 0;
480                 } else {
481                         set_asterisk_int(chan, args.resultvar, 0);
482                         ast_log(LOG_WARNING, "aMYSQL_fetch: Invalid result identifier %d passed\n", resultid);
483                 }
484         } else {
485                 ast_log(LOG_WARNING, "aMYSQL_fetch: missing some arguments\n");
486         }
487
488         return -1;
489 }
490
491 static int aMYSQL_clear(struct ast_channel *chan, char *data)
492 {
493         MYSQL_RES *mysqlres;
494
495         int id;
496         strsep(&data, " "); /* eat the first token, we already know it :P */
497         id = safe_scan_int(&data, " \n", -1);
498         if ((mysqlres = find_identifier(id, AST_MYSQL_ID_RESID)) == NULL) {
499                 ast_log(LOG_WARNING, "Invalid result identifier %d passed in aMYSQL_clear\n", id);
500         } else {
501                 mysql_free_result(mysqlres);
502                 del_identifier(id, AST_MYSQL_ID_RESID);
503         }
504
505         return 0;
506 }
507
508 static int aMYSQL_disconnect(struct ast_channel *chan, char *data)
509 {
510         MYSQL *mysql;
511         int id;
512         strsep(&data, " "); /* eat the first token, we already know it :P */
513
514         id = safe_scan_int(&data, " \n", -1);
515         if ((mysql = find_identifier(id, AST_MYSQL_ID_CONNID)) == NULL) {
516                 ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_disconnect\n", id);
517         } else {
518                 mysql_close(mysql);
519                 del_identifier(id, AST_MYSQL_ID_CONNID);
520         }
521
522         return 0;
523 }
524
525 static int MYSQL_exec(struct ast_channel *chan, const char *data)
526 {
527         int result;
528         char sresult[10];
529
530         ast_debug(5, "MYSQL: data=%s\n", data);
531
532         if (!data) {
533                 ast_log(LOG_WARNING, "MYSQL requires an argument (see manual)\n");
534                 return -1;
535         }
536
537         result = 0;
538
539         if (autoclear) {
540                 struct ast_datastore *mysql_store = ast_channel_datastore_find(chan, &mysql_ds_info, NULL);
541                 if (!mysql_store) {
542                         if (!(mysql_store = ast_datastore_alloc(&mysql_ds_info, NULL))) {
543                                 ast_log(LOG_WARNING, "Unable to allocate new datastore.\n");
544                         } else {
545                                 mysql_store->data = chan;
546                                 ast_channel_datastore_add(chan, mysql_store);
547                         }
548                 }
549         }
550         ast_mutex_lock(&_mysql_mutex);
551
552         if (strncasecmp("connect", data, strlen("connect")) == 0) {
553                 result = aMYSQL_connect(chan, ast_strdupa(data));
554         } else if (strncasecmp("query", data, strlen("query")) == 0) {
555                 result = aMYSQL_query(chan, ast_strdupa(data));
556         } else if (strncasecmp("nextresult", data, strlen("nextresult")) == 0) {
557                 result = aMYSQL_nextresult(chan, ast_strdupa(data));
558         } else if (strncasecmp("fetch", data, strlen("fetch")) == 0) {
559                 result = aMYSQL_fetch(chan, ast_strdupa(data));
560         } else if (strncasecmp("clear", data, strlen("clear")) == 0) {
561                 result = aMYSQL_clear(chan, ast_strdupa(data));
562         } else if (strncasecmp("disconnect", data, strlen("disconnect")) == 0) {
563                 result = aMYSQL_disconnect(chan, ast_strdupa(data));
564         } else if (strncasecmp("set", data, 3) == 0) {
565                 result = aMYSQL_set(chan, ast_strdupa(data));
566         } else {
567                 ast_log(LOG_WARNING, "Unknown argument to MYSQL application : %s\n", data);
568                 result = -1;
569         }
570
571         ast_mutex_unlock(&_mysql_mutex);
572
573         snprintf(sresult, sizeof(sresult), "%d", result);
574         pbx_builtin_setvar_helper(chan, "MYSQL_STATUS", sresult);
575         return 0;
576 }
577
578 static int unload_module(void)
579 {
580         return ast_unregister_application(app);
581 }
582
583 static int load_module(void)
584 {
585         struct MYSQLidshead *headp = &_mysql_ids_head;
586         struct ast_flags config_flags = { 0 };
587         struct ast_config *cfg = ast_config_load(MYSQL_CONFIG, config_flags);
588         const char *temp;
589
590         if (!cfg) {
591                 /* Backwards compatibility ftw */
592                 cfg = ast_config_load(MYSQL_CONFIG_OLD, config_flags);
593         }
594
595         if (cfg) {
596                 if ((temp = ast_variable_retrieve(cfg, "general", "nullvalue"))) {
597                         if (!strcasecmp(temp, "nullstring")) {
598                                 nullvalue = NULLSTRING;
599                         } else if (!strcasecmp(temp, "emptystring")) {
600                                 nullvalue = EMPTYSTRING;
601                         } else if (!strcasecmp(temp, "null")) {
602                                 nullvalue = NULLVALUE;
603                         } else {
604                                 ast_log(LOG_WARNING, "Illegal value for 'nullvalue': '%s' (must be 'nullstring', 'null', or 'emptystring')\n", temp);
605                         }
606                 }
607                 if ((temp = ast_variable_retrieve(cfg, "general", "autoclear")) && ast_true(temp)) {
608                         autoclear = 1;
609                 }
610                 ast_config_destroy(cfg);
611         }
612
613         AST_LIST_HEAD_INIT(headp);
614         return ast_register_application(app, MYSQL_exec, synopsis, descrip);
615 }
616
617 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Mysql Interface");