diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c index 8cdc3f92ba..a78f4c1189 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.c +++ b/src/mod/endpoints/mod_sofia/mod_sofia.c @@ -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)), diff --git a/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c b/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c index 1806a8b7eb..d5696cceed 100644 --- a/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c +++ b/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c @@ -33,6 +33,7 @@ #include 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 */ + { "", "" }, + { "", "" }, + { "\"Bob\" ", "\"Bob\" " }, + { "", "" }, + { ";x-leg=b", ";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) {