From d66839debe4ca57eaff4d477d239f88a3e30c60d Mon Sep 17 00:00:00 2001 From: karl anderson Date: Thu, 2 Apr 2015 18:46:23 -0400 Subject: [PATCH] FS-7774 add mod_kazoo #resolve --- build/modules.conf.in | 1 + configure.ac | 1 + src/mod/event_handlers/mod_kazoo/Makefile.am | 7 + .../conf/autoload_configs/kazoo.conf.xml | 215 +++ .../event_handlers/mod_kazoo/kazoo_commands.c | 152 ++ .../event_handlers/mod_kazoo/kazoo_dptools.c | 182 +++ .../mod_kazoo/kazoo_event_stream.c | 584 ++++++++ .../mod_kazoo/kazoo_fetch_agent.c | 707 ++++++++++ src/mod/event_handlers/mod_kazoo/kazoo_node.c | 1254 +++++++++++++++++ .../event_handlers/mod_kazoo/kazoo_utils.c | 614 ++++++++ src/mod/event_handlers/mod_kazoo/mod_kazoo.c | 776 ++++++++++ src/mod/event_handlers/mod_kazoo/mod_kazoo.h | 171 +++ 12 files changed, 4664 insertions(+) create mode 100644 src/mod/event_handlers/mod_kazoo/Makefile.am create mode 100644 src/mod/event_handlers/mod_kazoo/conf/autoload_configs/kazoo.conf.xml create mode 100644 src/mod/event_handlers/mod_kazoo/kazoo_commands.c create mode 100644 src/mod/event_handlers/mod_kazoo/kazoo_dptools.c create mode 100644 src/mod/event_handlers/mod_kazoo/kazoo_event_stream.c create mode 100644 src/mod/event_handlers/mod_kazoo/kazoo_fetch_agent.c create mode 100644 src/mod/event_handlers/mod_kazoo/kazoo_node.c create mode 100644 src/mod/event_handlers/mod_kazoo/kazoo_utils.c create mode 100644 src/mod/event_handlers/mod_kazoo/mod_kazoo.c create mode 100644 src/mod/event_handlers/mod_kazoo/mod_kazoo.h diff --git a/build/modules.conf.in b/build/modules.conf.in index b95f20029e..dff299fb68 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -105,6 +105,7 @@ event_handlers/mod_event_socket #event_handlers/mod_json_cdr #event_handlers/mod_radius_cdr #event_handlers/mod_odbc_cdr +#event_handlers/mod_kazoo #event_handlers/mod_rayo #event_handlers/mod_smpp #event_handlers/mod_snmp diff --git a/configure.ac b/configure.ac index 3b58e14685..1fb30e18c6 100644 --- a/configure.ac +++ b/configure.ac @@ -1772,6 +1772,7 @@ AC_CONFIG_FILES([Makefile src/mod/event_handlers/mod_event_test/Makefile src/mod/event_handlers/mod_format_cdr/Makefile src/mod/event_handlers/mod_json_cdr/Makefile + src/mod/event_handlers/mod_kazoo/Makefile src/mod/event_handlers/mod_radius_cdr/Makefile src/mod/event_handlers/mod_odbc_cdr/Makefile src/mod/event_handlers/mod_rayo/Makefile diff --git a/src/mod/event_handlers/mod_kazoo/Makefile.am b/src/mod/event_handlers/mod_kazoo/Makefile.am new file mode 100644 index 0000000000..86d556ba77 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/Makefile.am @@ -0,0 +1,7 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_kazoo +mod_LTLIBRARIES = mod_kazoo.la +mod_kazoo_la_SOURCES = mod_kazoo.c kazoo_utils.c kazoo_node.c kazoo_event_stream.c kazoo_fetch_agent.c kazoo_commands.c kazoo_dptools.c +mod_kazoo_la_CFLAGS = $(AM_CFLAGS) @ERLANG_CFLAGS@ -D_REENTRANT +mod_kazoo_la_LIBADD = $(switch_builddir)/libfreeswitch.la +mod_kazoo_la_LDFLAGS = -avoid-version -module -no-undefined -shared @ERLANG_LDFLAGS@ diff --git a/src/mod/event_handlers/mod_kazoo/conf/autoload_configs/kazoo.conf.xml b/src/mod/event_handlers/mod_kazoo/conf/autoload_configs/kazoo.conf.xml new file mode 100644 index 0000000000..b730523d4c --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/conf/autoload_configs/kazoo.conf.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + diff --git a/src/mod/event_handlers/mod_kazoo/kazoo_commands.c b/src/mod/event_handlers/mod_kazoo/kazoo_commands.c new file mode 100644 index 0000000000..ab9b65386b --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/kazoo_commands.c @@ -0,0 +1,152 @@ +/* + * 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): + * + * Karl Anderson + * Darren Schreiber + * + * + * kazoo_commands.c -- clones of mod_commands commands slightly modified for kazoo + * + */ +#include "mod_kazoo.h" + +#define UUID_SET_DESC "Set a variable" +#define UUID_SET_SYNTAX " [value]" + +#define UUID_MULTISET_DESC "Set multiple variables" +#define UUID_MULTISET_SYNTAX " =;=..." + +SWITCH_STANDARD_API(uuid_setvar_function) { + switch_core_session_t *psession = NULL; + char *mycmd = NULL, *argv[3] = { 0 }; + int argc = 0; + + if (!zstr(cmd) && (mycmd = strdup(cmd))) { + argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + if ((argc == 2 || argc == 3) && !zstr(argv[0])) { + char *uuid = argv[0]; + char *var_name = argv[1]; + char *var_value = NULL; + + if (argc == 3) { + var_value = argv[2]; + } + + if ((psession = switch_core_session_locate(uuid))) { + switch_channel_t *channel; + switch_event_t *event; + channel = switch_core_session_get_channel(psession); + + if (zstr(var_name)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No variable name specified.\n"); + stream->write_function(stream, "-ERR No variable specified\n"); + } else { + switch_channel_set_variable(channel, var_name, var_value); + stream->write_function(stream, "+OK\n"); + } + + if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) { + switch_channel_event_set_data(channel, event); + switch_event_fire(&event); + } + + switch_core_session_rwunlock(psession); + + } else { + stream->write_function(stream, "-ERR No such channel!\n"); + } + goto done; + } + } + + stream->write_function(stream, "-USAGE: %s\n", UUID_SET_SYNTAX); + + done: + switch_safe_free(mycmd); + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_STANDARD_API(uuid_setvar_multi_function) { + switch_core_session_t *psession = NULL; + char *mycmd = NULL, *vars, *argv[64] = { 0 }; + int argc = 0; + char *var_name, *var_value = NULL; + + if (!zstr(cmd) && (mycmd = strdup(cmd))) { + char *uuid = mycmd; + if (!(vars = strchr(uuid, ' '))) { + goto done; + } + *vars++ = '\0'; + + if ((psession = switch_core_session_locate(uuid))) { + switch_channel_t *channel = switch_core_session_get_channel(psession); + switch_event_t *event; + int x, y = 0; + argc = switch_separate_string(vars, ';', argv, (sizeof(argv) / sizeof(argv[0]))); + + for (x = 0; x < argc; x++) { + var_name = argv[x]; + if (var_name && (var_value = strchr(var_name, '='))) { + *var_value++ = '\0'; + } + if (zstr(var_name)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No variable name specified.\n"); + stream->write_function(stream, "-ERR No variable specified\n"); + } else { + switch_channel_set_variable(channel, var_name, var_value); + y++; + } + } + + /* keep kazoo nodes in sync */ + if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) { + switch_channel_event_set_data(channel, event); + switch_event_fire(&event); + } + + switch_core_session_rwunlock(psession); + if (y) { + stream->write_function(stream, "+OK\n"); + goto done; + } + } else { + stream->write_function(stream, "-ERR No such channel!\n"); + } + } + + stream->write_function(stream, "-USAGE: %s\n", UUID_MULTISET_SYNTAX); + + done: + switch_safe_free(mycmd); + return SWITCH_STATUS_SUCCESS; +} + +void add_kz_commands(switch_loadable_module_interface_t **module_interface, switch_api_interface_t *api_interface) { + SWITCH_ADD_API(api_interface, "kz_uuid_setvar_multi", UUID_SET_DESC, uuid_setvar_multi_function, UUID_MULTISET_SYNTAX); + switch_console_set_complete("add kz_uuid_setvar_multi ::console::list_uuid"); + SWITCH_ADD_API(api_interface, "kz_uuid_setvar", UUID_MULTISET_DESC, uuid_setvar_function, UUID_SET_SYNTAX); + switch_console_set_complete("add kz_uuid_setvar ::console::list_uuid"); +} diff --git a/src/mod/event_handlers/mod_kazoo/kazoo_dptools.c b/src/mod/event_handlers/mod_kazoo/kazoo_dptools.c new file mode 100644 index 0000000000..887b1ea32b --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/kazoo_dptools.c @@ -0,0 +1,182 @@ +/* + * 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): + * + * Karl Anderson + * Darren Schreiber + * + * + * kazoo_dptools.c -- clones of mod_dptools commands slightly modified for kazoo + * + */ +#include "mod_kazoo.h" + +#define SET_SHORT_DESC "Set a channel variable" +#define SET_LONG_DESC "Set a channel variable for the channel calling the application." +#define SET_SYNTAX "=" + +#define MULTISET_SHORT_DESC "Set many channel variables" +#define MULTISET_LONG_DESC "Set many channel variables for the channel calling the application" +#define MULTISET_SYNTAX "[^^]= =" + +#define UNSET_SHORT_DESC "Unset a channel variable" +#define UNSET_LONG_DESC "Unset a channel variable for the channel calling the application." +#define UNSET_SYNTAX "" + +#define MULTIUNSET_SHORT_DESC "Unset many channel variables" +#define MULTIUNSET_LONG_DESC "Unset many channel variables for the channel calling the application." +#define MULTIUNSET_SYNTAX "[^^] " + +static void base_set (switch_core_session_t *session, const char *data, switch_stack_t stack) { + char *var, *val = NULL; + + if (zstr(data)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No variable name specified.\n"); + } else { + switch_channel_t *channel = switch_core_session_get_channel(session); + char *expanded = NULL; + + var = switch_core_session_strdup(session, data); + + if (!(val = strchr(var, '='))) { + val = strchr(var, ','); + } + + if (val) { + *val++ = '\0'; + if (zstr(val)) { + val = NULL; + } + } + + if (val) { + expanded = switch_channel_expand_variables(channel, val); + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s SET [%s]=[%s]\n", switch_channel_get_name(channel), var, + expanded ? expanded : "UNDEF"); + switch_channel_add_variable_var_check(channel, var, expanded, SWITCH_FALSE, stack); + + if (expanded && expanded != val) { + switch_safe_free(expanded); + } + } +} + +SWITCH_STANDARD_APP(multiset_function) { + char delim = ' '; + char *arg = (char *) data; + switch_event_t *event; + + if (!zstr(arg) && *arg == '^' && *(arg+1) == '^') { + arg += 2; + delim = *arg++; + } + + if (arg) { + switch_channel_t *channel = switch_core_session_get_channel(session); + char *array[256] = {0}; + int i, argc; + + arg = switch_core_session_strdup(session, arg); + argc = switch_split(arg, delim, array); + + for(i = 0; i < argc; i++) { + base_set(session, array[i], SWITCH_STACK_BOTTOM); + } + + if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) { + switch_channel_event_set_data(channel, event); + switch_event_fire(&event); + } + + } else { + base_set(session, data, SWITCH_STACK_BOTTOM); + } +} + +SWITCH_STANDARD_APP(set_function) { + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_event_t *event; + + base_set(session, data, SWITCH_STACK_BOTTOM); + + if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) { + switch_channel_event_set_data(channel, event); + switch_event_fire(&event); + } +} + +SWITCH_STANDARD_APP(unset_function) { + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_event_t *event; + + if (zstr(data)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No variable name specified.\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "UNSET [%s]\n", (char *) data); + switch_channel_set_variable(switch_core_session_get_channel(session), data, NULL); + } + + if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) { + switch_channel_event_set_data(channel, event); + switch_event_fire(&event); + } +} + +SWITCH_STANDARD_APP(multiunset_function) { + char delim = ' '; + char *arg = (char *) data; + + if (!zstr(arg) && *arg == '^' && *(arg+1) == '^') { + arg += 2; + delim = *arg++; + } + + if (arg) { + char *array[256] = {0}; + int i, argc; + + arg = switch_core_session_strdup(session, arg); + argc = switch_split(arg, delim, array); + + for(i = 0; i < argc; i++) { + switch_channel_set_variable(switch_core_session_get_channel(session), array[i], NULL); + } + + } else { + switch_channel_set_variable(switch_core_session_get_channel(session), arg, NULL); + } +} + +void add_kz_dptools(switch_loadable_module_interface_t **module_interface, switch_application_interface_t *app_interface) { + SWITCH_ADD_APP(app_interface, "kz_set", SET_SHORT_DESC, SET_LONG_DESC, set_function, SET_SYNTAX, + SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC | SAF_ZOMBIE_EXEC); + SWITCH_ADD_APP(app_interface, "kz_multiset", MULTISET_SHORT_DESC, MULTISET_LONG_DESC, multiset_function, MULTISET_SYNTAX, + SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC | SAF_ZOMBIE_EXEC); + SWITCH_ADD_APP(app_interface, "kz_unset", UNSET_SHORT_DESC, UNSET_LONG_DESC, unset_function, UNSET_SYNTAX, + SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC | SAF_ZOMBIE_EXEC); + SWITCH_ADD_APP(app_interface, "kz_multiunset", MULTISET_SHORT_DESC, MULTISET_LONG_DESC, multiunset_function, MULTIUNSET_SYNTAX, + SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC | SAF_ZOMBIE_EXEC); +} diff --git a/src/mod/event_handlers/mod_kazoo/kazoo_event_stream.c b/src/mod/event_handlers/mod_kazoo/kazoo_event_stream.c new file mode 100644 index 0000000000..00eb06a126 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/kazoo_event_stream.c @@ -0,0 +1,584 @@ +/* + * 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): + * + * Karl Anderson + * Darren Schreiber + * + * + * kazoo_event_streams.c -- Event Publisher + * + */ +#include "mod_kazoo.h" + +/* Blatantly repurposed from switch_eventc */ +static char *my_dup(const char *s) { + size_t len = strlen(s) + 1; + void *new = malloc(len); + switch_assert(new); + + return (char *) memcpy(new, s, len); +} + +#ifndef DUP +#define DUP(str) my_dup(str) +#endif + +static const char* private_headers[] = {"variable_sip_h_", "sip_h_", "P-", "X-"}; + +static int is_private_header(const char *name) { + for(int i=0; i < 4; i++) { + if(!strncmp(name, private_headers[i], strlen(private_headers[i]))) { + return 1; + } + } + return 0; +} + +static switch_status_t kazoo_event_dup(switch_event_t **clone, switch_event_t *event, switch_hash_t *filter) { + switch_event_header_t *header; + + if (switch_event_create_subclass(clone, SWITCH_EVENT_CLONE, event->subclass_name) != SWITCH_STATUS_SUCCESS) { + return SWITCH_STATUS_GENERR; + } + + (*clone)->event_id = event->event_id; + (*clone)->event_user_data = event->event_user_data; + (*clone)->bind_user_data = event->bind_user_data; + (*clone)->flags = event->flags; + + for (header = event->headers; header; header = header->next) { + if (event->subclass_name && !strcmp(header->name, "Event-Subclass")) { + continue; + } + + if (strncmp(header->name, globals.kazoo_var_prefix, globals.var_prefix_length) + && filter + && !switch_core_hash_find(filter, header->name) + && (!globals.send_all_headers) + && (!(globals.send_all_private_headers && is_private_header(header->name))) + ) + { + continue; + } + + if (header->idx) { + int i; + for (i = 0; i < header->idx; i++) { + switch_event_add_header_string(*clone, SWITCH_STACK_PUSH, header->name, header->array[i]); + } + } else { + switch_event_add_header_string(*clone, SWITCH_STACK_BOTTOM, header->name, header->value); + } + } + + if (event->body) { + (*clone)->body = DUP(event->body); + } + + (*clone)->key = event->key; + + return SWITCH_STATUS_SUCCESS; +} + +static void event_handler(switch_event_t *event) { + switch_event_t *clone = NULL; + ei_event_stream_t *event_stream = (ei_event_stream_t *) event->bind_user_data; + + /* if mod_kazoo or the event stream isn't running dont push a new event */ + if (!switch_test_flag(event_stream, LFLAG_RUNNING) || !switch_test_flag(&globals, LFLAG_RUNNING)) { + return; + } + + if (event->event_id == SWITCH_EVENT_CUSTOM) { + ei_event_binding_t *event_binding = event_stream->bindings; + unsigned short int found = 0; + + if (!event->subclass_name) { + return; + } + + while(event_binding != NULL) { + if (event_binding->type == SWITCH_EVENT_CUSTOM) { + if(event_binding->subclass_name + && !strcmp(event->subclass_name, event_binding->subclass_name)) { + found = 1; + break; + } + } + event_binding = event_binding->next; + } + + if (!found) { + return; + } + } + + /* try to clone the event and push it to the event stream thread */ + /* TODO: someday maybe the filter comes from the event_stream (set during init only) + * and is per-binding so we only send headers that a process requests */ + if (kazoo_event_dup(&clone, event, globals.event_filter) == SWITCH_STATUS_SUCCESS) { + if (switch_queue_trypush(event_stream->queue, clone) != SWITCH_STATUS_SUCCESS) { + /* if we couldn't place the cloned event into the listeners */ + /* event queue make sure we destroy it, real good like */ + switch_event_destroy(&clone); + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory error: Have a good trip? See you next fall!\n"); + } +} + +static void *SWITCH_THREAD_FUNC event_stream_loop(switch_thread_t *thread, void *obj) { + ei_event_stream_t *event_stream = (ei_event_stream_t *) obj; + ei_event_binding_t *event_binding; + switch_sockaddr_t *sa; + uint16_t port; + char ipbuf[25]; + const char *ip_addr; + void *pop; + short event_stream_framing = globals.event_stream_framing; + + switch_atomic_inc(&globals.threads); + + switch_assert(event_stream != NULL); + + /* figure out what socket we just opened */ + switch_socket_addr_get(&sa, SWITCH_FALSE, event_stream->acceptor); + port = switch_sockaddr_get_port(sa); + ip_addr = switch_get_addr(ipbuf, sizeof(ipbuf), sa); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting erlang event stream %p on %s:%u for %s <%d.%d.%d>\n" + ,(void *)event_stream, ip_addr, port, event_stream->pid.node, event_stream->pid.creation + ,event_stream->pid.num, event_stream->pid.serial); + + while (switch_test_flag(event_stream, LFLAG_RUNNING) && switch_test_flag(&globals, LFLAG_RUNNING)) { + const switch_pollfd_t *fds; + int32_t numfds; + + /* check if a new connection is pending */ + if (switch_pollset_poll(event_stream->pollset, 0, &numfds, &fds) == SWITCH_STATUS_SUCCESS) { + for (int32_t i = 0; i < numfds; i++) { + switch_socket_t *newsocket; + + /* accept the new client connection */ + if (switch_socket_accept(&newsocket, event_stream->acceptor, event_stream->pool) == SWITCH_STATUS_SUCCESS) { + switch_sockaddr_t *sa; + + if (switch_socket_opt_set(newsocket, SWITCH_SO_NONBLOCK, TRUE)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Couldn't set socket as non-blocking\n"); + } + + if (switch_socket_opt_set(newsocket, SWITCH_SO_TCP_NODELAY, 1)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Couldn't disable Nagle.\n"); + } + + /* close the current client, if there is one */ + close_socket(&event_stream->socket); + + switch_mutex_lock(event_stream->socket_mutex); + /* start sending to the new client */ + event_stream->socket = newsocket; + + switch_socket_addr_get(&sa, SWITCH_TRUE, newsocket); + event_stream->local_port = switch_sockaddr_get_port(sa); + switch_get_addr(event_stream->remote_ip, sizeof (event_stream->remote_ip), sa); + + switch_socket_addr_get(&sa, SWITCH_FALSE, newsocket); + event_stream->remote_port = switch_sockaddr_get_port(sa); + switch_get_addr(event_stream->local_ip, sizeof (event_stream->local_ip), sa); + + event_stream->connected = SWITCH_TRUE; + switch_mutex_unlock(event_stream->socket_mutex); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Erlang event stream %p client %s:%u\n", (void *)event_stream, event_stream->remote_ip, event_stream->remote_port); + } + } + } + + /* if there was an event waiting in our queue send it to the client */ + if (switch_queue_pop_timeout(event_stream->queue, &pop, 500000) == SWITCH_STATUS_SUCCESS) { + switch_event_t *event = (switch_event_t *) pop; + + if (event_stream->socket) { + ei_x_buff ebuf; + char byte; + short i = event_stream_framing; + switch_size_t size = 1; + + if(globals.event_stream_preallocate > 0) { + ebuf.buff = malloc(globals.event_stream_preallocate); + ebuf.buffsz = globals.event_stream_preallocate; + ebuf.index = 0; + if(ebuf.buff == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not pre-allocate memory for mod_kazoo message\n"); + break; + } + ei_x_encode_version(&ebuf); + } else { + ei_x_new_with_version(&ebuf); + } + + ei_encode_switch_event(&ebuf, event); + + if (globals.event_stream_preallocate > 0 && ebuf.buffsz > globals.event_stream_preallocate) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "increased event stream buffer size to %d\n", ebuf.buffsz); + } + + while (i) { + byte = ebuf.index >> (8 * --i); + switch_socket_send(event_stream->socket, &byte, &size); + } + + size = (switch_size_t)ebuf.index; + switch_socket_send(event_stream->socket, ebuf.buff, &size); + + ei_x_free(&ebuf); + } + + switch_event_destroy(&event); + } + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Shutting down erlang event stream %p\n", (void *)event_stream); + + /* unbind from the system events */ + event_binding = event_stream->bindings; + while(event_binding != NULL) { + switch_event_unbind(&event_binding->node); + event_binding = event_binding->next; + } + event_stream->bindings = NULL; + + /* clear and destroy any remaining queued events */ + while (switch_queue_trypop(event_stream->queue, &pop) == SWITCH_STATUS_SUCCESS) { + switch_event_t *event = (switch_event_t *) pop; + switch_event_destroy(&event); + } + + /* remove the acceptor pollset */ + switch_pollset_remove(event_stream->pollset, event_stream->pollfd); + + /* close any open sockets */ + close_socket(&event_stream->acceptor); + + switch_mutex_lock(event_stream->socket_mutex); + event_stream->connected = SWITCH_FALSE; + close_socket(&event_stream->socket); + switch_mutex_unlock(event_stream->socket_mutex); + + switch_mutex_destroy(event_stream->socket_mutex); + + /* clean up the memory */ + switch_core_destroy_memory_pool(&event_stream->pool); + + switch_atomic_dec(&globals.threads); + + return NULL; +} + +ei_event_stream_t *new_event_stream(ei_event_stream_t **event_streams, const erlang_pid *from) { + switch_thread_t *thread; + switch_threadattr_t *thd_attr = NULL; + switch_memory_pool_t *pool = NULL; + ei_event_stream_t *event_stream; + + /* create memory pool for this event stream */ + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory: How many Alzheimer's patients does it take to screw in a light bulb? To get to the other side.\n"); + return NULL; + } + + /* from the memory pool, allocate the event stream structure */ + if (!(event_stream = switch_core_alloc(pool, sizeof (*event_stream)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory: I may have Alzheimers but at least I dont have Alzheimers.\n"); + return NULL; + } + + /* prepare the event stream */ + memset(event_stream, 0, sizeof(*event_stream)); + event_stream->bindings = NULL; + event_stream->pool = pool; + event_stream->connected = SWITCH_FALSE; + memcpy(&event_stream->pid, from, sizeof(erlang_pid)); + switch_queue_create(&event_stream->queue, MAX_QUEUE_LEN, pool); + + /* create a socket for accepting the event stream client */ + if (!(event_stream->acceptor = create_socket(pool))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Like car accidents, most hardware problems are due to driver error.\n"); + /* TODO: clean up */ + return NULL; + } + + if (switch_socket_opt_set(event_stream->acceptor, SWITCH_SO_NONBLOCK, TRUE)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Hey, it compiles!\n"); + /* TODO: clean up */ + return NULL; + } + + /* create a pollset so we can efficiently check for new client connections */ + if (switch_pollset_create(&event_stream->pollset, 1000, pool, 0) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "My software never has bugs. It just develops random features.\n"); + /* TODO: clean up */ + return NULL; + } + + switch_socket_create_pollfd(&event_stream->pollfd, event_stream->acceptor, SWITCH_POLLIN | SWITCH_POLLERR, NULL, pool); + if (switch_pollset_add(event_stream->pollset, event_stream->pollfd) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "If you saw a heat wave, would you wave back?\n"); + /* TODO: clean up */ + return NULL; + } + + switch_mutex_init(&event_stream->socket_mutex, SWITCH_MUTEX_DEFAULT, pool); + + /* add the new event stream to the link list + * since the event streams link list is only + * accessed from the same thread no locks + * are required */ + if (!*event_streams) { + *event_streams = event_stream; + } else { + event_stream->next = *event_streams; + *event_streams = event_stream; + } + + /* when we start we are running */ + switch_set_flag(event_stream, LFLAG_RUNNING); + + switch_threadattr_create(&thd_attr, event_stream->pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, event_stream_loop, event_stream, event_stream->pool); + + return event_stream; +} + +unsigned long get_stream_port(const ei_event_stream_t *event_stream) { + switch_sockaddr_t *sa; + switch_socket_addr_get(&sa, SWITCH_FALSE, event_stream->acceptor); + return (unsigned long) switch_sockaddr_get_port(sa); +} + +ei_event_stream_t *find_event_stream(ei_event_stream_t *event_stream, const erlang_pid *from) { + while (event_stream != NULL) { + if (ei_compare_pids(&event_stream->pid, from) == SWITCH_STATUS_SUCCESS) { + return event_stream; + } + event_stream = event_stream->next; + } + + return NULL; +} + +switch_status_t remove_event_stream(ei_event_stream_t **event_streams, const erlang_pid *from) { + ei_event_stream_t *event_stream, *prev = NULL; + int found = 0; + + /* if there are no event bindings there is nothing to do */ + if (!*event_streams) { + return SWITCH_STATUS_SUCCESS; + } + + /* try to find the event stream for the client process */ + event_stream = *event_streams; + while(event_stream != NULL) { + if (ei_compare_pids(&event_stream->pid, from) == SWITCH_STATUS_SUCCESS) { + found = 1; + break; + } + + prev = event_stream; + event_stream = event_stream->next; + } + + if (found) { + /* if we found an event stream remove it from + * from the link list */ + if (!prev) { + *event_streams = event_stream->next; + } else { + prev->next = event_stream->next; + } + + /* stop the event stream thread */ + switch_clear_flag(event_stream, LFLAG_RUNNING); + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t remove_event_streams(ei_event_stream_t **event_streams) { + ei_event_stream_t *event_stream = *event_streams; + + while(event_stream != NULL) { + /* stop the event bindings publisher thread */ + switch_clear_flag(event_stream, LFLAG_RUNNING); + + event_stream = event_stream->next; + } + + *event_streams = NULL; + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t add_event_binding(ei_event_stream_t *event_stream, const switch_event_types_t event_type, const char *subclass_name) { + ei_event_binding_t *event_binding = event_stream->bindings; + + /* check if the event binding already exists, ignore if so */ + while(event_binding != NULL) { + if (event_binding->type == SWITCH_EVENT_CUSTOM) { + if(subclass_name + && event_binding->subclass_name + && !strcmp(subclass_name, event_binding->subclass_name)) { + return SWITCH_STATUS_SUCCESS; + } + } else if (event_binding->type == event_type) { + return SWITCH_STATUS_SUCCESS; + } + event_binding = event_binding->next; + } + + /* from the event stream memory pool, allocate the event binding structure */ + if (!(event_binding = switch_core_alloc(event_stream->pool, sizeof (*event_binding)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of random-access memory, attempting write-only memory\n"); + return SWITCH_STATUS_FALSE; + } + + /* prepare the event binding struct */ + event_binding->type = event_type; + if (!subclass_name || zstr(subclass_name)) { + event_binding->subclass_name = NULL; + } else { + /* TODO: free strdup? */ + event_binding->subclass_name = strdup(subclass_name); + } + event_binding->next = NULL; + + /* bind to the event with a unique ID and capture the event_node pointer */ + switch_uuid_str(event_binding->id, sizeof(event_binding->id)); + if (switch_event_bind_removable(event_binding->id, event_type, subclass_name, event_handler, event_stream, &event_binding->node) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to bind to event %s %s!\n" + ,switch_event_name(event_binding->type), event_binding->subclass_name ? event_binding->subclass_name : ""); + return SWITCH_STATUS_GENERR; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding event binding %s to stream %p for %s <%d.%d.%d>: %s %s\n" + ,event_binding->id, (void *)event_stream, event_stream->pid.node, event_stream->pid.creation + ,event_stream->pid.num, event_stream->pid.serial, switch_event_name(event_binding->type) + ,event_binding->subclass_name ? event_binding->subclass_name : ""); + + /* add the new binding to the list */ + if (!event_stream->bindings) { + event_stream->bindings = event_binding; + } else { + event_binding->next = event_stream->bindings; + event_stream->bindings = event_binding; + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t remove_event_binding(ei_event_stream_t *event_stream, const switch_event_types_t event_type, const char *subclass_name) { + ei_event_binding_t *event_binding = event_stream->bindings, *prev = NULL; + int found = 0; + + /* if there are no bindings then there is nothing to do */ + if (!event_binding) { + return SWITCH_STATUS_SUCCESS; + } + + /* try to find the event binding specified */ + while(event_binding != NULL) { + if (event_binding->type == SWITCH_EVENT_CUSTOM + && subclass_name + && event_binding->subclass_name + && !strcmp(subclass_name, event_binding->subclass_name)) { + found = 1; + break; + } else if (event_binding->type == event_type) { + found = 1; + break; + } + + prev = event_binding; + event_binding = event_binding->next; + } + + if (found) { + /* if the event binding exists, unbind from the system */ + switch_event_unbind(&event_binding->node); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Removing event binding %s from %p for %s <%d.%d.%d>: %s %s\n" + ,event_binding->id, (void *)event_stream, event_stream->pid.node, event_stream->pid.creation + ,event_stream->pid.num, event_stream->pid.serial, switch_event_name(event_binding->type) + ,event_binding->subclass_name ? event_binding->subclass_name : ""); + + /* remove the event binding from the list */ + if (!prev) { + event_stream->bindings = event_binding->next; + } else { + prev->next = event_binding->next; + } + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t remove_event_bindings(ei_event_stream_t *event_stream) { + ei_event_binding_t *event_binding = event_stream->bindings; + + /* if there are no bindings then there is nothing to do */ + if (!event_binding) { + return SWITCH_STATUS_SUCCESS; + } + + /* try to find the event binding specified */ + while(event_binding != NULL) { + /* if the event binding exists, unbind from the system */ + switch_event_unbind(&event_binding->node); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Removing event binding %s from %p for %s <%d.%d.%d>: %s %s\n" + ,event_binding->id, (void *)event_stream, event_stream->pid.node, event_stream->pid.creation + ,event_stream->pid.num, event_stream->pid.serial, switch_event_name(event_binding->type) + ,event_binding->subclass_name ? event_binding->subclass_name : ""); + + event_binding = event_binding->next; + } + + event_stream->bindings = NULL; + + 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: + */ diff --git a/src/mod/event_handlers/mod_kazoo/kazoo_fetch_agent.c b/src/mod/event_handlers/mod_kazoo/kazoo_fetch_agent.c new file mode 100644 index 0000000000..522722c700 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/kazoo_fetch_agent.c @@ -0,0 +1,707 @@ +/* + * 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): + * + * Karl Anderson + * Darren Schreiber + * + * + * kazoo_fetch.c -- XML fetch request handler + * + */ +#include "mod_kazoo.h" + +struct xml_fetch_reply_s { + char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; + char *xml_str; + struct xml_fetch_reply_s *next; +}; +typedef struct xml_fetch_reply_s xml_fetch_reply_t; + +struct fetch_handler_s { + erlang_pid pid; + struct fetch_handler_s *next; +}; +typedef struct fetch_handler_s fetch_handler_t; + +struct ei_xml_client_s { + ei_node_t *ei_node; + fetch_handler_t *fetch_handlers; + struct ei_xml_client_s *next; +}; +typedef struct ei_xml_client_s ei_xml_client_t; + +struct ei_xml_agent_s { + switch_memory_pool_t *pool; + switch_xml_section_t section; + switch_thread_rwlock_t *lock; + ei_xml_client_t *clients; + switch_mutex_t *current_client_mutex; + ei_xml_client_t *current_client; + switch_mutex_t *replies_mutex; + switch_thread_cond_t *new_reply; + xml_fetch_reply_t *replies; +}; +typedef struct ei_xml_agent_s ei_xml_agent_t; + +static char *xml_section_to_string(switch_xml_section_t section) { + switch(section) { + case SWITCH_XML_SECTION_CONFIG: + return "configuration"; + case SWITCH_XML_SECTION_DIRECTORY: + return "directory"; + case SWITCH_XML_SECTION_DIALPLAN: + return "dialplan"; + case SWITCH_XML_SECTION_CHATPLAN: + return "chatplan"; + case SWITCH_XML_SECTION_CHANNELS: + return "channels"; + default: + return "unknown"; + } +} + +static char *expand_vars(char *xml_str) { + char *var, *val; + char *rp = xml_str; /* read pointer */ + char *ep, *wp, *buff; /* end pointer, write pointer, write buffer */ + + if (!(strstr(xml_str, "$${"))) { + return xml_str; + } + + switch_zmalloc(buff, strlen(xml_str) * 2); + wp = buff; + ep = buff + (strlen(xml_str) * 2) - 1; + + while (*rp && wp < ep) { + if (*rp == '$' && *(rp + 1) == '$' && *(rp + 2) == '{') { + char *e = switch_find_end_paren(rp + 2, '{', '}'); + + if (e) { + rp += 3; + var = rp; + *e++ = '\0'; + rp = e; + + if ((val = switch_core_get_variable_dup(var))) { + char *p; + for (p = val; p && *p && wp <= ep; p++) { + *wp++ = *p; + } + switch_safe_free(val); + } + continue; + } + } + + *wp++ = *rp++; + } + + *wp++ = '\0'; + + switch_safe_free(xml_str); + return buff; +} + +static switch_xml_t fetch_handler(const char *section, const char *tag_name, const char *key_name, const char *key_value, switch_event_t *params, void *user_data) { + switch_xml_t xml = NULL; + switch_uuid_t uuid; + switch_time_t now = 0; + ei_xml_agent_t *agent = (ei_xml_agent_t *) user_data; + ei_xml_client_t *client; + fetch_handler_t *fetch_handler; + xml_fetch_reply_t reply, *pending, *prev = NULL; + + now = switch_micro_time_now(); + + if (!switch_test_flag(&globals, LFLAG_RUNNING)) { + return xml; + } + + /* read-lock the agent */ + switch_thread_rwlock_rdlock(agent->lock); + + /* serialize access to current, used to round-robin requests */ + /* TODO: check globals for round-robin boolean or loop all clients */ + switch_mutex_lock(agent->current_client_mutex); + if (!agent->current_client) { + client = agent->clients; + } else { + client = agent->current_client; + } + if (client) { + agent->current_client = client->next; + } + switch_mutex_unlock(agent->current_client_mutex); + + /* no client, no work required */ + if (!client || !client->fetch_handlers) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "No %s XML erlang handler currently available\n" + ,section); + switch_thread_rwlock_unlock(agent->lock); + return xml; + } + + /* prepare the reply collector */ + switch_uuid_get(&uuid); + switch_uuid_format(reply.uuid_str, &uuid); + reply.next = NULL; + reply.xml_str = NULL; + + /* add our reply placeholder to the replies list */ + switch_mutex_lock(agent->replies_mutex); + if (!agent->replies) { + agent->replies = &reply; + } else { + reply.next = agent->replies; + agent->replies = &reply; + } + switch_mutex_unlock(agent->replies_mutex); + + fetch_handler = client->fetch_handlers; + while (fetch_handler != NULL) { + ei_send_msg_t *send_msg; + + switch_malloc(send_msg, sizeof(*send_msg)); + memcpy(&send_msg->pid, &fetch_handler->pid, sizeof(erlang_pid)); + + ei_x_new_with_version(&send_msg->buf); + + ei_x_encode_tuple_header(&send_msg->buf, 7); + ei_x_encode_atom(&send_msg->buf, "fetch"); + ei_x_encode_atom(&send_msg->buf, section); + _ei_x_encode_string(&send_msg->buf, tag_name ? tag_name : "undefined"); + _ei_x_encode_string(&send_msg->buf, key_name ? key_name : "undefined"); + _ei_x_encode_string(&send_msg->buf, key_value ? key_value : "undefined"); + _ei_x_encode_string(&send_msg->buf, reply.uuid_str); + + if (params) { + ei_encode_switch_event_headers(&send_msg->buf, params); + } else { + ei_x_encode_empty_list(&send_msg->buf); + } + + if (switch_queue_trypush(client->ei_node->send_msgs, send_msg) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to send %s XML request to %s <%d.%d.%d>\n" + ,section + ,fetch_handler->pid.node + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + ei_x_free(&send_msg->buf); + switch_safe_free(send_msg); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sending %s XML request (%s) to %s <%d.%d.%d>\n" + ,section + ,reply.uuid_str + ,fetch_handler->pid.node + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + } + + fetch_handler = fetch_handler->next; + } + + /* wait for a reply (if there isnt already one...amazingly improbable but lets not take shortcuts */ + switch_mutex_lock(agent->replies_mutex); + + switch_thread_rwlock_unlock(agent->lock); + + if (!reply.xml_str) { + switch_time_t timeout; + + timeout = switch_micro_time_now() + 3000000; + while (switch_micro_time_now() < timeout) { + /* unlock the replies list and go to sleep, calculate a three second timeout before we started the loop + * plus 100ms to add a little hysteresis between the timeout and the while loop */ + switch_thread_cond_timedwait(agent->new_reply, agent->replies_mutex, (timeout - switch_micro_time_now() + 100000)); + + /* if we woke up (and therefore have locked replies again) check if we got our reply + * otherwise we either timed-out (the while condition will fail) or one of + * our sibling processes got a reply and we should go back to sleep */ + if (reply.xml_str) { + break; + } + } + } + + /* find our reply placeholder in the linked list and remove it */ + pending = agent->replies; + while (pending != NULL) { + if (pending->uuid_str == reply.uuid_str) { + break; + } + + prev = pending; + pending = pending->next; + } + + if (pending) { + if (!prev) { + agent->replies = reply.next; + } else { + prev->next = reply.next; + } + } + + /* we are done with the replies link-list */ + switch_mutex_unlock(agent->replies_mutex); + + /* after all that did we get what we were after?! */ + if (reply.xml_str) { + /* HELL YA WE DID */ + reply.xml_str = expand_vars(reply.xml_str); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received %s XML (%s) after %dms: %s\n" + ,section + ,reply.uuid_str + ,(unsigned int) (switch_micro_time_now() - now) / 1000 + ,reply.xml_str); + + xml = switch_xml_parse_str_dynamic(reply.xml_str, SWITCH_FALSE); + } else { + /* facepalm */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Request for %s XML (%s) timed-out after %dms\n" + ,section + ,reply.uuid_str + ,(unsigned int) (switch_micro_time_now() - now) / 1000); + } + + return xml; +} + +static switch_status_t bind_fetch_agent(switch_xml_section_t section, switch_xml_binding_t **binding) { + switch_memory_pool_t *pool = NULL; + ei_xml_agent_t *agent; + + /* create memory pool for this xml search binging (lives for duration of mod_kazoo runtime) */ + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory: They're not people; they're hippies!\n"); + return SWITCH_STATUS_MEMERR; + } + + /* allocate some memory to store the fetch bindings for this section */ + if (!(agent = switch_core_alloc(pool, sizeof (*agent)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory: Oh, Jesus tap-dancing Christ!\n"); + return SWITCH_STATUS_MEMERR; + } + + /* try to bind to the switch */ + if (switch_xml_bind_search_function_ret(fetch_handler, section, agent, binding) != SWITCH_STATUS_SUCCESS) { + switch_core_destroy_memory_pool(&pool); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not bind to FreeSWITCH %s XML requests\n" + ,xml_section_to_string(section)); + return SWITCH_STATUS_GENERR; + } + + agent->pool = pool; + agent->section = section; + switch_thread_rwlock_create(&agent->lock, pool); + agent->clients = NULL; + switch_mutex_init(&agent->current_client_mutex, SWITCH_MUTEX_DEFAULT, pool); + agent->current_client = NULL; + switch_mutex_init(&agent->replies_mutex, SWITCH_MUTEX_DEFAULT, pool); + switch_thread_cond_create(&agent->new_reply, pool); + agent->replies = NULL; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Bound to %s XML requests\n" + ,xml_section_to_string(section)); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t unbind_fetch_agent(switch_xml_binding_t **binding) { + ei_xml_agent_t *agent; + ei_xml_client_t *client; + + /* get a pointer to our user_data */ + agent = (ei_xml_agent_t *)switch_xml_get_binding_user_data(*binding); + + /* unbind from the switch */ + switch_xml_unbind_search_function(binding); + + /* LOCK ALL THE THINGS */ + switch_thread_rwlock_wrlock(agent->lock); + switch_mutex_lock(agent->current_client_mutex); + switch_mutex_lock(agent->replies_mutex); + + /* cleanly destroy each client */ + client = agent->clients; + while(client != NULL) { + ei_xml_client_t *tmp_client = client; + fetch_handler_t *fetch_handler; + + fetch_handler = client->fetch_handlers; + while(fetch_handler != NULL) { + fetch_handler_t *tmp_fetch_handler = fetch_handler; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Removed %s XML handler %s <%d.%d.%d>\n" + ,xml_section_to_string(agent->section) + ,fetch_handler->pid.node + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + + fetch_handler = fetch_handler->next; + switch_safe_free(tmp_fetch_handler); + } + + client = client->next; + switch_safe_free(tmp_client); + } + + /* keep the pointers clean, even if its just for a moment */ + agent->clients = NULL; + agent->current_client = NULL; + + /* release the locks! */ + switch_thread_rwlock_unlock(agent->lock); + switch_mutex_unlock(agent->current_client_mutex); + switch_mutex_unlock(agent->replies_mutex); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Unbound from %s XML requests\n" + ,xml_section_to_string(agent->section)); + + /* cleanly destroy the bindings */ + switch_thread_rwlock_destroy(agent->lock); + switch_mutex_destroy(agent->current_client_mutex); + switch_mutex_destroy(agent->replies_mutex); + switch_thread_cond_destroy(agent->new_reply); + switch_core_destroy_memory_pool(&agent->pool); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t remove_xml_client(ei_node_t *ei_node, switch_xml_binding_t *binding) { + ei_xml_agent_t *agent; + ei_xml_client_t *client, *prev = NULL; + int found = 0; + + agent = (ei_xml_agent_t *)switch_xml_get_binding_user_data(binding); + + /* write-lock the agent */ + switch_thread_rwlock_wrlock(agent->lock); + + client = agent->clients; + while (client != NULL) { + if (client->ei_node == ei_node) { + found = 1; + break; + } + + prev = client; + client = client->next; + } + + if (found) { + fetch_handler_t *fetch_handler; + + if (!prev) { + agent->clients = client->next; + } else { + prev->next = client->next; + } + + /* the mutex lock is not required since we have the write lock + * but hey its fun and safe so do it anyway */ + switch_mutex_lock(agent->current_client_mutex); + if (agent->current_client == client) { + agent->current_client = agent->clients; + } + switch_mutex_unlock(agent->current_client_mutex); + + fetch_handler = client->fetch_handlers; + while(fetch_handler != NULL) { + fetch_handler_t *tmp_fetch_handler = fetch_handler; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Removed %s XML handler %s <%d.%d.%d>\n" + ,xml_section_to_string(agent->section) + ,fetch_handler->pid.node + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + + fetch_handler = fetch_handler->next; + switch_safe_free(tmp_fetch_handler); + } + + switch_safe_free(client); + } + + switch_thread_rwlock_unlock(agent->lock); + + return SWITCH_STATUS_SUCCESS; +} + +static ei_xml_client_t *add_xml_client(ei_node_t *ei_node, ei_xml_agent_t *agent) { + ei_xml_client_t *client; + + switch_malloc(client, sizeof(*client)); + + client->ei_node = ei_node; + client->fetch_handlers = NULL; + client->next = NULL; + + if (agent->clients) { + client->next = agent->clients; + } + + agent->clients = client; + + return client; +} + +static ei_xml_client_t *find_xml_client(ei_node_t *ei_node, ei_xml_agent_t *agent) { + ei_xml_client_t *client; + + client = agent->clients; + while (client != NULL) { + if (client->ei_node == ei_node) { + return client; + } + + client = client->next; + } + + return NULL; +} + +static switch_status_t remove_fetch_handler(ei_node_t *ei_node, erlang_pid *from, switch_xml_binding_t *binding) { + ei_xml_agent_t *agent; + ei_xml_client_t *client; + fetch_handler_t *fetch_handler, *prev = NULL; + int found = 0; + + agent = (ei_xml_agent_t *)switch_xml_get_binding_user_data(binding); + + /* write-lock the agent */ + switch_thread_rwlock_wrlock(agent->lock); + + if (!(client = find_xml_client(ei_node, agent))) { + switch_thread_rwlock_unlock(agent->lock); + return SWITCH_STATUS_SUCCESS; + } + + fetch_handler = client->fetch_handlers; + while (fetch_handler != NULL) { + if (ei_compare_pids(&fetch_handler->pid, from) == SWITCH_STATUS_SUCCESS) { + found = 1; + break; + } + + prev = fetch_handler; + fetch_handler = fetch_handler->next; + } + + if (found) { + if (!prev) { + client->fetch_handlers = fetch_handler->next; + } else { + prev->next = fetch_handler->next; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Removed %s XML handler %s <%d.%d.%d>\n" + ,xml_section_to_string(agent->section) + ,fetch_handler->pid.node + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + + switch_safe_free(fetch_handler); + } + + switch_thread_rwlock_unlock(agent->lock); + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_api_command_stream(ei_node_t *ei_node, switch_stream_handle_t *stream, switch_xml_binding_t *binding) { + ei_xml_agent_t *agent; + ei_xml_client_t *client; + + if (!binding) { + return SWITCH_STATUS_GENERR; + } + + agent = (ei_xml_agent_t *)switch_xml_get_binding_user_data(binding); + + /* read-lock the agent */ + switch_thread_rwlock_rdlock(agent->lock); + client = agent->clients; + while (client != NULL) { + if (client->ei_node == ei_node) { + fetch_handler_t *fetch_handler; + fetch_handler = client->fetch_handlers; + while (fetch_handler != NULL) { + stream->write_function(stream, "XML %s handler <%d.%d.%d>\n" + ,xml_section_to_string(agent->section) + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + fetch_handler = fetch_handler->next; + } + break; + } + + client = client->next; + } + switch_thread_rwlock_unlock(agent->lock); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t bind_fetch_agents() { + bind_fetch_agent(SWITCH_XML_SECTION_CONFIG, &globals.config_fetch_binding); + bind_fetch_agent(SWITCH_XML_SECTION_DIRECTORY, &globals.directory_fetch_binding); + bind_fetch_agent(SWITCH_XML_SECTION_DIALPLAN, &globals.dialplan_fetch_binding); + bind_fetch_agent(SWITCH_XML_SECTION_CHATPLAN, &globals.chatplan_fetch_binding); + bind_fetch_agent(SWITCH_XML_SECTION_CHANNELS, &globals.channels_fetch_binding); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t unbind_fetch_agents() { + unbind_fetch_agent(&globals.config_fetch_binding); + unbind_fetch_agent(&globals.directory_fetch_binding); + unbind_fetch_agent(&globals.dialplan_fetch_binding); + unbind_fetch_agent(&globals.chatplan_fetch_binding); + unbind_fetch_agent(&globals.channels_fetch_binding); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t remove_xml_clients(ei_node_t *ei_node) { + remove_xml_client(ei_node, globals.config_fetch_binding); + remove_xml_client(ei_node, globals.directory_fetch_binding); + remove_xml_client(ei_node, globals.dialplan_fetch_binding); + remove_xml_client(ei_node, globals.chatplan_fetch_binding); + remove_xml_client(ei_node, globals.channels_fetch_binding); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t add_fetch_handler(ei_node_t *ei_node, erlang_pid *from, switch_xml_binding_t *binding) { + ei_xml_agent_t *agent; + ei_xml_client_t *client; + fetch_handler_t *fetch_handler; + + agent = (ei_xml_agent_t *)switch_xml_get_binding_user_data(binding); + + /* write-lock the agent */ + switch_thread_rwlock_wrlock(agent->lock); + + if (!(client = find_xml_client(ei_node, agent))) { + client = add_xml_client(ei_node, agent); + } + + fetch_handler = client->fetch_handlers; + while (fetch_handler != NULL) { + if (ei_compare_pids(&fetch_handler->pid, from) == SWITCH_STATUS_SUCCESS) { + switch_thread_rwlock_unlock(agent->lock); + return SWITCH_STATUS_SUCCESS; + } + fetch_handler = fetch_handler->next; + } + + switch_malloc(fetch_handler, sizeof(*fetch_handler)); + + memcpy(&fetch_handler->pid, from, sizeof(erlang_pid));; + + fetch_handler->next = NULL; + if (client->fetch_handlers) { + fetch_handler->next = client->fetch_handlers; + } + + client->fetch_handlers = fetch_handler; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Added %s XML handler %s <%d.%d.%d>\n" + ,xml_section_to_string(agent->section) + ,fetch_handler->pid.node + ,fetch_handler->pid.creation + ,fetch_handler->pid.num + ,fetch_handler->pid.serial); + + switch_thread_rwlock_unlock(agent->lock); + + ei_link(ei_node, ei_self(&globals.ei_cnode), from); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t remove_fetch_handlers(ei_node_t *ei_node, erlang_pid *from) { + remove_fetch_handler(ei_node, from, globals.config_fetch_binding); + remove_fetch_handler(ei_node, from, globals.directory_fetch_binding); + remove_fetch_handler(ei_node, from, globals.dialplan_fetch_binding); + remove_fetch_handler(ei_node, from, globals.chatplan_fetch_binding); + remove_fetch_handler(ei_node, from, globals.channels_fetch_binding); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t fetch_reply(char *uuid_str, char *xml_str, switch_xml_binding_t *binding) { + ei_xml_agent_t *agent; + xml_fetch_reply_t *reply; + switch_status_t status = SWITCH_STATUS_NOTFOUND; + + agent = (ei_xml_agent_t *)switch_xml_get_binding_user_data(binding); + + switch_mutex_lock(agent->replies_mutex); + reply = agent->replies; + while (reply != NULL) { + if (!strncmp(reply->uuid_str, uuid_str, SWITCH_UUID_FORMATTED_LENGTH)) { + if (!reply->xml_str) { + reply->xml_str = xml_str; + switch_thread_cond_broadcast(agent->new_reply); + status = SWITCH_STATUS_SUCCESS; + } + break; + } + + reply = reply->next; + } + switch_mutex_unlock(agent->replies_mutex); + + return status; +} + +switch_status_t handle_api_command_streams(ei_node_t *ei_node, switch_stream_handle_t *stream) { + handle_api_command_stream(ei_node, stream, globals.config_fetch_binding); + handle_api_command_stream(ei_node, stream, globals.directory_fetch_binding); + handle_api_command_stream(ei_node, stream, globals.dialplan_fetch_binding); + handle_api_command_stream(ei_node, stream, globals.chatplan_fetch_binding); + handle_api_command_stream(ei_node, stream, globals.channels_fetch_binding); + + 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: + */ diff --git a/src/mod/event_handlers/mod_kazoo/kazoo_node.c b/src/mod/event_handlers/mod_kazoo/kazoo_node.c new file mode 100644 index 0000000000..0ffd3ffdf0 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/kazoo_node.c @@ -0,0 +1,1254 @@ +/* + * 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): + * + * Anthony Minessale II + * Andrew Thompson + * Rob Charlton + * Darren Schreiber + * Mike Jerris + * Tamas Cseke + * + * + * handle_msg.c -- handle messages received from erlang nodes + * + */ +#include "mod_kazoo.h" + +struct api_command_struct_s { + char *cmd; + char *arg; + ei_node_t *ei_node; + char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; + erlang_pid pid; + switch_memory_pool_t *pool; +}; +typedef struct api_command_struct_s api_command_struct_t; + +static char *REQUEST_ATOMS[] = { + "noevents", + "exit", + "link", + "nixevent", + "sendevent", + "sendmsg", + "bind", + "getpid", + "version", + "bgapi", + "api", + "event", + "fetch_reply" +}; + +typedef enum { + REQUEST_NOEVENTS, + REQUEST_EXIT, + REQUEST_LINK, + REQUEST_NIXEVENT, + REQUEST_SENDEVENT, + REQUEST_SENDMSG, + REQUEST_BIND, + REQUEST_GETPID, + REQUEST_VERSION, + REQUEST_BGAPI, + REQUEST_API, + REQUEST_EVENT, + REQUEST_FETCH_REPLY, + REQUEST_MAX +} request_atoms_t; + +static switch_status_t find_request(char *atom, int *request) { + for (int i = 0; i < REQUEST_MAX; i++) { + if(!strncmp(atom, REQUEST_ATOMS[i], MAXATOMLEN)) { + *request = i; + return SWITCH_STATUS_SUCCESS; + } + } + + return SWITCH_STATUS_FALSE; +} + +static void destroy_node_handler(ei_node_t *ei_node) { + int pending = 0; + void *pop; + + switch_clear_flag(ei_node, LFLAG_RUNNING); + + /* wait for pending bgapi requests to complete */ + while ((pending = switch_atomic_read(&ei_node->pending_bgapi))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for %d pending bgapi requests to complete\n", pending); + switch_yield(500000); + } + + /* wait for receive handlers to complete */ + while ((pending = switch_atomic_read(&ei_node->receive_handlers))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for %d receive handlers to complete\n", pending); + switch_yield(500000); + } + + switch_mutex_lock(ei_node->event_streams_mutex); + remove_event_streams(&ei_node->event_streams); + switch_mutex_unlock(ei_node->event_streams_mutex); + + remove_xml_clients(ei_node); + + while (switch_queue_trypop(ei_node->received_msgs, &pop) == SWITCH_STATUS_SUCCESS) { + ei_received_msg_t *received_msg = (ei_received_msg_t *) pop; + + ei_x_free(&received_msg->buf); + switch_safe_free(received_msg); + } + + while (switch_queue_trypop(ei_node->send_msgs, &pop) == SWITCH_STATUS_SUCCESS) { + ei_send_msg_t *send_msg = (ei_send_msg_t *) pop; + + ei_x_free(&send_msg->buf); + switch_safe_free(send_msg); + } + + close_socketfd(&ei_node->nodefd); + + switch_mutex_destroy(ei_node->event_streams_mutex); + + switch_core_destroy_memory_pool(&ei_node->pool); +} + +static switch_status_t add_to_ei_nodes(ei_node_t *this_ei_node) { + switch_thread_rwlock_wrlock(globals.ei_nodes_lock); + + if (!globals.ei_nodes) { + globals.ei_nodes = this_ei_node; + } else { + this_ei_node->next = globals.ei_nodes; + globals.ei_nodes = this_ei_node; + } + + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t remove_from_ei_nodes(ei_node_t *this_ei_node) { + ei_node_t *ei_node, *prev = NULL; + int found = 0; + + switch_thread_rwlock_wrlock(globals.ei_nodes_lock); + + /* try to find the event bindings list for the requestor */ + ei_node = globals.ei_nodes; + while(ei_node != NULL) { + if (ei_node == this_ei_node) { + found = 1; + break; + } + + prev = ei_node; + ei_node = ei_node->next; + } + + if (found) { + if (!prev) { + globals.ei_nodes = this_ei_node->next; + } else { + prev->next = ei_node->next; + } + } + + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t api_exec(char *cmd, char *arg, char **reply) { + switch_stream_handle_t stream = { 0 }; + switch_status_t status = SWITCH_STATUS_FALSE; + + SWITCH_STANDARD_STREAM(stream); + + if (switch_api_execute(cmd, arg, NULL, &stream) != SWITCH_STATUS_SUCCESS) { + *reply = switch_mprintf("%s: Command not found", cmd); + status = SWITCH_STATUS_NOTFOUND; + } else if (!stream.data || !strlen(stream.data)) { + *reply = switch_mprintf("%s: Command returned no output", cmd); + status = SWITCH_STATUS_FALSE; + } else { + *reply = strdup(stream.data); + status = SWITCH_STATUS_SUCCESS; + } + + /* if the reply starts with the char "-" (the start of -USAGE ...) */ + /* the args were missing or incorrect */ + if (**reply == '-') { + status = SWITCH_STATUS_FALSE; + } + + switch_safe_free(stream.data); + + return status; +} + +static void *SWITCH_THREAD_FUNC bgapi_exec(switch_thread_t *thread, void *obj) { + api_command_struct_t *acs = (api_command_struct_t *) obj; + switch_memory_pool_t *pool = acs->pool; + char *reply = NULL; + char *cmd = acs->cmd; + char *arg = acs->arg; + ei_node_t *ei_node = acs->ei_node; + ei_send_msg_t *send_msg; + + switch_malloc(send_msg, sizeof(*send_msg)); + memcpy(&send_msg->pid, &acs->pid, sizeof(erlang_pid)); + + if(!switch_test_flag(ei_node, LFLAG_RUNNING) || !switch_test_flag(&globals, LFLAG_RUNNING)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Ignoring command while shuting down\n"); + switch_atomic_dec(&ei_node->pending_bgapi); + return NULL; + } + + ei_x_new_with_version(&send_msg->buf); + + ei_x_encode_tuple_header(&send_msg->buf, 3); + + if (api_exec(cmd, arg, &reply) == SWITCH_STATUS_SUCCESS) { + ei_x_encode_atom(&send_msg->buf, "bgok"); + } else { + ei_x_encode_atom(&send_msg->buf, "bgerror"); + } + + _ei_x_encode_string(&send_msg->buf, acs->uuid_str); + _ei_x_encode_string(&send_msg->buf, reply); + + if (switch_queue_trypush(ei_node->send_msgs, send_msg) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to send bgapi response %s to %s <%d.%d.%d>\n" + ,acs->uuid_str + ,acs->pid.node + ,acs->pid.creation + ,acs->pid.num + ,acs->pid.serial); + ei_x_free(&send_msg->buf); + switch_safe_free(send_msg); + } + + switch_atomic_dec(&ei_node->pending_bgapi); + + switch_safe_free(reply); + switch_safe_free(acs->arg); + switch_core_destroy_memory_pool(&pool); + + return NULL; +} + +static void log_sendmsg_request(char *uuid, switch_event_t *event) +{ + char *cmd = switch_event_get_header(event, "call-command"); + unsigned long cmd_hash; + switch_ssize_t hlen = -1; + unsigned long CMD_EXECUTE = switch_hashfunc_default("execute", &hlen); + unsigned long CMD_XFEREXT = switch_hashfunc_default("xferext", &hlen); + // unsigned long CMD_HANGUP = switch_hashfunc_default("hangup", &hlen); + // unsigned long CMD_NOMEDIA = switch_hashfunc_default("nomedia", &hlen); + // unsigned long CMD_UNICAST = switch_hashfunc_default("unicast", &hlen); + + if (zstr(cmd)) { + return; + } + + cmd_hash = switch_hashfunc_default(cmd, &hlen); + + if (cmd_hash == CMD_EXECUTE) { + char *app_name = switch_event_get_header(event, "execute-app-name"); + char *app_arg = switch_event_get_header(event, "execute-app-arg"); + + if(app_name) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "log|%s|executing %s %s \n", uuid, app_name, switch_str_nil(app_arg)); + } + } else if (cmd_hash == CMD_XFEREXT) { + switch_event_header_t *hp; + + for (hp = event->headers; hp; hp = hp->next) { + char *app_name; + char *app_arg; + + if (!strcasecmp(hp->name, "application")) { + app_name = strdup(hp->value); + app_arg = strchr(app_name, ' '); + + if (app_arg) { + *app_arg++ = '\0'; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "log|%s|building xferext extension: %s %s\n", uuid, app_name, app_arg); + } + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "log|%s|transfered call to xferext extension\n", uuid); + } +} + +static switch_status_t build_event(switch_event_t *event, ei_x_buff * buf) { + int propslist_length, arity; + + if(!event) { + return SWITCH_STATUS_FALSE; + } + + if (ei_decode_list_header(buf->buff, &buf->index, &propslist_length)) { + return SWITCH_STATUS_FALSE; + } + + while (!ei_decode_tuple_header(buf->buff, &buf->index, &arity)) { + char key[1024]; + char *value; + + if (arity != 2) { + return SWITCH_STATUS_FALSE; + } + + if (ei_decode_string_or_binary_limited(buf->buff, &buf->index, sizeof(key), key)) { + return SWITCH_STATUS_FALSE; + } + + if (ei_decode_string_or_binary(buf->buff, &buf->index, &value)) { + return SWITCH_STATUS_FALSE; + } + + if (!strcmp(key, "body")) { + switch_safe_free(event->body); + event->body = value; + } else { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM | SWITCH_STACK_NODUP, key, value); + } + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t erlang_response_badarg(ei_x_buff * rbuf) { + if (rbuf) { + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_atom(rbuf, "error"); + ei_x_encode_atom(rbuf, "badarg"); + } + + return SWITCH_STATUS_GENERR; +} + +static switch_status_t erlang_response_baduuid(ei_x_buff * rbuf) { + if (rbuf) { + ei_x_format_wo_ver(rbuf, "{~a,~a}", "error", "baduuid"); + } + + return SWITCH_STATUS_NOTFOUND; +} + +static switch_status_t erlang_response_notimplemented(ei_x_buff * rbuf) { + if (rbuf) { + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_atom(rbuf, "error"); + ei_x_encode_atom(rbuf, "not_implemented"); + } + + return SWITCH_STATUS_NOTFOUND; +} + +static switch_status_t erlang_response_ok(ei_x_buff *rbuf) { + if (rbuf) { + ei_x_encode_atom(rbuf, "ok"); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_request_noevents(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + ei_event_stream_t *event_stream; + + switch_mutex_lock(ei_node->event_streams_mutex); + if ((event_stream = find_event_stream(ei_node->event_streams, pid))) { + remove_event_bindings(event_stream); + } + switch_mutex_unlock(ei_node->event_streams_mutex); + + return erlang_response_ok(rbuf); +} + +static switch_status_t handle_request_exit(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + switch_clear_flag(ei_node, LFLAG_RUNNING); + + return erlang_response_ok(rbuf); +} + +static switch_status_t handle_request_link(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + ei_link(ei_node, ei_self(&globals.ei_cnode), pid); + + return erlang_response_ok(rbuf); +} + +static switch_status_t handle_request_nixevent(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char event_name[MAXATOMLEN + 1]; + switch_event_types_t event_type; + ei_event_stream_t *event_stream; + int custom = 0, length = 0; + + if (ei_decode_list_header(buf->buff, &buf->index, &length) + || length == 0) { + return erlang_response_badarg(rbuf); + } + + switch_mutex_lock(ei_node->event_streams_mutex); + if (!(event_stream = find_event_stream(ei_node->event_streams, pid))) { + switch_mutex_unlock(ei_node->event_streams_mutex); + return erlang_response_ok(rbuf); + } + + for (int i = 1; i <= length; i++) { + if (ei_decode_atom_safe(buf->buff, &buf->index, event_name)) { + switch_mutex_unlock(ei_node->event_streams_mutex); + return erlang_response_badarg(rbuf); + } + + if (custom) { + remove_event_binding(event_stream, SWITCH_EVENT_CUSTOM, event_name); + } else if (switch_name_event(event_name, &event_type) == SWITCH_STATUS_SUCCESS) { + switch (event_type) { + case SWITCH_EVENT_CUSTOM: + custom++; + break; + case SWITCH_EVENT_ALL: + for (switch_event_types_t type = 0; type < SWITCH_EVENT_ALL; type++) { + if(type != SWITCH_EVENT_CUSTOM) { + remove_event_binding(event_stream, type, NULL); + } + } + break; + default: + remove_event_binding(event_stream, event_type, NULL); + } + } else { + switch_mutex_unlock(ei_node->event_streams_mutex); + return erlang_response_badarg(rbuf); + } + } + switch_mutex_unlock(ei_node->event_streams_mutex); + + return erlang_response_ok(rbuf); +} + +static switch_status_t handle_request_sendevent(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char event_name[MAXATOMLEN + 1]; + char subclass_name[MAXATOMLEN + 1]; + switch_event_types_t event_type; + switch_event_t *event = NULL; + + if (ei_decode_atom_safe(buf->buff, &buf->index, event_name) + || switch_name_event(event_name, &event_type) != SWITCH_STATUS_SUCCESS) + { + return erlang_response_badarg(rbuf); + } + + if (!strncasecmp(event_name, "CUSTOM", MAXATOMLEN)) { + if(ei_decode_atom(buf->buff, &buf->index, subclass_name)) { + return erlang_response_badarg(rbuf); + } + switch_event_create_subclass(&event, event_type, subclass_name); + } else { + switch_event_create(&event, event_type); + } + + if (build_event(event, buf) == SWITCH_STATUS_SUCCESS) { + switch_event_fire(&event); + return erlang_response_ok(rbuf); + } + + if(event) { + switch_event_destroy(&event); + } + + return erlang_response_badarg(rbuf); +} + +static switch_status_t handle_request_sendmsg(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + switch_core_session_t *session; + switch_event_t *event = NULL; + char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; + + if (ei_decode_string_or_binary_limited(buf->buff, &buf->index, sizeof(uuid_str), uuid_str)) { + return erlang_response_badarg(rbuf); + } + + switch_event_create(&event, SWITCH_EVENT_SEND_MESSAGE); + if (build_event(event, buf) != SWITCH_STATUS_SUCCESS) { + return erlang_response_badarg(rbuf); + } + + log_sendmsg_request(uuid_str, event); + + if (zstr_buf(uuid_str) || !(session = switch_core_session_locate(uuid_str))) { + return erlang_response_baduuid(rbuf); + } + switch_core_session_queue_private_event(session, &event, SWITCH_FALSE); + switch_core_session_rwunlock(session); + + return erlang_response_ok(rbuf); +} + +static switch_status_t handle_request_bind(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char section_str[MAXATOMLEN + 1]; + switch_xml_section_t section; + + if (ei_decode_atom_safe(buf->buff, &buf->index, section_str) + || !(section = switch_xml_parse_section_string(section_str))) { + return erlang_response_badarg(rbuf); + } + + switch(section) { + case SWITCH_XML_SECTION_CONFIG: + add_fetch_handler(ei_node, pid, globals.config_fetch_binding); + break; + case SWITCH_XML_SECTION_DIRECTORY: + add_fetch_handler(ei_node, pid, globals.directory_fetch_binding); + break; + case SWITCH_XML_SECTION_DIALPLAN: + add_fetch_handler(ei_node, pid, globals.dialplan_fetch_binding); + break; + case SWITCH_XML_SECTION_CHATPLAN: + add_fetch_handler(ei_node, pid, globals.chatplan_fetch_binding); + break; + case SWITCH_XML_SECTION_CHANNELS: + add_fetch_handler(ei_node, pid, globals.channels_fetch_binding); + break; + default: + return erlang_response_badarg(rbuf); + } + + return erlang_response_ok(rbuf); +} + +static switch_status_t handle_request_getpid(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + if (rbuf) { + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_atom(rbuf, "ok"); + ei_x_encode_pid(rbuf, ei_self(&globals.ei_cnode)); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_request_version(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + if (rbuf) { + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_atom(rbuf, "ok"); + _ei_x_encode_string(rbuf, VERSION); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_request_bgapi(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + api_command_struct_t *acs = NULL; + switch_memory_pool_t *pool; + switch_thread_t *thread; + switch_threadattr_t *thd_attr = NULL; + switch_uuid_t uuid; + char cmd[MAXATOMLEN + 1]; + + if (ei_decode_atom_safe(buf->buff, &buf->index, cmd)) { + return erlang_response_badarg(rbuf); + } + + switch_core_new_memory_pool(&pool); + acs = switch_core_alloc(pool, sizeof(*acs)); + + if (ei_decode_string_or_binary(buf->buff, &buf->index, &acs->arg)) { + switch_core_destroy_memory_pool(&pool); + return erlang_response_badarg(rbuf); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "bgexec: %s(%s)\n", cmd, acs->arg); + + acs->pool = pool; + acs->ei_node = ei_node; + acs->cmd = switch_core_strdup(pool, cmd); + memcpy(&acs->pid, pid, sizeof(erlang_pid)); + + switch_threadattr_create(&thd_attr, acs->pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + + switch_uuid_get(&uuid); + switch_uuid_format(acs->uuid_str, &uuid); + switch_thread_create(&thread, thd_attr, bgapi_exec, acs, acs->pool); + + switch_atomic_inc(&ei_node->pending_bgapi); + + if (rbuf) { + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_atom(rbuf, "ok"); + _ei_x_encode_string(rbuf, acs->uuid_str); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_request_api(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char cmd[MAXATOMLEN + 1]; + char *arg; + + if (ei_decode_atom_safe(buf->buff, &buf->index, cmd)) { + return erlang_response_badarg(rbuf); + } + + if (ei_decode_string_or_binary(buf->buff, &buf->index, &arg)) { + return erlang_response_badarg(rbuf); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "exec: %s(%s)\n", cmd, arg); + + if (rbuf) { + char *reply; + + ei_x_encode_tuple_header(rbuf, 2); + + if (api_exec(cmd, arg, &reply) == SWITCH_STATUS_SUCCESS) { + ei_x_encode_atom(rbuf, "ok"); + } else { + ei_x_encode_atom(rbuf, "error"); + } + + _ei_x_encode_string(rbuf, reply); + switch_safe_free(reply); + } + + switch_safe_free(arg); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_request_event(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char event_name[MAXATOMLEN + 1]; + switch_event_types_t event_type; + ei_event_stream_t *event_stream; + int custom = 0, length = 0; + + if (ei_decode_list_header(buf->buff, &buf->index, &length) || !length) { + return erlang_response_badarg(rbuf); + } + + switch_mutex_lock(ei_node->event_streams_mutex); + if (!(event_stream = find_event_stream(ei_node->event_streams, pid))) { + event_stream = new_event_stream(&ei_node->event_streams, pid); + /* ensure we are notified if the requesting processes dies so we can clean up */ + ei_link(ei_node, ei_self(&globals.ei_cnode), pid); + } + + for (int i = 1; i <= length; i++) { + if (ei_decode_atom_safe(buf->buff, &buf->index, event_name)) { + switch_mutex_unlock(ei_node->event_streams_mutex); + return erlang_response_badarg(rbuf); + } + + if (custom) { + add_event_binding(event_stream, SWITCH_EVENT_CUSTOM, event_name); + } else if (switch_name_event(event_name, &event_type) == SWITCH_STATUS_SUCCESS) { + switch (event_type) { + case SWITCH_EVENT_CUSTOM: + custom++; + break; + case SWITCH_EVENT_ALL: + for (switch_event_types_t type = 0; type < SWITCH_EVENT_ALL; type++) { + if(type != SWITCH_EVENT_CUSTOM) { + add_event_binding(event_stream, type, NULL); + } + } + break; + default: + add_event_binding(event_stream, event_type, NULL); + } + } else { + switch_mutex_unlock(ei_node->event_streams_mutex); + return erlang_response_badarg(rbuf); + } + } + switch_mutex_unlock(ei_node->event_streams_mutex); + + if (rbuf) { + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_atom(rbuf, "ok"); + + ei_x_encode_tuple_header(rbuf, 2); + ei_x_encode_string(rbuf, ei_node->local_ip); + ei_x_encode_ulong(rbuf, get_stream_port(event_stream)); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_request_fetch_reply(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char section_str[MAXATOMLEN + 1]; + char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; + char *xml_str; + switch_xml_section_t section; + switch_status_t result; + + if (ei_decode_atom_safe(buf->buff, &buf->index, section_str) + || !(section = switch_xml_parse_section_string(section_str))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Ignoring a fetch reply without a configuration section\n"); + return erlang_response_badarg(rbuf); + } + + if (ei_decode_string_or_binary_limited(buf->buff, &buf->index, sizeof(uuid_str), uuid_str) + || zstr_buf(uuid_str)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Ignoring a fetch reply without request UUID\n"); + return erlang_response_badarg(rbuf); + } + + if (ei_decode_string_or_binary(buf->buff, &buf->index, &xml_str)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Ignoring a fetch reply without XML\n"); + return erlang_response_badarg(rbuf); + } + + if (zstr(xml_str)) { + switch_safe_free(xml_str); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Ignoring an empty fetch reply\n"); + return erlang_response_badarg(rbuf); + } + + switch(section) { + case SWITCH_XML_SECTION_CONFIG: + result = fetch_reply(uuid_str, xml_str, globals.config_fetch_binding); + break; + case SWITCH_XML_SECTION_DIRECTORY: + result = fetch_reply(uuid_str, xml_str, globals.directory_fetch_binding); + break; + case SWITCH_XML_SECTION_DIALPLAN: + result = fetch_reply(uuid_str, xml_str, globals.dialplan_fetch_binding); + break; + case SWITCH_XML_SECTION_CHATPLAN: + result = fetch_reply(uuid_str, xml_str, globals.chatplan_fetch_binding); + break; + case SWITCH_XML_SECTION_CHANNELS: + result = fetch_reply(uuid_str, xml_str, globals.channels_fetch_binding); + break; + default: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved fetch reply for an unknown configuration section: %s\n", section_str); + return erlang_response_badarg(rbuf); + } + + if (result == SWITCH_STATUS_SUCCESS) { + return erlang_response_ok(rbuf); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved fetch reply for an unknown/expired UUID: %s\n", uuid_str); + return erlang_response_baduuid(rbuf); + } +} + +static switch_status_t handle_kazoo_request(ei_node_t *ei_node, erlang_pid *pid, ei_x_buff *buf, ei_x_buff *rbuf) { + char atom[MAXATOMLEN + 1]; + int type, size, arity = 0, request; + + /* ...{_, _}} | ...atom()} = Buf */ + ei_get_type(buf->buff, &buf->index, &type, &size); + + /* is_tuple(Type) */ + if (type == ERL_SMALL_TUPLE_EXT) { + /* ..._, _} = Buf */ + ei_decode_tuple_header(buf->buff, &buf->index, &arity); + } + + if (ei_decode_atom_safe(buf->buff, &buf->index, atom)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved mod_kazoo message that did not contain a command (ensure you are using Kazoo v2.14+).\n"); + return erlang_response_badarg(rbuf); + } + + if (find_request(atom, &request) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved mod_kazoo message for unimplemented feature (ensure you are using Kazoo v2.14+): %s\n", atom); + return erlang_response_badarg(rbuf); + } + + switch(request) { + case REQUEST_NOEVENTS: + return handle_request_noevents(ei_node, pid, buf, rbuf); + case REQUEST_EXIT: + return handle_request_exit(ei_node, pid, buf, rbuf); + case REQUEST_LINK: + return handle_request_link(ei_node, pid, buf, rbuf); + case REQUEST_NIXEVENT: + return handle_request_nixevent(ei_node, pid, buf, rbuf); + case REQUEST_SENDEVENT: + return handle_request_sendevent(ei_node, pid, buf, rbuf); + case REQUEST_SENDMSG: + return handle_request_sendmsg(ei_node, pid, buf, rbuf); + case REQUEST_BIND: + return handle_request_bind(ei_node, pid, buf, rbuf); + case REQUEST_GETPID: + return handle_request_getpid(ei_node, pid, buf, rbuf); + case REQUEST_VERSION: + return handle_request_version(ei_node, pid, buf, rbuf); + case REQUEST_BGAPI: + return handle_request_bgapi(ei_node, pid, buf, rbuf); + case REQUEST_API: + return handle_request_api(ei_node, pid, buf, rbuf); + case REQUEST_EVENT: + return handle_request_event(ei_node, pid, buf, rbuf); + case REQUEST_FETCH_REPLY: + return handle_request_fetch_reply(ei_node, pid, buf, rbuf); + default: + return erlang_response_notimplemented(rbuf); + } +} + +static switch_status_t handle_mod_kazoo_request(ei_node_t *ei_node, erlang_msg *msg, ei_x_buff *buf) { + char atom[MAXATOMLEN + 1]; + int version, type, size, arity; + + buf->index = 0; + ei_decode_version(buf->buff, &buf->index, &version); + ei_get_type(buf->buff, &buf->index, &type, &size); + + /* is_tuple(Type) */ + if (type != ERL_SMALL_TUPLE_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received erlang message of an unexpected type (ensure you are using Kazoo v2.14+).\n"); + return SWITCH_STATUS_GENERR; + } + + ei_decode_tuple_header(buf->buff, &buf->index, &arity); + + if (ei_decode_atom_safe(buf->buff, &buf->index, atom)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved erlang message tuple that did not start with an atom (ensure you are using Kazoo v2.14+).\n"); + return SWITCH_STATUS_GENERR; + } + + /* {'$gen_cast', {_, _}} = Buf */ + if (arity == 2 && !strncmp(atom, "$gen_cast", 9)) { + return handle_kazoo_request(ei_node, &msg->from, buf, NULL); + /* {'$gen_call', {_, _}, {_, _}} = Buf */ + } else if (arity == 3 && !strncmp(atom, "$gen_call", 9)) { + switch_status_t status; + ei_send_msg_t *send_msg; + erlang_ref ref; + + switch_malloc(send_msg, sizeof(*send_msg)); + + ei_x_new(&send_msg->buf); + + ei_x_new_with_version(&send_msg->buf); + + /* ...{_, _}, {_, _}} = Buf */ + ei_get_type(buf->buff, &buf->index, &type, &size); + + /* is_tuple(Type) */ + if (type != ERL_SMALL_TUPLE_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received erlang call message of an unexpected type (ensure you are using Kazoo v2.14+).\n"); + return SWITCH_STATUS_GENERR; + } + + /* ..._, _}, {_, _}} = Buf */ + ei_decode_tuple_header(buf->buff, &buf->index, &arity); + + /* ...pid(), _}, {_, _}} = Buf */ + if (ei_decode_pid(buf->buff, &buf->index, &send_msg->pid)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received erlang call without a reply pid (ensure you are using Kazoo v2.14+).\n"); + return SWITCH_STATUS_GENERR; + } + + /* ...ref()}, {_, _}} = Buf */ + if (ei_decode_ref(buf->buff, &buf->index, &ref)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received erlang call without a reply tag (ensure you are using Kazoo v2.14+).\n"); + return SWITCH_STATUS_GENERR; + } + + /* send_msg->buf = {ref(), ... */ + ei_x_encode_tuple_header(&send_msg->buf, 2); + ei_x_encode_ref(&send_msg->buf, &ref); + + status = handle_kazoo_request(ei_node, &msg->from, buf, &send_msg->buf); + + if (switch_queue_trypush(ei_node->send_msgs, send_msg) != SWITCH_STATUS_SUCCESS) { + ei_x_free(&send_msg->buf); + switch_safe_free(send_msg); + } + + return status; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved inappropriate erlang message (ensure you are using Kazoo v2.14+)\n"); + return SWITCH_STATUS_GENERR; + } +} + +/* fake enough of the net_kernel module to be able to respond to net_adm:ping */ +static switch_status_t handle_net_kernel_request(ei_node_t *ei_node, erlang_msg *msg, ei_x_buff *buf) { + int version, size, type, arity; + char atom[MAXATOMLEN + 1]; + ei_send_msg_t *send_msg; + erlang_ref ref; + + switch_malloc(send_msg, sizeof(*send_msg)); + + ei_x_new(&send_msg->buf); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received net_kernel message, attempting to reply\n"); + + buf->index = 0; + ei_decode_version(buf->buff, &buf->index, &version); + ei_get_type(buf->buff, &buf->index, &type, &size); + + /* is_tuple(Buff) */ + if (type != ERL_SMALL_TUPLE_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received net_kernel message of an unexpected type\n"); + return SWITCH_STATUS_GENERR; + } + + ei_decode_tuple_header(buf->buff, &buf->index, &arity); + + /* {_, _, _} = Buf */ + if (arity != 3) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received net_kernel tuple has an unexpected arity\n"); + return SWITCH_STATUS_GENERR; + } + + /* {'$gen_call', _, _} = Buf */ + if (ei_decode_atom_safe(buf->buff, &buf->index, atom) || strncmp(atom, "$gen_call", 9)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received net_kernel message tuple does not begin with the atom '$gen_call'\n"); + return SWITCH_STATUS_GENERR; + } + + ei_get_type(buf->buff, &buf->index, &type, &size); + + /* {_, Sender, _}=Buff, is_tuple(Sender) */ + if (type != ERL_SMALL_TUPLE_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Second element of the net_kernel tuple is an unexpected type\n"); + return SWITCH_STATUS_GENERR; + } + + ei_decode_tuple_header(buf->buff, &buf->index, &arity); + + /* {_, _}=Sender */ + if (arity != 2) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Second element of the net_kernel message has an unexpected arity\n"); + return SWITCH_STATUS_GENERR; + } + + /* {Pid, Ref}=Sender */ + if (ei_decode_pid(buf->buff, &buf->index, &send_msg->pid) || ei_decode_ref(buf->buff, &buf->index, &ref)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Unable to decode erlang pid or ref of the net_kernel tuple second element\n"); + return SWITCH_STATUS_GENERR; + } + + ei_get_type(buf->buff, &buf->index, &type, &size); + + /* {_, _, Request}=Buff, is_tuple(Request) */ + if (type != ERL_SMALL_TUPLE_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Third element of the net_kernel message is an unexpected type\n"); + return SWITCH_STATUS_GENERR; + } + + ei_decode_tuple_header(buf->buff, &buf->index, &arity); + + /* {_, _}=Request */ + if (arity != 2) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Third element of the net_kernel message has an unexpected arity\n"); + return SWITCH_STATUS_GENERR; + } + + /* {is_auth, _}=Request */ + if (ei_decode_atom_safe(buf->buff, &buf->index, atom) || strncmp(atom, "is_auth", MAXATOMLEN)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "The net_kernel message third element does not begin with the atom 'is_auth'\n"); + return SWITCH_STATUS_GENERR; + } + + /* To ! {Tag, Reply} */ + ei_x_new_with_version(&send_msg->buf); + ei_x_encode_tuple_header(&send_msg->buf, 2); + ei_x_encode_ref(&send_msg->buf, &ref); + ei_x_encode_atom(&send_msg->buf, "yes"); + + if (switch_queue_trypush(ei_node->send_msgs, send_msg) != SWITCH_STATUS_SUCCESS) { + ei_x_free(&send_msg->buf); + switch_safe_free(send_msg); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_erl_send(ei_node_t *ei_node, erlang_msg *msg, ei_x_buff *buf) { + if (!strncmp(msg->toname, "net_kernel", MAXATOMLEN)) { + return handle_net_kernel_request(ei_node, msg, buf); + } else if (!strncmp(msg->toname, "mod_kazoo", MAXATOMLEN)) { + return handle_mod_kazoo_request(ei_node, msg, buf); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Recieved erlang message to unknown process \"%s\" (ensure you are using Kazoo v2.14+).\n", msg->toname); + return SWITCH_STATUS_GENERR; + } +} + +static switch_status_t handle_erl_msg(ei_node_t *ei_node, erlang_msg *msg, ei_x_buff *buf) { + switch (msg->msgtype) { + case ERL_SEND: + case ERL_REG_SEND: + return handle_erl_send(ei_node, msg, buf); + case ERL_LINK: + /* we received an erlang link request? Should we be linking or are they linking to us and this just informs us? */ + return SWITCH_STATUS_SUCCESS; + case ERL_UNLINK: + /* we received an erlang unlink request? Same question as the ERL_LINK, are we expected to do something? */ + return SWITCH_STATUS_SUCCESS; + case ERL_EXIT: + /* we received a notice that a process we were linked to has exited, clean up any bindings */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received erlang exit notice for %s <%d.%d.%d>\n", msg->from.node, msg->from.creation, msg->from.num, msg->from.serial); + + switch_mutex_lock(ei_node->event_streams_mutex); + remove_event_stream(&ei_node->event_streams, &msg->from); + switch_mutex_unlock(ei_node->event_streams_mutex); + + remove_fetch_handlers(ei_node, &msg->from); + return SWITCH_STATUS_SUCCESS; + case ERL_EXIT2: + /* erlang nodes appear to send both the old and new style exit notices so just ignore these */ + return SWITCH_STATUS_FALSE; + default: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Received unexpected erlang message type %d\n", (int) (msg->msgtype)); + return SWITCH_STATUS_FALSE; + } +} + +static void *SWITCH_THREAD_FUNC receive_handler(switch_thread_t *thread, void *obj) { + ei_node_t *ei_node = (ei_node_t *) obj; + + switch_atomic_inc(&globals.threads); + switch_atomic_inc(&ei_node->receive_handlers); + + switch_assert(ei_node != NULL); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting erlang receive handler %p: %s (%s:%d)\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + + while (switch_test_flag(ei_node, LFLAG_RUNNING) && switch_test_flag(&globals, LFLAG_RUNNING)) { + void *pop; + + if (switch_queue_pop_timeout(ei_node->received_msgs, &pop, 500000) == SWITCH_STATUS_SUCCESS) { + ei_received_msg_t *received_msg = (ei_received_msg_t *) pop; + handle_erl_msg(ei_node, &received_msg->msg, &received_msg->buf); + ei_x_free(&received_msg->buf); + switch_safe_free(received_msg); + } + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Shutdown erlang receive handler %p: %s (%s:%d)\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + + switch_atomic_dec(&ei_node->receive_handlers); + switch_atomic_dec(&globals.threads); + + return NULL; +} + +static void *SWITCH_THREAD_FUNC handle_node(switch_thread_t *thread, void *obj) { + ei_node_t *ei_node = (ei_node_t *) obj; + ei_received_msg_t *received_msg = NULL; + + switch_atomic_inc(&globals.threads); + + switch_assert(ei_node != NULL); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting node request handler %p: %s (%s:%d)\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + + add_to_ei_nodes(ei_node); + + while (switch_test_flag(ei_node, LFLAG_RUNNING) && switch_test_flag(&globals, LFLAG_RUNNING)) { + int status; + int send_msg_count = 0; + void *pop; + + if (!received_msg) { + switch_malloc(received_msg, sizeof(*received_msg)); + /* create a new buf for the erlang message and a rbuf for the reply */ + if(globals.receive_msg_preallocate > 0) { + received_msg->buf.buff = malloc(globals.receive_msg_preallocate); + received_msg->buf.buffsz = globals.receive_msg_preallocate; + received_msg->buf.index = 0; + if(received_msg->buf.buff == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not pre-allocate memory for mod_kazoo message\n"); + goto exit; + } + } else { + ei_x_new(&received_msg->buf); + } + } + + while (switch_queue_trypop(ei_node->send_msgs, &pop) == SWITCH_STATUS_SUCCESS + && ++send_msg_count <= globals.send_msg_batch) { + ei_send_msg_t *send_msg = (ei_send_msg_t *) pop; + ei_helper_send(ei_node, &send_msg->pid, &send_msg->buf); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sent erlang message to %s <%d.%d.%d>\n" + ,send_msg->pid.node + ,send_msg->pid.creation + ,send_msg->pid.num + ,send_msg->pid.serial); + ei_x_free(&send_msg->buf); + switch_safe_free(send_msg); + } + + /* wait for a erlang message, or timeout to check if the module is still running */ + status = ei_xreceive_msg_tmo(ei_node->nodefd, &received_msg->msg, &received_msg->buf, globals.receive_timeout); + + switch (status) { + case ERL_TICK: + /* erlang nodes send ticks to eachother to validate they are still reachable, we dont have to do anything here */ + break; + case ERL_MSG: + if (switch_queue_trypush(ei_node->received_msgs, received_msg) != SWITCH_STATUS_SUCCESS) { + ei_x_free(&received_msg->buf); + switch_safe_free(received_msg); + } + if (globals.receive_msg_preallocate > 0 && received_msg->buf.buffsz > globals.receive_msg_preallocate) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "increased received message buffer size to %d\n", received_msg->buf.buffsz); + } + received_msg = NULL; + break; + case ERL_ERROR: + switch (erl_errno) { + case ETIMEDOUT: + case EAGAIN: + /* if ei_xreceive_msg_tmo just timed out, ignore it and let the while loop check if we are still running */ + /* the erlang lib just wants us to try to receive again, so we will! */ + break; + case EMSGSIZE: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Erlang communication fault with node %p %s (%s:%d): my spoon is too big\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + switch_clear_flag(ei_node, LFLAG_RUNNING); + break; + case EIO: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Erlang communication fault with node %p %s (%s:%d): socket closed or I/O error\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + switch_clear_flag(ei_node, LFLAG_RUNNING); + break; + default: + /* OH NOS! something has gone horribly wrong, shutdown the connection if status set by ei_xreceive_msg_tmo is less than or equal to 0 */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Erlang communication fault with node %p %s (%s:%d): erl_errno=%d errno=%d\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port, erl_errno, errno); + if (status < 0) { + switch_clear_flag(ei_node, LFLAG_RUNNING); + } + break; + } + break; + default: + /* HUH? didnt plan for this, whatevs shutdown the connection if status set by ei_xreceive_msg_tmo is less than or equal to 0 */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unexpected erlang receive status %p %s (%s:%d): %d\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port, status); + if (status < 0) { + switch_clear_flag(ei_node, LFLAG_RUNNING); + } + break; + } + } + + exit: + + if (received_msg) { + ei_x_free(&received_msg->buf); + switch_safe_free(received_msg); + } + + remove_from_ei_nodes(ei_node); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Shutdown erlang node handler %p: %s (%s:%d)\n", (void *)ei_node, ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + + destroy_node_handler(ei_node); + + switch_atomic_dec(&globals.threads); + return NULL; +} + +/* Create a thread to wait for messages from an erlang node and process them */ +switch_status_t new_kazoo_node(int nodefd, ErlConnect *conn) { + switch_thread_t *thread; + switch_threadattr_t *thd_attr = NULL; + switch_memory_pool_t *pool = NULL; + switch_sockaddr_t *sa; + ei_node_t *ei_node; + int i = 0; + + /* create memory pool for this erlang node */ + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory: Too bad drinking scotch isn't a paying job or Kenny's dad would be a millionare!\n"); + return SWITCH_STATUS_MEMERR; + } + + /* from the erlang node's memory pool, allocate some memory for the structure */ + if (!(ei_node = switch_core_alloc(pool, sizeof (*ei_node)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory: Stan, don't you know the first law of physics? Anything that's fun costs at least eight dollars.\n"); + return SWITCH_STATUS_MEMERR; + } + + memset(ei_node, 0, sizeof(*ei_node)); + + /* store the location of our pool */ + ei_node->pool = pool; + + /* save the file descriptor that the erlang interface lib uses to communicate with the new node */ + ei_node->nodefd = nodefd; + ei_node->peer_nodename = switch_core_strdup(ei_node->pool, conn->nodename); + ei_node->created_time = switch_micro_time_now(); + + /* store the IP and node name we are talking with */ + switch_os_sock_put(&ei_node->socket, (switch_os_socket_t *)&nodefd, pool); + + switch_socket_addr_get(&sa, SWITCH_TRUE, ei_node->socket); + ei_node->local_port = switch_sockaddr_get_port(sa); + switch_get_addr(ei_node->remote_ip, sizeof (ei_node->remote_ip), sa); + + switch_socket_addr_get(&sa, SWITCH_FALSE, ei_node->socket); + ei_node->remote_port = switch_sockaddr_get_port(sa); + switch_get_addr(ei_node->local_ip, sizeof (ei_node->local_ip), sa); + + switch_queue_create(&ei_node->send_msgs, MAX_QUEUE_LEN, pool); + switch_queue_create(&ei_node->received_msgs, MAX_QUEUE_LEN, pool); + + switch_mutex_init(&ei_node->event_streams_mutex, SWITCH_MUTEX_DEFAULT, pool); + + /* when we start we are running */ + switch_set_flag(ei_node, LFLAG_RUNNING); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "New erlang connection from node %s (%s:%d)\n", ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "New erlang connection to node %s (%s:%d)\n", ei_node->peer_nodename, ei_node->local_ip, ei_node->local_port); + + for(i = 0; i < globals.num_worker_threads; i++) { + switch_threadattr_create(&thd_attr, ei_node->pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, receive_handler, ei_node, ei_node->pool); + } + + switch_threadattr_create(&thd_attr, ei_node->pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, handle_node, ei_node, ei_node->pool); + + 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: + */ diff --git a/src/mod/event_handlers/mod_kazoo/kazoo_utils.c b/src/mod/event_handlers/mod_kazoo/kazoo_utils.c new file mode 100644 index 0000000000..e1e356a752 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/kazoo_utils.c @@ -0,0 +1,614 @@ +/* + * 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): + * + * Anthony Minessale II + * Andrew Thompson + * Rob Charlton + * Karl Anderson + * + * Original from mod_erlang_event. + * ei_helpers.c -- helper functions for ei + * + */ +#include +#include +#include +#include +#include +#include +#include "mod_kazoo.h" + +/* Stolen from code added to ei in R12B-5. + * Since not everyone has this version yet; + * provide our own version. + * */ + +#define put8(s,n) do { \ + (s)[0] = (char)((n) & 0xff); \ + (s) += 1; \ + } while (0) + +#define put32be(s,n) do { \ + (s)[0] = ((n) >> 24) & 0xff; \ + (s)[1] = ((n) >> 16) & 0xff; \ + (s)[2] = ((n) >> 8) & 0xff; \ + (s)[3] = (n) & 0xff; \ + (s) += 4; \ + } while (0) + +#ifdef EI_DEBUG +static void ei_x_print_reg_msg(ei_x_buff *buf, char *dest, int send) { + char *mbuf = NULL; + int i = 1; + + ei_s_print_term(&mbuf, buf->buff, &i); + + if (send) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Encoded term %s to '%s'\n", mbuf, dest); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Decoded term %s for '%s'\n", mbuf, dest); + } + + free(mbuf); +} + +static void ei_x_print_msg(ei_x_buff *buf, erlang_pid *pid, int send) { + char *pbuf = NULL; + int i = 0; + ei_x_buff pidbuf; + + ei_x_new(&pidbuf); + ei_x_encode_pid(&pidbuf, pid); + + ei_s_print_term(&pbuf, pidbuf.buff, &i); + + ei_x_print_reg_msg(buf, pbuf, send); + free(pbuf); +} +#endif + +void ei_encode_switch_event_headers(ei_x_buff *ebuf, switch_event_t *event) { + switch_event_header_t *hp; + char *uuid = switch_event_get_header(event, "unique-id"); + int i; + + for (i = 0, hp = event->headers; hp; hp = hp->next, i++); + + if (event->body) + i++; + + ei_x_encode_list_header(ebuf, i + 1); + + if (uuid) { + char *unique_id = switch_event_get_header(event, "unique-id"); + ei_x_encode_binary(ebuf, unique_id, strlen(unique_id)); + } else { + ei_x_encode_atom(ebuf, "undefined"); + } + + for (hp = event->headers; hp; hp = hp->next) { + ei_x_encode_tuple_header(ebuf, 2); + ei_x_encode_binary(ebuf, hp->name, strlen(hp->name)); + switch_url_decode(hp->value); + ei_x_encode_binary(ebuf, hp->value, strlen(hp->value)); + } + + if (event->body) { + ei_x_encode_tuple_header(ebuf, 2); + ei_x_encode_binary(ebuf, "body", strlen("body")); + ei_x_encode_binary(ebuf, event->body, strlen(event->body)); + } + + ei_x_encode_empty_list(ebuf); +} + +void close_socket(switch_socket_t ** sock) { + if (*sock) { + switch_socket_shutdown(*sock, SWITCH_SHUTDOWN_READWRITE); + switch_socket_close(*sock); + *sock = NULL; + } +} + +void close_socketfd(int *sockfd) { + if (*sockfd) { + shutdown(*sockfd, SHUT_RDWR); + close(*sockfd); + } +} + +switch_socket_t *create_socket(switch_memory_pool_t *pool) { + switch_sockaddr_t *sa; + switch_socket_t *socket; + + if(switch_sockaddr_info_get(&sa, globals.ip, SWITCH_UNSPEC, 0, 0, pool)) { + return NULL; + } + + if (switch_socket_create(&socket, switch_sockaddr_get_family(sa), SOCK_STREAM, SWITCH_PROTO_TCP, pool)) { + return NULL; + } + + if (switch_socket_opt_set(socket, SWITCH_SO_REUSEADDR, 1)) { + return NULL; + } + + if (switch_socket_bind(socket, sa)) { + return NULL; + } + + if (switch_socket_listen(socket, 5)){ + return NULL; + } + + // if (globals.nat_map && switch_nat_get_type()) { + // switch_nat_add_mapping(port, SWITCH_NAT_TCP, NULL, SWITCH_FALSE); + // } + + return socket; +} + +switch_status_t create_ei_cnode(const char *ip_addr, const char *name, struct ei_cnode_s *ei_cnode) { + struct hostent *nodehost; + char hostname[EI_MAXHOSTNAMELEN + 1] = ""; + char nodename[MAXNODELEN + 1]; + char cnodename[EI_MAXALIVELEN + 1]; + //EI_MAX_COOKIE_SIZE+1 + char *atsign; + + /* copy the erlang interface nodename into something we can modify */ + strncpy(cnodename, name, EI_MAXALIVELEN); + + if ((atsign = strchr(cnodename, '@'))) { + /* we got a qualified node name, don't guess the host/domain */ + snprintf(nodename, MAXNODELEN + 1, "%s", globals.ei_nodename); + /* truncate the alivename at the @ */ + *atsign = '\0'; + } else { + if ((nodehost = gethostbyaddr(ip_addr, sizeof (ip_addr), AF_INET))) { + memcpy(hostname, nodehost->h_name, EI_MAXHOSTNAMELEN); + } + + if (zstr_buf(hostname) || !strncasecmp(globals.ip, "0.0.0.0", 7)) { + gethostname(hostname, EI_MAXHOSTNAMELEN); + } + + snprintf(nodename, MAXNODELEN + 1, "%s@%s", globals.ei_nodename, hostname); + } + + if (globals.ei_shortname) { + char *off; + if ((off = strchr(nodename, '.'))) { + *off = '\0'; + } + } + + /* init the ec stuff */ + if (ei_connect_xinit(ei_cnode, hostname, cnodename, nodename, (Erl_IpAddr) ip_addr, globals.ei_cookie, 0) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize the erlang interface connection structure\n"); + return SWITCH_STATUS_FALSE; + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t ei_compare_pids(const erlang_pid *pid1, const erlang_pid *pid2) { + if ((!strcmp(pid1->node, pid2->node)) + && pid1->creation == pid2->creation + && pid1->num == pid2->num + && pid1->serial == pid2->serial) { + return SWITCH_STATUS_SUCCESS; + } else { + return SWITCH_STATUS_FALSE; + } +} + +void ei_link(ei_node_t *ei_node, erlang_pid * from, erlang_pid * to) { + char msgbuf[2048]; + char *s; + int index = 0; + + index = 5; /* max sizes: */ + ei_encode_version(msgbuf, &index); /* 1 */ + ei_encode_tuple_header(msgbuf, &index, 3); + ei_encode_long(msgbuf, &index, ERL_LINK); + ei_encode_pid(msgbuf, &index, from); /* 268 */ + ei_encode_pid(msgbuf, &index, to); /* 268 */ + + /* 5 byte header missing */ + s = msgbuf; + put32be(s, index - 4); /* 4 */ + put8(s, ERL_PASS_THROUGH); /* 1 */ + /* sum: 542 */ + + if (write(ei_node->nodefd, msgbuf, index) == -1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to link to process on %s\n", ei_node->peer_nodename); + } +} + +void ei_encode_switch_event(ei_x_buff *ebuf, switch_event_t *event) { + ei_x_encode_tuple_header(ebuf, 2); + ei_x_encode_atom(ebuf, "event"); + ei_encode_switch_event_headers(ebuf, event); +} + +int ei_helper_send(ei_node_t *ei_node, erlang_pid *to, ei_x_buff *buf) { + int ret = 0; + + if (ei_node->nodefd) { +#ifdef EI_DEBUG + ei_x_print_msg(buf, to, 1); +#endif + ret = ei_send(ei_node->nodefd, to, buf->buff, buf->index); + } + + return ret; +} + +int ei_decode_atom_safe(char *buf, int *index, char *dst) { + int type, size; + + ei_get_type(buf, index, &type, &size); + + if (type != ERL_ATOM_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unexpected erlang term type %d (size %d), needed atom\n", type, size); + return -1; + } else if (size > MAXATOMLEN) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Requested decoding of atom with size %d into a buffer of size %d\n", size, MAXATOMLEN); + return -1; + } else { + return ei_decode_atom(buf, index, dst); + } +} + +int ei_decode_string_or_binary(char *buf, int *index, char **dst) { + int type, size, res; + long len; + + ei_get_type(buf, index, &type, &size); + + if (type != ERL_STRING_EXT && type != ERL_BINARY_EXT && type != ERL_NIL_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unexpected erlang term type %d (size %d), needed binary or string\n", type, size); + return -1; + } + + *dst = malloc(size + 1); + + if (type == ERL_NIL_EXT) { + res = 0; + **dst = '\0'; + } else if (type == ERL_BINARY_EXT) { + res = ei_decode_binary(buf, index, *dst, &len); + (*dst)[len] = '\0'; + } else { + res = ei_decode_string(buf, index, *dst); + } + + return res; +} + +int ei_decode_string_or_binary_limited(char *buf, int *index, int maxsize, char *dst) { + int type, size, res; + long len; + + ei_get_type(buf, index, &type, &size); + + if (type != ERL_STRING_EXT && type != ERL_BINARY_EXT && type != ERL_NIL_EXT) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unexpected erlang term type %d (size %d), needed binary or string\n", type, size); + return -1; + } + + if (size > maxsize) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Requested decoding of %s with size %d into a buffer of size %d\n", + type == ERL_BINARY_EXT ? "binary" : "string", size, maxsize); + return -1; + } + + if (type == ERL_NIL_EXT) { + res = 0; + dst = '\0'; + } else if (type == ERL_BINARY_EXT) { + res = ei_decode_binary(buf, index, dst, &len); + dst[len] = '\0'; /* binaries aren't null terminated */ + } else { + res = ei_decode_string(buf, index, dst); + } + + return res; +} + +switch_hash_t *create_default_filter() { + switch_hash_t *filter; + + switch_core_hash_init(&filter); + + switch_core_hash_insert(filter, "Acquired-UUID", "1"); + switch_core_hash_insert(filter, "action", "1"); + switch_core_hash_insert(filter, "Action", "1"); + switch_core_hash_insert(filter, "alt_event_type", "1"); + switch_core_hash_insert(filter, "Answer-State", "1"); + switch_core_hash_insert(filter, "Application", "1"); + switch_core_hash_insert(filter, "Application-Data", "1"); + switch_core_hash_insert(filter, "Application-Name", "1"); + switch_core_hash_insert(filter, "Application-Response", "1"); + switch_core_hash_insert(filter, "att_xfer_replaced_by", "1"); + switch_core_hash_insert(filter, "Auth-Method", "1"); + switch_core_hash_insert(filter, "Auth-Realm", "1"); + switch_core_hash_insert(filter, "Auth-User", "1"); + switch_core_hash_insert(filter, "Bridge-A-Unique-ID", "1"); + switch_core_hash_insert(filter, "Bridge-B-Unique-ID", "1"); + switch_core_hash_insert(filter, "Call-Direction", "1"); + switch_core_hash_insert(filter, "Caller-Callee-ID-Name", "1"); + switch_core_hash_insert(filter, "Caller-Callee-ID-Number", "1"); + switch_core_hash_insert(filter, "Caller-Caller-ID-Name", "1"); + switch_core_hash_insert(filter, "Caller-Caller-ID-Number", "1"); + switch_core_hash_insert(filter, "Caller-Context", "1"); + switch_core_hash_insert(filter, "Caller-Controls", "1"); + switch_core_hash_insert(filter, "Caller-Destination-Number", "1"); + switch_core_hash_insert(filter, "Caller-Dialplan", "1"); + switch_core_hash_insert(filter, "Caller-Network-Addr", "1"); + switch_core_hash_insert(filter, "Caller-Unique-ID", "1"); + switch_core_hash_insert(filter, "Call-ID", "1"); + switch_core_hash_insert(filter, "Channel-Call-State", "1"); + switch_core_hash_insert(filter, "Channel-Call-UUID", "1"); + switch_core_hash_insert(filter, "Channel-Presence-ID", "1"); + switch_core_hash_insert(filter, "Channel-State", "1"); + switch_core_hash_insert(filter, "Chat-Permissions", "1"); + switch_core_hash_insert(filter, "Conference-Name", "1"); + switch_core_hash_insert(filter, "Conference-Profile-Name", "1"); + switch_core_hash_insert(filter, "Conference-Unique-ID", "1"); + switch_core_hash_insert(filter, "contact", "1"); + switch_core_hash_insert(filter, "Detected-Tone", "1"); + switch_core_hash_insert(filter, "dialog_state", "1"); + switch_core_hash_insert(filter, "direction", "1"); + switch_core_hash_insert(filter, "Distributed-From", "1"); + switch_core_hash_insert(filter, "DTMF-Digit", "1"); + switch_core_hash_insert(filter, "DTMF-Duration", "1"); + switch_core_hash_insert(filter, "Event-Date-Timestamp", "1"); + switch_core_hash_insert(filter, "Event-Name", "1"); + switch_core_hash_insert(filter, "Event-Subclass", "1"); + switch_core_hash_insert(filter, "expires", "1"); + switch_core_hash_insert(filter, "Expires", "1"); + switch_core_hash_insert(filter, "Ext-SIP-IP", "1"); + switch_core_hash_insert(filter, "File", "1"); + switch_core_hash_insert(filter, "FreeSWITCH-Hostname", "1"); + switch_core_hash_insert(filter, "from", "1"); + switch_core_hash_insert(filter, "Hunt-Destination-Number", "1"); + switch_core_hash_insert(filter, "ip", "1"); + switch_core_hash_insert(filter, "Message-Account", "1"); + switch_core_hash_insert(filter, "metadata", "1"); + switch_core_hash_insert(filter, "old_node_channel_uuid", "1"); + switch_core_hash_insert(filter, "Other-Leg-Callee-ID-Name", "1"); + switch_core_hash_insert(filter, "Other-Leg-Callee-ID-Number", "1"); + switch_core_hash_insert(filter, "Other-Leg-Caller-ID-Name", "1"); + switch_core_hash_insert(filter, "Other-Leg-Caller-ID-Number", "1"); + switch_core_hash_insert(filter, "Other-Leg-Destination-Number", "1"); + switch_core_hash_insert(filter, "Other-Leg-Direction", "1"); + switch_core_hash_insert(filter, "Other-Leg-Unique-ID", "1"); + switch_core_hash_insert(filter, "Other-Leg-Channel-Name", "1"); + switch_core_hash_insert(filter, "Participant-Type", "1"); + switch_core_hash_insert(filter, "Path", "1"); + switch_core_hash_insert(filter, "profile_name", "1"); + switch_core_hash_insert(filter, "Profiles", "1"); + switch_core_hash_insert(filter, "proto-specific-event-name", "1"); + switch_core_hash_insert(filter, "Raw-Application-Data", "1"); + switch_core_hash_insert(filter, "realm", "1"); + switch_core_hash_insert(filter, "Resigning-UUID", "1"); + switch_core_hash_insert(filter, "set", "1"); + switch_core_hash_insert(filter, "sip_auto_answer", "1"); + switch_core_hash_insert(filter, "sip_auth_method", "1"); + switch_core_hash_insert(filter, "sip_from_host", "1"); + switch_core_hash_insert(filter, "sip_from_user", "1"); + switch_core_hash_insert(filter, "sip_to_host", "1"); + switch_core_hash_insert(filter, "sip_to_user", "1"); + switch_core_hash_insert(filter, "sub-call-id", "1"); + switch_core_hash_insert(filter, "technology", "1"); + switch_core_hash_insert(filter, "to", "1"); + switch_core_hash_insert(filter, "Unique-ID", "1"); + switch_core_hash_insert(filter, "URL", "1"); + switch_core_hash_insert(filter, "username", "1"); + switch_core_hash_insert(filter, "variable_channel_is_moving", "1"); + switch_core_hash_insert(filter, "variable_collected_digits", "1"); + switch_core_hash_insert(filter, "variable_current_application", "1"); + switch_core_hash_insert(filter, "variable_current_application_data", "1"); + switch_core_hash_insert(filter, "variable_domain_name", "1"); + switch_core_hash_insert(filter, "variable_effective_caller_id_name", "1"); + switch_core_hash_insert(filter, "variable_effective_caller_id_number", "1"); + switch_core_hash_insert(filter, "variable_holding_uuid", "1"); + switch_core_hash_insert(filter, "variable_hold_music", "1"); + switch_core_hash_insert(filter, "variable_media_group_id", "1"); + switch_core_hash_insert(filter, "variable_originate_disposition", "1"); + switch_core_hash_insert(filter, "variable_origination_uuid", "1"); + switch_core_hash_insert(filter, "variable_playback_terminator_used", "1"); + switch_core_hash_insert(filter, "variable_presence_id", "1"); + switch_core_hash_insert(filter, "variable_record_ms", "1"); + switch_core_hash_insert(filter, "variable_recovered", "1"); + switch_core_hash_insert(filter, "variable_silence_hits_exhausted", "1"); + switch_core_hash_insert(filter, "variable_sip_auth_realm", "1"); + switch_core_hash_insert(filter, "variable_sip_from_host", "1"); + switch_core_hash_insert(filter, "variable_sip_from_user", "1"); + switch_core_hash_insert(filter, "variable_sip_from_tag", "1"); + switch_core_hash_insert(filter, "variable_sip_h_X-AUTH-IP", "1"); + switch_core_hash_insert(filter, "variable_sip_received_ip", "1"); + switch_core_hash_insert(filter, "variable_sip_to_host", "1"); + switch_core_hash_insert(filter, "variable_sip_to_user", "1"); + switch_core_hash_insert(filter, "variable_sip_to_tag", "1"); + switch_core_hash_insert(filter, "variable_sofia_profile_name", "1"); + switch_core_hash_insert(filter, "variable_transfer_history", "1"); + switch_core_hash_insert(filter, "variable_user_name", "1"); + switch_core_hash_insert(filter, "variable_endpoint_disposition", "1"); + switch_core_hash_insert(filter, "variable_originate_disposition", "1"); + switch_core_hash_insert(filter, "variable_bridge_hangup_cause", "1"); + switch_core_hash_insert(filter, "variable_hangup_cause", "1"); + switch_core_hash_insert(filter, "variable_last_bridge_proto_specific_hangup_cause", "1"); + switch_core_hash_insert(filter, "variable_proto_specific_hangup_cause", "1"); + switch_core_hash_insert(filter, "VM-Call-ID", "1"); + switch_core_hash_insert(filter, "VM-sub-call-id", "1"); + switch_core_hash_insert(filter, "whistle_application_name", "1"); + switch_core_hash_insert(filter, "whistle_application_response", "1"); + switch_core_hash_insert(filter, "whistle_event_name", "1"); + switch_core_hash_insert(filter, "sip_auto_answer_notify", "1"); + switch_core_hash_insert(filter, "eavesdrop_group", "1"); + switch_core_hash_insert(filter, "origination_caller_id_name", "1"); + switch_core_hash_insert(filter, "origination_caller_id_number", "1"); + switch_core_hash_insert(filter, "origination_callee_id_name", "1"); + switch_core_hash_insert(filter, "origination_callee_id_number", "1"); + switch_core_hash_insert(filter, "sip_auth_username", "1"); + switch_core_hash_insert(filter, "sip_auth_password", "1"); + switch_core_hash_insert(filter, "effective_caller_id_name", "1"); + switch_core_hash_insert(filter, "effective_caller_id_number", "1"); + switch_core_hash_insert(filter, "effective_callee_id_name", "1"); + switch_core_hash_insert(filter, "effective_callee_id_number", "1"); + + /* Registration headers */ + switch_core_hash_insert(filter, "call-id", "1"); + switch_core_hash_insert(filter, "profile-name", "1"); + switch_core_hash_insert(filter, "from-user", "1"); + switch_core_hash_insert(filter, "from-host", "1"); + switch_core_hash_insert(filter, "presence-hosts", "1"); + switch_core_hash_insert(filter, "contact", "1"); + switch_core_hash_insert(filter, "rpid", "1"); + switch_core_hash_insert(filter, "status", "1"); + switch_core_hash_insert(filter, "expires", "1"); + switch_core_hash_insert(filter, "to-user", "1"); + switch_core_hash_insert(filter, "to-host", "1"); + switch_core_hash_insert(filter, "network-ip", "1"); + switch_core_hash_insert(filter, "network-port", "1"); + switch_core_hash_insert(filter, "username", "1"); + switch_core_hash_insert(filter, "realm", "1"); + switch_core_hash_insert(filter, "user-agent", "1"); + + switch_core_hash_insert(filter, "Hangup-Cause", "1"); + switch_core_hash_insert(filter, "Unique-ID", "1"); + switch_core_hash_insert(filter, "variable_switch_r_sdp", "1"); + switch_core_hash_insert(filter, "variable_sip_local_sdp_str", "1"); + switch_core_hash_insert(filter, "variable_sip_to_uri", "1"); + switch_core_hash_insert(filter, "variable_sip_from_uri", "1"); + switch_core_hash_insert(filter, "variable_sip_user_agent", "1"); + switch_core_hash_insert(filter, "variable_duration", "1"); + switch_core_hash_insert(filter, "variable_billsec", "1"); + switch_core_hash_insert(filter, "variable_progresssec", "1"); + switch_core_hash_insert(filter, "variable_progress_uepoch", "1"); + switch_core_hash_insert(filter, "variable_progress_media_uepoch", "1"); + switch_core_hash_insert(filter, "variable_start_uepoch", "1"); + switch_core_hash_insert(filter, "variable_digits_dialed", "1"); + switch_core_hash_insert(filter, "Member-ID", "1"); + switch_core_hash_insert(filter, "Floor", "1"); + switch_core_hash_insert(filter, "Video", "1"); + switch_core_hash_insert(filter, "Hear", "1"); + switch_core_hash_insert(filter, "Speak", "1"); + switch_core_hash_insert(filter, "Talking", "1"); + switch_core_hash_insert(filter, "Current-Energy", "1"); + switch_core_hash_insert(filter, "Energy-Level", "1"); + switch_core_hash_insert(filter, "Mute-Detect", "1"); + + /* RTMP headers */ + switch_core_hash_insert(filter, "RTMP-Session-ID", "1"); + switch_core_hash_insert(filter, "RTMP-Profile", "1"); + switch_core_hash_insert(filter, "RTMP-Flash-Version", "1"); + switch_core_hash_insert(filter, "RTMP-SWF-URL", "1"); + switch_core_hash_insert(filter, "RTMP-TC-URL", "1"); + switch_core_hash_insert(filter, "RTMP-Page-URL", "1"); + switch_core_hash_insert(filter, "User", "1"); + switch_core_hash_insert(filter, "Domain", "1"); + + /* Fax headers */ + switch_core_hash_insert(filter, "variable_fax_bad_rows", "1"); + switch_core_hash_insert(filter, "variable_fax_document_total_pages", "1"); + switch_core_hash_insert(filter, "variable_fax_document_transferred_pages", "1"); + switch_core_hash_insert(filter, "variable_fax_ecm_used", "1"); + switch_core_hash_insert(filter, "variable_fax_result_code", "1"); + switch_core_hash_insert(filter, "variable_fax_result_text", "1"); + switch_core_hash_insert(filter, "variable_fax_success", "1"); + switch_core_hash_insert(filter, "variable_fax_transfer_rate", "1"); + switch_core_hash_insert(filter, "variable_fax_local_station_id", "1"); + switch_core_hash_insert(filter, "variable_fax_remote_station_id", "1"); + switch_core_hash_insert(filter, "variable_fax_remote_country", "1"); + switch_core_hash_insert(filter, "variable_fax_remote_vendor", "1"); + switch_core_hash_insert(filter, "variable_fax_remote_model", "1"); + switch_core_hash_insert(filter, "variable_fax_image_resolution", "1"); + switch_core_hash_insert(filter, "variable_fax_file_image_resolution", "1"); + switch_core_hash_insert(filter, "variable_fax_image_size", "1"); + switch_core_hash_insert(filter, "variable_fax_image_pixel_size", "1"); + switch_core_hash_insert(filter, "variable_fax_file_image_pixel_size", "1"); + switch_core_hash_insert(filter, "variable_fax_longest_bad_row_run", "1"); + switch_core_hash_insert(filter, "variable_fax_encoding", "1"); + switch_core_hash_insert(filter, "variable_fax_encoding_name", "1"); + switch_core_hash_insert(filter, "variable_fax_header", "1"); + switch_core_hash_insert(filter, "variable_fax_ident", "1"); + switch_core_hash_insert(filter, "variable_fax_timezone", "1"); + switch_core_hash_insert(filter, "variable_fax_doc_id", "1"); + switch_core_hash_insert(filter, "variable_fax_doc_database", "1"); + + /* Secure headers */ + /* + switch_core_hash_insert(filter, "variable_sdp_secure_savp_only", "1"); + switch_core_hash_insert(filter, "variable_rtp_has_crypto", "1"); + switch_core_hash_insert(filter, "variable_rtp_secure_media", "1"); + switch_core_hash_insert(filter, "variable_rtp_secure_media_confirmed", "1"); + switch_core_hash_insert(filter, "variable_rtp_secure_media_confirmed_audio", "1"); + switch_core_hash_insert(filter, "variable_rtp_secure_media_confirmed_video", "1"); + switch_core_hash_insert(filter, "variable_zrtp_secure_media", "1"); + switch_core_hash_insert(filter, "variable_zrtp_secure_media_confirmed", "1"); + switch_core_hash_insert(filter, "variable_zrtp_secure_media_confirmed_audio", "1"); + switch_core_hash_insert(filter, "variable_zrtp_secure_media_confirmed_video", "1"); + switch_core_hash_insert(filter, "sdp_secure_savp_only", "1"); + switch_core_hash_insert(filter, "rtp_has_crypto", "1"); + switch_core_hash_insert(filter, "rtp_secure_media", "1"); + switch_core_hash_insert(filter, "rtp_secure_media_confirmed", "1"); + switch_core_hash_insert(filter, "rtp_secure_media_confirmed_audio", "1"); + switch_core_hash_insert(filter, "rtp_secure_media_confirmed_video", "1"); + switch_core_hash_insert(filter, "zrtp_secure_media", "1"); + switch_core_hash_insert(filter, "zrtp_secure_media_confirmed", "1"); + switch_core_hash_insert(filter, "zrtp_secure_media_confirmed_audio", "1"); + switch_core_hash_insert(filter, "zrtp_secure_media_confirmed_video", "1"); + */ + + /* Device Redirect headers */ + /* + switch_core_hash_insert(filter, "variable_last_bridge_hangup_cause", "1"); + switch_core_hash_insert(filter, "variable_sip_redirected_by", "1"); + */ + + switch_core_hash_insert(filter, "intercepted_by", "1"); + + // SMS + switch_core_hash_insert(filter, "Message-ID", "1"); + switch_core_hash_insert(filter, "Delivery-Failure", "1"); + switch_core_hash_insert(filter, "Delivery-Result-Code", "1"); + + return filter; +} + +/* 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: + */ diff --git a/src/mod/event_handlers/mod_kazoo/mod_kazoo.c b/src/mod/event_handlers/mod_kazoo/mod_kazoo.c new file mode 100644 index 0000000000..c0931e35c6 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/mod_kazoo.c @@ -0,0 +1,776 @@ +/* + * 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): + * + * Karl Anderson + * Darren Schreiber + * + * + * mod_kazoo.c -- Socket Controlled Event Handler + * + */ +#include "mod_kazoo.h" + +#define KAZOO_DESC "kazoo information" +#define KAZOO_SYNTAX " []" + +SWITCH_MODULE_LOAD_FUNCTION(mod_kazoo_load); +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_kazoo_shutdown); +SWITCH_MODULE_RUNTIME_FUNCTION(mod_kazoo_runtime); +SWITCH_MODULE_DEFINITION(mod_kazoo, mod_kazoo_load, mod_kazoo_shutdown, mod_kazoo_runtime); + +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_pref_ip, globals.ip); +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_pref_ei_cookie, globals.ei_cookie); +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_pref_ei_nodename, globals.ei_nodename); +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_pref_kazoo_var_prefix, globals.kazoo_var_prefix); + +static switch_status_t api_erlang_status(switch_stream_handle_t *stream) { + switch_sockaddr_t *sa; + uint16_t port; + char ipbuf[25]; + const char *ip_addr; + ei_node_t *ei_node; + + switch_socket_addr_get(&sa, SWITCH_FALSE, globals.acceptor); + + port = switch_sockaddr_get_port(sa); + ip_addr = switch_get_addr(ipbuf, sizeof (ipbuf), sa); + + stream->write_function(stream, "Running %s\n", VERSION); + stream->write_function(stream, "Listening for new Erlang connections on %s:%u with cookie %s\n", ip_addr, port, globals.ei_cookie); + stream->write_function(stream, "Registered as Erlang node %s, visible as %s\n", globals.ei_cnode.thisnodename, globals.ei_cnode.thisalivename); + + if (globals.ei_compat_rel) { + stream->write_function(stream, "Using Erlang compatibility mode: %d\n", globals.ei_compat_rel); + } + + switch_thread_rwlock_rdlock(globals.ei_nodes_lock); + ei_node = globals.ei_nodes; + if (!ei_node) { + stream->write_function(stream, "No erlang nodes connected\n"); + } else { + stream->write_function(stream, "Connected to:\n"); + while(ei_node != NULL) { + unsigned int year, day, hour, min, sec, delta; + + delta = (switch_micro_time_now() - ei_node->created_time) / 1000000; + sec = delta % 60; + min = delta / 60 % 60; + hour = delta / 3600 % 24; + day = delta / 86400 % 7; + year = delta / 31556926 % 12; + stream->write_function(stream, " %s (%s:%d) up %d years, %d days, %d hours, %d minutes, %d seconds\n" + ,ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port, year, day, hour, min, sec); + ei_node = ei_node->next; + } + } + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t api_erlang_event_filter(switch_stream_handle_t *stream) { + switch_hash_index_t *hi = NULL; + int column = 0; + + for (hi = (switch_hash_index_t *)switch_core_hash_first_iter(globals.event_filter, hi); hi; hi = switch_core_hash_next(&hi)) { + const void *key; + void *val; + switch_core_hash_this(hi, &key, NULL, &val); + stream->write_function(stream, "%-50s", (char *)key); + if (++column > 2) { + stream->write_function(stream, "\n"); + column = 0; + } + } + + if (++column > 2) { + stream->write_function(stream, "\n"); + column = 0; + } + + stream->write_function(stream, "%-50s", globals.kazoo_var_prefix); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t api_erlang_nodes_list(switch_stream_handle_t *stream) { + ei_node_t *ei_node; + + switch_thread_rwlock_rdlock(globals.ei_nodes_lock); + ei_node = globals.ei_nodes; + while(ei_node != NULL) { + stream->write_function(stream, "%s (%s)\n", ei_node->peer_nodename, ei_node->remote_ip); + ei_node = ei_node->next; + } + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t api_erlang_nodes_count(switch_stream_handle_t *stream) { + ei_node_t *ei_node; + int count = 0; + + switch_thread_rwlock_rdlock(globals.ei_nodes_lock); + ei_node = globals.ei_nodes; + while(ei_node != NULL) { + count++; + ei_node = ei_node->next; + } + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + stream->write_function(stream, "%d\n", count); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t api_complete_erlang_node(const char *line, const char *cursor, switch_console_callback_match_t **matches) { + switch_console_callback_match_t *my_matches = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + ei_node_t *ei_node; + + switch_thread_rwlock_rdlock(globals.ei_nodes_lock); + ei_node = globals.ei_nodes; + while(ei_node != NULL) { + switch_console_push_match(&my_matches, ei_node->peer_nodename); + ei_node = ei_node->next; + } + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + if (my_matches) { + *matches = my_matches; + status = SWITCH_STATUS_SUCCESS; + } + + return status; +} + +static switch_status_t handle_node_api_event_stream(ei_event_stream_t *event_stream, switch_stream_handle_t *stream) { + ei_event_binding_t *binding; + int column = 0; + + switch_mutex_lock(event_stream->socket_mutex); + if (event_stream->connected == SWITCH_FALSE) { + switch_sockaddr_t *sa; + uint16_t port; + char ipbuf[25] = {0}; + const char *ip_addr; + + switch_socket_addr_get(&sa, SWITCH_TRUE, event_stream->acceptor); + port = switch_sockaddr_get_port(sa); + ip_addr = switch_get_addr(ipbuf, sizeof (ipbuf), sa); + + if (zstr(ip_addr)) { + ip_addr = globals.ip; + } + + stream->write_function(stream, "%s:%d -> disconnected\n" + ,ip_addr, port); + } else { + stream->write_function(stream, "%s:%d -> %s:%d\n" + ,event_stream->local_ip, event_stream->local_port + ,event_stream->remote_ip, event_stream->remote_port); + } + + binding = event_stream->bindings; + while(binding != NULL) { + if (binding->type == SWITCH_EVENT_CUSTOM) { + stream->write_function(stream, "CUSTOM %-43s", binding->subclass_name); + } else { + stream->write_function(stream, "%-50s", switch_event_name(binding->type)); + } + + if (++column > 2) { + stream->write_function(stream, "\n"); + column = 0; + } + + binding = binding->next; + } + switch_mutex_unlock(event_stream->socket_mutex); + + if (!column) { + stream->write_function(stream, "\n"); + } else { + stream->write_function(stream, "\n\n"); + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_node_api_event_streams(ei_node_t *ei_node, switch_stream_handle_t *stream) { + ei_event_stream_t *event_stream; + + switch_mutex_lock(ei_node->event_streams_mutex); + event_stream = ei_node->event_streams; + while(event_stream != NULL) { + handle_node_api_event_stream(event_stream, stream); + event_stream = event_stream->next; + } + switch_mutex_unlock(ei_node->event_streams_mutex); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t handle_node_api_command(ei_node_t *ei_node, switch_stream_handle_t *stream, uint32_t command) { + unsigned int year, day, hour, min, sec, delta; + + switch (command) { + case API_COMMAND_DISCONNECT: + stream->write_function(stream, "Disconnecting erlang node %s at managers request\n", ei_node->peer_nodename); + switch_clear_flag(ei_node, LFLAG_RUNNING); + break; + case API_COMMAND_REMOTE_IP: + delta = (switch_micro_time_now() - ei_node->created_time) / 1000000; + sec = delta % 60; + min = delta / 60 % 60; + hour = delta / 3600 % 24; + day = delta / 86400 % 7; + year = delta / 31556926 % 12; + + stream->write_function(stream, "Uptime %d years, %d days, %d hours, %d minutes, %d seconds\n", year, day, hour, min, sec); + stream->write_function(stream, "Local Address %s:%d\n", ei_node->local_ip, ei_node->local_port); + stream->write_function(stream, "Remote Address %s:%d\n", ei_node->remote_ip, ei_node->remote_port); + break; + case API_COMMAND_STREAMS: + handle_node_api_event_streams(ei_node, stream); + break; + case API_COMMAND_BINDINGS: + handle_api_command_streams(ei_node, stream); + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t api_erlang_node_command(switch_stream_handle_t *stream, const char *nodename, uint32_t command) { + ei_node_t *ei_node; + + switch_thread_rwlock_rdlock(globals.ei_nodes_lock); + ei_node = globals.ei_nodes; + while(ei_node != NULL) { + int length = strlen(ei_node->peer_nodename); + + if (!strncmp(ei_node->peer_nodename, nodename, length)) { + handle_node_api_command(ei_node, stream, command); + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + return SWITCH_STATUS_SUCCESS; + } + + ei_node = ei_node->next; + } + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + + return SWITCH_STATUS_NOTFOUND; +} + +static int read_cookie_from_file(char *filename) { + int fd; + char cookie[MAXATOMLEN + 1]; + char *end; + struct stat buf; + ssize_t res; + + if (!stat(filename, &buf)) { + if ((buf.st_mode & S_IRWXG) || (buf.st_mode & S_IRWXO)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s must only be accessible by owner only.\n", filename); + return 2; + } + if (buf.st_size > MAXATOMLEN) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s contains a cookie larger than the maximum atom size of %d.\n", filename, MAXATOMLEN); + return 2; + } + fd = open(filename, O_RDONLY); + if (fd < 1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open cookie file %s : %d.\n", filename, errno); + return 2; + } + + if ((res = read(fd, cookie, MAXATOMLEN)) < 1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read cookie file %s : %d.\n", filename, errno); + } + + cookie[MAXATOMLEN] = '\0'; + + /* replace any end of line characters with a null */ + if ((end = strchr(cookie, '\n'))) { + *end = '\0'; + } + + if ((end = strchr(cookie, '\r'))) { + *end = '\0'; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set cookie from file %s: %s\n", filename, cookie); + + set_pref_ei_cookie(cookie); + return 0; + } else { + /* don't error here, because we might be blindly trying to read $HOME/.erlang.cookie, and that can fail silently */ + return 1; + } +} + +static switch_status_t config(void) { + char *cf = "kazoo.conf"; + switch_xml_t cfg, xml, child, param; + globals.send_all_headers = globals.send_all_private_headers = 0; + globals.connection_timeout = 500; + globals.receive_timeout = 200; + globals.receive_msg_preallocate = 2000; + globals.event_stream_preallocate = 4000; + globals.send_msg_batch = 10; + globals.event_stream_framing = 2; + + if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to open configuration file %s\n", cf); + return SWITCH_STATUS_FALSE; + } else { + if ((child = switch_xml_child(cfg, "settings"))) { + for (param = switch_xml_child(child, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + if (!strcmp(var, "listen-ip")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set bind ip address: %s\n", val); + set_pref_ip(val); + } else if (!strcmp(var, "cookie")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set cookie: %s\n", val); + set_pref_ei_cookie(val); + } else if (!strcmp(var, "cookie-file")) { + if (read_cookie_from_file(val) == 1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read cookie from %s\n", val); + } + } else if (!strcmp(var, "nodename")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set node name: %s\n", val); + set_pref_ei_nodename(val); + } else if (!strcmp(var, "shortname")) { + globals.ei_shortname = switch_true(val); + } else if (!strcmp(var, "kazoo-var-prefix")) { + set_pref_kazoo_var_prefix(val); + } else if (!strcmp(var, "compat-rel")) { + if (atoi(val) >= 7) + globals.ei_compat_rel = atoi(val); + else + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid compatibility release '%s' specified\n", val); + } else if (!strcmp(var, "nat-map")) { + globals.nat_map = switch_true(val); + } else if (!strcmp(var, "send-all-headers")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set send-all-headers: %s\n", val); + globals.send_all_headers = switch_true(val); + } else if (!strcmp(var, "send-all-private-headers")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set send-all-private-headers: %s\n", val); + globals.send_all_private_headers = switch_true(val); + } else if (!strcmp(var, "connection-timeout")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set connection-timeout: %s\n", val); + globals.connection_timeout = atoi(val); + } else if (!strcmp(var, "receive-timeout")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set receive-timeout: %s\n", val); + globals.receive_timeout = atoi(val); + } else if (!strcmp(var, "receive-msg-preallocate")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set receive-msg-preallocate: %s\n", val); + globals.receive_msg_preallocate = atoi(val); + } else if (!strcmp(var, "event-stream-preallocate")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set event-stream-preallocate: %s\n", val); + globals.event_stream_preallocate = atoi(val); + } else if (!strcmp(var, "send-msg-batch-size")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set send-msg-batch-size: %s\n", val); + globals.send_msg_batch = atoi(val); + } else if (!strcmp(var, "event-stream-framing")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set event-stream-framing: %s\n", val); + globals.event_stream_framing = atoi(val); + } + } + } + + if ((child = switch_xml_child(cfg, "event-filter"))) { + switch_hash_t *filter; + + switch_core_hash_init(&filter); + for (param = switch_xml_child(child, "header"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + switch_core_hash_insert(filter, var, "1"); + } + + globals.event_filter = filter; + } + + switch_xml_free(xml); + } + + if (globals.receive_msg_preallocate < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid receive message preallocate value, disabled\n"); + globals.receive_msg_preallocate = 0; + } + + if (globals.event_stream_preallocate < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid event stream preallocate value, disabled\n"); + globals.event_stream_preallocate = 0; + } + + if (globals.send_msg_batch < 1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid send message batch size, reverting to default\n"); + globals.send_msg_batch = 10; + } + + if (!globals.event_filter) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Event filter not found in configuration, using default\n"); + globals.event_filter = create_default_filter(); + } + + if (globals.event_stream_framing < 1 || globals.event_stream_framing > 4) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid event stream framing value, using default\n"); + globals.event_stream_framing = 2; + } + + if (zstr(globals.kazoo_var_prefix)) { + set_pref_kazoo_var_prefix("variable_ecallmgr*"); + globals.var_prefix_length = 17; //ignore the * + } else { + /* we could use the global pool but then we would have to conditionally + * free the pointer if it was not drawn from the XML */ + char *buf; + int size = switch_snprintf(NULL, 0, "variable_%s*", globals.kazoo_var_prefix) + 1; + + switch_malloc(buf, size); + switch_snprintf(buf, size, "variable_%s*", globals.kazoo_var_prefix); + switch_safe_free(globals.kazoo_var_prefix); + globals.kazoo_var_prefix = buf; + globals.var_prefix_length = size - 2; //ignore the * + } + + if (!globals.num_worker_threads) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Number of worker threads not found in configuration, using default\n"); + globals.num_worker_threads = 10; + } + + if (zstr(globals.ip)) { + set_pref_ip("0.0.0.0"); + } + + if (zstr(globals.ei_cookie)) { + int res; + char *home_dir = getenv("HOME"); + char path_buf[1024]; + + if (!zstr(home_dir)) { + /* $HOME/.erlang.cookie */ + switch_snprintf(path_buf, sizeof (path_buf), "%s%s%s", home_dir, SWITCH_PATH_SEPARATOR, ".erlang.cookie"); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Checking for cookie at path: %s\n", path_buf); + + res = read_cookie_from_file(path_buf); + if (res) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No cookie or valid cookie file specified, using default cookie\n"); + set_pref_ei_cookie("ClueCon"); + } + } + } + + if (!globals.ei_nodename) { + set_pref_ei_nodename("freeswitch"); + } + + if (!globals.nat_map) { + globals.nat_map = 0; + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t create_acceptor() { + switch_sockaddr_t *sa; + uint16_t port; + char ipbuf[25]; + const char *ip_addr; + + /* if the config has specified an erlang release compatibility then pass that along to the erlang interface */ + if (globals.ei_compat_rel) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Compatability with OTP R%d requested\n", globals.ei_compat_rel); + ei_set_compat_rel(globals.ei_compat_rel); + } + + if (!(globals.acceptor = create_socket(globals.pool))) { + return SWITCH_STATUS_SOCKERR; + } + + switch_socket_addr_get(&sa, SWITCH_FALSE, globals.acceptor); + + port = switch_sockaddr_get_port(sa); + ip_addr = switch_get_addr(ipbuf, sizeof (ipbuf), sa); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Erlang connection acceptor listening on %s:%u\n", ip_addr, port); + + /* try to initialize the erlang interface */ + if (create_ei_cnode(ip_addr, globals.ei_nodename, &globals.ei_cnode) != SWITCH_STATUS_SUCCESS) { + return SWITCH_STATUS_SOCKERR; + } + + /* tell the erlang port manager where we can be reached. this returns a file descriptor pointing to epmd or -1 */ + if ((globals.epmdfd = ei_publish(&globals.ei_cnode, port)) == -1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, + "Failed to publish port to epmd. Try starting it yourself or run an erl shell with the -sname or -name option.\n"); + return SWITCH_STATUS_SOCKERR; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Connected to epmd and published erlang cnode name %s at port %d\n", globals.ei_cnode.thisnodename, port); + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_STANDARD_API(exec_api_cmd) +{ + char *argv[1024] = { 0 }; + int unknown_command = 1, argc = 0; + char *mycmd = NULL; + + const char *usage_string = "USAGE:\n" + "--------------------------------------------------------------------------------------------------------------------\n" + "erlang status - provides an overview of the current status\n" + "erlang event_filter - lists the event headers that will be sent to Erlang nodes\n" + "erlang nodes list - lists connected Erlang nodes (usefull for monitoring tools)\n" + "erlang nodes count - provides a count of connected Erlang nodes (usefull for monitoring tools)\n" + "erlang node disconnect - disconnects an Erlang node\n" + "erlang node connection - Shows the connection info\n" + "erlang node event_streams - lists the event streams for an Erlang node\n" + "erlang node fetch_bindings - lists the XML fetch bindings for an Erlang node\n" + "---------------------------------------------------------------------------------------------------------------------\n"; + + if (zstr(cmd)) { + stream->write_function(stream, "%s", usage_string); + return SWITCH_STATUS_SUCCESS; + } + + if (!(mycmd = strdup(cmd))) { + return SWITCH_STATUS_MEMERR; + } + + if (!(argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) { + stream->write_function(stream, "%s", usage_string); + switch_safe_free(mycmd); + return SWITCH_STATUS_SUCCESS; + } + + if (zstr(argv[0])) { + stream->write_function(stream, "%s", usage_string); + switch_safe_free(mycmd); + return SWITCH_STATUS_SUCCESS; + } + + if (!strncmp(argv[0], "status", 6)) { + unknown_command = 0; + api_erlang_status(stream); + } else if (!strncmp(argv[0], "event_filter", 6)) { + unknown_command = 0; + api_erlang_event_filter(stream); + } else if (!strncmp(argv[0], "nodes", 6) && !zstr(argv[1])) { + if (!strncmp(argv[1], "list", 6)) { + unknown_command = 0; + api_erlang_nodes_list(stream); + } else if (!strncmp(argv[1], "count", 6)) { + unknown_command = 0; + api_erlang_nodes_count(stream); + } + } else if (!strncmp(argv[0], "node", 6) && !zstr(argv[1]) && !zstr(argv[2])) { + if (!strncmp(argv[2], "disconnect", 6)) { + unknown_command = 0; + api_erlang_node_command(stream, argv[1], API_COMMAND_DISCONNECT); + } else if (!strncmp(argv[2], "connection", 2)) { + unknown_command = 0; + api_erlang_node_command(stream, argv[1], API_COMMAND_REMOTE_IP); + } else if (!strncmp(argv[2], "event_streams", 6)) { + unknown_command = 0; + api_erlang_node_command(stream, argv[1], API_COMMAND_STREAMS); + } else if (!strncmp(argv[2], "fetch_bindings", 6)) { + unknown_command = 0; + api_erlang_node_command(stream, argv[1], API_COMMAND_BINDINGS); + } + } + + if (unknown_command) { + stream->write_function(stream, "%s", usage_string); + } + + switch_safe_free(mycmd); + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MODULE_LOAD_FUNCTION(mod_kazoo_load) { + switch_api_interface_t *api_interface = NULL; + switch_application_interface_t *app_interface = NULL; + + memset(&globals, 0, sizeof(globals)); + + globals.pool = pool; + globals.ei_nodes = NULL; + + if(config() != SWITCH_STATUS_SUCCESS) { + // TODO: what would we need to clean up here? + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Improper configuration!\n"); + return SWITCH_STATUS_TERM; + } + + if(create_acceptor() != SWITCH_STATUS_SUCCESS) { + // TODO: what would we need to clean up here + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to create erlang connection acceptor!\n"); + close_socket(&globals.acceptor); + return SWITCH_STATUS_TERM; + } + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + /* create an api for cli debug commands */ + SWITCH_ADD_API(api_interface, "erlang", KAZOO_DESC, exec_api_cmd, KAZOO_SYNTAX); + switch_console_set_complete("add erlang status"); + switch_console_set_complete("add erlang event_filter"); + switch_console_set_complete("add erlang nodes list"); + switch_console_set_complete("add erlang nodes count"); + switch_console_set_complete("add erlang node ::erlang::node disconnect"); + switch_console_set_complete("add erlang node ::erlang::node connection"); + switch_console_set_complete("add erlang node ::erlang::node event_streams"); + switch_console_set_complete("add erlang node ::erlang::node fetch_bindings"); + switch_console_add_complete_func("::erlang::node", api_complete_erlang_node); + + switch_thread_rwlock_create(&globals.ei_nodes_lock, pool); + + switch_set_flag(&globals, LFLAG_RUNNING); + + /* create all XML fetch agents */ + bind_fetch_agents(); + + /* add our modified commands */ + add_kz_commands(module_interface, api_interface); + + /* add our modified dptools */ + add_kz_dptools(module_interface, app_interface); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_kazoo_shutdown) { + int sanity = 0; + + switch_console_set_complete("del erlang"); + switch_console_del_complete_func("::erlang::node"); + + /* stop taking new requests and start shuting down the threads */ + switch_clear_flag(&globals, LFLAG_RUNNING); + + /* give everyone time to cleanly shutdown */ + while (switch_atomic_read(&globals.threads)) { + switch_yield(100000); + if (++sanity >= 200) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to kill all threads, continuing. This probably wont end well.....good luck!\n"); + break; + } + } + + if (globals.event_filter) { + switch_core_hash_destroy(&globals.event_filter); + } + + switch_thread_rwlock_wrlock(globals.ei_nodes_lock); + switch_thread_rwlock_unlock(globals.ei_nodes_lock); + switch_thread_rwlock_destroy(globals.ei_nodes_lock); + + /* close the connection to epmd and the acceptor */ + close_socketfd(&globals.epmdfd); + close_socket(&globals.acceptor); + + /* remove all XML fetch agents */ + unbind_fetch_agents(); + + /* Close the port we reserved for uPnP/Switch behind firewall, if necessary */ + // if (globals.nat_map && switch_nat_get_type()) { + // switch_nat_del_mapping(globals.port, SWITCH_NAT_TCP); + // } + + /* clean up our allocated preferences */ + switch_safe_free(globals.ip); + switch_safe_free(globals.ei_cookie); + switch_safe_free(globals.ei_nodename); + switch_safe_free(globals.kazoo_var_prefix); + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MODULE_RUNTIME_FUNCTION(mod_kazoo_runtime) { + switch_os_socket_t os_socket; + + switch_atomic_inc(&globals.threads); + + switch_os_sock_get(&os_socket, globals.acceptor); + + while (switch_test_flag(&globals, LFLAG_RUNNING)) { + int nodefd; + ErlConnect conn; + + /* zero out errno because ei_accept doesn't differentiate between a */ + /* failed authentication or a socket failure, or a client version */ + /* mismatch or a godzilla attack (and a godzilla attack is highly likely) */ + errno = 0; + + /* wait here for an erlang node to connect, timming out to check if our module is still running every now-and-again */ + if ((nodefd = ei_accept_tmo(&globals.ei_cnode, (int) os_socket, &conn, globals.connection_timeout)) == ERL_ERROR) { + if (erl_errno == ETIMEDOUT) { + continue; + } else if (errno) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Erlang connection acceptor socket error %d %d\n", erl_errno, errno); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "Erlang node connection failed - ensure your cookie matches '%s' and you are using a good nodename\n", globals.ei_cookie); + } + continue; + } + + if (!switch_test_flag(&globals, LFLAG_RUNNING)) { + break; + } + + /* NEW ERLANG NODE CONNECTION! Hello friend! */ + new_kazoo_node(nodefd, &conn); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Erlang connection acceptor shut down\n"); + + switch_atomic_dec(&globals.threads); + + return SWITCH_STATUS_TERM; +} + + +/* 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: + */ diff --git a/src/mod/event_handlers/mod_kazoo/mod_kazoo.h b/src/mod/event_handlers/mod_kazoo/mod_kazoo.h new file mode 100644 index 0000000000..d030feab52 --- /dev/null +++ b/src/mod/event_handlers/mod_kazoo/mod_kazoo.h @@ -0,0 +1,171 @@ +#include +#include + +#define MAX_ACL 100 +#define CMD_BUFLEN 1024 * 1000 +#define MAX_QUEUE_LEN 25000 +#define MAX_MISSED 500 +#define MAX_PID_CHARS 255 +#define VERSION "mod_kazoo v1.2.10-14" + +#define API_COMMAND_DISCONNECT 0 +#define API_COMMAND_REMOTE_IP 1 +#define API_COMMAND_STREAMS 2 +#define API_COMMAND_BINDINGS 3 + +typedef enum { + LFLAG_RUNNING = (1 << 0) +} event_flag_t; + +struct ei_send_msg_s { + ei_x_buff buf; + erlang_pid pid; +}; +typedef struct ei_send_msg_s ei_send_msg_t; + +struct ei_received_msg_s { + ei_x_buff buf; + erlang_msg msg; +}; +typedef struct ei_received_msg_s ei_received_msg_t; + +struct ei_event_binding_s { + char id[SWITCH_UUID_FORMATTED_LENGTH + 1]; + switch_event_node_t *node; + switch_event_types_t type; + const char *subclass_name; + struct ei_event_binding_s *next; +}; +typedef struct ei_event_binding_s ei_event_binding_t; + +struct ei_event_stream_s { + switch_memory_pool_t *pool; + ei_event_binding_t *bindings; + switch_queue_t *queue; + switch_socket_t *acceptor; + switch_pollset_t *pollset; + switch_pollfd_t *pollfd; + switch_socket_t *socket; + switch_mutex_t *socket_mutex; + switch_bool_t connected; + char remote_ip[25]; + uint16_t remote_port; + char local_ip[25]; + uint16_t local_port; + erlang_pid pid; + uint32_t flags; + struct ei_event_stream_s *next; +}; +typedef struct ei_event_stream_s ei_event_stream_t; + +struct ei_node_s { + int nodefd; + switch_atomic_t pending_bgapi; + switch_atomic_t receive_handlers; + switch_memory_pool_t *pool; + ei_event_stream_t *event_streams; + switch_mutex_t *event_streams_mutex; + switch_queue_t *send_msgs; + switch_queue_t *received_msgs; + char *peer_nodename; + switch_time_t created_time; + switch_socket_t *socket; + char remote_ip[25]; + uint16_t remote_port; + char local_ip[25]; + uint16_t local_port; + uint32_t flags; + struct ei_node_s *next; +}; +typedef struct ei_node_s ei_node_t; + +struct globals_s { + switch_memory_pool_t *pool; + switch_atomic_t threads; + switch_socket_t *acceptor; + struct ei_cnode_s ei_cnode; + switch_thread_rwlock_t *ei_nodes_lock; + ei_node_t *ei_nodes; + switch_xml_binding_t *config_fetch_binding; + switch_xml_binding_t *directory_fetch_binding; + switch_xml_binding_t *dialplan_fetch_binding; + switch_xml_binding_t *chatplan_fetch_binding; + switch_xml_binding_t *channels_fetch_binding; + switch_hash_t *event_filter; + int epmdfd; + int num_worker_threads; + switch_bool_t nat_map; + switch_bool_t ei_shortname; + int ei_compat_rel; + char *ip; + char *ei_cookie; + char *ei_nodename; + char *kazoo_var_prefix; + int var_prefix_length; + uint32_t flags; + int send_all_headers; + int send_all_private_headers; + int connection_timeout; + int receive_timeout; + int receive_msg_preallocate; + int event_stream_preallocate; + int send_msg_batch; + short event_stream_framing; +} globals; +typedef struct globals_s globals_t; + +/* kazoo_node.c */ +switch_status_t new_kazoo_node(int nodefd, ErlConnect *conn); + +/* kazoo_event_stream.c */ +ei_event_stream_t *find_event_stream(ei_event_stream_t *event_streams, const erlang_pid *from); +ei_event_stream_t *new_event_stream(ei_event_stream_t **event_streams, const erlang_pid *from); +switch_status_t remove_event_stream(ei_event_stream_t **event_streams, const erlang_pid *from); +switch_status_t remove_event_streams(ei_event_stream_t **event_streams); +unsigned long get_stream_port(const ei_event_stream_t *event_stream); +switch_status_t add_event_binding(ei_event_stream_t *event_stream, const switch_event_types_t event_type, const char *subclass_name); +switch_status_t remove_event_binding(ei_event_stream_t *event_stream, const switch_event_types_t event_type, const char *subclass_name); +switch_status_t remove_event_bindings(ei_event_stream_t *event_stream); + +/* kazoo_fetch_agent.c */ +switch_status_t bind_fetch_agents(); +switch_status_t unbind_fetch_agents(); +switch_status_t remove_xml_clients(ei_node_t *ei_node); +switch_status_t add_fetch_handler(ei_node_t *ei_node, erlang_pid *from, switch_xml_binding_t *binding); +switch_status_t remove_fetch_handlers(ei_node_t *ei_node, erlang_pid *from); +switch_status_t fetch_reply(char *uuid_str, char *xml_str, switch_xml_binding_t *binding); +switch_status_t handle_api_command_streams(ei_node_t *ei_node, switch_stream_handle_t *stream); + +/* kazoo_utils.c */ +void close_socket(switch_socket_t **sock); +void close_socketfd(int *sockfd); +switch_socket_t *create_socket(switch_memory_pool_t *pool); +switch_status_t create_ei_cnode(const char *ip_addr, const char *name, struct ei_cnode_s *ei_cnode); +switch_status_t ei_compare_pids(const erlang_pid *pid1, const erlang_pid *pid2); +void ei_encode_switch_event_headers(ei_x_buff *ebuf, switch_event_t *event); +void ei_link(ei_node_t *ei_node, erlang_pid * from, erlang_pid * to); +void ei_encode_switch_event(ei_x_buff * ebuf, switch_event_t *event); +int ei_helper_send(ei_node_t *ei_node, erlang_pid* to, ei_x_buff *buf); +int ei_decode_atom_safe(char *buf, int *index, char *dst); +int ei_decode_string_or_binary_limited(char *buf, int *index, int maxsize, char *dst); +int ei_decode_string_or_binary(char *buf, int *index, char **dst); +switch_hash_t *create_default_filter(); + +/* kazoo_commands.c */ +void add_kz_commands(switch_loadable_module_interface_t **module_interface, switch_api_interface_t *api_interface); + +/* kazoo_dptools.c */ +void add_kz_dptools(switch_loadable_module_interface_t **module_interface, switch_application_interface_t *app_interface); + +#define _ei_x_encode_string(buf, string) { ei_x_encode_binary(buf, string, strlen(string)); } + +/* 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: + */