From 24d9284dd5fa3fac9941ee126c1ef9c1321a2f3d Mon Sep 17 00:00:00 2001
From: Mohamed Chibani <mohamed.chibani@savoirfairelinux.com>
Date: Mon, 15 Nov 2021 13:43:08 -0500
Subject: [PATCH] SIP MESSAGE - check UA allowed methods

SIP MESSAGE method is an extention to SIP methods and might not be
supported by all User Agents (UA).
In this change, the MESSAGE method is only used if it's present in
the list of allowed methods (Allow header).
Since the Allow header is not mandatory, if the Allow header is not
present we assume that the MESSAGE method is not supported.

Gitlab: #572

Change-Id: I85d35fa0f9be108b34a538b8c732f1530ad68bd6
---
 src/im/instant_messaging.cpp | 12 +++++------
 src/jamidht/jamiaccount.cpp  |  3 ++-
 src/manager.cpp              |  2 +-
 src/sip/sip_utils.cpp        | 25 ++++++++++++++++++++-
 src/sip/sip_utils.h          | 13 +++++++++++
 src/sip/sipaccount.cpp       |  3 ++-
 src/sip/sipcall.cpp          | 42 ++++++++++++++++++++++++++++++++++--
 src/sip/sipcall.h            | 13 +++++++++++
 src/sip/sipvoiplink.cpp      | 33 ++++++++++++++++------------
 9 files changed, 120 insertions(+), 26 deletions(-)

diff --git a/src/im/instant_messaging.cpp b/src/im/instant_messaging.cpp
index b9057ee1f7..bbf90bd3bc 100644
--- a/src/im/instant_messaging.cpp
+++ b/src/im/instant_messaging.cpp
@@ -87,7 +87,7 @@ createMessageBody(pj_pool_t* pool,
         // split paramPair into arg and value by '='
         auto paramSplit = paramPair.find('=');
         if (std::string::npos == paramSplit) {
-            JAMI_DBG("bad parameter: '%.*s'", (int)paramPair.size(), paramPair.data());
+            JAMI_DBG("bad parameter: '%.*s'", (int) paramPair.size(), paramPair.data());
             throw im::InstantMessageException("invalid parameter");
         }
 
@@ -149,7 +149,8 @@ im::sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::
         return;
     }
 
