diff options
author | George Hazan <ghazan@miranda.im> | 2022-10-19 20:42:14 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-10-19 20:42:21 +0300 |
commit | f58bf1b1c32a62f3b6f3d5dd309a86140ac2964d (patch) | |
tree | 1a72765af19a64cbb1f3a99c0acfe3287b0f62a6 /protocols/WhatsAppWeb | |
parent | e9a5f03334b340ebd9e893874425f59ec6fe2f5d (diff) |
WhatsApp: basic message processing
Diffstat (limited to 'protocols/WhatsAppWeb')
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/db.h | 2 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/iq.cpp | 66 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/message.cpp | 130 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 7 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 31 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 21 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/signal.cpp | 18 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 35 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 2 |
11 files changed, 278 insertions, 38 deletions
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj index 6231855f79..bc032bfdcc 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj @@ -66,6 +66,7 @@ <ClCompile Include="src\crypt.cpp" /> <ClCompile Include="src\iq.cpp" /> <ClCompile Include="src\main.cpp" /> + <ClCompile Include="src\message.cpp" /> <ClCompile Include="src\noise.cpp" /> <ClCompile Include="src\options.cpp" /> <ClCompile Include="src\pmsg.pb.cc"> diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters index fa7d636d56..a86fb8be96 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters @@ -50,6 +50,9 @@ <ClCompile Include="src\crypt.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\message.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\db.h"> diff --git a/protocols/WhatsAppWeb/src/db.h b/protocols/WhatsAppWeb/src/db.h index 0686693330..10c6f33822 100644 --- a/protocols/WhatsAppWeb/src/db.h +++ b/protocols/WhatsAppWeb/src/db.h @@ -10,6 +10,8 @@ Copyright © 2019-22 George Hazan // DB keys #define DBKEY_JID "jid" #define DBKEY_DEVICE_ID "DeviceId" +#define DBKEY_EPHEMERAL_TS "EphemeralTS" +#define DBKEY_EPHEMERAL_EXPIRE "EphemeralExpire" #define DBKEY_NOISE_PUB "NoisePublicKey" #define DBKEY_NOISE_PRIV "NoisePrivateKey" diff --git a/protocols/WhatsAppWeb/src/iq.cpp b/protocols/WhatsAppWeb/src/iq.cpp index d47384067e..4f74567f0c 100644 --- a/protocols/WhatsAppWeb/src/iq.cpp +++ b/protocols/WhatsAppWeb/src/iq.cpp @@ -103,7 +103,8 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) auto *msgId = node.getAttr("id"); auto *msgType = node.getAttr("type"); auto *msgFrom = node.getAttr("from"); - auto *recepient = node.getAttr("recepient"); + auto *category = node.getAttr("category"); + auto *recipient = node.getAttr("recipient"); auto *participant = node.getAttr("participant"); if (msgType == nullptr || msgFrom == nullptr || msgId == nullptr) { @@ -115,18 +116,21 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) WAJid jid(msgFrom); CMStringA szAuthor, szChatId; + if (node.getAttr("offline")) + type.bOffline = true; + // message from one user to another if (jid.isUser()) { - if (recepient) { + if (recipient) { if (m_szJid != msgFrom) { - debugLogA("strange message: with recepient, but not from me"); + debugLogA("strange message: with recipient, but not from me"); return; } - szChatId = recepient; + szChatId = recipient; } else szChatId = msgFrom; - type = WAMSG::PrivateChat; + type.bPrivateChat = true; szAuthor = msgFrom; } else if (jid.isGroup()) { @@ -135,7 +139,7 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) return; } - type = WAMSG::GroupChat; + type.bGroupChat = true; szAuthor = participant; szChatId = msgFrom; } @@ -146,10 +150,18 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) } bool bIsMe = m_szJid == participant; - if (jid.isStatusBroadcast()) - type = (bIsMe) ? WAMSG::DirectStatus : WAMSG::OtherStatus; - else - type = (bIsMe) ? WAMSG::PeerBroadcast : WAMSG::OtherBroadcast; + if (jid.isStatusBroadcast()) { + if (bIsMe) + type.bDirectStatus = true; + else + type.bOtherStatus = true; + } + else { + if (bIsMe) + type.bPeerBroadcast = true; + else + type.bOtherBroadcast = true; + } szChatId = msgFrom; szAuthor = participant; } @@ -158,7 +170,7 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) return; } - CMStringA szSender = (type == WAMSG::PrivateChat) ? szAuthor : szChatId; + CMStringA szSender = (type.bPrivateChat) ? szAuthor : szChatId; bool bFromMe = (m_szJid == msgFrom); if (!bFromMe && participant) bFromMe = m_szJid == participant; @@ -211,24 +223,48 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) iDecryptable++; + // remove message padding + if (size_t len = msgBody.len()) { + size_t c = msgBody.data()[len - 1]; + if (c < len) + msgBody.reset(len - c); + } + proto::Message encMsg; encMsg.ParseFromArray(msgBody.data(), msgBody.len()); if (encMsg.devicesentmessage().has_message()) - encMsg = encMsg.devicesentmessage().message(); + msg.set_allocated_message(new proto::Message(encMsg.devicesentmessage().message())); + else + msg.set_allocated_message(new proto::Message(encMsg)); if (encMsg.has_senderkeydistributionmessage()) m_signalStore.processSenderKeyMessage(encMsg.senderkeydistributionmessage()); - msg.set_allocated_message(new proto::Message(encMsg)); + ProcessMessage(type, msg); + msg.clear_message(); + + // send receipt + const char *pszReceiptType = nullptr, *pszReceiptTo = participant; + if (!mir_strcmp(category, "peer")) + pszReceiptType = "peer_msg"; + else if (bFromMe) { + // message was sent by me from a different device + pszReceiptType = "sender"; + if (WAJid(szChatId).isUser()) + pszReceiptTo = szAuthor; + } + else if (!m_hServerConn) + pszReceiptType = "inactive"; + + SendReceipt(szChatId, pszReceiptTo, msgId, pszReceiptType); } catch (const char *pszError) { debugLogA("Message cannot be parsed: %s", pszError); - msg.set_messagestubtype(proto::WebMessageInfo_StubType::WebMessageInfo_StubType_CIPHERTEXT); } if (!iDecryptable) { debugLogA("Nothing to decrypt"); - msg.set_messagestubtype(proto::WebMessageInfo_StubType::WebMessageInfo_StubType_CIPHERTEXT); + return; } } } diff --git a/protocols/WhatsAppWeb/src/message.cpp b/protocols/WhatsAppWeb/src/message.cpp new file mode 100644 index 0000000000..8e7ff03046 --- /dev/null +++ b/protocols/WhatsAppWeb/src/message.cpp @@ -0,0 +1,130 @@ +/* + +WhatsAppWeb plugin for Miranda NG +Copyright © 2019-22 George Hazan + +*/ + +#include "stdafx.h" + +static const proto::Message& getBody(const proto::Message &message) +{ + if (message.has_ephemeralmessage()) { + auto &pMsg = message.ephemeralmessage().message(); + return (pMsg.has_viewoncemessage()) ? pMsg.viewoncemessage().message() : pMsg; + } + + if (message.has_viewoncemessage()) + return message.viewoncemessage().message(); + + return message; +} + +void WhatsAppProto::ProcessMessage(WAMSG type, const proto::WebMessageInfo &msg) +{ + auto &key = msg.key(); + auto &body = getBody(msg.message()); + bool bFromMe = key.fromme(); + + debugLogA("Got a message: %s", msg.Utf8DebugString().c_str()); + + uint32_t timestamp = msg.messagetimestamp(); + auto &participant = key.participant(); + auto &chatId = key.remotejid(); + auto &msgId = key.id(); + + WAUser *pUser = FindUser(chatId.c_str()); + if (pUser == nullptr) { + if (type.bPrivateChat) + pUser = AddUser(chatId.c_str(), false, false); + else if (type.bGroupChat) + pUser = AddUser(chatId.c_str(), false, true); + } + + if (!bFromMe && msg.has_pushname() && pUser && !pUser->bIsGroupChat) + setUString(pUser->hContact, "Nick", msg.pushname().c_str()); + + // try to extract some text + if (body.has_conversation()) { + auto &conversation = body.conversation(); + if (!conversation.empty()) { + PROTORECVEVENT pre = {}; + pre.timestamp = timestamp; + pre.szMessage = (char*)conversation.c_str(); + pre.szMsgId = msgId.c_str(); + if (type.bOffline) + pre.flags |= PREF_CREATEREAD; + if (bFromMe) + pre.flags |= PREF_SENT; + ProtoChainRecvMsg(pUser->hContact, &pre); + } + } + + if (body.has_protocolmessage()) { + auto &protoMsg = body.protocolmessage(); + switch (protoMsg.type()) { + case proto::Message_ProtocolMessage_Type_APP_STATE_SYNC_KEY_SHARE: + if (protoMsg.appstatesynckeyshare().keys_size()) { + for (auto &it : protoMsg.appstatesynckeyshare().keys()) { + auto &keyid = it.keyid().keyid(); + auto &keydata = it.keydata().keydata(); + + CMStringA szSetting(FORMAT, "AppSyncKey%d", decodeBigEndian(keyid)); + db_set_blob(0, m_szModuleName, szSetting, keydata.c_str(), (unsigned)keydata.size()); + } + } + break; + + case proto::Message_ProtocolMessage_Type_HISTORY_SYNC_NOTIFICATION: + debugLogA("History sync notification"); + break; + + case proto::Message_ProtocolMessage_Type_REVOKE: + break; + + case proto::Message_ProtocolMessage_Type_EPHEMERAL_SETTING: + if (pUser) { + setDword(pUser->hContact, DBKEY_EPHEMERAL_TS, timestamp); + setDword(pUser->hContact, DBKEY_EPHEMERAL_EXPIRE, protoMsg.ephemeralexpiration()); + } + break; + } + } + else if (body.has_reactionmessage()) { + debugLogA("Got a reaction to a message"); + } + else if (msg.has_messagestubtype()) { + switch (msg.messagestubtype()) { + case proto::WebMessageInfo::GROUP_PARTICIPANT_LEAVE: + case proto::WebMessageInfo::GROUP_PARTICIPANT_REMOVE: + debugLogA("Participant %s removed from chat", participant.c_str()); + break; + + case proto::WebMessageInfo::GROUP_PARTICIPANT_ADD: + case proto::WebMessageInfo::GROUP_PARTICIPANT_INVITE: + case proto::WebMessageInfo::GROUP_PARTICIPANT_ADD_REQUEST_JOIN: + debugLogA("Participant %s added to chat", participant.c_str()); + break; + + case proto::WebMessageInfo::GROUP_PARTICIPANT_DEMOTE: + debugLogA("Participant %s demoted", participant.c_str()); + break; + + case proto::WebMessageInfo::GROUP_PARTICIPANT_PROMOTE: + debugLogA("Participant %s promoted", participant.c_str()); + break; + + case proto::WebMessageInfo::GROUP_CHANGE_ANNOUNCE: + debugLogA("Groupchat announce", participant.c_str()); + break; + + case proto::WebMessageInfo::GROUP_CHANGE_RESTRICT: + debugLogA("Groupchat restriction", participant.c_str()); + break; + + case proto::WebMessageInfo::GROUP_CHANGE_SUBJECT: + debugLogA("Groupchat subject was changed", participant.c_str()); + break; + } + } +} diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index 6beed4d91c..5e797c6431 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -107,10 +107,13 @@ WhatsAppProto::~WhatsAppProto() void WhatsAppProto::OnModulesLoaded() { // initialize contacts cache + m_arUsers.insert(new WAUser(0, m_szJid, false)); + for (auto &cc : AccContacts()) { - CMStringA szId(getMStringA(cc, isChatRoom(cc) ? "ChatRoomID" : DBKEY_JID)); + bool bIsChat = isChatRoom(cc); + CMStringA szId(getMStringA(cc, bIsChat ? "ChatRoomID" : DBKEY_JID)); if (!szId.IsEmpty()) - m_arUsers.insert(new WAUser(cc, szId)); + m_arUsers.insert(new WAUser(cc, szId, bIsChat)); } } diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index b34ebe3334..6a6a97589b 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -15,14 +15,20 @@ Copyright © 2019-22 George Hazan class WhatsAppProto; typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const WANode &node); -enum WAMSG +struct WAMSG { - PrivateChat, - GroupChat, - DirectStatus, - OtherStatus, - PeerBroadcast, - OtherBroadcast + union { + uint32_t dwFlags = 0; + struct { + bool bPrivateChat : 1; + bool bGroupChat : 1; + bool bDirectStatus : 1; + bool bOtherStatus : 1; + bool bPeerBroadcast : 1; + bool bOtherBroadcast : 1; + bool bOffline : 1; + }; + }; }; struct WARequest @@ -67,9 +73,10 @@ struct WAHistoryMessage struct WAUser { - WAUser(MCONTACT _1, const char *_2) : + WAUser(MCONTACT _1, const char *_2, bool _3 = false) : hContact(_1), szId(mir_strdup(_2)), + bIsGroupChat(_3), arHistory(1) { } @@ -82,7 +89,7 @@ struct WAUser MCONTACT hContact; DWORD dwModifyTag = 0; char *szId; - bool bInited = false; + bool bInited = false, bIsGroupChat; SESSION_INFO *si = 0; DWORD m_time1 = 0, m_time2 = 0; OBJLIST<WAHistoryMessage> arHistory; @@ -262,7 +269,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto> OBJLIST<WADevice> m_arDevices; WAUser* FindUser(const char *szId); - WAUser* AddUser(const char *szId, bool bTemporary); + WAUser* AddUser(const char *szId, bool bTemporary, bool isChat = false); // Group chats ///////////////////////////////////////////////////////////////////////// @@ -286,7 +293,8 @@ class WhatsAppProto : public PROTO<WhatsAppProto> int m_iPacketId; uint16_t m_wMsgPrefix[2]; - CMStringA generateMessageId(); + CMStringA GenerateMessageId(); + void ProcessMessage(WAMSG type, const proto::WebMessageInfo &msg); bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf); int WSSend(const MessageLite &msg); @@ -300,6 +308,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto> void ServerThreadWorker(void); void ShutdownSession(void); + void SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType); void SendKeepAlive(); void SetServerStatus(int iStatus); diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 3a83bc8f28..91f36b7df7 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -283,6 +283,27 @@ void WhatsAppProto::SendKeepAlive() } } +void WhatsAppProto::SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType) +{ + WANode receipt("receipt"); + receipt << CHAR_PARAM("id", pszId); + + if (!mir_strcmp(pszType, "read") || !mir_strcmp(pszType, "read-self")) + receipt << INT_PARAM("t", time(0)); + + if (!mir_strcmp(pszType, "sender") && WAJid(pszTo).isUser()) + receipt << CHAR_PARAM("to", pszParticipant) << CHAR_PARAM("recipient", pszTo); + else { + receipt << CHAR_PARAM("to", pszTo); + if (pszParticipant) + receipt << CHAR_PARAM("participant", pszParticipant); + } + + if (pszType) + receipt << CHAR_PARAM("type", pszType); + WSSendNode(receipt, &WhatsAppProto::OnIqDoNothing); +} + void WhatsAppProto::SetServerStatus(int iStatus) { if (mir_wstrlen(m_wszNick)) diff --git a/protocols/WhatsAppWeb/src/signal.cpp b/protocols/WhatsAppWeb/src/signal.cpp index b7ab9bfc6b..bd75f0ff3a 100644 --- a/protocols/WhatsAppWeb/src/signal.cpp +++ b/protocols/WhatsAppWeb/src/signal.cpp @@ -82,7 +82,7 @@ static int decrypt_func(signal_buffer **output, const uint8_t *ciphertext, size_t ciphertext_len, void * /*user_data*/) { - MBinBuffer res = aesDecrypt(EVP_aes_256_gcm(), key, iv, ciphertext, ciphertext_len); + MBinBuffer res = aesDecrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len); *output = signal_buffer_create(res.data(), res.length()); return SG_SUCCESS; } @@ -479,7 +479,19 @@ signal_buffer* MSignalStore::decryptSignalProto(const CMStringA &from, const cha signal_buffer* MSignalStore::decryptGroupSignalProto(const CMStringA &group, const CMStringA &sender, const MBinBuffer &encrypted) { - return nullptr; + WAJid jid(sender); + auto *pSession = createSession(group + CMStringA(FORMAT, "::%s::%d", jid.user.c_str(), jid.device), 0); + + signal_message *pMsg; + if (signal_message_deserialize(&pMsg, (BYTE *)encrypted.data(), encrypted.length(), m_pContext) < 0) + throw "unable to deserialize signal message"; + + signal_buffer *result = nullptr; + if (session_cipher_decrypt_signal_message(pSession->getCipher(), pMsg, this, &result) < 0) + throw "unable to decrypt signal message"; + + signal_message_destroy((signal_type_base *)pMsg); + return result; } ///////////////////////////////////////////////////////////////////////////////////////// @@ -512,6 +524,6 @@ void MSignalStore::generatePrekeys(int count) ///////////////////////////////////////////////////////////////////////////////////////// -void MSignalStore::processSenderKeyMessage(const proto::Message_SenderKeyDistributionMessage &msg) +void MSignalStore::processSenderKeyMessage(const proto::Message_SenderKeyDistributionMessage &) { } diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index 1a61f5b8ed..c176a894fe 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -80,7 +80,7 @@ WAUser* WhatsAppProto::FindUser(const char *szId) return m_arUsers.find(tmp); } -WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary) +WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary, bool isChat) { auto *pUser = FindUser(szId); if (pUser != nullptr) @@ -88,12 +88,22 @@ WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary) MCONTACT hContact = db_add_contact(); Proto_AddToContact(hContact, m_szModuleName); - setString(hContact, DBKEY_JID, szId); - pUser = new WAUser(hContact, mir_strdup(szId)); + + if (isChat) { + setByte(hContact, "ChatRoom", 1); + setString(hContact, "ChatRoomID", szId); + } + else { + setString(hContact, DBKEY_JID, szId); + if (m_wszDefaultGroup) + Clist_SetGroup(hContact, m_wszDefaultGroup); + } + if (bTemporary) Contact::RemoveFromList(hContact); - if (m_wszDefaultGroup) - Clist_SetGroup(hContact, m_wszDefaultGroup); + + pUser = new WAUser(hContact, mir_strdup(szId)); + pUser->bIsGroupChat = isChat; mir_cslock lck(m_csUsers); m_arUsers.insert(pUser); @@ -127,7 +137,7 @@ WA_PKT_HANDLER WhatsAppProto::FindPersistentHandler(const WANode &pNode) ///////////////////////////////////////////////////////////////////////////////////////// -CMStringA WhatsAppProto::generateMessageId() +CMStringA WhatsAppProto::GenerateMessageId() { return CMStringA(FORMAT, "%d.%d-%d", m_wMsgPrefix[0], m_wMsgPrefix[1], m_iPacketId++); } @@ -161,7 +171,7 @@ int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler) return 0; if (pHandler != nullptr) { - CMStringA id(generateMessageId()); + CMStringA id(GenerateMessageId()); node.addAttr("id", id); mir_cslock lck(m_csPacketQueue); @@ -183,6 +193,17 @@ int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler) ///////////////////////////////////////////////////////////////////////////////////////// +uint32_t decodeBigEndian(const std::string &buf) +{ + uint32_t ret = 0; + for (auto &cc : buf) { + ret <<= 8; + ret += (uint8_t)cc; + } + + return ret; +} + std::string encodeBigEndian(uint32_t num, size_t len) { std::string res; diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h index c6f9f8454b..b8ab1ee155 100644 --- a/protocols/WhatsAppWeb/src/utils.h +++ b/protocols/WhatsAppWeb/src/utils.h @@ -183,7 +183,9 @@ MBinBuffer aesDecrypt( const void *data, size_t dataLen, const void *additionalData = 0, size_t additionalLen = 0); +uint32_t decodeBigEndian(const std::string &buf); std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t)); + void generateIV(uint8_t *iv, int &pVar); __forceinline bool operator<<(MessageLite &msg, const MBinBuffer &buf) |