aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/cdr
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/cdr')
-rw-r--r--trunk/cdr/Makefile20
-rw-r--r--trunk/cdr/cdr_adaptive_odbc.c674
-rw-r--r--trunk/cdr/cdr_csv.c346
-rw-r--r--trunk/cdr/cdr_custom.c169
-rw-r--r--trunk/cdr/cdr_manager.c204
-rw-r--r--trunk/cdr/cdr_odbc.c264
-rw-r--r--trunk/cdr/cdr_pgsql.c335
-rw-r--r--trunk/cdr/cdr_radius.c260
-rw-r--r--trunk/cdr/cdr_sqlite.c214
-rw-r--r--trunk/cdr/cdr_sqlite3_custom.c357
-rw-r--r--trunk/cdr/cdr_tds.c531
11 files changed, 3374 insertions, 0 deletions
diff --git a/trunk/cdr/Makefile b/trunk/cdr/Makefile
new file mode 100644
index 000000000..a2767b31e
--- /dev/null
+++ b/trunk/cdr/Makefile
@@ -0,0 +1,20 @@
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Makefile for CDR backends
+#
+# Copyright (C) 1999-2006, Digium, Inc.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+-include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps
+
+MODULE_PREFIX=cdr
+MENUSELECT_CATEGORY=CDR
+MENUSELECT_DESCRIPTION=Call Detail Recording
+
+all: _all
+
+include $(ASTTOPDIR)/Makefile.moddir_rules
diff --git a/trunk/cdr/cdr_adaptive_odbc.c b/trunk/cdr/cdr_adaptive_odbc.c
new file mode 100644
index 000000000..9daca2cf8
--- /dev/null
+++ b/trunk/cdr/cdr_adaptive_odbc.c
@@ -0,0 +1,674 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2007, Tilghman Lesher
+ *
+ * Tilghman Lesher <cdr_adaptive_odbc__v1@the-tilghman.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Adaptive ODBC CDR backend
+ *
+ * \author Tilghman Lesher <cdr_adaptive_odbc__v1@the-tilghman.com>
+ * \ingroup cdr_drivers
+ */
+
+/*** MODULEINFO
+ <depend>unixodbc</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <time.h>
+
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/res_odbc.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+
+#define CONFIG "cdr_adaptive_odbc.conf"
+
+static char *name = "Adaptive ODBC";
+/* Optimization to reduce number of memory allocations */
+static int maxsize = 512, maxsize2 = 512;
+
+struct columns {
+ char *name;
+ char *cdrname;
+ char *filtervalue;
+ SQLSMALLINT type;
+ SQLINTEGER size;
+ SQLSMALLINT decimals;
+ SQLSMALLINT radix;
+ SQLSMALLINT nullable;
+ SQLINTEGER octetlen;
+ AST_LIST_ENTRY(columns) list;
+};
+
+struct tables {
+ char *connection;
+ char *table;
+ AST_LIST_HEAD_NOLOCK(odbc_columns, columns) columns;
+ AST_RWLIST_ENTRY(tables) list;
+};
+
+static AST_RWLIST_HEAD_STATIC(odbc_tables, tables);
+
+static int load_config(void)
+{
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ const char *tmp, *catg;
+ struct tables *tableptr;
+ struct columns *entry;
+ struct odbc_obj *obj;
+ char columnname[80];
+ char connection[40];
+ char table[40];
+ int lenconnection, lentable;
+ SQLLEN sqlptr;
+ int res = 0;
+ SQLHSTMT stmt = NULL;
+ struct ast_flags config_flags = { 0 }; /* Part of our config comes from the database */
+
+ cfg = ast_config_load(CONFIG, config_flags);
+ if (!cfg) {
+ ast_log(LOG_WARNING, "Unable to load " CONFIG ". No adaptive ODBC CDRs.\n");
+ return -1;
+ }
+
+ for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
+ var = ast_variable_browse(cfg, catg);
+ if (!var)
+ continue;
+
+ if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "connection"))) {
+ ast_log(LOG_WARNING, "No connection parameter found in '%s'. Skipping.\n", catg);
+ continue;
+ }
+ ast_copy_string(connection, tmp, sizeof(connection));
+ lenconnection = strlen(connection);
+
+ /* When loading, we want to be sure we can connect. */
+ obj = ast_odbc_request_obj(connection, 1);
+ if (!obj) {
+ ast_log(LOG_WARNING, "No such connection '%s' in the '%s' section of " CONFIG ". Check res_odbc.conf.\n", connection, catg);
+ continue;
+ }
+
+ if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "table"))) {
+ ast_log(LOG_NOTICE, "No table name found. Assuming 'cdr'.\n");
+ tmp = "cdr";
+ }
+ ast_copy_string(table, tmp, sizeof(table));
+ lentable = strlen(table);
+
+ res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", connection);
+ ast_odbc_release_obj(obj);
+ continue;
+ }
+
+ res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)table, SQL_NTS, (unsigned char *)"%", SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'. Skipping.\n", connection);
+ ast_odbc_release_obj(obj);
+ continue;
+ }
+
+ tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + lenconnection + 1 + lentable + 1);
+ if (!tableptr) {
+ ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", table, connection);
+ ast_odbc_release_obj(obj);
+ res = -1;
+ break;
+ }
+
+ tableptr->connection = (char *)tableptr + sizeof(*tableptr);
+ tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
+ ast_copy_string(tableptr->connection, connection, lenconnection + 1);
+ ast_copy_string(tableptr->table, table, lentable + 1);
+
+ ast_verb(3, "Found adaptive CDR table %s@%s.\n", tableptr->table, tableptr->connection);
+
+ /* Check for filters first */
+ for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
+ if (strncmp(var->name, "filter", 6) == 0) {
+ char *cdrvar = ast_strdupa(var->name + 6);
+ cdrvar = ast_strip(cdrvar);
+ ast_verb(3, "Found filter %s for cdr variable %s in %s@%s\n", var->value, cdrvar, tableptr->table, tableptr->connection);
+
+ entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(cdrvar) + 1 + strlen(var->value) + 1);
+ if (!entry) {
+ ast_log(LOG_ERROR, "Out of memory creating filter entry for CDR variable '%s' in table '%s' on connection '%s'\n", cdrvar, table, connection);
+ res = -1;
+ break;
+ }
+
+ /* NULL column entry means this isn't a column in the database */
+ entry->name = NULL;
+ entry->cdrname = (char *)entry + sizeof(*entry);
+ entry->filtervalue = (char *)entry + sizeof(*entry) + strlen(cdrvar) + 1;
+ strcpy(entry->cdrname, cdrvar);
+ strcpy(entry->filtervalue, var->value);
+
+ AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
+ }
+ }
+
+ while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
+ char *cdrvar = "";
+
+ SQLGetData(stmt, 4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
+
+ /* Is there an alias for this column? */
+
+ /* NOTE: This seems like a non-optimal parse method, but I'm going
+ * for user configuration readability, rather than fast parsing. We
+ * really don't parse this file all that often, anyway.
+ */
+ for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
+ if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, columnname) == 0) {
+ char *tmp = ast_strdupa(var->name + 5);
+ cdrvar = ast_strip(tmp);
+ ast_verb(3, "Found alias %s for column %s in %s@%s\n", cdrvar, columnname, tableptr->table, tableptr->connection);
+ break;
+ }
+ }
+
+ entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1 + strlen(cdrvar) + 1);
+ if (!entry) {
+ ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
+ res = -1;
+ break;
+ }
+ entry->name = (char *)entry + sizeof(*entry);
+ strcpy(entry->name, columnname);
+
+ if (!ast_strlen_zero(cdrvar)) {
+ entry->cdrname = entry->name + strlen(columnname) + 1;
+ strcpy(entry->cdrname, cdrvar);
+ } else /* Point to same place as the column name */
+ entry->cdrname = (char *)entry + sizeof(*entry);
+
+ SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
+ SQLGetData(stmt, 7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
+ SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
+ SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
+ SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
+ SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
+
+ /* Specification states that the octenlen should be the maximum number of bytes
+ * returned in a char or binary column, but it seems that some drivers just set
+ * it to NULL. (Bad Postgres! No biscuit!) */
+ if (entry->octetlen == 0)
+ entry->octetlen = entry->size;
+
+ ast_verb(10, "Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, (long) entry->size, (long) entry->octetlen, entry->decimals, entry->radix);
+ /* Insert column info into column list */
+ AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
+ res = 0;
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+
+ if (AST_LIST_FIRST(&(tableptr->columns)))
+ AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
+ else
+ ast_free(tableptr);
+ }
+ return res;
+}
+
+static int free_config(void)
+{
+ struct tables *table;
+ struct columns *entry;
+ while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
+ while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
+ ast_free(entry);
+ }
+ ast_free(table);
+ }
+ return 0;
+}
+
+static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
+{
+ int res, i;
+ char *sql = data;
+ SQLHSTMT stmt;
+ SQLINTEGER nativeerror = 0, numfields = 0;
+ SQLSMALLINT diagbytes = 0;
+ unsigned char state[10], diagnostic[256];
+
+ res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
+ return NULL;
+ }
+
+ res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
+ SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
+ for (i = 0; i < numfields; i++) {
+ SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
+ ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
+ if (i > 10) {
+ ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
+ break;
+ }
+ }
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+
+ return stmt;
+}
+
+#define LENGTHEN_BUF1(size) \
+ do { \
+ /* Lengthen buffer, if necessary */ \
+ if ((newsize = lensql + (size) + 3) > sizesql) { \
+ if ((tmp = ast_realloc(sql, (newsize / 512 + 1) * 512))) { \
+ sql = tmp; \
+ sizesql = (newsize / 512 + 1) * 512; \
+ } else { \
+ ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
+ ast_free(sql); \
+ ast_free(sql2); \
+ AST_RWLIST_UNLOCK(&odbc_tables); \
+ return -1; \
+ } \
+ } \
+ } while (0)
+
+#define LENGTHEN_BUF2(size) \
+ do { \
+ if ((newsize = lensql2 + (size) + 3) > sizesql2) { \
+ if ((tmp = ast_realloc(sql2, (newsize / 512 + 1) * 512))) { \
+ sql2 = tmp; \
+ sizesql2 = (newsize / 512 + 1) * 512; \
+ } else { \
+ ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
+ ast_free(sql); \
+ ast_free(sql2); \
+ AST_RWLIST_UNLOCK(&odbc_tables); \
+ return -1; \
+ } \
+ } \
+ } while (0)
+
+static int odbc_log(struct ast_cdr *cdr)
+{
+ struct tables *tableptr;
+ struct columns *entry;
+ struct odbc_obj *obj;
+ int lensql, lensql2, sizesql = maxsize, sizesql2 = maxsize2, newsize;
+ /* Allocated, so we can realloc() */
+ char *sql = ast_calloc(sizeof(char), sizesql), *sql2 = ast_calloc(sizeof(char), sizesql2), *tmp;
+ char colbuf[1024], *colptr;
+ SQLHSTMT stmt = NULL;
+ SQLLEN rows = 0;
+
+ if (!sql || !sql2) {
+ if (sql)
+ ast_free(sql);
+ if (sql2)
+ ast_free(sql2);
+ return -1;
+ }
+
+ if (AST_RWLIST_RDLOCK(&odbc_tables)) {
+ ast_log(LOG_ERROR, "Unable to lock table list. Insert CDR(s) failed.\n");
+ ast_free(sql);
+ ast_free(sql2);
+ return -1;
+ }
+
+ AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
+ lensql = snprintf(sql, sizesql, "INSERT INTO %s (", tableptr->table);
+ lensql2 = snprintf(sql2, sizesql2, " VALUES (");
+
+ /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
+ if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
+ ast_log(LOG_WARNING, "cdr_adaptive_odbc: Unable to retrieve database handle for '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table, sql);
+ continue;
+ }
+
+ AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
+ /* Check if we have a similarly named variable */
+ ast_cdr_getvar(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), 0,
+ (strcasecmp(entry->cdrname, "start") == 0 ||
+ strcasecmp(entry->cdrname, "answer") == 0 ||
+ strcasecmp(entry->cdrname, "end") == 0) ? 0 : 1);
+
+ if (colptr) {
+ /* Check first if the column filters this entry. Note that this
+ * is very specifically NOT ast_strlen_zero(), because the filter
+ * could legitimately specify that the field is blank, which is
+ * different from the field being unspecified (NULL). */
+ if (entry->filtervalue && strcasecmp(colptr, entry->filtervalue) != 0) {
+ ast_verb(4, "CDR column '%s' with value '%s' does not match filter of"
+ " '%s'. Cancelling this CDR.\n",
+ entry->cdrname, colptr, entry->filtervalue);
+ goto early_release;
+ }
+
+ /* Only a filter? */
+ if (ast_strlen_zero(entry->name))
+ continue;
+
+ LENGTHEN_BUF1(strlen(entry->name));
+
+ switch (entry->type) {
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ case SQL_LONGVARBINARY:
+ case SQL_GUID:
+ /* For these two field names, get the rendered form, instead of the raw
+ * form (but only when we're dealing with a character-based field).
+ */
+ if (strcasecmp(entry->name, "disposition") == 0)
+ ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
+ else if (strcasecmp(entry->name, "amaflags") == 0)
+ ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
+
+ /* Truncate too-long fields */
+ if (entry->type != SQL_GUID) {
+ if (strlen(colptr) > entry->octetlen)
+ colptr[entry->octetlen] = '\0';
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(strlen(colptr));
+
+ /* Encode value, with escaping */
+ strcpy(sql2 + lensql2, "'");
+ lensql2++;
+ for (tmp = colptr; *tmp; tmp++) {
+ if (*tmp == '\'') {
+ strcpy(sql2 + lensql2, "''");
+ lensql2 += 2;
+ } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
+ strcpy(sql2 + lensql2, "\\\\");
+ lensql2 += 2;
+ } else {
+ sql2[lensql2++] = *tmp;
+ sql2[lensql2] = '\0';
+ }
+ }
+ strcpy(sql2 + lensql2, "',");
+ lensql2 += 2;
+ break;
+ case SQL_TYPE_DATE:
+ {
+ int year = 0, month = 0, day = 0;
+ if (sscanf(colptr, "%d-%d-%d", &year, &month, &day) != 3 || year <= 0 ||
+ month <= 0 || month > 12 || day < 0 || day > 31 ||
+ ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
+ (month == 2 && year % 400 == 0 && day > 29) ||
+ (month == 2 && year % 100 == 0 && day > 28) ||
+ (month == 2 && year % 4 == 0 && day > 29) ||
+ (month == 2 && year % 4 != 0 && day > 28)) {
+ ast_log(LOG_WARNING, "CDR variable %s is not a valid date ('%s').\n", entry->name, colptr);
+ break;
+ }
+
+ if (year > 0 && year < 100)
+ year += 2000;
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(17);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "{ d '%04d-%02d-%02d' },", year, month, day);
+ }
+ break;
+ case SQL_TYPE_TIME:
+ {
+ int hour = 0, minute = 0, second = 0;
+ int count = sscanf(colptr, "%d:%d:%d", &hour, &minute, &second);
+
+ if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
+ ast_log(LOG_WARNING, "CDR variable %s is not a valid time ('%s').\n", entry->name, colptr);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(15);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "{ t '%02d:%02d:%02d' },", hour, minute, second);
+ }
+ break;
+ case SQL_TYPE_TIMESTAMP:
+ case SQL_TIMESTAMP:
+ {
+ int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
+ int count = sscanf(colptr, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);
+
+ if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
+ month <= 0 || month > 12 || day < 0 || day > 31 ||
+ ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
+ (month == 2 && year % 400 == 0 && day > 29) ||
+ (month == 2 && year % 100 == 0 && day > 28) ||
+ (month == 2 && year % 4 == 0 && day > 29) ||
+ (month == 2 && year % 4 != 0 && day > 28) ||
+ hour > 23 || minute > 59 || second > 59 || hour < 0 || minute < 0 || second < 0) {
+ ast_log(LOG_WARNING, "CDR variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
+ break;
+ }
+
+ if (year > 0 && year < 100)
+ year += 2000;
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(26);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "{ ts '%04d-%02d-%02d %02d:%02d:%02d' },", year, month, day, hour, minute, second);
+ }
+ break;
+ case SQL_INTEGER:
+ {
+ int integer = 0;
+ if (sscanf(colptr, "%d", &integer) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(12);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
+ }
+ break;
+ case SQL_BIGINT:
+ {
+ long long integer = 0;
+ if (sscanf(colptr, "%lld", &integer) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(24);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%lld,", integer);
+ }
+ break;
+ case SQL_SMALLINT:
+ {
+ short integer = 0;
+ if (sscanf(colptr, "%hd", &integer) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(6);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
+ }
+ break;
+ case SQL_TINYINT:
+ {
+ char integer = 0;
+ if (sscanf(colptr, "%hhd", &integer) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(4);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
+ }
+ break;
+ case SQL_BIT:
+ {
+ char integer = 0;
+ if (sscanf(colptr, "%hhd", &integer) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
+ break;
+ }
+ if (integer != 0)
+ integer = 1;
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(2);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
+ }
+ break;
+ case SQL_NUMERIC:
+ case SQL_DECIMAL:
+ {
+ double number = 0.0;
+ if (sscanf(colptr, "%lf", &number) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(entry->decimals);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%*.*lf,", entry->decimals, entry->radix, number);
+ }
+ break;
+ case SQL_FLOAT:
+ case SQL_REAL:
+ case SQL_DOUBLE:
+ {
+ double number = 0.0;
+ if (sscanf(colptr, "%lf", &number) != 1) {
+ ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
+ break;
+ }
+
+ lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
+ LENGTHEN_BUF2(entry->decimals);
+ lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%lf,", number);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Column type %d (field '%s:%s:%s') is unsupported at this time.\n", entry->type, tableptr->connection, tableptr->table, entry->name);
+ }
+ }
+ }
+
+ /* Concatenate the two constructed buffers */
+ LENGTHEN_BUF1(lensql2);
+ sql[lensql - 1] = ')';
+ sql2[lensql2 - 1] = ')';
+ strcat(sql + lensql, sql2);
+
+ ast_verb(11, "[%s]\n", sql);
+
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
+ if (stmt) {
+ SQLRowCount(stmt, &rows);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ }
+ if (rows == 0) {
+ ast_log(LOG_WARNING, "cdr_adaptive_odbc: Insert failed on '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table, sql);
+ }
+early_release:
+ ast_odbc_release_obj(obj);
+ }
+ AST_RWLIST_UNLOCK(&odbc_tables);
+
+ /* Next time, just allocate buffers that are that big to start with. */
+ if (sizesql > maxsize)
+ maxsize = sizesql;
+ if (sizesql2 > maxsize2)
+ maxsize2 = sizesql2;
+
+ ast_free(sql);
+ ast_free(sql2);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_cdr_unregister(name);
+ usleep(1);
+ if (AST_RWLIST_WRLOCK(&odbc_tables)) {
+ ast_cdr_register(name, ast_module_info->description, odbc_log);
+ ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
+ return -1;
+ }
+
+ free_config();
+ AST_RWLIST_UNLOCK(&odbc_tables);
+ return 0;
+}
+
+static int load_module(void)
+{
+ if (AST_RWLIST_WRLOCK(&odbc_tables)) {
+ ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
+ return 0;
+ }
+
+ load_config();
+ AST_RWLIST_UNLOCK(&odbc_tables);
+ ast_cdr_register(name, ast_module_info->description, odbc_log);
+ return 0;
+}
+
+static int reload(void)
+{
+ if (AST_RWLIST_WRLOCK(&odbc_tables)) {
+ ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
+ return -1;
+ }
+
+ free_config();
+ load_config();
+ AST_RWLIST_UNLOCK(&odbc_tables);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Adaptive ODBC CDR backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
+
diff --git a/trunk/cdr/cdr_csv.c b/trunk/cdr/cdr_csv.c
new file mode 100644
index 000000000..cca1e8714
--- /dev/null
+++ b/trunk/cdr/cdr_csv.c
@@ -0,0 +1,346 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Includes code and algorithms from the Zapata library.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Comma Separated Value CDR records.
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \arg See also \ref AstCDR
+ * \ingroup cdr_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+
+#include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+
+#define CSV_LOG_DIR "/cdr-csv"
+#define CSV_MASTER "/Master.csv"
+
+#define DATE_FORMAT "%Y-%m-%d %T"
+
+static int usegmtime = 0;
+static int loguniqueid = 0;
+static int loguserfield = 0;
+static char *config = "cdr.conf";
+
+/* #define CSV_LOGUNIQUEID 1 */
+/* #define CSV_LOGUSERFIELD 1 */
+
+/*----------------------------------------------------
+ The values are as follows:
+
+
+ "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
+ Detail records are configured on a channel basis, IAX and SIP are determined by user *
+ Zap is determined by channel in zaptel.conf
+ "source",
+ "destination",
+ "destination context",
+ "callerid",
+ "channel",
+ "destination channel", (if applicable)
+ "last application", Last application run on the channel
+ "last app argument", argument to the last channel
+ "start time",
+ "answer time",
+ "end time",
+ duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
+ "end time" minus "start time"
+ billable seconds, the duration that a call was up after other end answered which will be <= to duration
+ "end time" minus "answer time"
+ "disposition", ANSWERED, NO ANSWER, BUSY
+ "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
+ "uniqueid", unique call identifier
+ "userfield" user field set via SetCDRUserField
+----------------------------------------------------------*/
+
+static char *name = "csv";
+
+AST_MUTEX_DEFINE_STATIC(mf_lock);
+AST_MUTEX_DEFINE_STATIC(acf_lock);
+
+static int load_config(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ const char *tmp;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ usegmtime = 0;
+ loguniqueid = 0;
+ loguserfield = 0;
+
+ if (!(cfg = ast_config_load(config, config_flags))) {
+ ast_log(LOG_WARNING, "unable to load config: %s\n", config);
+ return 0;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ if (!(var = ast_variable_browse(cfg, "csv"))) {
+ ast_config_destroy(cfg);
+ return 0;
+ }
+
+ if ((tmp = ast_variable_retrieve(cfg, "csv", "usegmtime"))) {
+ usegmtime = ast_true(tmp);
+ if (usegmtime)
+ ast_debug(1, "logging time in GMT\n");
+ }
+
+ if ((tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid"))) {
+ loguniqueid = ast_true(tmp);
+ if (loguniqueid)
+ ast_debug(1, "logging CDR field UNIQUEID\n");
+ }
+
+ if ((tmp = ast_variable_retrieve(cfg, "csv", "loguserfield"))) {
+ loguserfield = ast_true(tmp);
+ if (loguserfield)
+ ast_debug(1, "logging CDR user-defined field\n");
+ }
+
+ ast_config_destroy(cfg);
+ return 1;
+}
+
+static int append_string(char *buf, char *s, size_t bufsize)
+{
+ int pos = strlen(buf), spos = 0, error = -1;
+
+ if (pos >= bufsize - 4)
+ return -1;
+
+ buf[pos++] = '\"';
+
+ while(pos < bufsize - 3) {
+ if (!s[spos]) {
+ error = 0;
+ break;
+ }
+ if (s[spos] == '\"')
+ buf[pos++] = '\"';
+ buf[pos++] = s[spos];
+ spos++;
+ }
+
+ buf[pos++] = '\"';
+ buf[pos++] = ',';
+ buf[pos++] = '\0';
+
+ return error;
+}
+
+static int append_int(char *buf, int s, size_t bufsize)
+{
+ char tmp[32];
+ int pos = strlen(buf);
+
+ snprintf(tmp, sizeof(tmp), "%d", s);
+
+ if (pos + strlen(tmp) > bufsize - 3)
+ return -1;
+
+ strncat(buf, tmp, bufsize - strlen(buf) - 1);
+ pos = strlen(buf);
+ buf[pos++] = ',';
+ buf[pos++] = '\0';
+
+ return 0;
+}
+
+static int append_date(char *buf, struct timeval tv, size_t bufsize)
+{
+ char tmp[80] = "";
+ struct ast_tm tm;
+
+ if (strlen(buf) > bufsize - 3)
+ return -1;
+
+ if (ast_tvzero(tv)) {
+ strncat(buf, ",", bufsize - strlen(buf) - 1);
+ return 0;
+ }
+
+ ast_localtime(&tv, &tm, usegmtime ? "GMT" : NULL);
+ ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
+
+ return append_string(buf, tmp, bufsize);
+}
+
+static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
+{
+
+ buf[0] = '\0';
+ /* Account code */
+ append_string(buf, cdr->accountcode, bufsize);
+ /* Source */
+ append_string(buf, cdr->src, bufsize);
+ /* Destination */
+ append_string(buf, cdr->dst, bufsize);
+ /* Destination context */
+ append_string(buf, cdr->dcontext, bufsize);
+ /* Caller*ID */
+ append_string(buf, cdr->clid, bufsize);
+ /* Channel */
+ append_string(buf, cdr->channel, bufsize);
+ /* Destination Channel */
+ append_string(buf, cdr->dstchannel, bufsize);
+ /* Last Application */
+ append_string(buf, cdr->lastapp, bufsize);
+ /* Last Data */
+ append_string(buf, cdr->lastdata, bufsize);
+ /* Start Time */
+ append_date(buf, cdr->start, bufsize);
+ /* Answer Time */
+ append_date(buf, cdr->answer, bufsize);
+ /* End Time */
+ append_date(buf, cdr->end, bufsize);
+ /* Duration */
+ append_int(buf, cdr->duration, bufsize);
+ /* Billable seconds */
+ append_int(buf, cdr->billsec, bufsize);
+ /* Disposition */
+ append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
+ /* AMA Flags */
+ append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
+ /* Unique ID */
+ if (loguniqueid)
+ append_string(buf, cdr->uniqueid, bufsize);
+ /* append the user field */
+ if(loguserfield)
+ append_string(buf, cdr->userfield,bufsize);
+ /* If we hit the end of our buffer, log an error */
+ if (strlen(buf) < bufsize - 5) {
+ /* Trim off trailing comma */
+ buf[strlen(buf) - 1] = '\0';
+ strncat(buf, "\n", bufsize - strlen(buf) - 1);
+ return 0;
+ }
+ return -1;
+}
+
+static int writefile(char *s, char *acc)
+{
+ char tmp[PATH_MAX];
+ FILE *f;
+
+ if (strchr(acc, '/') || (acc[0] == '.')) {
+ ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
+ return -1;
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
+
+ ast_mutex_lock(&acf_lock);
+ if (!(f = fopen(tmp, "a"))) {
+ ast_mutex_unlock(&acf_lock);
+ ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
+ return -1;
+ }
+ fputs(s, f);
+ fflush(f);
+ fclose(f);
+ ast_mutex_unlock(&acf_lock);
+
+ return 0;
+}
+
+
+static int csv_log(struct ast_cdr *cdr)
+{
+ FILE *mf = NULL;
+ /* Make sure we have a big enough buf */
+ char buf[1024];
+ char csvmaster[PATH_MAX];
+ snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
+#if 0
+ printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode);
+#endif
+ if (build_csv_record(buf, sizeof(buf), cdr)) {
+ ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
+ return 0;
+ }
+
+ /* because of the absolutely unconditional need for the
+ highest reliability possible in writing billing records,
+ we open write and close the log file each time */
+ ast_mutex_lock(&mf_lock);
+ if ((mf = fopen(csvmaster, "a"))) {
+ fputs(buf, mf);
+ fflush(mf); /* be particularly anal here */
+ fclose(mf);
+ mf = NULL;
+ ast_mutex_unlock(&mf_lock);
+ } else {
+ ast_mutex_unlock(&mf_lock);
+ ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
+ }
+
+ if (!ast_strlen_zero(cdr->accountcode)) {
+ if (writefile(buf, cdr->accountcode))
+ ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
+ }
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_cdr_unregister(name);
+ return 0;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ if(!load_config(0))
+ return AST_MODULE_LOAD_DECLINE;
+
+ if ((res = ast_cdr_register(name, ast_module_info->description, csv_log)))
+ ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
+
+ return res;
+}
+
+static int reload(void)
+{
+ load_config(1);
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/cdr/cdr_custom.c b/trunk/cdr/cdr_custom.c
new file mode 100644
index 000000000..fbfb8d527
--- /dev/null
+++ b/trunk/cdr/cdr_custom.c
@@ -0,0 +1,169 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Includes code and algorithms from the Zapata library.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Custom Comma Separated Value CDR records.
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \arg See also \ref AstCDR
+ *
+ * Logs in LOG_DIR/cdr_custom
+ * \ingroup cdr_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+
+#include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+
+#define CUSTOM_LOG_DIR "/cdr_custom"
+
+#define DATE_FORMAT "%Y-%m-%d %T"
+
+AST_MUTEX_DEFINE_STATIC(lock);
+
+static char *name = "cdr-custom";
+
+static FILE *mf = NULL;
+
+static char master[PATH_MAX];
+static char format[1024]="";
+
+static int load_config(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ int res = -1;
+
+ if ((cfg = ast_config_load("cdr_custom.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ strcpy(format, "");
+ strcpy(master, "");
+ ast_mutex_lock(&lock);
+ if (cfg) {
+ var = ast_variable_browse(cfg, "mappings");
+ while(var) {
+ if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
+ if (strlen(var->value) > (sizeof(format) - 1))
+ ast_log(LOG_WARNING, "Format string too long, will be truncated, at line %d\n", var->lineno);
+ ast_copy_string(format, var->value, sizeof(format) - 1);
+ strcat(format,"\n");
+ snprintf(master, sizeof(master),"%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name);
+ if (var->next) {
+ ast_log(LOG_NOTICE, "Sorry, only one mapping is supported at this time, mapping '%s' will be ignored at line %d.\n", var->next->name, var->next->lineno);
+ break;
+ }
+ } else
+ ast_log(LOG_NOTICE, "Mapping must have both filename and format at line %d\n", var->lineno);
+ var = var->next;
+ }
+ ast_config_destroy(cfg);
+ res = 0;
+ } else {
+ if (reload)
+ ast_log(LOG_WARNING, "Failed to reload configuration file.\n");
+ else
+ ast_log(LOG_WARNING, "Failed to load configuration file. Module not activated.\n");
+ }
+ ast_mutex_unlock(&lock);
+
+ return res;
+}
+
+
+
+static int custom_log(struct ast_cdr *cdr)
+{
+ /* Make sure we have a big enough buf */
+ char buf[2048];
+ struct ast_channel dummy;
+
+ /* Abort if no master file is specified */
+ if (ast_strlen_zero(master))
+ return 0;
+
+ /* Quite possibly the first use of a static struct ast_channel, we need it so the var funcs will work */
+ memset(&dummy, 0, sizeof(dummy));
+ dummy.cdr = cdr;
+ pbx_substitute_variables_helper(&dummy, format, buf, sizeof(buf) - 1);
+
+ /* because of the absolutely unconditional need for the
+ highest reliability possible in writing billing records,
+ we open write and close the log file each time */
+ mf = fopen(master, "a");
+ if (!mf) {
+ ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", master, strerror(errno));
+ }
+ if (mf) {
+ fputs(buf, mf);
+ fflush(mf); /* be particularly anal here */
+ fclose(mf);
+ mf = NULL;
+ }
+ return 0;
+}
+
+static int unload_module(void)
+{
+ if (mf)
+ fclose(mf);
+ ast_cdr_unregister(name);
+ return 0;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ if (!load_config(0)) {
+ res = ast_cdr_register(name, ast_module_info->description, custom_log);
+ if (res)
+ ast_log(LOG_ERROR, "Unable to register custom CDR handling\n");
+ if (mf)
+ fclose(mf);
+ return res;
+ } else
+ return AST_MODULE_LOAD_DECLINE;
+}
+
+static int reload(void)
+{
+ return load_config(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Customizable Comma Separated Values CDR Backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+
diff --git a/trunk/cdr/cdr_manager.c b/trunk/cdr/cdr_manager.c
new file mode 100644
index 000000000..8b2ab215d
--- /dev/null
+++ b/trunk/cdr/cdr_manager.c
@@ -0,0 +1,204 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2005
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Asterisk Call Manager CDR records.
+ *
+ * See also
+ * \arg \ref AstCDR
+ * \arg \ref AstAMI
+ * \arg \ref Config_ami
+ * \ingroup cdr_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+#include "asterisk/manager.h"
+#include "asterisk/config.h"
+#include "asterisk/pbx.h"
+
+#define DATE_FORMAT "%Y-%m-%d %T"
+#define CONF_FILE "cdr_manager.conf"
+#define CUSTOM_FIELDS_BUF_SIZE 1024
+
+static char *name = "cdr_manager";
+
+static int enablecdr = 0;
+struct ast_str *customfields;
+
+static int manager_log(struct ast_cdr *cdr);
+
+static int load_config(int reload)
+{
+ char *cat = NULL;
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ int newenablecdr = 0;
+
+ cfg = ast_config_load(CONF_FILE, config_flags);
+ if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ if (reload && customfields) {
+ ast_free(customfields);
+ }
+ customfields = NULL;
+
+ if (!cfg) {
+ /* Standard configuration */
+ ast_log(LOG_WARNING, "Failed to load configuration file. Module not activated.\n");
+ if (enablecdr)
+ ast_cdr_unregister(name);
+ enablecdr = 0;
+ return 0;
+ }
+
+ while ( (cat = ast_category_browse(cfg, cat)) ) {
+ if (!strcasecmp(cat, "general")) {
+ v = ast_variable_browse(cfg, cat);
+ while (v) {
+ if (!strcasecmp(v->name, "enabled"))
+ newenablecdr = ast_true(v->value);
+
+ v = v->next;
+ }
+ } else if (!strcasecmp(cat, "mappings")) {
+ customfields = ast_str_create(CUSTOM_FIELDS_BUF_SIZE);
+ v = ast_variable_browse(cfg, cat);
+ while (v) {
+ if (customfields && !ast_strlen_zero(v->name) && !ast_strlen_zero(v->value)) {
+ if( (customfields->used + strlen(v->value) + strlen(v->name) + 14) < customfields->len) {
+ ast_str_append(&customfields, -1, "%s: ${CDR(%s)}\r\n", v->value, v->name);
+ ast_log(LOG_NOTICE, "Added mapping %s: ${CDR(%s)}\n", v->value, v->name);
+ } else {
+ ast_log(LOG_WARNING, "No more buffer space to add other custom fields\n");
+ break;
+ }
+
+ }
+ v = v->next;
+ }
+ }
+ }
+
+ ast_config_destroy(cfg);
+
+ if (enablecdr && !newenablecdr)
+ ast_cdr_unregister(name);
+ else if (!enablecdr && newenablecdr)
+ ast_cdr_register(name, "Asterisk Manager Interface CDR Backend", manager_log);
+ enablecdr = newenablecdr;
+
+ return 1;
+}
+
+static int manager_log(struct ast_cdr *cdr)
+{
+ struct ast_tm timeresult;
+ char strStartTime[80] = "";
+ char strAnswerTime[80] = "";
+ char strEndTime[80] = "";
+ char buf[CUSTOM_FIELDS_BUF_SIZE];
+ struct ast_channel dummy;
+
+ if (!enablecdr)
+ return 0;
+
+ ast_localtime(&cdr->start, &timeresult, NULL);
+ ast_strftime(strStartTime, sizeof(strStartTime), DATE_FORMAT, &timeresult);
+
+ if (cdr->answer.tv_sec) {
+ ast_localtime(&cdr->answer, &timeresult, NULL);
+ ast_strftime(strAnswerTime, sizeof(strAnswerTime), DATE_FORMAT, &timeresult);
+ }
+
+ ast_localtime(&cdr->end, &timeresult, NULL);
+ ast_strftime(strEndTime, sizeof(strEndTime), DATE_FORMAT, &timeresult);
+
+ buf[0] = 0;
+ /* Custom fields handling */
+ if (customfields != NULL && customfields->used > 0) {
+ memset(&dummy, 0, sizeof(dummy));
+ dummy.cdr = cdr;
+ pbx_substitute_variables_helper(&dummy, customfields->str, buf, sizeof(buf) - 1);
+ }
+
+ manager_event(EVENT_FLAG_CDR, "Cdr",
+ "AccountCode: %s\r\n"
+ "Source: %s\r\n"
+ "Destination: %s\r\n"
+ "DestinationContext: %s\r\n"
+ "CallerID: %s\r\n"
+ "Channel: %s\r\n"
+ "DestinationChannel: %s\r\n"
+ "LastApplication: %s\r\n"
+ "LastData: %s\r\n"
+ "StartTime: %s\r\n"
+ "AnswerTime: %s\r\n"
+ "EndTime: %s\r\n"
+ "Duration: %ld\r\n"
+ "BillableSeconds: %ld\r\n"
+ "Disposition: %s\r\n"
+ "AMAFlags: %s\r\n"
+ "UniqueID: %s\r\n"
+ "UserField: %s\r\n"
+ "%s",
+ cdr->accountcode, cdr->src, cdr->dst, cdr->dcontext, cdr->clid, cdr->channel,
+ cdr->dstchannel, cdr->lastapp, cdr->lastdata, strStartTime, strAnswerTime, strEndTime,
+ cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition),
+ ast_cdr_flags2str(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_cdr_unregister(name);
+ if (customfields)
+ ast_free(customfields);
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ /* Configuration file */
+ if (!load_config(0))
+ return AST_MODULE_LOAD_DECLINE;
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload(void)
+{
+ return load_config(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk Manager Interface CDR Backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/cdr/cdr_odbc.c b/trunk/cdr/cdr_odbc.c
new file mode 100644
index 000000000..af02d393c
--- /dev/null
+++ b/trunk/cdr/cdr_odbc.c
@@ -0,0 +1,264 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2003-2005, Digium, Inc.
+ *
+ * Brian K. West <brian@bkw.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief ODBC CDR Backend
+ *
+ * \author Brian K. West <brian@bkw.org>
+ *
+ * See also:
+ * \arg http://www.unixodbc.org
+ * \arg \ref Config_cdr
+ * \ingroup cdr_drivers
+ */
+
+/*** MODULEINFO
+ <depend>unixodbc</depend>
+ <depend>ltdl</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/res_odbc.h"
+
+#define DATE_FORMAT "%Y-%m-%d %T"
+
+static char *name = "ODBC";
+static char *config_file = "cdr_odbc.conf";
+static char *dsn = NULL, *table = NULL;
+
+enum {
+ CONFIG_LOGUNIQUEID = 1 << 0,
+ CONFIG_USEGMTIME = 1 << 1,
+ CONFIG_DISPOSITIONSTRING = 1 << 2,
+};
+
+static struct ast_flags config = { 0 };
+
+static SQLHSTMT prepare_cb(struct odbc_obj *obj, void *data)
+{
+ struct ast_cdr *cdr = data;
+ SQLRETURN ODBC_res;
+ char sqlcmd[2048] = "", timestr[128];
+ struct ast_tm tm;
+ SQLHSTMT stmt;
+
+ ast_localtime(&cdr->start, &tm, ast_test_flag(&config, CONFIG_USEGMTIME) ? "GMT" : NULL);
+ ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
+
+ if (ast_test_flag(&config, CONFIG_LOGUNIQUEID)) {
+ snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s "
+ "(calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp,"
+ "lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) "
+ "VALUES ({ts '%s'},?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", table, timestr);
+ } else {
+ snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s "
+ "(calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp,lastdata,"
+ "duration,billsec,disposition,amaflags,accountcode) "
+ "VALUES ({ts '%s'},?,?,?,?,?,?,?,?,?,?,?,?,?)", table, timestr);
+ }
+
+ ODBC_res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
+
+ if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
+ ast_verb(11, "cdr_odbc: Failure in AllocStatement %d\n", ODBC_res);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+
+ ODBC_res = SQLPrepare(stmt, (unsigned char *)sqlcmd, SQL_NTS);
+
+ if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
+ ast_verb(11, "cdr_odbc: Error in PREPARE %d\n", ODBC_res);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+
+ SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->clid), 0, cdr->clid, 0, NULL);
+ SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->src), 0, cdr->src, 0, NULL);
+ SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dst), 0, cdr->dst, 0, NULL);
+ SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dcontext), 0, cdr->dcontext, 0, NULL);
+ SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->channel), 0, cdr->channel, 0, NULL);
+ SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dstchannel), 0, cdr->dstchannel, 0, NULL);
+ SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastapp), 0, cdr->lastapp, 0, NULL);
+ SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastdata), 0, cdr->lastdata, 0, NULL);
+ SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->duration, 0, NULL);
+ SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL);
+ if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING))
+ SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL);
+ else
+ SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->disposition, 0, NULL);
+ SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->amaflags, 0, NULL);
+ SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->accountcode), 0, cdr->accountcode, 0, NULL);
+
+ if (ast_test_flag(&config, CONFIG_LOGUNIQUEID)) {
+ SQLBindParameter(stmt, 14, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->uniqueid), 0, cdr->uniqueid, 0, NULL);
+ SQLBindParameter(stmt, 15, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->userfield), 0, cdr->userfield, 0, NULL);
+ }
+
+ return stmt;
+}
+
+
+static int odbc_log(struct ast_cdr *cdr)
+{
+ struct odbc_obj *obj = ast_odbc_request_obj(dsn, 0);
+ SQLHSTMT stmt;
+
+ if (!obj) {
+ ast_log(LOG_ERROR, "Unable to retrieve database handle. CDR failed.\n");
+ return -1;
+ }
+
+ stmt = ast_odbc_prepare_and_execute(obj, prepare_cb, cdr);
+ if (stmt) {
+ SQLLEN rows = 0;
+
+ SQLRowCount(stmt, &rows);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+ if (rows == 0)
+ ast_log(LOG_WARNING, "CDR successfully ran, but inserted 0 rows?\n");
+ } else
+ ast_log(LOG_ERROR, "CDR prepare or execute failed\n");
+ ast_odbc_release_obj(obj);
+ return 0;
+}
+
+static int odbc_load_module(int reload)
+{
+ int res = 0;
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ const char *tmp;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ do {
+ cfg = ast_config_load(config_file, config_flags);
+ if (!cfg) {
+ ast_log(LOG_WARNING, "cdr_odbc: Unable to load config for ODBC CDR's: %s\n", config_file);
+ res = AST_MODULE_LOAD_DECLINE;
+ break;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ break;
+
+ var = ast_variable_browse(cfg, "global");
+ if (!var) {
+ /* nothing configured */
+ break;
+ }
+
+ if ((tmp = ast_variable_retrieve(cfg, "global", "dsn")) == NULL) {
+ ast_log(LOG_WARNING, "cdr_odbc: dsn not specified. Assuming asteriskdb\n");
+ tmp = "asteriskdb";
+ }
+ if (dsn)
+ ast_free(dsn);
+ dsn = ast_strdup(tmp);
+ if (dsn == NULL) {
+ res = -1;
+ break;
+ }
+
+ if (((tmp = ast_variable_retrieve(cfg, "global", "dispositionstring"))) && ast_true(tmp))
+ ast_set_flag(&config, CONFIG_DISPOSITIONSTRING);
+ else
+ ast_clear_flag(&config, CONFIG_DISPOSITIONSTRING);
+
+ if (((tmp = ast_variable_retrieve(cfg, "global", "loguniqueid"))) && ast_true(tmp)) {
+ ast_set_flag(&config, CONFIG_LOGUNIQUEID);
+ ast_debug(1, "cdr_odbc: Logging uniqueid\n");
+ } else {
+ ast_clear_flag(&config, CONFIG_LOGUNIQUEID);
+ ast_debug(1, "cdr_odbc: Not logging uniqueid\n");
+ }
+
+ if (((tmp = ast_variable_retrieve(cfg, "global", "usegmtime"))) && ast_true(tmp)) {
+ ast_set_flag(&config, CONFIG_USEGMTIME);
+ ast_debug(1, "cdr_odbc: Logging in GMT\n");
+ } else {
+ ast_clear_flag(&config, CONFIG_USEGMTIME);
+ ast_debug(1, "cdr_odbc: Logging in local time\n");
+ }
+
+ if ((tmp = ast_variable_retrieve(cfg, "global", "table")) == NULL) {
+ ast_log(LOG_WARNING, "cdr_odbc: table not specified. Assuming cdr\n");
+ tmp = "cdr";
+ }
+ if (table)
+ ast_free(table);
+ table = ast_strdup(tmp);
+ if (table == NULL) {
+ res = -1;
+ break;
+ }
+
+ ast_verb(3, "cdr_odbc: dsn is %s\n", dsn);
+ ast_verb(3, "cdr_odbc: table is %s\n", table);
+
+ res = ast_cdr_register(name, ast_module_info->description, odbc_log);
+ if (res) {
+ ast_log(LOG_ERROR, "cdr_odbc: Unable to register ODBC CDR handling\n");
+ }
+ } while (0);
+
+ if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED)
+ ast_config_destroy(cfg);
+ return res;
+}
+
+static int load_module(void)
+{
+ return odbc_load_module(0);
+}
+
+static int unload_module(void)
+{
+ ast_cdr_unregister(name);
+
+ if (dsn) {
+ ast_verb(11, "cdr_odbc: free dsn\n");
+ ast_free(dsn);
+ }
+ if (table) {
+ ast_verb(11, "cdr_odbc: free table\n");
+ ast_free(table);
+ }
+
+ return 0;
+}
+
+static int reload(void)
+{
+ return odbc_load_module(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC CDR Backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/cdr/cdr_pgsql.c b/trunk/cdr/cdr_pgsql.c
new file mode 100644
index 000000000..eea4416a5
--- /dev/null
+++ b/trunk/cdr/cdr_pgsql.c
@@ -0,0 +1,335 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2003 - 2006
+ *
+ * Matthew D. Hardeman <mhardemn@papersoft.com>
+ * Adapted from the MySQL CDR logger originally by James Sharp
+ *
+ * Modified September 2003
+ * Matthew D. Hardeman <mhardemn@papersoft.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief PostgreSQL CDR logger
+ *
+ * \author Matthew D. Hardeman <mhardemn@papersoft.com>
+ * \extref PostgreSQL http://www.postgresql.org/
+ *
+ * See also
+ * \arg \ref Config_cdr
+ * \arg http://www.postgresql.org/
+ * \ingroup cdr_drivers
+ */
+
+/*** MODULEINFO
+ <depend>pgsql</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+
+#include <libpq-fe.h>
+
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+
+#define DATE_FORMAT "%Y-%m-%d %T"
+
+static char *name = "pgsql";
+static char *config = "cdr_pgsql.conf";
+static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL;
+static int connected = 0;
+
+AST_MUTEX_DEFINE_STATIC(pgsql_lock);
+
+static PGconn *conn = NULL;
+
+static int pgsql_log(struct ast_cdr *cdr)
+{
+ struct ast_tm tm;
+ char sqlcmd[2048] = "", timestr[128];
+ char *pgerror;
+ PGresult *result;
+
+ ast_mutex_lock(&pgsql_lock);
+
+ ast_localtime(&cdr->start, &tm, NULL);
+ ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
+
+ if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
+ conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
+ if (PQstatus(conn) != CONNECTION_BAD) {
+ connected = 1;
+ } else {
+ pgerror = PQerrorMessage(conn);
+ ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
+ ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
+ PQfinish(conn);
+ conn = NULL;
+ }
+ }
+
+ if (connected) {
+ char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL;
+ char *src=NULL, *dst=NULL, *uniqueid=NULL, *userfield=NULL;
+ int pgerr;
+
+ /* Maximum space needed would be if all characters needed to be escaped, plus a trailing NULL */
+ if ((clid = alloca(strlen(cdr->clid) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, clid, cdr->clid, strlen(cdr->clid), &pgerr);
+ if ((dcontext = alloca(strlen(cdr->dcontext) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, dcontext, cdr->dcontext, strlen(cdr->dcontext), &pgerr);
+ if ((channel = alloca(strlen(cdr->channel) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, channel, cdr->channel, strlen(cdr->channel), &pgerr);
+ if ((dstchannel = alloca(strlen(cdr->dstchannel) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, dstchannel, cdr->dstchannel, strlen(cdr->dstchannel), &pgerr);
+ if ((lastapp = alloca(strlen(cdr->lastapp) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, lastapp, cdr->lastapp, strlen(cdr->lastapp), &pgerr);
+ if ((lastdata = alloca(strlen(cdr->lastdata) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, lastdata, cdr->lastdata, strlen(cdr->lastdata), &pgerr);
+ if ((uniqueid = alloca(strlen(cdr->uniqueid) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, uniqueid, cdr->uniqueid, strlen(cdr->uniqueid), &pgerr);
+ if ((userfield = alloca(strlen(cdr->userfield) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, userfield, cdr->userfield, strlen(cdr->userfield), &pgerr);
+ if ((src = alloca(strlen(cdr->src) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, src, cdr->src, strlen(cdr->src), &pgerr);
+ if ((dst = alloca(strlen(cdr->dst) * 2 + 1)) != NULL)
+ PQescapeStringConn(conn, dst, cdr->dst, strlen(cdr->dst), &pgerr);
+
+ /* Check for all alloca failures above at once */
+ if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid) || (!userfield) || (!src) || (!dst)) {
+ ast_log(LOG_ERROR, "cdr_pgsql: Out of memory error (insert fails)\n");
+ ast_mutex_unlock(&pgsql_lock);
+ return -1;
+ }
+
+ ast_debug(2, "cdr_pgsql: inserting a CDR record.\n");
+
+ snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s (calldate,clid,src,dst,dcontext,channel,dstchannel,"
+ "lastapp,lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) VALUES"
+ " ('%s','%s','%s','%s','%s', '%s','%s','%s','%s',%ld,%ld,'%s',%ld,'%s','%s','%s')",
+ table, timestr, clid, src, dst, dcontext, channel, dstchannel, lastapp, lastdata,
+ cdr->duration,cdr->billsec,ast_cdr_disp2str(cdr->disposition),cdr->amaflags, cdr->accountcode, uniqueid, userfield);
+
+ ast_debug(3, "cdr_pgsql: SQL command executed: %s\n",sqlcmd);
+
+ /* Test to be sure we're still connected... */
+ /* If we're connected, and connection is working, good. */
+ /* Otherwise, attempt reconnect. If it fails... sorry... */
+ if (PQstatus(conn) == CONNECTION_OK) {
+ connected = 1;
+ } else {
+ ast_log(LOG_ERROR, "cdr_pgsql: Connection was lost... attempting to reconnect.\n");
+ PQreset(conn);
+ if (PQstatus(conn) == CONNECTION_OK) {
+ ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
+ connected = 1;
+ } else {
+ pgerror = PQerrorMessage(conn);
+ ast_log(LOG_ERROR, "cdr_pgsql: Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
+ ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
+ PQfinish(conn);
+ conn = NULL;
+ connected = 0;
+ ast_mutex_unlock(&pgsql_lock);
+ return -1;
+ }
+ }
+ result = PQexec(conn, sqlcmd);
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ pgerror = PQresultErrorMessage(result);
+ ast_log(LOG_ERROR,"cdr_pgsql: Failed to insert call detail record into database!\n");
+ ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
+ ast_log(LOG_ERROR,"cdr_pgsql: Connection may have been lost... attempting to reconnect.\n");
+ PQreset(conn);
+ if (PQstatus(conn) == CONNECTION_OK) {
+ ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
+ connected = 1;
+ PQclear(result);
+ result = PQexec(conn, sqlcmd);
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ pgerror = PQresultErrorMessage(result);
+ ast_log(LOG_ERROR,"cdr_pgsql: HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
+ ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
+ }
+ }
+ ast_mutex_unlock(&pgsql_lock);
+ PQclear(result);
+ return -1;
+ }
+ PQclear(result);
+ }
+ ast_mutex_unlock(&pgsql_lock);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ PQfinish(conn);
+ if (pghostname)
+ ast_free(pghostname);
+ if (pgdbname)
+ ast_free(pgdbname);
+ if (pgdbuser)
+ ast_free(pgdbuser);
+ if (pgpassword)
+ ast_free(pgpassword);
+ if (pgdbport)
+ ast_free(pgdbport);
+ if (table)
+ ast_free(table);
+ ast_cdr_unregister(name);
+ return 0;
+}
+
+static int config_module(int reload)
+{
+ struct ast_variable *var;
+ char *pgerror;
+ const char *tmp;
+ struct ast_config *cfg;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ if ((cfg = ast_config_load(config, config_flags)) == NULL) {
+ ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
+ return -1;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ if (!(var = ast_variable_browse(cfg, "global"))) {
+ ast_config_destroy(cfg);
+ return 0;
+ }
+
+ if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
+ ast_log(LOG_WARNING, "PostgreSQL server hostname not specified. Assuming unix socket connection\n");
+ tmp = ""; /* connect via UNIX-socket by default */
+ }
+
+ if (pghostname)
+ ast_free(pghostname);
+ if (!(pghostname = ast_strdup(tmp))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+
+ if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
+ ast_log(LOG_WARNING,"PostgreSQL database not specified. Assuming asterisk\n");
+ tmp = "asteriskcdrdb";
+ }
+
+ if (pgdbname)
+ ast_free(pgdbname);
+ if (!(pgdbname = ast_strdup(tmp))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+
+ if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
+ ast_log(LOG_WARNING,"PostgreSQL database user not specified. Assuming asterisk\n");
+ tmp = "asterisk";
+ }
+
+ if (pgdbuser)
+ ast_free(pgdbuser);
+ if (!(pgdbuser = ast_strdup(tmp))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+
+ if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
+ ast_log(LOG_WARNING,"PostgreSQL database password not specified. Assuming blank\n");
+ tmp = "";
+ }
+
+ if (pgpassword)
+ ast_free(pgpassword);
+ if (!(pgpassword = ast_strdup(tmp))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+
+ if (!(tmp = ast_variable_retrieve(cfg,"global","port"))) {
+ ast_log(LOG_WARNING,"PostgreSQL database port not specified. Using default 5432.\n");
+ tmp = "5432";
+ }
+
+ if (pgdbport)
+ ast_free(pgdbport);
+ if (!(pgdbport = ast_strdup(tmp))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+
+ if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
+ ast_log(LOG_WARNING,"CDR table not specified. Assuming cdr\n");
+ tmp = "cdr";
+ }
+
+ if (table)
+ ast_free(table);
+ if (!(table = ast_strdup(tmp))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+
+ if (option_debug) {
+ if (ast_strlen_zero(pghostname))
+ ast_debug(1, "cdr_pgsql: using default unix socket\n");
+ else
+ ast_debug(1, "cdr_pgsql: got hostname of %s\n", pghostname);
+ ast_debug(1, "cdr_pgsql: got port of %s\n", pgdbport);
+ ast_debug(1, "cdr_pgsql: got user of %s\n", pgdbuser);
+ ast_debug(1, "cdr_pgsql: got dbname of %s\n", pgdbname);
+ ast_debug(1, "cdr_pgsql: got password of %s\n", pgpassword);
+ ast_debug(1, "cdr_pgsql: got sql table name of %s\n", table);
+ }
+
+ conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
+ if (PQstatus(conn) != CONNECTION_BAD) {
+ ast_debug(1, "Successfully connected to PostgreSQL database.\n");
+ connected = 1;
+ } else {
+ pgerror = PQerrorMessage(conn);
+ ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
+ ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
+ connected = 0;
+ }
+
+ ast_config_destroy(cfg);
+
+ return ast_cdr_register(name, ast_module_info->description, pgsql_log);
+}
+
+static int load_module(void)
+{
+ return config_module(0) ? AST_MODULE_LOAD_DECLINE : 0;
+}
+
+static int reload(void)
+{
+ return config_module(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "PostgreSQL CDR Backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/cdr/cdr_radius.c b/trunk/cdr/cdr_radius.c
new file mode 100644
index 000000000..3f0b2c9f2
--- /dev/null
+++ b/trunk/cdr/cdr_radius.c
@@ -0,0 +1,260 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief RADIUS CDR Support
+ * \author Philippe Sultan
+ * \extref The Radius Client Library - http://developer.berlios.de/projects/radiusclient-ng/
+ *
+ * \arg See also \ref AstCDR
+ * \ingroup cdr_drivers
+ */
+
+/*** MODULEINFO
+ <depend>radius</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Rev$")
+
+#include <time.h>
+#include <radiusclient-ng.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+
+/*! ISO 8601 standard format */
+#define DATE_FORMAT "%Y-%m-%d %T %z"
+
+#define VENDOR_CODE 22736
+
+enum {
+ PW_AST_ACCT_CODE = 101,
+ PW_AST_SRC = 102,
+ PW_AST_DST = 103,
+ PW_AST_DST_CTX = 104,
+ PW_AST_CLID = 105,
+ PW_AST_CHAN = 106,
+ PW_AST_DST_CHAN = 107,
+ PW_AST_LAST_APP = 108,
+ PW_AST_LAST_DATA = 109,
+ PW_AST_START_TIME = 110,
+ PW_AST_ANSWER_TIME = 111,
+ PW_AST_END_TIME = 112,
+ PW_AST_DURATION = 113,
+ PW_AST_BILL_SEC = 114,
+ PW_AST_DISPOSITION = 115,
+ PW_AST_AMA_FLAGS = 116,
+ PW_AST_UNIQUE_ID = 117,
+ PW_AST_USER_FIELD = 118
+};
+
+enum {
+ /*! Log dates and times in UTC */
+ RADIUS_FLAG_USEGMTIME = (1 << 0),
+ /*! Log Unique ID */
+ RADIUS_FLAG_LOGUNIQUEID = (1 << 1),
+ /*! Log User Field */
+ RADIUS_FLAG_LOGUSERFIELD = (1 << 2)
+};
+
+static char *desc = "RADIUS CDR Backend";
+static char *name = "radius";
+static char *cdr_config = "cdr.conf";
+
+static char radiuscfg[PATH_MAX] = "/etc/radiusclient-ng/radiusclient.conf";
+
+static struct ast_flags global_flags = { RADIUS_FLAG_USEGMTIME | RADIUS_FLAG_LOGUNIQUEID | RADIUS_FLAG_LOGUSERFIELD };
+
+static rc_handle *rh = NULL;
+
+static int build_radius_record(VALUE_PAIR **send, struct ast_cdr *cdr)
+{
+ int recordtype = PW_STATUS_STOP;
+ struct ast_tm tm;
+ char timestr[128];
+ char *tmp;
+
+ if (!rc_avpair_add(rh, send, PW_ACCT_STATUS_TYPE, &recordtype, 0, 0))
+ return -1;
+
+ /* Account code */
+ if (!rc_avpair_add(rh, send, PW_AST_ACCT_CODE, &cdr->accountcode, strlen(cdr->accountcode), VENDOR_CODE))
+ return -1;
+
+ /* Source */
+ if (!rc_avpair_add(rh, send, PW_AST_SRC, &cdr->src, strlen(cdr->src), VENDOR_CODE))
+ return -1;
+
+ /* Destination */
+ if (!rc_avpair_add(rh, send, PW_AST_DST, &cdr->dst, strlen(cdr->dst), VENDOR_CODE))
+ return -1;
+
+ /* Destination context */
+ if (!rc_avpair_add(rh, send, PW_AST_DST_CTX, &cdr->dcontext, strlen(cdr->dcontext), VENDOR_CODE))
+ return -1;
+
+ /* Caller ID */
+ if (!rc_avpair_add(rh, send, PW_AST_CLID, &cdr->clid, strlen(cdr->clid), VENDOR_CODE))
+ return -1;
+
+ /* Channel */
+ if (!rc_avpair_add(rh, send, PW_AST_CHAN, &cdr->channel, strlen(cdr->channel), VENDOR_CODE))
+ return -1;
+
+ /* Destination Channel */
+ if (!rc_avpair_add(rh, send, PW_AST_DST_CHAN, &cdr->dstchannel, strlen(cdr->dstchannel), VENDOR_CODE))
+ return -1;
+
+ /* Last Application */
+ if (!rc_avpair_add(rh, send, PW_AST_LAST_APP, &cdr->lastapp, strlen(cdr->lastapp), VENDOR_CODE))
+ return -1;
+
+ /* Last Data */
+ if (!rc_avpair_add(rh, send, PW_AST_LAST_DATA, &cdr->lastdata, strlen(cdr->lastdata), VENDOR_CODE))
+ return -1;
+
+
+ /* Start Time */
+ ast_strftime(timestr, sizeof(timestr), DATE_FORMAT,
+ ast_localtime(&cdr->start, &tm,
+ ast_test_flag(&global_flags, RADIUS_FLAG_USEGMTIME) ? "GMT" : NULL));
+ if (!rc_avpair_add(rh, send, PW_AST_START_TIME, timestr, strlen(timestr), VENDOR_CODE))
+ return -1;
+
+ /* Answer Time */
+ ast_strftime(timestr, sizeof(timestr), DATE_FORMAT,
+ ast_localtime(&cdr->answer, &tm,
+ ast_test_flag(&global_flags, RADIUS_FLAG_USEGMTIME) ? "GMT" : NULL));
+ if (!rc_avpair_add(rh, send, PW_AST_ANSWER_TIME, timestr, strlen(timestr), VENDOR_CODE))
+ return -1;
+
+ /* End Time */
+ ast_strftime(timestr, sizeof(timestr), DATE_FORMAT,
+ ast_localtime(&cdr->end, &tm,
+ ast_test_flag(&global_flags, RADIUS_FLAG_USEGMTIME) ? "GMT" : NULL));
+ if (!rc_avpair_add(rh, send, PW_AST_END_TIME, timestr, strlen(timestr), VENDOR_CODE))
+ return -1;
+
+ /* Duration */
+ if (!rc_avpair_add(rh, send, PW_AST_DURATION, &cdr->duration, 0, VENDOR_CODE))
+ return -1;
+
+ /* Billable seconds */
+ if (!rc_avpair_add(rh, send, PW_AST_BILL_SEC, &cdr->billsec, 0, VENDOR_CODE))
+ return -1;
+
+ /* Disposition */
+ tmp = ast_cdr_disp2str(cdr->disposition);
+ if (!rc_avpair_add(rh, send, PW_AST_DISPOSITION, tmp, strlen(tmp), VENDOR_CODE))
+ return -1;
+
+ /* AMA Flags */
+ tmp = ast_cdr_flags2str(cdr->amaflags);
+ if (!rc_avpair_add(rh, send, PW_AST_AMA_FLAGS, tmp, strlen(tmp), VENDOR_CODE))
+ return -1;
+
+ if (ast_test_flag(&global_flags, RADIUS_FLAG_LOGUNIQUEID)) {
+ /* Unique ID */
+ if (!rc_avpair_add(rh, send, PW_AST_UNIQUE_ID, &cdr->uniqueid, strlen(cdr->uniqueid), VENDOR_CODE))
+ return -1;
+ }
+
+ if (ast_test_flag(&global_flags, RADIUS_FLAG_LOGUSERFIELD)) {
+ /* append the user field */
+ if (!rc_avpair_add(rh, send, PW_AST_USER_FIELD, &cdr->userfield, strlen(cdr->userfield), VENDOR_CODE))
+ return -1;
+ }
+
+ /* Setting Acct-Session-Id & User-Name attributes for proper generation
+ of Acct-Unique-Session-Id on server side */
+ /* Channel */
+ if (!rc_avpair_add(rh, send, PW_USER_NAME, &cdr->channel, strlen(cdr->channel), 0))
+ return -1;
+
+ /* Unique ID */
+ if (!rc_avpair_add(rh, send, PW_ACCT_SESSION_ID, &cdr->uniqueid, strlen(cdr->uniqueid), 0))
+ return -1;
+
+ return 0;
+}
+
+static int radius_log(struct ast_cdr *cdr)
+{
+ int result = ERROR_RC;
+ VALUE_PAIR *send = NULL;
+
+ if (build_radius_record(&send, cdr)) {
+ ast_debug(1, "Unable to create RADIUS record. CDR not recorded!\n");
+ return result;
+ }
+
+ result = rc_acct(rh, 0, send);
+ if (result != OK_RC)
+ ast_log(LOG_ERROR, "Failed to record Radius CDR record!\n");
+
+ return result;
+}
+
+static int unload_module(void)
+{
+ ast_cdr_unregister(name);
+ return 0;
+}
+
+static int load_module(void)
+{
+ struct ast_config *cfg;
+ struct ast_flags config_flags = { 0 };
+ int res;
+ const char *tmp;
+
+ if ((cfg = ast_config_load(cdr_config, config_flags))) {
+ ast_set2_flag(&global_flags, ast_true(ast_variable_retrieve(cfg, "radius", "usegmtime")), RADIUS_FLAG_USEGMTIME);
+ ast_set2_flag(&global_flags, ast_true(ast_variable_retrieve(cfg, "radius", "loguniqueid")), RADIUS_FLAG_LOGUNIQUEID);
+ ast_set2_flag(&global_flags, ast_true(ast_variable_retrieve(cfg, "radius", "loguserfield")), RADIUS_FLAG_LOGUSERFIELD);
+ if ((tmp = ast_variable_retrieve(cfg, "radius", "radiuscfg")))
+ ast_copy_string(radiuscfg, tmp, sizeof(radiuscfg));
+ ast_config_destroy(cfg);
+ } else
+ return AST_MODULE_LOAD_DECLINE;
+
+ /* start logging */
+ rc_openlog("asterisk");
+
+ /* read radiusclient-ng config file */
+ if (!(rh = rc_read_config(radiuscfg))) {
+ ast_log(LOG_NOTICE, "Cannot load radiusclient-ng configuration file %s.\n", radiuscfg);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ /* read radiusclient-ng dictionaries */
+ if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary"))) {
+ ast_log(LOG_NOTICE, "Cannot load radiusclient-ng dictionary file.\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ res = ast_cdr_register(name, desc, radius_log);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "RADIUS CDR Backend");
diff --git a/trunk/cdr/cdr_sqlite.c b/trunk/cdr/cdr_sqlite.c
new file mode 100644
index 000000000..601e5b245
--- /dev/null
+++ b/trunk/cdr/cdr_sqlite.c
@@ -0,0 +1,214 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2005, Holger Schurig
+ *
+ *
+ * Ideas taken from other cdr_*.c files
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Store CDR records in a SQLite database.
+ *
+ * \author Holger Schurig <hs4233@mail.mn-solutions.de>
+ * \extref SQLite http://www.sqlite.org/
+ *
+ * See also
+ * \arg \ref Config_cdr
+ * \arg http://www.sqlite.org/
+ *
+ * Creates the database and table on-the-fly
+ * \ingroup cdr_drivers
+ *
+ * \note This module has been marked deprecated in favor for cdr_sqlite3_custom
+ */
+
+/*** MODULEINFO
+ <depend>sqlite</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sqlite.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+#include "asterisk/paths.h"
+
+#define LOG_UNIQUEID 0
+#define LOG_USERFIELD 0
+
+/* When you change the DATE_FORMAT, be sure to change the CHAR(19) below to something else */
+#define DATE_FORMAT "%Y-%m-%d %T"
+
+static char *name = "sqlite";
+static sqlite* db = NULL;
+
+AST_MUTEX_DEFINE_STATIC(sqlite_lock);
+
+/*! \brief SQL table format */
+static char sql_create_table[] = "CREATE TABLE cdr ("
+" AcctId INTEGER PRIMARY KEY,"
+" clid VARCHAR(80),"
+" src VARCHAR(80),"
+" dst VARCHAR(80),"
+" dcontext VARCHAR(80),"
+" channel VARCHAR(80),"
+" dstchannel VARCHAR(80),"
+" lastapp VARCHAR(80),"
+" lastdata VARCHAR(80),"
+" start CHAR(19),"
+" answer CHAR(19),"
+" end CHAR(19),"
+" duration INTEGER,"
+" billsec INTEGER,"
+" disposition INTEGER,"
+" amaflags INTEGER,"
+" accountcode VARCHAR(20)"
+#if LOG_UNIQUEID
+" ,uniqueid VARCHAR(32)"
+#endif
+#if LOG_USERFIELD
+" ,userfield VARCHAR(255)"
+#endif
+");";
+
+static int sqlite_log(struct ast_cdr *cdr)
+{
+ int res = 0;
+ char *zErr = 0;
+ struct ast_tm tm;
+ char startstr[80], answerstr[80], endstr[80];
+ int count;
+
+ ast_mutex_lock(&sqlite_lock);
+
+ ast_localtime(&cdr->start, &tm, NULL);
+ ast_strftime(startstr, sizeof(startstr), DATE_FORMAT, &tm);
+
+ ast_localtime(&cdr->answer, &tm, NULL);
+ ast_strftime(answerstr, sizeof(answerstr), DATE_FORMAT, &tm);
+
+ ast_localtime(&cdr->end, &tm, NULL);
+ ast_strftime(endstr, sizeof(endstr), DATE_FORMAT, &tm);
+
+ for(count=0; count<5; count++) {
+ res = sqlite_exec_printf(db,
+ "INSERT INTO cdr ("
+ "clid,src,dst,dcontext,"
+ "channel,dstchannel,lastapp,lastdata, "
+ "start,answer,end,"
+ "duration,billsec,disposition,amaflags, "
+ "accountcode"
+# if LOG_UNIQUEID
+ ",uniqueid"
+# endif
+# if LOG_USERFIELD
+ ",userfield"
+# endif
+ ") VALUES ("
+ "'%q', '%q', '%q', '%q', "
+ "'%q', '%q', '%q', '%q', "
+ "'%q', '%q', '%q', "
+ "%d, %d, %d, %d, "
+ "'%q'"
+# if LOG_UNIQUEID
+ ",'%q'"
+# endif
+# if LOG_USERFIELD
+ ",'%q'"
+# endif
+ ")", NULL, NULL, &zErr,
+ cdr->clid, cdr->src, cdr->dst, cdr->dcontext,
+ cdr->channel, cdr->dstchannel, cdr->lastapp, cdr->lastdata,
+ startstr, answerstr, endstr,
+ cdr->duration, cdr->billsec, cdr->disposition, cdr->amaflags,
+ cdr->accountcode
+# if LOG_UNIQUEID
+ ,cdr->uniqueid
+# endif
+# if LOG_USERFIELD
+ ,cdr->userfield
+# endif
+ );
+ if (res != SQLITE_BUSY && res != SQLITE_LOCKED)
+ break;
+ usleep(200);
+ }
+
+ if (zErr) {
+ ast_log(LOG_ERROR, "cdr_sqlite: %s\n", zErr);
+ ast_free(zErr);
+ }
+
+ ast_mutex_unlock(&sqlite_lock);
+ return res;
+}
+
+static int unload_module(void)
+{
+ if (db)
+ sqlite_close(db);
+ ast_cdr_unregister(name);
+ return 0;
+}
+
+static int load_module(void)
+{
+ char *zErr;
+ char fn[PATH_MAX];
+ int res;
+
+ ast_log(LOG_WARNING, "This module has been marked deprecated in favor of "
+ "using cdr_sqlite3_custom. (May be removed after Asterisk 1.6)\n");
+
+ /* is the database there? */
+ snprintf(fn, sizeof(fn), "%s/cdr.db", ast_config_AST_LOG_DIR);
+ db = sqlite_open(fn, AST_FILE_MODE, &zErr);
+ if (!db) {
+ ast_log(LOG_ERROR, "cdr_sqlite: %s\n", zErr);
+ ast_free(zErr);
+ return -1;
+ }
+
+ /* is the table there? */
+ res = sqlite_exec(db, "SELECT COUNT(AcctId) FROM cdr;", NULL, NULL, NULL);
+ if (res) {
+ res = sqlite_exec(db, sql_create_table, NULL, NULL, &zErr);
+ if (res) {
+ ast_log(LOG_ERROR, "cdr_sqlite: Unable to create table 'cdr': %s\n", zErr);
+ ast_free(zErr);
+ goto err;
+ }
+
+ /* TODO: here we should probably create an index */
+ }
+
+ res = ast_cdr_register(name, ast_module_info->description, sqlite_log);
+ if (res) {
+ ast_log(LOG_ERROR, "Unable to register SQLite CDR handling\n");
+ return -1;
+ }
+ return 0;
+
+err:
+ if (db)
+ sqlite_close(db);
+ return -1;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SQLite CDR Backend");
diff --git a/trunk/cdr/cdr_sqlite3_custom.c b/trunk/cdr/cdr_sqlite3_custom.c
new file mode 100644
index 000000000..87718db59
--- /dev/null
+++ b/trunk/cdr/cdr_sqlite3_custom.c
@@ -0,0 +1,357 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2007, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com> and others.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Custom SQLite3 CDR records.
+ *
+ * \author Adapted by Alejandro Rios <alejandro.rios@avatar.com.co> and
+ * Russell Bryant <russell@digium.com> from
+ * cdr_mysql_custom by Edward Eastman <ed@dm3.co.uk>,
+ * and cdr_sqlite by Holger Schurig <hs4233@mail.mn-solutions.de>
+ *
+ *
+ * \arg See also \ref AstCDR
+ *
+ *
+ * \ingroup cdr_drivers
+ */
+
+/*** MODULEINFO
+ <depend>sqlite3</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+#include <sqlite3.h>
+
+#include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/cli.h"
+
+AST_MUTEX_DEFINE_STATIC(lock);
+
+static const char config_file[] = "cdr_sqlite3_custom.conf";
+
+static char *desc = "Customizable SQLite3 CDR Backend";
+static char *name = "cdr_sqlite3_custom";
+static sqlite3 *db = NULL;
+
+static char table[80];
+static char *columns;
+
+struct values {
+ char *expression;
+ AST_LIST_ENTRY(values) list;
+};
+
+static AST_LIST_HEAD_STATIC(sql_values, values);
+
+static int free_config(void);
+
+static int load_column_config(const char *tmp)
+{
+ char *col = NULL;
+ char *cols = NULL;
+ char *escaped = NULL;
+ struct ast_str *column_string = NULL;
+
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_WARNING, "Column names not specified. Module not loaded.\n");
+ return -1;
+ }
+ if (!(column_string = ast_str_create(1024))) {
+ ast_log(LOG_ERROR, "Out of memory creating temporary buffer for column list for table '%s.'\n", table);
+ return -1;
+ }
+ if (!(cols = ast_strdup(tmp))) {
+ ast_log(LOG_ERROR, "Out of memory creating temporary buffer for column list for table '%s.'\n", table);
+ ast_free(column_string);
+ return -1;
+ }
+ while ((col = strsep(&cols, ","))) {
+ col = ast_strip(col);
+ escaped = sqlite3_mprintf("%q", col);
+ if (!escaped) {
+ ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s.'\n", col, table);
+ ast_free(column_string);
+ ast_free(cols);
+ return -1;
+ }
+ if (!column_string->used)
+ ast_str_set(&column_string, 0, escaped);
+ else
+ ast_str_append(&column_string, 0, ",%s", escaped);
+ sqlite3_free(escaped);
+ }
+ if (!(columns = ast_strdup(column_string->str))) {
+ ast_log(LOG_ERROR, "Out of memory copying columns string for table '%s.'\n", table);
+ ast_free(column_string);
+ ast_free(cols);
+ return -1;
+ }
+ ast_free(column_string);
+ ast_free(cols);
+
+ return 0;
+}
+
+static int load_values_config(const char *tmp)
+{
+ char *val = NULL;
+ char *vals = NULL;
+ struct values *value = NULL;
+
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_WARNING, "Values not specified. Module not loaded.\n");
+ return -1;
+ }
+ if (!(vals = ast_strdup(tmp))) {
+ ast_log(LOG_ERROR, "Out of memory creating temporary buffer for value '%s'\n", tmp);
+ return -1;
+ }
+ while ((val = strsep(&vals, ","))) {
+ /* Strip the single quotes off if they are there */
+ val = ast_strip_quoted(val, "'", "'");
+ value = ast_calloc(sizeof(char), sizeof(*value) + strlen(val) + 1);
+ if (!value) {
+ ast_log(LOG_ERROR, "Out of memory creating entry for value '%s'\n", val);
+ ast_free(vals);
+ return -1;
+ }
+ value->expression = (char *) value + sizeof(*value);
+ ast_copy_string(value->expression, val, strlen(val) + 1);
+ AST_LIST_INSERT_TAIL(&sql_values, value, list);
+ }
+ ast_free(vals);
+
+ return 0;
+}
+
+static int load_config(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ struct ast_variable *mappingvar;
+ const char *tmp;
+
+ if (!(cfg = ast_config_load(config_file, config_flags))) {
+ if (reload)
+ ast_log(LOG_WARNING, "Failed to reload configuration file.\n");
+ else
+ ast_log(LOG_WARNING, "Failed to load configuration file. Module not activated.\n");
+ return -1;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ if (reload)
+ free_config();
+
+ ast_mutex_lock(&lock);
+
+ if (!(mappingvar = ast_variable_browse(cfg, "master"))) {
+ /* Nothing configured */
+ ast_mutex_unlock(&lock);
+ ast_config_destroy(cfg);
+ return 0;
+ }
+
+ /* Mapping must have a table name */
+ tmp = ast_variable_retrieve(cfg, "master", "table");
+ if (!ast_strlen_zero(tmp))
+ ast_copy_string(table, tmp, sizeof(table));
+ else {
+ ast_log(LOG_WARNING, "Table name not specified. Assuming cdr.\n");
+ strcpy(table, "cdr");
+ }
+
+ /* Columns */
+ tmp = ast_variable_retrieve(cfg, "master", "columns");
+ if (load_column_config(tmp)) {
+ ast_mutex_unlock(&lock);
+ ast_config_destroy(cfg);
+ free_config();
+ return -1;
+ }
+
+ /* Values */
+ tmp = ast_variable_retrieve(cfg, "master", "values");
+ if (load_values_config(tmp)) {
+ ast_mutex_unlock(&lock);
+ ast_config_destroy(cfg);
+ free_config();
+ return -1;
+ }
+
+ ast_verb(3, "cdr_sqlite3_custom: Logging CDR records to table '%s' in 'master.db'\n", table);
+
+ ast_mutex_unlock(&lock);
+ ast_config_destroy(cfg);
+
+ return 0;
+}
+
+static int free_config(void)
+{
+ struct values *value;
+
+ ast_mutex_lock(&lock);
+
+ if (db) {
+ sqlite3_close(db);
+ db = NULL;
+ }
+
+ if (columns) {
+ ast_free(columns);
+ columns = NULL;
+ }
+
+ while ((value = AST_LIST_REMOVE_HEAD(&sql_values, list)))
+ ast_free(value);
+
+ ast_mutex_unlock(&lock);
+
+ return 0;
+}
+
+static int sqlite3_log(struct ast_cdr *cdr)
+{
+ int res = 0;
+ char *error = NULL;
+ char *sql = NULL;
+ struct ast_channel dummy = { 0, };
+ int count = 0;
+
+ { /* Make it obvious that only sql should be used outside of this block */
+ char *escaped;
+ char subst_buf[2048];
+ struct values *value;
+ struct ast_str *value_string = ast_str_create(1024);
+ dummy.cdr = cdr;
+ AST_LIST_TRAVERSE(&sql_values, value, list) {
+ memset(subst_buf, 0, sizeof(subst_buf));
+ pbx_substitute_variables_helper(&dummy, value->expression, subst_buf, sizeof(subst_buf) - 1);
+ escaped = sqlite3_mprintf("%q", subst_buf);
+ if (!value_string->used)
+ ast_str_append(&value_string, 0, "'%s'", escaped);
+ else
+ ast_str_append(&value_string, 0, ",'%s'", escaped);
+ sqlite3_free(escaped);
+ }
+ sql = sqlite3_mprintf("INSERT INTO %q (%s) VALUES (%s)", table, columns, value_string->str);
+ ast_debug(1, "About to log: %s\n", sql);
+ ast_free(value_string);
+ }
+
+ ast_mutex_lock(&lock);
+
+ /* XXX This seems awful arbitrary... */
+ for (count = 0; count < 5; count++) {
+ res = sqlite3_exec(db, sql, NULL, NULL, &error);
+ if (res != SQLITE_BUSY && res != SQLITE_LOCKED)
+ break;
+ usleep(200);
+ }
+
+ if (error) {
+ ast_log(LOG_ERROR, "%s. SQL: %s.\n", error, sql);
+ sqlite3_free(error);
+ }
+
+ if (sql)
+ sqlite3_free(sql);
+
+ ast_mutex_unlock(&lock);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ free_config();
+
+ ast_cdr_unregister(name);
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ char *error;
+ char filename[PATH_MAX];
+ int res;
+ char *sql;
+
+ if (!load_config(0)) {
+ res = ast_cdr_register(name, desc, sqlite3_log);
+ if (res) {
+ ast_log(LOG_ERROR, "Unable to register custom SQLite3 CDR handling\n");
+ free_config();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ } else
+ return AST_MODULE_LOAD_DECLINE;
+
+ /* is the database there? */
+ snprintf(filename, sizeof(filename), "%s/master.db", ast_config_AST_LOG_DIR);
+ res = sqlite3_open(filename, &db);
+ if (res != SQLITE_OK) {
+ ast_log(LOG_ERROR, "Could not open database %s.\n", filename);
+ free_config();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ /* is the table there? */
+ sql = sqlite3_mprintf("SELECT COUNT(AcctId) FROM %q;", table);
+ res = sqlite3_exec(db, sql, NULL, NULL, NULL);
+ sqlite3_free(sql);
+ if (res != SQLITE_OK) {
+ /* We don't use %q for the column list here since we already escaped when building it */
+ sql = sqlite3_mprintf("CREATE TABLE %q (AcctId INTEGER PRIMARY KEY, %s)", table, columns);
+ res = sqlite3_exec(db, sql, NULL, NULL, &error);
+ sqlite3_free(sql);
+ if (res != SQLITE_OK) {
+ ast_log(LOG_WARNING, "Unable to create table '%s': %s.\n", table, error);
+ sqlite3_free(error);
+ free_config();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ }
+
+ return 0;
+}
+
+static int reload(void)
+{
+ return load_config(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "SQLite3 Custom CDR Module",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
diff --git a/trunk/cdr/cdr_tds.c b/trunk/cdr/cdr_tds.c
new file mode 100644
index 000000000..9d322fd27
--- /dev/null
+++ b/trunk/cdr/cdr_tds.c
@@ -0,0 +1,531 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2006, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief FreeTDS CDR logger
+ *
+ * See also
+ * \arg \ref Config_cdr
+ * \arg http://www.freetds.org/
+ * \ingroup cdr_drivers
+ */
+
+/*! \verbatim
+ *
+ * Table Structure for `cdr`
+ *
+ * Created on: 05/20/2004 16:16
+ * Last changed on: 07/27/2004 20:01
+
+CREATE TABLE [dbo].[cdr] (
+ [accountcode] [varchar] (20) NULL ,
+ [src] [varchar] (80) NULL ,
+ [dst] [varchar] (80) NULL ,
+ [dcontext] [varchar] (80) NULL ,
+ [clid] [varchar] (80) NULL ,
+ [channel] [varchar] (80) NULL ,
+ [dstchannel] [varchar] (80) NULL ,
+ [lastapp] [varchar] (80) NULL ,
+ [lastdata] [varchar] (80) NULL ,
+ [start] [datetime] NULL ,
+ [answer] [datetime] NULL ,
+ [end] [datetime] NULL ,
+ [duration] [int] NULL ,
+ [billsec] [int] NULL ,
+ [disposition] [varchar] (20) NULL ,
+ [amaflags] [varchar] (16) NULL ,
+ [uniqueid] [varchar] (32) NULL
+) ON [PRIMARY]
+
+\endverbatim
+
+*/
+
+/*** MODULEINFO
+ <depend>freetds</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+#include <math.h>
+
+#include <tds.h>
+#include <tdsconvert.h>
+#include <ctype.h>
+
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+
+#ifdef FREETDS_PRE_0_62
+#warning "You have older TDS, you should upgrade!"
+#endif
+
+#define DATE_FORMAT "%Y/%m/%d %T"
+
+static char *name = "mssql";
+static char *config = "cdr_tds.conf";
+
+static char *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *charset = NULL, *language = NULL;
+static char *table = NULL;
+
+static int connected = 0;
+
+AST_MUTEX_DEFINE_STATIC(tds_lock);
+
+static TDSSOCKET *tds;
+static TDSLOGIN *login;
+static TDSCONTEXT *context;
+
+static char *anti_injection(const char *, int);
+static void get_date(char *, struct timeval);
+
+static int mssql_connect(void);
+static int mssql_disconnect(void);
+
+static int tds_log(struct ast_cdr *cdr)
+{
+ char sqlcmd[2048], start[80], answer[80], end[80];
+ char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid;
+ int res = 0;
+ int retried = 0;
+#ifdef FREETDS_PRE_0_62
+ TDS_INT result_type;
+#endif
+
+ ast_mutex_lock(&tds_lock);
+
+ memset(sqlcmd, 0, 2048);
+
+ accountcode = anti_injection(cdr->accountcode, 20);
+ src = anti_injection(cdr->src, 80);
+ dst = anti_injection(cdr->dst, 80);
+ dcontext = anti_injection(cdr->dcontext, 80);
+ clid = anti_injection(cdr->clid, 80);
+ channel = anti_injection(cdr->channel, 80);
+ dstchannel = anti_injection(cdr->dstchannel, 80);
+ lastapp = anti_injection(cdr->lastapp, 80);
+ lastdata = anti_injection(cdr->lastdata, 80);
+ uniqueid = anti_injection(cdr->uniqueid, 32);
+
+ get_date(start, cdr->start);
+ get_date(answer, cdr->answer);
+ get_date(end, cdr->end);
+
+ sprintf(
+ sqlcmd,
+ "INSERT INTO %s "
+ "("
+ "accountcode, "
+ "src, "
+ "dst, "
+ "dcontext, "
+ "clid, "
+ "channel, "
+ "dstchannel, "
+ "lastapp, "
+ "lastdata, "
+ "start, "
+ "answer, "
+ "[end], "
+ "duration, "
+ "billsec, "
+ "disposition, "
+ "amaflags, "
+ "uniqueid"
+ ") "
+ "VALUES "
+ "("
+ "'%s', " /* accountcode */
+ "'%s', " /* src */
+ "'%s', " /* dst */
+ "'%s', " /* dcontext */
+ "'%s', " /* clid */
+ "'%s', " /* channel */
+ "'%s', " /* dstchannel */
+ "'%s', " /* lastapp */
+ "'%s', " /* lastdata */
+ "%s, " /* start */
+ "%s, " /* answer */
+ "%s, " /* end */
+ "%ld, " /* duration */
+ "%ld, " /* billsec */
+ "'%s', " /* disposition */
+ "'%s', " /* amaflags */
+ "'%s'" /* uniqueid */
+ ")",
+ table,
+ accountcode,
+ src,
+ dst,
+ dcontext,
+ clid,
+ channel,
+ dstchannel,
+ lastapp,
+ lastdata,
+ start,
+ answer,
+ end,
+ cdr->duration,
+ cdr->billsec,
+ ast_cdr_disp2str(cdr->disposition),
+ ast_cdr_flags2str(cdr->amaflags),
+ uniqueid
+ );
+
+ do {
+ if (!connected) {
+ if (mssql_connect())
+ ast_log(LOG_ERROR, "Failed to reconnect to SQL database.\n");
+ else
+ ast_log(LOG_WARNING, "Reconnected to SQL database.\n");
+
+ retried = 1; /* note that we have now tried */
+ }
+
+#ifdef FREETDS_PRE_0_62
+ if (!connected || (tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
+#else
+ if (!connected || (tds_submit_query(tds, sqlcmd) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
+#endif
+ {
+ ast_log(LOG_ERROR, "Failed to insert Call Data Record into SQL database.\n");
+
+ mssql_disconnect(); /* this is ok even if we are already disconnected */
+ }
+ } while (!connected && !retried);
+
+ ast_free(accountcode);
+ ast_free(src);
+ ast_free(dst);
+ ast_free(dcontext);
+ ast_free(clid);
+ ast_free(channel);
+ ast_free(dstchannel);
+ ast_free(lastapp);
+ ast_free(lastdata);
+ ast_free(uniqueid);
+
+ ast_mutex_unlock(&tds_lock);
+
+ return res;
+}
+
+static char *anti_injection(const char *str, int len)
+{
+ /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */
+
+ char *buf;
+ char *buf_ptr, *srh_ptr;
+ char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
+ int idx;
+
+ if ((buf = ast_malloc(len + 1)) == NULL)
+ {
+ ast_log(LOG_ERROR, "cdr_tds: Out of memory error\n");
+ return NULL;
+ }
+ memset(buf, 0, len);
+
+ buf_ptr = buf;
+
+ /* Escape single quotes */
+ for (; *str && strlen(buf) < len; str++)
+ {
+ if (*str == '\'')
+ *buf_ptr++ = '\'';
+ *buf_ptr++ = *str;
+ }
+ *buf_ptr = '\0';
+
+ /* Erase known bad input */
+ for (idx=0; *known_bad[idx]; idx++)
+ {
+ while((srh_ptr = strcasestr(buf, known_bad[idx])))
+ {
+ memmove(srh_ptr, srh_ptr+strlen(known_bad[idx]), strlen(srh_ptr+strlen(known_bad[idx]))+1);
+ }
+ }
+
+ return buf;
+}
+
+static void get_date(char *dateField, struct timeval tv)
+{
+ struct ast_tm tm;
+ char buf[80];
+
+ /* To make sure we have date variable if not insert null to SQL */
+ if (!ast_tvzero(tv))
+ {
+ ast_localtime(&tv, &tm, NULL);
+ ast_strftime(buf, 80, DATE_FORMAT, &tm);
+ sprintf(dateField, "'%s'", buf);
+ }
+ else
+ {
+ strcpy(dateField, "null");
+ }
+}
+
+static int mssql_disconnect(void)
+{
+ if (tds) {
+ tds_free_socket(tds);
+ tds = NULL;
+ }
+
+ if (context) {
+ tds_free_context(context);
+ context = NULL;
+ }
+
+ if (login) {
+ tds_free_login(login);
+ login = NULL;
+ }
+
+ connected = 0;
+
+ return 0;
+}
+
+static int mssql_connect(void)
+{
+#if (defined(FREETDS_0_63) || defined(FREETDS_0_64))
+ TDSCONNECTION *connection = NULL;
+#else
+ TDSCONNECTINFO *connection = NULL;
+#endif
+ char query[128];
+
+ /* Connect to M$SQL Server */
+ if (!(login = tds_alloc_login()))
+ {
+ ast_log(LOG_ERROR, "tds_alloc_login() failed.\n");
+ return -1;
+ }
+
+ tds_set_server(login, hostname);
+ tds_set_user(login, dbuser);
+ tds_set_passwd(login, password);
+ tds_set_app(login, "TSQL");
+ tds_set_library(login, "TDS-Library");
+#ifndef FREETDS_PRE_0_62
+ tds_set_client_charset(login, charset);
+#endif
+ tds_set_language(login, language);
+ tds_set_packet(login, 512);
+ tds_set_version(login, 7, 0);
+
+#ifdef FREETDS_0_64
+ if (!(context = tds_alloc_context(NULL)))
+#else
+ if (!(context = tds_alloc_context()))
+#endif
+ {
+ ast_log(LOG_ERROR, "tds_alloc_context() failed.\n");
+ goto connect_fail;
+ }
+
+ if (!(tds = tds_alloc_socket(context, 512))) {
+ ast_log(LOG_ERROR, "tds_alloc_socket() failed.\n");
+ goto connect_fail;
+ }
+
+ tds_set_parent(tds, NULL);
+ connection = tds_read_config_info(tds, login, context->locale);
+ if (!connection)
+ {
+ ast_log(LOG_ERROR, "tds_read_config() failed.\n");
+ goto connect_fail;
+ }
+
+ if (tds_connect(tds, connection) == TDS_FAIL)
+ {
+ ast_log(LOG_ERROR, "Failed to connect to MSSQL server.\n");
+ tds = NULL; /* freed by tds_connect() on error */
+#if (defined(FREETDS_0_63) || defined(FREETDS_0_64))
+ tds_free_connection(connection);
+#else
+ tds_free_connect(connection);
+#endif
+ connection = NULL;
+ goto connect_fail;
+ }
+#if (defined(FREETDS_0_63) || defined(FREETDS_0_64))
+ tds_free_connection(connection);
+#else
+ tds_free_connect(connection);
+#endif
+ connection = NULL;
+
+ sprintf(query, "USE %s", dbname);
+#ifdef FREETDS_PRE_0_62
+ if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds, &result_type) != TDS_SUCCEED || result_type != TDS_CMD_SUCCEED))
+#else
+ if ((tds_submit_query(tds, query) != TDS_SUCCEED) || (tds_process_simple_query(tds) != TDS_SUCCEED))
+#endif
+ {
+ ast_log(LOG_ERROR, "Could not change database (%s)\n", dbname);
+ goto connect_fail;
+ }
+
+ connected = 1;
+ return 0;
+
+connect_fail:
+ mssql_disconnect();
+ return -1;
+}
+
+static int tds_unload_module(void)
+{
+ mssql_disconnect();
+
+ ast_cdr_unregister(name);
+
+ if (hostname) ast_free(hostname);
+ if (dbname) ast_free(dbname);
+ if (dbuser) ast_free(dbuser);
+ if (password) ast_free(password);
+ if (charset) ast_free(charset);
+ if (language) ast_free(language);
+ if (table) ast_free(table);
+
+ return 0;
+}
+
+static int tds_load_module(int reload)
+{
+ int res = 0;
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ const char *ptr = NULL;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+#ifdef FREETDS_PRE_0_62
+ TDS_INT result_type;
+#endif
+
+ cfg = ast_config_load(config, config_flags);
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to load config for MSSQL CDR's: %s\n", config);
+ return 0;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ var = ast_variable_browse(cfg, "global");
+ if (!var) /* nothing configured */ {
+ ast_config_destroy(cfg);
+ return 0;
+ }
+
+ ptr = ast_variable_retrieve(cfg, "global", "hostname");
+ if (ptr) {
+ if (hostname)
+ ast_free(hostname);
+ hostname = ast_strdup(ptr);
+ } else
+ ast_log(LOG_ERROR, "Database server hostname not specified.\n");
+
+ ptr = ast_variable_retrieve(cfg, "global", "dbname");
+ if (ptr) {
+ if (dbname)
+ ast_free(dbname);
+ dbname = ast_strdup(ptr);
+ } else
+ ast_log(LOG_ERROR, "Database dbname not specified.\n");
+
+ ptr = ast_variable_retrieve(cfg, "global", "user");
+ if (ptr) {
+ if (dbuser)
+ ast_free(dbuser);
+ dbuser = ast_strdup(ptr);
+ } else
+ ast_log(LOG_ERROR, "Database dbuser not specified.\n");
+
+ ptr = ast_variable_retrieve(cfg, "global", "password");
+ if (ptr) {
+ if (password)
+ ast_free(password);
+ password = ast_strdup(ptr);
+ } else
+ ast_log(LOG_ERROR,"Database password not specified.\n");
+
+ ptr = ast_variable_retrieve(cfg, "global", "charset");
+ if (charset)
+ ast_free(charset);
+ if (ptr)
+ charset = ast_strdup(ptr);
+ else
+ charset = ast_strdup("iso_1");
+
+ if (language)
+ ast_free(language);
+ ptr = ast_variable_retrieve(cfg, "global", "language");
+ if (ptr)
+ language = ast_strdup(ptr);
+ else
+ language = ast_strdup("us_english");
+
+ ptr = ast_variable_retrieve(cfg, "global", "table");
+ if (ptr == NULL) {
+ ast_debug(1, "cdr_tds: table not specified. Assuming cdr\n");
+ ptr = "cdr";
+ }
+ if (table)
+ ast_free(table);
+ table = ast_strdup(ptr);
+
+ ast_config_destroy(cfg);
+
+ ast_mutex_lock(&tds_lock);
+ mssql_disconnect();
+ mssql_connect();
+ ast_mutex_unlock(&tds_lock);
+
+ return res;
+}
+
+static int reload(void)
+{
+ return tds_load_module(1);
+}
+
+static int load_module(void)
+{
+ if (!tds_load_module(0))
+ return AST_MODULE_LOAD_DECLINE;
+ ast_cdr_register(name, ast_module_info->description, tds_log);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ return tds_unload_module();
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MSSQL CDR Backend",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );