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