diff options
author | George Hazan <ghazan@miranda.im> | 2018-12-23 19:26:06 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2018-12-23 19:26:14 +0300 |
commit | 105158e0a4b2c23adb154e298aac9e10b665b18b (patch) | |
tree | c96270b623b3c6f21b142a98acfe5dabbed878e8 /protocols/Icq10 | |
parent | 8a93b6cd3c9e884bb5f15389441a71837eae5c80 (diff) |
Icq: implementation of new login
Diffstat (limited to 'protocols/Icq10')
-rw-r--r-- | protocols/Icq10/src/http.cpp | 164 | ||||
-rw-r--r-- | protocols/Icq10/src/http.h | 10 | ||||
-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.cpp | 189 | ||||
-rw-r--r-- | protocols/Icq10/src/stdafx.h | 9 |
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" |