[mod_sofia] Pass deflect targets with a URI scheme through unchanged

The deflect handler prefixed `sip:` and appended the profile address to any
target not starting with `sip:`. `tel:`, `sips:` and other URIs went out as
`sip:tel:...@<ip>`. A name-addr target, as a proxied `Refer-To` or
`${sip_refer_to}` passes it, went out as `sip:<sip:...>@<ip>`, or was not sent
at all when it carried a display name, URI params or `?Replaces=`.

`sofia_deflect_build_refer_to()` now builds the `Refer-To`:
- a target that has a URI scheme or contains `<` goes out as given
- `user@host`, with or without a host port, only gains the `sip:` scheme
- a bare user or number is addressed at the profile, as before
- an empty or `NULL` target, which the `deflect` app passes when run without
  data, is rejected; the handler logs it and sends no REFER

Adds `test_sofia_deflect_build_refer_to` to `test_sofia_funcs`: rejected
targets, bare users with IPv4 and IPv6 profile addresses, `user@host` forms,
URIs with a scheme, name-addr forms, the known limitations and truncation.
This commit is contained in:
Dmitry Verenitsin
2026-10-02 17:13:41 +05:00
parent 6c08a4177e
commit 91ecb6a36a
2 changed files with 128 additions and 8 deletions
+56 -8
View File
@@ -1352,6 +1352,53 @@ static switch_status_t sofia_send_dtmf(switch_core_session_t *session, const swi
return SWITCH_STATUS_SUCCESS;
}
/* True when the deflect target already carries a URI scheme or is a name-addr */
static switch_bool_t sofia_deflect_target_is_uri(const char *target)
{
const char *p = target;
if (strchr(target, '<')) {
return SWITCH_TRUE;
}
/* A scheme is a letter followed by letters, digits, '+', '-' or '.', terminated by ':' */
if (!isalpha((unsigned char)*p)) {
return SWITCH_FALSE;
}
for (p++; *p; p++) {
if (*p == ':') {
return SWITCH_TRUE;
}
if (!isalnum((unsigned char)*p) && *p != '+' && *p != '-' && *p != '.') {
break;
}
}
return SWITCH_FALSE;
}
/* Builds the Refer-To for a deflect target; fails for an empty target */
switch_status_t sofia_deflect_build_refer_to(const char *target, const char *sipip, char *buf, switch_size_t buflen)
{
if (zstr(target)) {
return SWITCH_STATUS_FALSE;
}
if (sofia_deflect_target_is_uri(target)) {
switch_copy_string(buf, target, buflen);
} else if (strchr(target, '@')) {
switch_snprintf(buf, buflen, "sip:%s", target);
} else {
const char *format = strchr(sipip, ':') ? "sip:%s@[%s]" : "sip:%s@%s";
switch_snprintf(buf, buflen, format, target, sipip);
}
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t sofia_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
@@ -1563,19 +1610,20 @@ static switch_status_t sofia_receive_message(switch_core_session_t *session, swi
case SWITCH_MESSAGE_INDICATE_DEFLECT: {
char *extra_headers = sofia_glue_get_extra_headers(channel, SOFIA_SIP_HEADER_PREFIX);
char *extra_headers;
char ref_to[1024] = "";
const char *var;
const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile);
const char *session_id_header;
if (strncasecmp(msg->string_arg, "sip:", 4)) {
const char *format = strchr(tech_pvt->profile->sipip, ':') ? "sip:%s@[%s]" : "sip:%s@%s";
switch_snprintf(ref_to, sizeof(ref_to), format, msg->string_arg, tech_pvt->profile->sipip);
} else {
switch_set_string(ref_to, msg->string_arg);
if (sofia_deflect_build_refer_to(msg->string_arg, tech_pvt->profile->sipip, ref_to, sizeof(ref_to)) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Deflect requires a target\n");
msg->string_reply = "no target";
break;
}
extra_headers = sofia_glue_get_extra_headers(channel, SOFIA_SIP_HEADER_PREFIX);
session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile);
nua_refer(tech_pvt->nh, SIPTAG_REFER_TO_STR(ref_to), SIPTAG_REFERRED_BY_STR(tech_pvt->contact_url),
TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)),
TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)),
@@ -33,6 +33,7 @@
#include <test/switch_test.h>
int protect_dest_uri(switch_caller_profile_t *cp);
switch_status_t sofia_deflect_build_refer_to(const char *target, const char *sipip, char *buf, switch_size_t buflen);
static int timeout_sec = 10;
static switch_interval_time_t delay_start_ms = 5000;
@@ -122,6 +123,77 @@ FST_TEST_BEGIN(originate_test)
}
FST_TEST_END()
FST_TEST_BEGIN(test_sofia_deflect_build_refer_to)
{
char ref_to[1024];
char short_buf[12];
size_t i;
const char *sipip = "203.0.113.5";
/* expect NULL means the target is rejected */
struct {
const char *target;
const char *expect;
} cases[] = {
{ NULL, NULL },
{ "", NULL },
/* a bare user or number is addressed at the profile */
{ "1234", switch_core_sprintf(fst_pool, "sip:1234@%s", sipip) },
{ "+15551234567", switch_core_sprintf(fst_pool, "sip:+15551234567@%s", sipip) },
/* user@host only gains the scheme, including a host port or an IPv6 literal */
{ "bob@example.com", "sip:bob@example.com" },
{ "bob@198.51.100.7:2222", "sip:bob@198.51.100.7:2222" },
{ "bob@[2001:db8::7]", "sip:bob@[2001:db8::7]" },
/* a URI with a scheme goes out as given */
{ "sip:bob@example.com", "sip:bob@example.com" },
{ "sips:bob@example.com", "sips:bob@example.com" },
{ "SIPS:bob@example.com", "SIPS:bob@example.com" },
{ "sip:bob@198.51.100.7:2222", "sip:bob@198.51.100.7:2222" },
{ "tel:+15551234567", "tel:+15551234567" },
{ "urn:service:sos", "urn:service:sos" },
/* a name-addr goes out as given */
{ "<sip:bob@example.com>", "<sip:bob@example.com>" },
{ "<sip:bob@example.com;user=phone>", "<sip:bob@example.com;user=phone>" },
{ "\"Bob\" <sip:bob@example.com>", "\"Bob\" <sip:bob@example.com>" },
{ "<sip:bob@example.com?Replaces=xfer-42%3Bto-tag%3Dt1%3Bfrom-tag%3Df1>", "<sip:bob@example.com?Replaces=xfer-42%3Bto-tag%3Dt1%3Bfrom-tag%3Df1>" },
{ "<sip:bob@example.com>;x-leg=b", "<sip:bob@example.com>;x-leg=b" },
/* Known limitations: a host:port without a user, or a user:password, reads as a scheme or is
* addressed at the profile, and none of these is a valid Refer-To */
{ "pbx.example.com:5060", "pbx.example.com:5060" },
{ "198.51.100.7:5060", switch_core_sprintf(fst_pool, "sip:198.51.100.7:5060@%s", sipip) },
{ "bob:secret@example.com", "bob:secret@example.com" },
};
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
switch_status_t status;
ref_to[0] = '\0';
status = sofia_deflect_build_refer_to(cases[i].target, sipip, ref_to, sizeof(ref_to));
if (!cases[i].expect) {
fst_xcheck(status == SWITCH_STATUS_FALSE, "an empty deflect target must be rejected");
continue;
}
fst_xcheck(status == SWITCH_STATUS_SUCCESS, cases[i].target);
fst_check_string_equals(ref_to, cases[i].expect);
}
/* An IPv6 profile address is bracketed */
sofia_deflect_build_refer_to("1234", "2001:db8:cafe::5", ref_to, sizeof(ref_to));
fst_check_string_equals(ref_to, "sip:1234@[2001:db8:cafe::5]");
/* The result is truncated to buflen on every branch */
sofia_deflect_build_refer_to("1234567", sipip, short_buf, sizeof(short_buf));
fst_check_string_equals(short_buf, "sip:1234567");
sofia_deflect_build_refer_to("bob@example.com", sipip, short_buf, sizeof(short_buf));
fst_check_string_equals(short_buf, "sip:bob@exa");
sofia_deflect_build_refer_to("sip:bob@example.com", sipip, short_buf, sizeof(short_buf));
fst_check_string_equals(short_buf, "sip:bob@exa");
}
FST_TEST_END()
#if HAVE_STIRSHAKEN
FST_TEST_BEGIN(sofia_verify_identity_test_no_identity)
{