/*
Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
Copyright (c) 2012-2018 Miranda NG team
Copyright (c) 2006-2012 Boris Krasnovskiy.
Copyright (c) 2003-2005 George Hazan.
Copyright (c) 2002-2003 Richard Hughes (original version).
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, see .
*/
#include "stdafx.h"
#include "msn_proto.h"
/////////////////////////////////////////////////////////////////////////////////////////
// Keep-alive thread for the main connection
void __cdecl CMsnProto::msn_keepAliveThread(void*)
{
bool keepFlag = true;
hKeepAliveThreadEvt = CreateEvent(nullptr, FALSE, FALSE, nullptr);
msnPingTimeout = 45;
while (keepFlag) {
switch (WaitForSingleObject(hKeepAliveThreadEvt, msnPingTimeout * 1000)) {
case WAIT_TIMEOUT:
keepFlag = msnNsThread != nullptr;
if (usingGateway)
msnPingTimeout = 45;
else {
msnPingTimeout = 20;
if (msnNsThread) {
if (lastMsgId)
keepFlag = msnNsThread->sendPacketPayload("PNG", "CON", "\bLast-Msg-Id: %I64u\r\n\r\n", lastMsgId);
else if (msnRegistration)
keepFlag = msnNsThread->sendPacketPayload("PNG", "CON", "\b\r\n");
else
keepFlag = msnNsThread->sendPacket("PNG", "CON 0");
}
}
if (hHttpsConnection && (clock() - mHttpsTS) > 60 * CLOCKS_PER_SEC) {
HNETLIBCONN hConn = hHttpsConnection;
hHttpsConnection = nullptr;
Netlib_Shutdown(hConn);
}
if (mStatusMsgTS && (clock() - mStatusMsgTS) > 60 * CLOCKS_PER_SEC) {
mStatusMsgTS = 0;
ForkThread(&CMsnProto::msn_storeProfileThread, nullptr);
}
if (keepFlag && MyOptions.netId != NETID_SKYPE && MSN_RefreshOAuthTokens(true))
ForkThread(&CMsnProto::msn_refreshOAuthThread, msnNsThread);
break;
case WAIT_OBJECT_0:
keepFlag = msnPingTimeout > 0;
break;
default:
keepFlag = false;
break;
}
}
CloseHandle(hKeepAliveThreadEvt); hKeepAliveThreadEvt = nullptr;
debugLogA("Closing keep-alive thread");
}
void __cdecl CMsnProto::msn_loginThread(void*)
{
MSN_RefreshContactList();
MSN_FetchRecentMessages();
}
void __cdecl CMsnProto::msn_refreshOAuthThread(void *param)
{
if (MSN_RefreshOAuthTokens(false) > 0) {
bIgnoreATH = true;
MSN_SendATH((ThreadData*)param);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// MSN server thread - read and process commands from a server
static bool ReallocInfoBuffer(ThreadData *info, size_t mDataSize)
{
char *mData = (char*)mir_realloc(info->mData, mDataSize + 1);
if (mData == nullptr)
return false;
info->mData = mData;
info->mDataSize = mDataSize;
ZeroMemory(&mData[info->mBytesInData], info->mDataSize - info->mBytesInData + 1);
return true;
}
void __cdecl CMsnProto::MSNServerThread(void* arg)
{
ThreadData* info = (ThreadData*)arg;
if (info->mIsMainThread)
isConnectSuccess = false;
int tPortNumber = -1;
{
char* tPortDelim = strrchr(info->mServer, ':');
if (tPortDelim != nullptr) {
*tPortDelim = '\0';
if ((tPortNumber = atoi(tPortDelim + 1)) == 0)
tPortNumber = -1;
else if (usingGateway && !(tPortNumber == 80 || tPortNumber == 443))
usingGateway = false;
}
}
if (usingGateway) {
if (info->mServer[0] == 0)
mir_strcpy(info->mServer, MSN_DEFAULT_LOGIN_SERVER);
else if (info->mIsMainThread)
mir_strcpy(info->mGatewayIP, info->mServer);
if (info->gatewayType)
mir_strcpy(info->mGatewayIP, info->mServer);
else {
if (info->mGatewayIP[0] == 0 && db_get_static(NULL, m_szModuleName, "GatewayServer", info->mGatewayIP, sizeof(info->mGatewayIP)))
mir_strcpy(info->mGatewayIP, MSN_DEFAULT_GATEWAY);
}
}
else {
if (info->mServer[0] == 0 && db_get_static(NULL, m_szModuleName, "DirectServer", info->mServer, sizeof(info->mServer)))
mir_strcpy(info->mServer, MSN_DEFAULT_LOGIN_SERVER);
}
NETLIBOPENCONNECTION tConn = { 0 };
tConn.cbSize = sizeof(tConn);
tConn.flags = NLOCF_V2;
tConn.timeout = 5;
if (usingGateway) {
tConn.flags |= NLOCF_HTTPGATEWAY;
tConn.szHost = info->mGatewayIP;
tConn.wPort = MSN_DEFAULT_GATEWAY_PORT;
}
else {
tConn.flags = NLOCF_SSL;
tConn.szHost = info->mServer;
tConn.wPort = MSN_DEFAULT_PORT;
}
if (tPortNumber != -1)
tConn.wPort = (WORD)tPortNumber;
debugLogA("Thread started: server='%s:%d', type=%d", tConn.szHost, tConn.wPort, info->mType);
info->s = Netlib_OpenConnection(m_hNetlibUser, &tConn);
if (info->s == nullptr) {
debugLogA("Connection Failed (%d) server='%s:%d'", WSAGetLastError(), tConn.szHost, tConn.wPort);
switch (info->mType) {
case SERVER_NOTIFICATION:
goto LBL_Exit;
break;
}
return;
}
if (usingGateway)
Netlib_SetPollingTimeout(info->s, info->mGatewayTimeout);
debugLogA("Connected with handle=%08X", info->s);
if (info->mType == SERVER_NOTIFICATION)
info->sendPacketPayload("CNT", "CON", "%s%s%s2winnt5.2x86en-us\r\n",
*info->mState ? "" : "", *info->mState ? info->mState : "", *info->mState ? "" : "");
if (info->mIsMainThread)
msnNsThread = info;
debugLogA("Entering main recv loop");
info->mBytesInData = 0;
for (;;) {
int recvResult = info->recv(info->mData + info->mBytesInData, info->mDataSize - info->mBytesInData);
if (recvResult == SOCKET_ERROR) {
debugLogA("Connection %08p [%08X] was abortively closed", info->s, GetCurrentThreadId());
break;
}
if (!recvResult) {
debugLogA("Connection %08p [%08X] was gracefully closed", info->s, GetCurrentThreadId());
break;
}
info->mBytesInData += recvResult;
for (;;) {
char* peol = strchr(info->mData, '\r');
if (peol == nullptr)
break;
int msgLen = (int)(peol - info->mData);
if (info->mBytesInData < msgLen + 2)
break; //wait for full line end
char msg[1024];
strncpy_s(msg, info->mData, msgLen);
if (*++peol != '\n')
debugLogA("Dodgy line ending to command: ignoring");
else
peol++;
info->mBytesInData -= peol - info->mData;
memmove(info->mData, peol, info->mBytesInData);
debugLogA("RECV: %s", msg);
if (!isalnum(msg[0]) || !isalnum(msg[1]) || !isalnum(msg[2]) || (msg[3] && msg[3] != ' ')) {
debugLogA("Invalid command name");
continue;
}
int handlerResult;
if (isdigit(msg[0]) && isdigit(msg[1]) && isdigit(msg[2])) //all error messages
handlerResult = MSN_HandleErrors(info, msg);
else
handlerResult = MSN_HandleCommands(info, msg);
if (handlerResult) {
if (info->sessionClosed) goto LBL_Exit;
info->sendTerminate();
}
}
if (info->mBytesInData == info->mDataSize) {
if (!ReallocInfoBuffer(info, info->mDataSize * 2)) {
debugLogA("sizeof(data) is too small: the longest line won't fit");
break;
}
}
}
LBL_Exit:
if (info->mIsMainThread) {
msnNsThread = nullptr;
if (hKeepAliveThreadEvt) {
msnPingTimeout *= -1;
SetEvent(hKeepAliveThreadEvt);
}
if (info->s == nullptr)
ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, nullptr, LOGINERR_NONETWORK);
else
MSN_CloseConnections();
if (hHttpsConnection) {
Netlib_CloseHandle(hHttpsConnection);
hHttpsConnection = nullptr;
}
MSN_GoOffline();
}
debugLogA("Thread [%08X] ending now", GetCurrentThreadId());
}
void CMsnProto::MSN_CloseConnections(void)
{
mir_cslockfull lck(m_csThreads);
NETLIBSELECTEX nls = {};
for (auto &it : m_arThreads) {
switch (it->mType) {
case SERVER_NOTIFICATION:
if (it->s != nullptr && !it->sessionClosed && !it->termPending) {
nls.hReadConns[0] = it->s;
int res = Netlib_SelectEx(&nls);
if (res >= 0 || nls.hReadStatus[0] == 0)
it->sendTerminate();
}
break;
}
}
lck.unlock();
if (hHttpsConnection)
Netlib_Shutdown(hHttpsConnection);
}
void CMsnProto::Threads_Uninit(void)
{
mir_cslock lck(m_csThreads);
m_arThreads.destroy();
}
GCThreadData* CMsnProto::MSN_GetThreadByChatId(const wchar_t* chatId)
{
if (mir_wstrlen(chatId) == 0)
return nullptr;
mir_cslock lck(m_csThreads);
for (auto &it : m_arGCThreads)
if (mir_wstrcmpi(it->mChatID, chatId) == 0)
return it;
return nullptr;
}
ThreadData* CMsnProto::MSN_GetThreadByConnection(HANDLE s)
{
mir_cslock lck(m_csThreads);
for (auto &it : m_arThreads)
if (it->s == s)
return it;
return nullptr;
}
/////////////////////////////////////////////////////////////////////////////////////////
// class ThreadData members
ThreadData::ThreadData()
{
memset(&mInitialContactWLID, 0, sizeof(ThreadData) - 2 * sizeof(STRLIST));
mGatewayTimeout = 2;
resetTimeout();
hWaitEvent = CreateSemaphore(nullptr, 0, MSN_PACKETS_COMBINE, nullptr);
mData = (char*)mir_calloc((mDataSize = 8192) + 1);
}
ThreadData::~ThreadData()
{
if (s != nullptr) {
proto->debugLogA("Closing connection handle %08X", s);
Netlib_CloseHandle(s);
}
if (mIncomingBoundPort != nullptr) {
Netlib_CloseHandle(mIncomingBoundPort);
}
if (mMsnFtp != nullptr) {
delete mMsnFtp;
mMsnFtp = nullptr;
}
if (hWaitEvent != INVALID_HANDLE_VALUE)
CloseHandle(hWaitEvent);
if (mTimerId != 0)
KillTimer(nullptr, mTimerId);
mJoinedContactsWLID.destroy();
mJoinedIdentContactsWLID.destroy();
mir_free(mInitialContactWLID); mInitialContactWLID = nullptr;
mir_free(mData);
}
void ThreadData::applyGatewayData(HNETLIBCONN hConn, bool isPoll)
{
char szHttpPostUrl[300];
getGatewayUrl(szHttpPostUrl, sizeof(szHttpPostUrl), isPoll);
proto->debugLogA("applying '%s' to %08X [%08X]", szHttpPostUrl, this, GetCurrentThreadId());
NETLIBHTTPPROXYINFO nlhpi = {};
nlhpi.flags = NLHPIF_HTTP11;
nlhpi.szHttpGetUrl = nullptr;
nlhpi.szHttpPostUrl = szHttpPostUrl;
nlhpi.combinePackets = 5;
Netlib_SetHttpProxyInfo(hConn, &nlhpi);
}
void ThreadData::getGatewayUrl(char* dest, int destlen, bool isPoll)
{
static const char openFmtStr[] = "http://%s/gateway/gateway.dll?Action=open&Server=%s&IP=%s";
static const char pollFmtStr[] = "http://%s/gateway/gateway.dll?Action=poll&SessionID=%s";
static const char cmdFmtStr[] = "http://%s/gateway/gateway.dll?SessionID=%s";
if (mSessionID[0] == 0) {
const char* svr = mType == SERVER_NOTIFICATION ? "NS" : "SB";
mir_snprintf(dest, destlen, openFmtStr, mGatewayIP, svr, mServer);
}
else mir_snprintf(dest, destlen, isPoll ? pollFmtStr : cmdFmtStr, mGatewayIP, mSessionID);
}
void ThreadData::processSessionData(const char* xMsgr, const char* xHost)
{
char tSessionID[40], tGateIP[80];
char* tDelim = (char*)strchr(xMsgr, ';');
if (tDelim == nullptr)
return;
*tDelim = 0; tDelim += 2;
if (!sscanf(xMsgr, "SessionID=%s", tSessionID))
return;
char* tDelim2 = strchr(tDelim, ';');
if (tDelim2 != nullptr)
*tDelim2 = '\0';
if (xHost)
mir_strcpy(tGateIP, xHost);
else if (!sscanf(tDelim, "GW-IP=%s", tGateIP))
return;
mir_strcpy(mGatewayIP, tGateIP);
if (gatewayType) mir_strcpy(mServer, tGateIP);
mir_strcpy(mSessionID, tSessionID);
}
/////////////////////////////////////////////////////////////////////////////////////////
// thread start code
/////////////////////////////////////////////////////////////////////////////////////////
void __cdecl CMsnProto::ThreadStub(void* arg)
{
ThreadData* info = (ThreadData*)arg;
debugLogA("Starting thread %08X (%08X)", GetCurrentThreadId(), info->mFunc);
(this->*(info->mFunc))(info);
debugLogA("Leaving thread %08X (%08X)", GetCurrentThreadId(), info->mFunc);
{
mir_cslock lck(m_csThreads);
m_arThreads.remove(info);
}
}
void ThreadData::startThread(MsnThreadFunc parFunc, CMsnProto *prt)
{
mFunc = parFunc;
proto = prt;
{
mir_cslock lck(prt->m_csThreads);
proto->m_arThreads.insert(this);
}
proto->ForkThread(&CMsnProto::ThreadStub, this);
}
/////////////////////////////////////////////////////////////////////////////////////////
// HReadBuffer members
HReadBuffer::HReadBuffer(ThreadData *T, int iStart)
{
owner = T;
buffer = (BYTE*)T->mData;
totalDataSize = T->mBytesInData;
startOffset = iStart;
}
HReadBuffer::~HReadBuffer()
{
if (totalDataSize > startOffset) {
memmove(buffer, buffer + startOffset, (totalDataSize -= startOffset));
owner->mBytesInData = (int)totalDataSize;
}
else owner->mBytesInData = 0;
buffer[owner->mBytesInData] = 0;
}
BYTE* HReadBuffer::surelyRead(size_t parBytes)
{
if ((startOffset + parBytes) > owner->mDataSize) {
if (totalDataSize > startOffset)
memmove(buffer, buffer + startOffset, (totalDataSize -= startOffset));
else
totalDataSize = 0;
startOffset = 0;
if (parBytes > owner->mDataSize) {
size_t mDataSize = owner->mDataSize;
while (parBytes > mDataSize) mDataSize *= 2;
if (!ReallocInfoBuffer(owner, mDataSize)) {
owner->proto->debugLogA("HReadBuffer::surelyRead: not enough memory, %d %d %d", parBytes, owner->mDataSize, startOffset);
return nullptr;
}
buffer = (BYTE*)owner->mData;
}
}
while ((startOffset + parBytes) > totalDataSize) {
int recvResult = owner->recv((char*)buffer + totalDataSize, owner->mDataSize - totalDataSize);
if (recvResult <= 0)
return nullptr;
totalDataSize += recvResult;
}
BYTE *result = buffer + startOffset; startOffset += parBytes;
buffer[totalDataSize] = 0;
return result;
}
/////////////////////////////////////////////////////////////////////////////////////////
// class GCThreadData members
GCThreadData::GCThreadData() :
mJoinedContacts(10, PtrKeySortT)
{
memset(&mCreator, 0, sizeof(GCThreadData) - sizeof(mJoinedContacts));
}
GCThreadData::~GCThreadData()
{
}