-    constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD, CONST_PJ_STR("MESSAGE")};
+    constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
+                                         CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
 
     {
         auto dialog = session->dlg;
@@ -183,8 +184,8 @@ im::sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::
 static std::pair<std::string, std::string>
 parseMessageBody(const pjsip_msg_body* body)
 {
-    std::string header = sip_utils::as_view(body->content_type.type)
-                 + "/" + sip_utils::as_view(body->content_type.subtype);
+    std::string header = sip_utils::as_view(body->content_type.type) + "/"
+                         + sip_utils::as_view(body->content_type.subtype);
 
     // iterate over parameters
     auto param = body->content_type.param.next;
@@ -194,8 +195,7 @@ parseMessageBody(const pjsip_msg_body* body)
     }
 
     // get the payload, assume we can interpret it as chars
-    return {std::move(header),
-            std::string(static_cast<char*>(body->data), (size_t)body->len)};
+    return {std::move(header), std::string(static_cast<char*>(body->data), (size_t) body->len)};
 }
 
 /**
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 77c57ec61b..8e9fe1da92 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -3937,7 +3937,8 @@ JamiAccount::sendSIPMessage(SipConnection& conn,
     pjsip_tx_data* tdata;
 
     // Build SIP message
-    constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD, sip_utils::CONST_PJ_STR("MESSAGE")};
+    constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
+                                         sip_utils::CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
     pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
     pj_str_t pjTo = sip_utils::CONST_PJ_STR(toURI);
 
diff --git a/src/manager.cpp b/src/manager.cpp
index b47ccfb368..870336ca65 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -1950,7 +1950,7 @@ Manager::sendCallTextMessage(const std::string& accountId,
             }
         }
     } else {
-        JAMI_ERR("Failed to send message to %s: inexistant call ID", callID.c_str());
+        JAMI_ERR("Failed to send message to %s: inexistent call ID", callID.c_str());
     }
 }
 
diff --git a/src/sip/sip_utils.cpp b/src/sip/sip_utils.cpp
index edfed41bed..9dd46906c2 100644
--- a/src/sip/sip_utils.cpp
+++ b/src/sip/sip_utils.cpp
@@ -223,7 +223,7 @@ std::string_view
 getPeerUserAgent(const pjsip_rx_data* rdata)
 {
     if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
-        JAMI_ERR("Unexpected null poiter!");
+        JAMI_ERR("Unexpected null pointer!");
         return {};
     }
 
@@ -236,6 +236,29 @@ getPeerUserAgent(const pjsip_rx_data* rdata)
     return {};
 }
 
+std::vector<std::string>
+getPeerAllowMethods(const pjsip_rx_data* rdata)
+{
+    if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
+        JAMI_ERR("Unexpected null pointer!");
+        return {};
+    }
+
+    std::vector<std::string> methods;
+
+    pjsip_allow_hdr* allow = static_cast<pjsip_allow_hdr*>(
+        pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ALLOW, nullptr));
+
+    if (allow != nullptr) {
+        methods.reserve(allow->count);
+        for (unsigned i = 0; i < allow->count; i++) {
+            methods.emplace_back(allow->values[i].ptr, allow->values[i].slen);
+        }
+    }
+
+    return methods;
+}
+
 void
 logMessageHeaders(const pjsip_hdr* hdr_list)
 {
diff --git a/src/sip/sip_utils.h b/src/sip/sip_utils.h
index 9b72323fac..ffe68f42ad 100644
--- a/src/sip/sip_utils.h
+++ b/src/sip/sip_utils.h
@@ -39,6 +39,18 @@
 namespace jami {
 namespace sip_utils {
 
+// SIP methods. Only list methods that need to be explicitly
+// handled
+
+namespace SIP_METHODS {
+constexpr std::string_view MESSAGE = "MESSAGE";
+constexpr std::string_view INFO = "INFO";
+constexpr std::string_view OPTIONS = "OPTIONS";
+constexpr std::string_view PUBLISH = "PUBLISH";
+constexpr std::string_view REFER = "REFER";
+constexpr std::string_view NOTIFY = "NOTIFY";
+} // namespace SIP_METHODS
+
 static constexpr int DEFAULT_SIP_PORT {5060};
 static constexpr int DEFAULT_SIP_TLS_PORT {5061};
 static constexpr int DEFAULT_AUTO_SELECT_PORT {0};
@@ -99,6 +111,7 @@ std::string_view getHostFromUri(std::string_view sipUri);
 void addContactHeader(const std::string& contact, pjsip_tx_data* tdata);
 void addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata);
 std::string_view getPeerUserAgent(const pjsip_rx_data* rdata);
+std::vector<std::string> getPeerAllowMethods(const pjsip_rx_data* rdata);
 void logMessageHeaders(const pjsip_hdr* hdr_list);
 
 std::string sip_strerror(pj_status_t code);
diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp
index ac92eae687..0c31930148 100644
--- a/src/sip/sipaccount.cpp
+++ b/src/sip/sipaccount.cpp
@@ -2243,7 +2243,8 @@ SIPAccount::sendTextMessage(const std::string& to,
 
     auto toUri = getToUri(to);
 
-    constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD, CONST_PJ_STR("MESSAGE")};
+    constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
+                                         CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
     std::string from(getFromUri());
     pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
     pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index bf245c80f1..02a90e8eae 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -1460,8 +1460,29 @@ SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, con
     } else {
         if (inviteSession_) {
             try {
+                // Ignore if the peer does not allow "MESSAGE" SIP method
+                // NOTE:
+                // The SIP "Allow" header is not mandatory as per RFC-3261. If it's
+                // not present and since "MESSAGE" method is an extention method,
+                // we choose to assume that the peer does not support the "MESSAGE"
+                // method to prevent unexpected behavior when interoperating with
+                // some SIP implementations.
+                if (not isSipMethodAllowedByPeer(sip_utils::SIP_METHODS::MESSAGE)) {
+                    JAMI_WARN() << fmt::format("[call:{}] Peer does not allow \"{}\" method]",
+                                               getCallId(),
+                                               sip_utils::SIP_METHODS::MESSAGE);
+
+                    // Print peer's allowed methods
+                    JAMI_INFO() << fmt::format("[call:{}] Peer's allowed methods: {}",
+                                               getCallId(),
+                                               peerAllowedMethods_);
+                    return;
+                }
+
                 im::sendSipMessage(inviteSession_.get(), messages);
+
             } catch (...) {
+                JAMI_ERR("[call:%s] Failed to send SIP text message", getCallId().c_str());
             }
         } else {
             pendingOutMessages_.emplace_back(messages, from);
@@ -1650,6 +1671,22 @@ SIPCall::setPeerUaVersion(std::string_view ua)
     }
 }
 
+void
+SIPCall::setPeerAllowMethods(std::vector<std::string> methods)
+{
+    std::lock_guard<std::recursive_mutex> lock {callMutex_};
+    peerAllowedMethods_ = std::move(methods);
+}
+
+bool
+SIPCall::isSipMethodAllowedByPeer(const std::string_view method) const
+{
+    std::lock_guard<std::recursive_mutex> lock {callMutex_};
+
+    return std::find(peerAllowedMethods_.begin(), peerAllowedMethods_.end(), method)
+           != peerAllowedMethods_.end();
+}
+
 void
 SIPCall::onPeerRinging()
 {
@@ -3182,6 +3219,7 @@ SIPCall::merge(Call& call)
     localVideoPort_ = subcall.localVideoPort_;
     peerUserAgent_ = subcall.peerUserAgent_;
     peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
+    peerAllowedMethods_ = subcall.peerAllowedMethods_;
 
     Call::merge(subcall);
     if (isIceEnabled())
@@ -3234,8 +3272,8 @@ SIPCall::setupIceResponse()
 
     if (not remoteHasValidIceAttributes()) {
         // If ICE attributes are not present, skip the ICE initialization
-        // step (most likely ICE is not used).
-        JAMI_ERR("[call:%s] no ICE data in remote SDP", getCallId().c_str());
+        // step (most likely peer does not support/enable ICE).
+        JAMI_WARN("[call:%s] no ICE data in remote SDP", getCallId().c_str());
         return;
     }
 
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 303bfb1a06..d89f4e03b2 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -178,6 +178,16 @@ public:
      */
     void setPeerUaVersion(std::string_view ua);
 
