From 1979fd80424d16b2e489f9b57d01d9c7811d25a2 Mon Sep 17 00:00:00 2001 From: dartraiden Date: Mon, 2 Jan 2023 21:10:29 +0300 Subject: Update copyrights --- protocols/WhatsApp/src/appsync.cpp | 2 +- protocols/WhatsApp/src/avatars.cpp | 338 ++++---- protocols/WhatsApp/src/chats.cpp | 2 +- protocols/WhatsApp/src/crypt.cpp | 354 ++++---- protocols/WhatsApp/src/db.h | 86 +- protocols/WhatsApp/src/dicts.h | 388 ++++----- protocols/WhatsApp/src/iq.cpp | 1140 +++++++++++++------------- protocols/WhatsApp/src/main.cpp | 142 ++-- protocols/WhatsApp/src/message.cpp | 1004 +++++++++++------------ protocols/WhatsApp/src/noise.cpp | 402 ++++----- protocols/WhatsApp/src/options.cpp | 190 ++--- protocols/WhatsApp/src/proto.cpp | 2 +- protocols/WhatsApp/src/proto.h | 2 +- protocols/WhatsApp/src/qrcode.cpp | 274 +++---- protocols/WhatsApp/src/server.cpp | 2 +- protocols/WhatsApp/src/signal.cpp | 1576 ++++++++++++++++++------------------ protocols/WhatsApp/src/stdafx.cxx | 16 +- protocols/WhatsApp/src/stdafx.h | 144 ++-- protocols/WhatsApp/src/utils.cpp | 1178 +++++++++++++-------------- protocols/WhatsApp/src/utils.h | 534 ++++++------ protocols/WhatsApp/src/version.h | 2 +- protocols/WhatsApp/src/wanode.cpp | 1216 ++++++++++++++-------------- 22 files changed, 4497 insertions(+), 4497 deletions(-) (limited to 'protocols/WhatsApp') 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 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 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(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(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 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 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 -{ - CCtrlCheck chkHideChats, chkBbcodes; - CCtrlEdit edtGroup, edtNick, edtDevName; - CCtrlButton btnUnregister; - ptrW m_wszOldGroup; - -public: - COptionsDlg(WhatsAppProto *ppro, int iDlgID, bool bFullDlg) : - CProtoDlgBase(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 +{ + CCtrlCheck chkHideChats, chkBbcodes; + CCtrlEdit edtGroup, edtNick, edtDevName; + CCtrlButton btnUnregister; + ptrW m_wszOldGroup; + +public: + COptionsDlg(WhatsAppProto *ppro, int iDlgID, bool bFullDlg) : + CProtoDlgBase(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 -{ -public: - CWhatsAppQRDlg(WhatsAppProto *ppro) : - CProtoDlgBase(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 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 +{ +public: + CWhatsAppQRDlg(WhatsAppProto *ppro) : + CProtoDlgBase(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 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 -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 attrs; - OBJLIST 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 &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 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 attrs; + OBJLIST 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 &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 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("\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 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("\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 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()); + } + } +} -- cgit v1.2.3