summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb/src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-10-19 20:42:14 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-10-19 20:42:21 +0300
commitf58bf1b1c32a62f3b6f3d5dd309a86140ac2964d (patch)
tree1a72765af19a64cbb1f3a99c0acfe3287b0f62a6 /protocols/WhatsAppWeb/src
parente9a5f03334b340ebd9e893874425f59ec6fe2f5d (diff)
WhatsApp: basic message processing
Diffstat (limited to 'protocols/WhatsAppWeb/src')
-rw-r--r--protocols/WhatsAppWeb/src/db.h2
-rw-r--r--protocols/WhatsAppWeb/src/iq.cpp66
-rw-r--r--protocols/WhatsAppWeb/src/message.cpp130
-rw-r--r--protocols/WhatsAppWeb/src/proto.cpp7
-rw-r--r--protocols/WhatsAppWeb/src/proto.h31
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp21
-rw-r--r--protocols/WhatsAppWeb/src/signal.cpp18
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp35
-rw-r--r--protocols/WhatsAppWeb/src/utils.h2
9 files changed, 274 insertions, 38 deletions
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)