+    /**
+     * Set peer's allowed methods
+     */
+    void setPeerAllowMethods(std::vector<std::string> methods);
+
+    /**
+     * Check if a SIP method is allowed by peer
+     */
+    bool isSipMethodAllowedByPeer(const std::string_view method) const;
+
     /**
      * Return the SDP's manager of this call
      */
@@ -415,6 +425,9 @@ private:
     // Flag to indicate the the peer's Daemon version support multi-stream.
     bool peerSupportMultiStream_ {false};
 
+    // Peer's allowed methods.
+    std::vector<std::string> peerAllowedMethods_;
+
     // Vector holding the current RTP sessions.
     std::vector<RtpStream> rtpStreams_;
 
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index 5c2d7975f7..aad68002c6 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -299,7 +299,7 @@ transaction_request_cb(pjsip_rx_data* rdata)
     if (method->id == PJSIP_OTHER_METHOD) {
         std::string_view request = sip_utils::as_view(method->name);
 
-        if (request.find("NOTIFY") != std::string_view::npos) {
+        if (request.find(sip_utils::SIP_METHODS::NOTIFY) != std::string_view::npos) {
             if (body and body->data) {
                 std::string_view body_view(static_cast<char*>(body->data), body->len);
                 auto pos = body_view.find("Voice-Message: ");
@@ -323,7 +323,7 @@ transaction_request_cb(pjsip_rx_data* rdata)
                                                                        urgentCount);
                 }
             }
-        } else if (request.find("MESSAGE") != std::string_view::npos) {
+        } else if (request.find(sip_utils::SIP_METHODS::MESSAGE) != std::string_view::npos) {
             // Reply 200 immediately (RFC 3428, ch. 7)
             try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, nullptr, nullptr, nullptr);
             // Process message content in case of multi-part body
