diff --git a/Freeswitch.2017.sln b/Freeswitch.2017.sln index 1962a9b283..be107fde3e 100644 --- a/Freeswitch.2017.sln +++ b/Freeswitch.2017.sln @@ -576,6 +576,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "48khz", "libs\win32\Sound_F EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "48khz music", "libs\win32\Sound_Files\48khzmusic.2017.vcxproj", "{EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod_signalwire", "src\mod\applications\mod_signalwire\mod_signalwire.2017.vcxproj", "{B19AE6FC-BFFF-428D-B483-3BBEAECCC618}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution All|Win32 = All|Win32 @@ -2627,6 +2629,18 @@ Global {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}.Debug|x64.Build.0 = Debug|x64 {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}.Release|Win32.ActiveCfg = Release|Win32 {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}.Release|x64.ActiveCfg = Release|x64 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|Win32.ActiveCfg = Release|Win32 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|Win32.Build.0 = Release|Win32 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|x64.ActiveCfg = Release|x64 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|x64.Build.0 = Release|x64 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|Win32.ActiveCfg = Debug|Win32 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|Win32.Build.0 = Debug|Win32 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|x64.ActiveCfg = Debug|x64 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|x64.Build.0 = Debug|x64 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|Win32.ActiveCfg = Release|Win32 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|Win32.Build.0 = Release|Win32 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|x64.ActiveCfg = Release|x64 + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2836,6 +2850,7 @@ Global {EF62B845-A0CE-44FD-B8E6-475FE87D06C3} = {9388C266-C3FC-468A-92EF-0CBC35941412} {8154C82D-58EE-4145-9DEC-A445A5AA3D6B} = {4F227C26-768F-46A3-8684-1D08A46FB374} {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31} = {4F227C26-768F-46A3-8684-1D08A46FB374} + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {09840DE7-9208-45AA-9667-1A71EE93BD1E} diff --git a/build/modules.conf.in b/build/modules.conf.in index d998e5dba8..a3297e744c 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -39,6 +39,7 @@ applications/mod_httapi #applications/mod_rad_auth #applications/mod_redis #applications/mod_rss +applications/mod_signalwire applications/mod_sms #applications/mod_sms_flowroute #applications/mod_snapshot diff --git a/build/modules.conf.most b/build/modules.conf.most index b442b966e6..a4946004d2 100644 --- a/build/modules.conf.most +++ b/build/modules.conf.most @@ -38,6 +38,7 @@ applications/mod_prefix #applications/mod_rad_auth applications/mod_redis applications/mod_rss +applications/mod_signalwire applications/mod_sms applications/mod_snapshot applications/mod_snom diff --git a/conf/insideout/autoload_configs/modules.conf.xml b/conf/insideout/autoload_configs/modules.conf.xml index 3e2b09cdcb..5078503489 100644 --- a/conf/insideout/autoload_configs/modules.conf.xml +++ b/conf/insideout/autoload_configs/modules.conf.xml @@ -37,6 +37,7 @@ + diff --git a/conf/minimal/autoload_configs/modules.conf.xml b/conf/minimal/autoload_configs/modules.conf.xml index 37ece9d202..a57544f2fe 100644 --- a/conf/minimal/autoload_configs/modules.conf.xml +++ b/conf/minimal/autoload_configs/modules.conf.xml @@ -17,6 +17,7 @@ + diff --git a/conf/rayo/autoload_configs/modules.conf.xml b/conf/rayo/autoload_configs/modules.conf.xml index f6c58680bc..ee44e06c57 100644 --- a/conf/rayo/autoload_configs/modules.conf.xml +++ b/conf/rayo/autoload_configs/modules.conf.xml @@ -14,6 +14,7 @@ + diff --git a/conf/sbc/autoload_configs/modules.conf.xml b/conf/sbc/autoload_configs/modules.conf.xml index 579b1d33be..31f46a4128 100644 --- a/conf/sbc/autoload_configs/modules.conf.xml +++ b/conf/sbc/autoload_configs/modules.conf.xml @@ -26,6 +26,7 @@ + diff --git a/conf/softphone/freeswitch.xml b/conf/softphone/freeswitch.xml index 771efd9544..5fbc0cbd39 100644 --- a/conf/softphone/freeswitch.xml +++ b/conf/softphone/freeswitch.xml @@ -89,6 +89,7 @@ + diff --git a/conf/vanilla/autoload_configs/modules.conf.xml b/conf/vanilla/autoload_configs/modules.conf.xml index 2c465d1231..0b336a168d 100644 --- a/conf/vanilla/autoload_configs/modules.conf.xml +++ b/conf/vanilla/autoload_configs/modules.conf.xml @@ -50,6 +50,7 @@ + diff --git a/configure.ac b/configure.ac index 1144a5faac..4045e72f62 100644 --- a/configure.ac +++ b/configure.ac @@ -1498,6 +1498,14 @@ PKG_CHECK_MODULES([V8FS_STATIC], [v8-6.1_static >= 6.1.298],[ ]) ]) +PKG_CHECK_MODULES([KS], [libks >= 1.1.0],[ + AM_CONDITIONAL([HAVE_KS],[true])],[ + AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_KS],[false])]) + +PKG_CHECK_MODULES([SIGNALWIRE_CLIENT], [signalwire_client >= 1.0.0],[ + AM_CONDITIONAL([HAVE_SIGNALWIRE_CLIENT],[true])],[ + AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_SIGNALWIRE_CLIENT],[false])]) + PKG_CHECK_MODULES([AMQP], [librabbitmq >= 0.5.2],[ AM_CONDITIONAL([HAVE_AMQP],[true])],[ AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_AMQP],[false])]) @@ -1853,6 +1861,7 @@ AC_CONFIG_FILES([Makefile src/mod/applications/mod_redis/Makefile src/mod/applications/mod_rss/Makefile src/mod/applications/mod_skel/Makefile + src/mod/applications/mod_signalwire/Makefile src/mod/applications/mod_sms/Makefile src/mod/applications/mod_sms_flowroute/Makefile src/mod/applications/mod_snapshot/Makefile diff --git a/debian/control-modules b/debian/control-modules index b19e12e575..d27ebbbc37 100644 --- a/debian/control-modules +++ b/debian/control-modules @@ -206,6 +206,11 @@ Module: applications/mod_skel Description: Adds mod_skel Adds mod_skel. +Module: applications/mod_signalwire +Description: mod_signalwire + Adds mod_signalwire. +Build-Depends: libks, signalwire-client-c + Module: applications/mod_sms Description: Astract SMS This module provides an abstract facility for interfacing with SMS diff --git a/freeswitch.spec b/freeswitch.spec index 0cc0462323..f0fee3ca72 100644 --- a/freeswitch.spec +++ b/freeswitch.spec @@ -3,7 +3,7 @@ # spec file for package freeswitch # # includes module(s): freeswitch-devel freeswitch-codec-passthru-amr freeswitch-codec-passthru-amrwb freeswitch-codec-passthru-g729 -# freeswitch-codec-passthru-g7231 freeswitch-lua freeswitch-perl freeswitch-python freeswitch-v8 +# freeswitch-codec-passthru-g7231 freeswitch-lua freeswitch-perl freeswitch-python freeswitch-v8 freeswitch-signalwire # freeswitch-lan-de freeswitch-lang-en freeswitch-lang-fr freeswitch-lang-hu freeswitch-lang-ru freeswitch-freetdm # and others # @@ -536,6 +536,14 @@ Requires: %{name} = %{version}-%{release} Provides FreeSWITCH mod_rss, edisrse and read an XML based RSS feed, then read the entries aloud via a TTS engine +%package application-signalwire +Summary: FreeSWITCH mod_signalwire +Group: System/Libraries +Requires: %{name} = %{version}-%{release} + +%description application-signalwire +Provides FreeSWITCH mod_signalwire + %package application-sms Summary: FreeSWITCH mod_sms Group: System/Libraries @@ -1373,6 +1381,7 @@ Requires: freeswitch-application-memcache Requires: freeswitch-application-nibblebill Requires: freeswitch-application-redis Requires: freeswitch-application-rss +Requires: freeswitch-application-signalwire Requires: freeswitch-application-sms Requires: freeswitch-application-snapshot Requires: freeswitch-application-snom @@ -1458,7 +1467,7 @@ APPLICATION_MODULES_FR="applications/mod_fifo applications/mod_fsk applications/ applications/mod_memcache applications/mod_mongo applications/mod_nibblebill applications/mod_rad_auth \ applications/mod_redis applications/mod_rss " -APPLICATION_MODULES_SZ="applications/mod_sms applications/mod_snapshot applications/mod_snom applications/mod_soundtouch \ +APPLICATION_MODULES_SZ="applications/mod_signalwire applications/mod_sms applications/mod_snapshot applications/mod_snom applications/mod_soundtouch \ applications/mod_spandsp applications/mod_spy applications/mod_stress \ applications/mod_valet_parking applications/mod_translate applications/mod_voicemail \ applications/mod_voicemail_ivr applications/mod_video_filter" @@ -2115,6 +2124,9 @@ fi %files application-rss %{MODINSTDIR}/mod_rss.so* +%files application-signalwire +%{MODINSTDIR}/mod_signalwire.so* + %files application-sms %{MODINSTDIR}/mod_sms.so* @@ -2541,6 +2553,8 @@ fi # ###################################################################################################################### %changelog +* Tue Dec 11 2018 - Andrey Volk +- add mod_signalwire * Sun Mar 13 2016 - Matthew Vale - add perl and python ESL language module packages * Thu Jul 09 2015 - Artur ZaprzaƂa diff --git a/libs/.gitignore b/libs/.gitignore index 8555f1a4f1..10b50dda73 100644 --- a/libs/.gitignore +++ b/libs/.gitignore @@ -858,3 +858,8 @@ libsndfile-*/ libsndfile-* opencv-*/ opencv-* +libks-*/ +libks* +signalwire-client-c-*/ +signalwire-client-c-* + diff --git a/src/mod/applications/mod_signalwire/Makefile.am b/src/mod/applications/mod_signalwire/Makefile.am new file mode 100644 index 0000000000..d46251b832 --- /dev/null +++ b/src/mod/applications/mod_signalwire/Makefile.am @@ -0,0 +1,27 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_signalwire + +if HAVE_KS +if HAVE_SIGNALWIRE_CLIENT + +mod_LTLIBRARIES = mod_signalwire.la +mod_signalwire_la_SOURCES = mod_signalwire.c +mod_signalwire_la_CFLAGS = $(AM_CFLAGS) +mod_signalwire_la_CFLAGS += $(KS_CFLAGS) $(SIGNALWIRE_CLIENT_CFLAGS) +mod_signalwire_la_LIBADD = $(switch_builddir)/libfreeswitch.la +mod_signalwire_la_LDFLAGS = -avoid-version -module -no-undefined -shared $(KS_LIBS) $(SIGNALWIRE_CLIENT_LIBS) + +else +install: error +all: error +error: + $(error You must install signalwire-client-c to build mod_signalwire) +endif + +else +install: error +all: error +error: + $(error You must install libks to build mod_signalwire) +endif + diff --git a/src/mod/applications/mod_signalwire/conf/autoload_configs/signalwire.conf.xml b/src/mod/applications/mod_signalwire/conf/autoload_configs/signalwire.conf.xml new file mode 100644 index 0000000000..c62ad72ff0 --- /dev/null +++ b/src/mod/applications/mod_signalwire/conf/autoload_configs/signalwire.conf.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/mod/applications/mod_signalwire/mod_signalwire.2017.vcxproj b/src/mod/applications/mod_signalwire/mod_signalwire.2017.vcxproj new file mode 100644 index 0000000000..192c82d51d --- /dev/null +++ b/src/mod/applications/mod_signalwire/mod_signalwire.2017.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + mod_signalwire + mod_signalwire + Win32Proj + {B19AE6FC-BFFF-428D-B483-3BBEAECCC618} + + + + DynamicLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + .;%(AdditionalIncludeDirectories) + + + + + + + + + _DEBUG;DEBUG;%(PreprocessorDefinitions) + + + false + + + + + + + X64 + + + + + _DEBUG;DEBUG;%(PreprocessorDefinitions) + + + false + + + MachineX64 + + + + + + + NDEBUG;%(PreprocessorDefinitions) + + + false + + + + + + + X64 + + + + + NDEBUG;%(PreprocessorDefinitions) + + + false + + + MachineX64 + + + + + + + + {202d7a4e-760d-4d0e-afa1-d7459ced30ff} + false + + + + + + diff --git a/src/mod/applications/mod_signalwire/mod_signalwire.c b/src/mod/applications/mod_signalwire/mod_signalwire.c new file mode 100644 index 0000000000..5fd2fe828e --- /dev/null +++ b/src/mod/applications/mod_signalwire/mod_signalwire.c @@ -0,0 +1,1308 @@ +/* + * mod_signalwire.c -- SignalWire module + * + * Copyright (c) 2018 SignalWire, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +#define SW_KS_JSON_PRINT(_h, _j) do { \ + char *_json = ks_json_print(_j); \ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "--- %s ---\n%s\n---\n", _h, _json); \ + ks_json_free(&_json); \ + } while (0) + +static int debug_level = 7; + +static int signalwire_gateway_exists(void); + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_signalwire_shutdown); +SWITCH_MODULE_LOAD_FUNCTION(mod_signalwire_load); +SWITCH_MODULE_RUNTIME_FUNCTION(mod_signalwire_runtime); + +/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) + * Defines a switch_loadable_module_function_table_t and a static const char[] modname + */ +SWITCH_MODULE_DEFINITION(mod_signalwire, mod_signalwire_load, mod_signalwire_shutdown, mod_signalwire_runtime); + +typedef enum { + SW_STATE_ADOPTION, + SW_STATE_OFFLINE, + SW_STATE_ONLINE, + SW_STATE_CONFIGURE, + SW_STATE_START_PROFILE, + SW_STATE_REGISTER, + SW_STATE_READY, +} sw_state_t; + +static struct { + int ssl_verify; + ks_bool_t shutdown; + ks_bool_t restarting; + swclt_config_t *config; + char blade_bootstrap[1024]; + char adoption_service[1024]; + char stun_server[1024]; + char adoption_token[64]; + ks_size_t adoption_backoff; + ks_time_t adoption_next; + + char adoption_data_local_ip[256]; + char adoption_data_external_ip[256]; + char adoption_data_uname[1024]; + + char relay_connector_id[256]; + + swclt_sess_t signalwire_session; + swclt_hmon_t signalwire_session_monitor; + sw_state_t state; + ks_bool_t profile_update; + ks_bool_t profile_reload; + ks_bool_t signalwire_reconnected; + switch_xml_t signalwire_profile; + char signalwire_profile_md5[SWITCH_MD5_DIGEST_STRING_SIZE]; + + ks_bool_t kslog_on; + + switch_mutex_t *mutex; // general mutex for this mod + char gateway_ip[80]; + char gateway_port[10]; +} globals; + +static void mod_signalwire_kslogger(const char *file, const char *func, int line, int level, const char *fmt, ...) +{ + const char *fp; + va_list ap; + char buf[32768]; + + if (level > debug_level) return; + + fp = switch_cut_path(file); + + va_start(ap, fmt); + + vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + buf[sizeof(buf) - 1] = '\0'; + + va_end(ap); + + switch_log_printf(SWITCH_CHANNEL_ID_LOG, fp, func, line, NULL, level, "%s\n", buf); +} + +static switch_status_t switch_find_available_port(switch_port_t *port, const char *ip, int type) +{ + switch_status_t ret = SWITCH_STATUS_SUCCESS; + switch_memory_pool_t *pool = NULL; + switch_sockaddr_t *addr = NULL; + switch_socket_t *sock = NULL; + switch_bool_t found = SWITCH_FALSE; + + if ((ret = switch_core_new_memory_pool(&pool)) != SWITCH_STATUS_SUCCESS) { + goto done; + } + + while (!found) { + if ((ret = switch_sockaddr_info_get(&addr, ip, SWITCH_UNSPEC, *port, 0, pool)) != SWITCH_STATUS_SUCCESS) { + goto done; + } + + if ((ret = switch_socket_create(&sock, switch_sockaddr_get_family(addr), type, 0, pool)) != SWITCH_STATUS_SUCCESS) { + goto done; + } + + if (!(found = (switch_socket_bind(sock, addr) == SWITCH_STATUS_SUCCESS))) { + *port = *port + 1; + } + + switch_socket_close(sock); + } + +done: + if (pool) switch_core_destroy_memory_pool(&pool); + + return ret; +} + +struct response_data { + char *data; + size_t size; +}; + +static size_t response_data_handler(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t received = size * nmemb; + struct response_data *rd = (struct response_data *)userp; + + if (!rd->data) rd->data = ks_pool_alloc(NULL, received + 1); + else rd->data = ks_pool_resize(rd->data, rd->size + received + 1); + + memcpy(rd->data + rd->size, contents, received); + rd->size += received; + rd->data[rd->size] = 0; + + return received; +} + +static void save_sip_config(const char *config) +{ + char confpath[1024]; + FILE *fp = NULL; + + switch_snprintf(confpath, sizeof(confpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat"); + fp = fopen(confpath, "w"); + if (!fp) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire SIP configuration\n", confpath); + return; + } + + fputs(config, fp); + fclose(fp); +} + +static void load_sip_config(void) +{ + char confpath[1024]; + char data[32767] = { 0 }; + FILE *fp = NULL; + + switch_snprintf(confpath, sizeof(confpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat"); + if (!(fp = fopen(confpath, "r"))) return; + + if (!fread(data, 1, sizeof(data), fp)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire SIP configuration from %s\n", confpath); + } + fclose(fp); + if (!zstr_buf(data)) { + switch_md5_string(globals.signalwire_profile_md5, (void *) data, strlen(data)); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "saved profile MD5 = \"%s\"\n", globals.signalwire_profile_md5); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "saved profile = \"%s\"\n", (char *)data); + globals.signalwire_profile = switch_xml_parse_str_dynamic((char *)data, SWITCH_TRUE); + } +} + +static ks_status_t load_credentials_from_json(ks_json_t *json) +{ + ks_status_t status = KS_STATUS_SUCCESS; + ks_json_t *authentication = NULL; + const char *authentication_str = NULL; + const char *bootstrap = NULL; + const char *relay_connector_id = NULL; + + if ((bootstrap = ks_json_get_object_cstr(json, "bootstrap")) == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing bootstrap URL\n"); + status = KS_STATUS_FAIL; + goto done; + } + + if ((relay_connector_id = ks_json_get_object_cstr(json, "relay_connector_id")) == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing relay_connector_id\n"); + status = KS_STATUS_FAIL; + goto done; + } + + if ((authentication = ks_json_get_object_item(json, "authentication")) == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing authentication\n"); + status = KS_STATUS_FAIL; + goto done; + } + + // update the internal connection target, which is normally assigned in swclt_sess_create() + if (swclt_sess_target_set(globals.signalwire_session, bootstrap) != KS_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to connect to SignalWire at %s\n", bootstrap); + status = KS_STATUS_FAIL; + goto done; + } + + // update the relay_connector_id passed to profile configuration + strncpy(globals.relay_connector_id, relay_connector_id, sizeof(globals.relay_connector_id) - 1); + strncpy(globals.blade_bootstrap, bootstrap, sizeof(globals.blade_bootstrap) - 1); + + // got adopted, update the client config authentication + authentication_str = ks_json_pprint_unformatted(NULL, authentication); + swclt_config_set_authentication(globals.config, authentication_str); + + ks_pool_free(&authentication_str); +done: + + return status; +} + +static ks_status_t mod_signalwire_adoption_post(void) +{ + ks_status_t status = KS_STATUS_SUCCESS; + switch_memory_pool_t *pool = NULL; + switch_CURL *curl = NULL; + switch_curl_slist_t *headers = NULL; + char url[1024]; + char errbuf[CURL_ERROR_SIZE]; + CURLcode res; + long rescode; + ks_json_t *json = ks_json_create_object(); + struct response_data rd = { 0 }; + char *jsonstr = NULL; + + // Determine and cache adoption data values that are heavier to figure out + if (!globals.adoption_data_local_ip[0]) { + switch_find_local_ip(globals.adoption_data_local_ip, sizeof(globals.adoption_data_local_ip), NULL, AF_INET); + } + + if (!globals.adoption_data_external_ip[0]) { + switch_port_t local_port = 6050; + char *error = NULL; + char *external_ip; + switch_port_t external_port; + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "SignalWire adoption failed: could not allocate memory pool\n"); + status = KS_STATUS_FAIL; + goto done; + } + if (switch_find_available_port(&local_port, globals.adoption_data_local_ip, SOCK_STREAM) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: could not get available local port\n"); + status = KS_STATUS_FAIL; + goto done; + } + + external_ip = globals.adoption_data_local_ip; + external_port = local_port; + if (switch_stun_lookup(&external_ip, &external_port, globals.stun_server, SWITCH_STUN_DEFAULT_PORT, &error, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: stun [%s] lookup error: %s\n", globals.stun_server, error); + status = KS_STATUS_FAIL; + goto done; + } + strncpy(globals.adoption_data_external_ip, external_ip, sizeof(globals.adoption_data_external_ip)); + } + + if (!globals.adoption_data_uname[0]) { +#ifndef WIN32 + struct utsname buf; + if (uname(&buf)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: could not get uname\n"); + status = KS_STATUS_FAIL; + goto done; + } + switch_snprintf(globals.adoption_data_uname, + sizeof(globals.adoption_data_uname), + "%s %s %s %s %s", + buf.sysname, + buf.nodename, + buf.release, + buf.version, + buf.machine); +#else + // @todo set globals.adoption_data_uname from GetVersion Win32API +#endif + } + + + ks_json_add_string_to_object(json, "client_uuid", globals.adoption_token); + ks_json_add_string_to_object(json, "hostname", switch_core_get_hostname()); + ks_json_add_string_to_object(json, "ip", globals.adoption_data_local_ip); + ks_json_add_string_to_object(json, "ext_ip", globals.adoption_data_external_ip); + ks_json_add_string_to_object(json, "version", switch_version_full()); + ks_json_add_string_to_object(json, "uname", globals.adoption_data_uname); + + jsonstr = ks_json_print_unformatted(json); + ks_json_delete(&json); + + switch_snprintf(url, sizeof(url), "%s/%s", globals.adoption_service, globals.adoption_token); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG10, "Checking %s for SignalWire adoption of this FreeSWITCH\n", url); + + curl = switch_curl_easy_init(); + + headers = switch_curl_slist_append(headers, "Accept: application/json"); + headers = switch_curl_slist_append(headers, "Accept-Charset: utf-8"); + headers = switch_curl_slist_append(headers, "Content-Type: application/json"); + + switch_curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5); + switch_curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); + + if (!strncasecmp(url, "https", 5)) { + switch_curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, globals.ssl_verify); + switch_curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, globals.ssl_verify); + } + + switch_curl_easy_setopt(curl, CURLOPT_URL, url); + switch_curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + switch_curl_easy_setopt(curl, CURLOPT_USERAGENT, "mod_signalwire/1"); + switch_curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonstr); + switch_curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); + switch_curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&rd); + switch_curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_data_handler); + + if ((res = switch_curl_easy_perform(curl))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Curl Result %d, Error: %s\n", res, errbuf); + status = KS_STATUS_FAIL; + goto done; + } + + switch_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rescode); + + if (rescode == 404) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, + "Go to https://signalwire.com to set up your Connector now! Enter connection token %s\n", globals.adoption_token); + status = KS_STATUS_FAIL; + goto done; + } + + if (rescode != 200) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed with HTTP code %ld, %s\n", rescode, rd.data); + status = KS_STATUS_FAIL; + goto done; + } + + json = ks_json_parse(rd.data); + if (!json) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received bad SignalWire adoption response\n%s\n", rd.data); + status = KS_STATUS_FAIL; + goto done; + } + + if ((status = load_credentials_from_json(json)) != KS_STATUS_SUCCESS) { + goto done; + } + + ks_json_delete(&json); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire adoption of this FreeSWITCH completed\n"); + + // write out the data to save it for reloading in the future + { + char authpath[1024]; + FILE *fp = NULL; + + switch_snprintf(authpath, sizeof(authpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat"); + fp = fopen(authpath, "w"); + if (!fp) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire creds\n", authpath); + status = KS_STATUS_FAIL; + goto done; + } + + fputs(rd.data, fp); + fclose(fp); + } + + globals.state = SW_STATE_OFFLINE; + swclt_sess_connect(globals.signalwire_session); + +done: + if (rd.data) ks_pool_free(&rd.data); + if (jsonstr) ks_json_free_ex((void **)&jsonstr); + if (json) ks_json_delete(&json); + if (curl) { + curl_easy_cleanup(curl); + if (headers) curl_slist_free_all(headers); + } + if (pool) switch_core_destroy_memory_pool(&pool); + return status; +} + +#define SIGNALWIRE_SYNTAX "token | adoption | adopted | reload | update | debug | kslog " +SWITCH_STANDARD_API(mod_signalwire_api_function) +{ + int argc = 0; + char *argv[2] = { 0 }; + char *buf = NULL; + + + if (!cmd || !(buf = strdup(cmd))) { + stream->write_function(stream, "-USAGE: signalwire %s\n", SIGNALWIRE_SYNTAX); + return SWITCH_STATUS_SUCCESS; + } + + if ((argc = switch_separate_string(buf, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) { + if (!strcmp(argv[0], "token")) { + if (globals.adoption_token[0]) { + stream->write_function(stream, + " _____ _ ___ ___\n" + " / ___/(_)___ _____ ____ _/ / | / (_)_______\n" + " \\__ \\/ / __ `/ __ \\/ __ `/ /| | /| / / / ___/ _ \\\n" + " ___/ / / /_/ / / / / /_/ / / | |/ |/ / / / / __/\n" + " /____/_/\\__, /_/ /_/\\__,_/_/ |__/|__/_/_/ \\___/\n" + " /____/\n" + "\n /=====================================================================\\\n" + "| Connection Token: %s |\n" + " \\=====================================================================/\n" + " Go to https://signalwire.com to set up your Connector now!\n", globals.adoption_token); + } else { + stream->write_function(stream, "-ERR connection token not available\n"); + } + goto done; + } + else if (!strcmp(argv[0], "adoption")) { + if (globals.state == SW_STATE_ADOPTION) { + globals.adoption_next = ks_time_now(); + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR adoption not currently pending\n"); + } + goto done; + } + else if (!strcmp(argv[0], "adopted")) { + stream->write_function(stream, "+OK %s\n", globals.state == SW_STATE_ADOPTION ? "Not Adopted" : "Adopted"); + goto done; + } + else if (!strcmp(argv[0], "debug")) { + if (argv[1]) { + debug_level = atoi(argv[1]); + } + + stream->write_function(stream, "+OK debug %d\n", debug_level); + goto done; + } else if (!strcmp(argv[0], "kslog")) { + if (argv[1]) { + if (!strcmp(argv[1], "on")) { + ks_global_set_logger(mod_signalwire_kslogger); + } else if (!strcmp(argv[1], "off")) { + ks_global_set_logger(NULL); + } + } + + stream->write_function(stream, "+OK %s\n", argv[1]); + goto done; + } else if (!strcmp(argv[0], "reload")) { + globals.profile_reload = KS_TRUE; + stream->write_function(stream, "+OK\n"); + goto done; + } else if (!strcmp(argv[0], "update")) { + globals.profile_update = KS_TRUE; + stream->write_function(stream, "+OK\n"); + goto done; + } + } + + stream->write_function(stream, "-USAGE: signalwire %s\n", SIGNALWIRE_SYNTAX); + +done: + switch_safe_free(buf); + + return SWITCH_STATUS_SUCCESS; +} + +static void mod_signalwire_session_state_handler(swclt_sess_t sess, swclt_hstate_change_t *state_change_info, const char *cb_data) +{ + SWCLT_HSTATE new_state = state_change_info->new_state; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire Session State Change: %s\n", swclt_hstate_describe_change(state_change_info)); + + if (new_state == SWCLT_HSTATE_ONLINE) { + // Connected with NEW or RESTORED session + globals.signalwire_reconnected = KS_TRUE; + } else if (new_state == SWCLT_HSTATE_OFFLINE) { + // Disconnected + } +} + +static void __on_provisioning_events(swclt_sess_t sess, blade_broadcast_rqu_t *rqu, void *cb_data) +{ + if (!strcmp(rqu->event, "update")) { + globals.profile_update = KS_TRUE; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update requested\n"); + } +} + +static switch_xml_t xml_config_handler(const char *section, const char *tag_name, const char *key_name, const char *key_value, switch_event_t *params, + void *user_data) +{ + char *profileName = NULL; + char *reconfigValue = NULL; + switch_xml_t signalwire_profile_dup = NULL; + + if (!section || strcmp(section, "configuration")) return NULL; + if (!key_name || strcmp(key_name, "name")) return NULL; + if (!key_value || strcmp(key_value, "sofia.conf")) return NULL; + if (!params) return NULL; + profileName = switch_event_get_header(params, "profile"); + if (!profileName || strcmp(profileName, "signalwire")) return NULL; + reconfigValue = switch_event_get_header(params, "reconfig"); + if (!reconfigValue || strcmp(reconfigValue, "true")) return NULL; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received XML lookup for SignalWire SIP profile\n"); + + if (globals.signalwire_profile) { + signalwire_profile_dup = switch_xml_dup(globals.signalwire_profile); + } + return signalwire_profile_dup; +} + +static switch_status_t mod_signalwire_load_or_generate_token(void) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + char tokenpath[1024]; + + switch_snprintf(tokenpath, sizeof(tokenpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-token.dat"); + if (switch_file_exists(tokenpath, NULL) != SWITCH_STATUS_SUCCESS) { + // generate first time uuid + ks_uuid_t uuid; + const char *token; + FILE *fp = fopen(tokenpath, "w"); + if (!fp) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire connection token\n", tokenpath); + status = SWITCH_STATUS_TERM; + goto done; + } + + ks_uuid(&uuid); + token = ks_uuid_str(NULL, &uuid); + + fputs(token, fp); + fclose(fp); + + strncpy(globals.adoption_token, token, sizeof(globals.adoption_token) - 1); + + ks_pool_free(&token); + } else { + char token[64]; + FILE *fp = fopen(tokenpath, "r"); + if (!fp) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to read SignalWire connection token\n", tokenpath); + status = SWITCH_STATUS_TERM; + goto done; + } + if (!fgets(token, sizeof(token), fp)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire connection token from %s\n", tokenpath); + fclose(fp); + status = SWITCH_STATUS_TERM; + goto done; + } + fclose(fp); + + // trim newline markers in case they exist, only want the token + for (size_t len = strlen(token); len > 0 && (token[len - 1] == '\r' || token[len - 1] == '\n'); --len) { + token[len - 1] = '\0'; + } + + strncpy(globals.adoption_token, token, sizeof(globals.adoption_token) - 1); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, + "\n /=====================================================================\\\n" + "| Connection Token: %s |\n" + " \\=====================================================================/\n" + " Go to https://signalwire.com to set up your Connector now!\n", globals.adoption_token); + +done: + return status; +} + +static switch_status_t load_config() +{ + char *cf = "signalwire.conf"; + switch_xml_t cfg, xml; + const char *data; + + globals.ssl_verify = 1; + switch_set_string(globals.blade_bootstrap, "edge..signalwire.com/api/relay/wss"); + switch_set_string(globals.adoption_service, "https://adopt.signalwire.com/adoption"); + switch_set_string(globals.stun_server, "stun.freeswitch.org"); + + if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "open of %s failed\n", cf); + // don't need the config + } else { + switch_xml_t settings, param, tmp; + if ((settings = switch_xml_child(cfg, "settings"))) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + if (!strcasecmp(var, "kslog") && !ks_zstr(val)) { + if (!strcmp(val, "off")) { + globals.kslog_on = KS_FALSE; + } else if (!strcmp(val, "on")) { + globals.kslog_on = KS_TRUE; + } + } else if (!strcasecmp(var, "blade-bootstrap") && !ks_zstr(val)) { + switch_set_string(globals.blade_bootstrap, val); + } else if (!strcasecmp(var, "adoption-service") && !ks_zstr(val)) { + switch_set_string(globals.adoption_service, val); + } else if (!strcasecmp(var, "stun-server") && !ks_zstr(val)) { + switch_set_string(globals.stun_server, val); + } else if (!strcasecmp(var, "ssl-verify")) { + globals.ssl_verify = switch_true(val) ? 1 : 0; + } + } + if ((tmp = switch_xml_child(settings, "authentication"))) { + const char *txt = switch_xml_txt(tmp); + if (!ks_zstr(txt)) { + swclt_config_set_authentication(globals.config, txt); + } + } + } + switch_xml_free(xml); + } + + if ((data = getenv("SW_BLADE_BOOTSTRAP"))) { + switch_set_string(globals.blade_bootstrap, data); + } + + if ((data = getenv("SW_ADOPTION_SERVICE"))) { + strncpy(globals.adoption_service, data, sizeof(globals.adoption_service)); + } + + swclt_config_load_from_env(globals.config); + + return SWITCH_STATUS_SUCCESS; +} + +static ks_status_t load_credentials(void) +{ + ks_status_t status = KS_STATUS_SUCCESS; + char authpath[1024]; + char data[2048]; + FILE *fp = NULL; + ks_json_t *json = NULL; + + switch_snprintf(authpath, sizeof(authpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat"); + if (!(fp = fopen(authpath, "r"))) goto done; + + if (!fgets(data, sizeof(data), fp)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire authentication data from %s\n", authpath); + fclose(fp); + status = KS_STATUS_FAIL; + goto done; + } + fclose(fp); + + json = ks_json_parse(data); + if (!json) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to parse SignalWire authentication data from %s\n", authpath); + status = KS_STATUS_FAIL; + goto done; + } + status = load_credentials_from_json(json); + ks_json_delete(&json); + +done: + return status; +} + +static void mod_signalwire_session_auth_failed_handler(swclt_sess_t sess) +{ + char path[1024]; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire authentication failed\n"); + + switch_snprintf(path, sizeof(path), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat"); + unlink(path); + + switch_snprintf(path, sizeof(path), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat"); + unlink(path); + + globals.restarting = KS_TRUE; + + globals.adoption_backoff = 0; + globals.adoption_next = 0; + + globals.state = SW_STATE_ADOPTION; +} + +/* Dialplan INTERFACE */ +SWITCH_STANDARD_DIALPLAN(dialplan_hunt) +{ + switch_caller_extension_t *extension = NULL; + switch_channel_t *channel = switch_core_session_get_channel(session); + const char *network_ip = switch_channel_get_variable(channel, "sip_network_ip"); + const char *network_port = switch_channel_get_variable(channel, "sip_network_port"); + + if (!caller_profile) { + if (!(caller_profile = switch_channel_get_caller_profile(channel))) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error obtaining caller profile!\n"); + goto done; + } + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Processing %s <%s>->%s in context %s\n", + caller_profile->caller_id_name, caller_profile->caller_id_number, caller_profile->destination_number, caller_profile->context); + + if ((extension = switch_caller_extension_new(session, "signalwire", caller_profile->destination_number)) == NULL) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n"); + goto done; + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "call from %s:%s\n", network_ip, network_port); + + switch_mutex_lock(globals.mutex); + + if (network_ip && + !zstr_buf(globals.gateway_ip) && !strcmp(globals.gateway_ip, network_ip)) { + // good to go + char transfer_to[1024]; + + switch_snprintf(transfer_to, sizeof(transfer_to), "%s %s %s", caller_profile->destination_number, "XML", caller_profile->context); + switch_caller_extension_add_application(session, extension, "transfer", transfer_to); + } else { + switch_caller_extension_add_application(session, extension, "respond", "500"); + } + + switch_mutex_unlock(globals.mutex); + +done: + return extension; +} + +/** + * Module load or unload callback from core + * @param event the event + */ +static void on_module_load_unload(switch_event_t *event) +{ + const char *type = switch_event_get_header(event, "type"); + const char *name = switch_event_get_header(event, "name"); + if (!zstr(type) && !zstr(name) && !strcmp(type, "endpoint") && !strcmp(name, "sofia")) { + globals.profile_reload = KS_TRUE; + } +} + +/** + * Sofia sofia::gateway_state change callback + * @param event the event + */ +static void on_sofia_gateway_state(switch_event_t *event) +{ + const char *ip = switch_event_get_header(event, "Register-Network-IP"); + const char *port = switch_event_get_header(event, "Register-Network-Port"); + const char *state = switch_event_get_header(event, "State"); + const char *gateway = switch_event_get_header(event, "Gateway"); + + if (!ip || !port || !state || !gateway) { + return; + } + + if (!strcmp(gateway, "signalwire")) { + switch_mutex_lock(globals.mutex); + + if (!strcmp(state, "REGED")) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP Gateway registered to %s:%s\n", ip, port); + switch_set_string(globals.gateway_ip, ip); + switch_set_string(globals.gateway_port, port); + } else if (!strcmp(state, "NOREG")) { + globals.gateway_ip[0] = '\0'; + globals.gateway_port[0] = '\0'; + } + + switch_mutex_unlock(globals.mutex); + } +} + +/* Macro expands to: switch_status_t mod_signalwire_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */ +SWITCH_MODULE_LOAD_FUNCTION(mod_signalwire_load) +{ + switch_api_interface_t *api_interface = NULL; + switch_dialplan_interface_t *dialplan_interface; + const char *kslog_env = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + memset(&globals, 0, sizeof(globals)); + + kslog_env = getenv("KSLOG"); + if (kslog_env && kslog_env[0] && kslog_env[0] != '0') globals.kslog_on = KS_TRUE; + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + ks_global_set_logger(mod_signalwire_kslogger); + + SWITCH_ADD_API(api_interface, "signalwire", "SignalWire API", mod_signalwire_api_function, SIGNALWIRE_SYNTAX); + switch_console_set_complete("add signalwire debug"); + switch_console_set_complete("add signalwire debug 1"); + switch_console_set_complete("add signalwire debug 2"); + switch_console_set_complete("add signalwire debug 3"); + switch_console_set_complete("add signalwire debug 4"); + switch_console_set_complete("add signalwire debug 5"); + switch_console_set_complete("add signalwire debug 6"); + switch_console_set_complete("add signalwire debug 7"); + switch_console_set_complete("add signalwire kslog"); + switch_console_set_complete("add signalwire kslog on"); + switch_console_set_complete("add signalwire kslog off"); + switch_console_set_complete("add signalwire token"); + switch_console_set_complete("add signalwire adoption"); + switch_console_set_complete("add signalwire adopted"); + switch_console_set_complete("add signalwire update"); + switch_console_set_complete("add signalwire reload"); + + switch_xml_bind_search_function(xml_config_handler, SWITCH_XML_SECTION_CONFIG, NULL); + + ks_ssl_init_skip(KS_TRUE); + + swclt_init(KS_LOG_LEVEL_DEBUG); + + if (globals.kslog_on == KS_FALSE) { + ks_global_set_logger(NULL); + } else { + ks_global_set_logger(mod_signalwire_kslogger); + } + + // Configuration + swclt_config_create(&globals.config); + load_config(); + + switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, pool); + + switch_event_bind("mod_signalwire", SWITCH_EVENT_MODULE_LOAD, NULL, on_module_load_unload, NULL); + switch_event_bind("mod_signalwire", SWITCH_EVENT_MODULE_UNLOAD, NULL, on_module_load_unload, NULL); + switch_event_bind("mod_signalwire", SWITCH_EVENT_CUSTOM, "sofia::gateway_state", on_sofia_gateway_state, NULL); + + SWITCH_ADD_DIALPLAN(dialplan_interface, "signalwire", dialplan_hunt); + + // Load credentials if they exist from a prior adoption + load_credentials(); + + // SignalWire + swclt_sess_create(&globals.signalwire_session, + globals.blade_bootstrap, + globals.config); + swclt_sess_set_auth_failed_cb(globals.signalwire_session, mod_signalwire_session_auth_failed_handler); + + if (!globals.signalwire_session) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "signalwire_session create error\n"); + switch_goto_status(SWITCH_STATUS_TERM, err); + } + + swclt_hmon_register(&globals.signalwire_session_monitor, globals.signalwire_session, mod_signalwire_session_state_handler, NULL); + + // @todo register nodestore callbacks here if needed + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Welcome to\n" + " _____ _ ___ ___\n" + " / ___/(_)___ _____ ____ _/ / | / (_)_______\n" + " \\__ \\/ / __ `/ __ \\/ __ `/ /| | /| / / / ___/ _ \\\n" + " ___/ / / /_/ / / / / /_/ / / | |/ |/ / / / / __/\n" + " /____/_/\\__, /_/ /_/\\__,_/_/ |__/|__/_/_/ \\___/\n" + " /____/\n"); + + // storage_dir was missing in clean install + switch_dir_make_recursive(SWITCH_GLOBAL_dirs.storage_dir, SWITCH_DEFAULT_DIR_PERMS, pool); + + if ((status = mod_signalwire_load_or_generate_token()) != SWITCH_STATUS_SUCCESS) { + goto err; + } + + if (swclt_sess_has_authentication(globals.signalwire_session)) { + // Load cached profile if we already have one. We'll still connect to SignalWire and + // fetch a new profile in the background. + load_sip_config(); + if (globals.signalwire_profile) { + globals.state = SW_STATE_START_PROFILE; + } else { + globals.state = SW_STATE_OFFLINE; + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Connecting to SignalWire\n"); + swclt_sess_connect(globals.signalwire_session); + } else { + globals.state = SW_STATE_ADOPTION; + } + + goto done; + +err: + if (globals.signalwire_session) ks_handle_destroy(&globals.signalwire_session); + swclt_config_destroy(&globals.config); + ks_global_set_logger(NULL); + +done: + + return status; +} + +/* + Called when the system shuts down + Macro expands to: switch_status_t mod_signalwire_shutdown() */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_signalwire_shutdown) +{ + /* Cleanup dynamically allocated config settings */ + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Disconnecting from SignalWire\n"); + + switch_event_unbind_callback(on_module_load_unload); + switch_event_unbind_callback(on_sofia_gateway_state); + + // stop things that might try to use blade or kafka while they are shutting down + globals.shutdown = KS_TRUE; + + swclt_sess_disconnect(globals.signalwire_session); + while (swclt_hstate_current_get(globals.signalwire_session) == SWCLT_HSTATE_ONLINE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sleeping for pending disconnect\n"); + ks_sleep_ms(1000); + } + + //signalwire_dialplan_shutdown(); + // @todo signalwire profile unbinding and unloading + switch_xml_unbind_search_function_ptr(xml_config_handler); + + // kill signalwire, so nothing more can come into the system + ks_handle_destroy(&globals.signalwire_session); + + // cleanup config + swclt_config_destroy(&globals.config); + + // shutdown libblade (but not libks?) + swclt_shutdown(); + + return SWITCH_STATUS_SUCCESS; +} + +static void mod_signalwire_state_adoption(void) +{ + // keep trying to check adoption token for authentication + if (ks_time_now() >= globals.adoption_next) { + // Use a very very simple backoff algorithm, every time we try, backoff another minute + // so that after first try we wait 1 minute, after next try we wait 2 minutes, at third + // try we are waiting 3 minutes, upto a max backoff of 15 minutes between adoption checks + if (globals.adoption_backoff < 15) globals.adoption_backoff++; + globals.adoption_next = ks_time_now() + (globals.adoption_backoff * 60 * KS_USEC_PER_SEC); + if (mod_signalwire_adoption_post() != KS_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Next SignalWire adoption check in %"SWITCH_SIZE_T_FMT" minutes\n", globals.adoption_backoff); + } + } + if (globals.signalwire_reconnected) { + // OK to continue as is + globals.signalwire_reconnected = KS_FALSE; + } +} + +static void mod_signalwire_state_offline(void) +{ + if (globals.signalwire_reconnected) { + globals.signalwire_reconnected = KS_FALSE; + globals.state = SW_STATE_ONLINE; + } +} + +static void mod_signalwire_state_online(void) +{ + globals.signalwire_reconnected = KS_FALSE; + if (!swclt_sess_provisioning_setup(globals.signalwire_session, __on_provisioning_events, NULL)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connected to SignalWire\n"); + globals.state = SW_STATE_CONFIGURE; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Failed to connect to SignalWire\n"); + ks_sleep_ms(4000); + globals.state = SW_STATE_OFFLINE; + globals.restarting = KS_TRUE; + } +} + +static void mod_signalwire_state_configure(void) +{ + switch_memory_pool_t *pool = NULL; + char local_ip[256]; + switch_port_t local_port = 6050; + char local_endpoint[256]; + char *external_ip; + switch_port_t external_port; + char external_endpoint[256]; + char *error = NULL; + swclt_cmd_t cmd; + + if (globals.signalwire_reconnected) { + globals.signalwire_reconnected = KS_FALSE; + globals.state = SW_STATE_ONLINE; + } + + // already restarting/updating... + globals.profile_reload = KS_FALSE; + globals.profile_update = KS_FALSE; + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "SignalWire configure failed: could not allocate memory pool\n"); + goto done; + } + + switch_find_local_ip(local_ip, sizeof(local_ip), NULL, AF_INET); + + if (switch_find_available_port(&local_port, local_ip, SOCK_STREAM) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire configure failed: could not get available local port\n"); + ks_sleep_ms(4000); + goto done; + } + + snprintf(local_endpoint, sizeof(local_endpoint), "%s:%u", local_ip, local_port); + + external_ip = local_ip; + external_port = local_port; + + if (switch_stun_lookup(&external_ip, &external_port, globals.stun_server, SWITCH_STUN_DEFAULT_PORT, &error, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire configure failed: stun [%s] lookup error: %s\n", globals.stun_server, error); + ks_sleep_ms(4000); + goto done; + } + + snprintf(external_endpoint, sizeof(external_endpoint), "%s:%u", external_ip, external_port); + + if (!swclt_sess_provisioning_configure(globals.signalwire_session, "freeswitch", local_endpoint, external_endpoint, globals.relay_connector_id, &cmd)) { + SWCLT_CMD_TYPE cmd_type; + swclt_cmd_type(cmd, &cmd_type); + if (cmd_type == SWCLT_CMD_TYPE_RESULT) { + const ks_json_t *result; + signalwire_provisioning_configure_response_t *configure_res; + + swclt_cmd_result(cmd, &result); + result = ks_json_get_object_item(result, "result"); + if (!SIGNALWIRE_PROVISIONING_CONFIGURE_RESPONSE_PARSE(ks_handle_pool(cmd), result, &configure_res)) { + const ks_json_t *configuration = configure_res->configuration; + const char *configuration_profile = ks_json_get_object_cstr(configuration, "profile"); + if (globals.signalwire_profile) { + switch_xml_free(globals.signalwire_profile); + globals.signalwire_profile = NULL; + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "\"%s\"\n", configuration_profile); + globals.signalwire_profile = switch_xml_parse_str_dynamic((char *)configuration_profile, SWITCH_TRUE); + if (!globals.signalwire_profile) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to parse configuration profile\n"); + } else { + char digest[SWITCH_MD5_DIGEST_STRING_SIZE] = { 0 }; + switch_md5_string(digest, (void *) configuration_profile, strlen(configuration_profile)); + save_sip_config(configuration_profile); + if (!signalwire_gateway_exists() || zstr_buf(globals.signalwire_profile_md5) || strcmp(globals.signalwire_profile_md5, digest)) { + // not registered or new profile - update md5 and load it + strcpy(globals.signalwire_profile_md5, digest); + globals.state = SW_STATE_START_PROFILE; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "profile MD5 = \"%s\"\n", globals.signalwire_profile_md5); + } else { + // already registered + globals.state = SW_STATE_READY; + } + } + } + } + } + ks_handle_destroy(&cmd); + if (globals.state == SW_STATE_CONFIGURE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to receive valid configuration from SignalWire\n"); + ks_sleep_ms(4000); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Received configuration from SignalWire\n"); + } + +done: + if (pool) switch_core_destroy_memory_pool(&pool); +} + +static int signalwire_gateway_exists(void) +{ + int exists = 0; + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + if (switch_api_execute("sofia", "profile signalwire gwlist", NULL, &stream) == SWITCH_STATUS_SUCCESS && stream.data) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "gwlist = \"%s\"\n", (char *)stream.data); + exists = (strstr((char *)stream.data, "Invalid Profile") == NULL) && + (strstr((char*)stream.data, "signalwire") != NULL); + } + switch_safe_free(stream.data); + return exists; +} + +static int signalwire_profile_is_started(void) +{ + int started = 0; + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + if (switch_api_execute("sofia", "status profile signalwire", NULL, &stream) == SWITCH_STATUS_SUCCESS && stream.data) { + started = (strstr((char *)stream.data, "Invalid Profile") == NULL) && + (strstr((char *)stream.data, "signalwire") != NULL); + } + switch_safe_free(stream.data); + return started; +} + +static int signalwire_profile_rescan(void) +{ + int success = 0; + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + if (switch_api_execute("sofia", "profile signalwire rescan", NULL, &stream) == SWITCH_STATUS_SUCCESS) { + success = signalwire_profile_is_started(); + } + switch_safe_free(stream.data); + return success; +} + +static int signalwire_profile_start(void) +{ + int success = 0; + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + if (switch_api_execute("sofia", "profile signalwire start", NULL, &stream) == SWITCH_STATUS_SUCCESS) { + success = signalwire_profile_is_started(); + } + switch_safe_free(stream.data); + return success; +} + +static void signalwire_profile_killgw(void) +{ + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + switch_api_execute("sofia", "profile signalwire killgw signalwire", NULL, &stream); + switch_safe_free(stream.data); +} + +static void mod_signalwire_state_start_profile(void) +{ + if (globals.profile_update) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update initiated\n"); + globals.state = SW_STATE_CONFIGURE; + globals.profile_update = KS_FALSE; + return; + } + globals.profile_reload = KS_FALSE; // already here + + // ignore SignalWire reconnections until register is attempted + + if (switch_loadable_module_exists("mod_sofia") != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for mod_sofia to load\n"); + } else if (signalwire_profile_is_started()) { + // kill gateway if already up and rescan the profile + if (signalwire_gateway_exists()) { + signalwire_profile_killgw(); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP gateway killed\n"); + } + if (signalwire_profile_rescan()) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP profile rescanned\n"); + globals.state = SW_STATE_REGISTER; + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting SignalWire SIP profile\n"); + signalwire_profile_start(); // assume success - it gets checked in next state + globals.state = SW_STATE_REGISTER; + } +} + +static void mod_signalwire_state_register(void) +{ + if (globals.profile_update) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update initiated\n"); + globals.state = SW_STATE_CONFIGURE; + globals.profile_update = KS_FALSE; + return; + } else if (globals.profile_reload) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile reload initiated\n"); + globals.state = SW_STATE_START_PROFILE; + globals.profile_reload = KS_FALSE; + return; + } + // ignore SignalWire reconnections until register is attempted + + if (switch_loadable_module_exists("mod_sofia") != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for mod_sofia to load\n"); + globals.state = SW_STATE_START_PROFILE; + } else if (signalwire_gateway_exists() || signalwire_profile_rescan()) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP gateway started\n"); + globals.state = SW_STATE_READY; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to start SignalWire SIP gateway\n"); + globals.state = SW_STATE_CONFIGURE; + ks_sleep_ms(5000); + } +} + +static void mod_signalwire_state_ready() +{ + if (globals.profile_update) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Signalwire SIP profile update initiated\n"); + globals.state = SW_STATE_CONFIGURE; + globals.profile_update = KS_FALSE; + } else if (globals.profile_reload) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile reload initiated\n"); + globals.state = SW_STATE_START_PROFILE; + globals.profile_reload = KS_FALSE; + } else if (globals.signalwire_reconnected) { + globals.signalwire_reconnected = KS_FALSE; + globals.state = SW_STATE_ONLINE; + } +} + +SWITCH_MODULE_RUNTIME_FUNCTION(mod_signalwire_runtime) +{ + while (!globals.shutdown) { + if (globals.restarting) { + swclt_sess_disconnect(globals.signalwire_session); + while (swclt_hstate_current_get(globals.signalwire_session) == SWCLT_HSTATE_ONLINE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sleeping for pending disconnect\n"); + ks_sleep_ms(1000); + } + + // kill signalwire, so nothing more can come into the system + ks_handle_destroy(&globals.signalwire_session); + + // Create a new session and start over + swclt_sess_create(&globals.signalwire_session, + globals.blade_bootstrap, + globals.config); + swclt_sess_set_auth_failed_cb(globals.signalwire_session, mod_signalwire_session_auth_failed_handler); + + swclt_hmon_register(&globals.signalwire_session_monitor, globals.signalwire_session, mod_signalwire_session_state_handler, NULL); + + globals.restarting = KS_FALSE; + continue; + } + + switch(globals.state) { + case SW_STATE_ADOPTION: // waiting for adoption to occur + mod_signalwire_state_adoption(); + break; + case SW_STATE_OFFLINE: // waiting for session to go online + mod_signalwire_state_offline(); + break; + case SW_STATE_ONLINE: // provisioning service setup + mod_signalwire_state_online(); + break; + case SW_STATE_CONFIGURE: // provisioning configuration + mod_signalwire_state_configure(); + break; + case SW_STATE_START_PROFILE: + mod_signalwire_state_start_profile(); + break; + case SW_STATE_REGISTER: + mod_signalwire_state_register(); + break; + case SW_STATE_READY: // ready for runtime + mod_signalwire_state_ready(); + break; + default: break; + } + ks_sleep_ms(1000); + } + + 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 noet + */ diff --git a/w32/Setup/Setup.2017.wixproj b/w32/Setup/Setup.2017.wixproj index 0017c5acef..cb65aa947c 100644 --- a/w32/Setup/Setup.2017.wixproj +++ b/w32/Setup/Setup.2017.wixproj @@ -293,6 +293,14 @@ Binaries;Content;Satellites INSTALLFOLDER + + mod_signalwire + {b19ae6fc-bfff-428d-b483-3bbeaeccc618} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + mod_sms {2469b306-b027-4ff2-8815-c9c1ea2cae79} diff --git a/w32/libks-version.props b/w32/libks-version.props new file mode 100644 index 0000000000..3b9fe4b776 --- /dev/null +++ b/w32/libks-version.props @@ -0,0 +1,19 @@ + + + + + + + 0.1.1 + + + true + + + + + + $(libksVersion) + + + diff --git a/w32/libks.props b/w32/libks.props new file mode 100644 index 0000000000..c2e2565172 --- /dev/null +++ b/w32/libks.props @@ -0,0 +1,78 @@ + + + + + + + + + true + + + + $(BaseDir)libs\libks-$(libksVersion) + + + + + + + + + + + + + + + + + + + + + + + + $(libksDir)\src\include;%(AdditionalIncludeDirectories) + __PRETTY_FUNCTION__=__FUNCSIG__;WIN32;_WINDOWS;SWCLT_VERSION_MAJOR=1;SWCLT_VERSION_MINOR=0;SWCLT_VERSION_REVISION=0;_WIN32_WINNT=0x0600;_WINSOCK_DEPRECATED_NO_WARNINGS=1;WIN32_LEAN_AND_MEAN=1;KS_PLAT_WIN=1;NOMAXMIN=1;_CRT_SECURE_NO_WARNINGS=1;SWCLT_EXPORTS;%(PreprocessorDefinitions) + + + $(libksDir)\binaries\$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) + ks.lib;%(AdditionalDependencies) + + + diff --git a/w32/signalwire-client-c-version.props b/w32/signalwire-client-c-version.props new file mode 100644 index 0000000000..0b0de97606 --- /dev/null +++ b/w32/signalwire-client-c-version.props @@ -0,0 +1,19 @@ + + + + + + + 1.0.0 + + + true + + + + + + $(signalwire-client-cVersion) + + + diff --git a/w32/signalwire-client-c.props b/w32/signalwire-client-c.props new file mode 100644 index 0000000000..e7e40e2c4a --- /dev/null +++ b/w32/signalwire-client-c.props @@ -0,0 +1,76 @@ + + + + + + + + true + + + + $(BaseDir)libs\signalwire-client-c-$(signalwire-client-cVersion) + + + + + + + + + + + + + + + + + + + + + + + $(signalwire-client-cDir)\include;%(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + $(signalwire-client-cDir)\binaries\$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) + signalwire_client.lib;%(AdditionalDependencies) + + +