summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2021-04-24 12:00:17 +0300
committerGeorge Hazan <ghazan@miranda.im>2021-04-24 12:00:17 +0300
commitf12ba22652c92aebc74eace3a3011285af3a116c (patch)
tree834373453dac043b176fd3464c29a22619fdba2a /protocols/WhatsAppWeb
parent7c9e299626e8ce572d69c9af176df49a819b6cbb (diff)
WhatsApp: infrastructure for sending messages, first variant (doesn't work always)
Diffstat (limited to 'protocols/WhatsAppWeb')
-rw-r--r--protocols/WhatsAppWeb/src/proto.cpp43
-rw-r--r--protocols/WhatsAppWeb/src/proto.h18
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp115
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp180
-rw-r--r--protocols/WhatsAppWeb/src/utils.h38
5 files changed, 367 insertions, 27 deletions
diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp
index d1117846aa..aab94609fe 100644
--- a/protocols/WhatsAppWeb/src/proto.cpp
+++ b/protocols/WhatsAppWeb/src/proto.cpp
@@ -27,6 +27,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
m_impl(*this),
m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)),
m_arUsers(10, CompareUsers),
+ m_arOwnMsgs(1, NumericKeySortT),
m_arPacketQueue(10, NumericKeySortT),
m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"),
m_bHideGroupchats(this, "HideChats", true)
@@ -191,6 +192,32 @@ int WhatsAppProto::SetStatus(int new_status)
return 0;
}
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnSendMessage(const JSONNode &node)
+{
+ int pktId = node["$id$"].as_int();
+
+ WAOwnMessage tmp(pktId, 0);
+ {
+ mir_cslock lck(m_csOwnMessages);
+ auto *pOwn = m_arOwnMsgs.find(&tmp);
+ if (pOwn == nullptr)
+ return;
+
+ tmp.hContact = pOwn->hContact;
+ m_arOwnMsgs.remove(pOwn);
+ }
+
+ int status = node["status"].as_int();
+ if (status == 200)
+ ProtoBroadcastAck(tmp.hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)pktId);
+ else {
+ CMStringW wszError(FORMAT, TranslateT("Operation failed with server error status %d"), status);
+ ProtoBroadcastAck(tmp.hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)pktId, LPARAM(wszError.c_str()));
+ }
+}
+
int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg)
{
ptrA jid(getStringA(hContact, DBKEY_ID));
@@ -214,7 +241,21 @@ int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg)
size_t cbBinaryLen = msg.ByteSizeLong();
mir_ptr<BYTE> pBuf((BYTE *)mir_alloc(cbBinaryLen));
msg.SerializeToArray(pBuf, (int)cbBinaryLen);
- return 0;
+
+ auto *message = new WANode();
+ message->title = "message";
+ message->content.assign(pBuf, cbBinaryLen);
+
+ WANode payLoad;
+ payLoad.title = "action";
+ payLoad.addAttr("type", "relay");
+ payLoad.children.push_back(message);
+
+ int pktId = WSSendNode(payLoad, &WhatsAppProto::OnSendMessage);
+
+ mir_cslock lck(m_csOwnMessages);
+ m_arOwnMsgs.insert(new WAOwnMessage(pktId, hContact));
+ return pktId;
}
int WhatsAppProto::UserIsTyping(MCONTACT hContact, int)
diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h
index 16ce1bb30f..d7a3723e89 100644
--- a/protocols/WhatsAppWeb/src/proto.h
+++ b/protocols/WhatsAppWeb/src/proto.h
@@ -38,6 +38,17 @@ struct WAUser
DWORD m_time1 = 0, m_time2 = 0;
};
+struct WAOwnMessage
+{
+ WAOwnMessage(int _1, MCONTACT _2) :
+ pktId(_1),
+ hContact(_2)
+ {}
+
+ int pktId;
+ MCONTACT hContact;
+};
+
class WhatsAppProto : public PROTO<WhatsAppProto>
{
class CWhatsAppProtoImpl
@@ -74,6 +85,9 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
mir_cs m_csUsers;
OBJLIST<WAUser> m_arUsers;
+ mir_cs m_csOwnMessages;
+ OBJLIST<WAOwnMessage> m_arOwnMsgs;
+
WAUser* FindUser(const char *szId);
WAUser* AddUser(const char *szId, bool bTemporary);
@@ -97,6 +111,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf);
int WSSend(const CMStringA &str, WA_PKT_HANDLER = nullptr);
+ int WSSendNode(WANode &node, WA_PKT_HANDLER = nullptr);
void OnLoggedIn(void);
void OnLoggedOut(void);
@@ -114,10 +129,11 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
void OnRestoreSession1(const JSONNode &node);
void OnRestoreSession2(const JSONNode &node);
+ void OnSendMessage(const JSONNode &node);
void OnStartSession(const JSONNode &node);
// binary packets
- void ProcessBinaryPacket(const MBinBuffer &buf);
+ void ProcessBinaryPacket(const void *pData, size_t cbLen);
void ProcessAdd(const CMStringA &type, const WANode *node);
void ProcessChats(const WANode *node);
void ProcessContacts(const WANode *node);
diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp
index 60eb227c97..c89926d1ce 100644
--- a/protocols/WhatsAppWeb/src/server.cpp
+++ b/protocols/WhatsAppWeb/src/server.cpp
@@ -35,10 +35,87 @@ int WhatsAppProto::WSSend(const CMStringA &str, WA_PKT_HANDLER pHandler)
}
debugLogA("Sending packet #%d: %s", pktId, buf.c_str());
- WebSocket_Send(m_hServerConn, buf.c_str(), buf.GetLength());
+ WebSocket_SendText(m_hServerConn, buf.c_str());
return pktId;
}
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static char zeroData[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ {
+ char str[100];
+ _i64toa(_time64(0), str, 10);
+ node.addAttr("epoch", str);
+
+ CMStringA szText;
+ node.print(szText);
+ debugLogA("Sending binary node: %s", szText.c_str());
+ }
+
+ WAWriter writer;
+ writer.writeNode(&node);
+
+ // AES block size = 16 bytes, let's expand data to block size boundary
+ size_t rest = writer.body.length() % 16;
+ if (rest != 0)
+ writer.body.append(zeroData, 16 - rest);
+
+ BYTE iv[16];
+ Utils_GetRandom(iv, sizeof(iv));
+
+ // allocate the buffer of the same size + 32 bytes for temporary operations
+ MBinBuffer enc;
+ enc.assign(writer.body.data(), writer.body.length());
+ enc.append(mac_key.data(), mac_key.length());
+
+ int dec_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (BYTE*)enc_key.data(), iv);
+ EVP_EncryptUpdate(ctx, (BYTE*)enc.data(), &dec_len, (BYTE*)writer.body.data(), (int)writer.body.length());
+ EVP_EncryptFinal_ex(ctx, (BYTE*)enc.data() + dec_len, &final_len);
+ EVP_CIPHER_CTX_free(ctx);
+
+ // build the resulting buffer of the following structure:
+ // - packet prefix
+ // - 32 bytes of HMAC
+ // - 16 bytes of iv
+ // - rest of encoded data
+
+ BYTE hmac[32];
+ unsigned int hmac_len = 32;
+ HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.length(), (BYTE*)enc.data(), (int)enc.length(), hmac, &hmac_len);
+
+ int pktId = ++m_iPktNumber;
+ CMStringA prefix(FORMAT, "%d.--%d,", (int)m_iLoginTime, pktId);
+
+ if (pHandler != nullptr) {
+ auto *pReq = new WARequest;
+ pReq->issued = time(0);
+ pReq->pHandler = pHandler;
+ pReq->pktId = pktId;
+
+ mir_cslock lck(m_csPacketQueue);
+ m_arPacketQueue.insert(pReq);
+ }
+
+ MBinBuffer ret;
+ ret.append(prefix, prefix.GetLength());
+ ret.append(hmac, sizeof(hmac));
+ ret.append(iv, sizeof(iv));
+ ret.append(enc.data(), enc.length());
+ WebSocket_SendBinary(m_hServerConn, ret.data(), ret.length());
+
+ return pktId;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
void WhatsAppProto::OnLoggedIn()
{
debugLogA("CDiscordProto::OnLoggedIn");
@@ -64,7 +141,7 @@ void WhatsAppProto::OnLoggedOut(void)
void WhatsAppProto::SendKeepAlive()
{
- WebSocket_Send(m_hServerConn, "?,,", 3);
+ WebSocket_SendText(m_hServerConn, "?,,");
time_t now = time(0);
@@ -362,28 +439,20 @@ bool WhatsAppProto::ServerThreadWorker()
size_t dataSize = hdr.payloadSize - size_t(pos - start);
// try to decode
- if (hdr.opCode == 2 && hdr.payloadSize > 32) {
- MBinBuffer dest;
- if (!decryptBinaryMessage(dataSize, pos, dest)) {
- Netlib_Dump(m_hServerConn, currPacket.data(), currPacket.length(), false, 0);
- debugLogA("cannot decrypt incoming message");
- break;
- }
-
- // Netlib_Dump(m_hServerConn, dest.data(), dest.length(), false, 0);
- ProcessBinaryPacket(dest);
- }
+ if (hdr.opCode == 2 && hdr.payloadSize > 32)
+ ProcessBinaryPacket(pos, dataSize);
else {
CMStringA szJson(pos, (int)dataSize);
JSONNode root = JSONNode::parse(szJson);
if (root) {
- debugLogA("JSON received:\n%s", szJson.c_str());
+ debugLogA("JSON received:\n%s", start);
int sessId, pktId;
if (sscanf(start, "%d.--%d,", &sessId, &pktId) == 2) {
auto *pReq = m_arPacketQueue.find((WARequest *)&pktId);
if (pReq != nullptr) {
+ root << INT_PARAM("$id$", pktId);
(this->*pReq->pHandler)(root);
}
}
@@ -433,16 +502,26 @@ bool WhatsAppProto::ServerThreadWorker()
/////////////////////////////////////////////////////////////////////////////////////////
// Binary data processing
-void WhatsAppProto::ProcessBinaryPacket(const MBinBuffer &buf)
+void WhatsAppProto::ProcessBinaryPacket(const void *pData, size_t cbDataLen)
{
- WAReader reader(buf.data(), buf.length());
+ MBinBuffer dest;
+ if (!decryptBinaryMessage(cbDataLen, pData, dest)) {
+ debugLogA("cannot decrypt incoming message");
+ Netlib_Dump(m_hServerConn, pData, cbDataLen, false, 0);
+ return;
+ }
+
+ WAReader reader(dest.data(), dest.length());
WANode *pRoot = reader.readNode();
- if (pRoot == nullptr) // smth went wrong
+ if (pRoot == nullptr) { // smth went wrong
+ debugLogA("cannot read binary message");
+ Netlib_Dump(m_hServerConn, dest.data(), dest.length(), false, 0);
return;
+ }
CMStringA szText;
pRoot->print(szText);
- debugLogA("packed JSON: %s", szText.c_str());
+ debugLogA("packed content: %s", szText.c_str());
CMStringA szType = pRoot->getAttr("type");
if (szType == "contacts")
diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp
index 4631b32fcd..853c5ae383 100644
--- a/protocols/WhatsAppWeb/src/utils.cpp
+++ b/protocols/WhatsAppWeb/src/utils.cpp
@@ -148,6 +148,7 @@ void WANode::print(CMStringA &dest, int level) const
}
/////////////////////////////////////////////////////////////////////////////////////////
+// WAReader class members
bool WAReader::readAttributes(WANode *pNode, int count)
{
@@ -411,3 +412,182 @@ CMStringA WAReader::readStringFromChars(int size)
m_buf += size;
return ret;
}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAWriter class members
+
+void WAWriter::writeByte(uint8_t b)
+{
+ body.append(&b, 1);
+}
+
+void WAWriter::writeIntN(int value, int n)
+{
+ for (int i = n - 1; i >= 0; i--)
+ writeByte((value >> i * 8) & 0xFF);
+}
+
+void WAWriter::writeInt20(int value)
+{
+ writeByte((value >> 16) & 0xFF);
+ writeByte((value >> 8) & 0xFF);
+ writeByte(value & 0xFF);
+}
+
+void WAWriter::writeLength(int value)
+{
+ if (value >= (1 << 20)) {
+ writeByte(BINARY_32);
+ writeInt32(value);
+ }
+ else if (value >= 256) {
+ writeByte(BINARY_20);
+ writeInt20(value);
+ }
+ else {
+ writeByte(BINARY_8);
+ writeInt8(value);
+ }
+}
+
+void WAWriter::writeListSize(int length)
+{
+ if (length == 0)
+ writeByte(LIST_EMPTY);
+ else if (length < 256) {
+ writeByte(LIST_8);
+ writeInt8(length);
+ }
+ else {
+ writeByte(LIST_16);
+ writeInt16(length);
+ }
+}
+
+void WAWriter::writeNode(const WANode *pNode)
+{
+ int numAttrs = (int)pNode->attrs.size();
+ int hasContent = pNode->content.length() != 0;
+ writeListSize(2*numAttrs + 1 + hasContent);
+
+ writeString(pNode->title.c_str());
+
+ // write attributes
+ for (auto &it : pNode->attrs) {
+ if (it->value.IsEmpty())
+ continue;
+
+ writeString(it->name.c_str());
+ writeString(it->value.c_str());
+ }
+
+ // write contents
+ if (pNode->content.length()) {
+ writeLength((int)pNode->content.length());
+ body.append(pNode->content.data(), pNode->content.length());
+ }
+
+ // write children
+ if (pNode->children.size()) {
+ writeListSize((int)pNode->children.size());
+ for (auto &it : pNode->children)
+ writeNode(it);
+ }
+}
+
+void WAWriter::writeString(const char *str, bool bRaw)
+{
+ if (!bRaw && !mir_strcmp(str, "c.us")) {
+ writeToken("s.whatsapp.net");
+ return;
+ }
+
+ if (writeToken(str))
+ return;
+
+ auto *pszDelimiter = strchr(str, '@');
+ if (pszDelimiter) {
+ writeByte(JID_PAIR);
+
+ if (pszDelimiter == str) // empty jid
+ writeByte(LIST_EMPTY);
+ else
+ writePacked(CMStringA(str, int(pszDelimiter - str)));
+ }
+ else {
+ int len = (int)strlen(str);
+ writeLength(len);
+ body.append((void *)str, len);
+ }
+}
+
+bool WAWriter::writeToken(const char *str)
+{
+ for (auto &it : SingleByteTokens)
+ if (!strcmp(str, it)) {
+ writeByte(int(&it - SingleByteTokens));
+ return true;
+ }
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static BYTE packNibble(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ switch (c) {
+ case '-': return 10;
+ case '.': return 11;
+ case 0: return 15;
+ }
+
+ return -1;
+}
+
+static BYTE packHex(char c)
+{
+ if (c == 0)
+ return 15;
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a';
+
+ return -1;
+}
+
+static BYTE packPair(int type, char c1, char c2)
+{
+ BYTE b1 = (type == NIBBLE_8) ? packNibble(c1) : packHex(c1);
+ BYTE b2 = (type == NIBBLE_8) ? packNibble(c2) : packHex(c2);
+ return (b1 << 4) + b2;
+}
+
+void WAWriter::writePacked(const CMStringA &str)
+{
+ if (str.GetLength() > 254)
+ return;
+
+ // all symbols of str can be a nibble?
+ int type = (strspn(str, "0123456789-.") == str.GetLength()) ? NIBBLE_8 : HEX_8;
+
+ int len = str.GetLength() / 2;
+ BYTE firstByte = (len % 2) == 0 ? 0 : 0x80;
+ writeByte(firstByte | len);
+
+ const char *p = str;
+ for (int i = 0; i < len; i++, p += 2)
+ writeByte(packPair(type, p[0], p[1]));
+
+ if (firstByte != 0)
+ writeByte(packPair(type, p[0], 0));
+}
diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h
index c2a5d90ca3..5ed3a451e1 100644
--- a/protocols/WhatsAppWeb/src/utils.h
+++ b/protocols/WhatsAppWeb/src/utils.h
@@ -22,6 +22,8 @@ Copyright © 2019-21 George Hazan
class WANode // kinda XML
{
+ friend class WAWriter;
+
struct Attr
{
Attr(const char *pszName, const char *pszValue) :
@@ -55,12 +57,6 @@ class WAReader
uint32_t readIntN(int i);
CMStringA readStringFromChars(int size);
-public:
- WAReader(const void *buf, size_t cbLen) :
- m_buf((BYTE*)buf),
- m_limit((BYTE*)buf + cbLen)
- {}
-
__forceinline uint32_t readInt8() { return readIntN(1); }
__forceinline uint32_t readInt16() { return readIntN(2); }
__forceinline uint32_t readInt32() { return readIntN(4); }
@@ -71,5 +67,33 @@ public:
int readListSize(int tag);
CMStringA readPacked(int tag);
CMStringA readString(int tag);
- WANode* readNode();
+
+public:
+ WAReader(const void *buf, size_t cbLen) :
+ m_buf((BYTE*)buf),
+ m_limit((BYTE*)buf + cbLen)
+ {}
+
+ WANode* readNode();
+};
+
+class WAWriter
+{
+ __forceinline void writeInt8(int value) { writeIntN(value, 1); }
+ __forceinline void writeInt16(int value) { writeIntN(value, 2); }
+ __forceinline void writeInt32(int value) { writeIntN(value, 4); }
+
+ void writeByte(uint8_t b);
+ void writeIntN(int value, int i);
+ void writeInt20(int value);
+ void writeLength(int value);
+ void writeListSize(int tag);
+ void writePacked(const CMStringA &str);
+ void writeString(const char *str, bool bRaw = false);
+ bool writeToken(const char *str);
+
+public:
+ void writeNode(const WANode *pNode);
+
+ MBinBuffer body;
};