@@ -452,6 +452,7 @@ transaction_request_cb(pjsip_rx_data* rdata)
     call->setPeerDisplayName(peerDisplayName);
     call->setState(Call::ConnectionState::PROGRESSING);
     call->getSDP().setPublishedIP(addrSdp);
+    call->setPeerAllowMethods(sip_utils::getPeerAllowMethods(rdata));
 
     // Set the temporary media list. Might change when we receive
     // the accept from the client.
@@ -714,10 +715,10 @@ SIPVoIPLink::SIPVoIPLink()
     TRY(pjsip_inv_usage_init(endpt_, &inv_cb));
 
     static constexpr pj_str_t allowed[] = {
-        CONST_PJ_STR("INFO"),
-        CONST_PJ_STR("OPTIONS"),
-        CONST_PJ_STR("MESSAGE"),
-        CONST_PJ_STR("PUBLISH"),
+        CONST_PJ_STR(sip_utils::SIP_METHODS::INFO),
+        CONST_PJ_STR(sip_utils::SIP_METHODS::OPTIONS),
+        CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE),
+        CONST_PJ_STR(sip_utils::SIP_METHODS::PUBLISH),
     };
 
     pjsip_endpt_add_capability(endpt_,
@@ -911,11 +912,15 @@ invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev)
                  pjsip_inv_state_name(inv->state),
                  inv->cause);
     }
-
+    pjsip_rx_data* rdata {nullptr};
     if (ev->type == PJSIP_EVENT_RX_MSG) {
-        call->setPeerUaVersion(sip_utils::getPeerUserAgent(ev->body.rx_msg.rdata));
+        rdata = ev->body.rx_msg.rdata;
     } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
-        call->setPeerUaVersion(sip_utils::getPeerUserAgent(ev->body.tsx_state.src.rdata));
+        rdata = ev->body.tsx_state.src.rdata;
+    }
+    if (rdata != nullptr) {
+        call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
+        call->setPeerAllowMethods(sip_utils::getPeerAllowMethods(rdata));
     }
 
     switch (inv->state) {
@@ -1348,15 +1353,15 @@ transaction_state_changed_cb(pjsip_inv_session* inv, pjsip_transaction* tsx, pjs
     JAMI_DBG("%s", msgbuf);
 #endif // DEBUG_SIP_MESSAGE
 
-    if (methodName == "REFER")
+    if (methodName == sip_utils::SIP_METHODS::REFER)
         onRequestRefer(inv, rdata, msg, *call);
-    else if (methodName == "INFO")
+    else if (methodName == sip_utils::SIP_METHODS::INFO)
         onRequestInfo(inv, rdata, msg, *call);
-    else if (methodName == "NOTIFY")
+    else if (methodName == sip_utils::SIP_METHODS::NOTIFY)
         onRequestNotify(inv, rdata, msg, *call);
-    else if (methodName == "OPTIONS")
+    else if (methodName == sip_utils::SIP_METHODS::OPTIONS)
         handleIncomingOptions(rdata);
-    else if (methodName == "MESSAGE") {
+    else if (methodName == sip_utils::SIP_METHODS::MESSAGE) {
         if (msg->body)
             runOnMainThread([call, m = im::parseSipMessage(msg)]() mutable {
                 call->onTextMessage(std::move(m));
-- 
GitLab