summaryrefslogtreecommitdiff
path: root/protocols/Icq10
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2018-12-23 19:26:06 +0300
committerGeorge Hazan <ghazan@miranda.im>2018-12-23 19:26:14 +0300
commit105158e0a4b2c23adb154e298aac9e10b665b18b (patch)
treec96270b623b3c6f21b142a98acfe5dabbed878e8 /protocols/Icq10
parent8a93b6cd3c9e884bb5f15389441a71837eae5c80 (diff)
Icq: implementation of new login
Diffstat (limited to 'protocols/Icq10')
-rw-r--r--protocols/Icq10/src/http.cpp164
-rw-r--r--protocols/Icq10/src/http.h10
-rw-r--r--protocols/Icq10/src/proto.cpp (renamed from protocols/Icq10/src/icq_proto.cpp)43
-rw-r--r--protocols/Icq10/src/proto.h (renamed from protocols/Icq10/src/icq_proto.h)40
-rw-r--r--protocols/Icq10/src/server.cpp189
-rw-r--r--protocols/Icq10/src/stdafx.h9
6 files changed, 448 insertions, 7 deletions
diff --git a/protocols/Icq10/src/http.cpp b/protocols/Icq10/src/http.cpp
new file mode 100644
index 0000000000..a65f0bc3c6
--- /dev/null
+++ b/protocols/Icq10/src/http.cpp
@@ -0,0 +1,164 @@
+// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018 Miranda NG team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+#pragma comment(lib, "Rpcrt4.lib")
+
+void __cdecl CIcqProto::ServerThread(void*)
+{
+ m_hAPIConnection = nullptr;
+ m_bTerminated = false;
+
+ int uin = getDword("UIN");
+ ptrA szPassword(getStringA("Password"));
+ if (uin == 0 || szPassword == nullptr) {
+ debugLogA("Thread ended, UIN/password are not configured");
+ ConnectionFailed(LOGINERR_BADUSERID);
+ return;
+ }
+
+ debugLogA("CIcqProto::WorkerThread: %s", "entering");
+ {
+ char mirVer[100];
+ Miranda_GetVersionText(mirVer, _countof(mirVer));
+
+ auto *pReq = new AsyncHttpRequest(REQUEST_POST, "https://api.login.icq.net/auth/clientLogin", &CIcqProto::OnCheckPassword);
+ pReq << CHAR_PARAM("clientName", "Miranda NG") << CHAR_PARAM("clientVersion", mirVer) << CHAR_PARAM("devId", "ic1nmMjqg7Yu-0hL")
+ << CHAR_PARAM("f", "json") << CHAR_PARAM("tokenType", "longTerm") << INT_PARAM("s", uin) << CHAR_PARAM("pwd", szPassword);
+ pReq->flags |= NLHRF_NODUMPSEND;
+ Push(pReq);
+ }
+
+ while (true) {
+ WaitForSingleObject(m_evRequestsQueue, 1000);
+ if (m_bTerminated)
+ break;
+
+ AsyncHttpRequest *pReq;
+ bool need_sleep = false;
+ while (true) {
+ {
+ mir_cslock lck(m_csHttpQueue);
+ if (m_arHttpQueue.getCount() == 0)
+ break;
+
+ pReq = m_arHttpQueue[0];
+ m_arHttpQueue.remove(0);
+ need_sleep = (m_arHttpQueue.getCount() > 1);
+ }
+ if (m_bTerminated)
+ break;
+ ExecuteRequest(pReq);
+ if (need_sleep) {
+ Sleep(330);
+ debugLogA("CIcqProto::WorkerThread: %s", "need to sleep");
+ }
+ }
+ }
+
+ m_hWorkerThread = nullptr;
+ if (m_hAPIConnection) {
+ Netlib_CloseHandle(m_hAPIConnection);
+ m_hAPIConnection = nullptr;
+ }
+
+ debugLogA("CIcqProto::WorkerThread: %s", "leaving");
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest::AsyncHttpRequest(int iType, const char *szUrl, MTHttpRequestHandler pFunc)
+{
+ flags = NLHRF_HTTP11 | NLHRF_SSL;
+ requestType = iType;
+ m_szUrl = szUrl;
+ m_pFunc = pFunc;
+ UuidCreate(&m_reqId);
+
+ if (iType == REQUEST_POST) {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ dataLength = m_szParam.GetLength();
+ pData = m_szParam.Detach();
+ }
+}
+
+void CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq)
+{
+ CMStringA str;
+
+ pReq->szUrl = pReq->m_szUrl.GetBuffer();
+ if (!pReq->m_szParam.IsEmpty()) {
+ if (pReq->requestType == REQUEST_GET) {
+ str.Format("%s?%s", pReq->m_szUrl.c_str(), pReq->m_szParam.c_str());
+ pReq->szUrl = str.GetBuffer();
+ }
+ else {
+ pReq->pData = mir_strdup(pReq->m_szParam);
+ pReq->dataLength = pReq->m_szParam.GetLength();
+ }
+ }
+
+ if (pReq->m_bMainSite) {
+ pReq->flags |= NLHRF_PERSISTENT;
+ pReq->nlc = m_hAPIConnection;
+ }
+
+ RPC_CSTR szId;
+ UuidToStringA(&pReq->m_reqId, &szId);
+ debugLogA("Executing request %s:\n%s", (char*)szId, pReq->szUrl);
+
+ NETLIBHTTPREQUEST *reply = Netlib_HttpTransaction(m_hNetlibUser, pReq);
+ if (reply != nullptr) {
+ if (pReq->m_pFunc != nullptr)
+ (this->*(pReq->m_pFunc))(reply, pReq);
+
+ if (pReq->m_bMainSite)
+ m_hAPIConnection = reply->nlc;
+
+ Netlib_FreeHttpRequest(reply);
+ }
+ else {
+ debugLogA("Request %s failed", (char*)szId);
+
+ if (pReq->m_bMainSite) {
+ if (IsStatusConnecting(m_iStatus))
+ ConnectionFailed(LOGINERR_NONETWORK);
+ m_hAPIConnection = nullptr;
+ }
+ }
+
+ RpcStringFreeA(&szId);
+ delete pReq;
+}
+
+void CIcqProto::Push(MHttpRequest *p)
+{
+ AsyncHttpRequest *pReq = (AsyncHttpRequest*)p;
+
+ pReq->timeout = 10000;
+ {
+ mir_cslock lck(m_csHttpQueue);
+ m_arHttpQueue.insert(pReq);
+ }
+
+ SetEvent(m_evRequestsQueue);
+}
diff --git a/protocols/Icq10/src/http.h b/protocols/Icq10/src/http.h
new file mode 100644
index 0000000000..3c5285e2ad
--- /dev/null
+++ b/protocols/Icq10/src/http.h
@@ -0,0 +1,10 @@
+
+class CIcqProto;
+
+struct AsyncHttpRequest : public MTHttpRequest<CIcqProto>
+{
+ bool m_bMainSite = true;
+ GUID m_reqId;
+
+ AsyncHttpRequest(int type, const char *szUrl, MTHttpRequestHandler pFunc = nullptr);
+};
diff --git a/protocols/Icq10/src/icq_proto.cpp b/protocols/Icq10/src/proto.cpp
index abb02d21d6..e9bb0ce0ea 100644
--- a/protocols/Icq10/src/icq_proto.cpp
+++ b/protocols/Icq10/src/proto.cpp
@@ -34,12 +34,23 @@
#pragma warning(disable:4355)
CIcqProto::CIcqProto(const char* aProtoName, const wchar_t* aUserName) :
- PROTO<CIcqProto>(aProtoName, aUserName)
+ PROTO<CIcqProto>(aProtoName, aUserName),
+ m_arHttpQueue(10),
+ m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr))
{
+ CMStringW descr(FORMAT, TranslateT("%s server connection"), m_tszUserName);
+
+ NETLIBUSER nlu = {};
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szDescriptiveName.w = descr.GetBuffer();
+ m_hNetlibUser = Netlib_RegisterUser(&nlu);
}
CIcqProto::~CIcqProto()
{
+ m_arHttpQueue.destroy();
+ ::CloseHandle(m_evRequestsQueue);
}
////////////////////////////////////////////////////////////////////////////////////////
@@ -266,6 +277,36 @@ int CIcqProto::SendUrl(MCONTACT hContact, int, const char* url)
int CIcqProto::SetStatus(int iNewStatus)
{
+ debugLogA("CIcqProto::SetStatus iNewStatus = %d, m_iStatus = %d, m_iDesiredStatus = %d m_hWorkerThread = %p", iNewStatus, m_iStatus, m_iDesiredStatus, m_hWorkerThread);
+
+ if (iNewStatus == m_iStatus)
+ return 0;
+
+ m_iDesiredStatus = iNewStatus;
+ int iOldStatus = m_iStatus;
+
+ // go offline
+ if (iNewStatus == ID_STATUS_OFFLINE) {
+ if (m_bOnline) {
+ SetServerStatus(ID_STATUS_OFFLINE);
+ ShutdownSession();
+ }
+ m_iStatus = m_iDesiredStatus;
+ setAllContactStatuses(ID_STATUS_OFFLINE, false);
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+ }
+ // not logged in? come on
+ else if (m_hWorkerThread == nullptr && !IsStatusConnecting(m_iStatus)) {
+ m_iStatus = ID_STATUS_CONNECTING;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+ m_hWorkerThread = ForkThreadEx(&CIcqProto::ServerThread, nullptr, nullptr);
+ }
+ else if (m_bOnline) {
+ debugLogA("setting server online status to %d", iNewStatus);
+ SetServerStatus(iNewStatus);
+ }
+
return 0;
}
diff --git a/protocols/Icq10/src/icq_proto.h b/protocols/Icq10/src/proto.h
index 3e3d56accf..45fb20f845 100644
--- a/protocols/Icq10/src/icq_proto.h
+++ b/protocols/Icq10/src/proto.h
@@ -33,14 +33,40 @@
#include "m_system.h"
#include "m_protoint.h"
-struct CIcqProto : public PROTO<CIcqProto>
+class CIcqProto : public PROTO<CIcqProto>
{
- CIcqProto(const char*, const wchar_t*);
- ~CIcqProto();
+ bool m_bOnline = false, m_bTerminated = false;
+ void ConnectionFailed(int iReason);
+ void OnLoggedIn(void);
+ void OnLoggedOut(void);
+ void SetServerStatus(int iNewStatus);
+ void ShutdownSession(void);
- //====================================================================================
+ void OnCheckPassword(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+ void OnStartSession(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+
+ HNETLIBCONN m_hAPIConnection;
+ CMStringA m_szSessionSecret;
+ CMStringA m_szAToken;
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // http queue
+
+ mir_cs m_csHttpQueue;
+ HANDLE m_evRequestsQueue;
+ LIST<AsyncHttpRequest> m_arHttpQueue;
+
+ void ExecuteRequest(AsyncHttpRequest*);
+ void Push(MHttpRequest*);
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // threads
+
+ HANDLE m_hWorkerThread;
+ void __cdecl ServerThread(void*);
+
+ //////////////////////////////////////////////////////////////////////////////////////
// PROTO_INTERFACE
- //====================================================================================
MCONTACT AddToList( int flags, PROTOSEARCHRESULT *psr) override;
MCONTACT AddToListByEvent( int flags, int iContact, MEVENT hDbEvent) override;
@@ -83,6 +109,10 @@ struct CIcqProto : public PROTO<CIcqProto>
void OnModulesLoaded() override;
void OnShutdown() override;
+
+public:
+ CIcqProto(const char*, const wchar_t*);
+ ~CIcqProto();
};
struct CMPlugin : public ACCPROTOPLUGIN<CIcqProto>
diff --git a/protocols/Icq10/src/server.cpp b/protocols/Icq10/src/server.cpp
new file mode 100644
index 0000000000..7668c154f6
--- /dev/null
+++ b/protocols/Icq10/src/server.cpp
@@ -0,0 +1,189 @@
+// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018 Miranda NG team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+void CIcqProto::ConnectionFailed(int iReason)
+{
+ debugLogA("ConnectionFailed -> reason %d", iReason);
+
+ ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, nullptr, iReason);
+ ShutdownSession();
+}
+
+void CIcqProto::OnLoggedIn()
+{
+ debugLogA("CIcqProto::OnLoggedIn");
+ m_bOnline = true;
+ SetServerStatus(m_iDesiredStatus);
+}
+
+void CIcqProto::OnLoggedOut()
+{
+ debugLogA("CIcqProto::OnLoggedOut");
+ m_bOnline = false;
+ 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 CIcqProto::SetServerStatus(int iStatus)
+{
+ int iOldStatus = m_iStatus; m_iStatus = iStatus;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+}
+
+void CIcqProto::ShutdownSession()
+{
+ if (m_bTerminated)
+ return;
+
+ debugLogA("CIcqProto::ShutdownSession");
+
+ // shutdown all resources
+ if (m_hWorkerThread)
+ SetEvent(m_evRequestsQueue);
+ if (m_hAPIConnection)
+ Netlib_Shutdown(m_hAPIConnection);
+
+ OnLoggedOut();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define CAPS "094613504c7f11d18222444553540000,094613514c7f11d18222444553540000,094613534c7f11d18222444553540000,094613544c7f11d18222444553540000,094613594c7f11d18222444553540000,0946135b4c7f11d18222444553540000,0946135a4c7f11d18222444553540000"
+#define EVENTS "myInfo,presence,buddylist,typing,dataIM,userAddedToBuddyList,service,webrtcMsg,mchat,hist,hiddenChat,diff,permitDeny,imState,notification,apps"
+#define FIELDS "aimId,buddyIcon,bigBuddyIcon,iconId,bigIconId,largeIconId,displayId,friendly,offlineMsg,state,statusMsg,userType,phoneNumber,cellNumber,smsNumber,workNumber,otherNumber,capabilities,ssl,abPhoneNumber,moodIcon,lastName,abPhones,abContactName,lastseen,mute,livechat,official"
+
+void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ if (pReply->resultCode != 200 || pReply->pData == nullptr) {
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ return;
+ }
+
+ JSONROOT root(pReply->pData);
+ if (!root) {
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ return;
+ }
+
+ JSONNode response = (*root)["response"];
+ switch (response["statusCode"].as_int()) {
+ case 200:
+ {
+ JSONNode data = response["data"];
+ m_szAToken = data["token"]["a"].as_mstring();
+ m_szSessionSecret = data["sessionSecret"].as_mstring();
+
+ CMStringA szUin = data["loginId"].as_mstring();
+ if (szUin)
+ setDword("UIN", atoi(szUin));
+ }
+ break;
+
+ case 440:
+ ConnectionFailed(LOGINERR_WRONGPASSWORD);
+ return;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ return;
+ }
+
+ ptrA szDeviceId(getStringA("DeviceId"));
+ if (szDeviceId == nullptr) {
+ UUID deviceId;
+ UuidCreate(&deviceId);
+ RPC_CSTR szId;
+ UuidToStringA(&deviceId, &szId);
+ szDeviceId = mir_strdup((char*)szId);
+ setString("DeviceId", szDeviceId);
+ RpcStringFreeA(&szId);
+ }
+
+ int ts = time(0);
+ CMStringA nonce(FORMAT, "%d-2", ts);
+
+ auto *pReq = new AsyncHttpRequest(REQUEST_POST, "https://api.icq.net/aim/startSession", &CIcqProto::OnStartSession);
+
+ RPC_CSTR szId;
+ UuidToStringA(&pReq->m_reqId, &szId);
+
+ pReq << CHAR_PARAM("a", m_szAToken) << INT_PARAM("activeTimeout", 180) << CHAR_PARAM("assertCaps", CAPS)
+ << INT_PARAM("buildNumber", __BUILD_NUM) << CHAR_PARAM("clientName", "Miranda NG") << INT_PARAM("clientVersion", 5000)
+ << CHAR_PARAM("deviceId", szDeviceId) << CHAR_PARAM("events", EVENTS) << CHAR_PARAM("f", "json") << CHAR_PARAM("imf", "plain")
+ << CHAR_PARAM("inactiveView", "offline") << CHAR_PARAM("includePresenceFields", FIELDS) << CHAR_PARAM("invisible", "false")
+ << CHAR_PARAM("k", "ic1nmMjqg7Yu-0hL") << INT_PARAM("majorVersion", __MAJOR_VERSION) << INT_PARAM("minorVersion", __MINOR_VERSION)
+ << INT_PARAM("mobile", 0) << CHAR_PARAM("nonce", nonce) << INT_PARAM("pointVersion", 0) << CHAR_PARAM("r", (char*)szId)
+ << INT_PARAM("rawMsg", 0) << INT_PARAM("sessionTimeout", 7776000) << INT_PARAM("ts", ts) << CHAR_PARAM("view", "online");
+
+ BYTE hashOut[MIR_SHA256_HASH_SIZE];
+ ptrA szPassword(getStringA("Password"));
+ mir_hmac_sha256(hashOut, (BYTE*)szPassword.get(), mir_strlen(szPassword), (BYTE*)m_szSessionSecret.c_str(), m_szSessionSecret.GetLength());
+ ptrA szSessionKey(mir_base64_encode(hashOut, sizeof(hashOut)));
+
+ CMStringA hashData(FORMAT, "POST&%s&%s", ptrA(mir_urlEncode(pReq->szUrl)), ptrA(mir_urlEncode(pReq->m_szParam)));
+ mir_hmac_sha256(hashOut, (BYTE*)szSessionKey.get(), mir_strlen(szSessionKey), (BYTE*)hashData.c_str(), hashData.GetLength());
+
+ pReq->m_szParam.Empty();
+ pReq << CHAR_PARAM("a", m_szAToken) << INT_PARAM("activeTimeout", 180) << CHAR_PARAM("assertCaps", CAPS)
+ << INT_PARAM("buildNumber", __BUILD_NUM) << CHAR_PARAM("clientName", "Miranda NG") << INT_PARAM("clientVersion", 5000)
+ << CHAR_PARAM("deviceId", szDeviceId) << CHAR_PARAM("events", EVENTS) << CHAR_PARAM("f", "json") << CHAR_PARAM("imf", "plain")
+ << CHAR_PARAM("inactiveView", "offline") << CHAR_PARAM("includePresenceFields", FIELDS) << CHAR_PARAM("invisible", "false")
+ << CHAR_PARAM("k", "ic1nmMjqg7Yu-0hL") << INT_PARAM("majorVersion", __MAJOR_VERSION) << INT_PARAM("minorVersion", __MINOR_VERSION)
+ << INT_PARAM("mobile", 0) << CHAR_PARAM("nonce", nonce) << INT_PARAM("pointVersion", 0) << CHAR_PARAM("r", (char*)szId)
+ << INT_PARAM("rawMsg", 0) << INT_PARAM("sessionTimeout", 7776000) << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut))))
+ << INT_PARAM("ts", ts) << CHAR_PARAM("view", "online");
+
+ Push(pReq);
+ RpcStringFreeA(&szId);
+}
+
+void CIcqProto::OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ if (pReply->resultCode != 200 || pReply->pData == nullptr) {
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ return;
+ }
+
+ JSONROOT root(pReply->pData);
+ if (!root) {
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ return;
+ }
+
+ JSONNode response = (*root)["response"];
+ switch (response["statusCode"].as_int()) {
+ case 200:
+ OnLoggedIn();
+ break;
+
+ case 401:
+ ConnectionFailed(LOGINERR_WRONGPASSWORD);
+ break;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ }
+}
diff --git a/protocols/Icq10/src/stdafx.h b/protocols/Icq10/src/stdafx.h
index 3191bcf750..078ef4fe19 100644
--- a/protocols/Icq10/src/stdafx.h
+++ b/protocols/Icq10/src/stdafx.h
@@ -58,12 +58,18 @@
#include <m_skin.h>
#include <m_popup.h>
#include <m_ignore.h>
+#include <m_json.h>
#include <m_icolib.h>
#include <m_avatars.h>
#include <m_timezones.h>
#include <win2k.h>
#include <m_gui.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
// Project resources
#include "resource.h"
@@ -72,4 +78,5 @@
#define MODULENAME "ICQ"
-#include "icq_proto.h"
+#include "http.h"
+#include "proto.h"