Merge "AST-2018-005: Fix tdata leaks when calling pjsip_endpt_send_response(2)"
[asterisk/asterisk.git] / addons / cdr_mysql.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * James Sharp <jsharp@psychoses.org>
5  *
6  * Modified August 2003
7  * Tilghman Lesher <asterisk__cdr__cdr_mysql__200308@the-tilghman.com>
8  *
9  * Modified August 6, 2005
10  * Joseph Benden <joe@thrallingpenguin.com>
11  * Added mysql connection timeout parameter
12  * Added an automatic reconnect as to not lose a cdr record
13  * Cleaned up the original code to match the coding guidelines
14  *
15  * Modified Juli 2006
16  * Martin Portmann <map@infinitum.ch>
17  * Added mysql ssl support
18  *
19  * See http://www.asterisk.org for more information about
20  * the Asterisk project. Please do not directly contact
21  * any of the maintainers of this project for assistance;
22  * the project provides a web site, mailing lists and IRC
23  * channels for your use.
24  *
25  * This program is free software, distributed under the terms of
26  * the GNU General Public License Version 2. See the LICENSE file
27  * at the top of the source tree.
28  */
29
30 /*!
31  * \file
32  * \brief MySQL CDR backend
33  * \ingroup cdr_drivers
34  */
35
36 /*** MODULEINFO
37         <depend>mysqlclient</depend>
38         <defaultenabled>no</defaultenabled>
39         <support_level>deprecated</support_level>
40         <replacement>cdr_adaptive_odbc</replacement>
41  ***/
42
43 #include "asterisk.h"
44
45 #include <mysql/mysql.h>
46 #include <mysql/errmsg.h>
47
48 #include "asterisk/config.h"
49 #include "asterisk/options.h"
50 #include "asterisk/channel.h"
51 #include "asterisk/cdr.h"
52 #include "asterisk/module.h"
53 #include "asterisk/logger.h"
54 #include "asterisk/cli.h"
55 #include "asterisk/strings.h"
56 #include "asterisk/linkedlists.h"
57 #include "asterisk/threadstorage.h"
58
59 #define DATE_FORMAT "%Y-%m-%d %T"
60
61 AST_THREADSTORAGE(sql1_buf);
62 AST_THREADSTORAGE(sql2_buf);
63 AST_THREADSTORAGE(escape_buf);
64
65 static const char desc[] = "MySQL CDR Backend";
66 static const char name[] = "mysql";
67 static const char config[] = "cdr_mysql.conf";
68
69 static struct ast_str *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *dbsock = NULL, *dbtable = NULL, *dbcharset = NULL, *cdrzone = NULL;
70
71 static struct ast_str *ssl_ca = NULL, *ssl_cert = NULL, *ssl_key = NULL;
72
73 static int dbport = 0;
74 static int connected = 0;
75 static time_t connect_time = 0;
76 static int records = 0;
77 static int totalrecords = 0;
78 static int timeout = 0;
79 static int calldate_compat = 0;
80
81 AST_MUTEX_DEFINE_STATIC(mysql_lock);
82
83 struct unload_string {
84         AST_LIST_ENTRY(unload_string) entry;
85         struct ast_str *str;
86 };
87
88 static AST_LIST_HEAD_STATIC(unload_strings, unload_string);
89
90 struct column {
91         char *name;
92         char *cdrname;
93         char *staticvalue;
94         char *type;
95         AST_LIST_ENTRY(column) list;
96 };
97
98 /* Protected with mysql_lock */
99 static AST_RWLIST_HEAD_STATIC(columns, column);
100
101 static MYSQL mysql = { { NULL }, };
102
103 static char *handle_cli_cdr_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
104 {
105         switch (cmd) {
106         case CLI_INIT:
107                 e->command = "cdr mysql status";
108                 e->usage =
109                         "Usage: cdr mysql status\n"
110                         "       Shows current connection status for cdr_mysql\n";
111                 return NULL;
112         case CLI_GENERATE:
113                 return NULL;
114         }
115
116         if (a->argc != 3)
117                 return CLI_SHOWUSAGE;
118
119         if (connected) {
120                 char status[256];
121                 char status2[100] = "";
122                 char buf[362]; /* 256+100+" for "+NULL */
123                 int ctime = time(NULL) - connect_time;
124                 if (dbport)
125                         snprintf(status, 255, "Connected to %s@%s, port %d", ast_str_buffer(dbname), ast_str_buffer(hostname), dbport);
126                 else if (dbsock)
127                         snprintf(status, 255, "Connected to %s on socket file %s", ast_str_buffer(dbname), S_OR(ast_str_buffer(dbsock), "default"));
128                 else
129                         snprintf(status, 255, "Connected to %s@%s", ast_str_buffer(dbname), ast_str_buffer(hostname));
130
131                 if (ast_str_strlen(dbuser))
132                         snprintf(status2, 99, " with username %s", ast_str_buffer(dbuser));
133                 if (ast_str_strlen(dbtable))
134                         snprintf(status2, 99, " using table %s", ast_str_buffer(dbtable));
135
136                 snprintf(buf, sizeof(buf), "%s%s for ", status, status2);
137                 ast_cli_print_timestr_fromseconds(a->fd, ctime, buf);
138
139                 if (records == totalrecords)
140                         ast_cli(a->fd, "  Wrote %d records since last restart.\n", totalrecords);
141                 else
142                         ast_cli(a->fd, "  Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
143         } else {
144                 ast_cli(a->fd, "Not currently connected to a MySQL server.\n");
145         }
146
147         return CLI_SUCCESS;
148 }
149
150 static struct ast_cli_entry cdr_mysql_status_cli[] = {
151         AST_CLI_DEFINE(handle_cli_cdr_mysql_status, "Show connection status of cdr_mysql"),
152 };
153
154 static void configure_connection_charset(void)
155 {
156         if (ast_str_strlen(dbcharset)) {
157                 const char *charset = ast_str_buffer(dbcharset);
158                 if (mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, charset)) {
159                         ast_log(LOG_WARNING, "Failed to set connection charset. Data inserted might be invalid.\n");
160                 }
161         }
162 }
163
164 static int mysql_log(struct ast_cdr *cdr)
165 {
166         struct ast_str *sql1 = ast_str_thread_get(&sql1_buf, 1024), *sql2 = ast_str_thread_get(&sql2_buf, 1024);
167         int retries = 5;
168 #if MYSQL_VERSION_ID >= 50013
169         my_bool my_bool_true = 1;
170 #endif
171
172         if (!sql1 || !sql2) {
173                 ast_log(LOG_ERROR, "Memory error\n");
174                 return -1;
175         }
176
177         ast_mutex_lock(&mysql_lock);
178
179 db_reconnect:
180         if ((!connected) && (hostname || dbsock) && dbuser && password && dbname && dbtable ) {
181                 /* Attempt to connect */
182                 mysql_init(&mysql);
183                 /* Add option to quickly timeout the connection */
184                 if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
185                         ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
186                 }
187 #if MYSQL_VERSION_ID >= 50013
188                 /* Add option for automatic reconnection */
189                 if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
190                         ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
191                 }
192 #endif
193                 if (ssl_ca || ssl_cert || ssl_key) {
194                         mysql_ssl_set(&mysql, ssl_key ? ast_str_buffer(ssl_key) : NULL, ssl_cert ? ast_str_buffer(ssl_cert) : NULL, ssl_ca ? ast_str_buffer(ssl_ca) : NULL, NULL, NULL);
195                 }
196
197                 configure_connection_charset();
198
199                 if (mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL, ssl_ca ? CLIENT_SSL : 0)) {
200                         connected = 1;
201                         connect_time = time(NULL);
202                         records = 0;
203                 } else {
204                         ast_log(LOG_ERROR, "Cannot connect to database server %s: (%d) %s\n", ast_str_buffer(hostname), mysql_errno(&mysql), mysql_error(&mysql));
205                         connected = 0;
206                 }
207         } else {
208                 /* Long connection - ping the server */
209                 int error;
210                 if ((error = mysql_ping(&mysql))) {
211                         connected = 0;
212                         records = 0;
213                         switch (mysql_errno(&mysql)) {
214                                 case CR_SERVER_GONE_ERROR:
215                                 case CR_SERVER_LOST:
216                                         ast_log(LOG_ERROR, "Server has gone away. Attempting to reconnect.\n");
217                                         break;
218                                 default:
219                                         ast_log(LOG_ERROR, "Unknown connection error: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
220                         }
221                         retries--;
222                         if (retries) {
223                                 goto db_reconnect;
224                         } else {
225                                 ast_log(LOG_ERROR, "Retried to connect five times, giving up.\n");
226                         }
227                 }
228         }
229
230         if (connected) {
231                 int column_count = 0;
232                 char *cdrname;
233                 char workspace[2048], *value = NULL;
234                 struct column *entry;
235                 struct ast_str *escape = ast_str_thread_get(&escape_buf, 16);
236
237                 ast_str_set(&sql1, 0, "INSERT INTO %s (", AS_OR(dbtable, "cdr"));
238                 ast_str_set(&sql2, 0, ") VALUES (");
239
240                 AST_RWLIST_RDLOCK(&columns);
241                 AST_RWLIST_TRAVERSE(&columns, entry, list) {
242                         if (!strcmp(entry->name, "calldate")) {
243                                 /*!\note
244                                  * For some dumb reason, "calldate" used to be formulated using
245                                  * the datetime the record was posted, rather than the start
246                                  * time of the call.  If someone really wants the old compatible
247                                  * behavior, it's provided here.
248                                  */
249                                 if (calldate_compat) {
250                                         struct timeval tv = ast_tvnow();
251                                         struct ast_tm tm;
252                                         char timestr[128];
253                                         ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
254                                         ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
255                                         value = ast_strdupa(timestr);
256                                         cdrname = "calldate";
257                                 } else {
258                                         cdrname = "start";
259                                 }
260                         } else {
261                                 cdrname = entry->cdrname;
262                         }
263
264                         /* Construct SQL */
265
266                         /* Need the type and value to determine if we want the raw value or not */
267                         if (entry->staticvalue) {
268                                 value = ast_strdupa(entry->staticvalue);
269                         } else if ((!strcmp(cdrname, "disposition") ||
270                                  !strcmp(cdrname, "amaflags")) &&
271                                 (strstr(entry->type, "int") ||
272                                  strstr(entry->type, "dec") ||
273                                  strstr(entry->type, "float") ||
274                                  strstr(entry->type, "double") ||
275                                  strstr(entry->type, "real") ||
276                                  strstr(entry->type, "numeric") ||
277                                  strstr(entry->type, "fixed"))) {
278                                 ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1);
279                         } else if (!strcmp(cdrname, "start") || !strcmp(cdrname, "answer") ||
280                                  !strcmp(cdrname, "end")) {
281                                 struct ast_tm tm;
282                                 char timestr[128];
283                                 ast_localtime(&cdr->start, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
284                                 ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
285                                 value = ast_strdupa(timestr);
286                         } else if (!strcmp(cdrname, "calldate")) {
287                                 /* Skip calldate - the value has already been dup'd */
288                         } else {
289                                 ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0);
290                         }
291
292                         if (value) {
293                                 size_t valsz;
294
295                                 if (column_count++) {
296                                         ast_str_append(&sql1, 0, ",");
297                                         ast_str_append(&sql2, 0, ",");
298                                 }
299
300                                 if (!strcasecmp(cdrname, "billsec") &&
301                                         (strstr(entry->type, "float") ||
302                                         strstr(entry->type, "double") ||
303                                         strstr(entry->type, "decimal") ||
304                                         strstr(entry->type, "numeric") ||
305                                         strstr(entry->type, "real"))) {
306
307                                         if (!ast_tvzero(cdr->answer)) {
308                                                 snprintf(workspace, sizeof(workspace), "%lf",
309                                                         (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
310                                         } else {
311                                                 ast_copy_string(workspace, "0", sizeof(workspace));
312                                         }
313
314                                         if (!ast_strlen_zero(workspace)) {
315                                                 value = workspace;
316                                         }
317                                 }
318
319                                 if (!strcasecmp(cdrname, "duration") &&
320                                         (strstr(entry->type, "float") ||
321                                         strstr(entry->type, "double") ||
322                                         strstr(entry->type, "decimal") ||
323                                         strstr(entry->type, "numeric") ||
324                                         strstr(entry->type, "real"))) {
325
326                                         snprintf(workspace, sizeof(workspace), "%lf",
327                                                 (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
328
329                                         if (!ast_strlen_zero(workspace)) {
330                                                 value = workspace;
331                                         }
332                                 }
333
334                                 ast_str_make_space(&escape, (valsz = strlen(value)) * 2 + 1);
335                                 mysql_real_escape_string(&mysql, ast_str_buffer(escape), value, valsz);
336
337                                 ast_str_append(&sql1, 0, "`%s`", entry->name);
338                                 ast_str_append(&sql2, 0, "'%s'", ast_str_buffer(escape));
339                         }
340                 }
341                 AST_RWLIST_UNLOCK(&columns);
342
343                 ast_debug(1, "Inserting a CDR record.\n");
344                 ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
345
346                 ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
347
348                 if (mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1))) {
349                         ast_log(LOG_ERROR, "Failed to insert into database: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
350                         mysql_close(&mysql);
351                         connected = 0;
352                 } else {
353                         records++;
354                         totalrecords++;
355                 }
356         }
357         ast_mutex_unlock(&mysql_lock);
358         return 0;
359 }
360
361 static void free_strings(void)
362 {
363         struct unload_string *us;
364
365         AST_LIST_LOCK(&unload_strings);
366         while ((us = AST_LIST_REMOVE_HEAD(&unload_strings, entry))) {
367                 ast_free(us->str);
368                 ast_free(us);
369         }
370         AST_LIST_UNLOCK(&unload_strings);
371 }
372
373 static int my_unload_module(int reload)
374 {
375         struct column *entry;
376
377         ast_cli_unregister_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
378
379         if (connected) {
380                 mysql_close(&mysql);
381                 connected = 0;
382                 records = 0;
383         }
384
385         free_strings();
386
387         if (!reload) {
388                 AST_RWLIST_WRLOCK(&columns);
389         }
390         while ((entry = AST_RWLIST_REMOVE_HEAD(&columns, list))) {
391                 ast_free(entry);
392         }
393         if (!reload) {
394                 AST_RWLIST_UNLOCK(&columns);
395         }
396
397         dbport = 0;
398         if (reload) {
399                 return ast_cdr_backend_suspend(name);
400         } else {
401                 return ast_cdr_unregister(name);
402         }
403 }
404
405 static int my_load_config_string(struct ast_config *cfg, const char *category, const char *variable, struct ast_str **field, const char *def)
406 {
407         struct unload_string *us;
408         const char *tmp;
409
410         if (!(us = ast_calloc(1, sizeof(*us))))
411                 return -1;
412
413         if (!(*field = ast_str_create(16))) {
414                 ast_free(us);
415                 return -1;
416         }
417
418         tmp = ast_variable_retrieve(cfg, category, variable);
419
420         ast_str_set(field, 0, "%s", tmp ? tmp : def);
421
422         us->str = *field;
423
424         AST_LIST_LOCK(&unload_strings);
425         AST_LIST_INSERT_HEAD(&unload_strings, us, entry);
426         AST_LIST_UNLOCK(&unload_strings);
427
428         return 0;
429 }
430
431 static int my_load_config_number(struct ast_config *cfg, const char *category, const char *variable, int *field, int def)
432 {
433         const char *tmp;
434
435         tmp = ast_variable_retrieve(cfg, category, variable);
436
437         if (!tmp || sscanf(tmp, "%30d", field) < 1)
438                 *field = def;
439
440         return 0;
441 }
442
443 static int my_load_module(int reload)
444 {
445         int res;
446         struct ast_config *cfg;
447         struct ast_variable *var;
448         /* CONFIG_STATUS_FILEUNCHANGED is impossible when config_flags is always 0,
449          * and it has to be zero, so a reload can be sent to tell the driver to
450          * rescan the table layout. */
451         struct ast_flags config_flags = { 0 };
452         struct column *entry;
453         char *temp;
454         struct ast_str *compat;
455         MYSQL_ROW row;
456         MYSQL_RES *result;
457         char sqldesc[128];
458 #if MYSQL_VERSION_ID >= 50013
459         my_bool my_bool_true = 1;
460 #endif
461
462         /* Cannot use a conditionally different flag, because the table layout may
463          * have changed, which is not detectable by config file change detection,
464          * but should still cause the configuration to be re-parsed. */
465         cfg = ast_config_load(config, config_flags);
466         if (cfg == CONFIG_STATUS_FILEMISSING) {
467                 ast_log(LOG_WARNING, "Unable to load config for mysql CDR's: %s\n", config);
468                 return AST_MODULE_LOAD_SUCCESS;
469         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
470                 ast_log(LOG_ERROR, "Unable to load configuration file '%s'\n", config);
471                 return AST_MODULE_LOAD_DECLINE;
472         }
473
474         if (reload) {
475                 AST_RWLIST_WRLOCK(&columns);
476                 my_unload_module(1);
477         }
478
479         var = ast_variable_browse(cfg, "global");
480         if (!var) {
481                 /* nothing configured */
482                 if (reload) {
483                         AST_RWLIST_UNLOCK(&columns);
484                 }
485                 ast_config_destroy(cfg);
486                 return AST_MODULE_LOAD_SUCCESS;
487         }
488
489         res = 0;
490
491         res |= my_load_config_string(cfg, "global", "hostname", &hostname, "localhost");
492         res |= my_load_config_string(cfg, "global", "dbname", &dbname, "astriskcdrdb");
493         res |= my_load_config_string(cfg, "global", "user", &dbuser, "root");
494         res |= my_load_config_string(cfg, "global", "sock", &dbsock, "");
495         res |= my_load_config_string(cfg, "global", "table", &dbtable, "cdr");
496         res |= my_load_config_string(cfg, "global", "password", &password, "");
497
498         res |= my_load_config_string(cfg, "global", "charset", &dbcharset, "");
499
500         res |= my_load_config_string(cfg, "global", "ssl_ca", &ssl_ca, "");
501         res |= my_load_config_string(cfg, "global", "ssl_cert", &ssl_cert, "");
502         res |= my_load_config_string(cfg, "global", "ssl_key", &ssl_key, "");
503
504         res |= my_load_config_number(cfg, "global", "port", &dbport, MYSQL_PORT);
505         res |= my_load_config_number(cfg, "global", "timeout", &timeout, 0);
506         res |= my_load_config_string(cfg, "global", "compat", &compat, "no");
507         res |= my_load_config_string(cfg, "global", "cdrzone", &cdrzone, "");
508         if (ast_str_strlen(cdrzone) == 0) {
509                 for (; var; var = var->next) {
510                         if (!strcasecmp(var->name, "usegmtime") && ast_true(var->value)) {
511                                 ast_str_set(&cdrzone, 0, "UTC");
512                         }
513                 }
514         }
515
516         if (ast_true(ast_str_buffer(compat))) {
517                 calldate_compat = 1;
518         } else {
519                 calldate_compat = 0;
520         }
521
522         if (res < 0) {
523                 if (reload) {
524                         AST_RWLIST_UNLOCK(&columns);
525                 }
526                 ast_config_destroy(cfg);
527                 free_strings();
528
529                 return AST_MODULE_LOAD_DECLINE;
530         }
531
532         /* Check for any aliases */
533         if (!reload) {
534                 /* Lock, if not already */
535                 AST_RWLIST_WRLOCK(&columns);
536         }
537         while ((entry = AST_LIST_REMOVE_HEAD(&columns, list))) {
538                 ast_free(entry);
539         }
540
541         ast_debug(1, "Got hostname of %s\n", ast_str_buffer(hostname));
542         ast_debug(1, "Got port of %d\n", dbport);
543         ast_debug(1, "Got a timeout of %d\n", timeout);
544         if (ast_str_strlen(dbsock)) {
545                 ast_debug(1, "Got sock file of %s\n", ast_str_buffer(dbsock));
546         }
547         ast_debug(1, "Got user of %s\n", ast_str_buffer(dbuser));
548         ast_debug(1, "Got dbname of %s\n", ast_str_buffer(dbname));
549         ast_debug(1, "Got password of %s\n", ast_str_buffer(password));
550         ast_debug(1, "%sunning in calldate compatibility mode\n", calldate_compat ? "R" : "Not r");
551         ast_debug(1, "Dates and times are localized to %s\n", S_OR(ast_str_buffer(cdrzone), "local timezone"));
552
553         if (ast_str_strlen(dbcharset)) {
554                 ast_debug(1, "Got DB charset of %s\n", ast_str_buffer(dbcharset));
555         }
556
557         mysql_init(&mysql);
558
559         if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
560                 ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
561         }
562
563 #if MYSQL_VERSION_ID >= 50013
564         /* Add option for automatic reconnection */
565         if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
566                 ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
567         }
568 #endif
569
570         if ((ssl_ca && ast_str_strlen(ssl_ca)) || (ssl_cert && ast_str_strlen(ssl_cert)) || (ssl_key && ast_str_strlen(ssl_key))) {
571                 mysql_ssl_set(&mysql,
572                         ssl_key ? ast_str_buffer(ssl_key) : NULL,
573                         ssl_cert ? ast_str_buffer(ssl_cert) : NULL,
574                         ssl_ca ? ast_str_buffer(ssl_ca) : NULL,
575                         NULL, NULL);
576         }
577         temp = dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL;
578
579         configure_connection_charset();
580
581         if (!mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, temp, ssl_ca && ast_str_strlen(ssl_ca) ? CLIENT_SSL : 0)) {
582                 ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n", ast_str_buffer(dbname), ast_str_buffer(hostname));
583                 connected = 0;
584                 records = 0;
585         } else {
586                 ast_debug(1, "Successfully connected to MySQL database.\n");
587                 connected = 1;
588                 records = 0;
589                 connect_time = time(NULL);
590
591                 /* Get table description */
592                 snprintf(sqldesc, sizeof(sqldesc), "DESC %s", dbtable ? ast_str_buffer(dbtable) : "cdr");
593                 if (mysql_query(&mysql, sqldesc)) {
594                         ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
595                         mysql_close(&mysql);
596                         connected = 0;
597                         AST_RWLIST_UNLOCK(&columns);
598                         ast_config_destroy(cfg);
599                         free_strings();
600
601                         return AST_MODULE_LOAD_DECLINE;
602                 }
603
604                 if (!(result = mysql_store_result(&mysql))) {
605                         ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
606                         mysql_close(&mysql);
607                         connected = 0;
608                         AST_RWLIST_UNLOCK(&columns);
609                         ast_config_destroy(cfg);
610                         free_strings();
611
612                         return AST_MODULE_LOAD_DECLINE;
613                 }
614
615                 while ((row = mysql_fetch_row(result))) {
616                         struct column *entry;
617                         char *cdrvar = "", *staticvalue = "";
618
619                         ast_debug(1, "Got a field '%s' of type '%s'\n", row[0], row[1]);
620                         /* Check for an alias or a static value */
621                         for (var = ast_variable_browse(cfg, "columns"); var; var = var->next) {
622                                 if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, row[0]) == 0 ) {
623                                         char *alias = ast_strdupa(var->name + 5);
624                                         cdrvar = ast_strip(alias);
625                                         ast_verb(3, "Found alias %s for column %s\n", cdrvar, row[0]);
626                                         break;
627                                 } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, row[0]) == 0) {
628                                         char *item = ast_strdupa(var->name + 6);
629                                         item = ast_strip(item);
630                                         if (item[0] == '"' && item[strlen(item) - 1] == '"') {
631                                                 /* Remove surrounding quotes */
632                                                 item[strlen(item) - 1] = '\0';
633                                                 item++;
634                                         }
635                                         staticvalue = item;
636                                 }
637                         }
638
639                         entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(row[0]) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1 + strlen(row[1]) + 1);
640                         if (!entry) {
641                                 ast_log(LOG_ERROR, "Out of memory creating entry for column '%s'\n", row[0]);
642                                 res = -1;
643                                 break;
644                         }
645
646                         entry->name = (char *)entry + sizeof(*entry);
647                         strcpy(entry->name, row[0]);
648
649                         if (!ast_strlen_zero(cdrvar)) {
650                                 entry->cdrname = entry->name + strlen(row[0]) + 1;
651                                 strcpy(entry->cdrname, cdrvar);
652                         } else { /* Point to same place as the column name */
653                                 entry->cdrname = (char *)entry + sizeof(*entry);
654                         }
655
656                         if (!ast_strlen_zero(staticvalue)) {
657                                 entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
658                                 strcpy(entry->staticvalue, staticvalue);
659                                 ast_debug(1, "staticvalue length: %d\n", (int) strlen(staticvalue) );
660                                 entry->type = entry->staticvalue + strlen(entry->staticvalue) + 1;
661                         } else {
662                                 entry->type = entry->cdrname + strlen(entry->cdrname) + 1;
663                         }
664                         strcpy(entry->type, row[1]);
665
666                         ast_debug(1, "Entry name '%s'\n", entry->name);
667                         ast_debug(1, "   cdrname '%s'\n", entry->cdrname);
668                         ast_debug(1, "    static '%s'\n", entry->staticvalue);
669                         ast_debug(1, "      type '%s'\n", entry->type);
670
671                         AST_LIST_INSERT_TAIL(&columns, entry, list);
672                 }
673                 mysql_free_result(result);
674         }
675         AST_RWLIST_UNLOCK(&columns);
676         ast_config_destroy(cfg);
677         if (res < 0) {
678                 my_unload_module(0);
679                 return AST_MODULE_LOAD_DECLINE;
680         }
681
682         if (!reload) {
683                 res = ast_cdr_register(name, desc, mysql_log);
684         } else {
685                 res = ast_cdr_backend_unsuspend(name);
686         }
687         if (res) {
688                 ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
689         } else {
690                 res = ast_cli_register_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
691         }
692
693         if (res) {
694                 my_unload_module(0);
695                 return AST_MODULE_LOAD_DECLINE;
696         }
697
698         return AST_MODULE_LOAD_SUCCESS;
699 }
700
701 static int load_module(void)
702 {
703         return my_load_module(0);
704 }
705
706 static int unload_module(void)
707 {
708         return my_unload_module(0);
709 }
710
711 static int reload(void)
712 {
713         int ret;
714
715         ast_mutex_lock(&mysql_lock);
716         ret = my_load_module(1);
717         ast_mutex_unlock(&mysql_lock);
718
719         return ret;
720 }
721
722 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MySQL CDR Backend",
723         .support_level = AST_MODULE_SUPPORT_DEPRECATED,
724         .load = load_module,
725         .unload = unload_module,
726         .reload = reload,
727 );