diff options
author | dartraiden <wowemuh@gmail.com> | 2023-01-02 21:10:29 +0300 |
---|---|---|
committer | dartraiden <wowemuh@gmail.com> | 2023-01-02 21:10:29 +0300 |
commit | 1979fd80424d16b2e489f9b57d01d9c7811d25a2 (patch) | |
tree | 960d42c5fe4a51f0fe2850bea91256e226bce221 /protocols/WhatsApp/src | |
parent | adfbbb217d4f4a05acf198755f219a5223d31c27 (diff) |
Update copyrights
Diffstat (limited to 'protocols/WhatsApp/src')
-rw-r--r-- | protocols/WhatsApp/src/appsync.cpp | 2 | ||||
-rw-r--r-- | protocols/WhatsApp/src/avatars.cpp | 338 | ||||
-rw-r--r-- | protocols/WhatsApp/src/chats.cpp | 2 | ||||
-rw-r--r-- | protocols/WhatsApp/src/crypt.cpp | 354 | ||||
-rw-r--r-- | protocols/WhatsApp/src/db.h | 86 | ||||
-rw-r--r-- | protocols/WhatsApp/src/dicts.h | 388 | ||||
-rw-r--r-- | protocols/WhatsApp/src/iq.cpp | 1140 | ||||
-rw-r--r-- | protocols/WhatsApp/src/main.cpp | 142 | ||||
-rw-r--r-- | protocols/WhatsApp/src/message.cpp | 1004 | ||||
-rw-r--r-- | protocols/WhatsApp/src/noise.cpp | 402 | ||||
-rw-r--r-- | protocols/WhatsApp/src/options.cpp | 190 | ||||
-rw-r--r-- | protocols/WhatsApp/src/proto.cpp | 2 | ||||
-rw-r--r-- | protocols/WhatsApp/src/proto.h | 2 | ||||
-rw-r--r-- | protocols/WhatsApp/src/qrcode.cpp | 274 | ||||
-rw-r--r-- | protocols/WhatsApp/src/server.cpp | 2 | ||||
-rw-r--r-- | protocols/WhatsApp/src/signal.cpp | 1576 | ||||
-rw-r--r-- | protocols/WhatsApp/src/stdafx.cxx | 16 | ||||
-rw-r--r-- | protocols/WhatsApp/src/stdafx.h | 144 | ||||
-rw-r--r-- | protocols/WhatsApp/src/utils.cpp | 1178 | ||||
-rw-r--r-- | protocols/WhatsApp/src/utils.h | 534 | ||||
-rw-r--r-- | protocols/WhatsApp/src/version.h | 2 | ||||
-rw-r--r-- | protocols/WhatsApp/src/wanode.cpp | 1216 |
22 files changed, 4497 insertions, 4497 deletions
diff --git a/protocols/WhatsApp/src/appsync.cpp b/protocols/WhatsApp/src/appsync.cpp index 42e911a3b7..d298f880d2 100644 --- a/protocols/WhatsApp/src/appsync.cpp +++ b/protocols/WhatsApp/src/appsync.cpp @@ -1,7 +1,7 @@ /* WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan +Copyright © 2019-23 George Hazan */ diff --git a/protocols/WhatsApp/src/avatars.cpp b/protocols/WhatsApp/src/avatars.cpp index d2eda4014a..d648bb3801 100644 --- a/protocols/WhatsApp/src/avatars.cpp +++ b/protocols/WhatsApp/src/avatars.cpp @@ -1,169 +1,169 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -void WhatsAppProto::OnIqGetAvatar(const WANode &node) -{ - auto *pUser = FindUser(node.getAttr("from")); - if (pUser == nullptr) - return; - - PROTO_AVATAR_INFORMATION ai = {}; - ai.hContact = pUser->hContact; - ai.format = PA_FORMAT_JPEG; - wcsncpy_s(ai.filename, GetAvatarFileName(pUser->hContact), _TRUNCATE); - - auto *pNode = node.getChild("picture"); - - DWORD dwLastChangeTime = pNode->getAttrInt("id"); - - CMStringA szUrl(pNode->getAttr("url")); - if (szUrl.IsEmpty()) { - setDword(pUser->hContact, DBKEY_AVATAR_TAG, 0); // avatar doesn't exist, don't check it later - -LBL_Error: - ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai)); - return; - } - - // if avatar was changed or not present at all, download it - if (dwLastChangeTime > getDword(pUser->hContact, DBKEY_AVATAR_TAG)) { - if (!g_plugin.SaveFile(szUrl, ai)) - goto LBL_Error; - - // set timestamp of avatar being saved - setDword(pUser->hContact, DBKEY_AVATAR_TAG, dwLastChangeTime); - } - ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai)); -} - -INT_PTR WhatsAppProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam) -{ - PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam; - - ptrA jid(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_ID)); - if (jid == NULL) - return GAIR_NOAVATAR; - - CMStringW tszFileName(GetAvatarFileName(pai->hContact)); - wcsncpy_s(pai->filename, tszFileName.c_str(), _TRUNCATE); - pai->format = PA_FORMAT_JPEG; - - DWORD dwTag = getDword(pai->hContact, DBKEY_AVATAR_TAG, -1); - if (dwTag == -1 || (wParam & GAIF_FORCE) != 0) - if (pai->hContact != NULL && isOnline()) { - ServerFetchAvatar(jid); - return GAIR_WAITFOR; - } - - debugLogA("No avatar"); - return GAIR_NOAVATAR; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR WhatsAppProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam) -{ - switch (wParam) { - case AF_PROPORTION: - return PIP_SQUARE; - - case AF_FORMATSUPPORTED: // Jabber supports avatars of virtually all formats - return PA_FORMAT_JPEG; - - case AF_ENABLED: - return TRUE; - - case AF_MAXSIZE: - POINT *size = (POINT*)lParam; - if (size) - size->x = size->y = 640; - return 0; - } - return -1; -} - -CMStringW WhatsAppProto::GetAvatarFileName(MCONTACT hContact) -{ - CMStringW result = m_tszAvatarFolder + L"\\"; - - CMStringA jid; - if (hContact != NULL) { - ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_ID)); - if (szId == NULL) - return L""; - - jid = szId; - } - else jid = m_szJid; - - return result + _A2T(jid.c_str()) + L".jpg"; -} - -INT_PTR WhatsAppProto::GetMyAvatar(WPARAM wParam, LPARAM lParam) -{ - std::wstring tszOwnAvatar(m_tszAvatarFolder + L"\\myavatar.jpg"); - wcsncpy_s((wchar_t*)wParam, lParam, tszOwnAvatar.c_str(), _TRUNCATE); - return 0; -} - -INT_PTR WhatsAppProto::SetMyAvatar(WPARAM, LPARAM) -{ - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::ServerFetchAvatar(const char *jid) -{ - WANodeIq iq(IQ::GET, "w:profile:picture", jid); - *iq.addChild("picture") << CHAR_PARAM("type", "preview") << CHAR_PARAM("query", "url"); - WSSendNode(iq, &WhatsAppProto::OnIqGetAvatar); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool CMPlugin::SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai) -{ - NETLIBHTTPREQUEST req = {}; - req.cbSize = sizeof(req); - req.flags = NLHRF_NODUMP | NLHRF_PERSISTENT | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT; - req.requestType = REQUEST_GET; - req.szUrl = (char*)pszUrl; - req.nlc = hAvatarConn; - - NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(hAvatarUser, &req); - if (pReply == nullptr) { - hAvatarConn = nullptr; - debugLogA("Failed to retrieve avatar from url: %s", pszUrl); - return false; - } - - hAvatarConn = pReply->nlc; - - bool bSuccess = false; - if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) { - if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type")) - ai.format = ProtoGetAvatarFormatByMimeType(pszHdr); - - if (ai.format != PA_FORMAT_UNKNOWN) { - FILE *fout = _wfopen(ai.filename, L"wb"); - if (fout) { - fwrite(pReply->pData, 1, pReply->dataLength, fout); - fclose(fout); - bSuccess = true; - } - else debugLogA("Error saving avatar to file %S", ai.filename); - } - else debugLogA("unknown avatar mime type"); - } - else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, pszUrl); - - Netlib_FreeHttpRequest(pReply); - return bSuccess; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::OnIqGetAvatar(const WANode &node)
+{
+ auto *pUser = FindUser(node.getAttr("from"));
+ if (pUser == nullptr)
+ return;
+
+ PROTO_AVATAR_INFORMATION ai = {};
+ ai.hContact = pUser->hContact;
+ ai.format = PA_FORMAT_JPEG;
+ wcsncpy_s(ai.filename, GetAvatarFileName(pUser->hContact), _TRUNCATE);
+
+ auto *pNode = node.getChild("picture");
+
+ DWORD dwLastChangeTime = pNode->getAttrInt("id");
+
+ CMStringA szUrl(pNode->getAttr("url"));
+ if (szUrl.IsEmpty()) {
+ setDword(pUser->hContact, DBKEY_AVATAR_TAG, 0); // avatar doesn't exist, don't check it later
+
+LBL_Error:
+ ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai));
+ return;
+ }
+
+ // if avatar was changed or not present at all, download it
+ if (dwLastChangeTime > getDword(pUser->hContact, DBKEY_AVATAR_TAG)) {
+ if (!g_plugin.SaveFile(szUrl, ai))
+ goto LBL_Error;
+
+ // set timestamp of avatar being saved
+ setDword(pUser->hContact, DBKEY_AVATAR_TAG, dwLastChangeTime);
+ }
+ ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai));
+}
+
+INT_PTR WhatsAppProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam;
+
+ ptrA jid(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_ID));
+ if (jid == NULL)
+ return GAIR_NOAVATAR;
+
+ CMStringW tszFileName(GetAvatarFileName(pai->hContact));
+ wcsncpy_s(pai->filename, tszFileName.c_str(), _TRUNCATE);
+ pai->format = PA_FORMAT_JPEG;
+
+ DWORD dwTag = getDword(pai->hContact, DBKEY_AVATAR_TAG, -1);
+ if (dwTag == -1 || (wParam & GAIF_FORCE) != 0)
+ if (pai->hContact != NULL && isOnline()) {
+ ServerFetchAvatar(jid);
+ return GAIR_WAITFOR;
+ }
+
+ debugLogA("No avatar");
+ return GAIR_NOAVATAR;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR WhatsAppProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
+{
+ switch (wParam) {
+ case AF_PROPORTION:
+ return PIP_SQUARE;
+
+ case AF_FORMATSUPPORTED: // Jabber supports avatars of virtually all formats
+ return PA_FORMAT_JPEG;
+
+ case AF_ENABLED:
+ return TRUE;
+
+ case AF_MAXSIZE:
+ POINT *size = (POINT*)lParam;
+ if (size)
+ size->x = size->y = 640;
+ return 0;
+ }
+ return -1;
+}
+
+CMStringW WhatsAppProto::GetAvatarFileName(MCONTACT hContact)
+{
+ CMStringW result = m_tszAvatarFolder + L"\\";
+
+ CMStringA jid;
+ if (hContact != NULL) {
+ ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_ID));
+ if (szId == NULL)
+ return L"";
+
+ jid = szId;
+ }
+ else jid = m_szJid;
+
+ return result + _A2T(jid.c_str()) + L".jpg";
+}
+
+INT_PTR WhatsAppProto::GetMyAvatar(WPARAM wParam, LPARAM lParam)
+{
+ std::wstring tszOwnAvatar(m_tszAvatarFolder + L"\\myavatar.jpg");
+ wcsncpy_s((wchar_t*)wParam, lParam, tszOwnAvatar.c_str(), _TRUNCATE);
+ return 0;
+}
+
+INT_PTR WhatsAppProto::SetMyAvatar(WPARAM, LPARAM)
+{
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ServerFetchAvatar(const char *jid)
+{
+ WANodeIq iq(IQ::GET, "w:profile:picture", jid);
+ *iq.addChild("picture") << CHAR_PARAM("type", "preview") << CHAR_PARAM("query", "url");
+ WSSendNode(iq, &WhatsAppProto::OnIqGetAvatar);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CMPlugin::SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai)
+{
+ NETLIBHTTPREQUEST req = {};
+ req.cbSize = sizeof(req);
+ req.flags = NLHRF_NODUMP | NLHRF_PERSISTENT | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ req.requestType = REQUEST_GET;
+ req.szUrl = (char*)pszUrl;
+ req.nlc = hAvatarConn;
+
+ NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(hAvatarUser, &req);
+ if (pReply == nullptr) {
+ hAvatarConn = nullptr;
+ debugLogA("Failed to retrieve avatar from url: %s", pszUrl);
+ return false;
+ }
+
+ hAvatarConn = pReply->nlc;
+
+ bool bSuccess = false;
+ if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
+ if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type"))
+ ai.format = ProtoGetAvatarFormatByMimeType(pszHdr);
+
+ if (ai.format != PA_FORMAT_UNKNOWN) {
+ FILE *fout = _wfopen(ai.filename, L"wb");
+ if (fout) {
+ fwrite(pReply->pData, 1, pReply->dataLength, fout);
+ fclose(fout);
+ bSuccess = true;
+ }
+ else debugLogA("Error saving avatar to file %S", ai.filename);
+ }
+ else debugLogA("unknown avatar mime type");
+ }
+ else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, pszUrl);
+
+ Netlib_FreeHttpRequest(pReply);
+ return bSuccess;
+}
diff --git a/protocols/WhatsApp/src/chats.cpp b/protocols/WhatsApp/src/chats.cpp index 5fbe0cf1ef..59f98d4548 100644 --- a/protocols/WhatsApp/src/chats.cpp +++ b/protocols/WhatsApp/src/chats.cpp @@ -1,7 +1,7 @@ /* WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan +Copyright © 2019-23 George Hazan */ diff --git a/protocols/WhatsApp/src/crypt.cpp b/protocols/WhatsApp/src/crypt.cpp index 20e8ddd1bd..049a75844c 100644 --- a/protocols/WhatsApp/src/crypt.cpp +++ b/protocols/WhatsApp/src/crypt.cpp @@ -1,177 +1,177 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -MBinBuffer aesDecrypt( - const EVP_CIPHER *cipher, - const uint8_t *key, - const uint8_t *iv, - const void *data, size_t dataLen, - const void *additionalData, size_t additionalLen) -{ - int tag_len = 0, dec_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv); - - if (additionalLen) - EVP_DecryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen); - - if (cipher == EVP_aes_256_gcm()) { - dataLen -= 16; - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen); - } - - MBinBuffer ret; - uint8_t outbuf[2000]; - for (size_t len = 0; len < dataLen; len += 1024) { - size_t portionSize = dataLen - len; - EVP_DecryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024)); - ret.append(outbuf, dec_len); - } - - EVP_DecryptFinal_ex(ctx, outbuf, &final_len); - if (final_len) - ret.append(outbuf, final_len); - - EVP_CIPHER_CTX_free(ctx); - return ret; -} - -MBinBuffer aesEncrypt( - const EVP_CIPHER *cipher, - const uint8_t *key, - const uint8_t *iv, - const void *data, size_t dataLen, - const void *additionalData, size_t additionalLen) -{ - int tag_len = 0, dec_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv); - - if (additionalLen) - EVP_EncryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen); - - if (cipher == EVP_aes_256_gcm()) { - dataLen -= 16; - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen); - } - - MBinBuffer ret; - uint8_t outbuf[2000]; - for (size_t len = 0; len < dataLen; len += 1024) { - size_t portionSize = dataLen - len; - EVP_EncryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024)); - ret.append(outbuf, dec_len); - } - - EVP_EncryptFinal_ex(ctx, outbuf, &final_len); - if (final_len) - ret.append(outbuf, final_len); - - EVP_CIPHER_CTX_free(ctx); - return ret; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static unsigned char *HKDF_Extract(const EVP_MD *evp_md, - const unsigned char *salt, size_t salt_len, - const unsigned char *key, size_t key_len, - unsigned char *prk, size_t *prk_len) -{ - unsigned int tmp_len; - - if (!HMAC(evp_md, salt, (int)salt_len, key, (int)key_len, prk, &tmp_len)) - return NULL; - - *prk_len = tmp_len; - return prk; -} - -static unsigned char *HKDF_Expand(const EVP_MD *evp_md, - const unsigned char *prk, size_t prk_len, - const unsigned char *info, size_t info_len, - unsigned char *okm, size_t okm_len) -{ - HMAC_CTX *hmac; - unsigned char *ret = NULL; - - unsigned int i; - - unsigned char prev[EVP_MAX_MD_SIZE]; - - size_t done_len = 0, dig_len = EVP_MD_size(evp_md); - - size_t n = okm_len / dig_len; - if (okm_len % dig_len) - n++; - - if (n > 255 || okm == NULL) - return NULL; - - if ((hmac = HMAC_CTX_new()) == NULL) - return NULL; - - if (!HMAC_Init_ex(hmac, prk, (int)prk_len, evp_md, NULL)) - goto err; - - for (i = 1; i <= n; i++) { - size_t copy_len; - const unsigned char ctr = i; - - if (i > 1) { - if (!HMAC_Init_ex(hmac, NULL, 0, NULL, NULL)) - goto err; - - if (!HMAC_Update(hmac, prev, dig_len)) - goto err; - } - - if (!HMAC_Update(hmac, info, info_len)) - goto err; - - if (!HMAC_Update(hmac, &ctr, 1)) - goto err; - - if (!HMAC_Final(hmac, prev, NULL)) - goto err; - - copy_len = (done_len + dig_len > okm_len) ? - okm_len - done_len : - dig_len; - - memcpy(okm + done_len, prev, copy_len); - - done_len += copy_len; - } - ret = okm; - -err: - OPENSSL_cleanse(prev, sizeof(prev)); - HMAC_CTX_free(hmac); - return ret; -} - -unsigned char *HKDF(const EVP_MD *evp_md, - const unsigned char *salt, size_t salt_len, - const unsigned char *key, size_t key_len, - const unsigned char *info, size_t info_len, - unsigned char *okm, size_t okm_len) -{ - unsigned char prk[EVP_MAX_MD_SIZE]; - unsigned char *ret; - size_t prk_len; - - if (!HKDF_Extract(evp_md, salt, salt_len, key, key_len, prk, &prk_len)) - return NULL; - - ret = HKDF_Expand(evp_md, prk, prk_len, info, info_len, okm, okm_len); - OPENSSL_cleanse(prk, sizeof(prk)); - - return ret; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+MBinBuffer aesDecrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData, size_t additionalLen)
+{
+ int tag_len = 0, dec_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv);
+
+ if (additionalLen)
+ EVP_DecryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen);
+
+ if (cipher == EVP_aes_256_gcm()) {
+ dataLen -= 16;
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen);
+ }
+
+ MBinBuffer ret;
+ uint8_t outbuf[2000];
+ for (size_t len = 0; len < dataLen; len += 1024) {
+ size_t portionSize = dataLen - len;
+ EVP_DecryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024));
+ ret.append(outbuf, dec_len);
+ }
+
+ EVP_DecryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ ret.append(outbuf, final_len);
+
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+MBinBuffer aesEncrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData, size_t additionalLen)
+{
+ int tag_len = 0, dec_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
+
+ if (additionalLen)
+ EVP_EncryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen);
+
+ if (cipher == EVP_aes_256_gcm()) {
+ dataLen -= 16;
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen);
+ }
+
+ MBinBuffer ret;
+ uint8_t outbuf[2000];
+ for (size_t len = 0; len < dataLen; len += 1024) {
+ size_t portionSize = dataLen - len;
+ EVP_EncryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024));
+ ret.append(outbuf, dec_len);
+ }
+
+ EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ ret.append(outbuf, final_len);
+
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static unsigned char *HKDF_Extract(const EVP_MD *evp_md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *key, size_t key_len,
+ unsigned char *prk, size_t *prk_len)
+{
+ unsigned int tmp_len;
+
+ if (!HMAC(evp_md, salt, (int)salt_len, key, (int)key_len, prk, &tmp_len))
+ return NULL;
+
+ *prk_len = tmp_len;
+ return prk;
+}
+
+static unsigned char *HKDF_Expand(const EVP_MD *evp_md,
+ const unsigned char *prk, size_t prk_len,
+ const unsigned char *info, size_t info_len,
+ unsigned char *okm, size_t okm_len)
+{
+ HMAC_CTX *hmac;
+ unsigned char *ret = NULL;
+
+ unsigned int i;
+
+ unsigned char prev[EVP_MAX_MD_SIZE];
+
+ size_t done_len = 0, dig_len = EVP_MD_size(evp_md);
+
+ size_t n = okm_len / dig_len;
+ if (okm_len % dig_len)
+ n++;
+
+ if (n > 255 || okm == NULL)
+ return NULL;
+
+ if ((hmac = HMAC_CTX_new()) == NULL)
+ return NULL;
+
+ if (!HMAC_Init_ex(hmac, prk, (int)prk_len, evp_md, NULL))
+ goto err;
+
+ for (i = 1; i <= n; i++) {
+ size_t copy_len;
+ const unsigned char ctr = i;
+
+ if (i > 1) {
+ if (!HMAC_Init_ex(hmac, NULL, 0, NULL, NULL))
+ goto err;
+
+ if (!HMAC_Update(hmac, prev, dig_len))
+ goto err;
+ }
+
+ if (!HMAC_Update(hmac, info, info_len))
+ goto err;
+
+ if (!HMAC_Update(hmac, &ctr, 1))
+ goto err;
+
+ if (!HMAC_Final(hmac, prev, NULL))
+ goto err;
+
+ copy_len = (done_len + dig_len > okm_len) ?
+ okm_len - done_len :
+ dig_len;
+
+ memcpy(okm + done_len, prev, copy_len);
+
+ done_len += copy_len;
+ }
+ ret = okm;
+
+err:
+ OPENSSL_cleanse(prev, sizeof(prev));
+ HMAC_CTX_free(hmac);
+ return ret;
+}
+
+unsigned char *HKDF(const EVP_MD *evp_md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *key, size_t key_len,
+ const unsigned char *info, size_t info_len,
+ unsigned char *okm, size_t okm_len)
+{
+ unsigned char prk[EVP_MAX_MD_SIZE];
+ unsigned char *ret;
+ size_t prk_len;
+
+ if (!HKDF_Extract(evp_md, salt, salt_len, key, key_len, prk, &prk_len))
+ return NULL;
+
+ ret = HKDF_Expand(evp_md, prk, prk_len, info, info_len, okm, okm_len);
+ OPENSSL_cleanse(prk, sizeof(prk));
+
+ return ret;
+}
diff --git a/protocols/WhatsApp/src/db.h b/protocols/WhatsApp/src/db.h index cb562715f4..1e4bebc6a8 100644 --- a/protocols/WhatsApp/src/db.h +++ b/protocols/WhatsApp/src/db.h @@ -1,43 +1,43 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#define MODULENAME "WhatsApp" - -// DB keys -#define DBKEY_ID "ID" -#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" - -#define DBKEY_SIGNED_IDENTITY_PUB "SignedIdentityPublicKey" -#define DBKEY_SIGNED_IDENTITY_PRIV "SignedIdentityPrivateKey" - -#define DBKEY_PREKEY "SignedPreKey1" -#define DBKEY_PREKEY_NEXT_ID "PrekeyNextId" -#define DBKEY_PREKEY_UPLOAD_ID "PrekeyUploadId" - -#define DBKEY_REG_ID "RegistrationId" -#define DBKEY_SECRET_KEY "AdvSecretKey" - -#define DBKEY_NICK "Nick" -#define DBKEY_DEF_GROUP "DefaultGroup" -#define DBKEY_AUTORUNCHATS "AutoRunChats" -#define DBKEY_AVATAR_TAG "AvatarTag" - -#define DBKEY_EVENT_CLIENT_COLBACK "PopupClientColorBack" -#define DBKEY_EVENT_CLIENT_COLTEXT "PopupClientColorText" -#define DBKEY_EVENT_CLIENT_TIMEOUT "PopupClientTimeout" -#define DBKEY_EVENT_CLIENT_DEFAULT "PopupClientColorDefault" - -#define DBKEY_EVENT_OTHER_COLBACK "PopupOtherColorBack" -#define DBKEY_EVENT_OTHER_COLTEXT "PopupOtherColorText" -#define DBKEY_EVENT_OTHER_TIMEOUT "PopupOtherTimeout" -#define DBKEY_EVENT_OTHER_DEFAULT "PopupOtherColorDefault" - +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#define MODULENAME "WhatsApp"
+
+// DB keys
+#define DBKEY_ID "ID"
+#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"
+
+#define DBKEY_SIGNED_IDENTITY_PUB "SignedIdentityPublicKey"
+#define DBKEY_SIGNED_IDENTITY_PRIV "SignedIdentityPrivateKey"
+
+#define DBKEY_PREKEY "SignedPreKey1"
+#define DBKEY_PREKEY_NEXT_ID "PrekeyNextId"
+#define DBKEY_PREKEY_UPLOAD_ID "PrekeyUploadId"
+
+#define DBKEY_REG_ID "RegistrationId"
+#define DBKEY_SECRET_KEY "AdvSecretKey"
+
+#define DBKEY_NICK "Nick"
+#define DBKEY_DEF_GROUP "DefaultGroup"
+#define DBKEY_AUTORUNCHATS "AutoRunChats"
+#define DBKEY_AVATAR_TAG "AvatarTag"
+
+#define DBKEY_EVENT_CLIENT_COLBACK "PopupClientColorBack"
+#define DBKEY_EVENT_CLIENT_COLTEXT "PopupClientColorText"
+#define DBKEY_EVENT_CLIENT_TIMEOUT "PopupClientTimeout"
+#define DBKEY_EVENT_CLIENT_DEFAULT "PopupClientColorDefault"
+
+#define DBKEY_EVENT_OTHER_COLBACK "PopupOtherColorBack"
+#define DBKEY_EVENT_OTHER_COLTEXT "PopupOtherColorText"
+#define DBKEY_EVENT_OTHER_TIMEOUT "PopupOtherTimeout"
+#define DBKEY_EVENT_OTHER_DEFAULT "PopupOtherColorDefault"
+
diff --git a/protocols/WhatsApp/src/dicts.h b/protocols/WhatsApp/src/dicts.h index 7b84487d14..ab37f10abb 100644 --- a/protocols/WhatsApp/src/dicts.h +++ b/protocols/WhatsApp/src/dicts.h @@ -1,194 +1,194 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -static char *SingleByteTokens[] = { - "", "xmlstreamstart", "xmlstreamend", "s.whatsapp.net", "type", "participant", "from", "receipt", "id", "broadcast", "status", "message", "notification", "notify", "to", "jid", "user", - "class", "offline", "g.us", "result", "mediatype", "enc", "skmsg", "off_cnt", "xmlns", "presence", "participants", "ack", "t", "iq", "device_hash", "read", "value", "media", "picture", - "chatstate", "unavailable", "text", "urn:xmpp:whatsapp:push", "devices", "verified_name", "contact", "composing", "edge_routing", "routing_info", "item", "image", "verified_level", - "get", "fallback_hostname", "2", "media_conn", "1", "v", "handshake", "fallback_class", "count", "config", "offline_preview", "download_buckets", "w:profile:picture", "set", - "creation", "location", "fallback_ip4", "msg", "urn:xmpp:ping", "fallback_ip6", "call-creator", "relaylatency", "success", "subscribe", "video", "business_hours_config", - "platform", "hostname", "version", "unknown", "0", "ping", "hash", "edit", "subject", "max_buckets", "download", "delivery", "props", "sticker", "name", "last", "contacts", - "business", "primary", "preview", "w:p", "pkmsg", "call-id", "retry", "prop", "call", "auth_ttl", "available", "relay_id", "last_id", "day_of_week", "w", "host", "seen", - "bits", "list", "atn", "upload", "is_new", "w:stats", "key", "paused", "specific_hours", "multicast", "stream:error", "mmg.whatsapp.net", "code", "deny", "played", "profile", - "fna", "device-list", "close_time", "latency", "gcm", "pop", "audio", "26", "w:web", "open_time", "error", "auth", "ip4", "update", "profile_options", "config_value", "category", - "catalog_not_created", "00", "config_code", "mode", "catalog_status", "ip6", "blocklist", "registration", "7", "web", "fail", "w:m", "cart_enabled", "ttl", "gif", "300", - "device_orientation", "identity", "query", "401", "media-gig2-1.cdn.whatsapp.net", "in", "3", "te2", "add", "fallback", "categories", "ptt", "encrypt", "notice", - "thumbnail-document", "item-not-found", "12", "thumbnail-image", "stage", "thumbnail-link", "usync", "out", "thumbnail-video", "8", "01", "context", "sidelist", - "thumbnail-gif", "terminate", "not-authorized", "orientation", "dhash", "capability", "side_list", "md-app-state", "description", "serial", "readreceipts", "te", - "business_hours", "md-msg-hist", "tag", "attribute_padding", "document", "open_24h", "delete", "expiration", "active", "prev_v_id", "true", "passive", "index", "4", - "conflict", "remove", "w:gp2", "config_expo_key", "screen_height", "replaced", "02", "screen_width", "uploadfieldstat", "2:47DEQpj8", "media-bog1-1.cdn.whatsapp.net", - "encopt", "url", "catalog_exists", "keygen", "rate", "offer", "opus", "media-mia3-1.cdn.whatsapp.net", "privacy", "media-mia3-2.cdn.whatsapp.net", "signature", - "preaccept", "token_id", "media-eze1-1.cdn.whatsapp.net" -}; - -static char *dict0[] = { - "media-for1-1.cdn.whatsapp.net", "relay", "media-gru2-2.cdn.whatsapp.net", "uncompressed", "medium", "voip_settings", "device", "reason", - "media-lim1-1.cdn.whatsapp.net", "media-qro1-2.cdn.whatsapp.net", "media-gru1-2.cdn.whatsapp.net", "action", "features", "media-gru2-1.cdn.whatsapp.net", - "media-gru1-1.cdn.whatsapp.net", "media-otp1-1.cdn.whatsapp.net", "kyc-id", "priority", "phash", "mute", "token", "100", "media-qro1-1.cdn.whatsapp.net", - "none", "media-mrs2-2.cdn.whatsapp.net", "sign_credential", "03", "media-mrs2-1.cdn.whatsapp.net", "protocol", "timezone", "transport", "eph_setting", "1080", - "original_dimensions", "media-frx5-1.cdn.whatsapp.net", "background", "disable", "original_image_url", "5", "transaction-id", "direct_path", "103", "appointment_only", - "request_image_url", "peer_pid", "address", "105", "104", "102", "media-cdt1-1.cdn.whatsapp.net", "101", "109", "110", "106", "background_location", "v_id", "sync", - "status-old", "111", "107", "ppic", "media-scl2-1.cdn.whatsapp.net", "business_profile", "108", "invite", "04", "audio_duration", "media-mct1-1.cdn.whatsapp.net", - "media-cdg2-1.cdn.whatsapp.net", "media-los2-1.cdn.whatsapp.net", "invis", "net", "voip_payload_type", "status-revoke-delay", "404", "state", "use_correct_order_for_hmac_sha1", - "ver", "media-mad1-1.cdn.whatsapp.net", "order", "540", "skey", "blinded_credential", "android", "contact_remove", "enable_downlink_relay_latency_only", "duration", - "enable_vid_one_way_codec_nego", "6", "media-sof1-1.cdn.whatsapp.net", "accept", "all", "signed_credential", "media-atl3-1.cdn.whatsapp.net", "media-lhr8-1.cdn.whatsapp.net", - "website", "05", "latitude", "media-dfw5-1.cdn.whatsapp.net", "forbidden", "enable_audio_piggyback_network_mtu_fix", "media-dfw5-2.cdn.whatsapp.net", "note.m4r", - "media-atl3-2.cdn.whatsapp.net", "jb_nack_discard_count_fix", "longitude", "Opening.m4r", "media-arn2-1.cdn.whatsapp.net", "email", "timestamp", "admin", - "media-pmo1-1.cdn.whatsapp.net", "America/Sao_Paulo", "contact_add", "media-sin6-1.cdn.whatsapp.net", "interactive", "8000", "acs_public_key", - "sigquit_anr_detector_release_rollover_percent", "media.fmed1-2.fna.whatsapp.net", "groupadd", "enabled_for_video_upgrade", "latency_update_threshold", - "media-frt3-2.cdn.whatsapp.net", "calls_row_constraint_layout", "media.fgbb2-1.fna.whatsapp.net", "mms4_media_retry_notification_encryption_enabled", "timeout", - "media-sin6-3.cdn.whatsapp.net", "audio_nack_jitter_multiplier", "jb_discard_count_adjust_pct_rc", "audio_reserve_bps", "delta", "account_sync", "default", - "media.fjed4-6.fna.whatsapp.net", "06", "lock_video_orientation", "media-frt3-1.cdn.whatsapp.net", "w:g2", "media-sin6-2.cdn.whatsapp.net", "audio_nack_algo_mask", - "media.fgbb2-2.fna.whatsapp.net", "media.fmed1-1.fna.whatsapp.net", "cond_range_target_bitrate", "mms4_server_error_receipt_encryption_enabled", "vid_rc_dyn", "fri", - "cart_v1_1_order_message_changes_enabled", "reg_push", "jb_hist_deposit_value", "privatestats", "media.fist7-2.fna.whatsapp.net", "thu", "jb_discard_count_adjust_pct", - "mon", "group_call_video_maximization_enabled", "mms_cat_v1_forward_hot_override_enabled", "audio_nack_new_rtt", "media.fsub2-3.fna.whatsapp.net", - "media_upload_aggressive_retry_exponential_backoff_enabled", "tue", "wed", "media.fruh4-2.fna.whatsapp.net", "audio_nack_max_seq_req", "max_rtp_audio_packet_resends", - "jb_hist_max_cdf_value", "07", "audio_nack_max_jb_delay", "mms_forward_partially_downloaded_video", "media-lcy1-1.cdn.whatsapp.net", "resume", "jb_inband_fec_aware", - "new_commerce_entry_point_enabled", "480", "payments_upi_generate_qr_amount_limit", "sigquit_anr_detector_rollover_percent", "media.fsdu2-1.fna.whatsapp.net", "fbns", - "aud_pkt_reorder_pct", "dec", "stop_probing_before_accept_send", "media_upload_max_aggressive_retries", "edit_business_profile_new_mode_enabled", - "media.fhex4-1.fna.whatsapp.net", "media.fjed4-3.fna.whatsapp.net", "sigquit_anr_detector_64bit_rollover_percent", "cond_range_ema_jb_last_delay", - "watls_enable_early_data_http_get", "media.fsdu2-2.fna.whatsapp.net", "message_qr_disambiguation_enabled", "media-mxp1-1.cdn.whatsapp.net", "sat", "vertical", - "media.fruh4-5.fna.whatsapp.net", "200", "media-sof1-2.cdn.whatsapp.net", "-1", "height", "product_catalog_hide_show_items_enabled", "deep_copy_frm_last", - "tsoffline", "vp8/h.264", "media.fgye5-3.fna.whatsapp.net", "media.ftuc1-2.fna.whatsapp.net", "smb_upsell_chat_banner_enabled", "canonical", "08", "9", ".", - "media.fgyd4-4.fna.whatsapp.net", "media.fsti4-1.fna.whatsapp.net", "mms_vcache_aggregation_enabled", "mms_hot_content_timespan_in_seconds", "nse_ver", "rte", - "third_party_sticker_web_sync", "cond_range_target_total_bitrate", "media_upload_aggressive_retry_enabled", "instrument_spam_report_enabled", "disable_reconnect_tone", - "move_media_folder_from_sister_app", "one_tap_calling_in_group_chat_size", "10", "storage_mgmt_banner_threshold_mb", "enable_backup_passive_mode", "sharechat_inline_player_enabled", - "media.fcnq2-1.fna.whatsapp.net", "media.fhex4-2.fna.whatsapp.net", "media.fist6-3.fna.whatsapp.net", "ephemeral_drop_column_stage", "reconnecting_after_network_change_threshold_ms", - "media-lhr8-2.cdn.whatsapp.net", "cond_jb_last_delay_ema_alpha", "entry_point_block_logging_enabled", "critical_event_upload_log_config", "respect_initial_bitrate_estimate", - "smaller_image_thumbs_status_enabled", "media.fbtz1-4.fna.whatsapp.net", "media.fjed4-1.fna.whatsapp.net", "width", "720", "enable_frame_dropper", "enable_one_side_mode", - "urn:xmpp:whatsapp:dirty", "new_sticker_animation_behavior_v2", "media.flim3-2.fna.whatsapp.net", "media.fuio6-2.fna.whatsapp.net", "skip_forced_signaling", "dleq_proof", - "status_video_max_bitrate", "lazy_send_probing_req", "enhanced_storage_management", "android_privatestats_endpoint_dit_enabled", "media.fscl13-2.fna.whatsapp.net", "video_duration" -}; - -static char *dict1[] = { - "group_call_discoverability_enabled", "media.faep9-2.fna.whatsapp.net", "msgr", "bloks_loggedin_access_app_id", "db_status_migration_step", "watls_prefer_ip6", - "jabber:iq:privacy", "68", "media.fsaw1-11.fna.whatsapp.net", "mms4_media_conn_persist_enabled", "animated_stickers_thread_clean_up", "media.fcgk3-2.fna.whatsapp.net", - "media.fcgk4-6.fna.whatsapp.net", "media.fgye5-2.fna.whatsapp.net", "media.flpb1-1.fna.whatsapp.net", "media.fsub2-1.fna.whatsapp.net", "media.fuio6-3.fna.whatsapp.net", - "not-allowed", "partial_pjpeg_bw_threshold", "cap_estimated_bitrate", "mms_chatd_resume_check_over_thrift", "smb_upsell_business_profile_enabled", "product_catalog_webclient", - "groups", "sigquit_anr_detector_release_updated_rollout", "syncd_key_rotation_enabled", "media.fdmm2-1.fna.whatsapp.net", "media-hou1-1.cdn.whatsapp.net", - "remove_old_chat_notifications", "smb_biztools_deeplink_enabled", "use_downloadable_filters_int", "group_qr_codes_enabled", "max_receipt_processing_time", - "optimistic_image_processing_enabled", "smaller_video_thumbs_status_enabled", "watls_early_data", "reconnecting_before_relay_failover_threshold_ms", "cond_range_packet_loss_pct", - "groups_privacy_blacklist", "status-revoke-drop", "stickers_animated_thumbnail_download", "dedupe_transcode_shared_images", "dedupe_transcode_shared_videos", - "media.fcnq2-2.fna.whatsapp.net", "media.fgyd4-1.fna.whatsapp.net", "media.fist7-1.fna.whatsapp.net", "media.flim3-3.fna.whatsapp.net", "add_contact_by_qr_enabled", - "https://faq.whatsapp.com/payments", "multicast_limit_global", "sticker_notification_preview", "smb_better_catalog_list_adapters_enabled", "bloks_use_minscript_android", - "pen_smoothing_enabled", "media.fcgk4-5.fna.whatsapp.net", "media.fevn1-3.fna.whatsapp.net", "media.fpoj7-1.fna.whatsapp.net", "media-arn2-2.cdn.whatsapp.net", - "reconnecting_before_network_change_threshold_ms", "android_media_use_fresco_for_gifs", "cond_in_congestion", "status_image_max_edge", "sticker_search_enabled", - "starred_stickers_web_sync", "db_blank_me_jid_migration_step", "media.fist6-2.fna.whatsapp.net", "media.ftuc1-1.fna.whatsapp.net", "09", "anr_fast_logs_upload_rollout", - "camera_core_integration_enabled", "11", "third_party_sticker_caching", "thread_dump_contact_support", "wam_privatestats_enabled", "vcard_as_document_size_kb", "maxfpp", - "fbip", "ephemeral_allow_group_members", "media-bom1-2.cdn.whatsapp.net", "media-xsp1-1.cdn.whatsapp.net", "disable_prewarm", "frequently_forwarded_max", - "media.fbtz1-5.fna.whatsapp.net", "media.fevn7-1.fna.whatsapp.net", "media.fgyd4-2.fna.whatsapp.net", "sticker_tray_animation_fully_visible_items", - "green_alert_banner_duration", "reconnecting_after_p2p_failover_threshold_ms", "connected", "share_biz_vcard_enabled", "stickers_animation", "0a", "1200", "WhatsApp", - "group_description_length", "p_v_id", "payments_upi_intent_transaction_limit", "frequently_forwarded_messages", "media-xsp1-2.cdn.whatsapp.net", - "media.faep8-1.fna.whatsapp.net", "media.faep8-2.fna.whatsapp.net", "media.faep9-1.fna.whatsapp.net", "media.fdmm2-2.fna.whatsapp.net", "media.fgzt3-1.fna.whatsapp.net", - "media.flim4-2.fna.whatsapp.net", "media.frao1-1.fna.whatsapp.net", "media.fscl9-2.fna.whatsapp.net", "media.fsub2-2.fna.whatsapp.net", "superadmin", - "media.fbog10-1.fna.whatsapp.net", "media.fcgh28-1.fna.whatsapp.net", "media.fjdo10-1.fna.whatsapp.net", "third_party_animated_sticker_import", "delay_fec", - "attachment_picker_refresh", "android_linked_devices_re_auth_enabled", "rc_dyn", "green_alert_block_jitter", "add_contact_logging_enabled", "biz_message_logging_enabled", - "conversation_media_preview_v2", "media-jnb1-1.cdn.whatsapp.net", "ab_key", "media.fcgk4-2.fna.whatsapp.net", "media.fevn1-1.fna.whatsapp.net", "media.fist6-1.fna.whatsapp.net", - "media.fruh4-4.fna.whatsapp.net", "media.fsti4-2.fna.whatsapp.net", "mms_vcard_autodownload_size_kb", "watls_enabled", "notif_ch_override_off", "media.fsaw1-14.fna.whatsapp.net", - "media.fscl13-1.fna.whatsapp.net", "db_group_participant_migration_step", "1020", "cond_range_sterm_rtt", "invites_logging_enabled", "triggered_block_enabled", - "group_call_max_participants", "media-iad3-1.cdn.whatsapp.net", "product_catalog_open_deeplink", "shops_required_tos_version", "image_max_kbytes", - "cond_low_quality_vid_mode", "db_receipt_migration_step", "jb_early_prob_hist_shrink", "media.fdmm2-3.fna.whatsapp.net", "media.fdmm2-4.fna.whatsapp.net", - "media.fruh4-1.fna.whatsapp.net", "media.fsaw2-2.fna.whatsapp.net", "remove_geolocation_videos", "new_animation_behavior", "fieldstats_beacon_chance", "403", - "authkey_reset_on_ban", "continuous_ptt_playback", "reconnecting_after_relay_failover_threshold_ms", "false", "group", "sun", "conversation_swipe_to_reply", - "ephemeral_messages_setting", "smaller_video_thumbs_enabled", "md_device_sync_enabled", "bloks_shops_pdp_url_regex", "lasso_integration_enabled", - "media-bom1-1.cdn.whatsapp.net", "new_backup_format_enabled", "256", "media.faep6-1.fna.whatsapp.net", "media.fasr1-1.fna.whatsapp.net", "media.fbtz1-7.fna.whatsapp.net", - "media.fesb4-1.fna.whatsapp.net", "media.fjdo1-2.fna.whatsapp.net", "media.frba2-1.fna.whatsapp.net", "watls_no_dns", "600", "db_broadcast_me_jid_migration_step", - "new_wam_runtime_enabled", "group_update", "enhanced_block_enabled", "sync_wifi_threshold_kb", "mms_download_nc_cat", "bloks_minification_enabled", - "ephemeral_messages_enabled", "reject", "voip_outgoing_xml_signaling", "creator", "dl_bw", "payments_request_messages", "target_bitrate", "bloks_rendercore_enabled", - "media-hbe1-1.cdn.whatsapp.net", "media-hel3-1.cdn.whatsapp.net", "media-kut2-2.cdn.whatsapp.net", "media-lax3-1.cdn.whatsapp.net", "media-lax3-2.cdn.whatsapp.net", - "sticker_pack_deeplink_enabled", "hq_image_bw_threshold", "status_info", "voip", "dedupe_transcode_videos", "grp_uii_cleanup", "linked_device_max_count", - "media.flim1-1.fna.whatsapp.net", "media.fsaw2-1.fna.whatsapp.net", "reconnecting_after_call_active_threshold_ms", "1140", "catalog_pdp_new_design", - "media.fbtz1-10.fna.whatsapp.net", "media.fsaw1-15.fna.whatsapp.net", "0b", "consumer_rc_provider", "mms_async_fast_forward_ttl", "jb_eff_size_fix", - "voip_incoming_xml_signaling", "media_provider_share_by_uuid", "suspicious_links", "dedupe_transcode_images", "green_alert_modal_start", "media-cgk1-1.cdn.whatsapp.net", - "media-lga3-1.cdn.whatsapp.net", "template_doc_mime_types", "important_messages", "user_add", "vcard_max_size_kb", "media.fada2-1.fna.whatsapp.net", - "media.fbog2-5.fna.whatsapp.net", "media.fbtz1-3.fna.whatsapp.net", "media.fcgk3-1.fna.whatsapp.net", "media.fcgk7-1.fna.whatsapp.net", "media.flim1-3.fna.whatsapp.net", - "media.fscl9-1.fna.whatsapp.net", "ctwa_context_enterprise_enabled", "media.fsaw1-13.fna.whatsapp.net", "media.fuio11-2.fna.whatsapp.net", "status_collapse_muted", - "db_migration_level_force", "recent_stickers_web_sync", "bloks_session_state", "bloks_shops_enabled", "green_alert_setting_deep_links_enabled", "restrict_groups", - "battery", "green_alert_block_start", "refresh", "ctwa_context_enabled", "md_messaging_enabled", "status_image_quality", "md_blocklist_v2_server", - "media-del1-1.cdn.whatsapp.net", "13", "userrate", "a_v_id", "cond_rtt_ema_alpha", "invalid" -}; - -static char *dict2[] = { - "media.fada1-1.fna.whatsapp.net", "media.fadb3-2.fna.whatsapp.net", "media.fbhz2-1.fna.whatsapp.net", "media.fcor2-1.fna.whatsapp.net", "media.fjed4-2.fna.whatsapp.net", - "media.flhe4-1.fna.whatsapp.net", "media.frak1-2.fna.whatsapp.net", "media.fsub6-3.fna.whatsapp.net", "media.fsub6-7.fna.whatsapp.net", "media.fvvi1-1.fna.whatsapp.net", - "search_v5_eligible", "wam_real_time_enabled", "report_disk_event", "max_tx_rott_based_bitrate", "product", "media.fjdo10-2.fna.whatsapp.net", "video_frame_crc_sample_interval", - "media_max_autodownload", "15", "h.264", "wam_privatestats_buffer_count", "md_phash_v2_enabled", "account_transfer_enabled", "business_product_catalog", - "enable_non_dyn_codec_param_fix", "is_user_under_epd_jurisdiction", "media.fbog2-4.fna.whatsapp.net", "media.fbtz1-2.fna.whatsapp.net", "media.fcfc1-1.fna.whatsapp.net", - "media.fjed4-5.fna.whatsapp.net", "media.flhe4-2.fna.whatsapp.net", "media.flim1-2.fna.whatsapp.net", "media.flos5-1.fna.whatsapp.net", "android_key_store_auth_ver", - "010", "anr_process_monitor", "delete_old_auth_key", "media.fcor10-3.fna.whatsapp.net", "storage_usage_enabled", "android_camera2_support_level", "dirty", - "consumer_content_provider", "status_video_max_duration", "0c", "bloks_cache_enabled", "media.fadb2-2.fna.whatsapp.net", "media.fbko1-1.fna.whatsapp.net", - "media.fbtz1-9.fna.whatsapp.net", "media.fcgk4-4.fna.whatsapp.net", "media.fesb4-2.fna.whatsapp.net", "media.fevn1-2.fna.whatsapp.net", "media.fist2-4.fna.whatsapp.net", - "media.fjdo1-1.fna.whatsapp.net", "media.fruh4-6.fna.whatsapp.net", "media.fsrg5-1.fna.whatsapp.net", "media.fsub6-6.fna.whatsapp.net", "minfpp", "5000", "locales", - "video_max_bitrate", "use_new_auth_key", "bloks_http_enabled", "heartbeat_interval", "media.fbog11-1.fna.whatsapp.net", "ephemeral_group_query_ts", "fec_nack", - "search_in_storage_usage", "c", "media-amt2-1.cdn.whatsapp.net", "linked_devices_ui_enabled", "14", "async_data_load_on_startup", "voip_incoming_xml_ack", "16", - "db_migration_step", "init_bwe", "max_participants", "wam_buffer_count", "media.fada2-2.fna.whatsapp.net", "media.fadb3-1.fna.whatsapp.net", "media.fcor2-2.fna.whatsapp.net", - "media.fdiy1-2.fna.whatsapp.net", "media.frba3-2.fna.whatsapp.net", "media.fsaw2-3.fna.whatsapp.net", "1280", "status_grid_enabled", "w:biz", "product_catalog_deeplink", - "media.fgye10-2.fna.whatsapp.net", "media.fuio11-1.fna.whatsapp.net", "optimistic_upload", "work_manager_init", "lc", "catalog_message", "cond_net_medium", - "enable_periodical_aud_rr_processing", "cond_range_ema_rtt", "media-tir2-1.cdn.whatsapp.net", "frame_ms", "group_invite_sending", "payments_web_enabled", - "wallpapers_v2", "0d", "browser", "hq_image_max_edge", "image_edit_zoom", "linked_devices_re_auth_enabled", "media.faly3-2.fna.whatsapp.net", - "media.fdoh5-3.fna.whatsapp.net", "media.fesb3-1.fna.whatsapp.net", "media.fknu1-1.fna.whatsapp.net", "media.fmex3-1.fna.whatsapp.net", "media.fruh4-3.fna.whatsapp.net", - "255", "web_upgrade_to_md_modal", "audio_piggyback_timeout_msec", "enable_audio_oob_fec_feature", "from_ip", "image_max_edge", "message_qr_enabled", "powersave", - "receipt_pre_acking", "video_max_edge", "full", "011", "012", "enable_audio_oob_fec_for_sender", "md_voip_enabled", "enable_privatestats", "max_fec_ratio", - "payments_cs_faq_url", "media-xsp1-3.cdn.whatsapp.net", "hq_image_quality", "media.fasr1-2.fna.whatsapp.net", "media.fbog3-1.fna.whatsapp.net", - "media.ffjr1-6.fna.whatsapp.net", "media.fist2-3.fna.whatsapp.net", "media.flim4-3.fna.whatsapp.net", "media.fpbc2-4.fna.whatsapp.net", "media.fpku1-1.fna.whatsapp.net", - "media.frba1-1.fna.whatsapp.net", "media.fudi1-1.fna.whatsapp.net", "media.fvvi1-2.fna.whatsapp.net", "gcm_fg_service", "enable_dec_ltr_size_check", "clear", "lg", - "media.fgru11-1.fna.whatsapp.net", "18", "media-lga3-2.cdn.whatsapp.net", "pkey", "0e", "max_subject", "cond_range_lterm_rtt", "announcement_groups", "biz_profile_options", - "s_t", "media.fabv2-1.fna.whatsapp.net", "media.fcai3-1.fna.whatsapp.net", "media.fcgh1-1.fna.whatsapp.net", "media.fctg1-4.fna.whatsapp.net", "media.fdiy1-1.fna.whatsapp.net", - "media.fisb4-1.fna.whatsapp.net", "media.fpku1-2.fna.whatsapp.net", "media.fros9-1.fna.whatsapp.net", "status_v3_text", "usync_sidelist", "17", "announcement", "...", - "md_group_notification", "0f", "animated_pack_in_store", "013", "America/Mexico_City", "1260", "media-ams4-1.cdn.whatsapp.net", "media-cgk1-2.cdn.whatsapp.net", - "media-cpt1-1.cdn.whatsapp.net", "media-maa2-1.cdn.whatsapp.net", "media.fgye10-1.fna.whatsapp.net", "e", "catalog_cart", "hfm_string_changes", "init_bitrate", - "packless_hsm", "group_info", "America/Belem", "50", "960", "cond_range_bwe", "decode", "encode", "media.fada1-8.fna.whatsapp.net", "media.fadb1-2.fna.whatsapp.net", - "media.fasu6-1.fna.whatsapp.net", "media.fbog4-1.fna.whatsapp.net", "media.fcgk9-2.fna.whatsapp.net", "media.fdoh5-2.fna.whatsapp.net", "media.ffjr1-2.fna.whatsapp.net", - "media.fgua1-1.fna.whatsapp.net", "media.fgye1-1.fna.whatsapp.net", "media.fist1-4.fna.whatsapp.net", "media.fpbc2-2.fna.whatsapp.net", "media.fres2-1.fna.whatsapp.net", - "media.fsdq1-2.fna.whatsapp.net", "media.fsub6-5.fna.whatsapp.net", "profilo_enabled", "template_hsm", "use_disorder_prefetching_timer", "video_codec_priority", - "vpx_max_qp", "ptt_reduce_recording_delay", "25", "iphone", "Windows", "s_o", "Africa/Lagos", "abt", "media-kut2-1.cdn.whatsapp.net", "media-mba1-1.cdn.whatsapp.net", - "media-mxp1-2.cdn.whatsapp.net", "md_blocklist_v2", "url_text", "enable_short_offset", "group_join_permissions", "enable_audio_piggyback_feature", "image_quality", - "media.fcgk7-2.fna.whatsapp.net", "media.fcgk8-2.fna.whatsapp.net", "media.fclo7-1.fna.whatsapp.net", "media.fcmn1-1.fna.whatsapp.net", "media.feoh1-1.fna.whatsapp.net", - "media.fgyd4-3.fna.whatsapp.net", "media.fjed4-4.fna.whatsapp.net", "media.flim1-4.fna.whatsapp.net", "media.flim2-4.fna.whatsapp.net", "media.fplu6-1.fna.whatsapp.net", - "media.frak1-1.fna.whatsapp.net", "media.fsdq1-1.fna.whatsapp.net", "to_ip", "015", "vp8", "19", "21", "1320", "auth_key_ver", "message_processing_dedup", "server-error", - "wap4_enabled", "420", "014", "cond_range_rtt", "ptt_fast_lock_enabled", "media-ort2-1.cdn.whatsapp.net", "fwd_ui_start_ts" -}; - -static char *dict3[] = { - "contact_blacklist", "Asia/Jakarta", "media.fepa10-1.fna.whatsapp.net", "media.fmex10-3.fna.whatsapp.net", "disorder_prefetching_start_when_empty", "America/Bogota", - "use_local_probing_rx_bitrate", "America/Argentina/Buenos_Aires", "cross_post", "media.fabb1-1.fna.whatsapp.net", "media.fbog4-2.fna.whatsapp.net", "media.fcgk9-1.fna.whatsapp.net", - "media.fcmn2-1.fna.whatsapp.net", "media.fdel3-1.fna.whatsapp.net", "media.ffjr1-1.fna.whatsapp.net", "media.fgdl5-1.fna.whatsapp.net", "media.flpb1-2.fna.whatsapp.net", - "media.fmex2-1.fna.whatsapp.net", "media.frba2-2.fna.whatsapp.net", "media.fros2-2.fna.whatsapp.net", "media.fruh2-1.fna.whatsapp.net", "media.fybz2-2.fna.whatsapp.net", - "options", "20", "a", "017", "018", "mute_always", "user_notice", "Asia/Kolkata", "gif_provider", "locked", "media-gua1-1.cdn.whatsapp.net", "piggyback_exclude_force_flush", - "24", "media.frec39-1.fna.whatsapp.net", "user_remove", "file_max_size", "cond_packet_loss_pct_ema_alpha", "media.facc1-1.fna.whatsapp.net", "media.fadb2-1.fna.whatsapp.net", - "media.faly3-1.fna.whatsapp.net", "media.fbdo6-2.fna.whatsapp.net", "media.fcmn2-2.fna.whatsapp.net", "media.fctg1-3.fna.whatsapp.net", "media.ffez1-2.fna.whatsapp.net", - "media.fist1-3.fna.whatsapp.net", "media.fist2-2.fna.whatsapp.net", "media.flim2-2.fna.whatsapp.net", "media.fmct2-3.fna.whatsapp.net", "media.fpei3-1.fna.whatsapp.net", - "media.frba3-1.fna.whatsapp.net", "media.fsdu8-2.fna.whatsapp.net", "media.fstu2-1.fna.whatsapp.net", "media_type", "receipt_agg", "016", "enable_pli_for_crc_mismatch", - "live", "enc_rekey", "frskmsg", "d", "media.fdel11-2.fna.whatsapp.net", "proto", "2250", "audio_piggyback_enable_cache", "skip_nack_if_ltrp_sent", "mark_dtx_jb_frames", - "web_service_delay", "7282", "catalog_send_all", "outgoing", "360", "30", "LIMITED", "019", "audio_picker", "bpv2_phase", "media.fada1-7.fna.whatsapp.net", - "media.faep7-1.fna.whatsapp.net", "media.fbko1-2.fna.whatsapp.net", "media.fbni1-2.fna.whatsapp.net", "media.fbtz1-1.fna.whatsapp.net", "media.fbtz1-8.fna.whatsapp.net", - "media.fcjs3-1.fna.whatsapp.net", "media.fesb3-2.fna.whatsapp.net", "media.fgdl5-4.fna.whatsapp.net", "media.fist2-1.fna.whatsapp.net", "media.flhe2-2.fna.whatsapp.net", - "media.flim2-1.fna.whatsapp.net", "media.fmex1-1.fna.whatsapp.net", "media.fpat3-2.fna.whatsapp.net", "media.fpat3-3.fna.whatsapp.net", "media.fros2-1.fna.whatsapp.net", - "media.fsdu8-1.fna.whatsapp.net", "media.fsub3-2.fna.whatsapp.net", "payments_chat_plugin", "cond_congestion_no_rtcp_thr", "green_alert", "not-a-biz", "..", - "shops_pdp_urls_config", "source", "media-dus1-1.cdn.whatsapp.net", "mute_video", "01b", "currency", "max_keys", "resume_check", "contact_array", "qr_scanning", "23", - "b", "media.fbfh15-1.fna.whatsapp.net", "media.flim22-1.fna.whatsapp.net", "media.fsdu11-1.fna.whatsapp.net", "media.fsdu15-1.fna.whatsapp.net", "Chrome", "fts_version", - "60", "media.fada1-6.fna.whatsapp.net", "media.faep4-2.fna.whatsapp.net", "media.fbaq5-1.fna.whatsapp.net", "media.fbni1-1.fna.whatsapp.net", "media.fcai3-2.fna.whatsapp.net", - "media.fdel3-2.fna.whatsapp.net", "media.fdmm3-2.fna.whatsapp.net", "media.fhex3-1.fna.whatsapp.net", "media.fisb4-2.fna.whatsapp.net", "media.fkhi5-2.fna.whatsapp.net", - "media.flos2-1.fna.whatsapp.net", "media.fmct2-1.fna.whatsapp.net", "media.fntr7-1.fna.whatsapp.net", "media.frak3-1.fna.whatsapp.net", "media.fruh5-2.fna.whatsapp.net", - "media.fsub6-1.fna.whatsapp.net", "media.fuab1-2.fna.whatsapp.net", "media.fuio1-1.fna.whatsapp.net", "media.fver1-1.fna.whatsapp.net", "media.fymy1-1.fna.whatsapp.net", - "product_catalog", "1380", "audio_oob_fec_max_pkts", "22", "254", "media-ort2-2.cdn.whatsapp.net", "media-sjc3-1.cdn.whatsapp.net", "1600", "01a", "01c", "405", - "key_frame_interval", "body", "media.fcgh20-1.fna.whatsapp.net", "media.fesb10-2.fna.whatsapp.net", "125", "2000", "media.fbsb1-1.fna.whatsapp.net", - "media.fcmn3-2.fna.whatsapp.net", "media.fcpq1-1.fna.whatsapp.net", "media.fdel1-2.fna.whatsapp.net", "media.ffor2-1.fna.whatsapp.net", "media.fgdl1-4.fna.whatsapp.net", - "media.fhex2-1.fna.whatsapp.net", "media.fist1-2.fna.whatsapp.net", "media.fjed5-2.fna.whatsapp.net", "media.flim6-4.fna.whatsapp.net", "media.flos2-2.fna.whatsapp.net", - "media.fntr6-2.fna.whatsapp.net", "media.fpku3-2.fna.whatsapp.net", "media.fros8-1.fna.whatsapp.net", "media.fymy1-2.fna.whatsapp.net", "ul_bw", "ltrp_qp_offset", - "request", "nack", "dtx_delay_state_reset", "timeoffline", "28", "01f", "32", "enable_ltr_pool", "wa_msys_crypto", "01d", "58", "dtx_freeze_hg_update", - "nack_if_rpsi_throttled", "253", "840", "media.famd15-1.fna.whatsapp.net", "media.fbog17-2.fna.whatsapp.net", "media.fcai19-2.fna.whatsapp.net", "media.fcai21-4.fna.whatsapp.net", - "media.fesb10-4.fna.whatsapp.net", "media.fesb10-5.fna.whatsapp.net", "media.fmaa12-1.fna.whatsapp.net", "media.fmex11-3.fna.whatsapp.net", "media.fpoa33-1.fna.whatsapp.net", - "1050", "021", "clean", "cond_range_ema_packet_loss_pct", "media.fadb6-5.fna.whatsapp.net", "media.faqp4-1.fna.whatsapp.net", "media.fbaq3-1.fna.whatsapp.net", - "media.fbel2-1.fna.whatsapp.net", "media.fblr4-2.fna.whatsapp.net", "media.fclo8-1.fna.whatsapp.net", "media.fcoo1-2.fna.whatsapp.net", "media.ffjr1-4.fna.whatsapp.net", - "media.ffor9-1.fna.whatsapp.net", "media.fisb3-1.fna.whatsapp.net", "media.fkhi2-2.fna.whatsapp.net", "media.fkhi4-1.fna.whatsapp.net", "media.fpbc1-2.fna.whatsapp.net", - "media.fruh2-2.fna.whatsapp.net", "media.fruh5-1.fna.whatsapp.net", "media.fsub3-1.fna.whatsapp.net", "payments_transaction_limit", "252", "27", "29", "tintagel", "01e", - "237", "780", "callee_updated_payload", "020", "257", "price", "025", "239", "payments_cs_phone_number", "mediaretry", "w:auth:backup:token", "Glass.caf", "max_bitrate", - "240", "251", "660", "media.fbog16-1.fna.whatsapp.net", "media.fcgh21-1.fna.whatsapp.net", "media.fkul19-2.fna.whatsapp.net", "media.flim21-2.fna.whatsapp.net", - "media.fmex10-4.fna.whatsapp.net", "64", "33", "34", "35", "interruption", "media.fabv3-1.fna.whatsapp.net", "media.fadb6-1.fna.whatsapp.net", "media.fagr1-1.fna.whatsapp.net", - "media.famd1-1.fna.whatsapp.net", "media.famm6-1.fna.whatsapp.net", "media.faqp2-3.fna.whatsapp.net" -}; +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+static char *SingleByteTokens[] = {
+ "", "xmlstreamstart", "xmlstreamend", "s.whatsapp.net", "type", "participant", "from", "receipt", "id", "broadcast", "status", "message", "notification", "notify", "to", "jid", "user",
+ "class", "offline", "g.us", "result", "mediatype", "enc", "skmsg", "off_cnt", "xmlns", "presence", "participants", "ack", "t", "iq", "device_hash", "read", "value", "media", "picture",
+ "chatstate", "unavailable", "text", "urn:xmpp:whatsapp:push", "devices", "verified_name", "contact", "composing", "edge_routing", "routing_info", "item", "image", "verified_level",
+ "get", "fallback_hostname", "2", "media_conn", "1", "v", "handshake", "fallback_class", "count", "config", "offline_preview", "download_buckets", "w:profile:picture", "set",
+ "creation", "location", "fallback_ip4", "msg", "urn:xmpp:ping", "fallback_ip6", "call-creator", "relaylatency", "success", "subscribe", "video", "business_hours_config",
+ "platform", "hostname", "version", "unknown", "0", "ping", "hash", "edit", "subject", "max_buckets", "download", "delivery", "props", "sticker", "name", "last", "contacts",
+ "business", "primary", "preview", "w:p", "pkmsg", "call-id", "retry", "prop", "call", "auth_ttl", "available", "relay_id", "last_id", "day_of_week", "w", "host", "seen",
+ "bits", "list", "atn", "upload", "is_new", "w:stats", "key", "paused", "specific_hours", "multicast", "stream:error", "mmg.whatsapp.net", "code", "deny", "played", "profile",
+ "fna", "device-list", "close_time", "latency", "gcm", "pop", "audio", "26", "w:web", "open_time", "error", "auth", "ip4", "update", "profile_options", "config_value", "category",
+ "catalog_not_created", "00", "config_code", "mode", "catalog_status", "ip6", "blocklist", "registration", "7", "web", "fail", "w:m", "cart_enabled", "ttl", "gif", "300",
+ "device_orientation", "identity", "query", "401", "media-gig2-1.cdn.whatsapp.net", "in", "3", "te2", "add", "fallback", "categories", "ptt", "encrypt", "notice",
+ "thumbnail-document", "item-not-found", "12", "thumbnail-image", "stage", "thumbnail-link", "usync", "out", "thumbnail-video", "8", "01", "context", "sidelist",
+ "thumbnail-gif", "terminate", "not-authorized", "orientation", "dhash", "capability", "side_list", "md-app-state", "description", "serial", "readreceipts", "te",
+ "business_hours", "md-msg-hist", "tag", "attribute_padding", "document", "open_24h", "delete", "expiration", "active", "prev_v_id", "true", "passive", "index", "4",
+ "conflict", "remove", "w:gp2", "config_expo_key", "screen_height", "replaced", "02", "screen_width", "uploadfieldstat", "2:47DEQpj8", "media-bog1-1.cdn.whatsapp.net",
+ "encopt", "url", "catalog_exists", "keygen", "rate", "offer", "opus", "media-mia3-1.cdn.whatsapp.net", "privacy", "media-mia3-2.cdn.whatsapp.net", "signature",
+ "preaccept", "token_id", "media-eze1-1.cdn.whatsapp.net"
+};
+
+static char *dict0[] = {
+ "media-for1-1.cdn.whatsapp.net", "relay", "media-gru2-2.cdn.whatsapp.net", "uncompressed", "medium", "voip_settings", "device", "reason",
+ "media-lim1-1.cdn.whatsapp.net", "media-qro1-2.cdn.whatsapp.net", "media-gru1-2.cdn.whatsapp.net", "action", "features", "media-gru2-1.cdn.whatsapp.net",
+ "media-gru1-1.cdn.whatsapp.net", "media-otp1-1.cdn.whatsapp.net", "kyc-id", "priority", "phash", "mute", "token", "100", "media-qro1-1.cdn.whatsapp.net",
+ "none", "media-mrs2-2.cdn.whatsapp.net", "sign_credential", "03", "media-mrs2-1.cdn.whatsapp.net", "protocol", "timezone", "transport", "eph_setting", "1080",
+ "original_dimensions", "media-frx5-1.cdn.whatsapp.net", "background", "disable", "original_image_url", "5", "transaction-id", "direct_path", "103", "appointment_only",
+ "request_image_url", "peer_pid", "address", "105", "104", "102", "media-cdt1-1.cdn.whatsapp.net", "101", "109", "110", "106", "background_location", "v_id", "sync",
+ "status-old", "111", "107", "ppic", "media-scl2-1.cdn.whatsapp.net", "business_profile", "108", "invite", "04", "audio_duration", "media-mct1-1.cdn.whatsapp.net",
+ "media-cdg2-1.cdn.whatsapp.net", "media-los2-1.cdn.whatsapp.net", "invis", "net", "voip_payload_type", "status-revoke-delay", "404", "state", "use_correct_order_for_hmac_sha1",
+ "ver", "media-mad1-1.cdn.whatsapp.net", "order", "540", "skey", "blinded_credential", "android", "contact_remove", "enable_downlink_relay_latency_only", "duration",
+ "enable_vid_one_way_codec_nego", "6", "media-sof1-1.cdn.whatsapp.net", "accept", "all", "signed_credential", "media-atl3-1.cdn.whatsapp.net", "media-lhr8-1.cdn.whatsapp.net",
+ "website", "05", "latitude", "media-dfw5-1.cdn.whatsapp.net", "forbidden", "enable_audio_piggyback_network_mtu_fix", "media-dfw5-2.cdn.whatsapp.net", "note.m4r",
+ "media-atl3-2.cdn.whatsapp.net", "jb_nack_discard_count_fix", "longitude", "Opening.m4r", "media-arn2-1.cdn.whatsapp.net", "email", "timestamp", "admin",
+ "media-pmo1-1.cdn.whatsapp.net", "America/Sao_Paulo", "contact_add", "media-sin6-1.cdn.whatsapp.net", "interactive", "8000", "acs_public_key",
+ "sigquit_anr_detector_release_rollover_percent", "media.fmed1-2.fna.whatsapp.net", "groupadd", "enabled_for_video_upgrade", "latency_update_threshold",
+ "media-frt3-2.cdn.whatsapp.net", "calls_row_constraint_layout", "media.fgbb2-1.fna.whatsapp.net", "mms4_media_retry_notification_encryption_enabled", "timeout",
+ "media-sin6-3.cdn.whatsapp.net", "audio_nack_jitter_multiplier", "jb_discard_count_adjust_pct_rc", "audio_reserve_bps", "delta", "account_sync", "default",
+ "media.fjed4-6.fna.whatsapp.net", "06", "lock_video_orientation", "media-frt3-1.cdn.whatsapp.net", "w:g2", "media-sin6-2.cdn.whatsapp.net", "audio_nack_algo_mask",
+ "media.fgbb2-2.fna.whatsapp.net", "media.fmed1-1.fna.whatsapp.net", "cond_range_target_bitrate", "mms4_server_error_receipt_encryption_enabled", "vid_rc_dyn", "fri",
+ "cart_v1_1_order_message_changes_enabled", "reg_push", "jb_hist_deposit_value", "privatestats", "media.fist7-2.fna.whatsapp.net", "thu", "jb_discard_count_adjust_pct",
+ "mon", "group_call_video_maximization_enabled", "mms_cat_v1_forward_hot_override_enabled", "audio_nack_new_rtt", "media.fsub2-3.fna.whatsapp.net",
+ "media_upload_aggressive_retry_exponential_backoff_enabled", "tue", "wed", "media.fruh4-2.fna.whatsapp.net", "audio_nack_max_seq_req", "max_rtp_audio_packet_resends",
+ "jb_hist_max_cdf_value", "07", "audio_nack_max_jb_delay", "mms_forward_partially_downloaded_video", "media-lcy1-1.cdn.whatsapp.net", "resume", "jb_inband_fec_aware",
+ "new_commerce_entry_point_enabled", "480", "payments_upi_generate_qr_amount_limit", "sigquit_anr_detector_rollover_percent", "media.fsdu2-1.fna.whatsapp.net", "fbns",
+ "aud_pkt_reorder_pct", "dec", "stop_probing_before_accept_send", "media_upload_max_aggressive_retries", "edit_business_profile_new_mode_enabled",
+ "media.fhex4-1.fna.whatsapp.net", "media.fjed4-3.fna.whatsapp.net", "sigquit_anr_detector_64bit_rollover_percent", "cond_range_ema_jb_last_delay",
+ "watls_enable_early_data_http_get", "media.fsdu2-2.fna.whatsapp.net", "message_qr_disambiguation_enabled", "media-mxp1-1.cdn.whatsapp.net", "sat", "vertical",
+ "media.fruh4-5.fna.whatsapp.net", "200", "media-sof1-2.cdn.whatsapp.net", "-1", "height", "product_catalog_hide_show_items_enabled", "deep_copy_frm_last",
+ "tsoffline", "vp8/h.264", "media.fgye5-3.fna.whatsapp.net", "media.ftuc1-2.fna.whatsapp.net", "smb_upsell_chat_banner_enabled", "canonical", "08", "9", ".",
+ "media.fgyd4-4.fna.whatsapp.net", "media.fsti4-1.fna.whatsapp.net", "mms_vcache_aggregation_enabled", "mms_hot_content_timespan_in_seconds", "nse_ver", "rte",
+ "third_party_sticker_web_sync", "cond_range_target_total_bitrate", "media_upload_aggressive_retry_enabled", "instrument_spam_report_enabled", "disable_reconnect_tone",
+ "move_media_folder_from_sister_app", "one_tap_calling_in_group_chat_size", "10", "storage_mgmt_banner_threshold_mb", "enable_backup_passive_mode", "sharechat_inline_player_enabled",
+ "media.fcnq2-1.fna.whatsapp.net", "media.fhex4-2.fna.whatsapp.net", "media.fist6-3.fna.whatsapp.net", "ephemeral_drop_column_stage", "reconnecting_after_network_change_threshold_ms",
+ "media-lhr8-2.cdn.whatsapp.net", "cond_jb_last_delay_ema_alpha", "entry_point_block_logging_enabled", "critical_event_upload_log_config", "respect_initial_bitrate_estimate",
+ "smaller_image_thumbs_status_enabled", "media.fbtz1-4.fna.whatsapp.net", "media.fjed4-1.fna.whatsapp.net", "width", "720", "enable_frame_dropper", "enable_one_side_mode",
+ "urn:xmpp:whatsapp:dirty", "new_sticker_animation_behavior_v2", "media.flim3-2.fna.whatsapp.net", "media.fuio6-2.fna.whatsapp.net", "skip_forced_signaling", "dleq_proof",
+ "status_video_max_bitrate", "lazy_send_probing_req", "enhanced_storage_management", "android_privatestats_endpoint_dit_enabled", "media.fscl13-2.fna.whatsapp.net", "video_duration"
+};
+
+static char *dict1[] = {
+ "group_call_discoverability_enabled", "media.faep9-2.fna.whatsapp.net", "msgr", "bloks_loggedin_access_app_id", "db_status_migration_step", "watls_prefer_ip6",
+ "jabber:iq:privacy", "68", "media.fsaw1-11.fna.whatsapp.net", "mms4_media_conn_persist_enabled", "animated_stickers_thread_clean_up", "media.fcgk3-2.fna.whatsapp.net",
+ "media.fcgk4-6.fna.whatsapp.net", "media.fgye5-2.fna.whatsapp.net", "media.flpb1-1.fna.whatsapp.net", "media.fsub2-1.fna.whatsapp.net", "media.fuio6-3.fna.whatsapp.net",
+ "not-allowed", "partial_pjpeg_bw_threshold", "cap_estimated_bitrate", "mms_chatd_resume_check_over_thrift", "smb_upsell_business_profile_enabled", "product_catalog_webclient",
+ "groups", "sigquit_anr_detector_release_updated_rollout", "syncd_key_rotation_enabled", "media.fdmm2-1.fna.whatsapp.net", "media-hou1-1.cdn.whatsapp.net",
+ "remove_old_chat_notifications", "smb_biztools_deeplink_enabled", "use_downloadable_filters_int", "group_qr_codes_enabled", "max_receipt_processing_time",
+ "optimistic_image_processing_enabled", "smaller_video_thumbs_status_enabled", "watls_early_data", "reconnecting_before_relay_failover_threshold_ms", "cond_range_packet_loss_pct",
+ "groups_privacy_blacklist", "status-revoke-drop", "stickers_animated_thumbnail_download", "dedupe_transcode_shared_images", "dedupe_transcode_shared_videos",
+ "media.fcnq2-2.fna.whatsapp.net", "media.fgyd4-1.fna.whatsapp.net", "media.fist7-1.fna.whatsapp.net", "media.flim3-3.fna.whatsapp.net", "add_contact_by_qr_enabled",
+ "https://faq.whatsapp.com/payments", "multicast_limit_global", "sticker_notification_preview", "smb_better_catalog_list_adapters_enabled", "bloks_use_minscript_android",
+ "pen_smoothing_enabled", "media.fcgk4-5.fna.whatsapp.net", "media.fevn1-3.fna.whatsapp.net", "media.fpoj7-1.fna.whatsapp.net", "media-arn2-2.cdn.whatsapp.net",
+ "reconnecting_before_network_change_threshold_ms", "android_media_use_fresco_for_gifs", "cond_in_congestion", "status_image_max_edge", "sticker_search_enabled",
+ "starred_stickers_web_sync", "db_blank_me_jid_migration_step", "media.fist6-2.fna.whatsapp.net", "media.ftuc1-1.fna.whatsapp.net", "09", "anr_fast_logs_upload_rollout",
+ "camera_core_integration_enabled", "11", "third_party_sticker_caching", "thread_dump_contact_support", "wam_privatestats_enabled", "vcard_as_document_size_kb", "maxfpp",
+ "fbip", "ephemeral_allow_group_members", "media-bom1-2.cdn.whatsapp.net", "media-xsp1-1.cdn.whatsapp.net", "disable_prewarm", "frequently_forwarded_max",
+ "media.fbtz1-5.fna.whatsapp.net", "media.fevn7-1.fna.whatsapp.net", "media.fgyd4-2.fna.whatsapp.net", "sticker_tray_animation_fully_visible_items",
+ "green_alert_banner_duration", "reconnecting_after_p2p_failover_threshold_ms", "connected", "share_biz_vcard_enabled", "stickers_animation", "0a", "1200", "WhatsApp",
+ "group_description_length", "p_v_id", "payments_upi_intent_transaction_limit", "frequently_forwarded_messages", "media-xsp1-2.cdn.whatsapp.net",
+ "media.faep8-1.fna.whatsapp.net", "media.faep8-2.fna.whatsapp.net", "media.faep9-1.fna.whatsapp.net", "media.fdmm2-2.fna.whatsapp.net", "media.fgzt3-1.fna.whatsapp.net",
+ "media.flim4-2.fna.whatsapp.net", "media.frao1-1.fna.whatsapp.net", "media.fscl9-2.fna.whatsapp.net", "media.fsub2-2.fna.whatsapp.net", "superadmin",
+ "media.fbog10-1.fna.whatsapp.net", "media.fcgh28-1.fna.whatsapp.net", "media.fjdo10-1.fna.whatsapp.net", "third_party_animated_sticker_import", "delay_fec",
+ "attachment_picker_refresh", "android_linked_devices_re_auth_enabled", "rc_dyn", "green_alert_block_jitter", "add_contact_logging_enabled", "biz_message_logging_enabled",
+ "conversation_media_preview_v2", "media-jnb1-1.cdn.whatsapp.net", "ab_key", "media.fcgk4-2.fna.whatsapp.net", "media.fevn1-1.fna.whatsapp.net", "media.fist6-1.fna.whatsapp.net",
+ "media.fruh4-4.fna.whatsapp.net", "media.fsti4-2.fna.whatsapp.net", "mms_vcard_autodownload_size_kb", "watls_enabled", "notif_ch_override_off", "media.fsaw1-14.fna.whatsapp.net",
+ "media.fscl13-1.fna.whatsapp.net", "db_group_participant_migration_step", "1020", "cond_range_sterm_rtt", "invites_logging_enabled", "triggered_block_enabled",
+ "group_call_max_participants", "media-iad3-1.cdn.whatsapp.net", "product_catalog_open_deeplink", "shops_required_tos_version", "image_max_kbytes",
+ "cond_low_quality_vid_mode", "db_receipt_migration_step", "jb_early_prob_hist_shrink", "media.fdmm2-3.fna.whatsapp.net", "media.fdmm2-4.fna.whatsapp.net",
+ "media.fruh4-1.fna.whatsapp.net", "media.fsaw2-2.fna.whatsapp.net", "remove_geolocation_videos", "new_animation_behavior", "fieldstats_beacon_chance", "403",
+ "authkey_reset_on_ban", "continuous_ptt_playback", "reconnecting_after_relay_failover_threshold_ms", "false", "group", "sun", "conversation_swipe_to_reply",
+ "ephemeral_messages_setting", "smaller_video_thumbs_enabled", "md_device_sync_enabled", "bloks_shops_pdp_url_regex", "lasso_integration_enabled",
+ "media-bom1-1.cdn.whatsapp.net", "new_backup_format_enabled", "256", "media.faep6-1.fna.whatsapp.net", "media.fasr1-1.fna.whatsapp.net", "media.fbtz1-7.fna.whatsapp.net",
+ "media.fesb4-1.fna.whatsapp.net", "media.fjdo1-2.fna.whatsapp.net", "media.frba2-1.fna.whatsapp.net", "watls_no_dns", "600", "db_broadcast_me_jid_migration_step",
+ "new_wam_runtime_enabled", "group_update", "enhanced_block_enabled", "sync_wifi_threshold_kb", "mms_download_nc_cat", "bloks_minification_enabled",
+ "ephemeral_messages_enabled", "reject", "voip_outgoing_xml_signaling", "creator", "dl_bw", "payments_request_messages", "target_bitrate", "bloks_rendercore_enabled",
+ "media-hbe1-1.cdn.whatsapp.net", "media-hel3-1.cdn.whatsapp.net", "media-kut2-2.cdn.whatsapp.net", "media-lax3-1.cdn.whatsapp.net", "media-lax3-2.cdn.whatsapp.net",
+ "sticker_pack_deeplink_enabled", "hq_image_bw_threshold", "status_info", "voip", "dedupe_transcode_videos", "grp_uii_cleanup", "linked_device_max_count",
+ "media.flim1-1.fna.whatsapp.net", "media.fsaw2-1.fna.whatsapp.net", "reconnecting_after_call_active_threshold_ms", "1140", "catalog_pdp_new_design",
+ "media.fbtz1-10.fna.whatsapp.net", "media.fsaw1-15.fna.whatsapp.net", "0b", "consumer_rc_provider", "mms_async_fast_forward_ttl", "jb_eff_size_fix",
+ "voip_incoming_xml_signaling", "media_provider_share_by_uuid", "suspicious_links", "dedupe_transcode_images", "green_alert_modal_start", "media-cgk1-1.cdn.whatsapp.net",
+ "media-lga3-1.cdn.whatsapp.net", "template_doc_mime_types", "important_messages", "user_add", "vcard_max_size_kb", "media.fada2-1.fna.whatsapp.net",
+ "media.fbog2-5.fna.whatsapp.net", "media.fbtz1-3.fna.whatsapp.net", "media.fcgk3-1.fna.whatsapp.net", "media.fcgk7-1.fna.whatsapp.net", "media.flim1-3.fna.whatsapp.net",
+ "media.fscl9-1.fna.whatsapp.net", "ctwa_context_enterprise_enabled", "media.fsaw1-13.fna.whatsapp.net", "media.fuio11-2.fna.whatsapp.net", "status_collapse_muted",
+ "db_migration_level_force", "recent_stickers_web_sync", "bloks_session_state", "bloks_shops_enabled", "green_alert_setting_deep_links_enabled", "restrict_groups",
+ "battery", "green_alert_block_start", "refresh", "ctwa_context_enabled", "md_messaging_enabled", "status_image_quality", "md_blocklist_v2_server",
+ "media-del1-1.cdn.whatsapp.net", "13", "userrate", "a_v_id", "cond_rtt_ema_alpha", "invalid"
+};
+
+static char *dict2[] = {
+ "media.fada1-1.fna.whatsapp.net", "media.fadb3-2.fna.whatsapp.net", "media.fbhz2-1.fna.whatsapp.net", "media.fcor2-1.fna.whatsapp.net", "media.fjed4-2.fna.whatsapp.net",
+ "media.flhe4-1.fna.whatsapp.net", "media.frak1-2.fna.whatsapp.net", "media.fsub6-3.fna.whatsapp.net", "media.fsub6-7.fna.whatsapp.net", "media.fvvi1-1.fna.whatsapp.net",
+ "search_v5_eligible", "wam_real_time_enabled", "report_disk_event", "max_tx_rott_based_bitrate", "product", "media.fjdo10-2.fna.whatsapp.net", "video_frame_crc_sample_interval",
+ "media_max_autodownload", "15", "h.264", "wam_privatestats_buffer_count", "md_phash_v2_enabled", "account_transfer_enabled", "business_product_catalog",
+ "enable_non_dyn_codec_param_fix", "is_user_under_epd_jurisdiction", "media.fbog2-4.fna.whatsapp.net", "media.fbtz1-2.fna.whatsapp.net", "media.fcfc1-1.fna.whatsapp.net",
+ "media.fjed4-5.fna.whatsapp.net", "media.flhe4-2.fna.whatsapp.net", "media.flim1-2.fna.whatsapp.net", "media.flos5-1.fna.whatsapp.net", "android_key_store_auth_ver",
+ "010", "anr_process_monitor", "delete_old_auth_key", "media.fcor10-3.fna.whatsapp.net", "storage_usage_enabled", "android_camera2_support_level", "dirty",
+ "consumer_content_provider", "status_video_max_duration", "0c", "bloks_cache_enabled", "media.fadb2-2.fna.whatsapp.net", "media.fbko1-1.fna.whatsapp.net",
+ "media.fbtz1-9.fna.whatsapp.net", "media.fcgk4-4.fna.whatsapp.net", "media.fesb4-2.fna.whatsapp.net", "media.fevn1-2.fna.whatsapp.net", "media.fist2-4.fna.whatsapp.net",
+ "media.fjdo1-1.fna.whatsapp.net", "media.fruh4-6.fna.whatsapp.net", "media.fsrg5-1.fna.whatsapp.net", "media.fsub6-6.fna.whatsapp.net", "minfpp", "5000", "locales",
+ "video_max_bitrate", "use_new_auth_key", "bloks_http_enabled", "heartbeat_interval", "media.fbog11-1.fna.whatsapp.net", "ephemeral_group_query_ts", "fec_nack",
+ "search_in_storage_usage", "c", "media-amt2-1.cdn.whatsapp.net", "linked_devices_ui_enabled", "14", "async_data_load_on_startup", "voip_incoming_xml_ack", "16",
+ "db_migration_step", "init_bwe", "max_participants", "wam_buffer_count", "media.fada2-2.fna.whatsapp.net", "media.fadb3-1.fna.whatsapp.net", "media.fcor2-2.fna.whatsapp.net",
+ "media.fdiy1-2.fna.whatsapp.net", "media.frba3-2.fna.whatsapp.net", "media.fsaw2-3.fna.whatsapp.net", "1280", "status_grid_enabled", "w:biz", "product_catalog_deeplink",
+ "media.fgye10-2.fna.whatsapp.net", "media.fuio11-1.fna.whatsapp.net", "optimistic_upload", "work_manager_init", "lc", "catalog_message", "cond_net_medium",
+ "enable_periodical_aud_rr_processing", "cond_range_ema_rtt", "media-tir2-1.cdn.whatsapp.net", "frame_ms", "group_invite_sending", "payments_web_enabled",
+ "wallpapers_v2", "0d", "browser", "hq_image_max_edge", "image_edit_zoom", "linked_devices_re_auth_enabled", "media.faly3-2.fna.whatsapp.net",
+ "media.fdoh5-3.fna.whatsapp.net", "media.fesb3-1.fna.whatsapp.net", "media.fknu1-1.fna.whatsapp.net", "media.fmex3-1.fna.whatsapp.net", "media.fruh4-3.fna.whatsapp.net",
+ "255", "web_upgrade_to_md_modal", "audio_piggyback_timeout_msec", "enable_audio_oob_fec_feature", "from_ip", "image_max_edge", "message_qr_enabled", "powersave",
+ "receipt_pre_acking", "video_max_edge", "full", "011", "012", "enable_audio_oob_fec_for_sender", "md_voip_enabled", "enable_privatestats", "max_fec_ratio",
+ "payments_cs_faq_url", "media-xsp1-3.cdn.whatsapp.net", "hq_image_quality", "media.fasr1-2.fna.whatsapp.net", "media.fbog3-1.fna.whatsapp.net",
+ "media.ffjr1-6.fna.whatsapp.net", "media.fist2-3.fna.whatsapp.net", "media.flim4-3.fna.whatsapp.net", "media.fpbc2-4.fna.whatsapp.net", "media.fpku1-1.fna.whatsapp.net",
+ "media.frba1-1.fna.whatsapp.net", "media.fudi1-1.fna.whatsapp.net", "media.fvvi1-2.fna.whatsapp.net", "gcm_fg_service", "enable_dec_ltr_size_check", "clear", "lg",
+ "media.fgru11-1.fna.whatsapp.net", "18", "media-lga3-2.cdn.whatsapp.net", "pkey", "0e", "max_subject", "cond_range_lterm_rtt", "announcement_groups", "biz_profile_options",
+ "s_t", "media.fabv2-1.fna.whatsapp.net", "media.fcai3-1.fna.whatsapp.net", "media.fcgh1-1.fna.whatsapp.net", "media.fctg1-4.fna.whatsapp.net", "media.fdiy1-1.fna.whatsapp.net",
+ "media.fisb4-1.fna.whatsapp.net", "media.fpku1-2.fna.whatsapp.net", "media.fros9-1.fna.whatsapp.net", "status_v3_text", "usync_sidelist", "17", "announcement", "...",
+ "md_group_notification", "0f", "animated_pack_in_store", "013", "America/Mexico_City", "1260", "media-ams4-1.cdn.whatsapp.net", "media-cgk1-2.cdn.whatsapp.net",
+ "media-cpt1-1.cdn.whatsapp.net", "media-maa2-1.cdn.whatsapp.net", "media.fgye10-1.fna.whatsapp.net", "e", "catalog_cart", "hfm_string_changes", "init_bitrate",
+ "packless_hsm", "group_info", "America/Belem", "50", "960", "cond_range_bwe", "decode", "encode", "media.fada1-8.fna.whatsapp.net", "media.fadb1-2.fna.whatsapp.net",
+ "media.fasu6-1.fna.whatsapp.net", "media.fbog4-1.fna.whatsapp.net", "media.fcgk9-2.fna.whatsapp.net", "media.fdoh5-2.fna.whatsapp.net", "media.ffjr1-2.fna.whatsapp.net",
+ "media.fgua1-1.fna.whatsapp.net", "media.fgye1-1.fna.whatsapp.net", "media.fist1-4.fna.whatsapp.net", "media.fpbc2-2.fna.whatsapp.net", "media.fres2-1.fna.whatsapp.net",
+ "media.fsdq1-2.fna.whatsapp.net", "media.fsub6-5.fna.whatsapp.net", "profilo_enabled", "template_hsm", "use_disorder_prefetching_timer", "video_codec_priority",
+ "vpx_max_qp", "ptt_reduce_recording_delay", "25", "iphone", "Windows", "s_o", "Africa/Lagos", "abt", "media-kut2-1.cdn.whatsapp.net", "media-mba1-1.cdn.whatsapp.net",
+ "media-mxp1-2.cdn.whatsapp.net", "md_blocklist_v2", "url_text", "enable_short_offset", "group_join_permissions", "enable_audio_piggyback_feature", "image_quality",
+ "media.fcgk7-2.fna.whatsapp.net", "media.fcgk8-2.fna.whatsapp.net", "media.fclo7-1.fna.whatsapp.net", "media.fcmn1-1.fna.whatsapp.net", "media.feoh1-1.fna.whatsapp.net",
+ "media.fgyd4-3.fna.whatsapp.net", "media.fjed4-4.fna.whatsapp.net", "media.flim1-4.fna.whatsapp.net", "media.flim2-4.fna.whatsapp.net", "media.fplu6-1.fna.whatsapp.net",
+ "media.frak1-1.fna.whatsapp.net", "media.fsdq1-1.fna.whatsapp.net", "to_ip", "015", "vp8", "19", "21", "1320", "auth_key_ver", "message_processing_dedup", "server-error",
+ "wap4_enabled", "420", "014", "cond_range_rtt", "ptt_fast_lock_enabled", "media-ort2-1.cdn.whatsapp.net", "fwd_ui_start_ts"
+};
+
+static char *dict3[] = {
+ "contact_blacklist", "Asia/Jakarta", "media.fepa10-1.fna.whatsapp.net", "media.fmex10-3.fna.whatsapp.net", "disorder_prefetching_start_when_empty", "America/Bogota",
+ "use_local_probing_rx_bitrate", "America/Argentina/Buenos_Aires", "cross_post", "media.fabb1-1.fna.whatsapp.net", "media.fbog4-2.fna.whatsapp.net", "media.fcgk9-1.fna.whatsapp.net",
+ "media.fcmn2-1.fna.whatsapp.net", "media.fdel3-1.fna.whatsapp.net", "media.ffjr1-1.fna.whatsapp.net", "media.fgdl5-1.fna.whatsapp.net", "media.flpb1-2.fna.whatsapp.net",
+ "media.fmex2-1.fna.whatsapp.net", "media.frba2-2.fna.whatsapp.net", "media.fros2-2.fna.whatsapp.net", "media.fruh2-1.fna.whatsapp.net", "media.fybz2-2.fna.whatsapp.net",
+ "options", "20", "a", "017", "018", "mute_always", "user_notice", "Asia/Kolkata", "gif_provider", "locked", "media-gua1-1.cdn.whatsapp.net", "piggyback_exclude_force_flush",
+ "24", "media.frec39-1.fna.whatsapp.net", "user_remove", "file_max_size", "cond_packet_loss_pct_ema_alpha", "media.facc1-1.fna.whatsapp.net", "media.fadb2-1.fna.whatsapp.net",
+ "media.faly3-1.fna.whatsapp.net", "media.fbdo6-2.fna.whatsapp.net", "media.fcmn2-2.fna.whatsapp.net", "media.fctg1-3.fna.whatsapp.net", "media.ffez1-2.fna.whatsapp.net",
+ "media.fist1-3.fna.whatsapp.net", "media.fist2-2.fna.whatsapp.net", "media.flim2-2.fna.whatsapp.net", "media.fmct2-3.fna.whatsapp.net", "media.fpei3-1.fna.whatsapp.net",
+ "media.frba3-1.fna.whatsapp.net", "media.fsdu8-2.fna.whatsapp.net", "media.fstu2-1.fna.whatsapp.net", "media_type", "receipt_agg", "016", "enable_pli_for_crc_mismatch",
+ "live", "enc_rekey", "frskmsg", "d", "media.fdel11-2.fna.whatsapp.net", "proto", "2250", "audio_piggyback_enable_cache", "skip_nack_if_ltrp_sent", "mark_dtx_jb_frames",
+ "web_service_delay", "7282", "catalog_send_all", "outgoing", "360", "30", "LIMITED", "019", "audio_picker", "bpv2_phase", "media.fada1-7.fna.whatsapp.net",
+ "media.faep7-1.fna.whatsapp.net", "media.fbko1-2.fna.whatsapp.net", "media.fbni1-2.fna.whatsapp.net", "media.fbtz1-1.fna.whatsapp.net", "media.fbtz1-8.fna.whatsapp.net",
+ "media.fcjs3-1.fna.whatsapp.net", "media.fesb3-2.fna.whatsapp.net", "media.fgdl5-4.fna.whatsapp.net", "media.fist2-1.fna.whatsapp.net", "media.flhe2-2.fna.whatsapp.net",
+ "media.flim2-1.fna.whatsapp.net", "media.fmex1-1.fna.whatsapp.net", "media.fpat3-2.fna.whatsapp.net", "media.fpat3-3.fna.whatsapp.net", "media.fros2-1.fna.whatsapp.net",
+ "media.fsdu8-1.fna.whatsapp.net", "media.fsub3-2.fna.whatsapp.net", "payments_chat_plugin", "cond_congestion_no_rtcp_thr", "green_alert", "not-a-biz", "..",
+ "shops_pdp_urls_config", "source", "media-dus1-1.cdn.whatsapp.net", "mute_video", "01b", "currency", "max_keys", "resume_check", "contact_array", "qr_scanning", "23",
+ "b", "media.fbfh15-1.fna.whatsapp.net", "media.flim22-1.fna.whatsapp.net", "media.fsdu11-1.fna.whatsapp.net", "media.fsdu15-1.fna.whatsapp.net", "Chrome", "fts_version",
+ "60", "media.fada1-6.fna.whatsapp.net", "media.faep4-2.fna.whatsapp.net", "media.fbaq5-1.fna.whatsapp.net", "media.fbni1-1.fna.whatsapp.net", "media.fcai3-2.fna.whatsapp.net",
+ "media.fdel3-2.fna.whatsapp.net", "media.fdmm3-2.fna.whatsapp.net", "media.fhex3-1.fna.whatsapp.net", "media.fisb4-2.fna.whatsapp.net", "media.fkhi5-2.fna.whatsapp.net",
+ "media.flos2-1.fna.whatsapp.net", "media.fmct2-1.fna.whatsapp.net", "media.fntr7-1.fna.whatsapp.net", "media.frak3-1.fna.whatsapp.net", "media.fruh5-2.fna.whatsapp.net",
+ "media.fsub6-1.fna.whatsapp.net", "media.fuab1-2.fna.whatsapp.net", "media.fuio1-1.fna.whatsapp.net", "media.fver1-1.fna.whatsapp.net", "media.fymy1-1.fna.whatsapp.net",
+ "product_catalog", "1380", "audio_oob_fec_max_pkts", "22", "254", "media-ort2-2.cdn.whatsapp.net", "media-sjc3-1.cdn.whatsapp.net", "1600", "01a", "01c", "405",
+ "key_frame_interval", "body", "media.fcgh20-1.fna.whatsapp.net", "media.fesb10-2.fna.whatsapp.net", "125", "2000", "media.fbsb1-1.fna.whatsapp.net",
+ "media.fcmn3-2.fna.whatsapp.net", "media.fcpq1-1.fna.whatsapp.net", "media.fdel1-2.fna.whatsapp.net", "media.ffor2-1.fna.whatsapp.net", "media.fgdl1-4.fna.whatsapp.net",
+ "media.fhex2-1.fna.whatsapp.net", "media.fist1-2.fna.whatsapp.net", "media.fjed5-2.fna.whatsapp.net", "media.flim6-4.fna.whatsapp.net", "media.flos2-2.fna.whatsapp.net",
+ "media.fntr6-2.fna.whatsapp.net", "media.fpku3-2.fna.whatsapp.net", "media.fros8-1.fna.whatsapp.net", "media.fymy1-2.fna.whatsapp.net", "ul_bw", "ltrp_qp_offset",
+ "request", "nack", "dtx_delay_state_reset", "timeoffline", "28", "01f", "32", "enable_ltr_pool", "wa_msys_crypto", "01d", "58", "dtx_freeze_hg_update",
+ "nack_if_rpsi_throttled", "253", "840", "media.famd15-1.fna.whatsapp.net", "media.fbog17-2.fna.whatsapp.net", "media.fcai19-2.fna.whatsapp.net", "media.fcai21-4.fna.whatsapp.net",
+ "media.fesb10-4.fna.whatsapp.net", "media.fesb10-5.fna.whatsapp.net", "media.fmaa12-1.fna.whatsapp.net", "media.fmex11-3.fna.whatsapp.net", "media.fpoa33-1.fna.whatsapp.net",
+ "1050", "021", "clean", "cond_range_ema_packet_loss_pct", "media.fadb6-5.fna.whatsapp.net", "media.faqp4-1.fna.whatsapp.net", "media.fbaq3-1.fna.whatsapp.net",
+ "media.fbel2-1.fna.whatsapp.net", "media.fblr4-2.fna.whatsapp.net", "media.fclo8-1.fna.whatsapp.net", "media.fcoo1-2.fna.whatsapp.net", "media.ffjr1-4.fna.whatsapp.net",
+ "media.ffor9-1.fna.whatsapp.net", "media.fisb3-1.fna.whatsapp.net", "media.fkhi2-2.fna.whatsapp.net", "media.fkhi4-1.fna.whatsapp.net", "media.fpbc1-2.fna.whatsapp.net",
+ "media.fruh2-2.fna.whatsapp.net", "media.fruh5-1.fna.whatsapp.net", "media.fsub3-1.fna.whatsapp.net", "payments_transaction_limit", "252", "27", "29", "tintagel", "01e",
+ "237", "780", "callee_updated_payload", "020", "257", "price", "025", "239", "payments_cs_phone_number", "mediaretry", "w:auth:backup:token", "Glass.caf", "max_bitrate",
+ "240", "251", "660", "media.fbog16-1.fna.whatsapp.net", "media.fcgh21-1.fna.whatsapp.net", "media.fkul19-2.fna.whatsapp.net", "media.flim21-2.fna.whatsapp.net",
+ "media.fmex10-4.fna.whatsapp.net", "64", "33", "34", "35", "interruption", "media.fabv3-1.fna.whatsapp.net", "media.fadb6-1.fna.whatsapp.net", "media.fagr1-1.fna.whatsapp.net",
+ "media.famd1-1.fna.whatsapp.net", "media.famm6-1.fna.whatsapp.net", "media.faqp2-3.fna.whatsapp.net"
+};
diff --git a/protocols/WhatsApp/src/iq.cpp b/protocols/WhatsApp/src/iq.cpp index b9cf5bd7d2..24513dd06b 100644 --- a/protocols/WhatsApp/src/iq.cpp +++ b/protocols/WhatsApp/src/iq.cpp @@ -1,570 +1,570 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -void WhatsAppProto::OnAccountSync(const WANode &node) -{ - if (auto *pList = node.getChild("devices")) { - auto *pUser = FindUser(m_szJid); - pUser->arDevices.destroy(); - - for (auto &it : pList->getChildren()) - if (it->title == "device") - pUser->arDevices.insert(new WAJid(it->getAttr("jid"), it->getAttrInt("key-index"))); - } - - if (auto *pList = node.getChild("blocklist")) - for (auto &it : pList->getChildren()) - if (it->title == "item") { - auto *pUser = AddUser(it->getAttr("jid"), false); - Contact::Hide(pUser->hContact, 0 == mir_strcmp(it->getAttr("action"), "block")); - } - - SendAck(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqBlockList(const WANode &node) -{ - for (auto &it : node.getChild("list")->getChildren()) { - auto *pUser = AddUser(it->getAttr("jid"), false); - Contact::Hide(pUser->hContact); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqCountPrekeys(const WANode &node) -{ - m_bUpdatedPrekeys = true; - - int iCount = node.getChild("count")->getAttrInt("value"); - if (iCount < 5) - UploadMorePrekeys(); -} - -void WhatsAppProto::UploadMorePrekeys() -{ - WANodeIq iq(IQ::SET, "encrypt"); - - auto regId = encodeBigEndian(getDword(DBKEY_REG_ID)); - iq.addChild("registration")->content.append(regId.c_str(), regId.size()); - - iq.addChild("type")->content.append(KEY_BUNDLE_TYPE, 1); - iq.addChild("identity")->content.append(m_signalStore.signedIdentity.pub); - - const int PORTION = 10; - m_signalStore.generatePrekeys(PORTION); - - int iStart = getDword(DBKEY_PREKEY_UPLOAD_ID, 1); - auto *n = iq.addChild("list"); - for (int i = 0; i < PORTION; i++) { - auto *nKey = n->addChild("key"); - - int keyId = iStart + i; - auto encId = encodeBigEndian(keyId, 3); - nKey->addChild("id")->content.append(encId.c_str(), encId.size()); - nKey->addChild("value")->content.append(getBlob(CMStringA(FORMAT, "PreKey%dPublic", keyId))); - } - setDword(DBKEY_PREKEY_UPLOAD_ID, iStart + PORTION); - - auto *skey = iq.addChild("skey"); - - auto encId = encodeBigEndian(m_signalStore.preKey.keyid, 3); - skey->addChild("id")->content.append(encId.c_str(), encId.size()); - skey->addChild("value")->content.append(m_signalStore.preKey.pub); - skey->addChild("signature")->content.append(m_signalStore.preKey.signature); - - WSSendNode(iq, &WhatsAppProto::OnIqDoNothing); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqDoNothing(const WANode&) -{ -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqGetKeys(const WANode &node, void *pUserInfo) -{ - for (auto &it : node.getChild("list")->getChildren()) - if (it->title == "user") - m_signalStore.injectSession(it->getAttr("jid"), it, it); - - // don't forget to send delayed message when all keys are retrieved - if (pUserInfo) - FinishTask((WASendTask *)pUserInfo); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqGetUsync(const WANode &node, void *pUserInfo) -{ - WANodeIq iq(IQ::GET, "encrypt"); - auto *pKey = iq.addChild("key"); - - for (auto *nUser : node.getChild("usync")->getChild("list")->getChildren()) { - auto *pszJid = nUser->getAttr("jid"); - - auto *pUser = AddUser(pszJid, false); - pUser->bDeviceInit = true; - pUser->arDevices.destroy(); - - if (auto *pList = nUser->getChild("devices")->getChild("device-list")) - for (auto &it : pList->getChildren()) - if (it->title == "device") - pUser->arDevices.insert(new WAJid(pszJid, it->getAttrInt("id"))); - - for (auto &it : pUser->arDevices) { - auto blob = getBlob(MSignalSession(it->user, it->device).getSetting()); - if (blob.isEmpty()) - pKey->addChild("user")->addAttr("jid", it->toString()); - } - } - - if (pKey->getChildren().getCount() > 0) - WSSendNode(iq, &WhatsAppProto::OnIqGetKeys, pUserInfo); - else if (pUserInfo) - FinishTask((WASendTask *)pUserInfo); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqPairDevice(const WANode &node) -{ - WSSendNode(WANodeIq(IQ::RESULT) << CHAR_PARAM("id", node.getAttr("id"))); - - if (auto *pRef = node.getChild("pair-device")->getChild("ref")) { - ShowQrCode(pRef->getBody()); - } - else { - debugLogA("OnIqPairDevice: got reply without ref, exiting"); - ShutdownSession(); - } -} - -void WhatsAppProto::OnIqPairSuccess(const WANode &node) -{ - CloseQrDialog(); - - auto *pRoot = node.getChild("pair-success"); - - try { - if (auto *pPlatform = pRoot->getChild("platform")) - debugLogA("Got response from platform: %s", pPlatform->getBody().c_str()); - - if (auto *pBiz = pRoot->getChild("biz")) - if (auto *pszName = pBiz->getAttr("name")) - setUString("Nick", pszName); - - if (auto *pDevice = pRoot->getChild("device")) { - if (auto *pszJid = pDevice->getAttr("jid")) { - WAJid jid(pszJid); - m_szJid = jid.user + "@" + jid.server; - m_arUsers.insert(new WAUser(0, m_szJid, false)); - - setUString(DBKEY_ID, m_szJid); - setDword(DBKEY_DEVICE_ID, jid.device); - } - } - else throw "OnIqPairSuccess: got reply without device info, exiting"; - - if (auto *pIdentity = pRoot->getChild("device-identity")) { - proto::ADVSignedDeviceIdentityHMAC payload(pIdentity->content); - - auto &hmac = payload->hmac; - auto &details = payload->details; - { - // check details signature using HMAC - uint8_t signature[32]; - unsigned int out_len = sizeof(signature); - MBinBuffer secret(getBlob(DBKEY_SECRET_KEY)); - HMAC(EVP_sha256(), secret.data(), (int)secret.length(), details.data, (int)details.len, signature, &out_len); - if (memcmp(hmac.data, signature, sizeof(signature))) - throw "OnIqPairSuccess: got reply with invalid details signature, exiting"; - } - - proto::ADVSignedDeviceIdentity account(details); - - auto &deviceDetails = account->details; - auto &accountSignature = account->accountsignature; - auto &accountSignatureKey = account->accountsignaturekey; - { - MBinBuffer buf; - buf.append("\x06\x00", 2); - buf.append(deviceDetails.data, deviceDetails.len); - buf.append(m_signalStore.signedIdentity.pub); - - ec_public_key key = {}; - memcpy(key.data, accountSignatureKey.data, sizeof(key.data)); - if (1 != curve_verify_signature(&key, buf.data(), buf.length(), accountSignature.data, accountSignature.len)) - throw "OnIqPairSuccess: got reply with invalid account signature, exiting"; - } - debugLogA("Received valid account signature"); - { - MBinBuffer buf; - buf.append("\x06\x01", 2); - buf.append(deviceDetails.data, deviceDetails.len); - buf.append(m_signalStore.signedIdentity.pub); - buf.append(accountSignatureKey.data, accountSignatureKey.len); - - signal_buffer *result; - ec_private_key key = {}; - memcpy(key.data, m_signalStore.signedIdentity.priv.data(), m_signalStore.signedIdentity.priv.length()); - if (curve_calculate_signature(m_signalStore.CTX(), &result, &key, buf.data(), buf.length()) != 0) - throw "OnIqPairSuccess: cannot calculate account signature, exiting"; - - account->devicesignature = proto::SetBinary(result->data, result->len); - account->has_devicesignature = true; - signal_buffer_free(result); - } - - { - MBinBuffer key; - if (accountSignatureKey.len == 32) - key.append(KEY_BUNDLE_TYPE, 1); - key.append(accountSignatureKey.data, accountSignatureKey.len); - db_set_blob(0, m_szModuleName, "SignalIdentifierKey", key.data(), (int)key.length()); - } - - proto::CleanBinary(account->accountsignaturekey); account->has_accountsignaturekey = false; - MBinBuffer accountEnc(proto::Serialize(account)); - db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length()); - - proto::ADVDeviceIdentity deviceIdentity(deviceDetails); - - WANodeIq reply(IQ::RESULT); reply << CHAR_PARAM("id", node.getAttr("id")); - - WANode *nodePair = reply.addChild("pair-device-sign"); - - WANode *nodeDeviceIdentity = nodePair->addChild("device-identity"); - nodeDeviceIdentity->addAttr("key-index", deviceIdentity->keyindex); - nodeDeviceIdentity->content.append(accountEnc.data(), accountEnc.length()); - WSSendNode(reply); - } - else throw "OnIqPairSuccess: got reply without identity, exiting"; - } - catch (const char *pErrMsg) { - debugLogA(pErrMsg); - ShutdownSession(); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnIqResult(const WANode &node) -{ - if (auto *pszId = node.getAttr("id")) { - for (auto &it : m_arPacketQueue) { - if (it->szPacketId == pszId) { - it->Execute(this, node); - m_arPacketQueue.remove(it); - break; - } - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnNotifyAny(const WANode &node) -{ - SendAck(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnReceiveChatState(const WANode &node) -{ - if (auto *pUser = FindUser(node.getAttr("from"))) { - if (node.getChild("composing")) { - pUser->m_timer1 = time(0); - pUser->m_timer2 = 0; - setWord(pUser->hContact, "Status", ID_STATUS_ONLINE); - - CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 60); - } - else if (node.getChild("paused")) - CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, PROTOTYPE_CONTACTTYPING_OFF); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnNotifyDevices(const WANode &node) -{ - if (!mir_strcmp(node.getAttr("jid"), m_szJid)) - debugLogA("received list of my own devices"); - SendAck(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnNotifyEncrypt(const WANode &node) -{ - if (!mir_strcmp(node.getAttr("from"), S_WHATSAPP_NET)) - OnIqCountPrekeys(node); - SendAck(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnNotifyPicture(const WANode &node) -{ - if (auto *pszFrom = node.getAttr("from")) - if (m_szJid != pszFrom) - if (auto *pszUser = FindUser(pszFrom)) - ServerFetchAvatar(pszFrom); - - SendAck(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnProcessHandshake(const uint8_t *pData, int cbLen) -{ - proto::HandshakeMessage msg(pData, cbLen); - if (!msg) { - debugLogA("Error parsing data, exiting"); - -LBL_Error: - ShutdownSession(); - return; - } - - auto &static_ = msg->serverhello->static_; - auto &payload_ = msg->serverhello->payload; - auto &ephemeral_ = msg->serverhello->ephemeral; - - m_noise->updateHash(ephemeral_.data, ephemeral_.len); - m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.data); - - MBinBuffer decryptedStatic = m_noise->decrypt(static_.data, static_.len); - m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data()); - - proto::CertChain cert(m_noise->decrypt(payload_.data, payload_.len)); - proto::CertChain__NoiseCertificate__Details details(cert->intermediate->details); - if (details->issuerserial != 0) { - debugLogA("Invalid certificate serial number, exiting"); - goto LBL_Error; - } - - MBinBuffer encryptedPub = m_noise->encrypt(m_noise->noiseKeys.pub.data(), m_noise->noiseKeys.pub.length()); - m_noise->mixIntoKey(m_noise->noiseKeys.priv.data(), ephemeral_.data); - - // create reply - Wa__ClientPayload node; - Wa__ClientPayload__DevicePairingRegistrationData pairingData; - Wa__DeviceProps companion; - Wa__DeviceProps__AppVersion appVersion; - T2Utf devName(m_wszDeviceName); - - MFileVersion v; - Miranda_GetFileVersion(&v); - - // not received our jid from server? generate registration packet then - if (m_szJid.IsEmpty()) { - uint8_t buildHash[16]; - mir_md5_hash((BYTE *)APP_VERSION, sizeof(APP_VERSION) - 1, buildHash); - - appVersion.primary = v[0]; appVersion.has_primary = true; - appVersion.secondary = v[1]; appVersion.has_secondary = true; - appVersion.tertiary = v[2]; appVersion.has_tertiary = true; - appVersion.quaternary = v[3]; appVersion.has_quaternary = true; - - companion.os = devName.get(); - companion.version = &appVersion; - companion.platformtype = WA__DEVICE_PROPS__PLATFORM_TYPE__DESKTOP; companion.has_platformtype = true; - companion.requirefullsync = false; companion.has_requirefullsync = true; - - MBinBuffer buf(proto::Serialize(&companion)); - auto szRegId(encodeBigEndian(getDword(DBKEY_REG_ID))); - auto szKeyId(encodeBigEndian(m_signalStore.preKey.keyid)); - - pairingData.deviceprops = proto::SetBinary(buf.data(), buf.length()); pairingData.has_deviceprops = true; - pairingData.buildhash = proto::SetBinary(buildHash, sizeof(buildHash)); pairingData.has_buildhash = true; - pairingData.eregid = proto::SetBinary(szRegId.c_str(), szRegId.size()); pairingData.has_eregid = true; - pairingData.ekeytype = proto::SetBinary(KEY_BUNDLE_TYPE, 1); pairingData.has_ekeytype = true; - pairingData.eident = proto::SetBinary(m_signalStore.signedIdentity.pub.data(), m_signalStore.signedIdentity.pub.length()); pairingData.has_eident = true; - pairingData.eskeyid = proto::SetBinary(szKeyId.c_str(), szKeyId.size()); pairingData.has_eskeyid = true; - pairingData.eskeyval = proto::SetBinary(m_signalStore.preKey.pub.data(), m_signalStore.preKey.pub.length()); pairingData.has_eskeyval = true; - pairingData.eskeysig = proto::SetBinary(m_signalStore.preKey.signature.data(), m_signalStore.preKey.signature.length()); pairingData.has_eskeysig = true; - node.devicepairingdata = &pairingData; - - node.passive = false; node.has_passive = true; - } - // generate login packet - else { - WAJid jid(m_szJid); - node.username = _atoi64(jid.user); node.has_username = true; - node.device = getDword(DBKEY_DEVICE_ID); node.has_device = true; - node.passive = true; node.has_passive = true; - } - - Wa__ClientPayload__UserAgent__AppVersion userVersion; - userVersion.primary = 2; userVersion.has_primary = true; - userVersion.secondary = 2230; userVersion.has_secondary = true; - userVersion.tertiary = 15; userVersion.has_tertiary = true; - - Wa__ClientPayload__UserAgent userAgent; - userAgent.appversion = &userVersion; - userAgent.platform = WA__CLIENT_PAYLOAD__USER_AGENT__PLATFORM__WEB; userAgent.has_platform = true; - userAgent.releasechannel = WA__CLIENT_PAYLOAD__USER_AGENT__RELEASE_CHANNEL__RELEASE; userAgent.has_releasechannel = true; - userAgent.mcc = "000"; - userAgent.mnc = "000"; - userAgent.osversion = "0.1"; - userAgent.osbuildnumber = "0.1"; - userAgent.manufacturer = ""; - userAgent.device = "Desktop"; - userAgent.localelanguageiso6391 = "en"; - userAgent.localecountryiso31661alpha2 = "US"; - - Wa__ClientPayload__WebInfo webInfo; - webInfo.websubplatform = WA__CLIENT_PAYLOAD__WEB_INFO__WEB_SUB_PLATFORM__WEB_BROWSER; webInfo.has_websubplatform = true; - - node.connecttype = WA__CLIENT_PAYLOAD__CONNECT_TYPE__WIFI_UNKNOWN; node.has_connecttype = true; - node.connectreason = WA__CLIENT_PAYLOAD__CONNECT_REASON__USER_ACTIVATED; node.has_connectreason = true; - node.useragent = &userAgent; - node.webinfo = &webInfo; - - MBinBuffer payload(proto::Serialize(&node)); - MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length()); - - Wa__HandshakeMessage__ClientFinish finish; - finish.payload = {payloadEnc.length(), payloadEnc.data()}; finish.has_payload = true; - finish.static_ = {encryptedPub.length(), encryptedPub.data()}; finish.has_static_ = true; - - Wa__HandshakeMessage handshake; - handshake.clientfinish = &finish; - WSSend(handshake); - - m_noise->finish(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnReceiveFailure(const WANode &node) -{ - m_bTerminated = true; - - ProcessFailure(node.getAttrInt("reason")); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnReceiveInfo(const WANode &node) -{ - if (auto *pChild = node.getFirstChild()) { - if (pChild->title == "offline") { - debugLogA("Processed %d offline events", pChild->getAttrInt("count")); - - // retrieve loaded prekeys count - if (!m_bUpdatedPrekeys) - WSSendNode(WANodeIq(IQ::GET, "encrypt") << XCHILD("count"), &WhatsAppProto::OnIqCountPrekeys); - - auto *pUser = FindUser(m_szJid); - if (pUser->arDevices.getCount() == 0) { - LIST<char> jids(1); - jids.insert(m_szJid.GetBuffer()); - SendUsync(jids, nullptr); - } - - for (auto &it : m_arCollections) { - if (it->version == 0) { - m_impl.m_resyncApp.Stop(); - m_impl.m_resyncApp.Start(1000); - break; - } - } - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead) -{ - MEVENT hEvent = db_event_getById(m_szModuleName, msgId); - if (hEvent == 0) - return; - - if (g_plugin.bHasMessageState) - CallService(MS_MESSAGESTATE_UPDATE, hContact, bRead ? MRD_TYPE_READ : MRD_TYPE_DELIVERED); - - if (bRead) - db_event_markRead(hContact, hEvent); -} - -void WhatsAppProto::OnReceiveReceipt(const WANode &node) -{ - if (!mir_strcmp(node.getAttr("type"), "retry")) { - for (auto &it : node.getChildren()) - if (it->title == "keys") - m_signalStore.injectSession(node.getAttr("from"), &node, it); - return; - } - - if (auto *pUser = FindUser(node.getAttr("from"))) { - bool bRead = mir_strcmp(node.getAttr("type"), "read") == 0; - ProcessReceipt(pUser->hContact, node.getAttr("id"), bRead); - - if (auto *pList = node.getChild("list")) - for (auto &it : pList->getChildren()) - if (it->title == "item") - ProcessReceipt(pUser->hContact, it->getAttr("id"), bRead); - } - - SendAck(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnStreamError(const WANode &node) -{ - m_bTerminated = true; - - if (auto *pszCode = node.getAttr("code")) - ProcessFailure(atoi(pszCode)); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnSuccess(const WANode &) -{ - OnLoggedIn(); - - WSSendNode(WANodeIq(IQ::SET, "passive") << XCHILD("active"), &WhatsAppProto::OnIqDoNothing); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::InitPersistentHandlers() -{ - m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-device", &WhatsAppProto::OnIqPairDevice)); - m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess)); - - m_arPersistent.insert(new WAPersistentHandler("notification", "devices", 0, 0, &WhatsAppProto::OnNotifyDevices)); - m_arPersistent.insert(new WAPersistentHandler("notification", "encrypt", 0, 0, &WhatsAppProto::OnNotifyEncrypt)); - m_arPersistent.insert(new WAPersistentHandler("notification", "picture", 0, 0, &WhatsAppProto::OnNotifyPicture)); - m_arPersistent.insert(new WAPersistentHandler("notification", "account_sync", 0, 0, &WhatsAppProto::OnAccountSync)); - m_arPersistent.insert(new WAPersistentHandler("notification", "server_sync", 0, 0, &WhatsAppProto::OnServerSync)); - m_arPersistent.insert(new WAPersistentHandler("notification", 0, 0, 0, &WhatsAppProto::OnNotifyAny)); - - m_arPersistent.insert(new WAPersistentHandler("ack", 0, 0, 0, &WhatsAppProto::OnReceiveAck)); - m_arPersistent.insert(new WAPersistentHandler("ib", 0, 0, 0, &WhatsAppProto::OnReceiveInfo)); - m_arPersistent.insert(new WAPersistentHandler("failure", 0, 0, 0, &WhatsAppProto::OnReceiveFailure)); - m_arPersistent.insert(new WAPersistentHandler("message", 0, 0, 0, &WhatsAppProto::OnReceiveMessage)); - m_arPersistent.insert(new WAPersistentHandler("receipt", 0, 0, 0, &WhatsAppProto::OnReceiveReceipt)); - m_arPersistent.insert(new WAPersistentHandler("chatstates", 0, 0, 0, &WhatsAppProto::OnReceiveChatState)); - m_arPersistent.insert(new WAPersistentHandler("stream:error", 0, 0, 0, &WhatsAppProto::OnStreamError)); - m_arPersistent.insert(new WAPersistentHandler("success", 0, 0, 0, &WhatsAppProto::OnSuccess)); - - m_arPersistent.insert(new WAPersistentHandler(0, "result", 0, 0, &WhatsAppProto::OnIqResult)); -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::OnAccountSync(const WANode &node)
+{
+ if (auto *pList = node.getChild("devices")) {
+ auto *pUser = FindUser(m_szJid);
+ pUser->arDevices.destroy();
+
+ for (auto &it : pList->getChildren())
+ if (it->title == "device")
+ pUser->arDevices.insert(new WAJid(it->getAttr("jid"), it->getAttrInt("key-index")));
+ }
+
+ if (auto *pList = node.getChild("blocklist"))
+ for (auto &it : pList->getChildren())
+ if (it->title == "item") {
+ auto *pUser = AddUser(it->getAttr("jid"), false);
+ Contact::Hide(pUser->hContact, 0 == mir_strcmp(it->getAttr("action"), "block"));
+ }
+
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqBlockList(const WANode &node)
+{
+ for (auto &it : node.getChild("list")->getChildren()) {
+ auto *pUser = AddUser(it->getAttr("jid"), false);
+ Contact::Hide(pUser->hContact);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqCountPrekeys(const WANode &node)
+{
+ m_bUpdatedPrekeys = true;
+
+ int iCount = node.getChild("count")->getAttrInt("value");
+ if (iCount < 5)
+ UploadMorePrekeys();
+}
+
+void WhatsAppProto::UploadMorePrekeys()
+{
+ WANodeIq iq(IQ::SET, "encrypt");
+
+ auto regId = encodeBigEndian(getDword(DBKEY_REG_ID));
+ iq.addChild("registration")->content.append(regId.c_str(), regId.size());
+
+ iq.addChild("type")->content.append(KEY_BUNDLE_TYPE, 1);
+ iq.addChild("identity")->content.append(m_signalStore.signedIdentity.pub);
+
+ const int PORTION = 10;
+ m_signalStore.generatePrekeys(PORTION);
+
+ int iStart = getDword(DBKEY_PREKEY_UPLOAD_ID, 1);
+ auto *n = iq.addChild("list");
+ for (int i = 0; i < PORTION; i++) {
+ auto *nKey = n->addChild("key");
+
+ int keyId = iStart + i;
+ auto encId = encodeBigEndian(keyId, 3);
+ nKey->addChild("id")->content.append(encId.c_str(), encId.size());
+ nKey->addChild("value")->content.append(getBlob(CMStringA(FORMAT, "PreKey%dPublic", keyId)));
+ }
+ setDword(DBKEY_PREKEY_UPLOAD_ID, iStart + PORTION);
+
+ auto *skey = iq.addChild("skey");
+
+ auto encId = encodeBigEndian(m_signalStore.preKey.keyid, 3);
+ skey->addChild("id")->content.append(encId.c_str(), encId.size());
+ skey->addChild("value")->content.append(m_signalStore.preKey.pub);
+ skey->addChild("signature")->content.append(m_signalStore.preKey.signature);
+
+ WSSendNode(iq, &WhatsAppProto::OnIqDoNothing);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqDoNothing(const WANode&)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqGetKeys(const WANode &node, void *pUserInfo)
+{
+ for (auto &it : node.getChild("list")->getChildren())
+ if (it->title == "user")
+ m_signalStore.injectSession(it->getAttr("jid"), it, it);
+
+ // don't forget to send delayed message when all keys are retrieved
+ if (pUserInfo)
+ FinishTask((WASendTask *)pUserInfo);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqGetUsync(const WANode &node, void *pUserInfo)
+{
+ WANodeIq iq(IQ::GET, "encrypt");
+ auto *pKey = iq.addChild("key");
+
+ for (auto *nUser : node.getChild("usync")->getChild("list")->getChildren()) {
+ auto *pszJid = nUser->getAttr("jid");
+
+ auto *pUser = AddUser(pszJid, false);
+ pUser->bDeviceInit = true;
+ pUser->arDevices.destroy();
+
+ if (auto *pList = nUser->getChild("devices")->getChild("device-list"))
+ for (auto &it : pList->getChildren())
+ if (it->title == "device")
+ pUser->arDevices.insert(new WAJid(pszJid, it->getAttrInt("id")));
+
+ for (auto &it : pUser->arDevices) {
+ auto blob = getBlob(MSignalSession(it->user, it->device).getSetting());
+ if (blob.isEmpty())
+ pKey->addChild("user")->addAttr("jid", it->toString());
+ }
+ }
+
+ if (pKey->getChildren().getCount() > 0)
+ WSSendNode(iq, &WhatsAppProto::OnIqGetKeys, pUserInfo);
+ else if (pUserInfo)
+ FinishTask((WASendTask *)pUserInfo);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqPairDevice(const WANode &node)
+{
+ WSSendNode(WANodeIq(IQ::RESULT) << CHAR_PARAM("id", node.getAttr("id")));
+
+ if (auto *pRef = node.getChild("pair-device")->getChild("ref")) {
+ ShowQrCode(pRef->getBody());
+ }
+ else {
+ debugLogA("OnIqPairDevice: got reply without ref, exiting");
+ ShutdownSession();
+ }
+}
+
+void WhatsAppProto::OnIqPairSuccess(const WANode &node)
+{
+ CloseQrDialog();
+
+ auto *pRoot = node.getChild("pair-success");
+
+ try {
+ if (auto *pPlatform = pRoot->getChild("platform"))
+ debugLogA("Got response from platform: %s", pPlatform->getBody().c_str());
+
+ if (auto *pBiz = pRoot->getChild("biz"))
+ if (auto *pszName = pBiz->getAttr("name"))
+ setUString("Nick", pszName);
+
+ if (auto *pDevice = pRoot->getChild("device")) {
+ if (auto *pszJid = pDevice->getAttr("jid")) {
+ WAJid jid(pszJid);
+ m_szJid = jid.user + "@" + jid.server;
+ m_arUsers.insert(new WAUser(0, m_szJid, false));
+
+ setUString(DBKEY_ID, m_szJid);
+ setDword(DBKEY_DEVICE_ID, jid.device);
+ }
+ }
+ else throw "OnIqPairSuccess: got reply without device info, exiting";
+
+ if (auto *pIdentity = pRoot->getChild("device-identity")) {
+ proto::ADVSignedDeviceIdentityHMAC payload(pIdentity->content);
+
+ auto &hmac = payload->hmac;
+ auto &details = payload->details;
+ {
+ // check details signature using HMAC
+ uint8_t signature[32];
+ unsigned int out_len = sizeof(signature);
+ MBinBuffer secret(getBlob(DBKEY_SECRET_KEY));
+ HMAC(EVP_sha256(), secret.data(), (int)secret.length(), details.data, (int)details.len, signature, &out_len);
+ if (memcmp(hmac.data, signature, sizeof(signature)))
+ throw "OnIqPairSuccess: got reply with invalid details signature, exiting";
+ }
+
+ proto::ADVSignedDeviceIdentity account(details);
+
+ auto &deviceDetails = account->details;
+ auto &accountSignature = account->accountsignature;
+ auto &accountSignatureKey = account->accountsignaturekey;
+ {
+ MBinBuffer buf;
+ buf.append("\x06\x00", 2);
+ buf.append(deviceDetails.data, deviceDetails.len);
+ buf.append(m_signalStore.signedIdentity.pub);
+
+ ec_public_key key = {};
+ memcpy(key.data, accountSignatureKey.data, sizeof(key.data));
+ if (1 != curve_verify_signature(&key, buf.data(), buf.length(), accountSignature.data, accountSignature.len))
+ throw "OnIqPairSuccess: got reply with invalid account signature, exiting";
+ }
+ debugLogA("Received valid account signature");
+ {
+ MBinBuffer buf;
+ buf.append("\x06\x01", 2);
+ buf.append(deviceDetails.data, deviceDetails.len);
+ buf.append(m_signalStore.signedIdentity.pub);
+ buf.append(accountSignatureKey.data, accountSignatureKey.len);
+
+ signal_buffer *result;
+ ec_private_key key = {};
+ memcpy(key.data, m_signalStore.signedIdentity.priv.data(), m_signalStore.signedIdentity.priv.length());
+ if (curve_calculate_signature(m_signalStore.CTX(), &result, &key, buf.data(), buf.length()) != 0)
+ throw "OnIqPairSuccess: cannot calculate account signature, exiting";
+
+ account->devicesignature = proto::SetBinary(result->data, result->len);
+ account->has_devicesignature = true;
+ signal_buffer_free(result);
+ }
+
+ {
+ MBinBuffer key;
+ if (accountSignatureKey.len == 32)
+ key.append(KEY_BUNDLE_TYPE, 1);
+ key.append(accountSignatureKey.data, accountSignatureKey.len);
+ db_set_blob(0, m_szModuleName, "SignalIdentifierKey", key.data(), (int)key.length());
+ }
+
+ proto::CleanBinary(account->accountsignaturekey); account->has_accountsignaturekey = false;
+ MBinBuffer accountEnc(proto::Serialize(account));
+ db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length());
+
+ proto::ADVDeviceIdentity deviceIdentity(deviceDetails);
+
+ WANodeIq reply(IQ::RESULT); reply << CHAR_PARAM("id", node.getAttr("id"));
+
+ WANode *nodePair = reply.addChild("pair-device-sign");
+
+ WANode *nodeDeviceIdentity = nodePair->addChild("device-identity");
+ nodeDeviceIdentity->addAttr("key-index", deviceIdentity->keyindex);
+ nodeDeviceIdentity->content.append(accountEnc.data(), accountEnc.length());
+ WSSendNode(reply);
+ }
+ else throw "OnIqPairSuccess: got reply without identity, exiting";
+ }
+ catch (const char *pErrMsg) {
+ debugLogA(pErrMsg);
+ ShutdownSession();
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqResult(const WANode &node)
+{
+ if (auto *pszId = node.getAttr("id")) {
+ for (auto &it : m_arPacketQueue) {
+ if (it->szPacketId == pszId) {
+ it->Execute(this, node);
+ m_arPacketQueue.remove(it);
+ break;
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyAny(const WANode &node)
+{
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveChatState(const WANode &node)
+{
+ if (auto *pUser = FindUser(node.getAttr("from"))) {
+ if (node.getChild("composing")) {
+ pUser->m_timer1 = time(0);
+ pUser->m_timer2 = 0;
+ setWord(pUser->hContact, "Status", ID_STATUS_ONLINE);
+
+ CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 60);
+ }
+ else if (node.getChild("paused"))
+ CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, PROTOTYPE_CONTACTTYPING_OFF);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyDevices(const WANode &node)
+{
+ if (!mir_strcmp(node.getAttr("jid"), m_szJid))
+ debugLogA("received list of my own devices");
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyEncrypt(const WANode &node)
+{
+ if (!mir_strcmp(node.getAttr("from"), S_WHATSAPP_NET))
+ OnIqCountPrekeys(node);
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyPicture(const WANode &node)
+{
+ if (auto *pszFrom = node.getAttr("from"))
+ if (m_szJid != pszFrom)
+ if (auto *pszUser = FindUser(pszFrom))
+ ServerFetchAvatar(pszFrom);
+
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnProcessHandshake(const uint8_t *pData, int cbLen)
+{
+ proto::HandshakeMessage msg(pData, cbLen);
+ if (!msg) {
+ debugLogA("Error parsing data, exiting");
+
+LBL_Error:
+ ShutdownSession();
+ return;
+ }
+
+ auto &static_ = msg->serverhello->static_;
+ auto &payload_ = msg->serverhello->payload;
+ auto &ephemeral_ = msg->serverhello->ephemeral;
+
+ m_noise->updateHash(ephemeral_.data, ephemeral_.len);
+ m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.data);
+
+ MBinBuffer decryptedStatic = m_noise->decrypt(static_.data, static_.len);
+ m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data());
+
+ proto::CertChain cert(m_noise->decrypt(payload_.data, payload_.len));
+ proto::CertChain__NoiseCertificate__Details details(cert->intermediate->details);
+ if (details->issuerserial != 0) {
+ debugLogA("Invalid certificate serial number, exiting");
+ goto LBL_Error;
+ }
+
+ MBinBuffer encryptedPub = m_noise->encrypt(m_noise->noiseKeys.pub.data(), m_noise->noiseKeys.pub.length());
+ m_noise->mixIntoKey(m_noise->noiseKeys.priv.data(), ephemeral_.data);
+
+ // create reply
+ Wa__ClientPayload node;
+ Wa__ClientPayload__DevicePairingRegistrationData pairingData;
+ Wa__DeviceProps companion;
+ Wa__DeviceProps__AppVersion appVersion;
+ T2Utf devName(m_wszDeviceName);
+
+ MFileVersion v;
+ Miranda_GetFileVersion(&v);
+
+ // not received our jid from server? generate registration packet then
+ if (m_szJid.IsEmpty()) {
+ uint8_t buildHash[16];
+ mir_md5_hash((BYTE *)APP_VERSION, sizeof(APP_VERSION) - 1, buildHash);
+
+ appVersion.primary = v[0]; appVersion.has_primary = true;
+ appVersion.secondary = v[1]; appVersion.has_secondary = true;
+ appVersion.tertiary = v[2]; appVersion.has_tertiary = true;
+ appVersion.quaternary = v[3]; appVersion.has_quaternary = true;
+
+ companion.os = devName.get();
+ companion.version = &appVersion;
+ companion.platformtype = WA__DEVICE_PROPS__PLATFORM_TYPE__DESKTOP; companion.has_platformtype = true;
+ companion.requirefullsync = false; companion.has_requirefullsync = true;
+
+ MBinBuffer buf(proto::Serialize(&companion));
+ auto szRegId(encodeBigEndian(getDword(DBKEY_REG_ID)));
+ auto szKeyId(encodeBigEndian(m_signalStore.preKey.keyid));
+
+ pairingData.deviceprops = proto::SetBinary(buf.data(), buf.length()); pairingData.has_deviceprops = true;
+ pairingData.buildhash = proto::SetBinary(buildHash, sizeof(buildHash)); pairingData.has_buildhash = true;
+ pairingData.eregid = proto::SetBinary(szRegId.c_str(), szRegId.size()); pairingData.has_eregid = true;
+ pairingData.ekeytype = proto::SetBinary(KEY_BUNDLE_TYPE, 1); pairingData.has_ekeytype = true;
+ pairingData.eident = proto::SetBinary(m_signalStore.signedIdentity.pub.data(), m_signalStore.signedIdentity.pub.length()); pairingData.has_eident = true;
+ pairingData.eskeyid = proto::SetBinary(szKeyId.c_str(), szKeyId.size()); pairingData.has_eskeyid = true;
+ pairingData.eskeyval = proto::SetBinary(m_signalStore.preKey.pub.data(), m_signalStore.preKey.pub.length()); pairingData.has_eskeyval = true;
+ pairingData.eskeysig = proto::SetBinary(m_signalStore.preKey.signature.data(), m_signalStore.preKey.signature.length()); pairingData.has_eskeysig = true;
+ node.devicepairingdata = &pairingData;
+
+ node.passive = false; node.has_passive = true;
+ }
+ // generate login packet
+ else {
+ WAJid jid(m_szJid);
+ node.username = _atoi64(jid.user); node.has_username = true;
+ node.device = getDword(DBKEY_DEVICE_ID); node.has_device = true;
+ node.passive = true; node.has_passive = true;
+ }
+
+ Wa__ClientPayload__UserAgent__AppVersion userVersion;
+ userVersion.primary = 2; userVersion.has_primary = true;
+ userVersion.secondary = 2230; userVersion.has_secondary = true;
+ userVersion.tertiary = 15; userVersion.has_tertiary = true;
+
+ Wa__ClientPayload__UserAgent userAgent;
+ userAgent.appversion = &userVersion;
+ userAgent.platform = WA__CLIENT_PAYLOAD__USER_AGENT__PLATFORM__WEB; userAgent.has_platform = true;
+ userAgent.releasechannel = WA__CLIENT_PAYLOAD__USER_AGENT__RELEASE_CHANNEL__RELEASE; userAgent.has_releasechannel = true;
+ userAgent.mcc = "000";
+ userAgent.mnc = "000";
+ userAgent.osversion = "0.1";
+ userAgent.osbuildnumber = "0.1";
+ userAgent.manufacturer = "";
+ userAgent.device = "Desktop";
+ userAgent.localelanguageiso6391 = "en";
+ userAgent.localecountryiso31661alpha2 = "US";
+
+ Wa__ClientPayload__WebInfo webInfo;
+ webInfo.websubplatform = WA__CLIENT_PAYLOAD__WEB_INFO__WEB_SUB_PLATFORM__WEB_BROWSER; webInfo.has_websubplatform = true;
+
+ node.connecttype = WA__CLIENT_PAYLOAD__CONNECT_TYPE__WIFI_UNKNOWN; node.has_connecttype = true;
+ node.connectreason = WA__CLIENT_PAYLOAD__CONNECT_REASON__USER_ACTIVATED; node.has_connectreason = true;
+ node.useragent = &userAgent;
+ node.webinfo = &webInfo;
+
+ MBinBuffer payload(proto::Serialize(&node));
+ MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length());
+
+ Wa__HandshakeMessage__ClientFinish finish;
+ finish.payload = {payloadEnc.length(), payloadEnc.data()}; finish.has_payload = true;
+ finish.static_ = {encryptedPub.length(), encryptedPub.data()}; finish.has_static_ = true;
+
+ Wa__HandshakeMessage handshake;
+ handshake.clientfinish = &finish;
+ WSSend(handshake);
+
+ m_noise->finish();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveFailure(const WANode &node)
+{
+ m_bTerminated = true;
+
+ ProcessFailure(node.getAttrInt("reason"));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveInfo(const WANode &node)
+{
+ if (auto *pChild = node.getFirstChild()) {
+ if (pChild->title == "offline") {
+ debugLogA("Processed %d offline events", pChild->getAttrInt("count"));
+
+ // retrieve loaded prekeys count
+ if (!m_bUpdatedPrekeys)
+ WSSendNode(WANodeIq(IQ::GET, "encrypt") << XCHILD("count"), &WhatsAppProto::OnIqCountPrekeys);
+
+ auto *pUser = FindUser(m_szJid);
+ if (pUser->arDevices.getCount() == 0) {
+ LIST<char> jids(1);
+ jids.insert(m_szJid.GetBuffer());
+ SendUsync(jids, nullptr);
+ }
+
+ for (auto &it : m_arCollections) {
+ if (it->version == 0) {
+ m_impl.m_resyncApp.Stop();
+ m_impl.m_resyncApp.Start(1000);
+ break;
+ }
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead)
+{
+ MEVENT hEvent = db_event_getById(m_szModuleName, msgId);
+ if (hEvent == 0)
+ return;
+
+ if (g_plugin.bHasMessageState)
+ CallService(MS_MESSAGESTATE_UPDATE, hContact, bRead ? MRD_TYPE_READ : MRD_TYPE_DELIVERED);
+
+ if (bRead)
+ db_event_markRead(hContact, hEvent);
+}
+
+void WhatsAppProto::OnReceiveReceipt(const WANode &node)
+{
+ if (!mir_strcmp(node.getAttr("type"), "retry")) {
+ for (auto &it : node.getChildren())
+ if (it->title == "keys")
+ m_signalStore.injectSession(node.getAttr("from"), &node, it);
+ return;
+ }
+
+ if (auto *pUser = FindUser(node.getAttr("from"))) {
+ bool bRead = mir_strcmp(node.getAttr("type"), "read") == 0;
+ ProcessReceipt(pUser->hContact, node.getAttr("id"), bRead);
+
+ if (auto *pList = node.getChild("list"))
+ for (auto &it : pList->getChildren())
+ if (it->title == "item")
+ ProcessReceipt(pUser->hContact, it->getAttr("id"), bRead);
+ }
+
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnStreamError(const WANode &node)
+{
+ m_bTerminated = true;
+
+ if (auto *pszCode = node.getAttr("code"))
+ ProcessFailure(atoi(pszCode));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnSuccess(const WANode &)
+{
+ OnLoggedIn();
+
+ WSSendNode(WANodeIq(IQ::SET, "passive") << XCHILD("active"), &WhatsAppProto::OnIqDoNothing);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::InitPersistentHandlers()
+{
+ m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-device", &WhatsAppProto::OnIqPairDevice));
+ m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess));
+
+ m_arPersistent.insert(new WAPersistentHandler("notification", "devices", 0, 0, &WhatsAppProto::OnNotifyDevices));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "encrypt", 0, 0, &WhatsAppProto::OnNotifyEncrypt));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "picture", 0, 0, &WhatsAppProto::OnNotifyPicture));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "account_sync", 0, 0, &WhatsAppProto::OnAccountSync));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "server_sync", 0, 0, &WhatsAppProto::OnServerSync));
+ m_arPersistent.insert(new WAPersistentHandler("notification", 0, 0, 0, &WhatsAppProto::OnNotifyAny));
+
+ m_arPersistent.insert(new WAPersistentHandler("ack", 0, 0, 0, &WhatsAppProto::OnReceiveAck));
+ m_arPersistent.insert(new WAPersistentHandler("ib", 0, 0, 0, &WhatsAppProto::OnReceiveInfo));
+ m_arPersistent.insert(new WAPersistentHandler("failure", 0, 0, 0, &WhatsAppProto::OnReceiveFailure));
+ m_arPersistent.insert(new WAPersistentHandler("message", 0, 0, 0, &WhatsAppProto::OnReceiveMessage));
+ m_arPersistent.insert(new WAPersistentHandler("receipt", 0, 0, 0, &WhatsAppProto::OnReceiveReceipt));
+ m_arPersistent.insert(new WAPersistentHandler("chatstates", 0, 0, 0, &WhatsAppProto::OnReceiveChatState));
+ m_arPersistent.insert(new WAPersistentHandler("stream:error", 0, 0, 0, &WhatsAppProto::OnStreamError));
+ m_arPersistent.insert(new WAPersistentHandler("success", 0, 0, 0, &WhatsAppProto::OnSuccess));
+
+ m_arPersistent.insert(new WAPersistentHandler(0, "result", 0, 0, &WhatsAppProto::OnIqResult));
+}
diff --git a/protocols/WhatsApp/src/main.cpp b/protocols/WhatsApp/src/main.cpp index dc4c252927..318a027b76 100644 --- a/protocols/WhatsApp/src/main.cpp +++ b/protocols/WhatsApp/src/main.cpp @@ -1,71 +1,71 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" -#include "version.h" - -CMPlugin g_plugin; - -PLUGININFOEX pluginInfo = { - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, //not transient - // {008B9CE1-154B-44E4-9823-97C1AAB00C3C} - { 0x8b9ce1, 0x154b, 0x44e4, { 0x98, 0x23, 0x97, 0xc1, 0xaa, 0xb0, 0xc, 0x3c }} -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Interface information - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; - -///////////////////////////////////////////////////////////////////////////////////////// - -CMPlugin::CMPlugin() : - ACCPROTOPLUGIN<WhatsAppProto>(MODULENAME, pluginInfo) -{ - SetUniqueId(DBKEY_ID); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Load - -static int OnPluginLoaded(WPARAM, LPARAM) -{ - g_plugin.bHasMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE); - return 0; -} - -int CMPlugin::Load() -{ - HookEvent(ME_SYSTEM_MODULELOAD, OnPluginLoaded); - HookEvent(ME_SYSTEM_MODULEUNLOAD, OnPluginLoaded); - OnPluginLoaded(0, 0); - - // special netlib user for reading avatars, blobs etc via HTTP protocol - NETLIBUSER nlu = {}; - nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; - nlu.szSettingsModule = "WhatsApp"; - nlu.szDescriptiveName.w = TranslateT("WhatsApp (HTTP)"); - hAvatarUser = Netlib_RegisterUser(&nlu); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Unload - -int CMPlugin::Unload() -{ - Netlib_CloseHandle(hAvatarConn); - Netlib_CloseHandle(hAvatarUser); - return 0; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+#include "version.h"
+
+CMPlugin g_plugin;
+
+PLUGININFOEX pluginInfo = {
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE, //not transient
+ // {008B9CE1-154B-44E4-9823-97C1AAB00C3C}
+ { 0x8b9ce1, 0x154b, 0x44e4, { 0x98, 0x23, 0x97, 0xc1, 0xaa, 0xb0, 0xc, 0x3c }}
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Interface information
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMPlugin::CMPlugin() :
+ ACCPROTOPLUGIN<WhatsAppProto>(MODULENAME, pluginInfo)
+{
+ SetUniqueId(DBKEY_ID);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Load
+
+static int OnPluginLoaded(WPARAM, LPARAM)
+{
+ g_plugin.bHasMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE);
+ return 0;
+}
+
+int CMPlugin::Load()
+{
+ HookEvent(ME_SYSTEM_MODULELOAD, OnPluginLoaded);
+ HookEvent(ME_SYSTEM_MODULEUNLOAD, OnPluginLoaded);
+ OnPluginLoaded(0, 0);
+
+ // special netlib user for reading avatars, blobs etc via HTTP protocol
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = "WhatsApp";
+ nlu.szDescriptiveName.w = TranslateT("WhatsApp (HTTP)");
+ hAvatarUser = Netlib_RegisterUser(&nlu);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Unload
+
+int CMPlugin::Unload()
+{
+ Netlib_CloseHandle(hAvatarConn);
+ Netlib_CloseHandle(hAvatarUser);
+ return 0;
+}
diff --git a/protocols/WhatsApp/src/message.cpp b/protocols/WhatsApp/src/message.cpp index e8d2b775ec..250a63e4d1 100644 --- a/protocols/WhatsApp/src/message.cpp +++ b/protocols/WhatsApp/src/message.cpp @@ -1,502 +1,502 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -void WhatsAppProto::OnReceiveMessage(const WANode &node) -{ - auto *msgId = node.getAttr("id"); - auto *msgType = node.getAttr("type"); - auto *msgFrom = node.getAttr("from"); - auto *category = node.getAttr("category"); - auto *recipient = node.getAttr("recipient"); - auto *participant = node.getAttr("participant"); - - if (msgType == nullptr || msgFrom == nullptr || msgId == nullptr) { - debugLogA("bad message received: <%s> <%s> <%s>", msgType, msgFrom, msgId); - return; - } - - SendAck(node); - - MEVENT hEvent = db_event_getById(m_szModuleName, msgId); - if (hEvent) { - debugLogA("this message is already processed: %s", msgId); - return; - } - - WAMSG type; - WAJid jid(msgFrom); - CMStringA szAuthor, szChatId; - - if (node.getAttr("offline")) - type.bOffline = true; - - // message from one user to another - if (jid.isUser()) { - if (recipient) { - if (m_szJid != msgFrom) { - debugLogA("strange message: with recipient, but not from me"); - return; - } - szChatId = recipient; - } - else szChatId = msgFrom; - - type.bPrivateChat = true; - szAuthor = msgFrom; - } - else if (jid.isGroup()) { - if (!participant) { - debugLogA("strange message: from group, but without participant"); - return; - } - - type.bGroupChat = true; - szAuthor = participant; - szChatId = msgFrom; - } - else if (jid.isBroadcast()) { - if (!participant) { - debugLogA("strange message: from group, but without participant"); - return; - } - - bool bIsMe = m_szJid == participant; - 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; - } - else { - debugLogA("invalid message type"); - return; - } - - CMStringA szSender = (type.bPrivateChat) ? szAuthor : szChatId; - bool bFromMe = (m_szJid == msgFrom); - if (!bFromMe && participant) - bFromMe = m_szJid == participant; - - Wa__MessageKey key; - key.remotejid = szChatId.GetBuffer(); - key.id = (char*)msgId; - key.fromme = bFromMe; key.has_fromme = true; - if (participant) - key.participant = (char*)participant; - - Wa__WebMessageInfo msg; - msg.key = &key; - msg.messagetimestamp = _atoi64(node.getAttr("t")); msg.has_messagetimestamp = true; - msg.pushname = (char*)node.getAttr("notify"); - if (bFromMe) - msg.status = WA__WEB_MESSAGE_INFO__STATUS__SERVER_ACK, msg.has_status = true; - - int iDecryptable = 0; - - for (auto &it : node.getChildren()) { - if (it->title != "enc" || it->content.length() == 0) - continue; - - MBinBuffer msgBody; - auto *pszType = it->getAttr("type"); - try { - if (!mir_strcmp(pszType, "pkmsg") || !mir_strcmp(pszType, "msg")) { - CMStringA szUser = (WAJid(szSender).isUser()) ? szSender : szAuthor; - msgBody = m_signalStore.decryptSignalProto(szUser, pszType, it->content); - } - else if (!mir_strcmp(pszType, "skmsg")) { - msgBody = m_signalStore.decryptGroupSignalProto(szSender, szAuthor, it->content); - } - else throw "Invalid e2e type"; - - if (msgBody.isEmpty()) - throw "Invalid e2e message"; - - iDecryptable++; - - proto::Message encMsg(unpadBuffer16(msgBody)); - if (!encMsg) - throw "Invalid decoded message"; - - if (encMsg->devicesentmessage) - msg.message = encMsg->devicesentmessage->message; - else - msg.message = encMsg; - - if (encMsg->senderkeydistributionmessage) - m_signalStore.processSenderKeyMessage(szAuthor, encMsg->senderkeydistributionmessage); - - ProcessMessage(type, msg); - - // 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 decryption failed with error: %s", pszError); - } - - if (!iDecryptable) { - debugLogA("Nothing to decrypt"); - return; - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static const Wa__Message* getBody(const Wa__Message *message) -{ - if (message->ephemeralmessage) { - auto *pMsg = message->ephemeralmessage->message; - return (pMsg->viewoncemessage) ? pMsg->viewoncemessage->message : pMsg; - } - - if (message->viewoncemessage) - return message->viewoncemessage->message; - - return message; -} - -void WhatsAppProto::ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg) -{ - auto *key = msg.key; - auto *body = getBody(msg.message); - - debugLogA("Got a message: %s", protobuf_c_text_to_string(&msg).c_str()); - - uint32_t timestamp = msg.messagetimestamp; - char *participant = key->participant, *chatId; - auto *msgId = key->id; - - if (type.bPrivateChat || type.bGroupChat) - chatId = key->remotejid; - else - chatId = (participant) ? participant : key->remotejid; - - WAJid jidFrom(chatId); jidFrom.device = 0; - WAUser *pUser = AddUser(jidFrom.toString(), false); - - if (!key->fromme && msg.pushname && pUser && !pUser->bIsGroupChat) - setUString(pUser->hContact, "Nick", msg.pushname); - - // try to extract some text - if (pUser) { - CMStringA szMessageText(GetMessageText(body)); - if (!szMessageText.IsEmpty()) { - // for chats & group chats store message in profile - if (type.bPrivateChat || type.bGroupChat) { - PROTORECVEVENT pre = {}; - pre.timestamp = timestamp; - pre.szMessage = szMessageText.GetBuffer(); - pre.szMsgId = msgId; - if (type.bOffline) - pre.flags |= PREF_CREATEREAD; - if (key->fromme) - pre.flags |= PREF_SENT; - ProtoChainRecvMsg(pUser->hContact, &pre); - - if (pUser->bIsGroupChat) { - GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE}; - gce.dwFlags = GCEF_UTF8; - gce.pszID.a = pUser->szId; - gce.pszUID.a = participant; - gce.bIsMe = key->fromme; - gce.pszText.a = szMessageText.GetBuffer(); - gce.time = timestamp; - Chat_Event(&gce); - } - } - // translate statuses into status messages - else if (type.bOtherStatus || type.bDirectStatus || type.bPeerBroadcast || type.bOtherBroadcast) { - setUString(pUser->hContact, "StatusMsg", szMessageText); - } - } - } - - if (body->protocolmessage) { - auto *protoMsg = body->protocolmessage; - switch (protoMsg->type) { - case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_SYNC_KEY_SHARE: - for (int i = 0; i < protoMsg->appstatesynckeyshare->n_keys; i++) { - auto *it = protoMsg->appstatesynckeyshare->keys[i]; - 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.data, (unsigned)keydata.len); - } - break; - - case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_FATAL_EXCEPTION_NOTIFICATION: - m_impl.m_resyncApp.Stop(); - m_impl.m_resyncApp.Start(10000); - break; - - case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__HISTORY_SYNC_NOTIFICATION: - debugLogA("History sync notification"); - if (auto *pHist = protoMsg->historysyncnotification) { - MBinBuffer buf(DownloadEncryptedFile(directPath2url(pHist->directpath), pHist->mediakey, "History")); - if (!buf.isEmpty()) { - MBinBuffer inflate(unzip(unpadBuffer16(buf))); - - proto::HistorySync sync(inflate); - if (sync) - ProcessHistorySync(sync); - } - } - break; - - case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__REVOKE: - break; - - case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__EPHEMERAL_SETTING: - if (pUser) { - setDword(pUser->hContact, DBKEY_EPHEMERAL_TS, timestamp); - setDword(pUser->hContact, DBKEY_EPHEMERAL_EXPIRE, protoMsg->ephemeralexpiration); - } - break; - } - } - else if (body->reactionmessage) { - debugLogA("Got a reaction to a message"); - } - else if (msg.has_messagestubtype) { - switch (msg.messagestubtype) { - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_LEAVE: - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_REMOVE: - debugLogA("Participant %s removed from chat", participant); - break; - - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD: - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_INVITE: - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD_REQUEST_JOIN: - debugLogA("Participant %s added to chat", participant); - break; - - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_DEMOTE: - debugLogA("Participant %s demoted", participant); - break; - - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_PROMOTE: - debugLogA("Participant %s promoted", participant); - break; - - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_ANNOUNCE: - debugLogA("Groupchat announce", participant); - break; - - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_RESTRICT: - debugLogA("Groupchat restriction", participant); - break; - - case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_SUBJECT: - debugLogA("Groupchat subject was changed", participant); - break; - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnReceiveAck(const WANode &node) -{ - auto *pUser = FindUser(node.getAttr("from")); - if (pUser == nullptr) - return; - - if (!mir_strcmp(node.getAttr("class"), "message")) { - WAOwnMessage tmp(0, 0, node.getAttr("id")); - { - mir_cslock lck(m_csOwnMessages); - if (auto *pOwn = m_arOwnMsgs.find(&tmp)) { - tmp.pktId = pOwn->pktId; - m_arOwnMsgs.remove(pOwn); - } - else return; - } - ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)tmp.pktId, (LPARAM)tmp.szMessageId.c_str()); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool WhatsAppProto::CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig) -{ - int type = 0; - - try { - MBinBuffer pBuffer(m_signalStore.encryptSignalProto(jid, orig, type)); - - auto *pNode = pParticipants->addChild("to"); - pNode->addAttr("jid", jid.toString()); - - auto *pEnc = pNode->addChild("enc"); - *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", (type == 3) ? "pkmsg" : "msg"); - pEnc->content.assign(pBuffer.data(), pBuffer.length()); - } - catch (const char *) { - } - - return type == 3; -} - -int WhatsAppProto::SendTextMessage(const char *jid, const char *pszMsg) -{ - WAJid toJid(jid); - - // send task creation - auto *pTask = new WASendTask(jid); - - // basic message - Wa__Message__ExtendedTextMessage extMessage; - extMessage.text = (char *)pszMsg; - - Wa__Message body; - body.extendedtextmessage = &extMessage; - - LIST<char> arCheckList(1); - if (toJid.isGroup()) { - MBinBuffer encodedMsg(proto::Serialize(&body)); - padBuffer16(encodedMsg); - - MBinBuffer skmsgKey; - MBinBuffer cipherText(m_signalStore.encryptSenderKey(toJid, m_szJid, encodedMsg, skmsgKey)); - - auto *pEnc = pTask->payLoad.addChild("enc"); - *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", "skmsg"); - pEnc->content.append(cipherText.data(), cipherText.length()); - - Wa__Message__SenderKeyDistributionMessage sentBody; - sentBody.axolotlsenderkeydistributionmessage.data = skmsgKey.data(); - sentBody.axolotlsenderkeydistributionmessage.len = skmsgKey.length(); - sentBody.has_axolotlsenderkeydistributionmessage = true; - sentBody.groupid = (char*)jid; - - Wa__Message msg; - msg.senderkeydistributionmessage = &sentBody; - - pTask->content.append(proto::Serialize(&msg)); - - if (auto *pChatUser = FindUser(jid)) { - if (pChatUser->si) { - for (auto &it : pChatUser->si->arUsers) { - T2Utf userJid(it->pszUID); - auto *pUser = FindUser(jid); - if (pUser == nullptr) - m_arUsers.insert(pUser = new WAUser(INVALID_CONTACT_ID, userJid, false)); - if (pUser->bDeviceInit) { - for (auto &jt : pUser->arDevices) - pTask->arDest.insert(new WAJid(*jt)); - } - else arCheckList.insert(mir_strdup(userJid)); - } - } - } - } - else { - Wa__Message__DeviceSentMessage sentBody; - sentBody.message = &body; - sentBody.destinationjid = (char*)jid; - - Wa__Message msg; - msg.devicesentmessage = &sentBody; - - pTask->content.append(proto::Serialize(&msg)); - - if (auto *pUser = FindUser(jid)) { - if (pUser->bDeviceInit) { - for (auto &it : pUser->arDevices) - pTask->arDest.insert(new WAJid(*it)); - } - else arCheckList.insert(mir_strdup(jid));; - } - } - - padBuffer16(pTask->content); - - auto *pOwnUser = FindUser(m_szJid); - for (auto &it : pOwnUser->arDevices) - if (it->device != (int)getDword(DBKEY_DEVICE_ID)) - pTask->arDest.insert(new WAJid(*it)); - - // generate & reserve packet id - int pktId; - { - mir_cslock lck(m_csOwnMessages); - pktId = m_iPacketId++; - m_arOwnMsgs.insert(new WAOwnMessage(pktId, jid, pTask->szMsgId)); - } - - // if some keys are missing, schedule task for execution & retrieve keys - if (arCheckList.getCount()) { - SendUsync(arCheckList, pTask); - for (auto &it : arCheckList) - mir_free(it); - } - else // otherwise simply execute the task - SendTask(pTask); - - return pktId; -} - -void WhatsAppProto::FinishTask(WASendTask *pTask) -{ - if (auto *pUser = FindUser(pTask->payLoad.getAttr("to"))) { - if (pUser->bIsGroupChat) { - for (auto &it : pUser->si->getUserList()) - if (auto *pChatUser = FindUser(T2Utf(it->pszUID))) - for (auto &cc: pChatUser->arDevices) - pTask->arDest.insert(new WAJid(*cc)); - } - else for (auto &it : pUser->arDevices) - pTask->arDest.insert(new WAJid(*it)); - } - - SendTask(pTask); -} - -void WhatsAppProto::SendTask(WASendTask *pTask) -{ - // pack all data and send the whole payload - bool shouldIncludeIdentity = false; - auto *pParticipants = pTask->payLoad.addChild("participants"); - - for (auto &it : pTask->arDest) - shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, *it, pTask->content); - - if (shouldIncludeIdentity) { - MBinBuffer encIdentity(m_signalStore.encodeSignedIdentity(true)); - auto *pNode = pTask->payLoad.addChild("device-identity"); - pNode->content.assign(encIdentity.data(), encIdentity.length()); - } - - WSSendNode(pTask->payLoad); - delete pTask; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::OnReceiveMessage(const WANode &node)
+{
+ auto *msgId = node.getAttr("id");
+ auto *msgType = node.getAttr("type");
+ auto *msgFrom = node.getAttr("from");
+ auto *category = node.getAttr("category");
+ auto *recipient = node.getAttr("recipient");
+ auto *participant = node.getAttr("participant");
+
+ if (msgType == nullptr || msgFrom == nullptr || msgId == nullptr) {
+ debugLogA("bad message received: <%s> <%s> <%s>", msgType, msgFrom, msgId);
+ return;
+ }
+
+ SendAck(node);
+
+ MEVENT hEvent = db_event_getById(m_szModuleName, msgId);
+ if (hEvent) {
+ debugLogA("this message is already processed: %s", msgId);
+ return;
+ }
+
+ WAMSG type;
+ WAJid jid(msgFrom);
+ CMStringA szAuthor, szChatId;
+
+ if (node.getAttr("offline"))
+ type.bOffline = true;
+
+ // message from one user to another
+ if (jid.isUser()) {
+ if (recipient) {
+ if (m_szJid != msgFrom) {
+ debugLogA("strange message: with recipient, but not from me");
+ return;
+ }
+ szChatId = recipient;
+ }
+ else szChatId = msgFrom;
+
+ type.bPrivateChat = true;
+ szAuthor = msgFrom;
+ }
+ else if (jid.isGroup()) {
+ if (!participant) {
+ debugLogA("strange message: from group, but without participant");
+ return;
+ }
+
+ type.bGroupChat = true;
+ szAuthor = participant;
+ szChatId = msgFrom;
+ }
+ else if (jid.isBroadcast()) {
+ if (!participant) {
+ debugLogA("strange message: from group, but without participant");
+ return;
+ }
+
+ bool bIsMe = m_szJid == participant;
+ 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;
+ }
+ else {
+ debugLogA("invalid message type");
+ return;
+ }
+
+ CMStringA szSender = (type.bPrivateChat) ? szAuthor : szChatId;
+ bool bFromMe = (m_szJid == msgFrom);
+ if (!bFromMe && participant)
+ bFromMe = m_szJid == participant;
+
+ Wa__MessageKey key;
+ key.remotejid = szChatId.GetBuffer();
+ key.id = (char*)msgId;
+ key.fromme = bFromMe; key.has_fromme = true;
+ if (participant)
+ key.participant = (char*)participant;
+
+ Wa__WebMessageInfo msg;
+ msg.key = &key;
+ msg.messagetimestamp = _atoi64(node.getAttr("t")); msg.has_messagetimestamp = true;
+ msg.pushname = (char*)node.getAttr("notify");
+ if (bFromMe)
+ msg.status = WA__WEB_MESSAGE_INFO__STATUS__SERVER_ACK, msg.has_status = true;
+
+ int iDecryptable = 0;
+
+ for (auto &it : node.getChildren()) {
+ if (it->title != "enc" || it->content.length() == 0)
+ continue;
+
+ MBinBuffer msgBody;
+ auto *pszType = it->getAttr("type");
+ try {
+ if (!mir_strcmp(pszType, "pkmsg") || !mir_strcmp(pszType, "msg")) {
+ CMStringA szUser = (WAJid(szSender).isUser()) ? szSender : szAuthor;
+ msgBody = m_signalStore.decryptSignalProto(szUser, pszType, it->content);
+ }
+ else if (!mir_strcmp(pszType, "skmsg")) {
+ msgBody = m_signalStore.decryptGroupSignalProto(szSender, szAuthor, it->content);
+ }
+ else throw "Invalid e2e type";
+
+ if (msgBody.isEmpty())
+ throw "Invalid e2e message";
+
+ iDecryptable++;
+
+ proto::Message encMsg(unpadBuffer16(msgBody));
+ if (!encMsg)
+ throw "Invalid decoded message";
+
+ if (encMsg->devicesentmessage)
+ msg.message = encMsg->devicesentmessage->message;
+ else
+ msg.message = encMsg;
+
+ if (encMsg->senderkeydistributionmessage)
+ m_signalStore.processSenderKeyMessage(szAuthor, encMsg->senderkeydistributionmessage);
+
+ ProcessMessage(type, msg);
+
+ // 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 decryption failed with error: %s", pszError);
+ }
+
+ if (!iDecryptable) {
+ debugLogA("Nothing to decrypt");
+ return;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static const Wa__Message* getBody(const Wa__Message *message)
+{
+ if (message->ephemeralmessage) {
+ auto *pMsg = message->ephemeralmessage->message;
+ return (pMsg->viewoncemessage) ? pMsg->viewoncemessage->message : pMsg;
+ }
+
+ if (message->viewoncemessage)
+ return message->viewoncemessage->message;
+
+ return message;
+}
+
+void WhatsAppProto::ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg)
+{
+ auto *key = msg.key;
+ auto *body = getBody(msg.message);
+
+ debugLogA("Got a message: %s", protobuf_c_text_to_string(&msg).c_str());
+
+ uint32_t timestamp = msg.messagetimestamp;
+ char *participant = key->participant, *chatId;
+ auto *msgId = key->id;
+
+ if (type.bPrivateChat || type.bGroupChat)
+ chatId = key->remotejid;
+ else
+ chatId = (participant) ? participant : key->remotejid;
+
+ WAJid jidFrom(chatId); jidFrom.device = 0;
+ WAUser *pUser = AddUser(jidFrom.toString(), false);
+
+ if (!key->fromme && msg.pushname && pUser && !pUser->bIsGroupChat)
+ setUString(pUser->hContact, "Nick", msg.pushname);
+
+ // try to extract some text
+ if (pUser) {
+ CMStringA szMessageText(GetMessageText(body));
+ if (!szMessageText.IsEmpty()) {
+ // for chats & group chats store message in profile
+ if (type.bPrivateChat || type.bGroupChat) {
+ PROTORECVEVENT pre = {};
+ pre.timestamp = timestamp;
+ pre.szMessage = szMessageText.GetBuffer();
+ pre.szMsgId = msgId;
+ if (type.bOffline)
+ pre.flags |= PREF_CREATEREAD;
+ if (key->fromme)
+ pre.flags |= PREF_SENT;
+ ProtoChainRecvMsg(pUser->hContact, &pre);
+
+ if (pUser->bIsGroupChat) {
+ GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE};
+ gce.dwFlags = GCEF_UTF8;
+ gce.pszID.a = pUser->szId;
+ gce.pszUID.a = participant;
+ gce.bIsMe = key->fromme;
+ gce.pszText.a = szMessageText.GetBuffer();
+ gce.time = timestamp;
+ Chat_Event(&gce);
+ }
+ }
+ // translate statuses into status messages
+ else if (type.bOtherStatus || type.bDirectStatus || type.bPeerBroadcast || type.bOtherBroadcast) {
+ setUString(pUser->hContact, "StatusMsg", szMessageText);
+ }
+ }
+ }
+
+ if (body->protocolmessage) {
+ auto *protoMsg = body->protocolmessage;
+ switch (protoMsg->type) {
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_SYNC_KEY_SHARE:
+ for (int i = 0; i < protoMsg->appstatesynckeyshare->n_keys; i++) {
+ auto *it = protoMsg->appstatesynckeyshare->keys[i];
+ 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.data, (unsigned)keydata.len);
+ }
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_FATAL_EXCEPTION_NOTIFICATION:
+ m_impl.m_resyncApp.Stop();
+ m_impl.m_resyncApp.Start(10000);
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__HISTORY_SYNC_NOTIFICATION:
+ debugLogA("History sync notification");
+ if (auto *pHist = protoMsg->historysyncnotification) {
+ MBinBuffer buf(DownloadEncryptedFile(directPath2url(pHist->directpath), pHist->mediakey, "History"));
+ if (!buf.isEmpty()) {
+ MBinBuffer inflate(unzip(unpadBuffer16(buf)));
+
+ proto::HistorySync sync(inflate);
+ if (sync)
+ ProcessHistorySync(sync);
+ }
+ }
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__REVOKE:
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__EPHEMERAL_SETTING:
+ if (pUser) {
+ setDword(pUser->hContact, DBKEY_EPHEMERAL_TS, timestamp);
+ setDword(pUser->hContact, DBKEY_EPHEMERAL_EXPIRE, protoMsg->ephemeralexpiration);
+ }
+ break;
+ }
+ }
+ else if (body->reactionmessage) {
+ debugLogA("Got a reaction to a message");
+ }
+ else if (msg.has_messagestubtype) {
+ switch (msg.messagestubtype) {
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_LEAVE:
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_REMOVE:
+ debugLogA("Participant %s removed from chat", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD:
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_INVITE:
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
+ debugLogA("Participant %s added to chat", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_DEMOTE:
+ debugLogA("Participant %s demoted", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_PROMOTE:
+ debugLogA("Participant %s promoted", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_ANNOUNCE:
+ debugLogA("Groupchat announce", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_RESTRICT:
+ debugLogA("Groupchat restriction", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_SUBJECT:
+ debugLogA("Groupchat subject was changed", participant);
+ break;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveAck(const WANode &node)
+{
+ auto *pUser = FindUser(node.getAttr("from"));
+ if (pUser == nullptr)
+ return;
+
+ if (!mir_strcmp(node.getAttr("class"), "message")) {
+ WAOwnMessage tmp(0, 0, node.getAttr("id"));
+ {
+ mir_cslock lck(m_csOwnMessages);
+ if (auto *pOwn = m_arOwnMsgs.find(&tmp)) {
+ tmp.pktId = pOwn->pktId;
+ m_arOwnMsgs.remove(pOwn);
+ }
+ else return;
+ }
+ ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)tmp.pktId, (LPARAM)tmp.szMessageId.c_str());
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool WhatsAppProto::CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig)
+{
+ int type = 0;
+
+ try {
+ MBinBuffer pBuffer(m_signalStore.encryptSignalProto(jid, orig, type));
+
+ auto *pNode = pParticipants->addChild("to");
+ pNode->addAttr("jid", jid.toString());
+
+ auto *pEnc = pNode->addChild("enc");
+ *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", (type == 3) ? "pkmsg" : "msg");
+ pEnc->content.assign(pBuffer.data(), pBuffer.length());
+ }
+ catch (const char *) {
+ }
+
+ return type == 3;
+}
+
+int WhatsAppProto::SendTextMessage(const char *jid, const char *pszMsg)
+{
+ WAJid toJid(jid);
+
+ // send task creation
+ auto *pTask = new WASendTask(jid);
+
+ // basic message
+ Wa__Message__ExtendedTextMessage extMessage;
+ extMessage.text = (char *)pszMsg;
+
+ Wa__Message body;
+ body.extendedtextmessage = &extMessage;
+
+ LIST<char> arCheckList(1);
+ if (toJid.isGroup()) {
+ MBinBuffer encodedMsg(proto::Serialize(&body));
+ padBuffer16(encodedMsg);
+
+ MBinBuffer skmsgKey;
+ MBinBuffer cipherText(m_signalStore.encryptSenderKey(toJid, m_szJid, encodedMsg, skmsgKey));
+
+ auto *pEnc = pTask->payLoad.addChild("enc");
+ *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", "skmsg");
+ pEnc->content.append(cipherText.data(), cipherText.length());
+
+ Wa__Message__SenderKeyDistributionMessage sentBody;
+ sentBody.axolotlsenderkeydistributionmessage.data = skmsgKey.data();
+ sentBody.axolotlsenderkeydistributionmessage.len = skmsgKey.length();
+ sentBody.has_axolotlsenderkeydistributionmessage = true;
+ sentBody.groupid = (char*)jid;
+
+ Wa__Message msg;
+ msg.senderkeydistributionmessage = &sentBody;
+
+ pTask->content.append(proto::Serialize(&msg));
+
+ if (auto *pChatUser = FindUser(jid)) {
+ if (pChatUser->si) {
+ for (auto &it : pChatUser->si->arUsers) {
+ T2Utf userJid(it->pszUID);
+ auto *pUser = FindUser(jid);
+ if (pUser == nullptr)
+ m_arUsers.insert(pUser = new WAUser(INVALID_CONTACT_ID, userJid, false));
+ if (pUser->bDeviceInit) {
+ for (auto &jt : pUser->arDevices)
+ pTask->arDest.insert(new WAJid(*jt));
+ }
+ else arCheckList.insert(mir_strdup(userJid));
+ }
+ }
+ }
+ }
+ else {
+ Wa__Message__DeviceSentMessage sentBody;
+ sentBody.message = &body;
+ sentBody.destinationjid = (char*)jid;
+
+ Wa__Message msg;
+ msg.devicesentmessage = &sentBody;
+
+ pTask->content.append(proto::Serialize(&msg));
+
+ if (auto *pUser = FindUser(jid)) {
+ if (pUser->bDeviceInit) {
+ for (auto &it : pUser->arDevices)
+ pTask->arDest.insert(new WAJid(*it));
+ }
+ else arCheckList.insert(mir_strdup(jid));;
+ }
+ }
+
+ padBuffer16(pTask->content);
+
+ auto *pOwnUser = FindUser(m_szJid);
+ for (auto &it : pOwnUser->arDevices)
+ if (it->device != (int)getDword(DBKEY_DEVICE_ID))
+ pTask->arDest.insert(new WAJid(*it));
+
+ // generate & reserve packet id
+ int pktId;
+ {
+ mir_cslock lck(m_csOwnMessages);
+ pktId = m_iPacketId++;
+ m_arOwnMsgs.insert(new WAOwnMessage(pktId, jid, pTask->szMsgId));
+ }
+
+ // if some keys are missing, schedule task for execution & retrieve keys
+ if (arCheckList.getCount()) {
+ SendUsync(arCheckList, pTask);
+ for (auto &it : arCheckList)
+ mir_free(it);
+ }
+ else // otherwise simply execute the task
+ SendTask(pTask);
+
+ return pktId;
+}
+
+void WhatsAppProto::FinishTask(WASendTask *pTask)
+{
+ if (auto *pUser = FindUser(pTask->payLoad.getAttr("to"))) {
+ if (pUser->bIsGroupChat) {
+ for (auto &it : pUser->si->getUserList())
+ if (auto *pChatUser = FindUser(T2Utf(it->pszUID)))
+ for (auto &cc: pChatUser->arDevices)
+ pTask->arDest.insert(new WAJid(*cc));
+ }
+ else for (auto &it : pUser->arDevices)
+ pTask->arDest.insert(new WAJid(*it));
+ }
+
+ SendTask(pTask);
+}
+
+void WhatsAppProto::SendTask(WASendTask *pTask)
+{
+ // pack all data and send the whole payload
+ bool shouldIncludeIdentity = false;
+ auto *pParticipants = pTask->payLoad.addChild("participants");
+
+ for (auto &it : pTask->arDest)
+ shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, *it, pTask->content);
+
+ if (shouldIncludeIdentity) {
+ MBinBuffer encIdentity(m_signalStore.encodeSignedIdentity(true));
+ auto *pNode = pTask->payLoad.addChild("device-identity");
+ pNode->content.assign(encIdentity.data(), encIdentity.length());
+ }
+
+ WSSendNode(pTask->payLoad);
+ delete pTask;
+}
diff --git a/protocols/WhatsApp/src/noise.cpp b/protocols/WhatsApp/src/noise.cpp index 004b71cdef..18ae206519 100644 --- a/protocols/WhatsApp/src/noise.cpp +++ b/protocols/WhatsApp/src/noise.cpp @@ -1,201 +1,201 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -WANoise class implementation - -*/ - -#include "stdafx.h" - -static uint8_t intro_header[] = {87, 65, 6, DICT_VERSION}; -static uint8_t noise_init[] = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0"; - -WANoise::WANoise(WhatsAppProto *_ppro) : - ppro(_ppro) -{ - salt.assign(noise_init, 32); - encKey.assign(noise_init, 32); - decKey.assign(noise_init, 32); - - // generate ephemeral keys: public & private - ec_key_pair *pKeys; - curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys); - - auto *pPubKey = ec_key_pair_get_public(pKeys); - ephemeral.pub.assign(pPubKey->data, sizeof(pPubKey->data)); - - auto *pPrivKey = ec_key_pair_get_private(pKeys); - ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data)); - ec_key_pair_destroy((signal_type_base*)pKeys); - - // prepare hash - memcpy(hash, noise_init, 32); - updateHash(intro_header, 4); - updateHash(ephemeral.pub.data(), ephemeral.pub.length()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// libsignal data initialization - -void WANoise::init() -{ - // no data? generate them - if (ppro->getDword(DBKEY_REG_ID, 0xFFFF) == 0xFFFF) { - // generate registration id - uint32_t regId; - Utils_GetRandom(®Id, sizeof(regId)); - ppro->setDword(DBKEY_REG_ID, regId & 0x3FFF); - - // generate secret key - uint8_t secretKey[32]; - Utils_GetRandom(secretKey, sizeof(secretKey)); - db_set_blob(0, ppro->m_szModuleName, DBKEY_SECRET_KEY, secretKey, sizeof(secretKey)); - - // generate noise keys (private & public) - ec_key_pair *pKeys; - curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys); - - auto *pPubKey = ec_key_pair_get_public(pKeys); - db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PUB, pPubKey->data, sizeof(pPubKey->data)); - - auto *pPrivKey = ec_key_pair_get_private(pKeys); - db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PRIV, pPrivKey->data, sizeof(pPrivKey->data)); - ec_key_pair_destroy((signal_type_base *)pKeys); - } - - noiseKeys.pub = ppro->getBlob(DBKEY_NOISE_PUB); - noiseKeys.priv = ppro->getBlob(DBKEY_NOISE_PRIV); -} - -void WANoise::finish() -{ - deriveKey("", 0, encKey, decKey); - readCounter = writeCounter = 0; - memset(hash, 0, sizeof(hash)); - bInitFinished = true; -} - -void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read) -{ - size_t outlen = 64; - uint8_t out[64]; - HKDF(EVP_sha256(), salt.data(), (int)salt.length(), (BYTE *)pData, (int)cbLen, (BYTE *)"", 0, out, outlen); - - write.assign(out, 32); - read.assign(out + 32, 32); -} - -void WANoise::mixIntoKey(const void *n, const void *p) -{ - uint8_t tmp[32]; - curve25519_donna((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p); - - deriveKey(tmp, sizeof(tmp), salt, encKey); - decKey.assign(encKey.data(), encKey.length()); - readCounter = writeCounter = 0; -} - -MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen) -{ - uint8_t iv[12]; - generateIV(iv, (bInitFinished) ? readCounter : writeCounter); - - MBinBuffer res; - if (!bInitFinished) - res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen, hash, sizeof(hash)); - else - res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen); - - updateHash(pData, cbLen); - return res; -} - -size_t WANoise::decodeFrame(const uint8_t *&p, size_t &cbLen) -{ - if (cbLen < 3) - return 0; - - size_t payloadLen = 0; - for (int i = 0; i < 3; i++) { - payloadLen <<= 8; - payloadLen += p[i]; - } - - // ppro->debugLogA("got payload of size %d", payloadLen); - - cbLen -= 3; - if (payloadLen > cbLen) { - ppro->debugLogA("payload length %d exceeds capacity %d", payloadLen, cbLen); - return 0; - } - - p += 3; - return payloadLen; -} - -MBinBuffer WANoise::encodeFrame(const void *pData, size_t cbLen) -{ - MBinBuffer res; - if (!bSendIntro) { - bSendIntro = true; - res.append(intro_header, 4); - } - - uint8_t buf[3]; - size_t foo = cbLen; - for (int i = 0; i < 3; i++) { - buf[2 - i] = foo & 0xFF; - foo >>= 8; - } - res.append(buf, 3); - res.append(pData, cbLen); - return res; -} - -MBinBuffer WANoise::encrypt(const void *pData, size_t cbLen) -{ - uint8_t iv[12]; - generateIV(iv, writeCounter); - - MBinBuffer res; - uint8_t outbuf[1024 + 64]; - - int enc_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, encKey.data(), iv); - - if (!bInitFinished) - EVP_EncryptUpdate(ctx, NULL, &enc_len, hash, sizeof(hash)); - - for (size_t len = 0; len < cbLen; len += 1024) { - size_t portionSize = cbLen - len; - EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024)); - res.append(outbuf, enc_len); - } - EVP_EncryptFinal_ex(ctx, outbuf, &final_len); - if (final_len) - res.append(outbuf, final_len); - - uint8_t tag[16]; - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag); - res.append(tag, sizeof(tag)); - - EVP_CIPHER_CTX_free(ctx); - - updateHash(res.data(), res.length()); - return res; -} - -void WANoise::updateHash(const void *pData, size_t cbLen) -{ - if (bInitFinished) - return; - - SHA256_CTX ctx; - SHA256_Init(&ctx); - SHA256_Update(&ctx, hash, sizeof(hash)); - SHA256_Update(&ctx, pData, cbLen); - SHA256_Final(hash, &ctx); -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+WANoise class implementation
+
+*/
+
+#include "stdafx.h"
+
+static uint8_t intro_header[] = {87, 65, 6, DICT_VERSION};
+static uint8_t noise_init[] = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0";
+
+WANoise::WANoise(WhatsAppProto *_ppro) :
+ ppro(_ppro)
+{
+ salt.assign(noise_init, 32);
+ encKey.assign(noise_init, 32);
+ decKey.assign(noise_init, 32);
+
+ // generate ephemeral keys: public & private
+ ec_key_pair *pKeys;
+ curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys);
+
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ ephemeral.pub.assign(pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy((signal_type_base*)pKeys);
+
+ // prepare hash
+ memcpy(hash, noise_init, 32);
+ updateHash(intro_header, 4);
+ updateHash(ephemeral.pub.data(), ephemeral.pub.length());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// libsignal data initialization
+
+void WANoise::init()
+{
+ // no data? generate them
+ if (ppro->getDword(DBKEY_REG_ID, 0xFFFF) == 0xFFFF) {
+ // generate registration id
+ uint32_t regId;
+ Utils_GetRandom(®Id, sizeof(regId));
+ ppro->setDword(DBKEY_REG_ID, regId & 0x3FFF);
+
+ // generate secret key
+ uint8_t secretKey[32];
+ Utils_GetRandom(secretKey, sizeof(secretKey));
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_SECRET_KEY, secretKey, sizeof(secretKey));
+
+ // generate noise keys (private & public)
+ ec_key_pair *pKeys;
+ curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys);
+
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PUB, pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy((signal_type_base *)pKeys);
+ }
+
+ noiseKeys.pub = ppro->getBlob(DBKEY_NOISE_PUB);
+ noiseKeys.priv = ppro->getBlob(DBKEY_NOISE_PRIV);
+}
+
+void WANoise::finish()
+{
+ deriveKey("", 0, encKey, decKey);
+ readCounter = writeCounter = 0;
+ memset(hash, 0, sizeof(hash));
+ bInitFinished = true;
+}
+
+void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read)
+{
+ size_t outlen = 64;
+ uint8_t out[64];
+ HKDF(EVP_sha256(), salt.data(), (int)salt.length(), (BYTE *)pData, (int)cbLen, (BYTE *)"", 0, out, outlen);
+
+ write.assign(out, 32);
+ read.assign(out + 32, 32);
+}
+
+void WANoise::mixIntoKey(const void *n, const void *p)
+{
+ uint8_t tmp[32];
+ curve25519_donna((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p);
+
+ deriveKey(tmp, sizeof(tmp), salt, encKey);
+ decKey.assign(encKey.data(), encKey.length());
+ readCounter = writeCounter = 0;
+}
+
+MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen)
+{
+ uint8_t iv[12];
+ generateIV(iv, (bInitFinished) ? readCounter : writeCounter);
+
+ MBinBuffer res;
+ if (!bInitFinished)
+ res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen, hash, sizeof(hash));
+ else
+ res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen);
+
+ updateHash(pData, cbLen);
+ return res;
+}
+
+size_t WANoise::decodeFrame(const uint8_t *&p, size_t &cbLen)
+{
+ if (cbLen < 3)
+ return 0;
+
+ size_t payloadLen = 0;
+ for (int i = 0; i < 3; i++) {
+ payloadLen <<= 8;
+ payloadLen += p[i];
+ }
+
+ // ppro->debugLogA("got payload of size %d", payloadLen);
+
+ cbLen -= 3;
+ if (payloadLen > cbLen) {
+ ppro->debugLogA("payload length %d exceeds capacity %d", payloadLen, cbLen);
+ return 0;
+ }
+
+ p += 3;
+ return payloadLen;
+}
+
+MBinBuffer WANoise::encodeFrame(const void *pData, size_t cbLen)
+{
+ MBinBuffer res;
+ if (!bSendIntro) {
+ bSendIntro = true;
+ res.append(intro_header, 4);
+ }
+
+ uint8_t buf[3];
+ size_t foo = cbLen;
+ for (int i = 0; i < 3; i++) {
+ buf[2 - i] = foo & 0xFF;
+ foo >>= 8;
+ }
+ res.append(buf, 3);
+ res.append(pData, cbLen);
+ return res;
+}
+
+MBinBuffer WANoise::encrypt(const void *pData, size_t cbLen)
+{
+ uint8_t iv[12];
+ generateIV(iv, writeCounter);
+
+ MBinBuffer res;
+ uint8_t outbuf[1024 + 64];
+
+ int enc_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, encKey.data(), iv);
+
+ if (!bInitFinished)
+ EVP_EncryptUpdate(ctx, NULL, &enc_len, hash, sizeof(hash));
+
+ for (size_t len = 0; len < cbLen; len += 1024) {
+ size_t portionSize = cbLen - len;
+ EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024));
+ res.append(outbuf, enc_len);
+ }
+ EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ res.append(outbuf, final_len);
+
+ uint8_t tag[16];
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag);
+ res.append(tag, sizeof(tag));
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ updateHash(res.data(), res.length());
+ return res;
+}
+
+void WANoise::updateHash(const void *pData, size_t cbLen)
+{
+ if (bInitFinished)
+ return;
+
+ SHA256_CTX ctx;
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, hash, sizeof(hash));
+ SHA256_Update(&ctx, pData, cbLen);
+ SHA256_Final(hash, &ctx);
+}
diff --git a/protocols/WhatsApp/src/options.cpp b/protocols/WhatsApp/src/options.cpp index 3df8e69343..9945da04e5 100644 --- a/protocols/WhatsApp/src/options.cpp +++ b/protocols/WhatsApp/src/options.cpp @@ -1,95 +1,95 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// - -class COptionsDlg : public CProtoDlgBase<WhatsAppProto> -{ - CCtrlCheck chkHideChats, chkBbcodes; - CCtrlEdit edtGroup, edtNick, edtDevName; - CCtrlButton btnUnregister; - ptrW m_wszOldGroup; - -public: - COptionsDlg(WhatsAppProto *ppro, int iDlgID, bool bFullDlg) : - CProtoDlgBase<WhatsAppProto>(ppro, iDlgID), - chkBbcodes(this, IDC_USEBBCODES), - chkHideChats(this, IDC_HIDECHATS), - edtNick(this, IDC_NICK), - edtGroup(this, IDC_DEFGROUP), - edtDevName(this, IDC_DEVICE_NAME), - btnUnregister(this, IDC_UNREGISTER), - m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) - { - CreateLink(edtNick, ppro->m_wszNick); - CreateLink(edtGroup, ppro->m_wszDefaultGroup); - CreateLink(edtDevName, ppro->m_wszDeviceName); - - if (bFullDlg) { - CreateLink(chkHideChats, ppro->m_bHideGroupchats); - CreateLink(chkBbcodes, ppro->m_bUseBbcodes); - } - - btnUnregister.OnClick = Callback(this, &COptionsDlg::onClick_Unregister); - } - - bool OnInitDialog() override - { - if (!m_proto->getMStringA(DBKEY_ID).IsEmpty()) - edtDevName.Disable(); - return true; - } - - bool OnApply() override - { - if (mir_wstrlen(m_proto->m_wszNick)) { - SetFocus(edtNick.GetHwnd()); - return false; - } - - if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) - Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); - return true; - } - - void onClick_Unregister(CCtrlButton *) - { - if (IDYES != MessageBoxW(0, TranslateT("Do you really want to unregister Miranda?"), m_proto->m_tszUserName, MB_ICONQUESTION | MB_YESNO)) - return; - - if (m_proto->isOnline()) - m_proto->SendUnregister(); - else - m_proto->OnErase(); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR WhatsAppProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) -{ - auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false); - pDlg->SetParent((HWND)hwndParent); - pDlg->Create(); - return (INT_PTR)pDlg->GetHwnd(); -} - -int WhatsAppProto::OnOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szTitle.w = m_tszUserName; - odp.flags = ODPF_UNICODE; - odp.szGroup.w = LPGENW("Network"); - - odp.position = 1; - odp.szTab.w = LPGENW("Account"); - odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class COptionsDlg : public CProtoDlgBase<WhatsAppProto>
+{
+ CCtrlCheck chkHideChats, chkBbcodes;
+ CCtrlEdit edtGroup, edtNick, edtDevName;
+ CCtrlButton btnUnregister;
+ ptrW m_wszOldGroup;
+
+public:
+ COptionsDlg(WhatsAppProto *ppro, int iDlgID, bool bFullDlg) :
+ CProtoDlgBase<WhatsAppProto>(ppro, iDlgID),
+ chkBbcodes(this, IDC_USEBBCODES),
+ chkHideChats(this, IDC_HIDECHATS),
+ edtNick(this, IDC_NICK),
+ edtGroup(this, IDC_DEFGROUP),
+ edtDevName(this, IDC_DEVICE_NAME),
+ btnUnregister(this, IDC_UNREGISTER),
+ m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup))
+ {
+ CreateLink(edtNick, ppro->m_wszNick);
+ CreateLink(edtGroup, ppro->m_wszDefaultGroup);
+ CreateLink(edtDevName, ppro->m_wszDeviceName);
+
+ if (bFullDlg) {
+ CreateLink(chkHideChats, ppro->m_bHideGroupchats);
+ CreateLink(chkBbcodes, ppro->m_bUseBbcodes);
+ }
+
+ btnUnregister.OnClick = Callback(this, &COptionsDlg::onClick_Unregister);
+ }
+
+ bool OnInitDialog() override
+ {
+ if (!m_proto->getMStringA(DBKEY_ID).IsEmpty())
+ edtDevName.Disable();
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ if (mir_wstrlen(m_proto->m_wszNick)) {
+ SetFocus(edtNick.GetHwnd());
+ return false;
+ }
+
+ if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup))
+ Clist_GroupCreate(0, m_proto->m_wszDefaultGroup);
+ return true;
+ }
+
+ void onClick_Unregister(CCtrlButton *)
+ {
+ if (IDYES != MessageBoxW(0, TranslateT("Do you really want to unregister Miranda?"), m_proto->m_tszUserName, MB_ICONQUESTION | MB_YESNO))
+ return;
+
+ if (m_proto->isOnline())
+ m_proto->SendUnregister();
+ else
+ m_proto->OnErase();
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR WhatsAppProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent)
+{
+ auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false);
+ pDlg->SetParent((HWND)hwndParent);
+ pDlg->Create();
+ return (INT_PTR)pDlg->GetHwnd();
+}
+
+int WhatsAppProto::OnOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.szTitle.w = m_tszUserName;
+ odp.flags = ODPF_UNICODE;
+ odp.szGroup.w = LPGENW("Network");
+
+ odp.position = 1;
+ odp.szTab.w = LPGENW("Account");
+ odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true);
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp index f97aa989e8..006f78dfc0 100644 --- a/protocols/WhatsApp/src/proto.cpp +++ b/protocols/WhatsApp/src/proto.cpp @@ -1,7 +1,7 @@ /* WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan +Copyright © 2019-23 George Hazan */ diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h index e6088cd4b8..dbcf57b597 100644 --- a/protocols/WhatsApp/src/proto.h +++ b/protocols/WhatsApp/src/proto.h @@ -1,7 +1,7 @@ /* WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan +Copyright © 2019-23 George Hazan */ diff --git a/protocols/WhatsApp/src/qrcode.cpp b/protocols/WhatsApp/src/qrcode.cpp index 18edfc3e91..22c9e1da95 100644 --- a/protocols/WhatsApp/src/qrcode.cpp +++ b/protocols/WhatsApp/src/qrcode.cpp @@ -1,137 +1,137 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -class CWhatsAppQRDlg : public CProtoDlgBase<WhatsAppProto> -{ -public: - CWhatsAppQRDlg(WhatsAppProto *ppro) : - CProtoDlgBase<WhatsAppProto>(ppro, IDD_SHOWQR) - {} - - void OnDestroy() override - { - m_proto->m_pQRDlg = nullptr; - - if (!m_bSucceeded) - m_proto->ShutdownSession(); - } - - void SetSuccess() - { - m_bSucceeded = true; - } - - void SetData(const CMStringA &str) - { - auto *pQR = QRcode_encodeString(str, 0, QR_ECLEVEL_L, QR_MODE_8, 1); - - HWND hwndRc = GetDlgItem(m_hwnd, IDC_QRPIC); - RECT rc; - GetClientRect(hwndRc, &rc); - - ::SetForegroundWindow(m_hwnd); - - int scale = 8; // (rc.bottom - rc.top) / pQR->width; - int rowLen = pQR->width * scale * 3; - if (rowLen % 4) - rowLen = (rowLen / 4 + 1) * 4; - int dataLen = rowLen * pQR->width * scale; - - mir_ptr<BYTE> pData((BYTE *)mir_alloc(dataLen)); - if (pData == nullptr) { - QRcode_free(pQR); - return; - } - - memset(pData, 0xFF, dataLen); // white background by default - - const BYTE *s = pQR->data; - for (int y = 0; y < pQR->width; y++) { - BYTE *d = pData.get() + rowLen * y * scale; - for (int x = 0; x < pQR->width; x++) { - if (*s & 1) - for (int i = 0; i < scale; i++) - for (int j = 0; j < scale; j++) { - d[j * 3 + i * rowLen] = 0; - d[1 + j * 3 + i * rowLen] = 0; - d[2 + j * 3 + i * rowLen] = 0; - } - - d += scale * 3; - s++; - } - } - - BITMAPFILEHEADER fih = {}; - fih.bfType = 0x4d42; // "BM" - fih.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dataLen; - fih.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); - - BITMAPINFOHEADER bih = {}; - bih.biSize = sizeof(BITMAPINFOHEADER); - bih.biWidth = pQR->width * scale; - bih.biHeight = -bih.biWidth; - bih.biPlanes = 1; - bih.biBitCount = 24; - bih.biCompression = BI_RGB; - - wchar_t wszTempPath[MAX_PATH], wszTempFile[MAX_PATH]; - GetTempPathW(_countof(wszTempPath), wszTempPath); - GetTempFileNameW(wszTempPath, L"wa_", TRUE, wszTempFile); - FILE *f = _wfopen(wszTempFile, L"wb"); - fwrite(&fih, sizeof(BITMAPFILEHEADER), 1, f); - fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, f); - fwrite(pData, sizeof(unsigned char), dataLen, f); - fclose(f); - - SendMessage(hwndRc, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)Image_Load(wszTempFile)); - - DeleteFileW(wszTempFile); - QRcode_free(pQR); - } -}; - -static INT_PTR __stdcall sttShowDialog(void *param) -{ - WhatsAppProto *ppro = (WhatsAppProto *)param; - - if (ppro->m_pQRDlg == nullptr) { - ppro->m_pQRDlg = new CWhatsAppQRDlg(ppro); - ppro->m_pQRDlg->Show(); - } - else { - SetForegroundWindow(ppro->m_pQRDlg->GetHwnd()); - SetActiveWindow(ppro->m_pQRDlg->GetHwnd()); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::CloseQrDialog() -{ - if (m_pQRDlg) { - m_pQRDlg->SetSuccess(); - m_pQRDlg->Close(); - } -} - -bool WhatsAppProto::ShowQrCode(const CMStringA &ref) -{ - CallFunctionSync(sttShowDialog, this); - - MBinBuffer secret(getBlob(DBKEY_SECRET_KEY)); - - ptrA s1(mir_base64_encode(m_noise->noiseKeys.pub)); - ptrA s2(mir_base64_encode(m_signalStore.signedIdentity.pub)); - ptrA s3(mir_base64_encode(secret)); - CMStringA szQrData(FORMAT, "%s,%s,%s,%s", ref.c_str(), s1.get(), s2.get(), s3.get()); - m_pQRDlg->SetData(szQrData); - return true; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+class CWhatsAppQRDlg : public CProtoDlgBase<WhatsAppProto>
+{
+public:
+ CWhatsAppQRDlg(WhatsAppProto *ppro) :
+ CProtoDlgBase<WhatsAppProto>(ppro, IDD_SHOWQR)
+ {}
+
+ void OnDestroy() override
+ {
+ m_proto->m_pQRDlg = nullptr;
+
+ if (!m_bSucceeded)
+ m_proto->ShutdownSession();
+ }
+
+ void SetSuccess()
+ {
+ m_bSucceeded = true;
+ }
+
+ void SetData(const CMStringA &str)
+ {
+ auto *pQR = QRcode_encodeString(str, 0, QR_ECLEVEL_L, QR_MODE_8, 1);
+
+ HWND hwndRc = GetDlgItem(m_hwnd, IDC_QRPIC);
+ RECT rc;
+ GetClientRect(hwndRc, &rc);
+
+ ::SetForegroundWindow(m_hwnd);
+
+ int scale = 8; // (rc.bottom - rc.top) / pQR->width;
+ int rowLen = pQR->width * scale * 3;
+ if (rowLen % 4)
+ rowLen = (rowLen / 4 + 1) * 4;
+ int dataLen = rowLen * pQR->width * scale;
+
+ mir_ptr<BYTE> pData((BYTE *)mir_alloc(dataLen));
+ if (pData == nullptr) {
+ QRcode_free(pQR);
+ return;
+ }
+
+ memset(pData, 0xFF, dataLen); // white background by default
+
+ const BYTE *s = pQR->data;
+ for (int y = 0; y < pQR->width; y++) {
+ BYTE *d = pData.get() + rowLen * y * scale;
+ for (int x = 0; x < pQR->width; x++) {
+ if (*s & 1)
+ for (int i = 0; i < scale; i++)
+ for (int j = 0; j < scale; j++) {
+ d[j * 3 + i * rowLen] = 0;
+ d[1 + j * 3 + i * rowLen] = 0;
+ d[2 + j * 3 + i * rowLen] = 0;
+ }
+
+ d += scale * 3;
+ s++;
+ }
+ }
+
+ BITMAPFILEHEADER fih = {};
+ fih.bfType = 0x4d42; // "BM"
+ fih.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dataLen;
+ fih.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+
+ BITMAPINFOHEADER bih = {};
+ bih.biSize = sizeof(BITMAPINFOHEADER);
+ bih.biWidth = pQR->width * scale;
+ bih.biHeight = -bih.biWidth;
+ bih.biPlanes = 1;
+ bih.biBitCount = 24;
+ bih.biCompression = BI_RGB;
+
+ wchar_t wszTempPath[MAX_PATH], wszTempFile[MAX_PATH];
+ GetTempPathW(_countof(wszTempPath), wszTempPath);
+ GetTempFileNameW(wszTempPath, L"wa_", TRUE, wszTempFile);
+ FILE *f = _wfopen(wszTempFile, L"wb");
+ fwrite(&fih, sizeof(BITMAPFILEHEADER), 1, f);
+ fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, f);
+ fwrite(pData, sizeof(unsigned char), dataLen, f);
+ fclose(f);
+
+ SendMessage(hwndRc, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)Image_Load(wszTempFile));
+
+ DeleteFileW(wszTempFile);
+ QRcode_free(pQR);
+ }
+};
+
+static INT_PTR __stdcall sttShowDialog(void *param)
+{
+ WhatsAppProto *ppro = (WhatsAppProto *)param;
+
+ if (ppro->m_pQRDlg == nullptr) {
+ ppro->m_pQRDlg = new CWhatsAppQRDlg(ppro);
+ ppro->m_pQRDlg->Show();
+ }
+ else {
+ SetForegroundWindow(ppro->m_pQRDlg->GetHwnd());
+ SetActiveWindow(ppro->m_pQRDlg->GetHwnd());
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::CloseQrDialog()
+{
+ if (m_pQRDlg) {
+ m_pQRDlg->SetSuccess();
+ m_pQRDlg->Close();
+ }
+}
+
+bool WhatsAppProto::ShowQrCode(const CMStringA &ref)
+{
+ CallFunctionSync(sttShowDialog, this);
+
+ MBinBuffer secret(getBlob(DBKEY_SECRET_KEY));
+
+ ptrA s1(mir_base64_encode(m_noise->noiseKeys.pub));
+ ptrA s2(mir_base64_encode(m_signalStore.signedIdentity.pub));
+ ptrA s3(mir_base64_encode(secret));
+ CMStringA szQrData(FORMAT, "%s,%s,%s,%s", ref.c_str(), s1.get(), s2.get(), s3.get());
+ m_pQRDlg->SetData(szQrData);
+ return true;
+}
diff --git a/protocols/WhatsApp/src/server.cpp b/protocols/WhatsApp/src/server.cpp index 0afe96662d..7512b05caf 100644 --- a/protocols/WhatsApp/src/server.cpp +++ b/protocols/WhatsApp/src/server.cpp @@ -1,7 +1,7 @@ /* WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan +Copyright © 2019-23 George Hazan */ diff --git a/protocols/WhatsApp/src/signal.cpp b/protocols/WhatsApp/src/signal.cpp index b94a3452e4..d8a57b0222 100644 --- a/protocols/WhatsApp/src/signal.cpp +++ b/protocols/WhatsApp/src/signal.cpp @@ -1,788 +1,788 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// MSignalStore members - -static int CompareSessions(const MSignalSession *p1, const MSignalSession *p2) -{ - if (int ret = mir_strcmp(p1->szName, p2->szName)) - return ret; - - return p1->getDeviceId() - p2->getDeviceId(); -} - -MSignalStore::MSignalStore(PROTO_INTERFACE *_1, const char *_2) : - pProto(_1), - prefix(_2), - arSessions(1, &CompareSessions) -{ - init(); -} - -MSignalStore::~MSignalStore() -{ - signal_protocol_store_context_destroy(m_pStore); - signal_context_destroy(m_pContext); -} - -void MSignalStore::logError(int err, const char *pszMessage) -{ - if (err < 0) { - pProto->debugLogA("libsignal error %d", err); - throw pszMessage; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static void log_func(int level, const char *pmsg, size_t /*msgLen*/, void *pUserData) -{ - auto *pStore = (MSignalStore *)pUserData; - pStore->pProto->debugLogA("libsignal {%d}: %s", level, pmsg); -} - -static int hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *) -{ - HMAC_CTX *ctx = HMAC_CTX_new(); - *hmac_context = ctx; - HMAC_Init(ctx, key, (int)key_len, EVP_sha256()); - return 0; -} - -static int hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *) -{ - return HMAC_Update((HMAC_CTX *)hmac_context, data, data_len); -} - -static int hmac_sha256_final(void *hmac_context, signal_buffer **output, void *) -{ - BYTE data[200]; - unsigned len = 0; - if (!HMAC_Final((HMAC_CTX *)hmac_context, data, &len)) - return 1; - - *output = signal_buffer_create(data, len); - return 0; -} - -static void hmac_sha256_cleanup(void *hmac_context, void *) -{ - HMAC_CTX_free((HMAC_CTX *)hmac_context); -} - -static int random_func(uint8_t *pData, size_t size, void *) -{ - Utils_GetRandom(pData, size); - return 0; -} - -static int decrypt_func(signal_buffer **output, - int /*cipher*/, - const uint8_t *key, size_t /*key_len*/, - const uint8_t *iv, size_t /*iv_len*/, - const uint8_t *ciphertext, size_t ciphertext_len, - void * /*user_data*/) -{ - MBinBuffer res = aesDecrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len); - *output = signal_buffer_create(res.data(), res.length()); - return SG_SUCCESS; -} - -static int encrypt_func(signal_buffer **output, - int /*cipher*/, - const uint8_t *key, size_t /*key_len*/, - const uint8_t *iv, size_t /*iv_len*/, - const uint8_t *ciphertext, size_t ciphertext_len, - void * /*user_data*/) -{ - MBinBuffer res = aesEncrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len); - *output = signal_buffer_create(res.data(), res.length()); - return SG_SUCCESS; -} - -static int contains_session_func(const signal_protocol_address *address, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - auto *pSession = pStore->getSession(address); - return pSession != nullptr; -} - -static int delete_all_sessions_func(const char *name, size_t name_len, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - auto &pList = pStore->arSessions; - - int count = 0; - for (auto &it : pList.rev_iter()) { - if (it->hasAddress(name, name_len)) { - pStore->pProto->delSetting(it->getSetting()); - pList.remove(pList.indexOf(&it)); - count++; - } - } - return count; -} - -int delete_session_func(const signal_protocol_address *address, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - auto &pList = pStore->arSessions; - - MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); - int idx = pList.getIndex(&tmp); - if (idx != -1) { - pStore->pProto->delSetting(tmp.getSetting()); - pList.remove(idx); - } - return 0; -} - -static int get_sub_device_sessions_func(signal_int_list **sessions, const char *name, size_t name_len, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - CMStringA szName(name, (int)name_len); - - signal_int_list *l = signal_int_list_alloc(); - unsigned int array_size = 0; - - for (auto &it : pStore->arSessions) - if (it->szName == szName) { - array_size++; - signal_int_list_push_back(l, it->getDeviceId()); - } - - *sessions = l; - return array_size; -} - -static void destroy_func(void *) -{} - -int load_session_func(signal_buffer **record, signal_buffer **user_data_storage, const signal_protocol_address *address, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - auto *pSession = pStore->getSession(address); - if (pSession == nullptr) - return 0; - - *record = signal_buffer_create(pSession->sessionData.data(), pSession->sessionData.length()); - *user_data_storage = 0; - return 1; -} - -static int store_session_func(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t *, size_t, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); - auto *pSession = pStore->arSessions.find(&tmp); - if (pSession == nullptr) { - pSession = new MSignalSession(tmp); - pStore->arSessions.insert(pSession); - } - - pSession->sessionData.assign(record, record_len); - db_set_blob(0, pStore->pProto->m_szModuleName, pSession->getSetting(), record, (unsigned)record_len); - return 0; -} - -static int contains_pre_key(uint32_t pre_key_id, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id); - MBinBuffer blob(pStore->pProto->getBlob(szSetting)); - return (blob.data() != 0); -} - -static int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id); - MBinBuffer blob(pStore->pProto->getBlob(szSetting)); - if (blob.isEmpty()) { - pStore->pProto->debugLogA("Prekey #%d not found", pre_key_id); - return SG_ERR_INVALID_KEY_ID; - } - - *record = signal_buffer_create(blob.data(), blob.length()); - return SG_SUCCESS; //key exists and succesfully loaded -} - -static int remove_pre_key(uint32_t pre_key_id, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - pStore->pProto->debugLogA("Request to remove prekey #%d", pre_key_id); - - /* - CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id); - pStore->pProto->delSetting(szSetting); - - szSetting.Format("PreKey%uPublic", pre_key_id); - pStore->pProto->delSetting(szSetting); - - szSetting.Format("PreKey%uPrivate", pre_key_id); - pStore->pProto->delSetting(szSetting); - */ - return 0; -} - -static int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id); - db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len); - - session_pre_key *prekey = nullptr; - session_pre_key_deserialize(&prekey, record, record_len, pStore->CTX()); //TODO: handle error - if (prekey) { - ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(prekey); - - SignalBuffer key_buf(ec_key_pair_get_public(pre_key_pair)); - szSetting.Format("PreKey%uPublic", pre_key_id); - db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_buf.data(), key_buf.len()); - } - - return 0; -} - -static int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id); - MBinBuffer blob(pStore->pProto->getBlob(szSetting)); - return blob.data() != 0; -} - -static int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - if (signed_pre_key_id == 0) - signed_pre_key_id = 1; - - CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id); - MBinBuffer blob(pStore->pProto->getBlob(szSetting)); - if (blob.isEmpty()) { - pStore->pProto->debugLogA("Signed prekey #%d not found", signed_pre_key_id); - return SG_ERR_INVALID_KEY_ID; - } - - *record = signal_buffer_create(blob.data(), blob.length()); - return SG_SUCCESS; //key exist and succesfully loaded -} - -static int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id); - db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len); - return 0; -} - -static int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id); - pStore->pProto->delSetting(szSetting); - return 0; -} - -static int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - MBinBuffer buf; - buf.append(KEY_BUNDLE_TYPE, 1); - buf.append(pStore->signedIdentity.pub); - *public_data = signal_buffer_create(buf.data(), (int)buf.length()); - - *private_data = signal_buffer_create(pStore->signedIdentity.priv.data(), (int)pStore->signedIdentity.priv.length()); - return 0; -} - -static int get_local_registration_id(void *user_data, uint32_t *registration_id) -{ - auto *pStore = (MSignalStore *)user_data; - *registration_id = pStore->pProto->getDword(DBKEY_REG_ID); - return 0; -} - -static int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(FORMAT, "%s_%s_%d", "SignalIdentity", CMStringA(address->name, (int)address->name_len).c_str(), address->device_id); - if (key_data != nullptr) - db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_data, (unsigned int)key_len); //TODO: check return value - else - pStore->pProto->delSetting(szSetting); - return 0; -} - -static int is_trusted_identity(const signal_protocol_address * /*address*/, uint8_t * /*key_data*/, size_t /*key_len*/, void * /*user_data*/) -{ - return 1; -} - -static CMStringA get_sender_setting(const signal_protocol_sender_key_name *skn) -{ - WAJid jid(CMStringA(skn->sender.name, (int)skn->sender.name_len)); - return CMStringA(FORMAT, "SenderKey_%*s_%s_%d", (unsigned)skn->group_id_len, skn->group_id, jid.user.c_str(), skn->sender.device_id); -} - -static int load_sender_key(signal_buffer **record, signal_buffer **, const signal_protocol_sender_key_name *skn, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(get_sender_setting(skn)); - MBinBuffer blob(pStore->pProto->getBlob(szSetting)); - if (blob.isEmpty()) - return 0; - - *record = signal_buffer_create(blob.data(), blob.length()); - return 1; -} - -static int store_sender_key(const signal_protocol_sender_key_name *skn, uint8_t *record, size_t record_len, uint8_t*, size_t, void *user_data) -{ - auto *pStore = (MSignalStore *)user_data; - - CMStringA szSetting(get_sender_setting(skn)); - db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned)record_len); - return 0; -} - -void MSignalStore::init() -{ - signal_context_create(&m_pContext, this); - signal_context_set_log_function(m_pContext, log_func); - - signal_crypto_provider prov; - memset(&prov, 0xFF, sizeof(prov)); - prov.hmac_sha256_init_func = hmac_sha256_init; - prov.hmac_sha256_final_func = hmac_sha256_final; - prov.hmac_sha256_update_func = hmac_sha256_update; - prov.hmac_sha256_cleanup_func = hmac_sha256_cleanup; - prov.random_func = random_func; - prov.decrypt_func = decrypt_func; - prov.encrypt_func = encrypt_func; - signal_context_set_crypto_provider(m_pContext, &prov); - - // read resident data from database - MBinBuffer blob(pProto->getBlob(DBKEY_PREKEY)); - if (blob.isEmpty()) { - // nothing? generate signed identity keys (private & public) - ratchet_identity_key_pair *keyPair; - signal_protocol_key_helper_generate_identity_key_pair(&keyPair, m_pContext); - - auto *pPubKey = ratchet_identity_key_pair_get_public(keyPair); - db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data)); - - auto *pPrivKey = ratchet_identity_key_pair_get_private(keyPair); - db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data)); - - session_signed_pre_key *signed_pre_key; - signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, 1, time(0), m_pContext); - - SignalBuffer prekeyBuf(signed_pre_key); - db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY, prekeyBuf.data(), prekeyBuf.len()); - blob.assign(prekeyBuf.data(), prekeyBuf.len()); - - SIGNAL_UNREF(signed_pre_key); - SIGNAL_UNREF(keyPair); - } - - session_signed_pre_key *signed_pre_key; - session_signed_pre_key_deserialize(&signed_pre_key, blob.data(), blob.length(), m_pContext); - - ec_key_pair *pKeys = session_signed_pre_key_get_key_pair(signed_pre_key); - auto *pPubKey = ec_key_pair_get_public(pKeys); - preKey.pub.assign(pPubKey->data, sizeof(pPubKey->data)); - - auto *pPrivKey = ec_key_pair_get_private(pKeys); - preKey.priv.assign(pPrivKey->data, sizeof(pPrivKey->data)); - - preKey.signature.assign(session_signed_pre_key_get_signature(signed_pre_key), session_signed_pre_key_get_signature_len(signed_pre_key)); - preKey.keyid = session_signed_pre_key_get_id(signed_pre_key); - SIGNAL_UNREF(signed_pre_key); - - signedIdentity.pub = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PUB); - signedIdentity.priv = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PRIV); - - // create store with callbacks - signal_protocol_store_context_create(&m_pStore, m_pContext); - - signal_protocol_session_store ss; - ss.contains_session_func = &contains_session_func; - ss.delete_all_sessions_func = &delete_all_sessions_func; - ss.delete_session_func = &delete_session_func; - ss.destroy_func = &destroy_func; - ss.get_sub_device_sessions_func = &get_sub_device_sessions_func; - ss.load_session_func = &load_session_func; - ss.store_session_func = &store_session_func; - ss.user_data = this; - signal_protocol_store_context_set_session_store(m_pStore, &ss); - - signal_protocol_pre_key_store sp; - sp.contains_pre_key = &contains_pre_key; - sp.destroy_func = &destroy_func; - sp.load_pre_key = &load_pre_key; - sp.remove_pre_key = &remove_pre_key; - sp.store_pre_key = &store_pre_key; - sp.user_data = this; - signal_protocol_store_context_set_pre_key_store(m_pStore, &sp); - - signal_protocol_sender_key_store sk; - sk.destroy_func = destroy_func; - sk.load_sender_key = load_sender_key; - sk.store_sender_key = store_sender_key; - sk.user_data = this; - signal_protocol_store_context_set_sender_key_store(m_pStore, &sk); - - signal_protocol_signed_pre_key_store ssp; - ssp.contains_signed_pre_key = &contains_signed_pre_key; - ssp.destroy_func = &destroy_func; - ssp.load_signed_pre_key = &load_signed_pre_key; - ssp.remove_signed_pre_key = &remove_signed_pre_key; - ssp.store_signed_pre_key = &store_signed_pre_key; - ssp.user_data = this; - signal_protocol_store_context_set_signed_pre_key_store(m_pStore, &ssp); - - signal_protocol_identity_key_store sip; - sip.destroy_func = &destroy_func; - sip.get_identity_key_pair = &get_identity_key_pair; - sip.get_local_registration_id = &get_local_registration_id; - sip.is_trusted_identity = &is_trusted_identity; - sip.save_identity = &save_identity; - sip.user_data = this; - signal_protocol_store_context_set_identity_key_store(m_pStore, &sip); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// MSignalSession members - -MSignalSession::MSignalSession(const CMStringA &_1, int _2) : - szName(_1) -{ - address.name = szName.GetBuffer(); - address.name_len = szName.GetLength(); - address.device_id = _2; -} - -MSignalSession::~MSignalSession() -{ - session_cipher_free(cipher); -} - -bool MSignalSession::hasAddress(const char *name, size_t name_len) const -{ - if (address.name_len != name_len) - return false; - return memcmp(address.name, name, name_len) == 0; -} - -CMStringA MSignalSession::getSetting() const -{ - return CMStringA(FORMAT, "SignalSession_%s_%d", szName.c_str(), getDeviceId()); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MSignalSession* MSignalStore::createSession(const CMStringA &szName, int deviceId) -{ - signal_protocol_address tmp = {szName.c_str(), (unsigned)szName.GetLength(), deviceId}; - auto *pSession = getSession(&tmp); - if (pSession == nullptr) { - pSession = new MSignalSession(szName, deviceId); - arSessions.insert(pSession); - } - - if (pSession->cipher == nullptr) - logError( - session_cipher_create(&pSession->cipher, m_pStore, &pSession->address, m_pContext), - "session_cipher_create failure"); - - return pSession; -} - -MSignalSession* MSignalStore::getSession(const signal_protocol_address *address) -{ - MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); - auto *pSession = arSessions.find(&tmp); - if (pSession == nullptr) { - MBinBuffer blob(pProto->getBlob(tmp.getSetting())); - if (blob.isEmpty()) - return nullptr; - - pSession = new MSignalSession(tmp); - pSession->sessionData.assign(blob.data(), blob.length()); - arSessions.insert(pSession); - } - - return pSession; -} - -void MSignalStore::importPublicKey(ec_public_key **result, MBinBuffer &buf) -{ - buf.appendBefore("\x05", 1); - curve_decode_point(result, buf.data(), buf.length(), m_pContext); -} - -void MSignalStore::injectSession(const char *szJid, const WANode *pNode, const WANode *pKey) -{ - WAJid jid(szJid); - auto *signedKey = pKey->getChild("skey"); - auto *key = pKey->getChild("key"); - auto *identity = pKey->getChild("identity"); - auto *registration = pNode->getChild("registration"); - if (!signedKey || !key || !identity || !registration) { - pProto->debugLogA("Bad key data for %s", jid.toString().c_str()); - return; - } - - signal_protocol_address address = {jid.user.c_str(), (unsigned)jid.user.GetLength(), jid.device}; - - session_builder *builder; - logError( - session_builder_create(&builder, m_pStore, &address, m_pContext), - "unable to create session cipher"); - - int regId = decodeBigEndian(registration->content); - int preKeyId = decodeBigEndian(key->getChild("id")->content); - int signedPreKeyId = decodeBigEndian(signedKey->getChild("id")->content); - - ec_public_key *preKeyPub, *signedPreKeyPub, *identityKey; - importPublicKey(&preKeyPub, key->getChild("value")->content); - importPublicKey(&identityKey, identity->content); - importPublicKey(&signedPreKeyPub, signedKey->getChild("value")->content); - - auto &sign = signedKey->getChild("signature")->content; - - session_pre_key_bundle *bundle; - logError( - session_pre_key_bundle_create(&bundle, regId, jid.device, preKeyId, preKeyPub, signedPreKeyId, signedPreKeyPub, sign.data(), sign.length(), identityKey), - "unable to create pre key bundle"); - - logError( - session_builder_process_pre_key_bundle(builder, bundle), - "unable to process pre key bundle"); - - session_pre_key_bundle_destroy((signal_type_base*)bundle); - session_builder_free(builder); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MBinBuffer MSignalStore::decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted) -{ - WAJid jid(from); - auto *pSession = createSession(jid.user, jid.device); - - signal_buffer *result = nullptr; - if (!mir_strcmp(pszType, "pkmsg")) { - pre_key_signal_message *pMsg; - logError( - pre_key_signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext), - "unable to deserialize prekey message"); - - logError( - session_cipher_decrypt_pre_key_signal_message(pSession->getCipher(), pMsg, this, &result), - "unable to decrypt prekey message"); - - pre_key_signal_message_destroy((signal_type_base*)pMsg); - } - else { - signal_message *pMsg; - logError( - signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext), - "unable to deserialize signal message"); - - logError( - session_cipher_decrypt_signal_message(pSession->getCipher(), pMsg, this, &result), - "unable to decrypt signal message"); - - signal_message_destroy((signal_type_base *)pMsg); - } - - MBinBuffer res; - res.assign(result->data, result->len); - signal_buffer_free(result); - return res; -} - -MBinBuffer MSignalStore::decryptGroupSignalProto(const CMStringA &group, const CMStringA &sender, const MBinBuffer &encrypted) -{ - WAJid jid(sender); - signal_protocol_sender_key_name senderKeyName; - senderKeyName.group_id = group.c_str(); - senderKeyName.group_id_len = group.GetLength(); - senderKeyName.sender.device_id = 0; - senderKeyName.sender.name = jid.user.c_str(); - senderKeyName.sender.name_len = jid.user.GetLength(); - - group_cipher *cipher; - logError( - group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext), - "unable to create group cipher"); - - sender_key_message *skmsg; - logError( - sender_key_message_deserialize(&skmsg, encrypted.data(), encrypted.length(), m_pContext), - "unable to deserialize skmsg"); - - signal_buffer *result = nullptr; - logError( - group_cipher_decrypt(cipher, skmsg, this, &result), - "unable to decrypt skmsg"); - - sender_key_message_destroy((signal_type_base *)skmsg); - group_cipher_free(cipher); - - MBinBuffer res; - res.assign(result->data, result->len); - signal_buffer_free(result); - return res; -} - -void MSignalStore::processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg) -{ - WAJid jid(author); - signal_protocol_sender_key_name senderKeyName; - senderKeyName.group_id = msg->groupid; - senderKeyName.group_id_len = mir_strlen(msg->groupid); - senderKeyName.sender.device_id = 0; - senderKeyName.sender.name = jid.user.c_str(); - senderKeyName.sender.name_len = jid.user.GetLength(); - - group_session_builder *builder; - logError( - group_session_builder_create(&builder, m_pStore, m_pContext), - "unable to create session builder"); - - sender_key_distribution_message *skmsg; - logError( - sender_key_distribution_message_deserialize(&skmsg, msg->axolotlsenderkeydistributionmessage.data, msg->axolotlsenderkeydistributionmessage.len, m_pContext), - "unable to decode skd message"); - - logError( - group_session_builder_process_session(builder, &senderKeyName, skmsg), - "unable to process skd message"); - - sender_key_distribution_message_destroy((signal_type_base *)skmsg); - group_session_builder_free(builder); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// encryption - -MBinBuffer MSignalStore::encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey) -{ - signal_protocol_sender_key_name senderKeyName; - senderKeyName.group_id = to.user.c_str(); - senderKeyName.group_id_len = to.user.GetLength(); - senderKeyName.sender.device_id = 0; - senderKeyName.sender.name = from.c_str(); - senderKeyName.sender.name_len = from.GetLength(); - - group_session_builder *builder; - logError( - group_session_builder_create(&builder, m_pStore, m_pContext), - "unable to create session builder"); - - sender_key_distribution_message *skmsg; - logError( - group_session_builder_create_session(builder, &skmsg, &senderKeyName), - "unable to create session"); - - group_cipher *cipher; - logError( - group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext), - "unable to create group cipher"); - - ciphertext_message *encMessage; - logError( - group_cipher_encrypt(cipher, buf.data(), buf.length(), &encMessage), - "unable to encrypt group message"); - - MBinBuffer res; - auto *cipherText = ciphertext_message_get_serialized(encMessage); - res.assign(cipherText->data, cipherText->len); - - auto *pKey = sender_key_distribution_message_get_signature_key(skmsg); - skmsgKey.assign(pKey->data, sizeof(pKey->data)); - - sender_key_distribution_message_destroy((signal_type_base*)skmsg); - group_cipher_free(cipher); - group_session_builder_free(builder); - return res; -} - -MBinBuffer MSignalStore::encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type) -{ - auto *pSession = createSession(to.user, to.device); - - ciphertext_message *pEncrypted; - logError( - session_cipher_encrypt(pSession->getCipher(), buf.data(), buf.length(), &pEncrypted), - "unable to encrypt signal message"); - - type = ciphertext_message_get_type(pEncrypted); - - MBinBuffer res; - auto *encBuf = ciphertext_message_get_serialized(pEncrypted); - res.assign(encBuf->data, encBuf->len); - SIGNAL_UNREF(pEncrypted); - return res; -} - -MBinBuffer MSignalStore::encodeSignedIdentity(bool bIncludeSignatureKey) -{ - proto::ADVSignedDeviceIdentity identity(pProto->getBlob("WAAccount")); - - if (!bIncludeSignatureKey) - proto::CleanBinary(identity->accountsignaturekey), identity->has_accountsignaturekey = false; - - return proto::Serialize(identity); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// generate and save pre keys set - -void MSignalStore::generatePrekeys(int count) -{ - int iNextKeyId = pProto->getDword(DBKEY_PREKEY_NEXT_ID, 1); - - CMStringA szSetting; - signal_protocol_key_helper_pre_key_list_node *keys_root; - signal_protocol_key_helper_generate_pre_keys(&keys_root, iNextKeyId, count, m_pContext); - for (auto *it = keys_root; it; it = signal_protocol_key_helper_key_list_next(it)) { - session_pre_key *pre_key = signal_protocol_key_helper_key_list_element(it); - uint32_t pre_key_id = session_pre_key_get_id(pre_key); - - SignalBuffer buf(pre_key); - szSetting.Format("PreKey%d", pre_key_id); - db_set_blob(0, pProto->m_szModuleName, szSetting, buf.data(), buf.len()); - - ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(pre_key); - auto *pPubKey = ec_key_pair_get_public(pre_key_pair); - szSetting.Format("PreKey%dPublic", pre_key_id); - db_set_blob(0, pProto->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data)); - } - signal_protocol_key_helper_key_list_free(keys_root); - - pProto->setDword(DBKEY_PREKEY_NEXT_ID, iNextKeyId + count); -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSignalStore members
+
+static int CompareSessions(const MSignalSession *p1, const MSignalSession *p2)
+{
+ if (int ret = mir_strcmp(p1->szName, p2->szName))
+ return ret;
+
+ return p1->getDeviceId() - p2->getDeviceId();
+}
+
+MSignalStore::MSignalStore(PROTO_INTERFACE *_1, const char *_2) :
+ pProto(_1),
+ prefix(_2),
+ arSessions(1, &CompareSessions)
+{
+ init();
+}
+
+MSignalStore::~MSignalStore()
+{
+ signal_protocol_store_context_destroy(m_pStore);
+ signal_context_destroy(m_pContext);
+}
+
+void MSignalStore::logError(int err, const char *pszMessage)
+{
+ if (err < 0) {
+ pProto->debugLogA("libsignal error %d", err);
+ throw pszMessage;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void log_func(int level, const char *pmsg, size_t /*msgLen*/, void *pUserData)
+{
+ auto *pStore = (MSignalStore *)pUserData;
+ pStore->pProto->debugLogA("libsignal {%d}: %s", level, pmsg);
+}
+
+static int hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *)
+{
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ *hmac_context = ctx;
+ HMAC_Init(ctx, key, (int)key_len, EVP_sha256());
+ return 0;
+}
+
+static int hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *)
+{
+ return HMAC_Update((HMAC_CTX *)hmac_context, data, data_len);
+}
+
+static int hmac_sha256_final(void *hmac_context, signal_buffer **output, void *)
+{
+ BYTE data[200];
+ unsigned len = 0;
+ if (!HMAC_Final((HMAC_CTX *)hmac_context, data, &len))
+ return 1;
+
+ *output = signal_buffer_create(data, len);
+ return 0;
+}
+
+static void hmac_sha256_cleanup(void *hmac_context, void *)
+{
+ HMAC_CTX_free((HMAC_CTX *)hmac_context);
+}
+
+static int random_func(uint8_t *pData, size_t size, void *)
+{
+ Utils_GetRandom(pData, size);
+ return 0;
+}
+
+static int decrypt_func(signal_buffer **output,
+ int /*cipher*/,
+ const uint8_t *key, size_t /*key_len*/,
+ const uint8_t *iv, size_t /*iv_len*/,
+ const uint8_t *ciphertext, size_t ciphertext_len,
+ void * /*user_data*/)
+{
+ MBinBuffer res = aesDecrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len);
+ *output = signal_buffer_create(res.data(), res.length());
+ return SG_SUCCESS;
+}
+
+static int encrypt_func(signal_buffer **output,
+ int /*cipher*/,
+ const uint8_t *key, size_t /*key_len*/,
+ const uint8_t *iv, size_t /*iv_len*/,
+ const uint8_t *ciphertext, size_t ciphertext_len,
+ void * /*user_data*/)
+{
+ MBinBuffer res = aesEncrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len);
+ *output = signal_buffer_create(res.data(), res.length());
+ return SG_SUCCESS;
+}
+
+static int contains_session_func(const signal_protocol_address *address, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ auto *pSession = pStore->getSession(address);
+ return pSession != nullptr;
+}
+
+static int delete_all_sessions_func(const char *name, size_t name_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ auto &pList = pStore->arSessions;
+
+ int count = 0;
+ for (auto &it : pList.rev_iter()) {
+ if (it->hasAddress(name, name_len)) {
+ pStore->pProto->delSetting(it->getSetting());
+ pList.remove(pList.indexOf(&it));
+ count++;
+ }
+ }
+ return count;
+}
+
+int delete_session_func(const signal_protocol_address *address, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ auto &pList = pStore->arSessions;
+
+ MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
+ int idx = pList.getIndex(&tmp);
+ if (idx != -1) {
+ pStore->pProto->delSetting(tmp.getSetting());
+ pList.remove(idx);
+ }
+ return 0;
+}
+
+static int get_sub_device_sessions_func(signal_int_list **sessions, const char *name, size_t name_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ CMStringA szName(name, (int)name_len);
+
+ signal_int_list *l = signal_int_list_alloc();
+ unsigned int array_size = 0;
+
+ for (auto &it : pStore->arSessions)
+ if (it->szName == szName) {
+ array_size++;
+ signal_int_list_push_back(l, it->getDeviceId());
+ }
+
+ *sessions = l;
+ return array_size;
+}
+
+static void destroy_func(void *)
+{}
+
+int load_session_func(signal_buffer **record, signal_buffer **user_data_storage, const signal_protocol_address *address, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ auto *pSession = pStore->getSession(address);
+ if (pSession == nullptr)
+ return 0;
+
+ *record = signal_buffer_create(pSession->sessionData.data(), pSession->sessionData.length());
+ *user_data_storage = 0;
+ return 1;
+}
+
+static int store_session_func(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t *, size_t, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
+ auto *pSession = pStore->arSessions.find(&tmp);
+ if (pSession == nullptr) {
+ pSession = new MSignalSession(tmp);
+ pStore->arSessions.insert(pSession);
+ }
+
+ pSession->sessionData.assign(record, record_len);
+ db_set_blob(0, pStore->pProto->m_szModuleName, pSession->getSetting(), record, (unsigned)record_len);
+ return 0;
+}
+
+static int contains_pre_key(uint32_t pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ return (blob.data() != 0);
+}
+
+static int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ if (blob.isEmpty()) {
+ pStore->pProto->debugLogA("Prekey #%d not found", pre_key_id);
+ return SG_ERR_INVALID_KEY_ID;
+ }
+
+ *record = signal_buffer_create(blob.data(), blob.length());
+ return SG_SUCCESS; //key exists and succesfully loaded
+}
+
+static int remove_pre_key(uint32_t pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ pStore->pProto->debugLogA("Request to remove prekey #%d", pre_key_id);
+
+ /*
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+
+ szSetting.Format("PreKey%uPublic", pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+
+ szSetting.Format("PreKey%uPrivate", pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+ */
+ return 0;
+}
+
+static int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len);
+
+ session_pre_key *prekey = nullptr;
+ session_pre_key_deserialize(&prekey, record, record_len, pStore->CTX()); //TODO: handle error
+ if (prekey) {
+ ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(prekey);
+
+ SignalBuffer key_buf(ec_key_pair_get_public(pre_key_pair));
+ szSetting.Format("PreKey%uPublic", pre_key_id);
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_buf.data(), key_buf.len());
+ }
+
+ return 0;
+}
+
+static int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ return blob.data() != 0;
+}
+
+static int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ if (signed_pre_key_id == 0)
+ signed_pre_key_id = 1;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ if (blob.isEmpty()) {
+ pStore->pProto->debugLogA("Signed prekey #%d not found", signed_pre_key_id);
+ return SG_ERR_INVALID_KEY_ID;
+ }
+
+ *record = signal_buffer_create(blob.data(), blob.length());
+ return SG_SUCCESS; //key exist and succesfully loaded
+}
+
+static int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len);
+ return 0;
+}
+
+static int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+ return 0;
+}
+
+static int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ MBinBuffer buf;
+ buf.append(KEY_BUNDLE_TYPE, 1);
+ buf.append(pStore->signedIdentity.pub);
+ *public_data = signal_buffer_create(buf.data(), (int)buf.length());
+
+ *private_data = signal_buffer_create(pStore->signedIdentity.priv.data(), (int)pStore->signedIdentity.priv.length());
+ return 0;
+}
+
+static int get_local_registration_id(void *user_data, uint32_t *registration_id)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ *registration_id = pStore->pProto->getDword(DBKEY_REG_ID);
+ return 0;
+}
+
+static int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s_%s_%d", "SignalIdentity", CMStringA(address->name, (int)address->name_len).c_str(), address->device_id);
+ if (key_data != nullptr)
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_data, (unsigned int)key_len); //TODO: check return value
+ else
+ pStore->pProto->delSetting(szSetting);
+ return 0;
+}
+
+static int is_trusted_identity(const signal_protocol_address * /*address*/, uint8_t * /*key_data*/, size_t /*key_len*/, void * /*user_data*/)
+{
+ return 1;
+}
+
+static CMStringA get_sender_setting(const signal_protocol_sender_key_name *skn)
+{
+ WAJid jid(CMStringA(skn->sender.name, (int)skn->sender.name_len));
+ return CMStringA(FORMAT, "SenderKey_%*s_%s_%d", (unsigned)skn->group_id_len, skn->group_id, jid.user.c_str(), skn->sender.device_id);
+}
+
+static int load_sender_key(signal_buffer **record, signal_buffer **, const signal_protocol_sender_key_name *skn, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(get_sender_setting(skn));
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ if (blob.isEmpty())
+ return 0;
+
+ *record = signal_buffer_create(blob.data(), blob.length());
+ return 1;
+}
+
+static int store_sender_key(const signal_protocol_sender_key_name *skn, uint8_t *record, size_t record_len, uint8_t*, size_t, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(get_sender_setting(skn));
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned)record_len);
+ return 0;
+}
+
+void MSignalStore::init()
+{
+ signal_context_create(&m_pContext, this);
+ signal_context_set_log_function(m_pContext, log_func);
+
+ signal_crypto_provider prov;
+ memset(&prov, 0xFF, sizeof(prov));
+ prov.hmac_sha256_init_func = hmac_sha256_init;
+ prov.hmac_sha256_final_func = hmac_sha256_final;
+ prov.hmac_sha256_update_func = hmac_sha256_update;
+ prov.hmac_sha256_cleanup_func = hmac_sha256_cleanup;
+ prov.random_func = random_func;
+ prov.decrypt_func = decrypt_func;
+ prov.encrypt_func = encrypt_func;
+ signal_context_set_crypto_provider(m_pContext, &prov);
+
+ // read resident data from database
+ MBinBuffer blob(pProto->getBlob(DBKEY_PREKEY));
+ if (blob.isEmpty()) {
+ // nothing? generate signed identity keys (private & public)
+ ratchet_identity_key_pair *keyPair;
+ signal_protocol_key_helper_generate_identity_key_pair(&keyPair, m_pContext);
+
+ auto *pPubKey = ratchet_identity_key_pair_get_public(keyPair);
+ db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ratchet_identity_key_pair_get_private(keyPair);
+ db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
+
+ session_signed_pre_key *signed_pre_key;
+ signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, 1, time(0), m_pContext);
+
+ SignalBuffer prekeyBuf(signed_pre_key);
+ db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY, prekeyBuf.data(), prekeyBuf.len());
+ blob.assign(prekeyBuf.data(), prekeyBuf.len());
+
+ SIGNAL_UNREF(signed_pre_key);
+ SIGNAL_UNREF(keyPair);
+ }
+
+ session_signed_pre_key *signed_pre_key;
+ session_signed_pre_key_deserialize(&signed_pre_key, blob.data(), blob.length(), m_pContext);
+
+ ec_key_pair *pKeys = session_signed_pre_key_get_key_pair(signed_pre_key);
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ preKey.pub.assign(pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ preKey.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
+
+ preKey.signature.assign(session_signed_pre_key_get_signature(signed_pre_key), session_signed_pre_key_get_signature_len(signed_pre_key));
+ preKey.keyid = session_signed_pre_key_get_id(signed_pre_key);
+ SIGNAL_UNREF(signed_pre_key);
+
+ signedIdentity.pub = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PUB);
+ signedIdentity.priv = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PRIV);
+
+ // create store with callbacks
+ signal_protocol_store_context_create(&m_pStore, m_pContext);
+
+ signal_protocol_session_store ss;
+ ss.contains_session_func = &contains_session_func;
+ ss.delete_all_sessions_func = &delete_all_sessions_func;
+ ss.delete_session_func = &delete_session_func;
+ ss.destroy_func = &destroy_func;
+ ss.get_sub_device_sessions_func = &get_sub_device_sessions_func;
+ ss.load_session_func = &load_session_func;
+ ss.store_session_func = &store_session_func;
+ ss.user_data = this;
+ signal_protocol_store_context_set_session_store(m_pStore, &ss);
+
+ signal_protocol_pre_key_store sp;
+ sp.contains_pre_key = &contains_pre_key;
+ sp.destroy_func = &destroy_func;
+ sp.load_pre_key = &load_pre_key;
+ sp.remove_pre_key = &remove_pre_key;
+ sp.store_pre_key = &store_pre_key;
+ sp.user_data = this;
+ signal_protocol_store_context_set_pre_key_store(m_pStore, &sp);
+
+ signal_protocol_sender_key_store sk;
+ sk.destroy_func = destroy_func;
+ sk.load_sender_key = load_sender_key;
+ sk.store_sender_key = store_sender_key;
+ sk.user_data = this;
+ signal_protocol_store_context_set_sender_key_store(m_pStore, &sk);
+
+ signal_protocol_signed_pre_key_store ssp;
+ ssp.contains_signed_pre_key = &contains_signed_pre_key;
+ ssp.destroy_func = &destroy_func;
+ ssp.load_signed_pre_key = &load_signed_pre_key;
+ ssp.remove_signed_pre_key = &remove_signed_pre_key;
+ ssp.store_signed_pre_key = &store_signed_pre_key;
+ ssp.user_data = this;
+ signal_protocol_store_context_set_signed_pre_key_store(m_pStore, &ssp);
+
+ signal_protocol_identity_key_store sip;
+ sip.destroy_func = &destroy_func;
+ sip.get_identity_key_pair = &get_identity_key_pair;
+ sip.get_local_registration_id = &get_local_registration_id;
+ sip.is_trusted_identity = &is_trusted_identity;
+ sip.save_identity = &save_identity;
+ sip.user_data = this;
+ signal_protocol_store_context_set_identity_key_store(m_pStore, &sip);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSignalSession members
+
+MSignalSession::MSignalSession(const CMStringA &_1, int _2) :
+ szName(_1)
+{
+ address.name = szName.GetBuffer();
+ address.name_len = szName.GetLength();
+ address.device_id = _2;
+}
+
+MSignalSession::~MSignalSession()
+{
+ session_cipher_free(cipher);
+}
+
+bool MSignalSession::hasAddress(const char *name, size_t name_len) const
+{
+ if (address.name_len != name_len)
+ return false;
+ return memcmp(address.name, name, name_len) == 0;
+}
+
+CMStringA MSignalSession::getSetting() const
+{
+ return CMStringA(FORMAT, "SignalSession_%s_%d", szName.c_str(), getDeviceId());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MSignalSession* MSignalStore::createSession(const CMStringA &szName, int deviceId)
+{
+ signal_protocol_address tmp = {szName.c_str(), (unsigned)szName.GetLength(), deviceId};
+ auto *pSession = getSession(&tmp);
+ if (pSession == nullptr) {
+ pSession = new MSignalSession(szName, deviceId);
+ arSessions.insert(pSession);
+ }
+
+ if (pSession->cipher == nullptr)
+ logError(
+ session_cipher_create(&pSession->cipher, m_pStore, &pSession->address, m_pContext),
+ "session_cipher_create failure");
+
+ return pSession;
+}
+
+MSignalSession* MSignalStore::getSession(const signal_protocol_address *address)
+{
+ MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
+ auto *pSession = arSessions.find(&tmp);
+ if (pSession == nullptr) {
+ MBinBuffer blob(pProto->getBlob(tmp.getSetting()));
+ if (blob.isEmpty())
+ return nullptr;
+
+ pSession = new MSignalSession(tmp);
+ pSession->sessionData.assign(blob.data(), blob.length());
+ arSessions.insert(pSession);
+ }
+
+ return pSession;
+}
+
+void MSignalStore::importPublicKey(ec_public_key **result, MBinBuffer &buf)
+{
+ buf.appendBefore("\x05", 1);
+ curve_decode_point(result, buf.data(), buf.length(), m_pContext);
+}
+
+void MSignalStore::injectSession(const char *szJid, const WANode *pNode, const WANode *pKey)
+{
+ WAJid jid(szJid);
+ auto *signedKey = pKey->getChild("skey");
+ auto *key = pKey->getChild("key");
+ auto *identity = pKey->getChild("identity");
+ auto *registration = pNode->getChild("registration");
+ if (!signedKey || !key || !identity || !registration) {
+ pProto->debugLogA("Bad key data for %s", jid.toString().c_str());
+ return;
+ }
+
+ signal_protocol_address address = {jid.user.c_str(), (unsigned)jid.user.GetLength(), jid.device};
+
+ session_builder *builder;
+ logError(
+ session_builder_create(&builder, m_pStore, &address, m_pContext),
+ "unable to create session cipher");
+
+ int regId = decodeBigEndian(registration->content);
+ int preKeyId = decodeBigEndian(key->getChild("id")->content);
+ int signedPreKeyId = decodeBigEndian(signedKey->getChild("id")->content);
+
+ ec_public_key *preKeyPub, *signedPreKeyPub, *identityKey;
+ importPublicKey(&preKeyPub, key->getChild("value")->content);
+ importPublicKey(&identityKey, identity->content);
+ importPublicKey(&signedPreKeyPub, signedKey->getChild("value")->content);
+
+ auto &sign = signedKey->getChild("signature")->content;
+
+ session_pre_key_bundle *bundle;
+ logError(
+ session_pre_key_bundle_create(&bundle, regId, jid.device, preKeyId, preKeyPub, signedPreKeyId, signedPreKeyPub, sign.data(), sign.length(), identityKey),
+ "unable to create pre key bundle");
+
+ logError(
+ session_builder_process_pre_key_bundle(builder, bundle),
+ "unable to process pre key bundle");
+
+ session_pre_key_bundle_destroy((signal_type_base*)bundle);
+ session_builder_free(builder);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MBinBuffer MSignalStore::decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted)
+{
+ WAJid jid(from);
+ auto *pSession = createSession(jid.user, jid.device);
+
+ signal_buffer *result = nullptr;
+ if (!mir_strcmp(pszType, "pkmsg")) {
+ pre_key_signal_message *pMsg;
+ logError(
+ pre_key_signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext),
+ "unable to deserialize prekey message");
+
+ logError(
+ session_cipher_decrypt_pre_key_signal_message(pSession->getCipher(), pMsg, this, &result),
+ "unable to decrypt prekey message");
+
+ pre_key_signal_message_destroy((signal_type_base*)pMsg);
+ }
+ else {
+ signal_message *pMsg;
+ logError(
+ signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext),
+ "unable to deserialize signal message");
+
+ logError(
+ session_cipher_decrypt_signal_message(pSession->getCipher(), pMsg, this, &result),
+ "unable to decrypt signal message");
+
+ signal_message_destroy((signal_type_base *)pMsg);
+ }
+
+ MBinBuffer res;
+ res.assign(result->data, result->len);
+ signal_buffer_free(result);
+ return res;
+}
+
+MBinBuffer MSignalStore::decryptGroupSignalProto(const CMStringA &group, const CMStringA &sender, const MBinBuffer &encrypted)
+{
+ WAJid jid(sender);
+ signal_protocol_sender_key_name senderKeyName;
+ senderKeyName.group_id = group.c_str();
+ senderKeyName.group_id_len = group.GetLength();
+ senderKeyName.sender.device_id = 0;
+ senderKeyName.sender.name = jid.user.c_str();
+ senderKeyName.sender.name_len = jid.user.GetLength();
+
+ group_cipher *cipher;
+ logError(
+ group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext),
+ "unable to create group cipher");
+
+ sender_key_message *skmsg;
+ logError(
+ sender_key_message_deserialize(&skmsg, encrypted.data(), encrypted.length(), m_pContext),
+ "unable to deserialize skmsg");
+
+ signal_buffer *result = nullptr;
+ logError(
+ group_cipher_decrypt(cipher, skmsg, this, &result),
+ "unable to decrypt skmsg");
+
+ sender_key_message_destroy((signal_type_base *)skmsg);
+ group_cipher_free(cipher);
+
+ MBinBuffer res;
+ res.assign(result->data, result->len);
+ signal_buffer_free(result);
+ return res;
+}
+
+void MSignalStore::processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg)
+{
+ WAJid jid(author);
+ signal_protocol_sender_key_name senderKeyName;
+ senderKeyName.group_id = msg->groupid;
+ senderKeyName.group_id_len = mir_strlen(msg->groupid);
+ senderKeyName.sender.device_id = 0;
+ senderKeyName.sender.name = jid.user.c_str();
+ senderKeyName.sender.name_len = jid.user.GetLength();
+
+ group_session_builder *builder;
+ logError(
+ group_session_builder_create(&builder, m_pStore, m_pContext),
+ "unable to create session builder");
+
+ sender_key_distribution_message *skmsg;
+ logError(
+ sender_key_distribution_message_deserialize(&skmsg, msg->axolotlsenderkeydistributionmessage.data, msg->axolotlsenderkeydistributionmessage.len, m_pContext),
+ "unable to decode skd message");
+
+ logError(
+ group_session_builder_process_session(builder, &senderKeyName, skmsg),
+ "unable to process skd message");
+
+ sender_key_distribution_message_destroy((signal_type_base *)skmsg);
+ group_session_builder_free(builder);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// encryption
+
+MBinBuffer MSignalStore::encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey)
+{
+ signal_protocol_sender_key_name senderKeyName;
+ senderKeyName.group_id = to.user.c_str();
+ senderKeyName.group_id_len = to.user.GetLength();
+ senderKeyName.sender.device_id = 0;
+ senderKeyName.sender.name = from.c_str();
+ senderKeyName.sender.name_len = from.GetLength();
+
+ group_session_builder *builder;
+ logError(
+ group_session_builder_create(&builder, m_pStore, m_pContext),
+ "unable to create session builder");
+
+ sender_key_distribution_message *skmsg;
+ logError(
+ group_session_builder_create_session(builder, &skmsg, &senderKeyName),
+ "unable to create session");
+
+ group_cipher *cipher;
+ logError(
+ group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext),
+ "unable to create group cipher");
+
+ ciphertext_message *encMessage;
+ logError(
+ group_cipher_encrypt(cipher, buf.data(), buf.length(), &encMessage),
+ "unable to encrypt group message");
+
+ MBinBuffer res;
+ auto *cipherText = ciphertext_message_get_serialized(encMessage);
+ res.assign(cipherText->data, cipherText->len);
+
+ auto *pKey = sender_key_distribution_message_get_signature_key(skmsg);
+ skmsgKey.assign(pKey->data, sizeof(pKey->data));
+
+ sender_key_distribution_message_destroy((signal_type_base*)skmsg);
+ group_cipher_free(cipher);
+ group_session_builder_free(builder);
+ return res;
+}
+
+MBinBuffer MSignalStore::encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type)
+{
+ auto *pSession = createSession(to.user, to.device);
+
+ ciphertext_message *pEncrypted;
+ logError(
+ session_cipher_encrypt(pSession->getCipher(), buf.data(), buf.length(), &pEncrypted),
+ "unable to encrypt signal message");
+
+ type = ciphertext_message_get_type(pEncrypted);
+
+ MBinBuffer res;
+ auto *encBuf = ciphertext_message_get_serialized(pEncrypted);
+ res.assign(encBuf->data, encBuf->len);
+ SIGNAL_UNREF(pEncrypted);
+ return res;
+}
+
+MBinBuffer MSignalStore::encodeSignedIdentity(bool bIncludeSignatureKey)
+{
+ proto::ADVSignedDeviceIdentity identity(pProto->getBlob("WAAccount"));
+
+ if (!bIncludeSignatureKey)
+ proto::CleanBinary(identity->accountsignaturekey), identity->has_accountsignaturekey = false;
+
+ return proto::Serialize(identity);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// generate and save pre keys set
+
+void MSignalStore::generatePrekeys(int count)
+{
+ int iNextKeyId = pProto->getDword(DBKEY_PREKEY_NEXT_ID, 1);
+
+ CMStringA szSetting;
+ signal_protocol_key_helper_pre_key_list_node *keys_root;
+ signal_protocol_key_helper_generate_pre_keys(&keys_root, iNextKeyId, count, m_pContext);
+ for (auto *it = keys_root; it; it = signal_protocol_key_helper_key_list_next(it)) {
+ session_pre_key *pre_key = signal_protocol_key_helper_key_list_element(it);
+ uint32_t pre_key_id = session_pre_key_get_id(pre_key);
+
+ SignalBuffer buf(pre_key);
+ szSetting.Format("PreKey%d", pre_key_id);
+ db_set_blob(0, pProto->m_szModuleName, szSetting, buf.data(), buf.len());
+
+ ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(pre_key);
+ auto *pPubKey = ec_key_pair_get_public(pre_key_pair);
+ szSetting.Format("PreKey%dPublic", pre_key_id);
+ db_set_blob(0, pProto->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data));
+ }
+ signal_protocol_key_helper_key_list_free(keys_root);
+
+ pProto->setDword(DBKEY_PREKEY_NEXT_ID, iNextKeyId + count);
+}
diff --git a/protocols/WhatsApp/src/stdafx.cxx b/protocols/WhatsApp/src/stdafx.cxx index 7942e82b45..5146fe9a0a 100644 --- a/protocols/WhatsApp/src/stdafx.cxx +++ b/protocols/WhatsApp/src/stdafx.cxx @@ -1,8 +1,8 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
diff --git a/protocols/WhatsApp/src/stdafx.h b/protocols/WhatsApp/src/stdafx.h index 945e385af2..3b1326e277 100644 --- a/protocols/WhatsApp/src/stdafx.h +++ b/protocols/WhatsApp/src/stdafx.h @@ -1,72 +1,72 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#pragma once -#pragma warning(disable:4996 4290 4200 4239 4324) - -#include <fcntl.h> -#include <malloc.h> -#include <io.h> -#include <time.h> -#include <windows.h> - -#include <list> -#include <map> -#include <memory> -#include <string> - -#include <newpluginapi.h> -#include <m_avatars.h> -#include <m_chat_int.h> -#include <m_clist.h> -#include <m_contacts.h> -#include <m_database.h> -#include <m_history.h> -#include <m_imgsrvc.h> -#include <m_ignore.h> -#include <m_json.h> -#include <m_langpack.h> -#include <m_message.h> -#include <m_netlib.h> -#include <m_options.h> -#include <m_popup.h> -#include <m_protocols.h> -#include <m_protosvc.h> -#include <m_protoint.h> -#include <m_skin.h> -#include <m_string.h> -#include <statusmodes.h> -#include <m_userinfo.h> -#include <m_icolib.h> -#include <m_utils.h> -#include <m_xml.h> -#include <m_hotkeys.h> -#include <m_folders.h> -#include <m_json.h> -#include <m_gui.h> -#include <m_messagestate.h> - -#include <openssl/evp.h> -#include <openssl/hmac.h> -#include <openssl/sha.h> -#include <openssl/kdf.h> - -#include "../../libs/libqrencode/src/qrencode.h" -#include "../../libs/zlib/src/zlib.h" - -#include "../../utils/mir_signal.h" - -#include "pmsg.proto.h" - -///////////////////////////////////////////////////////////////////////////////////////// - -#include "db.h" -#include "utils.h" -#include "proto.h" -#include "resource.h" - -#pragma comment(lib, "libcrypto.lib") +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#pragma once
+#pragma warning(disable:4996 4290 4200 4239 4324)
+
+#include <fcntl.h>
+#include <malloc.h>
+#include <io.h>
+#include <time.h>
+#include <windows.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include <newpluginapi.h>
+#include <m_avatars.h>
+#include <m_chat_int.h>
+#include <m_clist.h>
+#include <m_contacts.h>
+#include <m_database.h>
+#include <m_history.h>
+#include <m_imgsrvc.h>
+#include <m_ignore.h>
+#include <m_json.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_netlib.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_protocols.h>
+#include <m_protosvc.h>
+#include <m_protoint.h>
+#include <m_skin.h>
+#include <m_string.h>
+#include <statusmodes.h>
+#include <m_userinfo.h>
+#include <m_icolib.h>
+#include <m_utils.h>
+#include <m_xml.h>
+#include <m_hotkeys.h>
+#include <m_folders.h>
+#include <m_json.h>
+#include <m_gui.h>
+#include <m_messagestate.h>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+#include <openssl/kdf.h>
+
+#include "../../libs/libqrencode/src/qrencode.h"
+#include "../../libs/zlib/src/zlib.h"
+
+#include "../../utils/mir_signal.h"
+
+#include "pmsg.proto.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#include "db.h"
+#include "utils.h"
+#include "proto.h"
+#include "resource.h"
+
+#pragma comment(lib, "libcrypto.lib")
diff --git a/protocols/WhatsApp/src/utils.cpp b/protocols/WhatsApp/src/utils.cpp index 05e9d2c3bc..1e2d39f553 100644 --- a/protocols/WhatsApp/src/utils.cpp +++ b/protocols/WhatsApp/src/utils.cpp @@ -1,589 +1,589 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" - -WAJid::WAJid(const char *pszUser, const char *pszServer, int iDevice, int iAgent) : - user(pszUser ? pszUser : ""), - server(pszServer ? pszServer : ""), - device(iDevice), - agent(iAgent) -{} - -WAJid::WAJid(const char *pszJid, int _id) -{ - if (pszJid == nullptr) - pszJid = ""; - - auto *tmp = NEWSTR_ALLOCA(pszJid); - auto *p = strrchr(tmp, '@'); - if (p) { - *p = 0; - server = p + 1; - } - - if (p = strrchr(tmp, ':')) { - *p = 0; - device = atoi(p + 1); - } - else device = _id; - - if (p = strrchr(tmp, '_')) { - *p = 0; - agent = atoi(p + 1); - } - else agent = 0; - - user = tmp; -} - -bool WAJid::isUser() const -{ return server == "s.whatsapp.net"; -} - -bool WAJid::isGroup() const -{ return server == "g.us"; -} - -bool WAJid::isBroadcast() const -{ - return server == "broadcast"; -} - -bool WAJid::isStatusBroadcast() const -{ - return isBroadcast() && user == "status"; -} - -CMStringA WAJid::toString() const -{ - CMStringA ret(user); - if (agent > 0) - ret.AppendFormat("_%d", agent); - if (device > 0) - ret.AppendFormat(":%d", device); - ret.AppendFormat("@%s", server.c_str()); - return ret; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static uint8_t sttLtHashInfo[] = "WhatsApp Patch Integrity"; - -void LT_HASH::add(const void *pData, size_t len) -{ - uint16_t tmp[_countof(hash)]; - HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE*)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp)); - - for (int i = 0; i < _countof(hash); i++) - hash[i] += tmp[i]; -} - -void LT_HASH::sub(const void *pData, size_t len) -{ - uint16_t tmp[_countof(hash)]; - HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE *)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp)); - - for (int i = 0; i < _countof(hash); i++) - hash[i] -= tmp[i]; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -WAUser* WhatsAppProto::FindUser(const char *szId) -{ - if (szId == nullptr) - return nullptr; - - mir_cslock lck(m_csUsers); - auto *tmp = (WAUser *)_alloca(sizeof(WAUser)); - tmp->szId = (char*)szId; - return m_arUsers.find(tmp); -} - -WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary) -{ - auto *pUser = FindUser(szId); - if (pUser != nullptr) - return pUser; - - MCONTACT hContact = db_add_contact(); - Proto_AddToContact(hContact, m_szModuleName); - - pUser = new WAUser(hContact, mir_strdup(szId)); - pUser->bIsGroupChat = WAJid(szId).isGroup(); - - if (pUser->bIsGroupChat) { - setByte(hContact, "ChatRoom", 1); - setString(hContact, "ChatRoomID", szId); - } - else { - setString(hContact, DBKEY_ID, szId); - if (m_wszDefaultGroup) - Clist_SetGroup(hContact, m_wszDefaultGroup); - } - - if (bTemporary) - Contact::RemoveFromList(hContact); - - mir_cslock lck(m_csUsers); - m_arUsers.insert(pUser); - return pUser; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -WA_PKT_HANDLER WhatsAppProto::FindPersistentHandler(const WANode &pNode) -{ - auto *pChild = pNode.getFirstChild(); - CMStringA szChild = (pChild) ? pChild->title : ""; - CMStringA szTitle = pNode.title; - CMStringA szType = pNode.getAttr("type"); - CMStringA szXmlns = pNode.getAttr("xmlns"); - - for (auto &it : m_arPersistent) { - if (it->pszTitle && szTitle != it->pszTitle) - continue; - if (it->pszType && szType != it->pszType) - continue; - if (it->pszXmlns && szXmlns != it->pszXmlns) - continue; - if (it->pszChild && szChild != it->pszChild) - continue; - return it->pHandler; - } - - return nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CMStringA WhatsAppProto::GenerateMessageId() -{ - return CMStringA(FORMAT, "%d.%d-%d", m_wMsgPrefix[0], m_wMsgPrefix[1], m_iPacketId++); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// sends a piece of JSON to a server via a websocket, masked - -int WhatsAppProto::WSSend(const ProtobufCMessage &msg) -{ - if (m_hServerConn == nullptr) - return -1; - - MBinBuffer buf(proto::Serialize(&msg)); - Netlib_Dump(m_hServerConn, buf.data(), buf.length(), true, 0); - - MBinBuffer payload = m_noise->encodeFrame(buf.data(), buf.length()); - WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length()); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int WhatsAppProto::WSSendNode(WANode &node) -{ - if (m_hServerConn == nullptr) - return 0; - - CMStringA szText; - node.print(szText); - debugLogA("Sending binary node:\n%s", szText.c_str()); - - WAWriter writer; - writer.writeNode(&node); - - MBinBuffer encData = m_noise->encrypt(writer.body.data(), writer.body.length()); - MBinBuffer payload = m_noise->encodeFrame(encData.data(), encData.length()); - WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length()); - return 1; -} - -int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler) -{ - if (m_hServerConn == nullptr) - return 0; - - CMStringA id(GenerateMessageId()); - node.addAttr("id", id); - { - mir_cslock lck(m_csPacketQueue); - m_arPacketQueue.insert(new WARequestSimple(id, pHandler)); - } - - return WSSendNode(node); -} - -int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER_FULL pHandler, void *pUserInfo) -{ - if (m_hServerConn == nullptr) - return 0; - - CMStringA id(GenerateMessageId()); - node.addAttr("id", id); - { - mir_cslock lck(m_csPacketQueue); - m_arPacketQueue.insert(new WARequestParam(id, pHandler, pUserInfo)); - } - - return WSSendNode(node); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -std::string decodeBinStr(const std::string &buf) -{ - size_t cbLen; - void *pData = mir_base64_decode(buf.c_str(), &cbLen); - if (pData == nullptr) - return ""; - - std::string res((char *)pData, cbLen); - mir_free(pData); - return res; -} - -MBinBuffer decodeBufStr(const std::string &buf) -{ - MBinBuffer res; - size_t cbLen; - void *pData = mir_base64_decode(buf.c_str(), &cbLen); - if (pData == nullptr) - return res; - - res.assign(pData, cbLen); - mir_free(pData); - return res; -} - -uint32_t decodeBigEndian(const uint8_t *buf, size_t len) -{ - uint32_t ret = 0; - for (int i = 0; i < len; i++) { - ret <<= 8; - ret += buf[i]; - } - - return ret; -} - -std::string encodeBigEndian(uint32_t num, size_t len) -{ - std::string res; - for (int i = 0; i < len; i++) { - char c = num & 0xFF; - res = c + res; - num >>= 8; - } - return res; -} - -void generateIV(uint8_t *iv, uint32_t &pVar) -{ - auto counter = encodeBigEndian(pVar); - memset(iv, 0, 8); - memcpy(iv + 8, counter.c_str(), sizeof(uint32_t)); - - pVar++; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Padding - -void padBuffer16(MBinBuffer &buf) -{ - uint8_t c = 16 - buf.length() % 16; - - for (uint8_t i = 0; i < c; i++) - buf.append(&c, 1); -} - -MBinBuffer unpadBuffer16(const MBinBuffer &buf) -{ - size_t len = buf.length(); - auto p = buf.data() + len - 1; - if (*p <= 0x10) { - MBinBuffer res; - res.assign(buf.data(), len - *p); - return res; - } - - return buf; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Popups - -void WhatsAppProto::InitPopups(void) -{ - g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups); - - char name[256]; - mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); - - wchar_t desc[256]; - mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors")); - - POPUPCLASS ppc = {}; - ppc.flags = PCF_UNICODE; - ppc.pszName = name; - ppc.pszDescription.w = desc; - ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon); - ppc.colorBack = RGB(191, 0, 0); //Red - ppc.colorText = RGB(255, 245, 225); //Yellow - ppc.iSeconds = 60; - m_hPopupClass = Popup_RegisterClass(&ppc); - - IcoLib_ReleaseIcon(ppc.hIcon); -} - -void WhatsAppProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle) -{ - if (!m_bUsePopups) - return; - - char name[256]; - mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); - - CMStringW wszTitle(szTitle); - if (hContact == 0) { - wszTitle.Insert(0, L": "); - wszTitle.Insert(0, m_tszUserName); - } - - POPUPDATACLASS ppd = {}; - ppd.szTitle.w = wszTitle; - ppd.szText.w = szMsg; - ppd.pszClassName = name; - ppd.hContact = hContact; - Popup_AddClass(&ppd); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MBinBuffer WhatsAppProto::unzip(const MBinBuffer &src) -{ - z_stream strm = {}; - inflateInit(&strm); - - strm.avail_in = (uInt)src.length(); - strm.next_in = (Bytef *)src.data(); - - MBinBuffer res; - Bytef buf[2048]; - - while (strm.avail_in > 0) { - strm.avail_out = sizeof(buf); - strm.next_out = buf; - - int ret = inflate(&strm, Z_NO_FLUSH); - switch (ret) { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; - __fallthrough; - - case Z_DATA_ERROR: - case Z_MEM_ERROR: - inflateEnd(&strm); - return res; - } - - res.append(buf, sizeof(buf) - strm.avail_out); - if (ret == Z_STREAM_END) - break; - } - - inflateEnd(&strm); - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName) -{ - int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE); - if (fileId != -1) { - write(fileId, buf.data(), (unsigned)buf.length()); - close(fileId); - } -} - -void string2file(const std::string &str, const wchar_t *pwszFileName) -{ - int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE); - if (fileId != -1) { - write(fileId, str.c_str(), (unsigned)str.size()); - close(fileId); - } -} - -CMStringA file2string(const wchar_t *pwszFileName) -{ - CMStringA res; - - int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD | _S_IWRITE); - if (fileId != -1) { - res.Truncate(filelength(fileId)); - read(fileId, res.GetBuffer(), res.GetLength()); - close(fileId); - } - return res; -} - -void WhatsAppProto::GetMessageContent( - CMStringA &txt, - const char *szType, - const char *szMimeType, - const char *szUrl, - const char *szDirectPath, - const ProtobufCBinaryData &pMediaKey, - const char *szCaption) -{ - if (szCaption) { - if (m_bUseBbcodes) - txt.Append("[b]"); - txt.Append(szCaption); - if (m_bUseBbcodes) - txt.Append("[/b]"); - txt.Append("\n"); - } - - CMStringA url = szUrl; - int idx = url.ReverseFind('/'); - if (idx != -1) - url.Delete(0, idx+1); - idx = url.ReverseFind('.'); - if (idx != -1) - url.Truncate(idx); - if (szMimeType) - url.Append(_T2A(ProtoGetAvatarExtension(ProtoGetAvatarFormatByMimeType(szMimeType)))); - - char *szMediaType = NEWSTR_ALLOCA(szType); - szMediaType[0] = toupper(szMediaType[0]); - - MBinBuffer buf = DownloadEncryptedFile(directPath2url(szDirectPath), pMediaKey, szMediaType); - if (!buf.isEmpty()) { - CMStringW pwszFileName(GetTmpFileName(szType, url)); - bin2file(buf, pwszFileName); - - pwszFileName.Replace(L"\\", L"/"); - txt.AppendFormat("file://%s", T2Utf(pwszFileName).get()); - } -} - -CMStringA WhatsAppProto::GetMessageText(const Wa__Message *pMessage) -{ - CMStringA szMessageText; - - if (pMessage) { - if (auto *pExt = pMessage->extendedtextmessage) { - if (pExt->title) { - if (m_bUseBbcodes) - szMessageText.Append("[b]"); - szMessageText.Append(pExt->title); - if (m_bUseBbcodes) - szMessageText.Append("[/b]"); - szMessageText.Append("\n"); - } - - if (pExt->contextinfo && pExt->contextinfo->quotedmessage) - szMessageText.AppendFormat("> %s\n\n", pExt->contextinfo->quotedmessage->conversation); - - if (pExt->text) - szMessageText.Append(pExt->text); - } - else if (auto *pAudio = pMessage->audiomessage) { - GetMessageContent(szMessageText, "audio", pAudio->url, pAudio->directpath, pAudio->mimetype, pAudio->mediakey); - } - else if (auto *pVideo = pMessage->videomessage) { - GetMessageContent(szMessageText, "video", pVideo->url, pVideo->directpath, pVideo->mimetype, pVideo->mediakey, pVideo->caption); - } - else if (auto *pImage = pMessage->imagemessage) { - GetMessageContent(szMessageText, "image", pImage->url, pImage->directpath, pImage->mimetype, pImage->mediakey, pImage->caption); - } - else if (mir_strlen(pMessage->conversation)) - szMessageText = pMessage->conversation; - } - - return szMessageText; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void proto::CleanBinary(ProtobufCBinaryData &field) -{ - if (field.data) { - free(field.data); - field.data = nullptr; - } - field.len = 0; -} - -ProtobufCBinaryData proto::SetBinary(const void *pData, size_t len) -{ - ProtobufCBinaryData res; - if (pData == nullptr) { - res.data = nullptr; - res.len = 0; - } - else { - res.data = (uint8_t *)malloc(res.len = len); - memcpy(res.data, pData, len); - } - return res; -} - -MBinBuffer proto::Serialize(const ProtobufCMessage *msg) -{ - MBinBuffer res(protobuf_c_message_get_packed_size(msg)); - protobuf_c_message_pack(msg, res.data()); - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CMStringA directPath2url(const char *pszDirectPath) -{ - return CMStringA("https://mmg.whatsapp.net") + pszDirectPath; -} - -WAMediaKeys::WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType) -{ - CMStringA pszHkdfString(FORMAT, "WhatsApp %s Keys", pszMediaType); - - HKDF(EVP_sha256(), (BYTE *)"", 0, pKey, (int)keyLen, (BYTE *)pszHkdfString.c_str(), pszHkdfString.GetLength(), (BYTE *)this, sizeof(*this)); -} - -MBinBuffer WhatsAppProto::DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszMediaType) -{ - NETLIBHTTPHEADER headers[1] = {{"Origin", "https://web.whatsapp.com"}}; - - NETLIBHTTPREQUEST req = {}; - req.cbSize = sizeof(req); - req.requestType = REQUEST_GET; - req.szUrl = (char*)url; - req.headersCount = _countof(headers); - req.headers = headers; - - MBinBuffer ret; - auto *pResp = Netlib_HttpTransaction(m_hNetlibUser, &req); - if (pResp) { - if (pResp->resultCode == 200) { - WAMediaKeys out(mediaKeys.data, mediaKeys.len, pszMediaType); - ret = aesDecrypt(EVP_aes_256_cbc(), out.cipherKey, out.iv, pResp->pData, pResp->dataLength); - } - } - - return ret; -} - -CMStringW WhatsAppProto::GetTmpFileName(const char *pszClass, const char *pszAddition) -{ - CMStringW ret(VARSW(L"%miranda_userdata%")); - ret.AppendFormat(L"/%S/%S_%S", m_szModuleName, pszClass, pszAddition); - return ret; -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+WAJid::WAJid(const char *pszUser, const char *pszServer, int iDevice, int iAgent) :
+ user(pszUser ? pszUser : ""),
+ server(pszServer ? pszServer : ""),
+ device(iDevice),
+ agent(iAgent)
+{}
+
+WAJid::WAJid(const char *pszJid, int _id)
+{
+ if (pszJid == nullptr)
+ pszJid = "";
+
+ auto *tmp = NEWSTR_ALLOCA(pszJid);
+ auto *p = strrchr(tmp, '@');
+ if (p) {
+ *p = 0;
+ server = p + 1;
+ }
+
+ if (p = strrchr(tmp, ':')) {
+ *p = 0;
+ device = atoi(p + 1);
+ }
+ else device = _id;
+
+ if (p = strrchr(tmp, '_')) {
+ *p = 0;
+ agent = atoi(p + 1);
+ }
+ else agent = 0;
+
+ user = tmp;
+}
+
+bool WAJid::isUser() const
+{ return server == "s.whatsapp.net";
+}
+
+bool WAJid::isGroup() const
+{ return server == "g.us";
+}
+
+bool WAJid::isBroadcast() const
+{
+ return server == "broadcast";
+}
+
+bool WAJid::isStatusBroadcast() const
+{
+ return isBroadcast() && user == "status";
+}
+
+CMStringA WAJid::toString() const
+{
+ CMStringA ret(user);
+ if (agent > 0)
+ ret.AppendFormat("_%d", agent);
+ if (device > 0)
+ ret.AppendFormat(":%d", device);
+ ret.AppendFormat("@%s", server.c_str());
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static uint8_t sttLtHashInfo[] = "WhatsApp Patch Integrity";
+
+void LT_HASH::add(const void *pData, size_t len)
+{
+ uint16_t tmp[_countof(hash)];
+ HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE*)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp));
+
+ for (int i = 0; i < _countof(hash); i++)
+ hash[i] += tmp[i];
+}
+
+void LT_HASH::sub(const void *pData, size_t len)
+{
+ uint16_t tmp[_countof(hash)];
+ HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE *)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp));
+
+ for (int i = 0; i < _countof(hash); i++)
+ hash[i] -= tmp[i];
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+WAUser* WhatsAppProto::FindUser(const char *szId)
+{
+ if (szId == nullptr)
+ return nullptr;
+
+ mir_cslock lck(m_csUsers);
+ auto *tmp = (WAUser *)_alloca(sizeof(WAUser));
+ tmp->szId = (char*)szId;
+ return m_arUsers.find(tmp);
+}
+
+WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary)
+{
+ auto *pUser = FindUser(szId);
+ if (pUser != nullptr)
+ return pUser;
+
+ MCONTACT hContact = db_add_contact();
+ Proto_AddToContact(hContact, m_szModuleName);
+
+ pUser = new WAUser(hContact, mir_strdup(szId));
+ pUser->bIsGroupChat = WAJid(szId).isGroup();
+
+ if (pUser->bIsGroupChat) {
+ setByte(hContact, "ChatRoom", 1);
+ setString(hContact, "ChatRoomID", szId);
+ }
+ else {
+ setString(hContact, DBKEY_ID, szId);
+ if (m_wszDefaultGroup)
+ Clist_SetGroup(hContact, m_wszDefaultGroup);
+ }
+
+ if (bTemporary)
+ Contact::RemoveFromList(hContact);
+
+ mir_cslock lck(m_csUsers);
+ m_arUsers.insert(pUser);
+ return pUser;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+WA_PKT_HANDLER WhatsAppProto::FindPersistentHandler(const WANode &pNode)
+{
+ auto *pChild = pNode.getFirstChild();
+ CMStringA szChild = (pChild) ? pChild->title : "";
+ CMStringA szTitle = pNode.title;
+ CMStringA szType = pNode.getAttr("type");
+ CMStringA szXmlns = pNode.getAttr("xmlns");
+
+ for (auto &it : m_arPersistent) {
+ if (it->pszTitle && szTitle != it->pszTitle)
+ continue;
+ if (it->pszType && szType != it->pszType)
+ continue;
+ if (it->pszXmlns && szXmlns != it->pszXmlns)
+ continue;
+ if (it->pszChild && szChild != it->pszChild)
+ continue;
+ return it->pHandler;
+ }
+
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMStringA WhatsAppProto::GenerateMessageId()
+{
+ return CMStringA(FORMAT, "%d.%d-%d", m_wMsgPrefix[0], m_wMsgPrefix[1], m_iPacketId++);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// sends a piece of JSON to a server via a websocket, masked
+
+int WhatsAppProto::WSSend(const ProtobufCMessage &msg)
+{
+ if (m_hServerConn == nullptr)
+ return -1;
+
+ MBinBuffer buf(proto::Serialize(&msg));
+ Netlib_Dump(m_hServerConn, buf.data(), buf.length(), true, 0);
+
+ MBinBuffer payload = m_noise->encodeFrame(buf.data(), buf.length());
+ WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int WhatsAppProto::WSSendNode(WANode &node)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ CMStringA szText;
+ node.print(szText);
+ debugLogA("Sending binary node:\n%s", szText.c_str());
+
+ WAWriter writer;
+ writer.writeNode(&node);
+
+ MBinBuffer encData = m_noise->encrypt(writer.body.data(), writer.body.length());
+ MBinBuffer payload = m_noise->encodeFrame(encData.data(), encData.length());
+ WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
+ return 1;
+}
+
+int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ CMStringA id(GenerateMessageId());
+ node.addAttr("id", id);
+ {
+ mir_cslock lck(m_csPacketQueue);
+ m_arPacketQueue.insert(new WARequestSimple(id, pHandler));
+ }
+
+ return WSSendNode(node);
+}
+
+int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER_FULL pHandler, void *pUserInfo)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ CMStringA id(GenerateMessageId());
+ node.addAttr("id", id);
+ {
+ mir_cslock lck(m_csPacketQueue);
+ m_arPacketQueue.insert(new WARequestParam(id, pHandler, pUserInfo));
+ }
+
+ return WSSendNode(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+std::string decodeBinStr(const std::string &buf)
+{
+ size_t cbLen;
+ void *pData = mir_base64_decode(buf.c_str(), &cbLen);
+ if (pData == nullptr)
+ return "";
+
+ std::string res((char *)pData, cbLen);
+ mir_free(pData);
+ return res;
+}
+
+MBinBuffer decodeBufStr(const std::string &buf)
+{
+ MBinBuffer res;
+ size_t cbLen;
+ void *pData = mir_base64_decode(buf.c_str(), &cbLen);
+ if (pData == nullptr)
+ return res;
+
+ res.assign(pData, cbLen);
+ mir_free(pData);
+ return res;
+}
+
+uint32_t decodeBigEndian(const uint8_t *buf, size_t len)
+{
+ uint32_t ret = 0;
+ for (int i = 0; i < len; i++) {
+ ret <<= 8;
+ ret += buf[i];
+ }
+
+ return ret;
+}
+
+std::string encodeBigEndian(uint32_t num, size_t len)
+{
+ std::string res;
+ for (int i = 0; i < len; i++) {
+ char c = num & 0xFF;
+ res = c + res;
+ num >>= 8;
+ }
+ return res;
+}
+
+void generateIV(uint8_t *iv, uint32_t &pVar)
+{
+ auto counter = encodeBigEndian(pVar);
+ memset(iv, 0, 8);
+ memcpy(iv + 8, counter.c_str(), sizeof(uint32_t));
+
+ pVar++;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Padding
+
+void padBuffer16(MBinBuffer &buf)
+{
+ uint8_t c = 16 - buf.length() % 16;
+
+ for (uint8_t i = 0; i < c; i++)
+ buf.append(&c, 1);
+}
+
+MBinBuffer unpadBuffer16(const MBinBuffer &buf)
+{
+ size_t len = buf.length();
+ auto p = buf.data() + len - 1;
+ if (*p <= 0x10) {
+ MBinBuffer res;
+ res.assign(buf.data(), len - *p);
+ return res;
+ }
+
+ return buf;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popups
+
+void WhatsAppProto::InitPopups(void)
+{
+ g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups);
+
+ char name[256];
+ mir_snprintf(name, "%s_%s", m_szModuleName, "Error");
+
+ wchar_t desc[256];
+ mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors"));
+
+ POPUPCLASS ppc = {};
+ ppc.flags = PCF_UNICODE;
+ ppc.pszName = name;
+ ppc.pszDescription.w = desc;
+ ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon);
+ ppc.colorBack = RGB(191, 0, 0); //Red
+ ppc.colorText = RGB(255, 245, 225); //Yellow
+ ppc.iSeconds = 60;
+ m_hPopupClass = Popup_RegisterClass(&ppc);
+
+ IcoLib_ReleaseIcon(ppc.hIcon);
+}
+
+void WhatsAppProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle)
+{
+ if (!m_bUsePopups)
+ return;
+
+ char name[256];
+ mir_snprintf(name, "%s_%s", m_szModuleName, "Error");
+
+ CMStringW wszTitle(szTitle);
+ if (hContact == 0) {
+ wszTitle.Insert(0, L": ");
+ wszTitle.Insert(0, m_tszUserName);
+ }
+
+ POPUPDATACLASS ppd = {};
+ ppd.szTitle.w = wszTitle;
+ ppd.szText.w = szMsg;
+ ppd.pszClassName = name;
+ ppd.hContact = hContact;
+ Popup_AddClass(&ppd);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MBinBuffer WhatsAppProto::unzip(const MBinBuffer &src)
+{
+ z_stream strm = {};
+ inflateInit(&strm);
+
+ strm.avail_in = (uInt)src.length();
+ strm.next_in = (Bytef *)src.data();
+
+ MBinBuffer res;
+ Bytef buf[2048];
+
+ while (strm.avail_in > 0) {
+ strm.avail_out = sizeof(buf);
+ strm.next_out = buf;
+
+ int ret = inflate(&strm, Z_NO_FLUSH);
+ switch (ret) {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ __fallthrough;
+
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ inflateEnd(&strm);
+ return res;
+ }
+
+ res.append(buf, sizeof(buf) - strm.avail_out);
+ if (ret == Z_STREAM_END)
+ break;
+ }
+
+ inflateEnd(&strm);
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName)
+{
+ int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
+ if (fileId != -1) {
+ write(fileId, buf.data(), (unsigned)buf.length());
+ close(fileId);
+ }
+}
+
+void string2file(const std::string &str, const wchar_t *pwszFileName)
+{
+ int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
+ if (fileId != -1) {
+ write(fileId, str.c_str(), (unsigned)str.size());
+ close(fileId);
+ }
+}
+
+CMStringA file2string(const wchar_t *pwszFileName)
+{
+ CMStringA res;
+
+ int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD | _S_IWRITE);
+ if (fileId != -1) {
+ res.Truncate(filelength(fileId));
+ read(fileId, res.GetBuffer(), res.GetLength());
+ close(fileId);
+ }
+ return res;
+}
+
+void WhatsAppProto::GetMessageContent(
+ CMStringA &txt,
+ const char *szType,
+ const char *szMimeType,
+ const char *szUrl,
+ const char *szDirectPath,
+ const ProtobufCBinaryData &pMediaKey,
+ const char *szCaption)
+{
+ if (szCaption) {
+ if (m_bUseBbcodes)
+ txt.Append("[b]");
+ txt.Append(szCaption);
+ if (m_bUseBbcodes)
+ txt.Append("[/b]");
+ txt.Append("\n");
+ }
+
+ CMStringA url = szUrl;
+ int idx = url.ReverseFind('/');
+ if (idx != -1)
+ url.Delete(0, idx+1);
+ idx = url.ReverseFind('.');
+ if (idx != -1)
+ url.Truncate(idx);
+ if (szMimeType)
+ url.Append(_T2A(ProtoGetAvatarExtension(ProtoGetAvatarFormatByMimeType(szMimeType))));
+
+ char *szMediaType = NEWSTR_ALLOCA(szType);
+ szMediaType[0] = toupper(szMediaType[0]);
+
+ MBinBuffer buf = DownloadEncryptedFile(directPath2url(szDirectPath), pMediaKey, szMediaType);
+ if (!buf.isEmpty()) {
+ CMStringW pwszFileName(GetTmpFileName(szType, url));
+ bin2file(buf, pwszFileName);
+
+ pwszFileName.Replace(L"\\", L"/");
+ txt.AppendFormat("file://%s", T2Utf(pwszFileName).get());
+ }
+}
+
+CMStringA WhatsAppProto::GetMessageText(const Wa__Message *pMessage)
+{
+ CMStringA szMessageText;
+
+ if (pMessage) {
+ if (auto *pExt = pMessage->extendedtextmessage) {
+ if (pExt->title) {
+ if (m_bUseBbcodes)
+ szMessageText.Append("[b]");
+ szMessageText.Append(pExt->title);
+ if (m_bUseBbcodes)
+ szMessageText.Append("[/b]");
+ szMessageText.Append("\n");
+ }
+
+ if (pExt->contextinfo && pExt->contextinfo->quotedmessage)
+ szMessageText.AppendFormat("> %s\n\n", pExt->contextinfo->quotedmessage->conversation);
+
+ if (pExt->text)
+ szMessageText.Append(pExt->text);
+ }
+ else if (auto *pAudio = pMessage->audiomessage) {
+ GetMessageContent(szMessageText, "audio", pAudio->url, pAudio->directpath, pAudio->mimetype, pAudio->mediakey);
+ }
+ else if (auto *pVideo = pMessage->videomessage) {
+ GetMessageContent(szMessageText, "video", pVideo->url, pVideo->directpath, pVideo->mimetype, pVideo->mediakey, pVideo->caption);
+ }
+ else if (auto *pImage = pMessage->imagemessage) {
+ GetMessageContent(szMessageText, "image", pImage->url, pImage->directpath, pImage->mimetype, pImage->mediakey, pImage->caption);
+ }
+ else if (mir_strlen(pMessage->conversation))
+ szMessageText = pMessage->conversation;
+ }
+
+ return szMessageText;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void proto::CleanBinary(ProtobufCBinaryData &field)
+{
+ if (field.data) {
+ free(field.data);
+ field.data = nullptr;
+ }
+ field.len = 0;
+}
+
+ProtobufCBinaryData proto::SetBinary(const void *pData, size_t len)
+{
+ ProtobufCBinaryData res;
+ if (pData == nullptr) {
+ res.data = nullptr;
+ res.len = 0;
+ }
+ else {
+ res.data = (uint8_t *)malloc(res.len = len);
+ memcpy(res.data, pData, len);
+ }
+ return res;
+}
+
+MBinBuffer proto::Serialize(const ProtobufCMessage *msg)
+{
+ MBinBuffer res(protobuf_c_message_get_packed_size(msg));
+ protobuf_c_message_pack(msg, res.data());
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMStringA directPath2url(const char *pszDirectPath)
+{
+ return CMStringA("https://mmg.whatsapp.net") + pszDirectPath;
+}
+
+WAMediaKeys::WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType)
+{
+ CMStringA pszHkdfString(FORMAT, "WhatsApp %s Keys", pszMediaType);
+
+ HKDF(EVP_sha256(), (BYTE *)"", 0, pKey, (int)keyLen, (BYTE *)pszHkdfString.c_str(), pszHkdfString.GetLength(), (BYTE *)this, sizeof(*this));
+}
+
+MBinBuffer WhatsAppProto::DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszMediaType)
+{
+ NETLIBHTTPHEADER headers[1] = {{"Origin", "https://web.whatsapp.com"}};
+
+ NETLIBHTTPREQUEST req = {};
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_GET;
+ req.szUrl = (char*)url;
+ req.headersCount = _countof(headers);
+ req.headers = headers;
+
+ MBinBuffer ret;
+ auto *pResp = Netlib_HttpTransaction(m_hNetlibUser, &req);
+ if (pResp) {
+ if (pResp->resultCode == 200) {
+ WAMediaKeys out(mediaKeys.data, mediaKeys.len, pszMediaType);
+ ret = aesDecrypt(EVP_aes_256_cbc(), out.cipherKey, out.iv, pResp->pData, pResp->dataLength);
+ }
+ }
+
+ return ret;
+}
+
+CMStringW WhatsAppProto::GetTmpFileName(const char *pszClass, const char *pszAddition)
+{
+ CMStringW ret(VARSW(L"%miranda_userdata%"));
+ ret.AppendFormat(L"/%S/%S_%S", m_szModuleName, pszClass, pszAddition);
+ return ret;
+}
diff --git a/protocols/WhatsApp/src/utils.h b/protocols/WhatsApp/src/utils.h index 02882f1c17..af02695818 100644 --- a/protocols/WhatsApp/src/utils.h +++ b/protocols/WhatsApp/src/utils.h @@ -1,267 +1,267 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#define DICT_VERSION 2 - -#define LIST_EMPTY 0 -#define STREAM_END 2 -#define DICTIONARY_0 236 -#define DICTIONARY_1 237 -#define DICTIONARY_2 238 -#define DICTIONARY_3 239 -#define AD_JID 247 -#define LIST_8 248 -#define LIST_16 249 -#define JID_PAIR 250 -#define HEX_8 251 -#define BINARY_8 252 -#define BINARY_20 253 -#define BINARY_32 254 -#define NIBBLE_8 255 - -class WANode // kinda XML -{ - friend class WAReader; - friend class WAWriter; - - WANode *pParent = nullptr; - OBJLIST<struct Attr> attrs; - OBJLIST<WANode> children; - -public: - WANode(); - WANode(const char *pszTitle); - ~WANode(); - - void addAttr(const char *pszName, const char *pszValue); - void addAttr(const char *pszName, int iValue); - int getAttrInt(const char *pszName) const; - const char *getAttr(const char *pszName) const; - - CMStringA getBody() const; - - WANode *addChild(const char *pszName); - WANode *getChild(const char *pszName) const; - WANode *getFirstChild(void) const; - const OBJLIST<WANode> &getChildren(void) const - { return children; - } - - void print(CMStringA &dest, int level = 0) const; - - CMStringA title; - MBinBuffer content; -}; - -__forceinline WANode &operator<<(WANode &node, const CHAR_PARAM ¶m) -{ - node.addAttr(param.szName, param.szValue); - return node; -} - -__forceinline WANode &operator<<(WANode &node, const INT_PARAM ¶m) -{ - node.addAttr(param.szName, param.iValue); - return node; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -namespace IQ -{ - enum Type { GET, SET, RESULT }; -}; - -struct WANodeIq : public WANode -{ - WANodeIq(IQ::Type type, const char *pszXmlns = nullptr, const char *pszTo = nullptr); -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct XCHILD -{ - const char *name, *value; - - __forceinline XCHILD(const char *_name) : - name(_name) - {} -}; - -__forceinline WANode& operator<<(WANode &node, const XCHILD &child) -{ - node.addChild(child.name); - return node; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// WAReader - -class WAReader -{ - const BYTE *m_buf, *m_limit; - - uint32_t readIntN(int i); - CMStringA readStringFromChars(int size); - - bool readAttributes(WANode *node, int count); - uint32_t readInt20(); - bool readList(WANode *pParent, int tag); - int readListSize(int tag); - CMStringA readPacked(int tag); - CMStringA readString(int tag); - -public: - WAReader(const void *buf, size_t cbLen) : - m_buf((BYTE*)buf), - m_limit((BYTE*)buf + cbLen) - {} - - WANode* readNode(); - - __forceinline uint32_t readInt8() { return readIntN(1); } - __forceinline uint32_t readInt16() { return readIntN(2); } - __forceinline uint32_t readInt32() { return readIntN(4); } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// WAWriter - -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, int tag); - void writeString(const char *str); - bool writeToken(const char *str); - -public: - void writeNode(const WANode *pNode); - - MBinBuffer body; -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// WAJid - -struct WAJid -{ - int device, agent; - CMStringA user, server; - - WAJid(const char *pszJid, int device = 0); - WAJid(const char *pszUser, const char *pszServer, int device = 0, int agent = 0); - - CMStringA toString() const; - - bool isUser() const; - bool isGroup() const; - bool isBroadcast() const; - bool isStatusBroadcast() const; -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// WASendTask - -struct WASendTask -{ - WASendTask(const char *jid) : - payLoad("message"), - arDest(1) - { - uint8_t msgId[8]; - Utils_GetRandom(&msgId, sizeof(msgId)); - bin2hex(msgId, sizeof(msgId), szMsgId); - strupr(szMsgId); - - payLoad << CHAR_PARAM("id", szMsgId) << CHAR_PARAM("type", "text") << CHAR_PARAM("to", jid); - } - - char szMsgId[40]; - WANode payLoad; - OBJLIST<WAJid> arDest; - MBinBuffer content; -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// LT_HASH - -struct LT_HASH -{ - LT_HASH() - { - init(); - }; - - uint16_t hash[64]; - - void add(const void *pData, size_t len); - void sub(const void *pData, size_t len); - - void init() - { - SecureZeroMemory(hash, sizeof(hash)); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// functions - -void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName); - -void string2file(const std::string &str, const wchar_t *pwszFileName); -CMStringA file2string(const wchar_t *pwszFileName); - -CMStringA directPath2url(const char *pszDirectPath); - -std::string decodeBinStr(const std::string &buf); -MBinBuffer decodeBufStr(const std::string &buf); - -void padBuffer16(MBinBuffer &buf); -MBinBuffer unpadBuffer16(const MBinBuffer &buf); - -CMStringA protobuf_c_text_to_string(const ProtobufCMessage *m); - -MBinBuffer aesDecrypt( - const EVP_CIPHER *cipher, - const uint8_t *key, - const uint8_t *iv, - const void *data, size_t dataLen, - const void *additionalData = 0, size_t additionalLen = 0); - -MBinBuffer aesEncrypt( - const EVP_CIPHER *cipher, - const uint8_t *key, - const uint8_t *iv, - const void *data, size_t dataLen, - const void *additionalData = 0, size_t additionalLen = 0); - -uint32_t decodeBigEndian(const uint8_t *pData, size_t len); - -__forceinline uint32_t decodeBigEndian(const ProtobufCBinaryData &buf) { - return decodeBigEndian(buf.data, buf.len); -} -__forceinline uint32_t decodeBigEndian(const MBinBuffer &buf) { - return decodeBigEndian(buf.data(), buf.length()); -} - -std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t)); - -void generateIV(uint8_t *iv, uint32_t &pVar); - -unsigned char* HKDF(const EVP_MD *evp_md, - const unsigned char *salt, size_t salt_len, - const unsigned char *key, size_t key_len, - const unsigned char *info, size_t info_len, - unsigned char *okm, size_t okm_len); +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#define DICT_VERSION 2
+
+#define LIST_EMPTY 0
+#define STREAM_END 2
+#define DICTIONARY_0 236
+#define DICTIONARY_1 237
+#define DICTIONARY_2 238
+#define DICTIONARY_3 239
+#define AD_JID 247
+#define LIST_8 248
+#define LIST_16 249
+#define JID_PAIR 250
+#define HEX_8 251
+#define BINARY_8 252
+#define BINARY_20 253
+#define BINARY_32 254
+#define NIBBLE_8 255
+
+class WANode // kinda XML
+{
+ friend class WAReader;
+ friend class WAWriter;
+
+ WANode *pParent = nullptr;
+ OBJLIST<struct Attr> attrs;
+ OBJLIST<WANode> children;
+
+public:
+ WANode();
+ WANode(const char *pszTitle);
+ ~WANode();
+
+ void addAttr(const char *pszName, const char *pszValue);
+ void addAttr(const char *pszName, int iValue);
+ int getAttrInt(const char *pszName) const;
+ const char *getAttr(const char *pszName) const;
+
+ CMStringA getBody() const;
+
+ WANode *addChild(const char *pszName);
+ WANode *getChild(const char *pszName) const;
+ WANode *getFirstChild(void) const;
+ const OBJLIST<WANode> &getChildren(void) const
+ { return children;
+ }
+
+ void print(CMStringA &dest, int level = 0) const;
+
+ CMStringA title;
+ MBinBuffer content;
+};
+
+__forceinline WANode &operator<<(WANode &node, const CHAR_PARAM ¶m)
+{
+ node.addAttr(param.szName, param.szValue);
+ return node;
+}
+
+__forceinline WANode &operator<<(WANode &node, const INT_PARAM ¶m)
+{
+ node.addAttr(param.szName, param.iValue);
+ return node;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+namespace IQ
+{
+ enum Type { GET, SET, RESULT };
+};
+
+struct WANodeIq : public WANode
+{
+ WANodeIq(IQ::Type type, const char *pszXmlns = nullptr, const char *pszTo = nullptr);
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct XCHILD
+{
+ const char *name, *value;
+
+ __forceinline XCHILD(const char *_name) :
+ name(_name)
+ {}
+};
+
+__forceinline WANode& operator<<(WANode &node, const XCHILD &child)
+{
+ node.addChild(child.name);
+ return node;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAReader
+
+class WAReader
+{
+ const BYTE *m_buf, *m_limit;
+
+ uint32_t readIntN(int i);
+ CMStringA readStringFromChars(int size);
+
+ bool readAttributes(WANode *node, int count);
+ uint32_t readInt20();
+ bool readList(WANode *pParent, int tag);
+ int readListSize(int tag);
+ CMStringA readPacked(int tag);
+ CMStringA readString(int tag);
+
+public:
+ WAReader(const void *buf, size_t cbLen) :
+ m_buf((BYTE*)buf),
+ m_limit((BYTE*)buf + cbLen)
+ {}
+
+ WANode* readNode();
+
+ __forceinline uint32_t readInt8() { return readIntN(1); }
+ __forceinline uint32_t readInt16() { return readIntN(2); }
+ __forceinline uint32_t readInt32() { return readIntN(4); }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAWriter
+
+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, int tag);
+ void writeString(const char *str);
+ bool writeToken(const char *str);
+
+public:
+ void writeNode(const WANode *pNode);
+
+ MBinBuffer body;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAJid
+
+struct WAJid
+{
+ int device, agent;
+ CMStringA user, server;
+
+ WAJid(const char *pszJid, int device = 0);
+ WAJid(const char *pszUser, const char *pszServer, int device = 0, int agent = 0);
+
+ CMStringA toString() const;
+
+ bool isUser() const;
+ bool isGroup() const;
+ bool isBroadcast() const;
+ bool isStatusBroadcast() const;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WASendTask
+
+struct WASendTask
+{
+ WASendTask(const char *jid) :
+ payLoad("message"),
+ arDest(1)
+ {
+ uint8_t msgId[8];
+ Utils_GetRandom(&msgId, sizeof(msgId));
+ bin2hex(msgId, sizeof(msgId), szMsgId);
+ strupr(szMsgId);
+
+ payLoad << CHAR_PARAM("id", szMsgId) << CHAR_PARAM("type", "text") << CHAR_PARAM("to", jid);
+ }
+
+ char szMsgId[40];
+ WANode payLoad;
+ OBJLIST<WAJid> arDest;
+ MBinBuffer content;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// LT_HASH
+
+struct LT_HASH
+{
+ LT_HASH()
+ {
+ init();
+ };
+
+ uint16_t hash[64];
+
+ void add(const void *pData, size_t len);
+ void sub(const void *pData, size_t len);
+
+ void init()
+ {
+ SecureZeroMemory(hash, sizeof(hash));
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// functions
+
+void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName);
+
+void string2file(const std::string &str, const wchar_t *pwszFileName);
+CMStringA file2string(const wchar_t *pwszFileName);
+
+CMStringA directPath2url(const char *pszDirectPath);
+
+std::string decodeBinStr(const std::string &buf);
+MBinBuffer decodeBufStr(const std::string &buf);
+
+void padBuffer16(MBinBuffer &buf);
+MBinBuffer unpadBuffer16(const MBinBuffer &buf);
+
+CMStringA protobuf_c_text_to_string(const ProtobufCMessage *m);
+
+MBinBuffer aesDecrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData = 0, size_t additionalLen = 0);
+
+MBinBuffer aesEncrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData = 0, size_t additionalLen = 0);
+
+uint32_t decodeBigEndian(const uint8_t *pData, size_t len);
+
+__forceinline uint32_t decodeBigEndian(const ProtobufCBinaryData &buf) {
+ return decodeBigEndian(buf.data, buf.len);
+}
+__forceinline uint32_t decodeBigEndian(const MBinBuffer &buf) {
+ return decodeBigEndian(buf.data(), buf.length());
+}
+
+std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t));
+
+void generateIV(uint8_t *iv, uint32_t &pVar);
+
+unsigned char* HKDF(const EVP_MD *evp_md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *key, size_t key_len,
+ const unsigned char *info, size_t info_len,
+ unsigned char *okm, size_t okm_len);
diff --git a/protocols/WhatsApp/src/version.h b/protocols/WhatsApp/src/version.h index 3a160f44ec..caa1a7ea52 100644 --- a/protocols/WhatsApp/src/version.h +++ b/protocols/WhatsApp/src/version.h @@ -10,4 +10,4 @@ #define __DESCRIPTION "WhatsApp protocol support for Miranda NG."
#define __AUTHOR "George Hazan"
#define __AUTHORWEB "https://miranda-ng.org/p/WhatsApp"
-#define __COPYRIGHT "© 2019-22 Miranda NG team"
+#define __COPYRIGHT "© 2019-23 Miranda NG team"
diff --git a/protocols/WhatsApp/src/wanode.cpp b/protocols/WhatsApp/src/wanode.cpp index 0c3fe15d03..9c40912f28 100644 --- a/protocols/WhatsApp/src/wanode.cpp +++ b/protocols/WhatsApp/src/wanode.cpp @@ -1,608 +1,608 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-22 George Hazan - -*/ - -#include "stdafx.h" -#include "dicts.h" - -struct Attr -{ - Attr(const char *pszName, const char *pszValue) : - name(pszName), - value(pszValue) - {} - - Attr(const char *pszName, int iValue) : - name(pszName), - value(FORMAT, "%d", iValue) - {} - - CMStringA name, value; -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// WANodeIq members - -WANodeIq::WANodeIq(IQ::Type type, const char *pszXmlns, const char *pszTo) : - WANode("iq") -{ - switch (type) { - case IQ::GET: addAttr("type", "get"); break; - case IQ::SET: addAttr("type", "set"); break; - case IQ::RESULT: addAttr("type", "result"); break; - } - - if (pszXmlns) - addAttr("xmlns", pszXmlns); - - addAttr("to", pszTo ? pszTo : S_WHATSAPP_NET); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// WANode members - -WANode::WANode() : - attrs(1), - children(1) -{} - -WANode::WANode(const char *pszTitle) : - attrs(1), - children(1), - title(pszTitle) -{} - -WANode::~WANode() -{ -} - -const char *WANode::getAttr(const char *pszName) const -{ - if (this != nullptr) - for (auto &p : attrs) - if (p->name == pszName) - return p->value.c_str(); - - return nullptr; -} - -int WANode::getAttrInt(const char *pszName) const -{ - if (this != nullptr) - for (auto &p : attrs) - if (p->name == pszName) - return atoi(p->value.c_str()); - - return 0; -} - -void WANode::addAttr(const char *pszName, const char *pszValue) -{ - attrs.insert(new Attr(pszName, pszValue)); -} - -void WANode::addAttr(const char *pszName, int iValue) -{ - attrs.insert(new Attr(pszName, iValue)); -} - -CMStringA WANode::getBody() const -{ - return CMStringA((char *)content.data(), (int)content.length()); -} - -WANode *WANode::addChild(const char *pszName) -{ - auto *pNew = new WANode(pszName); - pNew->pParent = this; - children.insert(pNew); - return pNew; -} - -WANode* WANode::getChild(const char *pszName) const -{ - if (this == nullptr) - return nullptr; - - for (auto &it : children) - if (it->title == pszName) - return it; - - return nullptr; -} - -WANode* WANode::getFirstChild(void) const -{ - return (children.getCount()) ? &children[0] : nullptr; -} - -void WANode::print(CMStringA &dest, int level) const -{ - for (int i = 0; i < level; i++) - dest.Append(" "); - - dest.AppendFormat("<%s ", title.c_str()); - for (auto &p : attrs) - dest.AppendFormat("%s=\"%s\" ", p->name.c_str(), p->value.c_str()); - dest.Truncate(dest.GetLength() - 1); - - if (content.isEmpty() && !children.getCount()) { - dest.Append("/>\n"); - return; - } - - dest.Append(">"); - if (!content.isEmpty()) { - ptrA tmp((char *)mir_alloc(content.length() * 2 + 1)); - bin2hex(content.data(), content.length(), tmp); - dest.AppendFormat("%s", tmp.get()); - } - - if (children.getCount()) { - dest.Append("\n"); - - for (auto &p : children) - p->print(dest, level + 1); - - for (int i = 0; i < level; i++) - dest.Append(" "); - } - - dest.AppendFormat("</%s>\n", title.c_str()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// WAReader class members - -bool WAReader::readAttributes(WANode *pNode, int count) -{ - if (count == 0) - return true; - - for (int i = 0; i < count; i++) { - CMStringA name = readString(readInt8()); - if (name.IsEmpty()) - return false; - - CMStringA value = readString(readInt8()); - if (value.IsEmpty()) - return false; - - pNode->addAttr(name, value); - } - return true; -} - -uint32_t WAReader::readInt20() -{ - if (m_limit - m_buf < 3) - return 0; - - int ret = (int(m_buf[0] & 0x0F) << 16) + (int(m_buf[1]) << 8) + int(m_buf[2]); - m_buf += 3; - return ret; -} - -uint32_t WAReader::readIntN(int n) -{ - if (m_limit - m_buf < n) - return 0; - - uint32_t res = 0; - for (int i = 0; i < n; i++, m_buf++) - res = (res <<= 8) + *m_buf; - return res; -} - -bool WAReader::readList(WANode *pParent, int tag) -{ - int size = readListSize(tag); - if (size == -1) - return false; - - for (int i = 0; i < size; i++) { - WANode *pNew = readNode(); - if (pNew == nullptr) - return false; - pParent->children.insert(pNew); - } - - return true; -} - -int WAReader::readListSize(int tag) -{ - switch (tag) { - case LIST_EMPTY: - return 0; - case LIST_8: - return readInt8(); - case LIST_16: - return readInt16(); - } - return -1; -} - -WANode *WAReader::readNode() -{ - int listSize = readListSize(readInt8()); - if (listSize == -1) - return nullptr; - - int descrTag = readInt8(); - if (descrTag == STREAM_END) - return nullptr; - - CMStringA name = readString(descrTag); - if (name.IsEmpty()) - return nullptr; - - std::unique_ptr<WANode> ret(new WANode()); - ret->title = name.c_str(); - - if (!readAttributes(ret.get(), (listSize - 1) >> 1)) - return nullptr; - - if ((listSize % 2) == 1) - return ret.release(); - - int size, tag = readInt8(); - switch (tag) { - case LIST_EMPTY: case LIST_8: case LIST_16: - readList(ret.get(), tag); - break; - - case BINARY_8: - size = readInt8(); - -LBL_Binary: - if (m_limit - m_buf < size) - return false; - - ret->content.assign((void *)m_buf, size); - m_buf += size; - break; - - case BINARY_20: - size = readInt20(); - goto LBL_Binary; - - case BINARY_32: - size = readInt32(); - goto LBL_Binary; - - default: - CMStringA str = readString(tag); - ret->content.assign(str.GetBuffer(), str.GetLength() + 1); - } - - return ret.release(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static int unpackHex(int val) -{ - if (val < 0 || val > 15) - return -1; - - return (val < 10) ? val + '0' : val - 10 + 'A'; -} - -static int unpackNibble(int val) -{ - if (val < 0 || val > 15) - return -1; - - switch (val) { - case 10: return '-'; - case 11: return '.'; - case 15: return 0; - default: return '0' + val; - } -} - -CMStringA WAReader::readPacked(int tag) -{ - int startByte = readInt8(); - bool bTrim = false; - if (startByte & 0x80) { - startByte &= ~0x80; - bTrim = true; - } - - CMStringA ret; - for (int i = 0; i < startByte; i++) { - BYTE b = readInt8(); - int lower = (tag == NIBBLE_8) ? unpackNibble(b >> 4) : unpackHex(b >> 4); - if (lower == -1) - return ""; - - int higher = (tag == NIBBLE_8) ? unpackNibble(b & 0x0F) : unpackHex(b & 0x0F); - if (higher == -1) - return ""; - - ret.AppendChar(lower); - ret.AppendChar(higher); - } - - if (bTrim && !ret.IsEmpty()) - ret.Truncate(ret.GetLength() - 1); - return ret; -} - -CMStringA WAReader::readString(int tag) -{ - if (tag >= 1 && tag < _countof(SingleByteTokens)) - return SingleByteTokens[tag]; - - int idx; - switch (tag) { - case DICTIONARY_0: - idx = readInt8(); - return (idx < _countof(dict0)) ? dict0[idx] : ""; - - case DICTIONARY_1: - idx = readInt8(); - return (idx < _countof(dict1)) ? dict1[idx] : ""; - - case DICTIONARY_2: - idx = readInt8(); - return (idx < _countof(dict2)) ? dict2[idx] : ""; - - case DICTIONARY_3: - idx = readInt8(); - return (idx < _countof(dict3)) ? dict3[idx] : ""; - - case LIST_EMPTY: - return ""; - - case BINARY_8: - return readStringFromChars(readInt8()); - - case BINARY_20: - return readStringFromChars(readInt20()); - - case BINARY_32: - return readStringFromChars(readInt32()); - - case NIBBLE_8: - case HEX_8: - return readPacked(tag); - - case AD_JID: - { - int agent = readInt8(); - int device = readInt8(); - WAJid jid(readString(readInt8()), "s.whatsapp.net", device, agent); - return jid.toString(); - } - - case JID_PAIR: - CMStringA s1 = readString(readInt8()); - CMStringA s2 = readString(readInt8()); - if (s1.IsEmpty() && s2.IsEmpty()) - break; - - return CMStringA(FORMAT, "%s@%s", s1.c_str(), s2.c_str()); - } - - // error - return ""; -} - -CMStringA WAReader::readStringFromChars(int size) -{ - if (m_limit - m_buf < size) - return ""; - - CMStringA ret((char *)m_buf, 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) -{ - // we never send zipped content - if (pNode->pParent == nullptr) - writeByte(0); - - int numAttrs = (int)pNode->attrs.getCount(); - int hasContent = pNode->content.length() != 0 || pNode->children.getCount() != 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 - else if (pNode->children.getCount()) { - writeListSize(pNode->children.getCount()); - for (auto &it : pNode->children) - writeNode(it); - } -} - -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' && c <= '9') - return c - '0'; - - if (c >= 'A' && c <= 'F') - return 10 + c - 'A'; - - if (c >= 'a' && c <= 'f') - return 10 + c - 'a'; - - if (c == 0) - return 15; - - 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; -} - -static bool isNibble(const CMStringA &str) -{ - return strspn(str, "0123456789-.") == str.GetLength(); -} - -static bool isHex(const CMStringA &str) -{ - return strspn(str, "0123456789abcdefABCDEF") == str.GetLength(); -} - -void WAWriter::writePacked(const CMStringA &str, int tag) -{ - if (str.GetLength() > 254) - return; - - writeByte(tag); - - int len = str.GetLength() / 2; - BYTE firstByte = (str.GetLength() % 2) == 0 ? 0 : 0x81; - writeByte(firstByte + len); - - const char *p = str; - for (int i = 0; i < len; i++, p += 2) - writeByte(packPair(tag, p[0], p[1])); - - if (firstByte != 0) - writeByte(packPair(tag, p[0], 0)); -} - -void WAWriter::writeString(const char *str) -{ - if (writeToken(str)) - return; - - auto *pszDelimiter = strchr(str, '@'); - if (pszDelimiter) { - WAJid jid(str); - if (jid.device || jid.agent) { - writeByte(AD_JID); - writeByte(jid.agent); - writeByte(jid.device); - writeString(jid.user); - } - else { - writeByte(JID_PAIR); - - if (jid.user.IsEmpty()) // empty user - writeByte(LIST_EMPTY); - else - writeString(jid.user); - - writeString(jid.server); - } - } - else { - CMStringA buf(str); - if (isNibble(buf)) - writePacked(buf, NIBBLE_8); - else if (isHex(buf)) - writePacked(buf, HEX_8); - else { - writeLength(buf.GetLength()); - body.append(buf, buf.GetLength()); - } - } -} +/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+#include "dicts.h"
+
+struct Attr
+{
+ Attr(const char *pszName, const char *pszValue) :
+ name(pszName),
+ value(pszValue)
+ {}
+
+ Attr(const char *pszName, int iValue) :
+ name(pszName),
+ value(FORMAT, "%d", iValue)
+ {}
+
+ CMStringA name, value;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WANodeIq members
+
+WANodeIq::WANodeIq(IQ::Type type, const char *pszXmlns, const char *pszTo) :
+ WANode("iq")
+{
+ switch (type) {
+ case IQ::GET: addAttr("type", "get"); break;
+ case IQ::SET: addAttr("type", "set"); break;
+ case IQ::RESULT: addAttr("type", "result"); break;
+ }
+
+ if (pszXmlns)
+ addAttr("xmlns", pszXmlns);
+
+ addAttr("to", pszTo ? pszTo : S_WHATSAPP_NET);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WANode members
+
+WANode::WANode() :
+ attrs(1),
+ children(1)
+{}
+
+WANode::WANode(const char *pszTitle) :
+ attrs(1),
+ children(1),
+ title(pszTitle)
+{}
+
+WANode::~WANode()
+{
+}
+
+const char *WANode::getAttr(const char *pszName) const
+{
+ if (this != nullptr)
+ for (auto &p : attrs)
+ if (p->name == pszName)
+ return p->value.c_str();
+
+ return nullptr;
+}
+
+int WANode::getAttrInt(const char *pszName) const
+{
+ if (this != nullptr)
+ for (auto &p : attrs)
+ if (p->name == pszName)
+ return atoi(p->value.c_str());
+
+ return 0;
+}
+
+void WANode::addAttr(const char *pszName, const char *pszValue)
+{
+ attrs.insert(new Attr(pszName, pszValue));
+}
+
+void WANode::addAttr(const char *pszName, int iValue)
+{
+ attrs.insert(new Attr(pszName, iValue));
+}
+
+CMStringA WANode::getBody() const
+{
+ return CMStringA((char *)content.data(), (int)content.length());
+}
+
+WANode *WANode::addChild(const char *pszName)
+{
+ auto *pNew = new WANode(pszName);
+ pNew->pParent = this;
+ children.insert(pNew);
+ return pNew;
+}
+
+WANode* WANode::getChild(const char *pszName) const
+{
+ if (this == nullptr)
+ return nullptr;
+
+ for (auto &it : children)
+ if (it->title == pszName)
+ return it;
+
+ return nullptr;
+}
+
+WANode* WANode::getFirstChild(void) const
+{
+ return (children.getCount()) ? &children[0] : nullptr;
+}
+
+void WANode::print(CMStringA &dest, int level) const
+{
+ for (int i = 0; i < level; i++)
+ dest.Append(" ");
+
+ dest.AppendFormat("<%s ", title.c_str());
+ for (auto &p : attrs)
+ dest.AppendFormat("%s=\"%s\" ", p->name.c_str(), p->value.c_str());
+ dest.Truncate(dest.GetLength() - 1);
+
+ if (content.isEmpty() && !children.getCount()) {
+ dest.Append("/>\n");
+ return;
+ }
+
+ dest.Append(">");
+ if (!content.isEmpty()) {
+ ptrA tmp((char *)mir_alloc(content.length() * 2 + 1));
+ bin2hex(content.data(), content.length(), tmp);
+ dest.AppendFormat("%s", tmp.get());
+ }
+
+ if (children.getCount()) {
+ dest.Append("\n");
+
+ for (auto &p : children)
+ p->print(dest, level + 1);
+
+ for (int i = 0; i < level; i++)
+ dest.Append(" ");
+ }
+
+ dest.AppendFormat("</%s>\n", title.c_str());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAReader class members
+
+bool WAReader::readAttributes(WANode *pNode, int count)
+{
+ if (count == 0)
+ return true;
+
+ for (int i = 0; i < count; i++) {
+ CMStringA name = readString(readInt8());
+ if (name.IsEmpty())
+ return false;
+
+ CMStringA value = readString(readInt8());
+ if (value.IsEmpty())
+ return false;
+
+ pNode->addAttr(name, value);
+ }
+ return true;
+}
+
+uint32_t WAReader::readInt20()
+{
+ if (m_limit - m_buf < 3)
+ return 0;
+
+ int ret = (int(m_buf[0] & 0x0F) << 16) + (int(m_buf[1]) << 8) + int(m_buf[2]);
+ m_buf += 3;
+ return ret;
+}
+
+uint32_t WAReader::readIntN(int n)
+{
+ if (m_limit - m_buf < n)
+ return 0;
+
+ uint32_t res = 0;
+ for (int i = 0; i < n; i++, m_buf++)
+ res = (res <<= 8) + *m_buf;
+ return res;
+}
+
+bool WAReader::readList(WANode *pParent, int tag)
+{
+ int size = readListSize(tag);
+ if (size == -1)
+ return false;
+
+ for (int i = 0; i < size; i++) {
+ WANode *pNew = readNode();
+ if (pNew == nullptr)
+ return false;
+ pParent->children.insert(pNew);
+ }
+
+ return true;
+}
+
+int WAReader::readListSize(int tag)
+{
+ switch (tag) {
+ case LIST_EMPTY:
+ return 0;
+ case LIST_8:
+ return readInt8();
+ case LIST_16:
+ return readInt16();
+ }
+ return -1;
+}
+
+WANode *WAReader::readNode()
+{
+ int listSize = readListSize(readInt8());
+ if (listSize == -1)
+ return nullptr;
+
+ int descrTag = readInt8();
+ if (descrTag == STREAM_END)
+ return nullptr;
+
+ CMStringA name = readString(descrTag);
+ if (name.IsEmpty())
+ return nullptr;
+
+ std::unique_ptr<WANode> ret(new WANode());
+ ret->title = name.c_str();
+
+ if (!readAttributes(ret.get(), (listSize - 1) >> 1))
+ return nullptr;
+
+ if ((listSize % 2) == 1)
+ return ret.release();
+
+ int size, tag = readInt8();
+ switch (tag) {
+ case LIST_EMPTY: case LIST_8: case LIST_16:
+ readList(ret.get(), tag);
+ break;
+
+ case BINARY_8:
+ size = readInt8();
+
+LBL_Binary:
+ if (m_limit - m_buf < size)
+ return false;
+
+ ret->content.assign((void *)m_buf, size);
+ m_buf += size;
+ break;
+
+ case BINARY_20:
+ size = readInt20();
+ goto LBL_Binary;
+
+ case BINARY_32:
+ size = readInt32();
+ goto LBL_Binary;
+
+ default:
+ CMStringA str = readString(tag);
+ ret->content.assign(str.GetBuffer(), str.GetLength() + 1);
+ }
+
+ return ret.release();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int unpackHex(int val)
+{
+ if (val < 0 || val > 15)
+ return -1;
+
+ return (val < 10) ? val + '0' : val - 10 + 'A';
+}
+
+static int unpackNibble(int val)
+{
+ if (val < 0 || val > 15)
+ return -1;
+
+ switch (val) {
+ case 10: return '-';
+ case 11: return '.';
+ case 15: return 0;
+ default: return '0' + val;
+ }
+}
+
+CMStringA WAReader::readPacked(int tag)
+{
+ int startByte = readInt8();
+ bool bTrim = false;
+ if (startByte & 0x80) {
+ startByte &= ~0x80;
+ bTrim = true;
+ }
+
+ CMStringA ret;
+ for (int i = 0; i < startByte; i++) {
+ BYTE b = readInt8();
+ int lower = (tag == NIBBLE_8) ? unpackNibble(b >> 4) : unpackHex(b >> 4);
+ if (lower == -1)
+ return "";
+
+ int higher = (tag == NIBBLE_8) ? unpackNibble(b & 0x0F) : unpackHex(b & 0x0F);
+ if (higher == -1)
+ return "";
+
+ ret.AppendChar(lower);
+ ret.AppendChar(higher);
+ }
+
+ if (bTrim && !ret.IsEmpty())
+ ret.Truncate(ret.GetLength() - 1);
+ return ret;
+}
+
+CMStringA WAReader::readString(int tag)
+{
+ if (tag >= 1 && tag < _countof(SingleByteTokens))
+ return SingleByteTokens[tag];
+
+ int idx;
+ switch (tag) {
+ case DICTIONARY_0:
+ idx = readInt8();
+ return (idx < _countof(dict0)) ? dict0[idx] : "";
+
+ case DICTIONARY_1:
+ idx = readInt8();
+ return (idx < _countof(dict1)) ? dict1[idx] : "";
+
+ case DICTIONARY_2:
+ idx = readInt8();
+ return (idx < _countof(dict2)) ? dict2[idx] : "";
+
+ case DICTIONARY_3:
+ idx = readInt8();
+ return (idx < _countof(dict3)) ? dict3[idx] : "";
+
+ case LIST_EMPTY:
+ return "";
+
+ case BINARY_8:
+ return readStringFromChars(readInt8());
+
+ case BINARY_20:
+ return readStringFromChars(readInt20());
+
+ case BINARY_32:
+ return readStringFromChars(readInt32());
+
+ case NIBBLE_8:
+ case HEX_8:
+ return readPacked(tag);
+
+ case AD_JID:
+ {
+ int agent = readInt8();
+ int device = readInt8();
+ WAJid jid(readString(readInt8()), "s.whatsapp.net", device, agent);
+ return jid.toString();
+ }
+
+ case JID_PAIR:
+ CMStringA s1 = readString(readInt8());
+ CMStringA s2 = readString(readInt8());
+ if (s1.IsEmpty() && s2.IsEmpty())
+ break;
+
+ return CMStringA(FORMAT, "%s@%s", s1.c_str(), s2.c_str());
+ }
+
+ // error
+ return "";
+}
+
+CMStringA WAReader::readStringFromChars(int size)
+{
+ if (m_limit - m_buf < size)
+ return "";
+
+ CMStringA ret((char *)m_buf, 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)
+{
+ // we never send zipped content
+ if (pNode->pParent == nullptr)
+ writeByte(0);
+
+ int numAttrs = (int)pNode->attrs.getCount();
+ int hasContent = pNode->content.length() != 0 || pNode->children.getCount() != 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
+ else if (pNode->children.getCount()) {
+ writeListSize(pNode->children.getCount());
+ for (auto &it : pNode->children)
+ writeNode(it);
+ }
+}
+
+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' && c <= '9')
+ return c - '0';
+
+ if (c >= 'A' && c <= 'F')
+ return 10 + c - 'A';
+
+ if (c >= 'a' && c <= 'f')
+ return 10 + c - 'a';
+
+ if (c == 0)
+ return 15;
+
+ 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;
+}
+
+static bool isNibble(const CMStringA &str)
+{
+ return strspn(str, "0123456789-.") == str.GetLength();
+}
+
+static bool isHex(const CMStringA &str)
+{
+ return strspn(str, "0123456789abcdefABCDEF") == str.GetLength();
+}
+
+void WAWriter::writePacked(const CMStringA &str, int tag)
+{
+ if (str.GetLength() > 254)
+ return;
+
+ writeByte(tag);
+
+ int len = str.GetLength() / 2;
+ BYTE firstByte = (str.GetLength() % 2) == 0 ? 0 : 0x81;
+ writeByte(firstByte + len);
+
+ const char *p = str;
+ for (int i = 0; i < len; i++, p += 2)
+ writeByte(packPair(tag, p[0], p[1]));
+
+ if (firstByte != 0)
+ writeByte(packPair(tag, p[0], 0));
+}
+
+void WAWriter::writeString(const char *str)
+{
+ if (writeToken(str))
+ return;
+
+ auto *pszDelimiter = strchr(str, '@');
+ if (pszDelimiter) {
+ WAJid jid(str);
+ if (jid.device || jid.agent) {
+ writeByte(AD_JID);
+ writeByte(jid.agent);
+ writeByte(jid.device);
+ writeString(jid.user);
+ }
+ else {
+ writeByte(JID_PAIR);
+
+ if (jid.user.IsEmpty()) // empty user
+ writeByte(LIST_EMPTY);
+ else
+ writeString(jid.user);
+
+ writeString(jid.server);
+ }
+ }
+ else {
+ CMStringA buf(str);
+ if (isNibble(buf))
+ writePacked(buf, NIBBLE_8);
+ else if (isHex(buf))
+ writePacked(buf, HEX_8);
+ else {
+ writeLength(buf.GetLength());
+ body.append(buf, buf.GetLength());
+ }
+ }
+}
|