From 544c5faf5e6ce6fe2b87523304d1f00e2d201d90 Mon Sep 17 00:00:00 2001 From: "E. Schmidbauer" Date: Wed, 8 Oct 2014 08:31:35 -0400 Subject: [PATCH 01/12] Add module mod_odbc_cdr --- build/modules.conf.in | 1 + configure.ac | 1 + .../event_handlers/mod_odbc_cdr/Makefile.am | 9 + .../conf/autoload_configs/odbc_cdr.conf.xml | 58 ++ .../mod_odbc_cdr/mod_odbc_cdr.c | 565 ++++++++++++++++++ 5 files changed, 634 insertions(+) create mode 100644 src/mod/event_handlers/mod_odbc_cdr/Makefile.am create mode 100644 src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml create mode 100644 src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c diff --git a/build/modules.conf.in b/build/modules.conf.in index 71ece983ef..ecade12ef5 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -105,6 +105,7 @@ event_handlers/mod_event_socket #event_handlers/mod_format_cdr #event_handlers/mod_json_cdr #event_handlers/mod_radius_cdr +#event_handlers/mod_odbc_cdr #event_handlers/mod_rayo #event_handlers/mod_snmp formats/mod_local_stream diff --git a/configure.ac b/configure.ac index e8f9efade0..11b68a3a8e 100644 --- a/configure.ac +++ b/configure.ac @@ -1564,6 +1564,7 @@ AC_CONFIG_FILES([Makefile src/mod/event_handlers/mod_format_cdr/Makefile src/mod/event_handlers/mod_json_cdr/Makefile src/mod/event_handlers/mod_radius_cdr/Makefile + src/mod/event_handlers/mod_odbc_cdr/Makefile src/mod/event_handlers/mod_rayo/Makefile src/mod/event_handlers/mod_snmp/Makefile src/mod/event_handlers/mod_event_zmq/Makefile diff --git a/src/mod/event_handlers/mod_odbc_cdr/Makefile.am b/src/mod/event_handlers/mod_odbc_cdr/Makefile.am new file mode 100644 index 0000000000..ce799a3e1e --- /dev/null +++ b/src/mod/event_handlers/mod_odbc_cdr/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_odbc_cdr + +mod_LTLIBRARIES = mod_odbc_cdr.la +mod_odbc_cdr_la_SOURCES = mod_odbc_cdr.c +mod_odbc_cdr_la_CFLAGS = $(AM_CFLAGS) +mod_odbc_cdr_la_LIBADD = $(switch_builddir)/libfreeswitch.la +mod_odbc_cdr_la_LDFLAGS = -avoid-version -module -no-undefined -shared + diff --git a/src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml b/src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml new file mode 100644 index 0000000000..0e764d14ec --- /dev/null +++ b/src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + +
+
+
diff --git a/src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c b/src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c new file mode 100644 index 0000000000..edfdbf08c4 --- /dev/null +++ b/src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c @@ -0,0 +1,565 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2012, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Emmanuel Schmidbauer + * + * mod_odbc_cdr.c + * + */ + +#include "switch.h" + +#define ODBC_CDR_SQLITE_DB_NAME "odbc_cdr" + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_odbc_cdr_shutdown); +SWITCH_MODULE_LOAD_FUNCTION(mod_odbc_cdr_load); +SWITCH_MODULE_DEFINITION(mod_odbc_cdr, mod_odbc_cdr_load, mod_odbc_cdr_shutdown, NULL); + +static const char *global_cf = "odbc_cdr.conf"; + +typedef enum { + ODBC_CDR_LOG_A, + ODBC_CDR_LOG_B, + ODBC_CDR_LOG_BOTH +} odbc_cdr_log_leg_t; + +typedef enum { + ODBC_CDR_CSV_ALWAYS, + ODBC_CDR_CSV_NEVER, + ODBC_CDR_CSV_ON_FAIL +} odbc_cdr_write_csv_t; + +static struct { + char *odbc_dsn; + char *dbname; + char *csv_path; + char *csv_fail_path; + odbc_cdr_log_leg_t log_leg; + odbc_cdr_write_csv_t write_csv; + switch_bool_t debug_sql; + switch_hash_t *table_hash; + uint32_t running; + switch_mutex_t *mutex; + switch_memory_pool_t *pool; +} globals; + +struct table_profile { + char *name; + odbc_cdr_log_leg_t log_leg; + switch_hash_t *field_hash; + uint32_t flags; + switch_mutex_t *mutex; + switch_memory_pool_t *pool; +}; +typedef struct table_profile table_profile_t; + +static table_profile_t *load_table(const char *table_name) +{ + table_profile_t *table = NULL; + switch_xml_t x_tables, cfg, xml, x_table, x_field; + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + return table; + } + + if (!(x_tables = switch_xml_child(cfg, "tables"))) { + goto end; + } + + if ((x_table = switch_xml_find_child(x_tables, "table", "name", table_name))) { + switch_memory_pool_t *pool; + char *table_log_leg = (char *) switch_xml_attr_soft(x_table, "log-leg"); + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n"); + goto end; + } + + if (!(table = switch_core_alloc(pool, sizeof(table_profile_t)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n"); + switch_core_destroy_memory_pool(&pool); + goto end; + } + + table->pool = pool; + + switch_mutex_init(&table->mutex, SWITCH_MUTEX_NESTED, table->pool); + + table->name = switch_core_strdup(pool, table_name); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Found table [%s]\n", table->name); + + if (!strcasecmp(table_log_leg, "a-leg")) { + table->log_leg = ODBC_CDR_LOG_A; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set table [%s] to log A-legs only\n", table->name); + } else if (!strcasecmp(table_log_leg, "b-leg")) { + table->log_leg = ODBC_CDR_LOG_B; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set table [%s] to log B-legs only\n", table->name); + } else { + table->log_leg = ODBC_CDR_LOG_BOTH; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set table [%s] to log both legs\n", table->name); + } + + switch_core_hash_init(&table->field_hash); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding fields to table [%s]\n", table->name); + + for (x_field = switch_xml_child(x_table, "field"); x_field; x_field = x_field->next) { + char *var = (char *) switch_xml_attr_soft(x_field, "name"); + char *val = (char *) switch_xml_attr_soft(x_field, "chan-var-name"); + char *value = NULL; + if (zstr(var) || zstr(val)) { + continue; // Ignore empty entries + } + value = switch_core_strdup(pool, val); + switch_core_hash_insert_locked(table->field_hash, var, value, table->mutex); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Field [%s] (%s) added to [%s]\n", var, val, table->name); + } + + switch_core_hash_insert(globals.table_hash, table->name, table); + } + +end: + + if (xml) { + switch_xml_free(xml); + } + + return table; +} + +switch_cache_db_handle_t *get_db_handle(void) +{ + switch_cache_db_handle_t *dbh = NULL; + char *dsn; + if (!zstr(globals.odbc_dsn)) { + dsn = globals.odbc_dsn; + } else { + dsn = globals.dbname; + } + if (switch_cache_db_get_db_handle_dsn(&dbh, dsn) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); + dbh = NULL; + } + return dbh; +} + +static switch_status_t odbc_cdr_execute_sql_no_callback(char *sql) +{ + switch_cache_db_handle_t *dbh = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + + if (!(dbh = get_db_handle())) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); + goto end; + } + + status = switch_cache_db_execute_sql(dbh, sql, NULL); + +end: + + switch_cache_db_release_db_handle(&dbh); + + return status; +} + +static void write_cdr(const char *path, const char *log_line) +{ + int fd = -1; +#ifdef _MSC_VER + if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) { +#else + if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) > -1) { +#endif + int wrote; + wrote = write(fd, log_line, (unsigned) strlen(log_line)); + wrote += write(fd, "\n", 1); + wrote++; + close(fd); + fd = -1; + } +} + +static switch_status_t odbc_cdr_reporting(switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_memory_pool_t *pool = switch_core_session_get_pool(session); + switch_caller_profile_t *caller_profile = switch_channel_get_caller_profile(channel); + switch_hash_index_t *hi; + const void *var; + void *val; + switch_console_callback_match_t *matches = NULL; + switch_console_callback_match_node_t *m; + const char *uuid = NULL; + + if (globals.log_leg == ODBC_CDR_LOG_A && caller_profile->direction == SWITCH_CALL_DIRECTION_OUTBOUND) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Only logging A-Leg, ignoring B-leg\n"); + return SWITCH_STATUS_SUCCESS; + } else if (globals.log_leg == ODBC_CDR_LOG_B && caller_profile->direction == SWITCH_CALL_DIRECTION_INBOUND) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Only logging B-Leg, ignoring A-leg\n"); + return SWITCH_STATUS_SUCCESS; + } + + if (!(uuid = switch_channel_get_variable(channel, "uuid"))) { + uuid = switch_core_strdup(pool, caller_profile->uuid); + } + + // copy all table names from global hash + switch_mutex_lock(globals.mutex); + for (hi = switch_core_hash_first(globals.table_hash); hi; hi = switch_core_hash_next(&hi)) { + switch_core_hash_this(hi, &var, NULL, &val); + switch_console_push_match(&matches, (const char *) var); + } + switch_mutex_unlock(globals.mutex); + + if (matches) { + table_profile_t *table = NULL; + + // loop through table names + for (m = matches->head; m; m = m->next) { + char *table_name = m->val; + switch_bool_t started = SWITCH_FALSE; + switch_bool_t skip_leg = SWITCH_FALSE; + + switch_mutex_lock(globals.mutex); + table = switch_core_hash_find(globals.table_hash, table_name); + switch_mutex_unlock(globals.mutex); + + if (!table) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Table [%s] not found, ignoring leg\n", table_name); + skip_leg = SWITCH_TRUE; + } + + if (table->log_leg == ODBC_CDR_LOG_A && caller_profile->direction == SWITCH_CALL_DIRECTION_OUTBOUND) { + skip_leg = SWITCH_TRUE; + } + + if (table->log_leg == ODBC_CDR_LOG_B && caller_profile->direction == SWITCH_CALL_DIRECTION_INBOUND) { + skip_leg = SWITCH_TRUE; + } + + if (skip_leg == SWITCH_FALSE) { + switch_hash_index_t *i_hi = NULL; + const void *i_var; + void *i_val; + char *field_hash_key; + char *field_hash_val; + char *sql = NULL; + char *full_path = NULL; + switch_stream_handle_t stream_field = { 0 }; + switch_stream_handle_t stream_value = { 0 }; + switch_bool_t insert_fail = SWITCH_FALSE; + + SWITCH_STANDARD_STREAM(stream_field); + SWITCH_STANDARD_STREAM(stream_value); + + for (i_hi = switch_core_hash_first_iter( table->field_hash, i_hi); i_hi; i_hi = switch_core_hash_next(&i_hi)) { + const char *tmp; + switch_core_hash_this(i_hi, &i_var, NULL, &i_val); + field_hash_key = (char *) i_var; + field_hash_val = (char *) i_val; + + if ((tmp = switch_channel_get_variable(channel, field_hash_val))) { + if (started == SWITCH_FALSE) { + stream_field.write_function(&stream_field, "%s", field_hash_key); + stream_value.write_function(&stream_value, "'%s'", tmp); + } else { + stream_field.write_function(&stream_field, ", %s", field_hash_key); + stream_value.write_function(&stream_value, ", '%s'", tmp); + } + started = SWITCH_TRUE; + } + + } + switch_safe_free(i_hi); + + sql = switch_mprintf("INSERT INTO %s (%s) VALUES (%s)", table_name, stream_field.data, stream_value.data); + if (globals.debug_sql == SWITCH_TRUE) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "sql %s\n", sql); + } + if (odbc_cdr_execute_sql_no_callback(sql) == SWITCH_STATUS_FALSE) { + insert_fail = SWITCH_TRUE; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error executing query %s\n", sql); + } + + if (globals.write_csv == ODBC_CDR_CSV_ALWAYS) { + if (insert_fail == SWITCH_TRUE) { + full_path = switch_mprintf("%s%s%s.csv", globals.csv_fail_path, SWITCH_PATH_SEPARATOR, uuid); + } else { + full_path = switch_mprintf("%s%s%s.csv", globals.csv_path, SWITCH_PATH_SEPARATOR, uuid); + } + assert(full_path); + write_cdr(full_path, stream_value.data); + switch_safe_free(full_path); + } else if (globals.write_csv == ODBC_CDR_CSV_ON_FAIL && insert_fail == SWITCH_TRUE) { + full_path = switch_mprintf("%s%s%s.csv", globals.csv_fail_path, SWITCH_PATH_SEPARATOR, uuid); + assert(full_path); + write_cdr(full_path, stream_value.data); + switch_safe_free(full_path); + } + + switch_safe_free(sql); + + switch_safe_free(stream_field.data); + switch_safe_free(stream_value.data); + + } + + } + + switch_console_free_matches(&matches); + } + + switch_safe_free(hi); + + return SWITCH_STATUS_SUCCESS; +} + + +switch_state_handler_table_t odbc_cdr_state_handlers = { + /*.on_init */ NULL, + /*.on_routing */ NULL, + /*.on_execute */ NULL, + /*.on_hangup */ NULL, + /*.on_exchange_media */ NULL, + /*.on_soft_execute */ NULL, + /*.on_consume_media */ NULL, + /*.on_hibernate */ NULL, + /*.on_reset */ NULL, + /*.on_park */ NULL, + /*.on_reporting */ odbc_cdr_reporting, + /*.on_destroy */ NULL +}; + +static switch_status_t odbc_cdr_load_config(void) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_xml_t cfg, xml, settings, param, tables, table; + switch_cache_db_handle_t *dbh = NULL; + + switch_mutex_lock(globals.mutex); + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + status = SWITCH_STATUS_TERM; + goto end; + } + + globals.debug_sql = SWITCH_FALSE; + globals.log_leg = ODBC_CDR_LOG_BOTH; + globals.write_csv = ODBC_CDR_CSV_NEVER; + + if ((settings = switch_xml_child(cfg, "settings")) != NULL) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + if (zstr(var) || zstr(val)) { + continue; // Ignore empty entries + } + if (!strcasecmp(var, "dbname")) { + globals.dbname = strdup(val); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set dbname [%s]\n", globals.dbname); + } else if (!strcasecmp(var, "odbc-dsn")) { + globals.odbc_dsn = strdup(val); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set odbc-dsn [%s]\n", globals.odbc_dsn); + } else if (!strcasecmp(var, "log-leg")) { + if (!strcasecmp(val, "a-leg")) { + globals.log_leg = ODBC_CDR_LOG_A; + } else if (!strcasecmp(val, "b-leg")) { + globals.log_leg = ODBC_CDR_LOG_B; + } + } else if (!strcasecmp(var, "debug-sql") && switch_true(val)) { + globals.debug_sql = SWITCH_TRUE; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set debug-sql [true]\n"); + } else if (!strcasecmp(var, "write-csv") && !zstr(val)) { + if (!strcasecmp(val, "always")) { + globals.write_csv = ODBC_CDR_CSV_ALWAYS; + } else if (!strcasecmp(val, "on-db-fail")) { + globals.write_csv = ODBC_CDR_CSV_ON_FAIL; + } + } else if (!strcasecmp(var, "csv-path") && !zstr(val)) { + globals.csv_path = switch_mprintf("%s%s", val, SWITCH_PATH_SEPARATOR); + } else if (!strcasecmp(var, "csv-path-on-fail") && !zstr(val)) { + globals.csv_fail_path = switch_mprintf("%s%s", val, SWITCH_PATH_SEPARATOR); + } + } + } + + if (globals.log_leg == ODBC_CDR_LOG_A) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set log-leg [a-leg]\n"); + } else if (globals.log_leg == ODBC_CDR_LOG_B) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set log-leg [b-leg]\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set log-leg [both]\n"); + } + + if (!globals.csv_path) { + globals.csv_path = switch_mprintf("%s%sodbc-cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR); + } + + if (!globals.csv_fail_path) { + globals.csv_fail_path = switch_mprintf("%s%sodbc-cdr-failed", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set csv-path [%s]\n", globals.csv_path); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set csv-path-on-fail [%s]\n", globals.csv_fail_path); + + if ((tables = switch_xml_child(cfg, "tables"))) { + for (table = switch_xml_child(tables, "table"); table; table = table->next) { + load_table(switch_xml_attr_soft(table, "name")); + } + } + + if (!globals.dbname) { + globals.dbname = strdup(ODBC_CDR_SQLITE_DB_NAME); + } + + // Initialize database + if (!(dbh = get_db_handle())) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot open DB!\n"); + status = SWITCH_STATUS_TERM; + goto end; + } + + switch_cache_db_release_db_handle(&dbh); + +end: + switch_mutex_unlock(globals.mutex); + + if (xml) { + switch_xml_free(xml); + } + + return status; +} + + +SWITCH_MODULE_LOAD_FUNCTION(mod_odbc_cdr_load) +{ + switch_status_t status; + + memset(&globals, 0, sizeof(globals)); + switch_core_hash_init(&globals.table_hash); + if (switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to initialize mutex\n"); + } + globals.pool = pool; + + if ((status = odbc_cdr_load_config()) != SWITCH_STATUS_SUCCESS) { + return status; + } + + if (globals.write_csv != ODBC_CDR_CSV_NEVER) { + if ((status = switch_dir_make_recursive(globals.csv_path, SWITCH_DEFAULT_DIR_PERMS, pool)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s\n", globals.csv_path); + return status; + } + if (strcasecmp(globals.csv_path, globals.csv_fail_path)) { + if ((status = switch_dir_make_recursive(globals.csv_fail_path, SWITCH_DEFAULT_DIR_PERMS, pool)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s\n", globals.csv_path); + return status; + } + } + } + + switch_mutex_lock(globals.mutex); + globals.running = 1; + switch_mutex_unlock(globals.mutex); + + switch_core_add_state_handler(&odbc_cdr_state_handlers); + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/* + Called when the system shuts down + Macro expands to: switch_status_t mod_odbc_cdr_shutdown() */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_odbc_cdr_shutdown) +{ + switch_hash_index_t *hi = NULL; + table_profile_t *table; + void *val = NULL; + const void *key; + switch_ssize_t keylen; + + switch_mutex_lock(globals.mutex); + if (globals.running == 1) { + globals.running = 0; + } + + while ((hi = switch_core_hash_first_iter(globals.table_hash, hi))) { + switch_hash_index_t *field_hi = NULL; + void *field_val = NULL; + const void *field_key; + switch_ssize_t field_keylen; + + switch_core_hash_this(hi, &key, &keylen, &val); + table = (table_profile_t *) val; + + while ((field_hi = switch_core_hash_first_iter(table->field_hash, field_hi))) { + switch_core_hash_this(field_hi, &field_key, &field_keylen, &field_val); + switch_core_hash_delete_locked(table->field_hash, field_key, table->mutex); + } + switch_core_hash_destroy(&table->field_hash); + switch_safe_free(field_hi); + + switch_core_hash_delete(globals.table_hash, table->name); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying table %s\n", table->name); + + switch_core_destroy_memory_pool(&table->pool); + table = NULL; + } + switch_core_hash_destroy(&globals.table_hash); + switch_safe_free(hi); + + switch_safe_free(globals.csv_path) + switch_safe_free(globals.csv_fail_path) + switch_safe_free(globals.odbc_dsn); + switch_safe_free(globals.dbname); + + switch_mutex_unlock(globals.mutex); + switch_mutex_destroy(globals.mutex); + + switch_core_remove_state_handler(&odbc_cdr_state_handlers); + + return SWITCH_STATUS_SUCCESS; +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */ From 94278b5e545b58bad784a95da6181fc5f299457f Mon Sep 17 00:00:00 2001 From: Hristo Trendev Date: Wed, 24 Sep 2014 14:25:39 +0200 Subject: [PATCH 02/12] allow enter and exit sounds to interrupt the MOH in a wait_mod conference This patch does the following: * only starts MOH if no other file (sync or async) is currently playing * adds a variable "conference_permanent_wait_mod_moh" that controls the behavior of how the enter and exit sounds interact with the MOH when wait_mod is set. When the variable is set, the MOH keeps playing and the enter and exit sounds are mixed with the MOH. When the variable is unset, then any playing MOH is first stopped, then the enter or exit sound is played and the MOH is started again. This functionality is useful in case the enter and exit sounds are used to announce the name of the caller, who is joining or leaving a conference. FS-5159 #resolve --- .../mod_conference/mod_conference.c | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c index 1ea3045de6..38b2f893d4 100644 --- a/src/mod/applications/mod_conference/mod_conference.c +++ b/src/mod/applications/mod_conference/mod_conference.c @@ -2277,7 +2277,8 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe } if (conference->count > 1) { - if (conference->moh_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD)) { + if ((conference->moh_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD)) || + (switch_test_flag(conference, CFLAG_WAIT_MOD) && !switch_true(switch_channel_get_variable(channel, "conference_permanent_wait_mod_moh")))) { /* stop MoH if any */ conference_stop_file(conference, FILE_STOP_ASYNC); } @@ -2287,10 +2288,9 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe if (switch_test_flag(conference, CFLAG_ENTER_SOUND)) { if (!zstr(enter_sound)) { conference_play_file(conference, (char *)enter_sound, CONF_DEFAULT_LEADIN, - switch_core_session_get_channel(member->session), !switch_test_flag(conference, CFLAG_WAIT_MOD) ? 0 : 1); + switch_core_session_get_channel(member->session), 0); } else { - conference_play_file(conference, conference->enter_sound, CONF_DEFAULT_LEADIN, switch_core_session_get_channel(member->session), - !switch_test_flag(conference, CFLAG_WAIT_MOD) ? 0 : 1); + conference_play_file(conference, conference->enter_sound, CONF_DEFAULT_LEADIN, switch_core_session_get_channel(member->session), 0); } } } @@ -2316,7 +2316,7 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe if (conference->alone_sound && !switch_test_flag(member, MFLAG_GHOST)) { conference_stop_file(conference, FILE_STOP_ASYNC); conference_play_file(conference, conference->alone_sound, CONF_DEFAULT_LEADIN, - switch_core_session_get_channel(member->session), 1); + switch_core_session_get_channel(member->session), 0); } else { switch_snprintf(msg, sizeof(msg), "You are currently the only person in this conference."); conference_member_say(member, msg, CONF_DEFAULT_LEADIN); @@ -2683,7 +2683,7 @@ static switch_status_t conference_del_member(conference_obj_t *conference, confe if (member->session && (exit_sound = switch_channel_get_variable(switch_core_session_get_channel(member->session), "conference_exit_sound"))) { conference_play_file(conference, (char *)exit_sound, CONF_DEFAULT_LEADIN, - switch_core_session_get_channel(member->session), !switch_test_flag(conference, CFLAG_WAIT_MOD) ? 0 : 1); + switch_core_session_get_channel(member->session), 0); } @@ -2786,12 +2786,16 @@ static switch_status_t conference_del_member(conference_obj_t *conference, confe || (switch_test_flag(conference, CFLAG_DYNAMIC) && (conference->count + conference->count_ghosts == 0))) { switch_set_flag(conference, CFLAG_DESTRUCT); } else { + if (!switch_true(switch_channel_get_variable(channel, "conference_permanent_wait_mod_moh")) && switch_test_flag(conference, CFLAG_WAIT_MOD)) { + /* Stop MOH if any */ + conference_stop_file(conference, FILE_STOP_ASYNC); + } if (!exit_sound && conference->exit_sound && switch_test_flag(conference, CFLAG_EXIT_SOUND)) { conference_play_file(conference, conference->exit_sound, 0, channel, 0); } if (conference->count == 1 && conference->alone_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD) && !switch_test_flag(member, MFLAG_GHOST)) { conference_stop_file(conference, FILE_STOP_ASYNC); - conference_play_file(conference, conference->alone_sound, 0, channel, 1); + conference_play_file(conference, conference->alone_sound, 0, channel, 0); } } @@ -3146,7 +3150,7 @@ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, v if (conference->perpetual_sound && !conference->async_fnode) { conference_play_file(conference, conference->perpetual_sound, CONF_DEFAULT_LEADIN, NULL, 1); } else if (conference->moh_sound && ((nomoh == 0 && conference->count == 1) - || switch_test_flag(conference, CFLAG_WAIT_MOD)) && !conference->async_fnode) { + || switch_test_flag(conference, CFLAG_WAIT_MOD)) && !conference->async_fnode && !conference->fnode) { conference_play_file(conference, conference->moh_sound, CONF_DEFAULT_LEADIN, NULL, 1); } From 1944f9a5ee63ec51bed1bfb900072d168a81d004 Mon Sep 17 00:00:00 2001 From: Aaron Paolozzi Date: Thu, 6 Nov 2014 19:21:58 -0500 Subject: [PATCH 03/12] FS-6968 Changes to mod_fifo.c to add outbound_per_cycle_min --- src/mod/applications/mod_fifo/mod_fifo.c | 40 +++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/mod/applications/mod_fifo/mod_fifo.c b/src/mod/applications/mod_fifo/mod_fifo.c index ac0d1167b7..d6877ef8dc 100644 --- a/src/mod/applications/mod_fifo/mod_fifo.c +++ b/src/mod/applications/mod_fifo/mod_fifo.c @@ -79,6 +79,15 @@ SWITCH_MODULE_DEFINITION(mod_fifo, mod_fifo_load, mod_fifo_shutdown, NULL); * The /enterprise/ outbound strategy does not preserve the caller ID * of the caller thereby allowing deliver of callers to agents at the * fastest possible rate. + * + * outbound_per_cycle is used to define the maximum number of agents + * who will be available to answer a single caller. In ringall this + * maximum is the number who will be called, in enterprise the need defines + * how many agents will be called. outbound_per_cycle_min will define + * the minimum agents who will be called to answer a caller regardless of + * need, giving the enterprise strategy the ability to ring through more + * than one agent for one caller. + * * ## Manual calls * @@ -391,6 +400,7 @@ struct fifo_node { long busy; int is_static; int outbound_per_cycle; + int outbound_per_cycle_min; char *outbound_name; outbound_strategy_t outbound_strategy; int ring_timeout; @@ -1985,6 +1995,21 @@ static int place_call_enterprise_callback(void *pArg, int argc, char **argv, cha * the results. The enterprise strategy handler can simply take each * member one at a time, so the `place_call_enterprise_callback` takes * care of invoking the handler. + * + * Within the ringall call strategy outbound_per_cycle is used to define + * how many agents exactly are assigned to the caller. With ringall if + * multiple callers are calling in and one is answered, because the call + * is assigned to all agents the call to the agents that is not answered + * will be lose raced and the other agents will drop the call before the + * next one will begin to ring. When oubound_per_cycle is used in the + * enterprise strategy it acts as a maximum value for how many agents + * are rung at once on any call, the caller is not assigned to any agent + * until the call is answered. Enterprise only rings the number of phones + * that are needed, so outbound_per_cycle as a max does not give you the + * effect of ringall. outbound_per_cycle_min defines how many agents minimum + * will be rung by an incoming caller through fifo, which can give a ringall + * effect. outbound_per_cycle and outbound_per_cycle_min both default to 1. + * */ static void find_consumers(fifo_node_t *node) { @@ -2005,6 +2030,8 @@ static void find_consumers(fifo_node_t *node) if (node->outbound_per_cycle && node->outbound_per_cycle < need) { need = node->outbound_per_cycle; + } else if (node->outbound_per_cycle_min && node->outbound_per_cycle_min > need) { + need = node->outbound_per_cycle_min; } fifo_execute_sql_callback(globals.sql_mutex, sql, place_call_enterprise_callback, &need); @@ -4045,6 +4072,9 @@ static void list_node(fifo_node_t *node, switch_xml_t x_report, int *off, int ve switch_snprintf(tmp, sizeof(buffer), "%u", node->outbound_per_cycle); switch_xml_set_attr_d(x_fifo, "outbound_per_cycle", tmp); + switch_snprintf(tmp, sizeof(buffer), "%u", node->outbound_per_cycle_min); + switch_xml_set_attr_d(x_fifo, "outbound_per_cycle_min", tmp); + switch_snprintf(tmp, sizeof(buffer), "%u", node->ring_timeout); switch_xml_set_attr_d(x_fifo, "ring_timeout", tmp); @@ -4088,6 +4118,7 @@ void node_dump(switch_stream_handle_t *stream) stream->write_function(stream, "node: %s\n" " outbound_name: %s\n" " outbound_per_cycle: %d" + " outbound_per_cycle_min: %d" " outbound_priority: %d" " outbound_strategy: %s\n" " has_outbound: %d\n" @@ -4096,7 +4127,7 @@ void node_dump(switch_stream_handle_t *stream) " ready: %d\n" " waiting: %d\n" , - node->name, node->outbound_name, node->outbound_per_cycle, + node->name, node->outbound_name, node->outbound_per_cycle, node->outbound_per_cycle_min, node->outbound_priority, print_strategy(node->outbound_strategy), node->has_outbound, node->outbound_priority, @@ -4508,6 +4539,13 @@ static switch_status_t load_config(int reload, int del_all) node->has_outbound = 1; } + node->outbound_per_cycle_min = 1; + if ((val = switch_xml_attr(fifo, "outbound_per_cycle_min"))) { + if (!((i = atoi(val)) < 0)) { + node->outbound_per_cycle_min = i; + } + } + if ((val = switch_xml_attr(fifo, "retry_delay"))) { if ((i = atoi(val)) < 0) i = 0; node->retry_delay = i; From 08ff88ec31572071c5e5dbe6fad51578814c4bd9 Mon Sep 17 00:00:00 2001 From: Brian West Date: Fri, 7 Nov 2014 07:26:31 -0600 Subject: [PATCH 04/12] Might needs this for testing. --- scripts/perl/mkgws.pl | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 scripts/perl/mkgws.pl diff --git a/scripts/perl/mkgws.pl b/scripts/perl/mkgws.pl new file mode 100644 index 0000000000..6743c969d0 --- /dev/null +++ b/scripts/perl/mkgws.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl +# +# Make bulk gateway xml from csv file. +# + +open(CSV, "gateways.csv"); +my @data = ; +close(CSV); + +foreach my $line (@data) { + chomp($line); + my ($gwname, $username, $password) = split(/,/, $line); + print < + + + + + + + + + + + + + + + + + + + + +XML + +} From 5ce5199be9b7255ebcbfae7e3e3f62a8eb914310 Mon Sep 17 00:00:00 2001 From: Anthony Minessale Date: Fri, 7 Nov 2014 08:37:53 -0600 Subject: [PATCH 05/12] FS-6969 #resolve #comment This patch should accomplish the same and handle other platforms, please test --- src/include/switch_utils.h | 22 ++++++++++++++++++++++ src/switch_utils.c | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/include/switch_utils.h b/src/include/switch_utils.h index e8b6843d14..523e3a4768 100644 --- a/src/include/switch_utils.h +++ b/src/include/switch_utils.h @@ -541,6 +541,28 @@ SWITCH_DECLARE(int) switch_build_uri(char *uri, switch_size_t size, const char * #define SWITCH_STATUS_IS_BREAK(x) (x == SWITCH_STATUS_BREAK || x == 730035 || x == 35 || x == SWITCH_STATUS_INTR) + +#ifdef _MSC_VER + +#define switch_errno() WSAGetLastError() + +static inline int switch_errno_is_break(int errcode) +{ + return errcode == WSAEWOULDBLOCK || errcode == WSAEINPROGRESS || errcode == WSAEINTR; +} + +#else + +#define switch_errno() errno + +static inline int switch_errno_is_break(int errcode) +{ + return errcode == EAGAIN || errcode == EWOULDBLOCK || errcode == EINPROGRESS || errcode == EINTR || errcode == ETIMEDOUT; +} + +#endif + + /*! \brief Return a printable name of a switch_priority_t \param priority the priority to get the name of diff --git a/src/switch_utils.c b/src/switch_utils.c index 3cde97a127..444333ec12 100644 --- a/src/switch_utils.c +++ b/src/switch_utils.c @@ -2567,6 +2567,12 @@ SWITCH_DECLARE(int) switch_wait_sock(switch_os_socket_t sock, uint32_t ms, switc s = poll(pfds, 1, ms); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { @@ -2645,6 +2651,12 @@ SWITCH_DECLARE(int) switch_wait_socklist(switch_waitlist_t *waitlist, uint32_t l s = poll(pfds, len, ms); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { @@ -2758,6 +2770,12 @@ SWITCH_DECLARE(int) switch_wait_sock(switch_os_socket_t sock, uint32_t ms, switc s = select(sock + 1, (flags & SWITCH_POLL_READ) ? rfds : NULL, (flags & SWITCH_POLL_WRITE) ? wfds : NULL, (flags & SWITCH_POLL_ERROR) ? efds : NULL, &tv); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { @@ -2858,6 +2876,12 @@ SWITCH_DECLARE(int) switch_wait_socklist(switch_waitlist_t *waitlist, uint32_t l s = select(max_fd + 1, (flags & SWITCH_POLL_READ) ? rfds : NULL, (flags & SWITCH_POLL_WRITE) ? wfds : NULL, (flags & SWITCH_POLL_ERROR) ? efds : NULL, &tv); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { From e3a647810c8e90e5b4dc379521a9a3275d27d71e Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Mon, 15 Sep 2014 20:49:42 +0200 Subject: [PATCH 06/12] debian: Allow use of secondary groups When '-g' is passed, freeswitch drops all other groups except for the given group. This impacts people who depend on FS having access to resources that would be allowed by membership to those other groups. It was possible to override this by setting DAEMON_ARGS in /etc/default/freeswitch, but we'll go ahead and make this the default. Since freeswitch uses the primary group of a user when `-g` is omitted, we'll just omit it, and do similarly when setting the ownership of our directory in /var/run. Edited-by: Travis Cross --- debian/freeswitch-sysvinit.freeswitch.init | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/debian/freeswitch-sysvinit.freeswitch.init b/debian/freeswitch-sysvinit.freeswitch.init index f393ff48a6..98be1af324 100644 --- a/debian/freeswitch-sysvinit.freeswitch.init +++ b/debian/freeswitch-sysvinit.freeswitch.init @@ -19,8 +19,7 @@ DESC=freeswitch NAME=freeswitch DAEMON=/usr/bin/freeswitch USER=freeswitch -GROUP=freeswitch -DAEMON_ARGS="-u $USER -g $GROUP -ncwait" +DAEMON_ARGS="-u $USER -ncwait" CONFDIR=/etc/$NAME RUNDIR=/var/run/$NAME PIDFILE=$RUNDIR/$NAME.pid @@ -43,7 +42,7 @@ do_start() { # Directory in /var/run may disappear on reboot (e.g. when tmpfs used for /var/run). mkdir -p $RUNDIR - chown -R $USER:$GROUP $RUNDIR + chown -R $USER: $RUNDIR chmod -R ug=rwX,o= $RUNDIR start-stop-daemon --start --quiet \ From 070aaefaeba3232023286983833acd309022521a Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 7 Nov 2014 18:17:46 +0000 Subject: [PATCH 07/12] Fix whitespace inconsistency --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f4ba7ac252..a11de35c84 100644 --- a/configure.ac +++ b/configure.ac @@ -1587,7 +1587,7 @@ AC_CONFIG_FILES([Makefile src/mod/say/mod_say_de/Makefile src/mod/say/mod_say_en/Makefile src/mod/say/mod_say_es/Makefile - src/mod/say/mod_say_es_ar/Makefile + src/mod/say/mod_say_es_ar/Makefile src/mod/say/mod_say_fa/Makefile src/mod/say/mod_say_fr/Makefile src/mod/say/mod_say_he/Makefile From 185e6ec5e185f1e68426798d61102f822d108d74 Mon Sep 17 00:00:00 2001 From: Chris Rienzo Date: Fri, 7 Nov 2014 13:22:22 -0500 Subject: [PATCH 08/12] RPM packaging - add mongo C driver to list of sources --- freeswitch.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/freeswitch.spec b/freeswitch.spec index 08596a8f4f..ca95e20657 100644 --- a/freeswitch.spec +++ b/freeswitch.spec @@ -129,6 +129,7 @@ Source10: http://files.freeswitch.org/downloads/libs/libmemcached-0.32.tar.gz Source11: http://files.freeswitch.org/downloads/libs/json-c-0.9.tar.gz Source12: http://files.freeswitch.org/downloads/libs/opus-1.1-p2.tar.gz Source13: http://files.freeswitch.org/downloads/libs/v8-3.24.14.tar.bz2 +Source14: http://files.freeswitch.org/downloads/libs/mongo-c-driver-0.92.2.tar.gz Prefix: %{prefix} From 51f61c78342be6bfbab2c5719772874ded389510 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 30 Jul 2014 19:20:08 +0200 Subject: [PATCH 09/12] debian: Remove duplicate clean command `dh clean` invokes `dh_testdir`, `dh_auto_clean` and `dh_clean`. We don't need to invoke dh_clean twice. Acked-by: Travis Cross --- debian/rules | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/rules b/debian/rules index eefc3bc277..cf6a7ed1ea 100755 --- a/debian/rules +++ b/debian/rules @@ -55,7 +55,6 @@ clean: dh $@ override_dh_auto_clean: - dh_clean .stamp-bootstrap: @$(call show_vars) From f4527d77cb41e4cb74798f6a92861dc87d6d1c6b Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 7 Nov 2014 18:52:11 +0000 Subject: [PATCH 10/12] Fix placement of build-dep for libsngtc-dev Since we compare our generated control-modules to the one in tree, we want to match the exact format we use to generate the file. --- debian/control-modules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control-modules b/debian/control-modules index a494304f6e..c025bd071e 100644 --- a/debian/control-modules +++ b/debian/control-modules @@ -333,9 +333,9 @@ Description: mod_opus Adds mod_opus. Module: codecs/mod_sangoma_codec -Build-Depends: libsngtc-dev Description: mod_sangoma_codec Adds mod_sangoma_codec. +Build-Depends: libsngtc-dev Module: codecs/mod_silk Description: mod_silk From 4b76c2aea9da5ac1e30e248095faeed5e380fff9 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 7 Nov 2014 18:53:33 +0000 Subject: [PATCH 11/12] Add mod_odbc_cdr to debian packaging --- debian/control-modules | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/debian/control-modules b/debian/control-modules index c025bd071e..64ca7c0363 100644 --- a/debian/control-modules +++ b/debian/control-modules @@ -500,6 +500,10 @@ Module: event_handlers/mod_json_cdr Description: mod_json_cdr Adds mod_json_cdr. +Module: event_handlers/mod_odbc_cdr +Description: mod_odbc_cdr + Adds mod_odbc_cdr. + Module: event_handlers/mod_radius_cdr Description: mod_radius_cdr Adds mod_radius_cdr. From ebb3c8fbfa3ec8b9f4b4a2ef20d62d343c8ff6c4 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 7 Nov 2014 18:53:46 +0000 Subject: [PATCH 12/12] Add mod_say_es_ar to debian packaging --- debian/control-modules | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/debian/control-modules b/debian/control-modules index 64ca7c0363..84b7d8d140 100644 --- a/debian/control-modules +++ b/debian/control-modules @@ -634,6 +634,10 @@ Module: say/mod_say_es Description: mod_say_es Adds mod_say_es. +Module: say/mod_say_es_ar +Description: mod_say_es_ar + Adds mod_say_es_ar. + Module: say/mod_say_fa Description: mod_say_fa Adds mod_say_fa.