From c67e7825ccd72b889b6b8448d17e206c88bb21ca Mon Sep 17 00:00:00 2001 From: George Hazan Date: Thu, 29 Sep 2022 22:36:58 +0300 Subject: WhatsApp: persistent iq handlers + qr code dialog --- protocols/WhatsAppWeb/src/proto.cpp | 4 ++ protocols/WhatsAppWeb/src/proto.h | 21 ++++++- protocols/WhatsAppWeb/src/qrcode.cpp | 9 ++- protocols/WhatsAppWeb/src/server.cpp | 103 +++++++++++++++++++++++++++++++---- protocols/WhatsAppWeb/src/utils.cpp | 50 +++++++++++++---- protocols/WhatsAppWeb/src/utils.h | 6 +- 6 files changed, 168 insertions(+), 25 deletions(-) (limited to 'protocols/WhatsAppWeb/src') diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index 65b449807b..c3d965e919 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -33,6 +33,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), m_arUsers(10, CompareUsers), m_arOwnMsgs(1, CompareOwnMsgs), + m_arPersistent(1), m_arPacketQueue(10), m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"), m_bHideGroupchats(this, "HideChats", true) @@ -48,6 +49,9 @@ 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()) { diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index 3383ee6306..828c912406 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -26,6 +26,18 @@ struct WARequest void *pUserInfo; }; +struct WAPersistentHandler +{ + WAPersistentHandler(const char *_1, const char *_2, const char *_3, WA_PKT_HANDLER _4) : + pszType(_1), pszXmlns(_2), pszNode(_3), pHandler(_4) + {} + + const char *pszType; + const char *pszXmlns; + const char *pszNode; + WA_PKT_HANDLER pHandler; +}; + struct WAHistoryMessage { CMStringA jid, text; @@ -166,8 +178,11 @@ class WhatsAppProto : public PROTO mir_cs m_csPacketQueue; OBJLIST m_arPacketQueue; + LIST m_arPersistent; + WA_PKT_HANDLER FindPersistentHandler(const WANode &node); + bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf); - int WSSend(const MessageLite &msg, WA_PKT_HANDLER = nullptr, void *pUserIndo = nullptr); + int WSSend(const MessageLite &msg); int WSSendNode(WANode &node, WA_PKT_HANDLER = nullptr); void OnLoggedIn(void); @@ -185,10 +200,12 @@ class WhatsAppProto : public PROTO void OnProcessHandshake(const void *pData, int cbLen); - void OnStartSession(const WANode &node); + void OnIqPairDevice(const WANode &node); + void OnIqPairSuccess(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); diff --git a/protocols/WhatsAppWeb/src/qrcode.cpp b/protocols/WhatsAppWeb/src/qrcode.cpp index 7d8f1e187f..46b71700c5 100644 --- a/protocols/WhatsAppWeb/src/qrcode.cpp +++ b/protocols/WhatsAppWeb/src/qrcode.cpp @@ -113,8 +113,13 @@ bool WhatsAppProto::ShowQrCode(const CMStringA &ref) { CallFunctionSync(sttShowDialog, this); - auto &pubKey = m_noise->noiseKeys.pub; - CMStringA szQrData(FORMAT, "%s,%s,%s", ref.c_str(), ptrA(mir_base64_encode(pubKey.data(), pubKey.length())).get(), m_szClientId.c_str()); + MBinBuffer secret; + getBlob(DBKEY_SECRET_KEY, secret); + + ptrA s1(mir_base64_encode(m_noise->noiseKeys.pub)); + ptrA s2(mir_base64_encode(m_noise->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/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 192e203521..1139aed2e5 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -68,11 +68,83 @@ void WhatsAppProto::ShutdownSession() ///////////////////////////////////////////////////////////////////////////////////////// -void WhatsAppProto::OnStartSession(const WANode &node) +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.children.front()->getChild("ref")) { + ShowQrCode(pRef->getBody()); + } + else { + debugLogA("OnIqPairDevice: got reply without ref, exiting"); + ShutdownSession(); + } +} + +void WhatsAppProto::OnIqPairSuccess(const WANode &node) +{ + CloseQrDialog(); + + auto *pRoot = node.children.front(); + + 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(), 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); + + 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 accountMsg; + accountMsg.append("\x06\x00", 2); + accountMsg.append(deviceDetails.c_str(), deviceDetails.size()); + accountMsg.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length()); + + // Curve.verify(accountSignatureKey, accountMsg, accountSignature) + debugLogA("Received account signature"); + Netlib_Dump(nullptr, accountSignatureKey.c_str(), accountSignatureKey.size(), false, 0); + Netlib_Dump(nullptr, accountMsg.data(), accountMsg.length(), false, 0); + Netlib_Dump(nullptr, accountSignature.c_str(), accountSignature.size(), false, 0); + + MBinBuffer deviceMsg; + deviceMsg.append("\x06\x01", 2); + deviceMsg.append(deviceDetails.c_str(), deviceDetails.size()); + deviceMsg.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length()); + deviceMsg.append(accountSignatureKey.c_str(), accountSignatureKey.size()); + } + else throw "OnIqPairSuccess: got reply without identity, exiting"; + } + catch (const char *pErrMsg) { + debugLogA(pErrMsg); + ShutdownSession(); + } } ///////////////////////////////////////////////////////////////////////////////////////// @@ -195,7 +267,7 @@ LBL_Error: proto::HandshakeMessage handshake; handshake.set_allocated_clientfinish(pFinish); - WSSend(handshake, &WhatsAppProto::OnStartSession); + WSSend(handshake); m_noise->finish(); } @@ -395,14 +467,8 @@ void WhatsAppProto::ProcessBinaryPacket(const void *pData, size_t cbDataLen) pNode->print(szText); debugLogA("Got binary node:\n%s", szText.c_str()); - if (m_arPacketQueue.getCount()) { - WARequest req = m_arPacketQueue[0]; - m_arPacketQueue.remove(0); - - (this->*req.pHandler)(*pNode); - delete pNode; - } - else debugLogA("cannot handle incoming message"); + ProcessBinaryNode(*pNode); + delete pNode; } else { debugLogA("wrong or broken payload"); @@ -416,6 +482,23 @@ void WhatsAppProto::ProcessBinaryPacket(const void *pData, size_t cbDataLen) } } +void WhatsAppProto::ProcessBinaryNode(const WANode &node) +{ + if (m_arPacketQueue.getCount()) { + WARequest req = m_arPacketQueue[0]; + m_arPacketQueue.remove(0); + + (this->*req.pHandler)(node); + return; + } + + auto pHandler = FindPersistentHandler(node); + if (pHandler) + (this->*pHandler)(node); + else + debugLogA("cannot handle incoming message"); +} + ///////////////////////////////////////////////////////////////////////////////////////// // Json data processing diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index 85c545273a..ad4e2d1e7e 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -37,6 +37,27 @@ WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary) ///////////////////////////////////////////////////////////////////////////////////////// +WA_PKT_HANDLER WhatsAppProto::FindPersistentHandler(const WANode &pNode) +{ + CMStringA szTitle = (pNode.children.size() > 0) ? pNode.children.front()->title : ""; + CMStringA szType = pNode.title; + CMStringA szXmlns = pNode.getAttr("xmlns"); + + for (auto &it : m_arPersistent) { + if (it->pszType && szType != it->pszType) + continue; + if (it->pszXmlns && szXmlns != it->pszXmlns) + continue; + if (it->pszNode && szTitle != it->pszNode) + continue; + return it->pHandler; + } + + return nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////// + bool WhatsAppProto::getBlob(const char *szSetting, MBinBuffer &buf) { DBVARIANT dbv = { DBVT_BLOB }; @@ -51,18 +72,13 @@ bool WhatsAppProto::getBlob(const char *szSetting, MBinBuffer &buf) ///////////////////////////////////////////////////////////////////////////////////////// // sends a piece of JSON to a server via a websocket, masked -int WhatsAppProto::WSSend(const MessageLite &msg, WA_PKT_HANDLER pHandler, void *pUserInfo) +int WhatsAppProto::WSSend(const MessageLite &msg) { if (m_hServerConn == nullptr) return -1; // debugLogA("Sending packet: %s", msg..DebugString().c_str()); - if (pHandler != nullptr) { - mir_cslock lck(m_csPacketQueue); - m_arPacketQueue.insert(new WARequest(pHandler, pUserInfo)); - } - int cbLen = msg.ByteSize(); ptrA protoBuf((char *)mir_alloc(cbLen)); msg.SerializeToArray(protoBuf, cbLen); @@ -118,13 +134,13 @@ WANode::~WANode() delete p; } -CMStringA WANode::getAttr(const char *pszFieldName) const +const char* WANode::getAttr(const char *pszName) const { for (auto &p: attrs) - if (p->name == pszFieldName) - return p->value; + if (p->name == pszName) + return p->value.c_str(); - return ""; + return nullptr; } void WANode::addAttr(const char *pszName, const char *pszValue) @@ -132,6 +148,20 @@ void WANode::addAttr(const char *pszName, const char *pszValue) attrs.push_back(new Attr(pszName, pszValue)); } +CMStringA WANode::getBody() const +{ + return CMStringA((char *)content.data(), (int)content.length()); +} + +WANode* WANode::getChild(const char *pszName) const +{ + for (auto &it : children) + if (it->title == pszName) + return it; + + return nullptr; +} + void WANode::print(CMStringA &dest, int level) const { for (int i = 0; i < level; i++) diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h index dfa98705c6..21a5bd5efe 100644 --- a/protocols/WhatsAppWeb/src/utils.h +++ b/protocols/WhatsAppWeb/src/utils.h @@ -45,7 +45,11 @@ public: ~WANode(); void addAttr(const char *pszName, const char *pszValue); - CMStringA getAttr(const char *pszFieldValue) const; + const char* getAttr(const char *pszName) const; + + CMStringA getBody() const; + + WANode* getChild(const char *pszName) const; void print(CMStringA &dest, int level = 0) const; -- cgit v1.2.3