diff --git a/coreapi/tester_utils.cpp b/coreapi/tester_utils.cpp index defaec7abed4390c1bb9f426dd186441dbba6e48..6ad6c04b4d223e621a9b09763f61c38629adf0bc 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 0d51f65c270aa93e079670635fe1e38d430bd25a..941431ac47732614088a46ff02ea35e0329e745f 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 48e83898aea5c8abc474e3ff90aeddf44b461120..8fa6848705766a279a311eccd27cce9208903494 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 9b079762abe384f07aef585be2188794fe096ecf..ef7c248ac27c158010389b3286cc5894c8b2b0a3 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 819abe5449f20b5ba29e924481cdfad7016f7975..3008ca34979eb54b33bb1c4684fa0bf01a8f0ec5 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 b94259b7d3bc2eecb14bd8361495c10ccd21825e..bc4cb20cf74d4c0e7bbd0fee9b44ae5f463b5f15 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 705a2b7f0bdc58a8a5b8d591cdb6f23dbb6e50b0..e8a72aa5501bcc0b85c7fc21554cbb782419d148 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 82eef613610c4724a9de84c73876d327a44575c3..d08b8cb6cfeb032be4771f9f2720a94d670aefa4 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 f81cbf1108128def367969c781956d4b69a746c0..7035df4daaac20456660801fcbd43d10fdc07b34 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 cc7ac2dac21f0071e4be9a47951de28f3f34fb97..ebc6665e2c365cfe9f2618bc8ecb13e0599edc0a 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 ¶ms = 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 4792c874402105b582bd3abab3a3e8068aa7f930..406d879bc848ef5d774a14bbdf3572e8d9d5bbd9 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 f978e0d5c59a0100e037858d851c3373db210138..0aeef0574a9c55b5538ed500ff76e34059991283 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 72cf5dbd832309d78fcce5e270a74fcec2866056..84212a2ca697114ea4840d24a4743bfe7cf8dab2 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),