diff options
| author | George Hazan <ghazan@miranda.im> | 2022-10-01 14:20:05 +0300 | 
|---|---|---|
| committer | George Hazan <ghazan@miranda.im> | 2022-10-01 14:20:05 +0300 | 
| commit | 87ff6ba67d4df0b4c0ae3db86220b816ec6a1e75 (patch) | |
| tree | 9927b65a05688d70cf1fef5ef52a7fe956138de2 /protocols | |
| parent | 6de31630215f88f3639efb834be02e5af9bfce57 (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
Diffstat (limited to 'protocols')
| -rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj | 3 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters | 5 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/avatars.cpp | 4 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/chats.cpp | 2 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/db.h | 10 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/dicts.h | 7 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/iq.cpp | 308 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/main.cpp | 2 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/noise.cpp | 3 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 21 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 15 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 469 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.cxx | 2 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 51 | ||||
| -rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 13 | ||||
| -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: | 
