summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb/src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-09-29 22:36:58 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-09-29 22:37:06 +0300
commitc67e7825ccd72b889b6b8448d17e206c88bb21ca (patch)
tree596150a7b7964a67d9bfb9892586cccfc234c60f /protocols/WhatsAppWeb/src
parent41177f3c026f3eacfc02bc9c94373750dab35326 (diff)
WhatsApp: persistent iq handlers + qr code dialog
Diffstat (limited to 'protocols/WhatsAppWeb/src')
-rw-r--r--protocols/WhatsAppWeb/src/proto.cpp4
-rw-r--r--protocols/WhatsAppWeb/src/proto.h21
-rw-r--r--protocols/WhatsAppWeb/src/qrcode.cpp9
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp103
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp50
-rw-r--r--protocols/WhatsAppWeb/src/utils.h6
6 files changed, 168 insertions, 25 deletions
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<WhatsAppProto>
mir_cs m_csPacketQueue;
OBJLIST<WARequest> m_arPacketQueue;
+ LIST<WAPersistentHandler> 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<WhatsAppProto>
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;