From 4dfbf7afbb6f0359d902ff34cadfb6c6257b91cc Mon Sep 17 00:00:00 2001
From: Sylvain Berfini <sylvain.berfini@belledonne-communications.com>
Date: Wed, 19 Mar 2025 09:57:45 +0100
Subject: [PATCH] Using same workaround that is used for calls but for SIP
 simple messages

---
 coreapi/tester_utils.cpp                |  7 ++
 coreapi/tester_utils.h                  |  3 +
 include/linphone/api/c-chat-room.h      |  7 ++
 src/c-wrapper/api/c-chat-room.cpp       |  5 ++
 src/chat/chat-room/basic-chat-room.cpp  |  4 +
 src/chat/chat-room/basic-chat-room.h    |  1 +
 src/conference/conference.h             |  4 +-
 src/conference/session/call-session.cpp | 30 ++++----
 src/core/core-chat-room.cpp             | 60 +++++++++++++++
 src/core/core.cpp                       |  2 +-
 src/core/core.h                         |  3 +
 src/db/main-db.cpp                      |  6 +-
 tester/message_tester.c                 | 99 +++++++++++++++++++++++++
 13 files changed, 210 insertions(+), 21 deletions(-)

diff --git a/coreapi/tester_utils.cpp b/coreapi/tester_utils.cpp
index defaec7abe..6ad6c04b4d 100644
--- a/coreapi/tester_utils.cpp
+++ b/coreapi/tester_utils.cpp
@@ -345,3 +345,10 @@ int linphone_core_get_number_of_duplicated_messages(const LinphoneCore *core) {
 void linphone_core_set_account_deletion_timeout(LinphoneCore *core, unsigned int seconds) {
 	L_GET_CPP_PTR_FROM_C_OBJECT(core)->setAccountDeletionTimeout(seconds);
 }
+
+LinphoneChatRoom *
+linphone_core_create_basic_chat_room(LinphoneCore *core, const char *localSipUri, const char *remoteSipUri) {
+	return L_GET_CPP_PTR_FROM_C_OBJECT(core)
+	    ->getOrCreateBasicChatRoomFromUri(L_C_TO_STRING(localSipUri), L_C_TO_STRING(remoteSipUri))
+	    ->toC();
+}
\ No newline at end of file
diff --git a/coreapi/tester_utils.h b/coreapi/tester_utils.h
index 0d51f65c27..941431ac47 100644
--- a/coreapi/tester_utils.h
+++ b/coreapi/tester_utils.h
@@ -450,6 +450,9 @@ LINPHONE_PUBLIC void linphone_core_enable_gruu_in_conference_address(LinphoneCor
  **/
 LINPHONE_PUBLIC bool_t linphone_core_gruu_in_conference_address_enabled(const LinphoneCore *core);
 
+LINPHONE_PUBLIC LinphoneChatRoom *
+linphone_core_create_basic_chat_room(LinphoneCore *core, const char *localSipUri, const char *remoteSipUri);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/linphone/api/c-chat-room.h b/include/linphone/api/c-chat-room.h
index 48e83898ae..8fa6848705 100644
--- a/include/linphone/api/c-chat-room.h
+++ b/include/linphone/api/c-chat-room.h
@@ -141,6 +141,13 @@ LINPHONE_PUBLIC const LinphoneAddress *linphone_chat_room_get_local_address(Linp
  */
 LINPHONE_PUBLIC const char *linphone_chat_room_get_identifier(const LinphoneChatRoom *chat_room);
 
+/**
+ * Returns the local account to which this chat room is related.
+ * @param chat_room The #LinphoneChatRoom object. @notnil
+ * @return the related #LinphoneAccount object if any, NULL otherwise. @maybenil
+ */
+LINPHONE_PUBLIC LinphoneAccount *linphone_chat_room_get_account(LinphoneChatRoom *chat_room);
+
 /**
  * Used to receive a chat message when using async mechanism with IM enchat_roomyption engine
  * @param chat_room #LinphoneChatRoom object @notnil
diff --git a/src/c-wrapper/api/c-chat-room.cpp b/src/c-wrapper/api/c-chat-room.cpp
index 9b079762ab..ef7c248ac2 100644
--- a/src/c-wrapper/api/c-chat-room.cpp
+++ b/src/c-wrapper/api/c-chat-room.cpp
@@ -113,6 +113,11 @@ const char *linphone_chat_room_get_identifier(const LinphoneChatRoom *chat_room)
 	return NULL;
 }
 
+LinphoneAccount *linphone_chat_room_get_account(LinphoneChatRoom *chat_room) {
+	shared_ptr<Account> account = AbstractChatRoom::toCpp(chat_room)->getAccount();
+	return (account) ? account->toC() : nullptr;
+}
+
 LinphoneChatMessage *linphone_chat_room_create_empty_message(LinphoneChatRoom *chat_room) {
 	ChatRoomLogContextualizer logContextualizer(chat_room);
 	shared_ptr<ChatMessage> cppPtr = AbstractChatRoom::toCpp(chat_room)->createChatMessage();
diff --git a/src/chat/chat-room/basic-chat-room.cpp b/src/chat/chat-room/basic-chat-room.cpp
index 819abe5449..3008ca3497 100644
--- a/src/chat/chat-room/basic-chat-room.cpp
+++ b/src/chat/chat-room/basic-chat-room.cpp
@@ -116,6 +116,10 @@ const ConferenceId &BasicChatRoom::getConferenceId() const {
 	return mConferenceId;
 }
 
+void BasicChatRoom::setConferenceId(const ConferenceId &conferenceId) {
+	mConferenceId = conferenceId;
+}
+
 std::optional<std::reference_wrapper<const std::string>> BasicChatRoom::getIdentifier() const {
 	if (mState == ConferenceInterface::State::Instantiated) {
 		return std::nullopt;
diff --git a/src/chat/chat-room/basic-chat-room.h b/src/chat/chat-room/basic-chat-room.h
index b94259b7d3..bc4cb20cf7 100644
--- a/src/chat/chat-room/basic-chat-room.h
+++ b/src/chat/chat-room/basic-chat-room.h
@@ -44,6 +44,7 @@ public:
 	bool isReadOnly() const override;
 
 	const ConferenceId &getConferenceId() const override;
+	void setConferenceId(const ConferenceId &conferenceId);
 	std::optional<std::reference_wrapper<const std::string>> getIdentifier() const override;
 
 	void invalidateAccount() override;
diff --git a/src/conference/conference.h b/src/conference/conference.h
index 705a2b7f0b..e8a72aa550 100644
--- a/src/conference/conference.h
+++ b/src/conference/conference.h
@@ -355,6 +355,8 @@ public:
 
 	void resetLastNotify();
 
+	void setConferenceId(const ConferenceId &conferenceId);
+
 protected:
 	explicit Conference(const std::shared_ptr<Core> &core,
 	                    std::shared_ptr<CallSessionListener> callSessionListener,
@@ -433,8 +435,6 @@ protected:
 	void incrementLastNotify();
 	void setLastNotify(unsigned int lastNotify);
 
-	void setConferenceId(const ConferenceId &conferenceId);
-
 	void setChatRoom(const std::shared_ptr<AbstractChatRoom> &chatRoom);
 
 	std::unique_ptr<LogContextualizer> getLogContextualizer() override;
diff --git a/src/conference/session/call-session.cpp b/src/conference/session/call-session.cpp
index 82eef61361..d08b8cb6cf 100644
--- a/src/conference/session/call-session.cpp
+++ b/src/conference/session/call-session.cpp
@@ -1486,24 +1486,22 @@ void CallSession::assignAccount(const std::shared_ptr<Account> &account) {
 				cAccount = linphone_core_lookup_account_by_conference_factory_strict(core, toAddr);
 			}
 			if (!cAccount) {
-				cAccount = linphone_core_lookup_account_by_identity_strict(core, toAddr);
-				if (!cAccount) {
-					string toUser = d->log->getToAddress()->getUsername();
-					if (!toUser.empty()) {
-						cAccount = linphone_core_lookup_known_account_2(core, fromAddr, FALSE);
-						if (cAccount &&
-						    Account::toCpp(cAccount)->getAccountParams()->getIdentityAddress()->getUsername() ==
-						        toUser) {
-							// We have a match for the from domain and the to username.
-							// We may face an IPBPX that sets the To domain to our IP address, which is
-							// a terribly stupid idea.
-							lWarning() << "Detecting to header probably ill-choosen. Applying workaround to have this "
-							              "call assigned to a known account.";
-							// We must "hack" the call-log so it is correctly reported for this Account.
-							d->log->setToAddress(Account::toCpp(cAccount)->getAccountParams()->getIdentityAddress());
-						}
+				auto account = getCore()->findAccountByIdentityAddress(d->log->getToAddress());
+				if (!account) {
+					account = getCore()->guessLocalAccountFromMalformedMessage(d->log->getToAddress(),
+					                                                           d->log->getFromAddress());
+					if (account) {
+						// We have a match for the from domain and the to username.
+						// We may face an IPBPX that sets the To domain to our IP address, which is
+						// a terribly stupid idea.
+						lWarning() << "Applying workaround to have this call assigned to a known account.";
+						// We must "hack" the call-log so it is correctly reported for this Account.
+						d->log->setToAddress(account->getAccountParams()->getIdentityAddress());
 					}
 				}
+				if (account) {
+					cAccount = account->toC();
+				}
 			}
 		} else {
 			cAccount = linphone_core_lookup_account_by_identity(core, fromAddr);
diff --git a/src/core/core-chat-room.cpp b/src/core/core-chat-room.cpp
index f81cbf1108..7035df4daa 100644
--- a/src/core/core-chat-room.cpp
+++ b/src/core/core-chat-room.cpp
@@ -929,6 +929,30 @@ LinphoneReason Core::onSipMessageReceived(SalOp *op, const SalMessage *sal_msg)
 	ConferenceId conferenceId(std::move(peerAddress), std::move(localAddress), createConferenceIdParams());
 	shared_ptr<AbstractChatRoom> chatRoom = findChatRoom(conferenceId, false);
 	if (chatRoom) {
+		bool isBasic = (chatRoom->getCurrentParams()->getChatParams()->getBackend() == ChatParams::Backend::Basic);
+		if (isBasic) {
+			auto localAccount =
+			    guessLocalAccountFromMalformedMessage(conferenceId.getLocalAddress(), conferenceId.getPeerAddress());
+			if (localAccount) {
+				// We have a match for the from domain and the to username.
+				// We may face an IPBPX that sets the To domain to our IP address, which is
+				// a terribly stupid idea.
+				lWarning() << "Applying workaround to have this existing chat room assigned to a known account.";
+				auto oldConfId = chatRoom->getConferenceId();
+				conferenceId.setLocalAddress(localAccount->getAccountParams()->getIdentityAddress(), true);
+
+				d->mainDb->updateChatRoomConferenceId(oldConfId, conferenceId);
+
+				auto basicChatRoom = dynamic_pointer_cast<BasicChatRoom>(chatRoom);
+				basicChatRoom->setConferenceId(conferenceId);
+
+				d->mChatRoomsById.erase(oldConfId);
+				d->mChatRoomsById[conferenceId] = chatRoom;
+
+				updateChatRoomList();
+			}
+		}
+
 		reason = handleChatMessagesAggregation(chatRoom, op, sal_msg);
 	} else if (!linphone_core_conference_server_enabled(cCore)) {
 		const char *session_mode = sal_custom_header_find(op->getRecvCustomHeaders(), "Session-mode");
@@ -937,6 +961,16 @@ LinphoneReason Core::onSipMessageReceived(SalOp *op, const SalMessage *sal_msg)
 			lError() << "Message is received in the context of a client chatroom for which we have no context.";
 			reason = LinphoneReasonNotAcceptable;
 		} else {
+			auto localAccount =
+			    guessLocalAccountFromMalformedMessage(conferenceId.getLocalAddress(), conferenceId.getPeerAddress());
+			if (localAccount) {
+				// We have a match for the from domain and the to username.
+				// We may face an IPBPX that sets the To domain to our IP address, which is
+				// a terribly stupid idea.
+				lWarning() << "Applying workaround to have this chat room assigned to a known account.";
+				conferenceId.setLocalAddress(localAccount->getAccountParams()->getIdentityAddress(), true);
+			}
+
 			chatRoom = getOrCreateBasicChatRoom(conferenceId);
 			if (chatRoom) {
 				reason = handleChatMessagesAggregation(chatRoom, op, sal_msg);
@@ -1030,4 +1064,30 @@ void Core::decrementRemainingDownloadFileCount() {
 	}
 }
 
+std::shared_ptr<Account> Core::guessLocalAccountFromMalformedMessage(const std::shared_ptr<Address> &localAddress,
+                                                                     const std::shared_ptr<Address> &peerAddress) {
+	if (!localAddress) return nullptr;
+
+	auto account = findAccountByIdentityAddress(localAddress);
+	if (!account) {
+		string toUser = localAddress->getUsername();
+		if (!toUser.empty() && peerAddress) {
+			auto localAddressWithPeerDomain = make_shared<Address>(*localAddress);
+			localAddressWithPeerDomain->setDomain(peerAddress->getDomain());
+			account = lookupKnownAccount(localAddressWithPeerDomain, false);
+			if (account) {
+				// We have a match for the from domain and the to username.
+				// We may face an IPBPX that sets the To domain to our IP address, which is
+				// a terribly stupid idea.
+				lWarning() << "Detecting TO header probably ill-choosen.";
+				return account;
+			} else {
+				lWarning() << "Failed to find an account matching TO header";
+			}
+		}
+	}
+
+	return nullptr;
+}
+
 LINPHONE_END_NAMESPACE
diff --git a/src/core/core.cpp b/src/core/core.cpp
index cc7ac2dac2..ebc6665e2c 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -2867,7 +2867,7 @@ std::shared_ptr<Account> Core::findAccountByIdentityAddress(const std::shared_pt
 	for (const auto &account : accounts) {
 		const auto &params = account->getAccountParams();
 		const auto &address = params->getIdentityAddress();
-		if (identity->weakEqual(*address)) {
+		if (address && identity->weakEqual(address)) {
 			found = account;
 			break;
 		}
diff --git a/src/core/core.h b/src/core/core.h
index 4792c87440..406d879bc8 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -474,6 +474,9 @@ private:
 	const ConferenceId prepareConfereceIdForSearch(const ConferenceId &conferenceId) const;
 	void clearProxyConfigList() const;
 
+	std::shared_ptr<Account> guessLocalAccountFromMalformedMessage(const std::shared_ptr<Address> &localAddress,
+	                                                               const std::shared_ptr<Address> &peerAddress);
+
 	std::list<std::string> plugins;
 #if defined(_WIN32) && !defined(_WIN32_WCE)
 	std::list<HINSTANCE> loadedPlugins;
diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp
index f978e0d5c5..0aeef0574a 100644
--- a/src/db/main-db.cpp
+++ b/src/db/main-db.cpp
@@ -6541,12 +6541,14 @@ void MainDb::updateChatRoomConferenceId(const ConferenceId oldConferenceId, cons
 		L_D();
 
 		const long long &peerSipAddressId = d->insertSipAddress(newConferenceId.getPeerAddress());
+		const long long &localSipAddressId = d->insertSipAddress(newConferenceId.getLocalAddress());
 		const long long &dbChatRoomId = d->selectChatRoomId(oldConferenceId);
 
 		*d->dbSession.getBackendSession() << "UPDATE chat_room"
-		                                     " SET peer_sip_address_id = :peerSipAddressId"
+		                                     " SET peer_sip_address_id = :peerSipAddressId,"
+		                                     " local_sip_address_id = :localSipAddressId"
 		                                     " WHERE id = :chatRoomId",
-		    soci::use(peerSipAddressId), soci::use(dbChatRoomId);
+		    soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(dbChatRoomId);
 
 		tr.commit();
 
diff --git a/tester/message_tester.c b/tester/message_tester.c
index 72cf5dbd83..84212a2ca6 100644
--- a/tester/message_tester.c
+++ b/tester/message_tester.c
@@ -4445,6 +4445,102 @@ static void baudot_text_message_voice_switch_voice_carryover_europe(void) {
 
 #endif /* HAVE_BAUDOT */
 
+static void crappy_to_header(bool_t existing_chat_room) {
+	const char *template = "MESSAGE sip:%s@192.168.0.24:9597 SIP/2.0\r\n"
+	                       "Via: SIP/2.0/TCP 10.0.0.1;rport;branch=z9hG4bKSg92gr72NvDUp\r\n"
+	                       "Route: <sip:%s@192.168.0.24:9597>;transport=tcp\r\n"
+	                       "Max-Forwards: 70\r\n"
+	                       "From: <sip:marcel@sip.example.org>;tag=yPe~pl-Rg\r\n"
+	                       "To: <sip:%s@192.168.0.24:9597>;transport=tcp\r\n"
+	                       "Call-ID: e1431428-4229-4744-9479-e432618ba7f9\r\n"
+	                       "CSeq: 95887387 MESSAGE\r\n"
+	                       "User-Agent: FreeSWITCH\r\n"
+	                       "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, "
+	                       "PUBLISH, SUBSCRIBE\r\n"
+	                       "Supported: path, replaces\r\n"
+	                       "Content-Type: text/plain\r\n"
+	                       "Content-Length: 2\r\n"
+	                       "\r\n"
+	                       "Hi\r\n";
+
+	LinphoneCoreManager *laure = linphone_core_manager_new("laure_rc_udp");
+	const char *laure_username = linphone_address_get_username(laure->identity);
+
+	if (existing_chat_room) {
+		char *localSipUri = bctbx_strdup_printf("sip:%s@192.168.0.24", laure_username);
+		LinphoneChatRoom *existing_cr =
+		    linphone_core_create_basic_chat_room(laure->lc, localSipUri, "sip:marcel@sip.example.org");
+		BC_ASSERT_PTR_NOT_NULL(existing_cr);
+		if (existing_cr) {
+			const LinphoneAddress *local_address = linphone_chat_room_get_local_address(existing_cr);
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_username(local_address), laure_username);
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_domain(local_address), "192.168.0.24");
+
+			const LinphoneAddress *peer_address = linphone_chat_room_get_peer_address(existing_cr);
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_username(peer_address), "marcel");
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_domain(peer_address), "sip.example.org");
+		}
+		bctbx_free(localSipUri);
+	}
+
+	char *message = bctbx_strdup_printf(template, laure_username, laure_username, laure_username);
+	LinphoneTransports *tp = linphone_core_get_transports_used(laure->lc);
+	BC_ASSERT_TRUE(liblinphone_tester_send_data(message, strlen(message), "127.0.0.1",
+	                                            linphone_transports_get_udp_port(tp), SOCK_DGRAM) > 0);
+	linphone_transports_unref(tp);
+	bctbx_free(message);
+
+	BC_ASSERT_TRUE(wait_for(laure->lc, NULL, &laure->stat.number_of_LinphoneMessageReceived, 1));
+	LinphoneChatMessage *received_message = laure->stat.last_received_chat_message;
+	BC_ASSERT_PTR_NOT_NULL(received_message);
+	if (received_message != NULL) {
+		LinphoneChatRoom *chat_room = linphone_chat_message_get_chat_room(received_message);
+		BC_ASSERT_PTR_NOT_NULL(chat_room);
+		if (chat_room) {
+			const LinphoneAddress *local_address = linphone_chat_room_get_local_address(chat_room);
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_username(local_address), laure_username);
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_domain(local_address), "sip.example.org");
+
+			const LinphoneAddress *peer_address = linphone_chat_room_get_peer_address(chat_room);
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_username(peer_address), "marcel");
+			BC_ASSERT_STRING_EQUAL(linphone_address_get_domain(peer_address), "sip.example.org");
+
+			LinphoneAccount *account = linphone_chat_room_get_account(chat_room);
+			BC_ASSERT_PTR_NOT_NULL(account);
+			if (account) {
+				const LinphoneAddress *identity_address =
+				    linphone_account_params_get_identity_address(linphone_account_get_params(account));
+				BC_ASSERT_STRING_EQUAL(linphone_address_get_username(identity_address), laure_username);
+				BC_ASSERT_STRING_EQUAL(linphone_address_get_domain(identity_address), "sip.example.org");
+
+				const bctbx_list_t *chat_rooms = linphone_account_get_chat_rooms(account);
+				BC_ASSERT_PTR_NOT_NULL(chat_rooms);
+				if (chat_rooms) {
+					BC_ASSERT_EQUAL((int)bctbx_list_size(chat_rooms), 1, int, "%d");
+					LinphoneChatRoom *first_chat_room = (LinphoneChatRoom *)bctbx_list_get_data(chat_rooms);
+					LinphoneChatMessage *last_message = linphone_chat_room_get_last_message_in_history(first_chat_room);
+					BC_ASSERT_PTR_NOT_NULL(last_message);
+					if (last_message) {
+						BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text_content(last_message),
+						                       linphone_chat_message_get_text_content(received_message));
+						linphone_chat_message_unref(last_message);
+					}
+				}
+			}
+		}
+	}
+
+	linphone_core_manager_destroy(laure);
+}
+
+void text_message_crappy_to_header_existing_chat_room(void) {
+	crappy_to_header(TRUE);
+}
+
+void text_message_crappy_to_header(void) {
+	crappy_to_header(FALSE);
+}
+
 test_t message_tests[] = {
     TEST_NO_TAG("File transfer content", file_transfer_content),
     TEST_NO_TAG("Message with 2 attachments", message_with_two_attachments),
@@ -4479,6 +4575,9 @@ test_t message_tests[] = {
                 text_message_in_call_chat_room_from_denied_text_offer_when_room_exists),
     TEST_NO_TAG("Text message duplication", text_message_duplication),
     TEST_NO_TAG("Text message auto resent after failure", text_message_auto_resent_after_failure),
+    TEST_NO_TAG("Text message with crappy TO header", text_message_crappy_to_header),
+    TEST_NO_TAG("Text message with crappy TO header from existing chat room",
+                text_message_crappy_to_header_existing_chat_room),
     TEST_NO_TAG("Transfer message", transfer_message),
     TEST_NO_TAG("Transfer message 2", transfer_message_2),
     TEST_NO_TAG("Transfer message 3", transfer_message_3),
-- 
GitLab