diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 24546bfbb3..e51ff574a8 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -35,11 +35,11 @@ jobs:
autoconf \
automake \
curl \
- ffmpeg@5 \
+ ffmpeg@7 \
gnu-sed \
jpeg \
ldns \
- libpq@16 \
+ libpq@18 \
libsndfile \
libtool \
lua \
@@ -60,8 +60,8 @@ jobs:
signalwire/homebrew-signalwire/spandsp \
&& \
brew link --force --overwrite \
- ffmpeg@5 \
- libpq@16
+ ffmpeg@7 \
+ libpq@18
- name: Bootstrap FreeSWITCH
run: ./bootstrap.sh -j
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 32c1b2c580..5e414ab835 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -107,6 +107,13 @@ jobs:
run: |
./run-tests.sh ${{ inputs.total-groups }} ${{ inputs.current-group }} --output-dir logs || exit 1
+ - name: Run libesl tests
+ if: ${{ inputs.current-group == 1 }}
+ shell: bash
+ working-directory: ${{ inputs.working-directory }}/../../libs/esl
+ run: |
+ make check
+
- name: Collect unit test logs
if: always()
shell: bash
diff --git a/build/next-release.txt b/build/next-release.txt
index 03cb3dc9a0..ac362175f6 100644
--- a/build/next-release.txt
+++ b/build/next-release.txt
@@ -1 +1 @@
-1.11.0-release
+1.11.1-release
diff --git a/conf/vanilla/vars.xml b/conf/vanilla/vars.xml
index 54a15a2534..ae7c355f99 100644
--- a/conf/vanilla/vars.xml
+++ b/conf/vanilla/vars.xml
@@ -430,4 +430,11 @@
+
+
+
diff --git a/configure.ac b/configure.ac
index b4938c80f0..cd7cd5794f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,10 +3,10 @@
# Must change all of the below together
# For a release, set revision for that tagged release as well and uncomment
-AC_INIT([freeswitch], [1.11.0-release], bugs@freeswitch.org)
+AC_INIT([freeswitch], [1.11.1-release], bugs@freeswitch.org)
AC_SUBST(SWITCH_VERSION_MAJOR, [1])
AC_SUBST(SWITCH_VERSION_MINOR, [11])
-AC_SUBST(SWITCH_VERSION_MICRO, [0-release])
+AC_SUBST(SWITCH_VERSION_MICRO, [1-release])
AC_SUBST(SWITCH_VERSION_REVISION, [])
AC_SUBST(SWITCH_VERSION_REVISION_HUMAN, [])
@@ -308,6 +308,11 @@ SWITCH_AM_CXXFLAGS="-I${switch_srcdir}/src/include -I${switch_builddir}/src/incl
SWITCH_AM_CPPFLAGS="-I${switch_srcdir}/src/include -I${switch_builddir}/src/include -I${switch_srcdir}/libs/libteletone/src"
SWITCH_AM_LDFLAGS="-lm"
+# Cap cJSON parser recursion depth. Default upstream limit (1000) can overflow
+# small thread stacks; both vendored cJSON copies (src/ and libs/esl/) honor this.
+APR_ADDTO(SWITCH_AM_CFLAGS, [-DCJSON_NESTING_LIMIT=64])
+APR_ADDTO(SWITCH_AM_CXXFLAGS, [-DCJSON_NESTING_LIMIT=64])
+
#set SOLINK variable based on compiler and host
if test "x${ax_cv_c_compiler_vendor}" = "xsun" ; then
SOLINK="-Bdynamic -dy -G"
@@ -1518,7 +1523,7 @@ PKG_CHECK_MODULES([V8FS_STATIC], [v8-6.1_static >= 6.1.298],[
])
])
-PKG_CHECK_MODULES([KS], [libks2 >= 2.0.0],[
+PKG_CHECK_MODULES([KS], [libks2 >= 2.0.11],[
AM_CONDITIONAL([HAVE_KS],[true])],[
PKG_CHECK_MODULES([KS], [libks >= 1.8.2],[
AM_CONDITIONAL([HAVE_KS],[true])],[
@@ -2140,6 +2145,7 @@ AC_CONFIG_FILES([Makefile
build/standalone_module/freeswitch.pc
build/modmake.rules
libs/esl/Makefile
+ libs/esl/tests/Makefile
libs/esl/perl/Makefile
libs/esl/php/Makefile
libs/xmlrpc-c/include/xmlrpc-c/config.h
diff --git a/libs/esl/Makefile.am b/libs/esl/Makefile.am
index 03f530d0bf..34ce19c53a 100644
--- a/libs/esl/Makefile.am
+++ b/libs/esl/Makefile.am
@@ -1,5 +1,5 @@
AUTOMAKE_OPTIONS = foreign subdir-objects
-SUBDIRS = . perl
+SUBDIRS = . perl tests
MYLIB=./.libs/libesl.a
LIBS=-lncurses -lpthread -lm
LDFLAGS=-L. $(SYSTEM_LDFLAGS)
diff --git a/libs/esl/src/esl.c b/libs/esl/src/esl.c
index 6f085e26db..383b981a08 100644
--- a/libs/esl/src/esl.c
+++ b/libs/esl/src/esl.c
@@ -1349,12 +1349,22 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_
if ((cl = esl_event_get_header(revent, "content-length"))) {
char *body;
esl_ssize_t sofar = 0;
-
+
len = atol(cl);
- body = malloc(len+1);
- esl_assert(body);
- *(body + len) = '\0';
-
+
+ if (len < 0 || len > ESL_MAX_CONTENT_LENGTH) {
+ esl_event_destroy(&revent);
+ goto fail;
+ }
+
+ body = malloc(len + 1);
+ if (!body) {
+ esl_event_destroy(&revent);
+ goto fail;
+ }
+
+ body[len] = '\0';
+
do {
esl_ssize_t r,s = esl_buffer_inuse(handle->packet_buf);
@@ -1367,6 +1377,7 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_
if (!(strerror_r(handle->errnum, handle->err, sizeof(handle->err))))
*(handle->err)=0;
free(body);
+ esl_event_destroy(&revent);
goto fail;
} else if (r == 0) {
continue;
diff --git a/libs/esl/src/include/esl.h b/libs/esl/src/include/esl.h
index 4d2baac871..ab1742be65 100644
--- a/libs/esl/src/include/esl.h
+++ b/libs/esl/src/include/esl.h
@@ -217,6 +217,8 @@ typedef enum {
#define esl_strlen_zero_buf(s) (*(s) == '\0')
#define end_of(_s) *(*_s == '\0' ? _s : _s + strlen(_s) - 1)
+#define ESL_MAX_CONTENT_LENGTH (16 * 1024 * 1024)
+
#ifdef WIN32
#include
#include
diff --git a/libs/esl/tests/Makefile.am b/libs/esl/tests/Makefile.am
new file mode 100644
index 0000000000..8e4b096e54
--- /dev/null
+++ b/libs/esl/tests/Makefile.am
@@ -0,0 +1,9 @@
+AUTOMAKE_OPTIONS = foreign
+
+noinst_PROGRAMS = test_recv_event
+TESTS = $(noinst_PROGRAMS)
+
+test_recv_event_SOURCES = test_recv_event.c
+test_recv_event_CFLAGS = $(AM_CFLAGS) -I$(switch_srcdir)/libs/esl/src/include
+test_recv_event_LDADD = $(top_builddir)/libs/esl/libesl.la
+test_recv_event_LDFLAGS = $(AM_LDFLAGS) -lpthread -lm
diff --git a/libs/esl/tests/test_recv_event.c b/libs/esl/tests/test_recv_event.c
new file mode 100644
index 0000000000..568e2a467b
--- /dev/null
+++ b/libs/esl/tests/test_recv_event.c
@@ -0,0 +1,96 @@
+/*
+ * test_recv_event.c
+ *
+ * Verifies that esl_recv_event() rejects out-of-range Content-Length
+ * values: negative numbers and values above ESL_MAX_CONTENT_LENGTH must
+ * cause the function to return ESL_FAIL and mark the handle as
+ * disconnected, leaving no allocated state behind.
+ *
+ * POSIX-only: uses socketpair(2). Returns 77 on Windows so automake
+ * marks the test as skipped.
+ */
+
+#include
+#include
+#include
+
+#ifdef _WIN32
+
+int main(void)
+{
+ return 77;
+}
+
+#else
+
+#include
+#include
+#include
+
+#include
+
+#define TEST_ASSERT(cond) do { \
+ if (!(cond)) { \
+ fprintf(stderr, "FAIL %s:%d: %s\n", \
+ __FILE__, __LINE__, #cond); \
+ exit(1); \
+ } \
+} while (0)
+
+static void prepare_handle(esl_handle_t *h, esl_socket_t s)
+{
+ memset(h, 0, sizeof(*h));
+ h->sock = s;
+ h->connected = 1;
+ TEST_ASSERT(esl_mutex_create(&h->mutex) == ESL_SUCCESS);
+ TEST_ASSERT(esl_buffer_create(&h->packet_buf,
+ BUF_CHUNK, BUF_START, 0) == ESL_SUCCESS);
+}
+
+static void expect_rejected(const char *frame, const char *desc)
+{
+ int sv[2];
+ esl_handle_t h;
+ size_t n = strlen(frame);
+ ssize_t w;
+
+ fprintf(stderr, " case: %s\n", desc);
+
+ TEST_ASSERT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0);
+
+ prepare_handle(&h, sv[0]);
+
+ w = write(sv[1], frame, n);
+ TEST_ASSERT(w == (ssize_t) n);
+ close(sv[1]);
+
+ TEST_ASSERT(esl_recv_event(&h, 0, NULL) == ESL_FAIL);
+ TEST_ASSERT(h.connected == 0);
+
+ esl_disconnect(&h);
+}
+
+int main(void)
+{
+ fprintf(stderr, "test_recv_event: invalid Content-Length is rejected\n");
+
+ expect_rejected(
+ "Content-Type: text/event-plain\n"
+ "Content-Length: -1\n\n",
+ "negative Content-Length: -1");
+
+ expect_rejected(
+ "Content-Type: text/event-plain\n"
+ "Content-Length: -2\n\n",
+ "negative Content-Length: -2");
+
+ expect_rejected(
+ "Content-Type: text/event-plain\n"
+ "Content-Length: 99999999999\n\n",
+ "Content-Length above ESL_MAX_CONTENT_LENGTH");
+
+ fprintf(stderr, "OK\n");
+ return 0;
+}
+
+#endif
diff --git a/src/include/switch_types.h b/src/include/switch_types.h
index 294c64ee3f..a68d5bbf70 100644
--- a/src/include/switch_types.h
+++ b/src/include/switch_types.h
@@ -2075,6 +2075,7 @@ typedef uint32_t switch_io_flag_t;
SWITCH_EVENT_CALL_DETAIL
SWITCH_EVENT_DEVICE_STATE
SWITCH_EVENT_SHUTDOWN_REQUESTED - Shutdown of the system has been requested
+ SWITCH_EVENT_CERT_RELOAD - SSL/TLS certificates reload has been requested
SWITCH_EVENT_ALL - All events at once
@@ -2172,6 +2173,7 @@ typedef enum {
SWITCH_EVENT_DEVICE_STATE,
SWITCH_EVENT_TEXT,
SWITCH_EVENT_SHUTDOWN_REQUESTED,
+ SWITCH_EVENT_CERT_RELOAD,
SWITCH_EVENT_ALL
} switch_event_types_t;
diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c
index 9184a78b93..b44035044a 100644
--- a/src/mod/applications/mod_commands/mod_commands.c
+++ b/src/mod/applications/mod_commands/mod_commands.c
@@ -549,7 +549,7 @@ SWITCH_STANDARD_API(reg_url_function)
char *domain = NULL, *dup_domain = NULL;
char *concat = NULL;
const char *exclude_contact = NULL;
- char *reply = "error/facility_not_subscribed";
+ char *reply;
switch_stream_handle_t mystream = { 0 };
if (!cmd) {
@@ -2324,7 +2324,7 @@ SWITCH_STANDARD_API(status_function)
int sps = 0, last_sps = 0, max_sps = 0, max_sps_fivemin = 0;
int sessions_peak = 0, sessions_peak_fivemin = 0; /* Max Concurrent Sessions buffers */
switch_bool_t html = SWITCH_FALSE; /* shortcut to format.html */
- char * nl = "\n"; /* shortcut to format.nl */
+ char *nl; /* shortcut to format.nl */
stream_format format = { 0 };
switch_size_t cur = 0, max = 0;
@@ -2860,6 +2860,22 @@ SWITCH_STANDARD_API(reload_xml_function)
return SWITCH_STATUS_SUCCESS;
}
+SWITCH_STANDARD_API(reload_cert_function)
+{
+ switch_event_t *event;
+
+ if (switch_event_create(&event, SWITCH_EVENT_CERT_RELOAD) == SWITCH_STATUS_SUCCESS) {
+ switch_event_fire(&event);
+ stream->write_function(stream, "+OK cert reload event sent\n");
+
+ return SWITCH_STATUS_SUCCESS;
+ }
+
+ stream->write_function(stream, "-ERR failed to create event\n");
+
+ return SWITCH_STATUS_FALSE;
+}
+
#define KILL_SYNTAX " [cause]"
SWITCH_STANDARD_API(kill_function)
{
@@ -7523,7 +7539,7 @@ SWITCH_STANDARD_JSON_API(json_status_function)
SWITCH_STANDARD_API(json_function)
{
cJSON *jcmd = NULL, *format = NULL;
- const char *message = "";
+ const char *message;
char *response = NULL;
if (zstr(cmd)) {
@@ -7653,9 +7669,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
SWITCH_ADD_API(commands_api_interface, "pool_stats", "Core pool memory usage", pool_stats_function, "Core pool memory usage.");
SWITCH_ADD_API(commands_api_interface, "quote_shell_arg", "Quote/escape a string for use on shell command line", quote_shell_arg_function, "");
SWITCH_ADD_API(commands_api_interface, "regex", "Evaluate a regex", regex_function, "|[|][n|b]");
- SWITCH_ADD_API(commands_api_interface, "reloadacl", "Reload XML", reload_acl_function, "");
+ SWITCH_ADD_API(commands_api_interface, "reloadacl", "Reload ACL", reload_acl_function, "");
SWITCH_ADD_API(commands_api_interface, "reload", "Reload module", reload_function, UNLOAD_SYNTAX);
SWITCH_ADD_API(commands_api_interface, "reloadxml", "Reload XML", reload_xml_function, "");
+ SWITCH_ADD_API(commands_api_interface, "reloadcert", "Reload SSL/TLS certificates", reload_cert_function, "");
SWITCH_ADD_API(commands_api_interface, "replace", "Replace a string", replace_function, "||");
SWITCH_ADD_API(commands_api_interface, "say_string", "", say_string_function, SAY_STRING_SYNTAX);
SWITCH_ADD_API(commands_api_interface, "sched_api", "Schedule an api command", sched_api_function, SCHED_SYNTAX);
@@ -7831,6 +7848,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
switch_console_set_complete("add nat_map status");
switch_console_set_complete("add reload ::console::list_loaded_modules");
switch_console_set_complete("add reloadacl reloadxml");
+ switch_console_set_complete("add reloadcert");
switch_console_set_complete("add show aliases");
switch_console_set_complete("add show api");
switch_console_set_complete("add show application");
diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c
index aa606170d5..4f0374263d 100644
--- a/src/mod/applications/mod_conference/mod_conference.c
+++ b/src/mod/applications/mod_conference/mod_conference.c
@@ -1911,8 +1911,8 @@ SWITCH_STANDARD_APP(conference_function)
member_flag_t mflags[MFLAG_MAX] = { 0 };
switch_core_session_message_t msg = { 0 };
uint8_t isbr = 0;
- char *dpin = "";
- const char *mdpin = "";
+ char *dpin;
+ const char *mdpin;
conference_xml_cfg_t xml_cfg = { 0 };
switch_event_t *params = NULL;
int locked = 0;
diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_fax.c b/src/mod/applications/mod_spandsp/mod_spandsp_fax.c
index 8c8d0103c4..0767315c92 100644
--- a/src/mod/applications/mod_spandsp/mod_spandsp_fax.c
+++ b/src/mod/applications/mod_spandsp/mod_spandsp_fax.c
@@ -562,7 +562,7 @@ static void phase_e_handler(void *user_data, int result)
switch_event_t *event;
const char *var;
char *expanded;
- const char *fax_result_str = "";
+ const char *fax_result_str;
pvt = (pvt_t *) user_data;
switch_assert(pvt);
diff --git a/src/mod/endpoints/mod_sofia/Makefile.am b/src/mod/endpoints/mod_sofia/Makefile.am
index 3a5d295ddb..378ba06c73 100644
--- a/src/mod/endpoints/mod_sofia/Makefile.am
+++ b/src/mod/endpoints/mod_sofia/Makefile.am
@@ -15,7 +15,7 @@ mod_sofia_la_SOURCES =
mod_sofia_la_LIBADD = $(switch_builddir)/libfreeswitch.la libsofiamod.la
mod_sofia_la_LDFLAGS = -avoid-version -module -no-undefined -shared $(SOFIA_SIP_LIBS) $(STIRSHAKEN_LIBS)
-noinst_PROGRAMS = test/test_sofia_funcs test/test_nuafail test/sipp-based-tests
+noinst_PROGRAMS = test/test_sofia_funcs test/test_nuafail test/sipp-based-tests test/test_603plus
test_test_sofia_funcs_SOURCES = test/test_sofia_funcs.c
test_test_sofia_funcs_CFLAGS = $(AM_CFLAGS) $(SOFIA_SIP_CFLAGS) $(STIRSHAKEN_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
@@ -25,6 +25,11 @@ endif
test_test_sofia_funcs_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS) $(STIRSHAKEN_LIBS)
test_test_sofia_funcs_LDADD = libsofiamod.la $(SOFIA_SIP_LIBS) $(STIRSHAKEN_LIBS)
+test_test_603plus_SOURCES = test/test_603plus.c
+test_test_603plus_CFLAGS = $(AM_CFLAGS) $(SOFIA_SIP_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
+test_test_603plus_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
+test_test_603plus_LDADD = libsofiamod.la $(SOFIA_SIP_LIBS)
+
test_test_nuafail_SOURCES = test/test_nuafail.c
test_test_nuafail_CFLAGS = $(AM_CFLAGS) $(SOFIA_SIP_CFLAGS) $(STIRSHAKEN_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
if HAVE_STIRSHAKEN
@@ -38,13 +43,14 @@ test_sipp_based_tests_CFLAGS = $(AM_CFLAGS) $(SOFIA_SIP_CFLAGS) -DSWITCH_TEST_BA
test_sipp_based_tests_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
test_sipp_based_tests_LDADD = libsofiamod.la $(SOFIA_SIP_LIBS)
-TESTS = test/test_sofia_funcs.sh test/test_nuafail
+TESTS = test/test_sofia_funcs.sh test/test_nuafail test/test_603plus
#TESTS += test/test_run_sipp.sh
if ISMAC
mod_sofia_la_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
test_test_sofia_funcs_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
test_test_nuafail_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
+test_test_603plus_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
test_sipp_based_tests_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
endif
diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c
index 086d6dd088..47b2d0681e 100644
--- a/src/mod/endpoints/mod_sofia/mod_sofia.c
+++ b/src/mod/endpoints/mod_sofia/mod_sofia.c
@@ -493,6 +493,7 @@ switch_status_t sofia_on_hangup(switch_core_session_t *session)
const char *val = NULL;
const char *max_forwards = switch_channel_get_variable(channel, SWITCH_MAX_FORWARDS_VARIABLE);
const char *call_info = switch_channel_get_variable(channel, "presence_call_info_full");
+ const char *passthrough_603plus = switch_channel_get_variable(channel, "sip_603plus_passthrough");
const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile);
val = switch_channel_get_variable(tech_pvt->channel, "disable_q850_reason");
@@ -512,6 +513,23 @@ switch_status_t sofia_on_hangup(switch_core_session_t *session)
}
}
+ /* 603+ (ATIS-1000099) Reason header override — applied after standard reason construction.
+ *
+ * passthrough=true: Restore 603+ Reason even if disable_q850_reason suppressed it.
+ * Allows selective forwarding of 603+ while suppressing other Reason headers.
+ * passthrough=false: Strip Reason header entirely — send clean 603 Decline with no Reason. */
+ if (passthrough_603plus) {
+ const char *reason_603plus = switch_channel_get_variable(channel, "sip_603plus_reason");
+
+ if (!zstr(reason_603plus)) {
+ if (switch_true(passthrough_603plus)) {
+ reason = switch_core_session_sprintf(session, "%s", reason_603plus);
+ } else if (switch_false(passthrough_603plus)) {
+ reason = switch_core_session_sprintf(session, "");
+ }
+ }
+ }
+
if (switch_channel_test_flag(channel, CF_INTERCEPT) || cause == SWITCH_CAUSE_PICKED_OFF || cause == SWITCH_CAUSE_LOSE_RACE) {
switch_channel_set_variable(channel, "call_completed_elsewhere", "true");
}
@@ -557,6 +575,11 @@ switch_status_t sofia_on_hangup(switch_core_session_t *session)
if (tech_pvt->respond_phrase) {
//phrase = su_strdup(nua_handle_home(tech_pvt->nh), tech_pvt->respond_phrase);
phrase = tech_pvt->respond_phrase;
+ } else if (sip_cause == 603
+ && !zstr(reason)
+ && switch_true(passthrough_603plus)
+ && !zstr(switch_channel_get_variable(channel, "sip_603plus_reason"))) {
+ phrase = "Network Blocked";
} else {
phrase = sip_status_phrase(sip_cause);
}
@@ -4091,7 +4114,7 @@ SWITCH_STANDARD_API(sofia_contact_function)
sofia_profile_t *profile = NULL;
const char *exclude_contact = NULL;
const char *match_user_agent = NULL;
- char *reply = "error/facility_not_subscribed";
+ char *reply;
switch_stream_handle_t mystream = { 0 };
if (!cmd) {
@@ -6528,6 +6551,42 @@ char *sofia_stir_shaken_as_create_identity_header(switch_core_session_t *session
}
+#ifdef HAVE_NUA_RELOAD_TLS
+static void sofia_cert_reload_handler(switch_event_t *event)
+{
+ switch_hash_index_t *hi;
+ const void *vvar;
+ void *val;
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Certificate reload event received, processing\n");
+
+ switch_mutex_lock(mod_sofia_globals.hash_mutex);
+
+ for (hi = switch_core_hash_first(mod_sofia_globals.profile_hash); hi; hi = switch_core_hash_next(&hi)) {
+ sofia_profile_t *profile;
+
+ switch_core_hash_this(hi, &vvar, NULL, &val);
+ profile = (sofia_profile_t *) val;
+
+ if (!sofia_test_pflag(profile, PFLAG_RUNNING) || !profile->nua || !profile->tls_cert_dir) {
+ continue;
+ }
+
+ if (strcmp(vvar, profile->name)) {
+ continue;
+ }
+
+ nua_reload_tls(profile->nua, profile->tls_cert_dir);
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "TLS certificate reload signaled for sofia profile %s\n", profile->name);
+ }
+
+ switch_mutex_unlock(mod_sofia_globals.hash_mutex);
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Certificate reload event processed\n");
+}
+#endif
+
SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load)
{
switch_chat_interface_t *chat_interface;
@@ -6694,6 +6753,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load)
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for profiles to start\n");
switch_yield(1500000);
+#ifdef HAVE_NUA_RELOAD_TLS
+ switch_event_bind(modname, SWITCH_EVENT_CERT_RELOAD, SWITCH_EVENT_SUBCLASS_ANY, sofia_cert_reload_handler, NULL);
+#endif
+
if (switch_event_bind(modname, SWITCH_EVENT_CUSTOM, MULTICAST_EVENT, event_handler, NULL) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind!\n");
switch_goto_status(SWITCH_STATUS_TERM, err);
@@ -6876,6 +6939,9 @@ void mod_sofia_shutdown_cleanup(void) {
}
switch_mutex_unlock(mod_sofia_globals.mutex);
+#ifdef HAVE_NUA_RELOAD_TLS
+ switch_event_unbind_callback(sofia_cert_reload_handler);
+#endif
switch_event_unbind_callback(sofia_presence_event_handler);
switch_event_unbind_callback(general_queue_event_handler);
diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.h b/src/mod/endpoints/mod_sofia/mod_sofia.h
index 8e2b1b483c..3689f82a20 100644
--- a/src/mod/endpoints/mod_sofia/mod_sofia.h
+++ b/src/mod/endpoints/mod_sofia/mod_sofia.h
@@ -168,7 +168,6 @@ typedef struct sofia_dispatch_event_s {
int save;
switch_core_session_t *session;
switch_core_session_t *init_session;
- switch_memory_pool_t *pool;
struct sofia_dispatch_event_s *next;
} sofia_dispatch_event_t;
diff --git a/src/mod/endpoints/mod_sofia/sofia.c b/src/mod/endpoints/mod_sofia/sofia.c
index 77e4750812..8d82e9d3fd 100644
--- a/src/mod/endpoints/mod_sofia/sofia.c
+++ b/src/mod/endpoints/mod_sofia/sofia.c
@@ -1210,7 +1210,7 @@ void sofia_update_callee_id(switch_core_session_t *session, sofia_profile_t *pro
switch_channel_t *channel = switch_core_session_get_channel(session);
sip_p_asserted_identity_t *passerted = NULL;
char *name = NULL;
- const char *number = "unknown", *tmp;
+ const char *number, *tmp;
switch_caller_profile_t *caller_profile;
char *dup = NULL;
switch_event_t *event;
@@ -2199,22 +2199,15 @@ static uint32_t DE_THREAD_CNT = 0;
void *SWITCH_THREAD_FUNC sofia_msg_thread_run_once(switch_thread_t *thread, void *obj)
{
sofia_dispatch_event_t *de = (sofia_dispatch_event_t *) obj;
- switch_memory_pool_t *pool = NULL;
switch_mutex_lock(mod_sofia_globals.mutex);
DE_THREAD_CNT++;
switch_mutex_unlock(mod_sofia_globals.mutex);
if (de) {
- pool = de->pool;
- de->pool = NULL;
sofia_process_dispatch_event(&de);
}
- if (pool) {
- switch_core_destroy_memory_pool(&pool);
- }
-
switch_mutex_lock(mod_sofia_globals.mutex);
DE_THREAD_CNT--;
switch_mutex_unlock(mod_sofia_globals.mutex);
@@ -2225,16 +2218,12 @@ void *SWITCH_THREAD_FUNC sofia_msg_thread_run_once(switch_thread_t *thread, void
void sofia_process_dispatch_event_in_thread(sofia_dispatch_event_t **dep)
{
sofia_dispatch_event_t *de = *dep;
- switch_memory_pool_t *pool;
- //sofia_profile_t *profile = (*dep)->profile;
switch_thread_data_t *td;
- switch_core_new_memory_pool(&pool);
-
*dep = NULL;
- de->pool = pool;
- td = switch_core_alloc(pool, sizeof(*td));
+ switch_zmalloc(td, sizeof(*td));
+ td->alloc = 1;
td->func = sofia_msg_thread_run_once;
td->obj = de;
@@ -2460,13 +2449,15 @@ void sofia_event_callback(nua_event_t event,
}
if (!sofia_private) {
- if (sess_count >= sess_max || !sofia_test_pflag(profile, PFLAG_RUNNING) || !switch_core_ready_inbound()) {
+ int unavailable = (sess_count >= sess_max || !sofia_test_pflag(profile, PFLAG_RUNNING) || !switch_core_ready_inbound());
+ int bypass = (event == nua_i_options && !sofia_test_pflag(profile, PFLAG_OPTIONS_RESPOND_503_ON_BUSY));
+
+ if (unavailable && !bypass) {
nua_respond(nh, 503, "Maximum Calls In Progress", SIPTAG_RETRY_AFTER_STR("300"), NUTAG_WITH_THIS(nua), TAG_END());
nua_handle_destroy(nh);
goto end;
}
-
if (switch_queue_size(mod_sofia_globals.msg_queue) > (unsigned int)critical) {
nua_respond(nh, 503, "System Busy", SIPTAG_RETRY_AFTER_STR("300"), NUTAG_WITH_THIS(nua), TAG_END());
nua_handle_destroy(nh);
@@ -6654,15 +6645,36 @@ static void sofia_handle_sip_r_invite(switch_core_session_t *session, int status
}
if (status >= 400) {
+ char *reason_header = NULL;
char status_str[5];
switch_snprintf(status_str, sizeof(status_str), "%d", status);
switch_channel_set_variable(channel, "sip_invite_failure_status", status_str);
switch_channel_set_variable(channel, "sip_invite_failure_phrase", phrase);
switch_channel_set_variable_partner(channel, "sip_invite_failure_status", status_str);
switch_channel_set_variable_partner(channel, "sip_invite_failure_phrase", phrase);
+
+ reason_header = sip_header_as_string(nua_handle_get_home(nh), (void *) sip->sip_reason);
+ if (!zstr(reason_header)) {
+ switch_channel_set_variable(channel, "sip_reason", reason_header);
+ switch_channel_set_variable_partner(channel, "sip_reason", reason_header);
+ }
+
+ /* 603+ (ATIS-1000099) detection: clear stale state from serial forking, then check */
+ switch_channel_set_variable(channel, "sip_603plus_reason", NULL);
+ switch_channel_set_variable_partner(channel, "sip_603plus_reason", NULL);
+
+ if (status == 603 && phrase && !strcasecmp(phrase, "Network Blocked")
+ && sip->sip_reason && sip->sip_reason->re_text
+ && !strncmp(sip->sip_reason->re_text, "\"v=analytics1;", 14)
+ && !zstr(reason_header)) {
+
+ switch_channel_set_variable(channel, "sip_603plus_reason", reason_header);
+ switch_channel_set_variable_partner(channel, "sip_603plus_reason", reason_header);
+ }
} else {
switch_channel_set_variable_partner(channel, "sip_invite_failure_status", NULL);
switch_channel_set_variable_partner(channel, "sip_invite_failure_phrase", NULL);
+ switch_channel_set_variable_partner(channel, "sip_603plus_reason", NULL);
}
if (status >= 400 && sip->sip_reason && sip->sip_reason->re_protocol && (!strcasecmp(sip->sip_reason->re_protocol, "Q.850")
@@ -7533,7 +7545,7 @@ static void sofia_handle_sip_i_state(switch_core_session_t *session, int status,
}
if (channel && profile->pres_type && ss_state == nua_callstate_ready && status == 200) {
- const char* to_tag = "";
+ const char* to_tag;
char *sql = NULL;
to_tag = switch_str_nil(switch_channel_get_variable(channel, "sip_to_tag"));
sql = switch_mprintf("update sip_dialogs set sip_to_tag='%q' "
@@ -10390,7 +10402,7 @@ void sofia_handle_sip_i_invite(switch_core_session_t *session, nua_t *nua, sofia
nua_handle_t *bnh = NULL;
char sip_acl_authed_by[512] = "";
char sip_acl_token[512] = "";
- const char *dialog_from_user = "", *dialog_from_host = "", *to_user = "", *to_host = "", *contact_user = "", *contact_host = "";
+ const char *dialog_from_user = "", *dialog_from_host = "", *to_user = "", *to_host = "", *contact_user, *contact_host;
const char *user_agent = "", *call_id = "";
url_t *from = NULL, *to = NULL, *contact = NULL;
const char *to_tag = "";
@@ -11633,7 +11645,7 @@ void sofia_handle_sip_i_invite(switch_core_session_t *session, nua_t *nua, sofia
if (profile->pres_type) {
const char *presence_data = switch_channel_get_variable(channel, "presence_data");
const char *presence_id = switch_channel_get_variable(channel, "presence_id");
- char *full_contact = "";
+ char *full_contact;
char *p = NULL;
time_t now;
diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c
index 33904aaac4..d490ebd65c 100644
--- a/src/mod/endpoints/mod_sofia/sofia_glue.c
+++ b/src/mod/endpoints/mod_sofia/sofia_glue.c
@@ -1038,7 +1038,7 @@ switch_status_t sofia_glue_do_invite(switch_core_session_t *session)
switch_caller_profile_t *caller_profile;
const char *cid_name, *cid_num;
char *e_dest = NULL;
- const char *holdstr = "";
+ const char *holdstr;
char *extra_headers = NULL;
switch_status_t status = SWITCH_STATUS_FALSE;
uint32_t session_timeout = tech_pvt->profile->session_timeout;
@@ -3334,7 +3334,7 @@ char *sofia_glue_gen_contact_str(sofia_profile_t *profile, sip_t const *sip, nua
const char *contact_host;//, *contact_user;
sip_contact_t const *contact;
char *port;
- const char *display = "\"user\"";
+ const char *display;
char new_port[25] = "";
sofia_nat_parse_t lnp = { { 0 } };
const char *ipv6;
diff --git a/src/mod/endpoints/mod_sofia/sofia_presence.c b/src/mod/endpoints/mod_sofia/sofia_presence.c
index 151d6ec634..6108cc5a6a 100644
--- a/src/mod/endpoints/mod_sofia/sofia_presence.c
+++ b/src/mod/endpoints/mod_sofia/sofia_presence.c
@@ -2392,7 +2392,7 @@ static int sofia_dialog_probe_notify_callback(void *pArg, int argc, char **argv,
switch_stream_handle_t stream = { 0 };
char *to;
const char *pl = NULL;
- const char *ct = "application/dialog-info+xml";
+ const char *ct;
if (mod_sofia_globals.debug_presence > 0) {
int i;
@@ -3659,7 +3659,7 @@ void sofia_presence_handle_sip_i_subscribe(int status,
char *orig_proto = "";
char *alt_proto = NULL;
char *d_user = NULL;
- char *contact_str = "";
+ char *contact_str;
const char *call_id = NULL;
char *to_str = NULL;
char *full_from = NULL;
diff --git a/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml b/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml
index fa626af93e..6a2959918e 100644
--- a/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml
+++ b/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml
@@ -140,6 +140,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mod/endpoints/mod_sofia/test/test_603plus.c b/src/mod/endpoints/mod_sofia/test/test_603plus.c
new file mode 100644
index 0000000000..658881c884
--- /dev/null
+++ b/src/mod/endpoints/mod_sofia/test/test_603plus.c
@@ -0,0 +1,490 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2026, 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):
+ *
+ * Dmitry Verenitsin
+ *
+ *
+ * test_603plus.c -- Tests for SIP 603+ (ATIS-1000099) detection and passthrough
+ *
+ * Detection requires BOTH:
+ * 1. SIP status 603 with phrase "Network Blocked" (case-insensitive)
+ * 2. Reason header text starts with "v=analytics1;" (ATIS version AVP)
+ *
+ * Test approach: originate via loopback gateway (same FS instance).
+ * The responding extension sends a crafted 603 with/without Reason header.
+ * We bind to CHANNEL_HANGUP_COMPLETE to capture sip_603plus_reason from
+ * the outbound leg before it is destroyed.
+ *
+ * Passthrough tests use a bridge scenario: originate -> middle extension
+ * (sets passthrough) -> bridges to 603+ target. The originate leg receives
+ * the response FROM the middle box, letting us verify what was actually sent.
+ */
+
+#include
+#include
+
+/* Event capture state */
+
+static struct {
+ char sip_603plus_reason[1024];
+ char sip_invite_failure_phrase[256];
+ char sip_reason[1024];
+ switch_bool_t received;
+} capture;
+
+static void reset_capture(void)
+{
+ memset(capture.sip_603plus_reason, 0, sizeof(capture.sip_603plus_reason));
+ memset(capture.sip_invite_failure_phrase, 0, sizeof(capture.sip_invite_failure_phrase));
+ memset(capture.sip_reason, 0, sizeof(capture.sip_reason));
+ capture.received = SWITCH_FALSE;
+}
+
+static void on_hangup_complete(switch_event_t *event)
+{
+ const char *direction, *val;
+
+ /* Only capture from outbound legs (the originating call, not the responder).
+ * In bridge tests, multiple outbound legs hang up (bridge B-leg, then originate O-leg).
+ * Reset on every outbound event so the last one (O-leg) wins cleanly. */
+ direction = switch_event_get_header(event, "Call-Direction");
+ if (zstr(direction) || strcmp(direction, "outbound")) return;
+
+ reset_capture();
+
+ val = switch_event_get_header(event, "variable_sip_603plus_reason");
+ if (!zstr(val)) {
+ switch_snprintf(capture.sip_603plus_reason, sizeof(capture.sip_603plus_reason), "%s", val);
+ }
+
+ val = switch_event_get_header(event, "variable_sip_invite_failure_phrase");
+ if (!zstr(val)) {
+ switch_snprintf(capture.sip_invite_failure_phrase, sizeof(capture.sip_invite_failure_phrase), "%s", val);
+ }
+
+ val = switch_event_get_header(event, "variable_sip_reason");
+ if (!zstr(val)) {
+ switch_snprintf(capture.sip_reason, sizeof(capture.sip_reason), "%s", val);
+ }
+
+ capture.received = SWITCH_TRUE;
+}
+
+static void originate_and_wait(const char *dest, switch_call_cause_t *cause)
+{
+ switch_core_session_t *session = NULL;
+
+ switch_ivr_originate(NULL, &session, cause,
+ dest, 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+
+ if (session) {
+ switch_channel_hangup(switch_core_session_get_channel(session), SWITCH_CAUSE_NORMAL_CLEARING);
+ switch_core_session_rwunlock(session);
+ }
+
+ /* Let event dispatch thread deliver CHANNEL_HANGUP_COMPLETE */
+ switch_yield(1000000);
+}
+
+/* Test suite */
+
+FST_CORE_EX_BEGIN("./conf", SCF_VG | SCF_USE_SQL)
+{
+FST_MODULE_BEGIN(mod_sofia, sofia)
+{
+ FST_SETUP_BEGIN()
+ {
+ }
+ FST_SETUP_END()
+
+ FST_TEARDOWN_BEGIN()
+ {
+ }
+ FST_TEARDOWN_END()
+
+ /* Detection: positive cases */
+
+ FST_TEST_BEGIN(detect_valid_603plus_sip)
+ {
+ /*
+ * Extension +15553336050 sends:
+ * 603 Network Blocked
+ * Reason: SIP;cause=603;text="v=analytics1;url=https://example.com/redress";location=TN
+ *
+ * Both conditions met -> sip_603plus_reason MUST be set.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336050", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(!zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must be set for valid 603+");
+ fst_xcheck(!!strstr(capture.sip_603plus_reason, "v=analytics1"), "sip_603plus_reason must contain v=analytics1");
+ fst_xcheck(!strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked"), "Failure phrase must be 'Network Blocked'");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(detect_valid_603plus_q850)
+ {
+ /*
+ * Extension +15553336051 sends:
+ * 603 Network Blocked
+ * Reason: Q.850;cause=21;text="v=analytics1;url=https://example.com/redress";location=LN
+ *
+ * Q.850 protocol is equally valid per ATIS-1000099 section 4.1.1.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336051", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(!zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must be set for Q.850 ATIS Reason");
+ fst_xcheck(!!strstr(capture.sip_603plus_reason, "v=analytics1"), "sip_603plus_reason must contain v=analytics1");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(detect_603plus_after_180)
+ {
+ /*
+ * Extension +15553336056 sends 180 Ringing, waits 500ms, then:
+ * 603 Network Blocked
+ * Reason: SIP;cause=603;text="v=analytics1;url=https://example.com/redress";location=TN
+ *
+ * Detection must work after provisional responses.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336056", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(!zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must be set after 180+603");
+ fst_xcheck(!!strstr(capture.sip_603plus_reason, "v=analytics1"), "sip_603plus_reason must contain v=analytics1");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ /* Detection: negative cases */
+
+ FST_TEST_BEGIN(detect_wrong_phrase)
+ {
+ /*
+ * Extension +15553336052 sends:
+ * 603 Decline <- wrong phrase
+ * Reason: SIP;cause=603;text="v=analytics1;url=https://example.com/redress";location=TN
+ *
+ * Phrase is "Decline", not "Network Blocked". Detection must NOT fire.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336052", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must NOT be set when phrase is 'Decline'");
+ /* sip_reason should still be set (existing behavior for any Reason header) */
+ fst_xcheck(!zstr_buf(capture.sip_reason), "sip_reason should be set regardless of phrase");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(detect_no_analytics_in_reason)
+ {
+ /*
+ * Extension +15553336053 sends:
+ * 603 Network Blocked
+ * Reason: Q.850;cause=21;text="Call Rejected" <- no v=analytics1
+ *
+ * Reason header lacks v=analytics1. Detection must NOT fire.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336053", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must NOT be set without v=analytics1");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(detect_no_reason_header)
+ {
+ /*
+ * Extension +15553336054 sends:
+ * 603 Network Blocked
+ * (no Reason header -- disable_q850_reason=true suppresses it)
+ *
+ * No Reason header -> sip->sip_reason is NULL. Detection must NOT fire.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336054", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must NOT be set without Reason header");
+ fst_xcheck(zstr_buf(capture.sip_reason), "sip_reason should not be set when Reason header is suppressed");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(detect_non_603_status)
+ {
+ /*
+ * Extension +15553336055 sends:
+ * 486 Busy Here <- not 603
+ * Reason: SIP;cause=603;text="v=analytics1;url=https://example.com/redress";location=TN
+ *
+ * Status code is 486, not 603. Detection must NOT fire.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336055", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_USER_BUSY, "Expected USER_BUSY for 486");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(zstr_buf(capture.sip_603plus_reason), "sip_603plus_reason must NOT be set for non-603 status");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ /*
+ * Passthrough behavior.
+ *
+ * Bridge scenario: originate -> middle extension (sets passthrough) -> bridges to 603+ target.
+ * The originate leg receives the response FROM the middle box. We capture its
+ * sip_invite_failure_phrase and sip_reason to verify what was actually sent.
+ */
+
+ FST_TEST_BEGIN(passthrough_true)
+ {
+ /*
+ * Extension +15553336060 sets sip_603plus_passthrough=true, bridges to 603+ target.
+ * The middle box should forward both "Network Blocked" phrase and ATIS Reason header.
+ * Our originate leg should see a valid 603+.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336060", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(!strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked"),
+ "passthrough=true must preserve 'Network Blocked' phrase");
+ fst_xcheck(!zstr_buf(capture.sip_603plus_reason),
+ "passthrough=true must result in valid 603+ on originate leg");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(passthrough_false)
+ {
+ /*
+ * Extension +15553336061 sets sip_603plus_passthrough=false, bridges to 603+ target.
+ * The middle box should strip the ATIS Reason and use default phrase "Decline".
+ * Our originate leg should NOT see a 603+.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336061", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked") != 0,
+ "passthrough=false must NOT send 'Network Blocked' phrase");
+ fst_xcheck(zstr_buf(capture.sip_603plus_reason),
+ "passthrough=false must strip ATIS Reason (no 603+ on originate leg)");
+ fst_xcheck(zstr_buf(capture.sip_reason),
+ "passthrough=false must suppress Reason header entirely");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(passthrough_default)
+ {
+ /*
+ * Extension +15553336062 does NOT set sip_603plus_passthrough, bridges to 603+ target.
+ * Default: phrase is "Decline" (existing behavior), but ATIS Reason leaks through.
+ * This is the backward-compatible state.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336062", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked") != 0,
+ "default passthrough must NOT change phrase (stays 'Decline')");
+ /* ATIS Reason leaks through via sip_reason -- this is existing behavior */
+ fst_xcheck(!zstr_buf(capture.sip_reason),
+ "default passthrough: sip_reason should still be set (existing behavior)");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ /*
+ * disable_q850_reason + passthrough combinations.
+ *
+ * Tests that disable_q850_reason and sip_603plus_passthrough work independently.
+ * disable_q850_reason suppresses standard Reason headers;
+ * sip_603plus_passthrough controls 603+ ATIS Reason forwarding.
+ */
+
+ FST_TEST_BEGIN(disable_reason_passthrough_true)
+ {
+ /*
+ * Extension +15553336063: disable_q850_reason=true + sip_603plus_passthrough=true.
+ * Standard Reason suppressed, but ATIS 603+ Reason restored.
+ * The customer use case: suppress all Reason headers except FCC-required 603+.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336063", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(!strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked"),
+ "disable_q850+passthrough=true must preserve 'Network Blocked' phrase");
+ fst_xcheck(!zstr_buf(capture.sip_603plus_reason),
+ "disable_q850+passthrough=true must restore ATIS Reason");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(disable_reason_passthrough_false)
+ {
+ /*
+ * Extension +15553336064: disable_q850_reason=true + sip_603plus_passthrough=false.
+ * Both suppress -- no Reason header at all.
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336064", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked") != 0,
+ "disable_q850+passthrough=false must NOT send 'Network Blocked' phrase");
+ fst_xcheck(zstr_buf(capture.sip_reason),
+ "disable_q850+passthrough=false must suppress Reason header entirely");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(disable_reason_passthrough_default)
+ {
+ /*
+ * Extension +15553336065: disable_q850_reason=true, passthrough not set.
+ * disable_q850_reason suppresses everything, passthrough not set = no override.
+ * No Reason header, phrase is "Decline".
+ */
+ switch_call_cause_t cause;
+
+ switch_event_bind("test_603plus", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE,
+ SWITCH_EVENT_SUBCLASS_ANY, on_hangup_complete, NULL);
+
+ reset_capture();
+ originate_and_wait("sofia/gateway/test/+15553336065", &cause);
+
+ fst_xcheck(cause == SWITCH_CAUSE_CALL_REJECTED, "Expected CALL_REJECTED for 603");
+ fst_xcheck(capture.received == SWITCH_TRUE, "Should have received outbound hangup event");
+ fst_xcheck(strcasecmp(capture.sip_invite_failure_phrase, "Network Blocked") != 0,
+ "disable_q850+default must NOT send 'Network Blocked' phrase");
+ fst_xcheck(zstr_buf(capture.sip_reason),
+ "disable_q850+default must suppress Reason header");
+
+ switch_event_unbind_callback(on_hangup_complete);
+ }
+ FST_TEST_END()
+
+}
+FST_MODULE_END()
+}
+FST_CORE_END()
diff --git a/src/mod/endpoints/mod_verto/mod_verto.c b/src/mod/endpoints/mod_verto/mod_verto.c
index 40bb51171a..784a1379cb 100644
--- a/src/mod/endpoints/mod_verto/mod_verto.c
+++ b/src/mod/endpoints/mod_verto/mod_verto.c
@@ -41,7 +41,9 @@ SWITCH_MODULE_RUNTIME_FUNCTION(mod_verto_runtime);
SWITCH_MODULE_DEFINITION(mod_verto, mod_verto_load, mod_verto_shutdown, mod_verto_runtime);
#define HTTP_CHUNK_SIZE 1024 * 32
+#define HTTP_POST_MAX_BODY (10 * 1024 * 1024) /* max accepted Content-Length for form-urlencoded POST */
#define EP_NAME "verto.rtc"
+#define VERTO_SPEED_TEST_MAX_SIZE (10 * 1024 * 1024)
//#define WSS_STANDALONE 1
#include "libks/ks.h"
@@ -150,6 +152,114 @@ static void verto_deinit_ssl(verto_profile_t *profile)
}
}
+static SSL_CTX *verto_create_ssl_ctx(verto_profile_t *profile, const char **errp)
+{
+ SSL_CTX *ctx = SSL_CTX_new(profile->ssl_method);
+
+ if (!ctx) {
+ *errp = "Failed to create SSL context";
+
+ return NULL;
+ }
+
+ /* Disable SSLv2 */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
+ /* Disable SSLv3 */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
+ /* Disable TLSv1 */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
+ /* Disable Compression CRIME (Compression Ratio Info-leak Made Easy) */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
+
+ if (!zstr(profile->chain)) {
+ if (switch_file_exists(profile->chain, NULL) != SWITCH_STATUS_SUCCESS) {
+ *errp = "SUPPLIED CHAIN FILE NOT FOUND";
+ goto fail;
+ }
+
+ if (!SSL_CTX_use_certificate_chain_file(ctx, profile->chain)) {
+ *errp = "CERT CHAIN FILE ERROR";
+ goto fail;
+ }
+ }
+
+ if (switch_file_exists(profile->cert, NULL) != SWITCH_STATUS_SUCCESS) {
+ *errp = "SUPPLIED CERT FILE NOT FOUND";
+ goto fail;
+ }
+
+ if (!SSL_CTX_use_certificate_file(ctx, profile->cert, SSL_FILETYPE_PEM)) {
+ *errp = "CERT FILE ERROR";
+ goto fail;
+ }
+
+ if (switch_file_exists(profile->key, NULL) != SWITCH_STATUS_SUCCESS) {
+ *errp = "SUPPLIED KEY FILE NOT FOUND";
+ goto fail;
+ }
+
+ if (!SSL_CTX_use_PrivateKey_file(ctx, profile->key, SSL_FILETYPE_PEM)) {
+ *errp = "PRIVATE KEY FILE ERROR";
+ goto fail;
+ }
+
+ if (!SSL_CTX_check_private_key(ctx)) {
+ *errp = "PRIVATE KEY FILE ERROR";
+ goto fail;
+ }
+
+ SSL_CTX_set_cipher_list(ctx, "HIGH:!DSS:!aNULL@STRENGTH");
+
+ return ctx;
+
+ fail:
+ SSL_CTX_free(ctx);
+
+ return NULL;
+}
+
+static int verto_reload_ssl(verto_profile_t *profile)
+{
+ const char *err = NULL;
+ SSL_CTX *new_ctx = verto_create_ssl_ctx(profile, &err);
+
+ if (!new_ctx) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL reload failed for profile %s: %s\n", profile->name, err);
+
+ return 0;
+ }
+
+ SSL_CTX_free(profile->ssl_ctx);
+
+ profile->ssl_ctx = new_ctx;
+ profile->ssl_ready = 1;
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SSL certificates reloaded for profile %s\n", profile->name);
+
+ return 1;
+}
+
+static void cert_reload_handler(switch_event_t *event)
+{
+ verto_profile_t *p;
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Certificate reload event received, processing\n");
+
+ switch_mutex_lock(verto_globals.mutex);
+
+ for (p = verto_globals.profile_head; p; p = p->next) {
+ if (p->running) {
+ switch_thread_rwlock_wrlock(p->rwlock);
+ verto_reload_ssl(p);
+ switch_thread_rwlock_unlock(p->rwlock);
+ }
+ }
+
+ switch_mutex_unlock(verto_globals.mutex);
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Certificate reload event processed\n");
+}
+
static void close_file(ks_socket_t *sock)
{
if (*sock != KS_SOCK_INVALID) {
@@ -174,84 +284,30 @@ void verto_broadcast(const char *event_channel, cJSON *json, const char *key, sw
static int verto_init_ssl(verto_profile_t *profile)
{
- const char *err = "";
+ const char *err = NULL;
int i = 0;
- profile->ssl_method = SSLv23_server_method(); /* create server instance */
- profile->ssl_ctx = SSL_CTX_new(profile->ssl_method); /* create context */
+ profile->ssl_method = SSLv23_server_method();
+ profile->ssl_ctx = verto_create_ssl_ctx(profile, &err);
+
+ if (!profile->ssl_ctx) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL ERR: %s\n", err);
+
+ profile->ssl_ready = 0;
+
+ for (i = 0; i < profile->i; i++) {
+ if (profile->ip[i].secure) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "SSL NOT READY FOR LISTENER %s:%d. USE reloadcert AFTER FIXING CERTIFICATES\n",
+ profile->ip[i].local_ip, profile->ip[i].local_port);
+ }
+ }
+
+ return 0;
+ }
+
profile->ssl_ready = 1;
- assert(profile->ssl_ctx);
-
- /* Disable SSLv2 */
- SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_SSLv2);
- /* Disable SSLv3 */
- SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_SSLv3);
- /* Disable TLSv1 */
- SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_TLSv1);
- /* Disable Compression CRIME (Compression Ratio Info-leak Made Easy) */
- SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_COMPRESSION);
-
- /* set the local certificate from CertFile */
- if (!zstr(profile->chain)) {
- if (switch_file_exists(profile->chain, NULL) != SWITCH_STATUS_SUCCESS) {
- err = "SUPPLIED CHAIN FILE NOT FOUND\n";
- goto fail;
- }
-
- if (!SSL_CTX_use_certificate_chain_file(profile->ssl_ctx, profile->chain)) {
- err = "CERT CHAIN FILE ERROR";
- goto fail;
- }
- }
-
- if (switch_file_exists(profile->cert, NULL) != SWITCH_STATUS_SUCCESS) {
- err = "SUPPLIED CERT FILE NOT FOUND\n";
- goto fail;
- }
-
- if (!SSL_CTX_use_certificate_file(profile->ssl_ctx, profile->cert, SSL_FILETYPE_PEM)) {
- err = "CERT FILE ERROR";
- goto fail;
- }
-
- /* set the private key from KeyFile */
-
- if (switch_file_exists(profile->key, NULL) != SWITCH_STATUS_SUCCESS) {
- err = "SUPPLIED KEY FILE NOT FOUND\n";
- goto fail;
- }
-
- if (!SSL_CTX_use_PrivateKey_file(profile->ssl_ctx, profile->key, SSL_FILETYPE_PEM)) {
- err = "PRIVATE KEY FILE ERROR";
- goto fail;
- }
-
- /* verify private key */
- if ( !SSL_CTX_check_private_key(profile->ssl_ctx) ) {
- err = "PRIVATE KEY FILE ERROR";
- goto fail;
- }
-
- SSL_CTX_set_cipher_list(profile->ssl_ctx, "HIGH:!DSS:!aNULL@STRENGTH");
return 1;
-
- fail:
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL ERR: %s\n", err);
-
- profile->ssl_ready = 0;
- verto_deinit_ssl(profile);
-
- for (i = 0; i < profile->i; i++) {
- if (profile->ip[i].secure) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL NOT ENABLED FOR LISTENER %s:%d. REVERTING TO WS\n",
- profile->ip[i].local_ip, profile->ip[i].local_port);
- profile->ip[i].secure = 0;
- }
- }
-
- return 0;
-
}
@@ -1001,7 +1057,7 @@ static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *
if (jsock->profile->chop_domain && (domain = strchr(id, '@'))) {
*domain++ = '\0';
}
-
+
}
if (jsock->profile->register_domain) {
@@ -1031,27 +1087,10 @@ static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *
}
}
-
- if ((json_ptr = cJSON_GetObjectItem(params, "userVariables"))) {
- cJSON * i;
-
- switch_mutex_lock(jsock->flag_mutex);
- for(i = json_ptr->child; i; i = i->next) {
- if (i->type == cJSON_True) {
- switch_event_add_header_string(jsock->user_vars, SWITCH_STACK_BOTTOM, i->string, "true");
- } else if (i->type == cJSON_False) {
- switch_event_add_header_string(jsock->user_vars, SWITCH_STACK_BOTTOM, i->string, "false");
- } else if (!zstr(i->string) && !zstr(i->valuestring)) {
- switch_event_add_header_string(jsock->user_vars, SWITCH_STACK_BOTTOM, i->string, i->valuestring);
- }
- }
- switch_mutex_unlock(jsock->flag_mutex);
- }
-
if (jsock->profile->send_passwd || verto_globals.send_passwd) {
switch_event_add_header_string(req_params, SWITCH_STACK_BOTTOM, "user_supplied_pass", passwd);
}
-
+
switch_event_add_header_string(req_params, SWITCH_STACK_BOTTOM, "action", "jsonrpc-authenticate");
if (switch_xml_locate_user_merged("id", id, domain, NULL, &x_user, req_params) != SWITCH_STATUS_SUCCESS && !jsock->profile->blind_reg) {
@@ -1064,20 +1103,8 @@ static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *
const char *use_passwd = NULL, *verto_context = NULL, *verto_dialplan = NULL;
time_t now = switch_epoch_time_now(NULL);
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Login sucessful for user: %s domain: %s\n", id, domain);
-
- jsock->logintime = now;
- jsock->id = switch_core_strdup(jsock->pool, id);
- jsock->domain = switch_core_strdup(jsock->pool, domain);
- jsock->uid = switch_core_sprintf(jsock->pool, "%s@%s", id, domain);
- jsock->ready = 1;
-
- if (!x_user) {
- switch_event_destroy(&req_params);
- r = SWITCH_TRUE;
- goto end;
- }
-
+ /* Pre-scan : extract credentials and verto-context/dialplan
+ * into locals only. No jsock writes here. */
if ((x_params = switch_xml_child(x_user, "params"))) {
for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) {
const char *var = switch_xml_attr_soft(x_param, "name");
@@ -1099,8 +1126,63 @@ static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *
} else if (!strcasecmp(var, "verto-dialplan")) {
verto_dialplan = val;
}
+ }
+ }
- switch_event_add_header_string(jsock->params, SWITCH_STACK_BOTTOM, var, val);
+ /* Password gate. blind_reg with no x_user passes by config. */
+ if (x_user && (zstr(use_passwd) || strcmp(a1_hash ? a1_hash : passwd, use_passwd))) {
+ *code = CODE_AUTH_FAILED;
+ switch_snprintf(message, mlen, "Authentication Failure");
+ login_fire_custom_event(jsock, params, 0, "Authentication Failure");
+ switch_xml_clear_user_cache("id", id, domain);
+ switch_xml_free(x_user);
+ switch_event_destroy(&req_params);
+ goto end;
+ }
+
+ /* Commit jsock state — reachable only post-gate. */
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Login successful for user: %s domain: %s\n", id, domain);
+
+ jsock->logintime = now;
+ jsock->id = switch_core_strdup(jsock->pool, id);
+ jsock->domain = switch_core_strdup(jsock->pool, domain);
+ jsock->uid = switch_core_sprintf(jsock->pool, "%s@%s", id, domain);
+
+ if ((json_ptr = cJSON_GetObjectItem(params, "userVariables"))) {
+ cJSON *i;
+
+ switch_mutex_lock(jsock->flag_mutex);
+ for (i = json_ptr->child; i; i = i->next) {
+ if (i->type == cJSON_True) {
+ switch_event_add_header_string(jsock->user_vars, SWITCH_STACK_BOTTOM, i->string, "true");
+ } else if (i->type == cJSON_False) {
+ switch_event_add_header_string(jsock->user_vars, SWITCH_STACK_BOTTOM, i->string, "false");
+ } else if (!zstr(i->string) && !zstr(i->valuestring)) {
+ switch_event_add_header_string(jsock->user_vars, SWITCH_STACK_BOTTOM, i->string, i->valuestring);
+ }
+ }
+ switch_mutex_unlock(jsock->flag_mutex);
+ }
+
+ /* blind_reg path: no XML user located — jsock state already committed above;
+ * skip directory persistence (params/variables/dialplan/context) and return. */
+ if (!x_user) {
+ switch_event_destroy(&req_params);
+ /* ready=1 is the last state write so cross-thread readers that
+ * gate on `ready && !zstr(uid)` see a fully populated jsock. */
+ jsock->ready = 1;
+ r = SWITCH_TRUE;
+ goto end;
+ }
+
+ /* Second pass over : persist every entry into jsock->params.
+ * Pre-scan above only read credentials/verto-context/dialplan into locals.
+ * Must run post-gate — these headers feed channel variables on later calls. */
+ if ((x_params = switch_xml_child(x_user, "params"))) {
+ for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) {
+ switch_event_add_header_string(jsock->params, SWITCH_STACK_BOTTOM,
+ switch_xml_attr_soft(x_param, "name"),
+ switch_xml_attr_soft(x_param, "value"));
}
}
@@ -1115,7 +1197,7 @@ static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *
switch_mutex_unlock(jsock->flag_mutex);
switch_clear_flag(jsock, JPFLAG_AUTH_EXPIRED);
-
+
if (!strcmp(var, "login-expires")) {
uint32_t tmp = atol(val);
@@ -1138,21 +1220,12 @@ static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *
jsock->context = switch_core_strdup(jsock->pool, verto_context);
}
-
- if (!use_passwd || zstr(use_passwd) || strcmp(a1_hash ? a1_hash : passwd, use_passwd)) {
- r = SWITCH_FALSE;
- *code = CODE_AUTH_FAILED;
- switch_snprintf(message, mlen, "Authentication Failure");
- jsock->uid = NULL;
- login_fire_custom_event(jsock, params, 0, "Authentication Failure");
- switch_xml_clear_user_cache("id", id, domain);
- } else {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"auth using %s\n",a1_hash ? "a1-hash" : "username & password");
- r = SWITCH_TRUE;
- check_permissions(jsock, x_user, params);
- }
-
-
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"auth using %s\n",a1_hash ? "a1-hash" : "username & password");
+ check_permissions(jsock, x_user, params);
+ /* ready=1 is the last state write so cross-thread readers that
+ * gate on `ready && !zstr(uid)` see a fully populated jsock. */
+ jsock->ready = 1;
+ r = SWITCH_TRUE;
switch_xml_free(x_user);
}
@@ -1216,10 +1289,11 @@ static jsock_t *get_jsock(const char *uuid)
static void tech_reattach(verto_pvt_t *tech_pvt, jsock_t *jsock);
-static void attach_jsock(jsock_t *jsock)
+static switch_bool_t attach_jsock(jsock_t *jsock)
{
jsock_t *jp;
int proceed = 1;
+ switch_bool_t result = SWITCH_TRUE;
switch_mutex_lock(verto_globals.jsock_mutex);
@@ -1228,6 +1302,17 @@ static void attach_jsock(jsock_t *jsock)
if ((jp = switch_core_hash_find(verto_globals.jsock_hash, jsock->uuid_str))) {
if (jp == jsock) {
proceed = 0;
+ } else if (!zstr(jp->uid) && !zstr(jsock->uid) && strcmp(jp->uid, jsock->uid)) {
+ /* Refuse cross-identity takeover when both jsocks are authenticated under different uids.
+ * Clear uuid_str and set nodelete to prevent any uuid_str-keyed teardown
+ * (detach_jsock, del_jsock, detach_calls) from touching jp. */
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,
+ "User %s blocked from taking over session %s owned by %s\n",
+ jsock->uid, jsock->uuid_str, jp->uid);
+ jsock->nodelete = 1;
+ jsock->uuid_str[0] = '\0';
+ proceed = 0;
+ result = SWITCH_FALSE;
} else {
cJSON *params = NULL;
cJSON *msg = NULL;
@@ -1248,6 +1333,7 @@ static void attach_jsock(jsock_t *jsock)
}
switch_mutex_unlock(verto_globals.jsock_mutex);
+ return result;
}
static void detach_jsock(jsock_t *jsock)
@@ -1426,10 +1512,8 @@ static void process_jrpc_response(jsock_t *jsock, cJSON *json)
{
}
-static void set_session_id(jsock_t *jsock, const char *uuid)
+static switch_bool_t set_session_id(jsock_t *jsock, const char *uuid)
{
- //cJSON *params, *msg = jrpc_new(0);
-
if (!zstr(uuid)) {
switch_set_string(jsock->uuid_str, uuid);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s re-connecting session %s\n", jsock->name, jsock->uuid_str);
@@ -1438,8 +1522,7 @@ static void set_session_id(jsock_t *jsock, const char *uuid)
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s new RPC session %s\n", jsock->name, jsock->uuid_str);
}
- attach_jsock(jsock);
-
+ return attach_jsock(jsock);
}
static cJSON *process_jrpc(jsock_t *jsock, cJSON *json)
@@ -1459,11 +1542,6 @@ static cJSON *process_jrpc(jsock_t *jsock, cJSON *json)
sessid = cJSON_GetObjectCstr(params, "sessid");
}
- if (!switch_test_flag(jsock, JPFLAG_INIT)) {
- set_session_id(jsock, sessid);
- switch_set_flag(jsock, JPFLAG_INIT);
- }
-
if (zstr(version) || strcmp(version, "2.0")) {
reply = jrpc_new(0);
jrpc_add_error(reply, CODE_INVALID, "Invalid message", id);
@@ -1490,6 +1568,17 @@ static cJSON *process_jrpc(jsock_t *jsock, cJSON *json)
switch_set_flag(jsock, JPFLAG_AUTHED);
}
+ /* Bind only after the auth gate — attach_jsock()'s eviction
+ * must not be reachable pre-auth. */
+ if (!switch_test_flag(jsock, JPFLAG_INIT)) {
+ if (!set_session_id(jsock, sessid)) {
+ jrpc_add_error(reply, CODE_AUTH_FAILED, "Session in use", id);
+ jsock->drop = 1;
+ goto end;
+ }
+ switch_set_flag(jsock, JPFLAG_INIT);
+ }
+
if (!method || !(func = jrpc_get_func(jsock, method))) {
jrpc_add_error(reply, -32601, "Invalid Method, Missing Method or Permission Denied", id);
} else {
@@ -1770,7 +1859,7 @@ new_req:
char *buffer = NULL;
switch_ssize_t len = 0, bytes = 0;
- if (request->content_length && request->content_length > 10 * 1024 * 1024 - 1) {
+ if (request->content_length && request->content_length >= HTTP_POST_MAX_BODY) {
char *data = "HTTP/1.1 413 Request Entity Too Large\r\n"
"Content-Length: 0\r\n\r\n";
kws_raw_write(jsock->ws, data, strlen(data));
@@ -1778,16 +1867,16 @@ new_req:
goto done;
}
- if (!(buffer = malloc(2 * 1024 * 1024))) {
+ if (!(buffer = malloc(request->content_length + 1))) {
goto request_err;
}
while(bytes < (switch_ssize_t)request->content_length) {
len = request->content_length - bytes;
-#define WS_BLOCK 1
+#define WS_BLOCK 10000 /* ms; matches libks's internal default */
- if ((len = kws_raw_read(jsock->ws, buffer + bytes, len, WS_BLOCK)) < 0) {
+ if ((len = kws_raw_read(jsock->ws, buffer + bytes, len, WS_BLOCK)) <= 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error %" SWITCH_SSIZE_T_FMT"\n", len);
goto done;
}
@@ -1972,6 +2061,7 @@ static void client_run(jsock_t *jsock)
ks_pool_open(&jsock->kpool);
+ switch_thread_rwlock_rdlock(jsock->profile->rwlock);
#if defined(KS_VERSION_NUM) && KS_VERSION_NUM >= 20000
params = ks_json_create_object();
ks_json_add_number_to_object(params, "payload_size_max", 1000000);
@@ -1979,8 +2069,10 @@ static void client_run(jsock_t *jsock)
#else
if (kws_init(&jsock->ws, jsock->client_socket, (jsock->ptype & PTYPE_CLIENT_SSL) ? jsock->profile->ssl_ctx : NULL, 0, flags, jsock->kpool) != KS_STATUS_SUCCESS) {
#endif
+ switch_thread_rwlock_unlock(jsock->profile->rwlock);
log_and_exit(SWITCH_LOG_NOTICE, "%s WS SETUP FAILED\n", jsock->name);
}
+ switch_thread_rwlock_unlock(jsock->profile->rwlock);
if (kws_test_flag(jsock->ws, KWS_HTTP)) {
http_run(jsock);
@@ -2054,16 +2146,26 @@ static void client_run(jsock_t *jsock)
char repl[2048] = "";
switch_time_t a, b;
+ if (!switch_test_flag(jsock, JPFLAG_AUTHED)) {
+ die("%s Speed-test request before authentication\n", jsock->name);
+ }
+
+ if (bytes < 4) {
+ continue;
+ }
+
if (s[1] == 'S' && s[2] == 'P') {
if (s[3] == 'U') {
- int i, size = 0;
+ int i;
+ long size;
char *p = s+4;
int loops = 0;
int rem = 0;
int dur = 0, j = 0;
- if ((size = atoi(p)) <= 0) {
+ size = strtol(p, NULL, 10);
+ if (size <= 0 || size > VERTO_SPEED_TEST_MAX_SIZE) {
continue;
}
@@ -2071,7 +2173,7 @@ static void client_run(jsock_t *jsock)
do {
bytes = kws_read_frame(jsock->ws, &oc, &data);
s = (char *) data;
- } while (bytes && data && s[0] == '#' && s[3] == 'B');
+ } while (bytes >= 4 && data && s[0] == '#' && s[3] == 'B');
b = switch_time_now();
if (!bytes || !data) continue;
@@ -4690,7 +4792,7 @@ static int start_jsock(verto_profile_t *profile, ks_socket_t sock, int family)
for (i = 0; i < profile->i; i++) {
if ( profile->server_socket[i] == sock ) {
- if (profile->ip[i].secure) {
+ if (profile->ip[i].secure && profile->ssl_ready) {
ptype = PTYPE_CLIENT_SSL;
}
break;
@@ -6887,6 +6989,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_verto_load)
switch_core_register_secondary_recover_callback(modname, verto_recover_callback);
+ switch_event_bind(modname, SWITCH_EVENT_CERT_RELOAD, SWITCH_EVENT_SUBCLASS_ANY, cert_reload_handler, NULL);
+
if (verto_globals.enable_presence) {
switch_event_bind(modname, SWITCH_EVENT_CHANNEL_CALLSTATE, SWITCH_EVENT_SUBCLASS_ANY, presence_event_handler, NULL);
}
@@ -6922,6 +7026,7 @@ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_verto_shutdown)
switch_core_hash_destroy(&json_GLOBALS.store_hash);
switch_event_channel_unbind(NULL, verto_broadcast, NULL);
+ switch_event_unbind_callback(cert_reload_handler);
switch_event_unbind_callback(presence_event_handler);
switch_event_unbind_callback(event_handler);
diff --git a/src/mod/event_handlers/mod_erlang_event/ei_helpers.c b/src/mod/event_handlers/mod_erlang_event/ei_helpers.c
index 6472180a2e..74dc860cb1 100644
--- a/src/mod/event_handlers/mod_erlang_event/ei_helpers.c
+++ b/src/mod/event_handlers/mod_erlang_event/ei_helpers.c
@@ -64,6 +64,7 @@ void ei_link(listener_t *listener, erlang_pid * from, erlang_pid * to)
char msgbuf[2048];
char *s;
int index = 0;
+ switch_size_t send_len;
int status = SWITCH_STATUS_SUCCESS;
switch_socket_t *sock = NULL;
switch_os_sock_put(&sock, &listener->sockdes, listener->pool);
@@ -82,7 +83,8 @@ void ei_link(listener_t *listener, erlang_pid * from, erlang_pid * to)
/* sum: 542 */
switch_mutex_lock(listener->sock_mutex);
- status = switch_socket_send(sock, msgbuf, (switch_size_t *) &index);
+ send_len = (switch_size_t)index;
+ status = switch_socket_send(sock, msgbuf, &send_len);
if (status != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to link to process on %s\n", listener->peer_nodename);
}
@@ -283,8 +285,7 @@ int ei_sendto(ei_cnode * ec, int fd, struct erlang_process *process, ei_x_buff *
/* convert an erlang reference to some kind of hashed string so we can store it as a hash key */
void ei_hash_ref(erlang_ref * ref, char *output)
{
- /* very lazy */
- sprintf(output, "%d.%d.%d@%s", ref->n[0], ref->n[1], ref->n[2], ref->node);
+ snprintf(output, EI_HASH_REF_LEN, "%d.%d.%d@%s", ref->n[0], ref->n[1], ref->n[2], ref->node);
}
diff --git a/src/mod/event_handlers/mod_erlang_event/freeswitch.erl b/src/mod/event_handlers/mod_erlang_event/freeswitch.erl
index 9a08d88ae2..a82f53b651 100644
--- a/src/mod/event_handlers/mod_erlang_event/freeswitch.erl
+++ b/src/mod/event_handlers/mod_erlang_event/freeswitch.erl
@@ -91,7 +91,7 @@ api(Node, Cmd) ->
%% sent to calling process after it is received. This function
%% returns the result of the initial bgapi call or `timeout' if FreeSWITCH fails
%% to respond.
--spec(bgapi/3 :: (Node :: atom(), Cmd :: atom(), Args :: string()) -> {'ok', string()} | {'error', any()} | 'timeout').
+-spec bgapi(Node :: atom(), Cmd :: atom(), Args :: string()) -> {'ok', string()} | {'error', any()} | 'timeout'.
bgapi(Node, Cmd, Args) ->
Self = self(),
% spawn a new process so that both responses go here instead of directly to
@@ -128,7 +128,7 @@ bgapi(Node, Cmd, Args) ->
%% passed as the argument to `Fun' after it is received. This function
%% returns the result of the initial bgapi call or `timeout' if FreeSWITCH fails
%% to respond.
--spec(bgapi/4 :: (Node :: atom(), Cmd :: atom(), Args :: string(), Fun :: fun()) -> 'ok' | {'error', any()} | 'timeout').
+-spec bgapi(Node :: atom(), Cmd :: atom(), Args :: string(), Fun :: fun()) -> 'ok' | {'error', any()} | 'timeout'.
bgapi(Node, Cmd, Args, Fun) ->
Self = self(),
% spawn a new process so that both responses go here instead of directly to
diff --git a/src/mod/event_handlers/mod_erlang_event/handle_msg.c b/src/mod/event_handlers/mod_erlang_event/handle_msg.c
index aad45a4986..a54a5123e9 100644
--- a/src/mod/event_handlers/mod_erlang_event/handle_msg.c
+++ b/src/mod/event_handlers/mod_erlang_event/handle_msg.c
@@ -804,13 +804,13 @@ static switch_status_t handle_msg_sendevent(listener_t *listener, int arity, ei_
} else {
switch_event_types_t etype;
if (switch_name_event(ename, &etype) == SWITCH_STATUS_SUCCESS) {
- switch_event_t *event;
+ switch_event_t *event = NULL;
if ((strlen(esname) && switch_event_create_subclass(&event, etype, esname) == SWITCH_STATUS_SUCCESS) ||
switch_event_create(&event, etype) == SWITCH_STATUS_SUCCESS) {
char key[1024];
- char *value;
- int type;
- int size;
+ char *value = NULL;
+ int type;
+ int size;
int i = 0;
switch_bool_t fail = SWITCH_FALSE;
@@ -828,14 +828,15 @@ static switch_status_t handle_msg_sendevent(listener_t *listener, int arity, ei_
value = malloc(size + 1);
if (ei_decode_string(buf->buff, &buf->index, value)) {
- fail = SWITCH_TRUE;
+ switch_safe_free(value);
+ fail = SWITCH_TRUE;
break;
}
- if (!fail && !strcmp(key, "body")) {
+ if (!strcmp(key, "body")) {
switch_safe_free(event->body);
event->body = value;
- } else if (!fail) {
+ } else {
switch_event_add_header_string_nodup(event, SWITCH_STACK_BOTTOM, key, value);
}
@@ -896,13 +897,12 @@ static switch_status_t handle_msg_sendmsg(listener_t *listener, int arity, ei_x_
value = malloc(size + 1);
if (ei_decode_string(buf->buff, &buf->index, value)) {
+ switch_safe_free(value);
fail = SWITCH_TRUE;
break;
}
- if (!fail) {
- switch_event_add_header_string_nodup(event, SWITCH_STACK_BOTTOM, key, value);
- }
+ switch_event_add_header_string_nodup(event, SWITCH_STACK_BOTTOM, key, value);
}
if (headerlength != i || fail) {
@@ -1204,7 +1204,7 @@ static switch_status_t handle_ref_tuple(listener_t *listener, erlang_msg * msg,
{
erlang_ref ref;
erlang_pid pid;
- char hash[100];
+ char hash[EI_HASH_REF_LEN];
int arity;
const void *key;
void *val;
@@ -1232,7 +1232,7 @@ static switch_status_t handle_ref_tuple(listener_t *listener, erlang_msg * msg,
for (iter = switch_core_hash_first(listener->sessions); iter; iter = switch_core_hash_next(&iter)) {
switch_core_hash_this(iter, &key, NULL, &val);
se = (session_elem_t*)val;
- if (switch_test_flag(se, LFLAG_WAITING_FOR_PID) && se->spawn_reply && !strncmp(se->spawn_reply->hash, hash, 100)) {
+ if (switch_test_flag(se, LFLAG_WAITING_FOR_PID) && se->spawn_reply && !strncmp(se->spawn_reply->hash, hash, EI_HASH_REF_LEN)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "found matching session for %s : %s\n", hash, se->uuid_str);
@@ -1376,6 +1376,7 @@ int handle_msg(listener_t *listener, erlang_msg * msg, ei_x_buff * buf, ei_x_buf
break;
case ERL_REFERENCE_EXT:
case ERL_NEW_REFERENCE_EXT:
+ case ERL_NEWER_REFERENCE_EXT:
ret = handle_ref_tuple(listener, msg, buf, rbuf);
break;
default:
diff --git a/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.c b/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.c
index f163b2197f..3315d60fb8 100644
--- a/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.c
+++ b/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.c
@@ -968,9 +968,10 @@ static void handle_exit(listener_t *listener, erlang_pid * pid)
static void listener_main_loop(listener_t *listener)
{
int status = 1;
+ int recv_erl_errno = ETIMEDOUT;
int msgs_sent = 0; /* how many messages we sent in a loop */
- while ((status >= 0 || erl_errno == ETIMEDOUT || erl_errno == EAGAIN) && !prefs.done) {
+ while ((status >= 0 || recv_erl_errno == ETIMEDOUT || recv_erl_errno == EAGAIN) && !prefs.done) {
erlang_msg msg;
ei_x_buff buf;
ei_x_buff rbuf;
@@ -983,6 +984,9 @@ static void listener_main_loop(listener_t *listener)
/* do we need the mutex when reading? */
/*switch_mutex_lock(listener->sock_mutex); */
status = ei_xreceive_msg_tmo(listener->sockdes, &msg, &buf, 1);
+ /* snapshot erl_errno before any outbound ei call (queue flushers below)
+ clobbers this thread-local slot. */
+ recv_erl_errno = erl_errno;
/*switch_mutex_unlock(listener->sock_mutex); */
switch (status) {
@@ -1001,6 +1005,8 @@ static void listener_main_loop(listener_t *listener)
if (handle_msg(listener, &msg, &buf, &rbuf)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "handle_msg requested exit\n");
+ ei_x_free(&buf);
+ ei_x_free(&rbuf);
return;
}
break;
@@ -1016,6 +1022,8 @@ static void listener_main_loop(listener_t *listener)
if (handle_msg(listener, &msg, &buf, &rbuf)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "handle_msg requested exit\n");
+ ei_x_free(&buf);
+ ei_x_free(&rbuf);
return;
}
break;
@@ -1026,6 +1034,7 @@ static void listener_main_loop(listener_t *listener)
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "erl_unlink\n");
break;
case ERL_EXIT:
+ case ERL_EXIT2:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "erl_exit from %s <%d.%d.%d>\n", msg.from.node, msg.from.creation, msg.from.num,
msg.from.serial);
@@ -1037,8 +1046,8 @@ static void listener_main_loop(listener_t *listener)
}
break;
case ERL_ERROR:
- if (erl_errno != ETIMEDOUT && erl_errno != EAGAIN) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "erl_error: status=%d, erl_errno=%d errno=%d\n", status, erl_errno, errno);
+ if (recv_erl_errno != ETIMEDOUT && recv_erl_errno != EAGAIN) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "erl_error: status=%d, erl_errno=%d errno=%d\n", status, recv_erl_errno, errno);
}
break;
default:
@@ -1069,7 +1078,7 @@ static void listener_main_loop(listener_t *listener)
if (prefs.done) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "shutting down listener\n");
} else {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "listener exit: status=%d, erl_errno=%d errno=%d\n", status, erl_errno, errno);
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "listener exit: status=%d, erl_errno=%d errno=%d\n", status, recv_erl_errno, errno);
}
}
@@ -1513,7 +1522,7 @@ session_elem_t *attach_call_to_spawned_process(listener_t *listener, char *modul
{
/* create a session list element */
session_elem_t *session_element = session_elem_create(listener, session);
- char hash[100];
+ char hash[EI_HASH_REF_LEN];
spawn_reply_t *p;
erlang_ref ref;
@@ -1720,8 +1729,6 @@ SWITCH_STANDARD_APP(erlang_sendmsg_function)
ei_x_buff buf;
listener_t *listener;
- ei_x_new_with_version(&buf);
-
/* process app arguments */
if (data && (mydata = switch_core_session_strdup(session, data))) {
argc = switch_separate_string(mydata, ' ', argv, 3);
@@ -1737,6 +1744,7 @@ SWITCH_STANDARD_APP(erlang_sendmsg_function)
/*switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "sendmsg: {%s, %s} ! %s\n", reg_name, node, argv[2]); */
+ ei_x_new_with_version(&buf);
ei_x_encode_tuple_header(&buf, 2);
ei_x_encode_atom(&buf, "freeswitch_sendmsg");
_ei_x_encode_string(&buf, argv[2]);
@@ -1754,6 +1762,8 @@ SWITCH_STANDARD_APP(erlang_sendmsg_function)
switch_thread_rwlock_unlock(listener->rwlock);
}
+
+ ei_x_free(&buf);
}
diff --git a/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.h b/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.h
index 2f2c0ed059..e662273208 100644
--- a/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.h
+++ b/src/mod/event_handlers/mod_erlang_event/mod_erlang_event.h
@@ -238,6 +238,7 @@ extern prefs_t prefs;
int handle_msg(listener_t *listener, erlang_msg * msg, ei_x_buff * buf, ei_x_buff * rbuf);
/* ei_helpers.c */
+#define EI_HASH_REF_LEN (MAXATOMLEN_UTF8 + 64)
void ei_link(listener_t *listener, erlang_pid * from, erlang_pid * to);
void ei_encode_switch_event_headers(ei_x_buff * ebuf, switch_event_t *event);
void ei_encode_switch_event_tag(ei_x_buff * ebuf, switch_event_t *event, char *tag);
diff --git a/src/mod/event_handlers/mod_event_socket/mod_event_socket.c b/src/mod/event_handlers/mod_event_socket/mod_event_socket.c
index 1c35b6f049..31bac3305b 100644
--- a/src/mod/event_handlers/mod_event_socket/mod_event_socket.c
+++ b/src/mod/event_handlers/mod_event_socket/mod_event_socket.c
@@ -1117,7 +1117,7 @@ SWITCH_STANDARD_API(event_sink_function)
}
if (listener->format == EVENT_FORMAT_JSON) {
- char *p = "{}";
+ char *p;
cJSON_AddItemToObject(cj, "events", cjevents);
p = cJSON_Print(cj);
if (cj && p) stream->write_function(stream, p);
diff --git a/src/mod/formats/mod_shout/mod_shout.c b/src/mod/formats/mod_shout/mod_shout.c
index d6cad23bbd..5cc54a4f1a 100644
--- a/src/mod/formats/mod_shout/mod_shout.c
+++ b/src/mod/formats/mod_shout/mod_shout.c
@@ -1289,7 +1289,7 @@ void do_telecast(switch_stream_handle_t *stream)
char *path_info = switch_event_get_header(stream->param_event, "http-path-info");
char *uuid = strdup(path_info + 4);
switch_core_session_t *tsession;
- char *fname = "stream.mp3";
+ char *fname;
switch_assert(uuid);
if ((fname = strchr(uuid, '/'))) {
diff --git a/src/mod/languages/mod_managed/managed/swig.cs b/src/mod/languages/mod_managed/managed/swig.cs
index 9bced266e1..acd3aa0f12 100644
--- a/src/mod/languages/mod_managed/managed/swig.cs
+++ b/src/mod/languages/mod_managed/managed/swig.cs
@@ -39455,6 +39455,7 @@ public enum switch_event_types_t {
SWITCH_EVENT_DEVICE_STATE,
SWITCH_EVENT_TEXT,
SWITCH_EVENT_SHUTDOWN_REQUESTED,
+ SWITCH_EVENT_CERT_RELOAD,
SWITCH_EVENT_ALL
}
diff --git a/src/mod/languages/mod_python3/freeswitch_python.cpp b/src/mod/languages/mod_python3/freeswitch_python.cpp
index e35c7ed464..600d8f87df 100644
--- a/src/mod/languages/mod_python3/freeswitch_python.cpp
+++ b/src/mod/languages/mod_python3/freeswitch_python.cpp
@@ -293,7 +293,7 @@ switch_status_t Session::run_dtmf_callback(void *input, switch_input_type_t ityp
PyObject *pyresult, *arglist, *io = NULL;
int ts = 0;
- char *str = NULL, *what = (char*)"";
+ char *str = NULL, *what;
if (TS) {
ts++;
diff --git a/src/mod/languages/mod_v8/mod_v8.cpp b/src/mod/languages/mod_v8/mod_v8.cpp
index b297fa39e1..96ac67f2f6 100644
--- a/src/mod/languages/mod_v8/mod_v8.cpp
+++ b/src/mod/languages/mod_v8/mod_v8.cpp
@@ -424,7 +424,7 @@ static void v8_error(Isolate* isolate, TryCatch* try_catch)
String::Utf8Value exception(try_catch->Exception());
const char *exception_string = js_safe_str(*exception);
Handle message = try_catch->Message();
- const char *msg = "";
+ const char *msg;
string filename = __FILE__;
int line = __LINE__;
string text = "";
diff --git a/src/switch_core_media.c b/src/switch_core_media.c
index de5d0eff74..51a4d32604 100644
--- a/src/switch_core_media.c
+++ b/src/switch_core_media.c
@@ -4549,7 +4549,7 @@ static void restore_pmaps(switch_rtp_engine_t *engine)
static const char *media_flow_varname(switch_media_type_t type)
{
- const char *varname = "invalid";
+ const char *varname;
switch(type) {
case SWITCH_MEDIA_TYPE_AUDIO:
@@ -4561,6 +4561,9 @@ static const char *media_flow_varname(switch_media_type_t type)
case SWITCH_MEDIA_TYPE_TEXT:
varname = "text_media_flow";
break;
+ default:
+ varname = "invalid";
+ break;
}
return varname;
@@ -4568,7 +4571,7 @@ static const char *media_flow_varname(switch_media_type_t type)
static const char *remote_media_flow_varname(switch_media_type_t type)
{
- const char *varname = "invalid";
+ const char *varname;
switch(type) {
case SWITCH_MEDIA_TYPE_AUDIO:
@@ -4580,6 +4583,9 @@ static const char *remote_media_flow_varname(switch_media_type_t type)
case SWITCH_MEDIA_TYPE_TEXT:
varname = "remote_text_media_flow";
break;
+ default:
+ varname = "invalid";
+ break;
}
return varname;
@@ -4587,7 +4593,7 @@ static const char *remote_media_flow_varname(switch_media_type_t type)
static void media_flow_get_mode(switch_media_flow_t smode, const char **mode_str, switch_media_flow_t *opp_mode)
{
- const char *smode_str = "";
+ const char *smode_str;
switch_media_flow_t opp_smode = smode;
switch(smode) {
@@ -4608,6 +4614,9 @@ static void media_flow_get_mode(switch_media_flow_t smode, const char **mode_str
case SWITCH_MEDIA_FLOW_SENDRECV:
smode_str = "sendrecv";
break;
+ default:
+ smode_str = "";
+ break;
}
*mode_str = smode_str;
@@ -11775,7 +11784,7 @@ SWITCH_DECLARE(void) switch_core_media_set_udptl_image_sdp(switch_core_session_t
char max_data[128] = "";
const char *ip;
uint32_t port;
- const char *family = "IP4";
+ const char *family;
const char *username;
const char *bit_removal_on = "a=T38FaxFillBitRemoval\r\n";
const char *bit_removal_off = "";
@@ -12033,7 +12042,7 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session)
switch_size_t len;
if (oe) {
- const char *family = "IP4";
+ const char *family;
char o_line[1024] = "";
if (oe >= pe) {
diff --git a/src/switch_core_session.c b/src/switch_core_session.c
index 94944faa2f..c0d3853cf1 100644
--- a/src/switch_core_session.c
+++ b/src/switch_core_session.c
@@ -1932,7 +1932,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_thread_pool_launch(switch_co
} else {
switch_set_flag(session, SSF_THREAD_RUNNING);
switch_set_flag(session, SSF_THREAD_STARTED);
- td = switch_core_session_alloc(session, sizeof(*td));
+ switch_zmalloc(td, sizeof(*td));
+ td->alloc = 1;
td->obj = session;
td->func = switch_core_session_thread;
status = switch_queue_push(session_manager.thread_queue, td);
diff --git a/src/switch_event.c b/src/switch_event.c
index 8a8c8d6c35..25bf961512 100644
--- a/src/switch_event.c
+++ b/src/switch_event.c
@@ -227,6 +227,7 @@ static char *EVENT_NAMES[] = {
"DEVICE_STATE",
"TEXT",
"SHUTDOWN_REQUESTED",
+ "CERT_RELOAD",
"ALL"
};
diff --git a/src/switch_msrp.c b/src/switch_msrp.c
index 9fd84d846b..89b2a886c6 100644
--- a/src/switch_msrp.c
+++ b/src/switch_msrp.c
@@ -114,7 +114,7 @@ static void msrp_deinit_ssl(void)
static void msrp_init_ssl(void)
{
- const char *err = "";
+ const char *err;
globals.ssl_client_method = SSLv23_client_method();
globals.ssl_client_ctx = SSL_CTX_new(globals.ssl_client_method);
diff --git a/src/switch_rtp.c b/src/switch_rtp.c
index d038566d90..d92b23001e 100644
--- a/src/switch_rtp.c
+++ b/src/switch_rtp.c
@@ -3822,7 +3822,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_dtls(switch_rtp_t *rtp_session, d
switch_dtls_t *dtls;
const char *var;
int ret;
- const char *kind = "";
+ const char *kind;
unsigned long ssl_method_error = 0;
unsigned long ssl_ctx_error = 0;
const SSL_METHOD *ssl_method;
diff --git a/src/switch_stun.c b/src/switch_stun.c
index 35c9daed91..1c0027e1a1 100644
--- a/src/switch_stun.c
+++ b/src/switch_stun.c
@@ -485,31 +485,24 @@ SWITCH_DECLARE(switch_stun_packet_t *) switch_stun_packet_build_header(switch_st
SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_add_binded_address(switch_stun_packet_t *packet, char *ipstr, uint16_t port, int family)
{
switch_stun_packet_attribute_t *attribute;
- switch_stun_ip_t *ip;
attribute = (switch_stun_packet_attribute_t *) ((uint8_t *) & packet->first_attribute + ntohs(packet->header.length));
attribute->type = htons(SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS);
if (family == AF_INET6) {
+ switch_stun_ipv6_t *ipv6 = (switch_stun_ipv6_t *) attribute->value;
+
attribute->length = htons(20);
+ ipv6->family = 2;
+ ipv6->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16));
+ inet_pton(AF_INET6, ipstr, ipv6->address);
} else {
+ switch_stun_ip_t *ip = (switch_stun_ip_t *) attribute->value;
+
attribute->length = htons(8);
- }
-
- ip = (switch_stun_ip_t *) attribute->value;
-
- ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16));
-
- if (family == AF_INET6) {
- ip->family = 2;
- } else {
ip->family = 1;
- }
-
- if (family == AF_INET6) {
- inet_pton(AF_INET6, ipstr, (struct in6_addr *) &ip->address);
- } else {
- inet_pton(AF_INET, ipstr, (int *) &ip->address);
+ ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16));
+ inet_pton(AF_INET, ipstr, &ip->address);
}
packet->header.length += htons(sizeof(switch_stun_packet_attribute_t)) + attribute->length;
@@ -519,32 +512,25 @@ SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_add_binded_address(switch_s
SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_add_xor_binded_address(switch_stun_packet_t *packet, char *ipstr, uint16_t port, int family)
{
switch_stun_packet_attribute_t *attribute;
- switch_stun_ip_t *ip;
attribute = (switch_stun_packet_attribute_t *) ((uint8_t *) & packet->first_attribute + ntohs(packet->header.length));
attribute->type = htons(SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS);
if (family == AF_INET6) {
+ switch_stun_ipv6_t *ipv6 = (switch_stun_ipv6_t *) attribute->value;
+
attribute->length = htons(20);
+ ipv6->family = 2;
+ ipv6->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16));
+ inet_pton(AF_INET6, ipstr, ipv6->address);
+ v6_xor(ipv6->address, (uint8_t *)packet->header.id);
} else {
+ switch_stun_ip_t *ip = (switch_stun_ip_t *) attribute->value;
+
attribute->length = htons(8);
- }
-
- ip = (switch_stun_ip_t *) attribute->value;
-
- ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16));
-
- if (family == AF_INET6) {
- ip->family = 2;
- } else {
ip->family = 1;
- }
-
- if (family == AF_INET6) {
- inet_pton(AF_INET6, ipstr, (struct in6_addr *) &ip->address);
- v6_xor((uint8_t *)&ip->address, (uint8_t *)packet->header.id);
- } else {
- inet_pton(AF_INET, ipstr, (int *) &ip->address);
+ ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16));
+ inet_pton(AF_INET, ipstr, &ip->address);
ip->address = htonl(ntohl(ip->address) ^ STUN_MAGIC_COOKIE);
}
diff --git a/src/switch_utils.c b/src/switch_utils.c
index 206f99218a..dc855c8def 100644
--- a/src/switch_utils.c
+++ b/src/switch_utils.c
@@ -4270,7 +4270,8 @@ switch_status_t clean_uri(char *uri)
argc = switch_separate_string(uri, '/', argv, sizeof(argv) / sizeof(argv[0]));
- if (argc == sizeof(argv)) { /* too deep */
+ /* Intentionally using == instead of > because this way we would know that the url was fully parsed for sure */
+ if (argc == (sizeof(argv) / sizeof(argv[0]))) { /* too deep */
return SWITCH_STATUS_FALSE;
}
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
index 719152d6df..47542e34ea 100644
--- a/tests/unit/Makefile.am
+++ b/tests/unit/Makefile.am
@@ -3,8 +3,10 @@ include $(top_srcdir)/build/modmake.rulesam
noinst_PROGRAMS = switch_event switch_hash switch_ivr_originate switch_utils switch_core switch_console switch_vpx switch_core_file \
switch_ivr_play_say switch_core_codec switch_rtp switch_xml
noinst_PROGRAMS += switch_core_video switch_core_db switch_vad switch_packetizer switch_core_session test_sofia switch_ivr_async switch_core_asr switch_log
+noinst_PROGRAMS += switch_stun
noinst_PROGRAMS += test_tts_format
noinst_PROGRAMS+= switch_hold switch_sip
+noinst_PROGRAMS += test_mod_verto
if HAVE_PCAP
noinst_PROGRAMS += switch_rtp_pcap
diff --git a/tests/unit/conf_stun/freeswitch.xml b/tests/unit/conf_stun/freeswitch.xml
new file mode 100644
index 0000000000..c9ad71b4df
--- /dev/null
+++ b/tests/unit/conf_stun/freeswitch.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/conf_verto/freeswitch.xml b/tests/unit/conf_verto/freeswitch.xml
new file mode 100644
index 0000000000..fb316ffde4
--- /dev/null
+++ b/tests/unit/conf_verto/freeswitch.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/conf_verto/verto.conf.xml b/tests/unit/conf_verto/verto.conf.xml
new file mode 100644
index 0000000000..ece12290ae
--- /dev/null
+++ b/tests/unit/conf_verto/verto.conf.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/switch_stun.c b/tests/unit/switch_stun.c
new file mode 100644
index 0000000000..675333c029
--- /dev/null
+++ b/tests/unit/switch_stun.c
@@ -0,0 +1,172 @@
+/*
+* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+* Copyright (C) 2005-2026, 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):
+* Dmitry Verenitsin
+*
+* switch_stun.c -- tests STUN (https://www.rfc-editor.org/rfc/rfc5389).
+*/
+
+
+#include
+#include
+#include
+
+FST_CORE_BEGIN("./conf_stun")
+{
+FST_SUITE_BEGIN(switch_stun)
+{
+FST_SETUP_BEGIN()
+{
+}
+FST_SETUP_END()
+
+FST_TEARDOWN_BEGIN()
+{
+}
+FST_TEARDOWN_END()
+
+ FST_TEST_BEGIN(test_stun_add_binded_address_ipv6)
+ {
+ /*
+ * Encode an IPv6 XOR-MAPPED-ADDRESS attribute and verify the
+ * attribute type, length, address family, and the raw 16-byte
+ * address payload at its expected offset inside the value.
+ */
+ uint8_t buf[512];
+ switch_stun_packet_t *packet;
+ switch_stun_packet_attribute_t *attr;
+ const char *ipv6_str = "2001:db8::dead:beef";
+ uint8_t expected[16];
+ uint8_t *value_bytes;
+
+ memset(buf, 0, sizeof(buf));
+ packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf);
+ fst_xcheck(inet_pton(AF_INET6, ipv6_str, expected) == 1, "test IPv6 literal parses");
+
+ switch_stun_packet_attribute_add_binded_address(packet, (char *)ipv6_str, 12345, AF_INET6);
+
+ attr = (switch_stun_packet_attribute_t *)packet->first_attribute;
+ fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS");
+ fst_xcheck(ntohs(attr->length) == 20, "attribute length is 20 for IPv6");
+
+ /* Attribute value layout: wasted(1) + family(1) + port(2) + address(16). */
+ value_bytes = (uint8_t *)attr->value;
+ fst_xcheck(value_bytes[1] == 2, "attribute family byte is 2 for IPv6");
+ fst_xcheck(memcmp(value_bytes + 4, expected, 16) == 0, "16-byte IPv6 address written at offset 4 of attribute value");
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(test_stun_add_xor_binded_address_ipv6)
+ {
+ /*
+ * Encode then decode an IPv6 XOR-MAPPED-ADDRESS attribute and
+ * confirm the round-trip recovers the original IPv6 string —
+ * the write path must XOR the address with the transaction ID
+ * symmetrically to the read path.
+ */
+ uint8_t buf[512];
+ switch_stun_packet_t *packet;
+ switch_stun_packet_attribute_t *attr;
+ const char *ipv6_str = "2001:db8::dead:beef";
+ char out_ip[64] = { 0 };
+ uint16_t out_port = 0;
+
+ memset(buf, 0, sizeof(buf));
+ packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf);
+
+ switch_stun_packet_attribute_add_xor_binded_address(packet, (char *)ipv6_str, 12345, AF_INET6);
+
+ attr = (switch_stun_packet_attribute_t *)packet->first_attribute;
+ fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS");
+ fst_xcheck(ntohs(attr->length) == 20, "attribute length is 20 for IPv6");
+
+ switch_stun_packet_attribute_get_xor_mapped_address(attr, &packet->header, out_ip, sizeof(out_ip), &out_port);
+ fst_check_string_equals(out_ip, ipv6_str);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(test_stun_add_binded_address_ipv4)
+ {
+ /*
+ * Encode an IPv4 XOR-MAPPED-ADDRESS attribute and verify the
+ * attribute type, length, address family, and the raw 4-byte
+ * address payload at its expected offset inside the value.
+ */
+ uint8_t buf[512];
+ switch_stun_packet_t *packet;
+ switch_stun_packet_attribute_t *attr;
+ const char *ipv4_str = "192.0.2.42";
+ uint8_t expected[4];
+ uint8_t *value_bytes;
+
+ memset(buf, 0, sizeof(buf));
+ packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf);
+ fst_xcheck(inet_pton(AF_INET, ipv4_str, expected) == 1, "test IPv4 literal parses");
+
+ switch_stun_packet_attribute_add_binded_address(packet, (char *)ipv4_str, 12345, AF_INET);
+
+ attr = (switch_stun_packet_attribute_t *)packet->first_attribute;
+ fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS");
+ fst_xcheck(ntohs(attr->length) == 8, "attribute length is 8 for IPv4");
+
+ /* Attribute value layout: wasted(1) + family(1) + port(2) + address(4). */
+ value_bytes = (uint8_t *)attr->value;
+ fst_xcheck(value_bytes[1] == 1, "attribute family byte is 1 for IPv4");
+ fst_xcheck(memcmp(value_bytes + 4, expected, 4) == 0, "4-byte IPv4 address written at offset 4 of attribute value");
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(test_stun_add_xor_binded_address_ipv4)
+ {
+ /*
+ * Encode then decode an IPv4 XOR-MAPPED-ADDRESS attribute and
+ * confirm the round-trip recovers the original IPv4 string —
+ * the write path must XOR the address with the magic cookie
+ * symmetrically to the read path.
+ */
+ uint8_t buf[512];
+ switch_stun_packet_t *packet;
+ switch_stun_packet_attribute_t *attr;
+ const char *ipv4_str = "192.0.2.42";
+ char out_ip[64] = { 0 };
+ uint16_t out_port = 0;
+
+ memset(buf, 0, sizeof(buf));
+ packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf);
+
+ switch_stun_packet_attribute_add_xor_binded_address(packet, (char *)ipv4_str, 12345, AF_INET);
+
+ attr = (switch_stun_packet_attribute_t *)packet->first_attribute;
+ fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS");
+ fst_xcheck(ntohs(attr->length) == 8, "attribute length is 8 for IPv4");
+
+ switch_stun_packet_attribute_get_xor_mapped_address(attr, &packet->header, out_ip, sizeof(out_ip), &out_port);
+ fst_check_string_equals(out_ip, ipv4_str);
+ }
+ FST_TEST_END()
+}
+FST_SUITE_END()
+}
+FST_CORE_END()
+
diff --git a/tests/unit/switch_utils.c b/tests/unit/switch_utils.c
index 391ec6e8e6..959f9f2e4f 100644
--- a/tests/unit/switch_utils.c
+++ b/tests/unit/switch_utils.c
@@ -124,6 +124,69 @@ FST_TEST_BEGIN(b64_pad1)
}
FST_TEST_END()
+#define test_uri_count 6
+
+/* Currently tests only clear_uri() */
+FST_TEST_BEGIN(test_switch_http_parse_header)
+{
+ int i = 0;
+ switch_status_t status = SWITCH_STATUS_SUCCESS;
+ switch_http_request_t request = {0};
+ char bad_uris[][200] = {
+ "/t/o/o/_/l/o/n/g/_/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/2/3/4",
+ "without_a_slash/",
+ };
+ char raw_uris[test_uri_count][200] = {
+ "/////////uri1",
+ "/././././uri2",
+ "/uri3/uri3_1/.//uri3_2/../../uri3_3",
+ "/../../../uri4",
+ "/uri5/uri5_1/",
+ "/uri6/uri6_1",
+ };
+ const char clear_uris[test_uri_count][200] = {
+ "/uri1",
+ "/uri2",
+ "/uri3/uri3_3",
+ "/uri4",
+ "/uri5/uri5_1",
+ "/uri6/uri6_1",
+ };
+
+ for (i = 0; i < (sizeof(bad_uris) / sizeof(bad_uris[0])); i++) {
+ char bad_header[256];
+ const char *bad_uri = bad_uris[i];
+
+ /* Use precision specifier to suppress false-positive "format-truncation" warning. */
+ snprintf(bad_header, sizeof(bad_header), "GET %.199s HTTP/1.1\r\n\r\nBODY", bad_uri);
+
+ fst_check((status = switch_http_parse_header(bad_header, sizeof(bad_header), &request)) == SWITCH_STATUS_FALSE);
+
+ if (status == SWITCH_STATUS_SUCCESS) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Bad uri parsed [%d]: [%s]\n", i, request.uri);
+ switch_http_free_request(&request);
+ }
+ }
+
+ for (i = 0; i < test_uri_count; i++) {
+ char raw_header[256];
+ const char *clear_uri = clear_uris[i];
+ const char *raw_uri = raw_uris[i];
+
+ /* Use precision specifier to suppress false-positive "format-truncation" warning. */
+ snprintf(raw_header, sizeof(raw_header), "GET %.199s HTTP/1.1\r\n\r\nBODY", raw_uri);
+
+ fst_check((status = switch_http_parse_header(raw_header, sizeof(raw_header), &request)) == SWITCH_STATUS_SUCCESS);
+ fst_check_string_equals(clear_uri, request.uri);
+
+ if (status == SWITCH_STATUS_SUCCESS) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "URI [%d]: [%s] => [%s]\n", i, raw_uri, request.uri);
+ switch_http_free_request(&request);
+ }
+ }
+}
+FST_TEST_END()
+
FST_SUITE_END()
FST_MINCORE_END()
diff --git a/tests/unit/test_mod_verto.c b/tests/unit/test_mod_verto.c
new file mode 100644
index 0000000000..c3f414af07
--- /dev/null
+++ b/tests/unit/test_mod_verto.c
@@ -0,0 +1,316 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2026, 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):
+ * Dmitry Verenitsin
+ *
+ *
+ * test_mod_verto.c -- Tests for mod_verto
+ *
+ */
+
+#include
+#include
+
+#define VERTO_TEST_HOST "127.0.0.1"
+#define VERTO_TEST_PORT 33081
+
+/* Must match HTTP_POST_MAX_BODY in src/mod/endpoints/mod_verto/mod_verto.c */
+#define VERTO_POST_MAX_BODY (10 * 1024 * 1024)
+
+static switch_status_t verto_connect(switch_socket_t **sock_out, switch_memory_pool_t *pool)
+{
+ switch_sockaddr_t *addr = NULL;
+ switch_socket_t *sock = NULL;
+ int attempts;
+
+ if (switch_sockaddr_info_get(&addr, VERTO_TEST_HOST, SWITCH_UNSPEC,
+ VERTO_TEST_PORT, 0, pool) != SWITCH_STATUS_SUCCESS) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+ for (attempts = 0; attempts < 50; attempts++) {
+ if (switch_socket_create(&sock, switch_sockaddr_get_family(addr),
+ SOCK_STREAM, SWITCH_PROTO_TCP, pool) != SWITCH_STATUS_SUCCESS) {
+ return SWITCH_STATUS_FALSE;
+ }
+ switch_socket_opt_set(sock, SWITCH_SO_TCP_NODELAY, 1);
+
+ if (switch_socket_connect(sock, addr) == SWITCH_STATUS_SUCCESS) {
+ *sock_out = sock;
+ return SWITCH_STATUS_SUCCESS;
+ }
+
+ switch_socket_close(sock);
+ sock = NULL;
+ switch_yield(100000);
+ }
+
+ return SWITCH_STATUS_FALSE;
+}
+
+static switch_status_t send_all(switch_socket_t *sock, const char *buf, switch_size_t len)
+{
+ switch_size_t remaining = len;
+ const char *p = buf;
+
+ while (remaining > 0) {
+ switch_size_t n = remaining;
+ if (switch_socket_send(sock, p, &n) != SWITCH_STATUS_SUCCESS) {
+ return SWITCH_STATUS_FALSE;
+ }
+ if (n == 0) {
+ return SWITCH_STATUS_FALSE;
+ }
+ p += n;
+ remaining -= n;
+ }
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_size_t read_status_line(switch_socket_t *sock, char *out, switch_size_t cap)
+{
+ switch_size_t got = 0;
+
+ while (got < cap - 1) {
+ switch_size_t want = cap - 1 - got;
+ if (switch_socket_recv(sock, out + got, &want) != SWITCH_STATUS_SUCCESS || want == 0) {
+ break;
+ }
+ got += want;
+ if (memchr(out, '\n', got)) break;
+ }
+ out[got] = '\0';
+ return got;
+}
+
+FST_CORE_DB_BEGIN("./conf_verto")
+{
+ FST_SUITE_BEGIN(test_mod_verto)
+ {
+ FST_SETUP_BEGIN()
+ {
+ fst_requires_module("mod_verto");
+ switch_yield(500000);
+ }
+ FST_SETUP_END()
+
+ FST_TEARDOWN_BEGIN()
+ {
+ }
+ FST_TEARDOWN_END()
+
+ FST_TEST_BEGIN(post_at_cap_returns_413)
+ {
+ switch_memory_pool_t *pool = NULL;
+ switch_socket_t *sock = NULL;
+ char req[256];
+ char resp[64] = { 0 };
+ switch_size_t req_len;
+
+ do {
+ if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not allocate memory pool");
+ break;
+ }
+ if (verto_connect(&sock, pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not connect to verto listener");
+ break;
+ }
+
+ req_len = switch_snprintf(req, sizeof(req),
+ "POST / HTTP/1.1\r\n"
+ "Host: " VERTO_TEST_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n",
+ VERTO_POST_MAX_BODY);
+
+ if (send_all(sock, req, req_len) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not send request");
+ break;
+ }
+
+ read_status_line(sock, resp, sizeof(resp));
+ fst_check_string_starts_with(resp, "HTTP/1.1 413");
+ } while (0);
+
+ if (sock) switch_socket_close(sock);
+ if (pool) switch_core_destroy_memory_pool(&pool);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(post_small_body_parsed)
+ {
+ switch_memory_pool_t *pool = NULL;
+ switch_socket_t *sock = NULL;
+ const switch_size_t body_len = 32 * 1024;
+ char *body = NULL;
+ char req[256];
+ char resp[64] = { 0 };
+ switch_size_t req_len;
+
+ do {
+ if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not allocate memory pool");
+ break;
+ }
+ if (verto_connect(&sock, pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not connect to verto listener");
+ break;
+ }
+
+ body = malloc(body_len);
+ if (!body) {
+ fst_fail("could not allocate body buffer");
+ break;
+ }
+ memset(body, 'x', body_len);
+
+ req_len = switch_snprintf(req, sizeof(req),
+ "POST / HTTP/1.1\r\n"
+ "Host: " VERTO_TEST_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %" SWITCH_SIZE_T_FMT "\r\n"
+ "\r\n",
+ body_len);
+
+ if (send_all(sock, req, req_len) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not send headers");
+ break;
+ }
+ if (send_all(sock, body, body_len) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not send body");
+ break;
+ }
+
+ read_status_line(sock, resp, sizeof(resp));
+ fst_check_string_starts_with(resp, "HTTP/1.1 ");
+ fst_xcheck(strncmp(resp, "HTTP/1.1 413", 12) != 0,
+ "server returned 413 below cap");
+ } while (0);
+
+ free(body);
+ if (sock) switch_socket_close(sock);
+ if (pool) switch_core_destroy_memory_pool(&pool);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(post_large_body_no_overflow)
+ {
+ switch_memory_pool_t *pool = NULL;
+ switch_socket_t *sock = NULL;
+ const switch_size_t body_len = 8 * 1024 * 1024;
+ char *body = NULL;
+ char req[256];
+ char resp[64] = { 0 };
+ switch_size_t req_len;
+
+ do {
+ if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not allocate memory pool");
+ break;
+ }
+ if (verto_connect(&sock, pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not connect to verto listener");
+ break;
+ }
+
+ body = malloc(body_len);
+ if (!body) {
+ fst_fail("could not allocate body buffer");
+ break;
+ }
+ memset(body, 'x', body_len);
+
+ req_len = switch_snprintf(req, sizeof(req),
+ "POST / HTTP/1.1\r\n"
+ "Host: " VERTO_TEST_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %" SWITCH_SIZE_T_FMT "\r\n"
+ "\r\n",
+ body_len);
+
+ if (send_all(sock, req, req_len) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not send headers");
+ break;
+ }
+ if (send_all(sock, body, body_len) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not send body");
+ break;
+ }
+
+ read_status_line(sock, resp, sizeof(resp));
+ fst_check_string_starts_with(resp, "HTTP/1.1 ");
+ fst_xcheck(strncmp(resp, "HTTP/1.1 413", 12) != 0,
+ "server returned 413 below cap");
+ } while (0);
+
+ free(body);
+ if (sock) switch_socket_close(sock);
+ if (pool) switch_core_destroy_memory_pool(&pool);
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(post_overflow_length_returns_413)
+ {
+ switch_memory_pool_t *pool = NULL;
+ switch_socket_t *sock = NULL;
+ char req[256];
+ char resp[64] = { 0 };
+ switch_size_t req_len;
+
+ do {
+ if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not allocate memory pool");
+ break;
+ }
+ if (verto_connect(&sock, pool) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not connect to verto listener");
+ break;
+ }
+
+ req_len = switch_snprintf(req, sizeof(req),
+ "POST / HTTP/1.1\r\n"
+ "Host: " VERTO_TEST_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: 9999999999\r\n"
+ "\r\n");
+
+ if (send_all(sock, req, req_len) != SWITCH_STATUS_SUCCESS) {
+ fst_fail("could not send request");
+ break;
+ }
+
+ read_status_line(sock, resp, sizeof(resp));
+ fst_check_string_starts_with(resp, "HTTP/1.1 413");
+ } while (0);
+
+ if (sock) switch_socket_close(sock);
+ if (pool) switch_core_destroy_memory_pool(&pool);
+ }
+ FST_TEST_END()
+ }
+ FST_SUITE_END()
+}
+FST_CORE_END()
diff --git a/w32/download_sofia-sip.props b/w32/download_sofia-sip.props
index 52c1e6fe27..a4cdf6a2d9 100644
--- a/w32/download_sofia-sip.props
+++ b/w32/download_sofia-sip.props
@@ -29,7 +29,7 @@
- 2.0.7
+ 2.0.11
0