summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-10-01 14:20:05 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-10-01 14:20:05 +0300
commit87ff6ba67d4df0b4c0ae3db86220b816ec6a1e75 (patch)
tree9927b65a05688d70cf1fef5ef52a7fe956138de2
parent6de31630215f88f3639efb834be02e5af9bfce57 (diff)
WhatsApp:
- first version that registers ok in the phone (YAHOO!!); - the rest of legacy code removed; - class WAJid introduced to handle various perversions; - ServerThread behavior changed to use global variables instead of locals; - IQ processing core extracted to the separate module; - added <stream:error> processing
-rw-r--r--protocols/WhatsAppWeb/WhatsAppWeb.vcxproj3
-rw-r--r--protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters5
-rw-r--r--protocols/WhatsAppWeb/src/avatars.cpp4
-rw-r--r--protocols/WhatsAppWeb/src/chats.cpp2
-rw-r--r--protocols/WhatsAppWeb/src/db.h10
-rw-r--r--protocols/WhatsAppWeb/src/dicts.h7
-rw-r--r--protocols/WhatsAppWeb/src/iq.cpp308
-rw-r--r--protocols/WhatsAppWeb/src/main.cpp2
-rw-r--r--protocols/WhatsAppWeb/src/noise.cpp3
-rw-r--r--protocols/WhatsAppWeb/src/proto.cpp21
-rw-r--r--protocols/WhatsAppWeb/src/proto.h15
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp469
-rw-r--r--protocols/WhatsAppWeb/src/stdafx.cxx2
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp51
-rw-r--r--protocols/WhatsAppWeb/src/utils.h13
-rw-r--r--protocols/WhatsAppWeb/src/wanode.cpp (renamed from protocols/WhatsAppWeb/src/wareader.cpp)6
16 files changed, 477 insertions, 444 deletions
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
index 12a1eebf49..da5fc7e34f 100644
--- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
+++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
@@ -57,6 +57,7 @@
<ItemGroup>
<ClCompile Include="src\avatars.cpp" />
<ClCompile Include="src\chats.cpp" />
+ <ClCompile Include="src\iq.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\noise.cpp" />
<ClCompile Include="src\options.cpp" />
@@ -70,7 +71,7 @@
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="src\utils.cpp" />
- <ClCompile Include="src\wareader.cpp" />
+ <ClCompile Include="src\wanode.cpp" />
<ClInclude Include="src\db.h" />
<ClInclude Include="src\dicts.h" />
<ClInclude Include="src\proto.h" />
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
index bba054ab20..4b97a898d3 100644
--- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
+++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
@@ -35,7 +35,10 @@
<ClCompile Include="src\noise.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="src\wareader.cpp">
+ <ClCompile Include="src\wanode.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\iq.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
diff --git a/protocols/WhatsAppWeb/src/avatars.cpp b/protocols/WhatsAppWeb/src/avatars.cpp
index 5649067330..c9eefb9d6b 100644
--- a/protocols/WhatsAppWeb/src/avatars.cpp
+++ b/protocols/WhatsAppWeb/src/avatars.cpp
@@ -44,7 +44,7 @@ INT_PTR WhatsAppProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam)
{
PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam;
- ptrA id(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_ID));
+ ptrA id(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_JID));
if (id == NULL)
return GAIR_NOAVATAR;
@@ -92,7 +92,7 @@ CMStringW WhatsAppProto::GetAvatarFileName(MCONTACT hContact)
CMStringA jid;
if (hContact != NULL) {
- ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_ID));
+ ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_JID));
if (szId == NULL)
return L"";
diff --git a/protocols/WhatsAppWeb/src/chats.cpp b/protocols/WhatsAppWeb/src/chats.cpp
index 3017313a5a..a478fae692 100644
--- a/protocols/WhatsAppWeb/src/chats.cpp
+++ b/protocols/WhatsAppWeb/src/chats.cpp
@@ -1,7 +1,7 @@
/*
WhatsAppWeb plugin for Miranda NG
-Copyright © 2019 George Hazan
+Copyright © 2019-22 George Hazan
*/
diff --git a/protocols/WhatsAppWeb/src/db.h b/protocols/WhatsAppWeb/src/db.h
index 66f1686ecc..adab0945e7 100644
--- a/protocols/WhatsAppWeb/src/db.h
+++ b/protocols/WhatsAppWeb/src/db.h
@@ -8,14 +8,8 @@ Copyright © 2019-22 George Hazan
#define MODULENAME "WhatsApp"
// DB keys
-#define DBKEY_ID "ID"
-#define DBKEY_LOGIN "Login"
-#define DBKEY_CC "CountryCode"
-
-#define DBKEY_CLIENT_ID "ClientId"
-#define DBKEY_CLIENT_TOKEN "ClientToken"
-#define DBKEY_SERVER_TOKEN "ServerToken"
-#define DBKEY_BROWSER_TOKEN "BrowserToken"
+#define DBKEY_JID "jid"
+#define DBKEY_DEVICE_ID "DeviceId"
#define DBKEY_NOISE_PUB "NoisePublicKey"
#define DBKEY_NOISE_PRIV "NoisePrivateKey"
diff --git a/protocols/WhatsAppWeb/src/dicts.h b/protocols/WhatsAppWeb/src/dicts.h
index a8c8b25d61..281983c618 100644
--- a/protocols/WhatsAppWeb/src/dicts.h
+++ b/protocols/WhatsAppWeb/src/dicts.h
@@ -1,3 +1,10 @@
+/*
+
+WhatsAppWeb 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",
diff --git a/protocols/WhatsAppWeb/src/iq.cpp b/protocols/WhatsAppWeb/src/iq.cpp
new file mode 100644
index 0000000000..1442e5c02f
--- /dev/null
+++ b/protocols/WhatsAppWeb/src/iq.cpp
@@ -0,0 +1,308 @@
+/*
+
+WhatsAppWeb plugin for Miranda NG
+Copyright © 2019-22 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnStreamError(const WANode &node)
+{
+ m_bTerminated = true;
+
+ if (auto *pszCode = node.getAttr("code")) {
+ switch (atoi(pszCode)) {
+ case 401:
+ debugLogA("Connection logged out from another device, exiting");
+ break;
+
+ case 408:
+ debugLogA("Connection lost, exiting");
+ break;
+
+ case 411:
+ debugLogA("Conflict between two devices, exiting");
+ break;
+
+ case 428:
+ debugLogA("Connection forcibly closed by the server, exiting");
+ break;
+
+ case 440:
+ debugLogA("Connection replaced from another device, exiting");
+ break;
+
+ case 515:
+ debugLogA("Server required to restart immediately, leaving thread");
+ m_bRespawn = true;
+ break;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqPairDevice(const WANode &node)
+{
+ WANode reply("iq");
+ reply << CHAR_PARAM("to", S_WHATSAPP_NET) << CHAR_PARAM("type", "result") << CHAR_PARAM("id", node.getAttr("id"));
+ WSSendNode(reply);
+
+ 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;
+ setUString(DBKEY_JID, 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;
+ if (!payload.ParseFromArray(pIdentity->content.data(), (int)pIdentity->content.length()))
+ throw "OnIqPairSuccess: got reply with invalid identity, exiting";
+
+ 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(), (BYTE *)details.c_str(), (int)details.size(), signature, &out_len);
+ if (memcmp(hmac.c_str(), signature, sizeof(signature)))
+ throw "OnIqPairSuccess: got reply with invalid details signature, exiting";
+ }
+
+ proto::ADVSignedDeviceIdentity account;
+ if (!account.ParseFromString(details))
+ throw "OnIqPairSuccess: got reply with invalid account, exiting";
+
+ auto &deviceDetails = account.details();
+ auto &accountSignature = account.accountsignature();
+ auto &accountSignatureKey = account.accountsignaturekey();
+ {
+ MBinBuffer buf;
+ buf.append("\x06\x00", 2);
+ buf.append(deviceDetails.c_str(), deviceDetails.size());
+ buf.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length());
+
+ ec_public_key key = {};
+ memcpy(key.data, accountSignatureKey.c_str(), sizeof(key.data));
+ if (1 != curve_verify_signature(&key, (BYTE *)buf.data(), buf.length(), (BYTE *)accountSignature.c_str(), accountSignature.size()))
+ throw "OnIqPairSuccess: got reply with invalid account signature, exiting";
+ }
+ debugLogA("Received valid account signature");
+ {
+ MBinBuffer buf;
+ buf.append("\x06\x01", 2);
+ buf.append(deviceDetails.c_str(), deviceDetails.size());
+ buf.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length());
+ buf.append(accountSignatureKey.c_str(), accountSignatureKey.size());
+
+ signal_buffer *result;
+ ec_private_key key = {};
+ memcpy(key.data, m_noise->signedIdentity.priv.data(), m_noise->signedIdentity.priv.length());
+ if (curve_calculate_signature(g_plugin.pCtx, &result, &key, (BYTE *)buf.data(), buf.length()) != 0)
+ throw "OnIqPairSuccess: cannot calculate account signature, exiting";
+
+ account.set_devicesignature(result->data, result->len);
+ signal_buffer_free(result);
+ }
+
+ setDword("SignalDeviceId", 0);
+ {
+ MBinBuffer key;
+ if (accountSignatureKey.size() == 32)
+ key.append(KEY_BUNDLE_TYPE, 1);
+ key.append(accountSignatureKey.c_str(), accountSignatureKey.size());
+ db_set_blob(0, m_szModuleName, "SignalIdentifierKey", key.data(), (int)key.length());
+ }
+
+ account.clear_accountsignaturekey();
+
+ MBinBuffer accountEnc(account.ByteSize());
+ account.SerializeToArray(accountEnc.data(), (int)accountEnc.length());
+ db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length());
+
+ proto::ADVDeviceIdentity deviceIdentity;
+ deviceIdentity.ParseFromString(deviceDetails);
+
+ WANode reply("iq");
+ reply << CHAR_PARAM("to", S_WHATSAPP_NET) << CHAR_PARAM("type", "result") << 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::OnProcessHandshake(const void *pData, int cbLen)
+{
+ proto::HandshakeMessage msg;
+ if (!msg.ParseFromArray(pData, cbLen)) {
+ 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_.c_str(), ephemeral_.size());
+ m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.c_str());
+
+ MBinBuffer decryptedStatic = m_noise->decrypt(static_.c_str(), static_.size());
+ m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data());
+
+ MBinBuffer decryptedCert = m_noise->decrypt(payload_.c_str(), payload_.size());
+
+ proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length());
+ proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(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_.c_str());
+
+ // create reply
+ proto::ClientPayload node;
+
+ MFileVersion v;
+ Miranda_GetFileVersion(&v);
+
+ // not received our jid from server? generate registration packet then
+ if (m_szJid.IsEmpty()) {
+ uint8_t appVersion[16];
+ mir_md5_hash((BYTE *)APP_VERSION, sizeof(APP_VERSION) - 1, appVersion);
+
+ auto *pAppVersion = new proto::DeviceProps_AppVersion();
+ pAppVersion->set_primary(v[0]);
+ pAppVersion->set_secondary(v[1]);
+ pAppVersion->set_tertiary(v[2]);
+ pAppVersion->set_quaternary(v[3]);
+
+ proto::DeviceProps pCompanion;
+ pCompanion.set_os("Miranda");
+ pCompanion.set_allocated_version(pAppVersion);
+ pCompanion.set_platformtype(proto::DeviceProps_PlatformType_DESKTOP);
+ pCompanion.set_requirefullsync(false);
+
+ MBinBuffer buf(pCompanion.ByteSize());
+ pCompanion.SerializeToArray(buf.data(), (int)buf.length());
+
+ auto *pPairingData = new proto::ClientPayload_DevicePairingRegistrationData();
+ pPairingData->set_deviceprops(buf.data(), buf.length());
+ pPairingData->set_buildhash(appVersion, sizeof(appVersion));
+ pPairingData->set_eregid(encodeBigEndian(getDword(DBKEY_REG_ID)));
+ pPairingData->set_ekeytype(KEY_BUNDLE_TYPE);
+ pPairingData->set_eident(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length());
+ pPairingData->set_eskeyid(encodeBigEndian(m_noise->preKey.keyid));
+ pPairingData->set_eskeyval(m_noise->preKey.pub.data(), m_noise->preKey.pub.length());
+ pPairingData->set_eskeysig(m_noise->preKey.signature.data(), m_noise->preKey.signature.length());
+ node.set_allocated_devicepairingdata(pPairingData);
+
+ node.set_passive(false);
+ }
+ // generate login packet
+ else {
+ WAJid jid(m_szJid);
+ node.set_username(_atoi64(jid.user));
+ node.set_device(getDword(DBKEY_DEVICE_ID));
+ node.set_passive(true);
+ }
+
+ auto *pUserVersion = new proto::ClientPayload_UserAgent_AppVersion();
+ pUserVersion->set_primary(2);
+ pUserVersion->set_secondary(2230);
+ pUserVersion->set_tertiary(15);
+
+ auto *pUserAgent = new proto::ClientPayload_UserAgent();
+ pUserAgent->set_allocated_appversion(pUserVersion);
+ pUserAgent->set_platform(proto::ClientPayload_UserAgent_Platform_WEB);
+ pUserAgent->set_releasechannel(proto::ClientPayload_UserAgent_ReleaseChannel_RELEASE);
+ pUserAgent->set_mcc("000");
+ pUserAgent->set_mnc("000");
+ pUserAgent->set_osversion("0.1");
+ pUserAgent->set_osbuildnumber("0.1");
+ pUserAgent->set_manufacturer("");
+ pUserAgent->set_device("Desktop");
+ pUserAgent->set_localelanguageiso6391("en");
+ pUserAgent->set_localecountryiso31661alpha2("US");
+
+ auto *pWebInfo = new proto::ClientPayload_WebInfo();
+ pWebInfo->set_websubplatform(proto::ClientPayload_WebInfo_WebSubPlatform_WEB_BROWSER);
+
+ node.set_connecttype(proto::ClientPayload_ConnectType_WIFI_UNKNOWN);
+ node.set_connectreason(proto::ClientPayload_ConnectReason_USER_ACTIVATED);
+ node.set_allocated_useragent(pUserAgent);
+ node.set_allocated_webinfo(pWebInfo);
+
+ MBinBuffer payload(node.ByteSize());
+ node.SerializeToArray(payload.data(), (int)payload.length());
+
+ MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length());
+
+ auto *pFinish = new proto::HandshakeMessage_ClientFinish();
+ pFinish->set_payload(payloadEnc.data(), payloadEnc.length());
+ pFinish->set_static_(encryptedPub.data(), encryptedPub.length());
+
+ proto::HandshakeMessage handshake;
+ handshake.set_allocated_clientfinish(pFinish);
+ WSSend(handshake);
+
+ m_noise->finish();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::InitPersistentHandlers()
+{
+ m_arPersistent.insert(new WAPersistentHandler("iq", "md", "pair-device", &WhatsAppProto::OnIqPairDevice));
+ m_arPersistent.insert(new WAPersistentHandler("iq", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess));
+
+ m_arPersistent.insert(new WAPersistentHandler("stream:error", nullptr, nullptr, &WhatsAppProto::OnStreamError));
+}
diff --git a/protocols/WhatsAppWeb/src/main.cpp b/protocols/WhatsAppWeb/src/main.cpp
index b7a8a790ed..445f22edb3 100644
--- a/protocols/WhatsAppWeb/src/main.cpp
+++ b/protocols/WhatsAppWeb/src/main.cpp
@@ -39,7 +39,7 @@ extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOC
CMPlugin::CMPlugin() :
ACCPROTOPLUGIN<WhatsAppProto>(MODULENAME, pluginInfo)
{
- SetUniqueId(DBKEY_ID);
+ SetUniqueId(DBKEY_JID);
}
/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/protocols/WhatsAppWeb/src/noise.cpp b/protocols/WhatsAppWeb/src/noise.cpp
index 72b64fcc8a..68f2549bf9 100644
--- a/protocols/WhatsAppWeb/src/noise.cpp
+++ b/protocols/WhatsAppWeb/src/noise.cpp
@@ -30,9 +30,6 @@ WANoise::WANoise(WhatsAppProto *_ppro) :
ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
ec_key_pair_destroy(pKeys);
- // ephemeral.pub.assign("\xd7\x58\xeb\xcc\x79\xb8\x58\xde\xc7\x60\x5c\x12\x22\xc1\x3b\x7c\xf6\x73\x38\x0b\x89\x56\xf1\xe2\xa1\xb0\xaa\x3a\xba\xbc\x08\x3f", 32);
- // ephemeral.priv.assign("\xa0\xef\xd2\xbd\x2d\x4a\x6f\x9c\xd0\x9e\xc5\x75\x3c\x78\x78\xed\xe5\xec\x99\xd7\x4b\xeb\xf8\xb0\xdd\x1e\xe2\xc1\x85\xc4\xd8\x72", 32);
-
// prepare hash
memcpy(hash, noise_init, 32);
updateHash(intro_header, 4);
diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp
index c3d965e919..8e94628b92 100644
--- a/protocols/WhatsAppWeb/src/proto.cpp
+++ b/protocols/WhatsAppWeb/src/proto.cpp
@@ -30,6 +30,7 @@ static int CompareUsers(const WAUser *p1, const WAUser *p2)
WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
PROTO<WhatsAppProto>(proto_name, username),
m_impl(*this),
+ m_szJid(getMStringA(DBKEY_JID)),
m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)),
m_arUsers(10, CompareUsers),
m_arOwnMsgs(1, CompareOwnMsgs),
@@ -49,18 +50,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit);
- m_arPersistent.insert(new WAPersistentHandler("iq", "md", "pair-device", &WhatsAppProto::OnIqPairDevice));
- m_arPersistent.insert(new WAPersistentHandler("iq", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess));
-
- // Client id generation
- m_szClientId = getMStringA(DBKEY_CLIENT_ID);
- if (m_szClientId.IsEmpty()) {
- int8_t randBytes[16];
- Utils_GetRandom(randBytes, sizeof(randBytes));
-
- m_szClientId = ptrA(mir_base64_encode(randBytes, sizeof(randBytes)));
- setString(DBKEY_CLIENT_ID, m_szClientId);
- }
+ InitPersistentHandlers();
// Create standard network connection
wchar_t descr[512];
@@ -72,6 +62,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
nlu.szDescriptiveName.w = descr;
m_hNetlibUser = Netlib_RegisterUser(&nlu);
+ // Avatars folder
m_tszAvatarFolder = CMStringW(VARSW(L"%miranda_avatarcache%")) + L"\\" + m_tszUserName;
DWORD dwAttributes = GetFileAttributes(m_tszAvatarFolder.c_str());
if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
@@ -101,7 +92,7 @@ void WhatsAppProto::OnModulesLoaded()
{
// initialize contacts cache
for (auto &cc : AccContacts()) {
- CMStringA szId(getMStringA(cc, isChatRoom(cc) ? "ChatRoomID" : DBKEY_ID));
+ CMStringA szId(getMStringA(cc, isChatRoom(cc) ? "ChatRoomID" : DBKEY_JID));
if (!szId.IsEmpty())
m_arUsers.insert(new WAUser(cc, szId));
}
@@ -225,7 +216,7 @@ void WhatsAppProto::OnSendMessage(const JSONNode &node, void*)
int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg)
{
- ptrA jid(getStringA(hContact, DBKEY_ID));
+ ptrA jid(getStringA(hContact, DBKEY_JID));
if (jid == nullptr || pszMsg == nullptr)
return 0;
@@ -267,7 +258,7 @@ int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg)
int WhatsAppProto::UserIsTyping(MCONTACT hContact, int)
{
if (hContact && isOnline()) {
- ptrA jid(getStringA(hContact, DBKEY_ID));
+ ptrA jid(getStringA(hContact, DBKEY_JID));
if (jid && isOnline()) {
}
}
diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h
index 307615636b..b2e0bc6301 100644
--- a/protocols/WhatsAppWeb/src/proto.h
+++ b/protocols/WhatsAppWeb/src/proto.h
@@ -139,10 +139,10 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
} m_impl;
- bool m_bTerminated;
+ bool m_bTerminated, m_bRespawn;
ptrW m_tszDefaultGroup;
- CMStringA m_szJid, m_szClientId, m_szClientToken;
+ CMStringA m_szJid;
CMStringW m_tszAvatarFolder;
EVP_PKEY *m_pKeys; // private & public keys
@@ -187,7 +187,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
void OnLoggedIn(void);
void OnLoggedOut(void);
- bool ServerThreadWorker(void);
+ void ServerThreadWorker(void);
void ShutdownSession(void);
void SendKeepAlive();
@@ -200,20 +200,15 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
void OnProcessHandshake(const void *pData, int cbLen);
+ void InitPersistentHandlers();
void OnIqPairDevice(const WANode &node);
void OnIqPairSuccess(const WANode &node);
+ void OnStreamError(const WANode &node);
// binary packets
void ProcessBinaryPacket(const void *pData, size_t cbLen);
void ProcessBinaryNode(const WANode &node);
- // text packets
- void ProcessPacket(const JSONNode &node);
- void ProcessBlocked(const JSONNode &node);
- void ProcessCmd(const JSONNode &node);
- void ProcessConn(const JSONNode &node);
- void ProcessPresence(const JSONNode &node);
-
/// Avatars ////////////////////////////////////////////////////////////////////////////
CMStringW GetAvatarFileName(MCONTACT hContact);
diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp
index 63979d9cc3..126d73e956 100644
--- a/protocols/WhatsAppWeb/src/server.cpp
+++ b/protocols/WhatsAppWeb/src/server.cpp
@@ -8,354 +8,20 @@ Copyright © 2019-22 George Hazan
#include "stdafx.h"
/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnLoggedIn()
-{
- debugLogA("WhatsAppProto::OnLoggedIn");
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
- m_iStatus = m_iDesiredStatus;
-
- SendKeepAlive();
- m_impl.m_keepAlive.Start(60000);
-}
-
-void WhatsAppProto::OnLoggedOut(void)
-{
- debugLogA("WhatsAppProto::OnLoggedOut");
- m_bTerminated = true;
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
- m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
-
- setAllContactStatuses(ID_STATUS_OFFLINE, false);
-}
-
-void WhatsAppProto::SendKeepAlive()
-{
- WebSocket_SendText(m_hServerConn, "?,,");
-
- time_t now = time(0);
-
- for (auto &it : m_arUsers) {
- if (it->m_time1 && now - it->m_time1 >= 1200) { // 20 minutes
- setWord(it->hContact, "Status", ID_STATUS_NA);
- it->m_time1 = 0;
- it->m_time2 = now;
- }
- else if (it->m_time2 && now - it->m_time2 >= 1200) { // 20 minutes
- setWord(it->hContact, "Status", ID_STATUS_OFFLINE);
- it->m_time2 = 0;
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::ShutdownSession()
-{
- if (m_bTerminated)
- return;
-
- debugLogA("WhatsAppProto::ShutdownSession");
-
- // shutdown all resources
- if (m_hServerConn)
- Netlib_Shutdown(m_hServerConn);
-
- OnLoggedOut();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqPairDevice(const WANode &node)
-{
- WANode reply("iq");
- reply << CHAR_PARAM("to", S_WHATSAPP_NET) << CHAR_PARAM("type", "result") << CHAR_PARAM("id", node.getAttr("id"));
- WSSendNode(reply);
-
- 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"))
- setUString("jid", pszJid);
- }
- else throw "OnIqPairSuccess: got reply without device info, exiting";
-
- if (auto *pIdentity = pRoot->getChild("device-identity")) {
- proto::ADVSignedDeviceIdentityHMAC payload;
- if (!payload.ParseFromArray(pIdentity->content.data(), (int)pIdentity->content.length()))
- throw "OnIqPairSuccess: got reply with invalid identity, exiting";
-
- auto &hmac = payload.hmac();
- debugLogA("Received HMAC signature: %s", hmac.c_str());
-
- auto &details = payload.details();
- Netlib_Dump(nullptr, details.c_str(), details.size(), false, 0);
-
- // 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(), (BYTE *)details.c_str(), (int)details.size(), signature, &out_len);
- if (memcmp(hmac.c_str(), signature, sizeof(signature)))
- throw "OnIqPairSuccess: got reply with invalid details signature, exiting";
- }
-
- proto::ADVSignedDeviceIdentity account;
- if (!account.ParseFromString(details))
- throw "OnIqPairSuccess: got reply with invalid account, exiting";
-
- auto &deviceDetails = account.details();
- auto &accountSignature = account.accountsignature();
- auto &accountSignatureKey = account.accountsignaturekey();
- {
- MBinBuffer buf;
- buf.append("\x06\x00", 2);
- buf.append(deviceDetails.c_str(), deviceDetails.size());
- buf.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length());
-
- ec_public_key key = {};
- memcpy(key.data, accountSignatureKey.c_str(), sizeof(key.data));
- if (1 != curve_verify_signature(&key, (BYTE *)buf.data(), buf.length(), (BYTE *)accountSignature.c_str(), accountSignature.size()))
- throw "OnIqPairSuccess: got reply with invalid account signature, exiting";
- }
- debugLogA("Received valid account signature");
- {
- MBinBuffer buf;
- buf.append("\x06\x01", 2);
- buf.append(deviceDetails.c_str(), deviceDetails.size());
- buf.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length());
- buf.append(accountSignatureKey.c_str(), accountSignatureKey.size());
-
- signal_buffer *result;
- ec_private_key key = {};
- memcpy(key.data, m_noise->signedIdentity.priv.data(), m_noise->signedIdentity.priv.length());
- if (curve_calculate_signature(g_plugin.pCtx, &result, &key, (BYTE *)buf.data(), buf.length()) != 0)
- throw "OnIqPairSuccess: cannot calculate account signature, exiting";
-
- account.set_devicesignature(result->data, result->len);
- signal_buffer_free(result);
- }
-
- setDword("SignalDeviceId", 0);
- {
- MBinBuffer key;
- if (accountSignatureKey.size() == 32)
- key.append(KEY_BUNDLE_TYPE, 1);
- key.append(accountSignatureKey.c_str(), accountSignatureKey.size());
- db_set_blob(0, m_szModuleName, "SignalIdentifierKey", key.data(), (int)key.length());
- }
-
- account.clear_accountsignaturekey();
-
- MBinBuffer accountEnc(account.ByteSize());
- account.SerializeToArray(accountEnc.data(), (int)accountEnc.length());
- db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length());
-
- proto::ADVDeviceIdentity deviceIdentity;
- deviceIdentity.ParseFromString(deviceDetails);
-
- WANode reply("iq");
- reply << CHAR_PARAM("to", S_WHATSAPP_NET) << CHAR_PARAM("type", "result") << 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::OnProcessHandshake(const void *pData, int cbLen)
-{
- proto::HandshakeMessage msg;
- if (!msg.ParseFromArray(pData, cbLen)) {
- debugLogA("Error parsing data, exiting");
-
-LBL_Error:
- ShutdownSession();
- return;
- }
-
- auto &static_ = msg.serverhello().static_();
- auto &payload_ = msg.serverhello().payload();
- auto &ephemeral_ = msg.serverhello().ephemeral();
-
- // std::string ephemeral_("\xe5\x3d\xff\xc6\x59\xef\xa7\x64\xf6\x48\xf3\x46\x15\xb4\x4f\x13\xfa\xc6\x29\x18\x34\xd4\xa4\xa9\xad\xa0\xa3\x05\xaa\x4d\xda\x36", 32);
- // std::string static_("\xa9\x70\xc0\xfe\xe0\x72\x3b\x0a\x6e\x0a\xd4\xe1\xb4\x5f\xcd\xb8\x06\x68\xdd\x6b\x45\x36\xe0\x7d\x0d\xe9\x00\x4d\xb6\xaf\xfa\xa3\xb7\x54\x82\x24\xa9\xe4\x2c\xd4\xe5\xd0\x2f\xd6\x31\x2b\xca\xec", 48);
- // std::string payload_("\x11\xcc\xb8\x74\xe6\x27\x29\x37\x65\xb2\x9e\x47\x53\x89\xf7\xce\x23\x03\x9c\xd4\x9e\x4b\x12\xdc\x3f\x10\xe4\x68\xfe\xfd\x31\x80\x1d\x48\x01\x9c\x31\xef\x54\xdb\xa1\x8f\xdb\x6b\x53\x84\xbb\x6d\xb4\x0c\x61\x1f\xcd\xe7\x3c\x0e\xe2\x18\xe4\x95\xf7\xbc\x5b\xbf\x80\x93\x21\x98\x80\x20\x9b\x71\x27\x47\x39\xe9\x04\x08\x5d\xd2\x62\x48\xf6\x23\xba\xa0\x31\xfc\x7c\xeb\xa0\xaa\x56\x04\x71\x71\x84\x9b\x08\xa4\xc9\x33\xd5\x07\x04\x5f\x1c\xd2\x6f\x7d\x5d\x83\x29\x5f\x80\x4a\xbf\x7c\xd9\x7c\xd0\x2b\x9a\x1e\xe0\x28\x33\x89\xb5\x3b\xd2\xe7\x7f\xfc\xd6\xa8\x55\xe2\x9c\x6e\x5f\x6f\x08\xa1\xf3\xfd\x5e\xff\x56\xee\x6f\x31\x47\xeb\xd4\x07\x92\x81\x72\x68\x91\x9d\xe9\xb3\x5f\x1d\x61\x8e\xce\x55\x4b\xbe\x74\x5d\xef\xea\xe4\x23\x63\x0d\x7c\xd4\xa3\xf8\xa1\x29\x60\xd7\x2c\xe8\xfc\xb5\x89\x99\x32\x95\xfc\xec\x6f\x7b\x2a\x23\xf4\x75\xbe\xe3\x21\x6a\x71\x3f\xc4\x1b\x99\x9f\x42\xd5\x19\xd8\xcc\xe7\xab\x90\xdd\xe5\xd8\xa5\xe3\xb5\x5c\xa2\x54\xf3\x4b\x0c\xa1\xe2\xa2\x91\xa3\xd0\x92\x6d\xfa\xab\x5a\xf6\x80\xee\x84\xbe\xaa\x75\x5e\xee\x6b\x91\x49", 257);
-
- m_noise->updateHash(ephemeral_.c_str(), ephemeral_.size());
- m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.c_str());
-
- MBinBuffer decryptedStatic = m_noise->decrypt(static_.c_str(), static_.size());
- m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data());
-
- MBinBuffer decryptedCert = m_noise->decrypt(payload_.c_str(), payload_.size());
-
- proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length());
- proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(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_.c_str());
-
- // create reply
- proto::ClientPayload node;
-
- MFileVersion v;
- Miranda_GetFileVersion(&v);
-
- // generate registration packet
- if (m_szClientToken.IsEmpty()) {
- uint8_t appVersion[16];
- mir_md5_hash((BYTE*)APP_VERSION, sizeof(APP_VERSION) - 1, appVersion);
-
- auto *pAppVersion = new proto::DeviceProps_AppVersion();
- pAppVersion->set_primary(v[0]);
- pAppVersion->set_secondary(v[1]);
- pAppVersion->set_tertiary(v[2]);
- pAppVersion->set_quaternary(v[3]);
-
- proto::DeviceProps pCompanion;
- pCompanion.set_os("Miranda");
- pCompanion.set_allocated_version(pAppVersion);
- pCompanion.set_platformtype(proto::DeviceProps_PlatformType_DESKTOP);
- pCompanion.set_requirefullsync(false);
-
- MBinBuffer buf(pCompanion.ByteSize());
- pCompanion.SerializeToArray(buf.data(), (int)buf.length());
-
- auto *pPairingData = new proto::ClientPayload_DevicePairingRegistrationData();
- pPairingData->set_deviceprops(buf.data(), buf.length());
- pPairingData->set_buildhash(appVersion, sizeof(appVersion));
- pPairingData->set_eregid(encodeBigEndian(getDword(DBKEY_REG_ID)));
- pPairingData->set_ekeytype(KEY_BUNDLE_TYPE);
- pPairingData->set_eident(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length());
- pPairingData->set_eskeyid(encodeBigEndian(m_noise->preKey.keyid));
- pPairingData->set_eskeyval(m_noise->preKey.pub.data(), m_noise->preKey.pub.length());
- pPairingData->set_eskeysig(m_noise->preKey.signature.data(), m_noise->preKey.signature.length());
- node.set_allocated_devicepairingdata(pPairingData);
-
- node.set_passive(false);
- }
- // generate login packet
- else {
- node.set_passive(true);
- }
-
- auto *pUserVersion = new proto::ClientPayload_UserAgent_AppVersion();
- pUserVersion->set_primary(2);
- pUserVersion->set_secondary(2230);
- pUserVersion->set_tertiary(15);
-
- auto *pUserAgent = new proto::ClientPayload_UserAgent();
- pUserAgent->set_allocated_appversion(pUserVersion);
- pUserAgent->set_platform(proto::ClientPayload_UserAgent_Platform_WEB);
- pUserAgent->set_releasechannel(proto::ClientPayload_UserAgent_ReleaseChannel_RELEASE);
- pUserAgent->set_mcc("000");
- pUserAgent->set_mnc("000");
- pUserAgent->set_osversion("0.1");
- pUserAgent->set_osbuildnumber("0.1");
- pUserAgent->set_manufacturer("");
- pUserAgent->set_device("Desktop");
- pUserAgent->set_localelanguageiso6391("en");
- pUserAgent->set_localecountryiso31661alpha2("US");
-
- auto *pWebInfo = new proto::ClientPayload_WebInfo();
- pWebInfo->set_websubplatform(proto::ClientPayload_WebInfo_WebSubPlatform_WEB_BROWSER);
-
- node.set_connecttype(proto::ClientPayload_ConnectType_WIFI_UNKNOWN);
- node.set_connectreason(proto::ClientPayload_ConnectReason_USER_ACTIVATED);
- node.set_allocated_useragent(pUserAgent);
- node.set_allocated_webinfo(pWebInfo);
-
- MBinBuffer payload(node.ByteSize());
- node.SerializeToArray(payload.data(), (int)payload.length());
-
- MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length());
-
- auto *pFinish = new proto::HandshakeMessage_ClientFinish();
- pFinish->set_payload(payloadEnc.data(), payloadEnc.length());
- pFinish->set_static_(encryptedPub.data(), encryptedPub.length());
-
- proto::HandshakeMessage handshake;
- handshake.set_allocated_clientfinish(pFinish);
- WSSend(handshake);
-
- m_noise->finish();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
// gateway worker thread
-bool WhatsAppProto::WSReadPacket(const WSHeader &hdr, MBinBuffer &res)
-{
- size_t currPacketSize = res.length() - hdr.headerSize;
-
- char buf[1024];
- while (currPacketSize < hdr.payloadSize) {
- int result = Netlib_Recv(m_hServerConn, buf, _countof(buf), MSG_NODUMP);
- if (result == 0) {
- debugLogA("Gateway connection gracefully closed");
- return false;
- }
- if (result < 0) {
- debugLogA("Gateway connection error, exiting");
- return false;
- }
-
- currPacketSize += result;
- res.append(buf, result);
- }
- return true;
-}
-
void WhatsAppProto::ServerThread(void *)
{
- m_bTerminated = false;
+ do {
+ m_bRespawn = false;
+ ServerThreadWorker();
+ }
+ while (m_bRespawn);
- while (ServerThreadWorker())
- ;
ShutdownSession();
}
-bool WhatsAppProto::ServerThreadWorker()
+void WhatsAppProto::ServerThreadWorker()
{
// connect websocket
NETLIBHTTPHEADER hdrs[] =
@@ -367,11 +33,11 @@ bool WhatsAppProto::ServerThreadWorker()
NLHR_PTR pReply(WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws/chat", hdrs));
if (pReply == nullptr) {
debugLogA("Server connection failed, exiting");
- return false;
+ return;
}
if (pReply->resultCode != 101)
- return false;
+ return;
delete m_noise;
m_noise = new WANoise(this);
@@ -380,17 +46,15 @@ bool WhatsAppProto::ServerThreadWorker()
debugLogA("Server connection succeeded");
m_hServerConn = pReply->nlc;
m_iLoginTime = time(0);
- m_szClientToken = getMStringA(DBKEY_CLIENT_TOKEN);
auto &pubKey = m_noise->ephemeral.pub;
auto *client = new proto::HandshakeMessage::ClientHello(); client->set_ephemeral(pubKey.data(), pubKey.length());
proto::HandshakeMessage msg; msg.set_allocated_clienthello(client);
WSSend(msg);
- bool bExit = false;
MBinBuffer netbuf;
- while (!bExit && !m_bTerminated) {
+ for (m_bTerminated = false; !m_bTerminated;) {
unsigned char buf[2048];
int bufSize = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP);
if (bufSize == 0) {
@@ -452,7 +116,6 @@ bool WhatsAppProto::ServerThreadWorker()
if (pReq != nullptr) {
root << CHAR_PARAM("$id$", szPrefix);
}
- else ProcessPacket(root);
}
}
}
@@ -460,7 +123,7 @@ bool WhatsAppProto::ServerThreadWorker()
case 8: // close
debugLogA("server required to exit");
- bExit = true; // simply reconnect, don't exit
+ m_bRespawn = m_bTerminated = true; // simply reconnect, don't exit
break;
default:
@@ -492,7 +155,28 @@ bool WhatsAppProto::ServerThreadWorker()
debugLogA("Server connection dropped");
Netlib_CloseHandle(m_hServerConn);
m_hServerConn = nullptr;
- return false;
+}
+
+bool WhatsAppProto::WSReadPacket(const WSHeader &hdr, MBinBuffer &res)
+{
+ size_t currPacketSize = res.length() - hdr.headerSize;
+
+ char buf[1024];
+ while (currPacketSize < hdr.payloadSize) {
+ int result = Netlib_Recv(m_hServerConn, buf, _countof(buf), MSG_NODUMP);
+ if (result == 0) {
+ debugLogA("Gateway connection gracefully closed");
+ return false;
+ }
+ if (result < 0) {
+ debugLogA("Gateway connection error, exiting");
+ return false;
+ }
+
+ currPacketSize += result;
+ res.append(buf, result);
+ }
+ return true;
}
/////////////////////////////////////////////////////////////////////////////////////////
@@ -549,67 +233,60 @@ void WhatsAppProto::ProcessBinaryNode(const WANode &node)
}
/////////////////////////////////////////////////////////////////////////////////////////
-// Json data processing
-void WhatsAppProto::ProcessPacket(const JSONNode &root)
+void WhatsAppProto::OnLoggedIn()
{
- CMStringA szType = root[(size_t)0].as_mstring();
- const JSONNode &content = root[1];
-
- if (szType == "Conn")
- ProcessConn(content);
- else if (szType == "Cmd")
- ProcessCmd(content);
- else if (szType == "Presence")
- ProcessPresence(content);
- else if (szType == "Blocklist")
- ProcessBlocked(content);
-}
+ debugLogA("WhatsAppProto::OnLoggedIn");
-void WhatsAppProto::ProcessBlocked(const JSONNode &node)
-{
- for (auto &it : node["blocklist"]) {
- auto *pUser = AddUser(it.as_string().c_str(), false);
- Ignore_Ignore(pUser->hContact, IGNOREEVENT_ALL);
- Contact::Hide(pUser->hContact);
- }
-}
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
+ m_iStatus = m_iDesiredStatus;
-void WhatsAppProto::ProcessCmd(const JSONNode &root)
-{
- CMStringW wszType(root["type"].as_mstring());
- if (wszType == L"challenge") {
- }
+ SendKeepAlive();
+ m_impl.m_keepAlive.Start(60000);
}
-void WhatsAppProto::ProcessConn(const JSONNode &root)
+void WhatsAppProto::OnLoggedOut(void)
{
- CloseQrDialog();
-
- writeStr(DBKEY_ID, root["wid"]);
- m_szJid = getMStringA(DBKEY_ID);
+ debugLogA("WhatsAppProto::OnLoggedOut");
+ m_bTerminated = true;
- writeStr(DBKEY_NICK, root["pushname"]);
- writeStr(DBKEY_CLIENT_TOKEN, root["clientToken"]);
- writeStr(DBKEY_SERVER_TOKEN, root["serverToken"]);
- writeStr(DBKEY_BROWSER_TOKEN, root["browserToken"]);
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
- OnLoggedIn();
+ setAllContactStatuses(ID_STATUS_OFFLINE, false);
}
-void WhatsAppProto::ProcessPresence(const JSONNode &root)
+void WhatsAppProto::SendKeepAlive()
{
- CMStringA jid = root["id"].as_mstring();
- if (auto *pUser = FindUser(jid)) {
- CMStringA state(root["type"].as_mstring());
- DWORD timestamp(_wtoi(root["t"].as_mstring()));
- if (state == "available") {
- setWord(pUser->hContact, "Status", ID_STATUS_ONLINE);
+ WebSocket_SendText(m_hServerConn, "?,,");
+
+ time_t now = time(0);
+
+ for (auto &it : m_arUsers) {
+ if (it->m_time1 && now - it->m_time1 >= 1200) { // 20 minutes
+ setWord(it->hContact, "Status", ID_STATUS_NA);
+ it->m_time1 = 0;
+ it->m_time2 = now;
}
- else if (state == "unavailable") {
- setWord(pUser->hContact, "Status", ID_STATUS_AWAY);
- pUser->m_time1 = timestamp;
+ else if (it->m_time2 && now - it->m_time2 >= 1200) { // 20 minutes
+ setWord(it->hContact, "Status", ID_STATUS_OFFLINE);
+ it->m_time2 = 0;
}
}
- else debugLogA("Presence from unknown contact %s ignored", jid.c_str());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ShutdownSession()
+{
+ if (m_bTerminated)
+ return;
+
+ debugLogA("WhatsAppProto::ShutdownSession");
+
+ // shutdown all resources
+ if (m_hServerConn)
+ Netlib_Shutdown(m_hServerConn);
+
+ OnLoggedOut();
}
diff --git a/protocols/WhatsAppWeb/src/stdafx.cxx b/protocols/WhatsAppWeb/src/stdafx.cxx
index 46e6a71904..8784e33705 100644
--- a/protocols/WhatsAppWeb/src/stdafx.cxx
+++ b/protocols/WhatsAppWeb/src/stdafx.cxx
@@ -1,7 +1,7 @@
/*
WhatsAppWeb plugin for Miranda NG
-Copyright © 2019 George Hazan
+Copyright © 2019-22 George Hazan
*/
diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp
index 79e5c0217a..7e45d65ea2 100644
--- a/protocols/WhatsAppWeb/src/utils.cpp
+++ b/protocols/WhatsAppWeb/src/utils.cpp
@@ -1,12 +1,59 @@
/*
WhatsAppWeb plugin for Miranda NG
-Copyright © 2019 George Hazan
+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)
+{
+ 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 = 0;
+
+ if (p = strrchr(tmp, '_')) {
+ *p = 0;
+ agent = atoi(p + 1);
+ }
+ else agent = 0;
+
+ user = tmp;
+}
+
+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;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
WAUser* WhatsAppProto::FindUser(const char *szId)
{
mir_cslock lck(m_csUsers);
@@ -23,7 +70,7 @@ WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary)
MCONTACT hContact = db_add_contact();
Proto_AddToContact(hContact, m_szModuleName);
- setString(hContact, DBKEY_ID, szId);
+ setString(hContact, DBKEY_JID, szId);
pUser = new WAUser(hContact, mir_strdup(szId));
if (bTemporary)
Contact::RemoveFromList(hContact);
diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h
index 507c674c1f..e517301b86 100644
--- a/protocols/WhatsAppWeb/src/utils.h
+++ b/protocols/WhatsAppWeb/src/utils.h
@@ -109,6 +109,19 @@ public:
/////////////////////////////////////////////////////////////////////////////////////////
+struct WAJid
+{
+ int device, agent;
+ CMStringA user, server;
+
+ WAJid(const char *pszJid);
+ WAJid(const char *pszUser, const char *pszServer, int device = 0, int agent = 0);
+
+ CMStringA toString() const;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t));
void generateIV(uint8_t *iv, int &pVar);
diff --git a/protocols/WhatsAppWeb/src/wareader.cpp b/protocols/WhatsAppWeb/src/wanode.cpp
index 8a7ab3f99d..72d8bec38c 100644
--- a/protocols/WhatsAppWeb/src/wareader.cpp
+++ b/protocols/WhatsAppWeb/src/wanode.cpp
@@ -82,7 +82,7 @@ WANode* WANode::getChild(const char *pszName) const
return nullptr;
}
-WANode *WANode::getFirstChild(void) const
+WANode* WANode::getFirstChild(void) const
{
return (children.getCount()) ? &children[0] : nullptr;
}
@@ -345,8 +345,8 @@ CMStringA WAReader::readString(int tag)
{
int agent = readInt8();
int device = readInt8();
- CMStringA user = readString(readInt8());
- return user + "@c.us";
+ WAJid jid(readString(readInt8()), "s.whatsapp.net", device, agent);
+ return jid.toString();
}
case JID_PAIR: