/* Plugin of Miranda IM for communicating with users of the MSN Messenger protocol. Copyright (c) 2012-2014 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 "msn_global.h" #include "msn_proto.h" ///////////////////////////////////////////////////////////////////////////////////////// // Keep-alive thread for the main connection void __cdecl CMsnProto::msn_keepAliveThread(void*) { bool keepFlag = true; hKeepAliveThreadEvt = CreateEvent(NULL, FALSE, FALSE, NULL); msnPingTimeout = 45; while (keepFlag) { switch (WaitForSingleObject(hKeepAliveThreadEvt, msnPingTimeout * 1000)) { case WAIT_TIMEOUT: keepFlag = msnNsThread != NULL; if (usingGateway) msnPingTimeout = 45; else { msnPingTimeout = 20; keepFlag = keepFlag && msnNsThread->send("PNG\r\n", 5); } p2p_clearDormantSessions(); if (hHttpsConnection && (clock() - mHttpsTS) > 60 * CLOCKS_PER_SEC) { HANDLE hConn = hHttpsConnection; hHttpsConnection = NULL; Netlib_CloseHandle(hConn); } if (mStatusMsgTS && (clock() - mStatusMsgTS) > 60 * CLOCKS_PER_SEC) { mStatusMsgTS = 0; ForkThread(&CMsnProto::msn_storeProfileThread, NULL); } break; case WAIT_OBJECT_0: keepFlag = msnPingTimeout > 0; break; default: keepFlag = false; break; } } CloseHandle(hKeepAliveThreadEvt); hKeepAliveThreadEvt = NULL; debugLogA("Closing keep-alive thread"); } ///////////////////////////////////////////////////////////////////////////////////////// // MSN server thread - read and process commands from a server void __cdecl CMsnProto::MSNServerThread(void* arg) { ThreadData* info = (ThreadData*)arg; if (info->mIsMainThread) isConnectSuccess = false; char* tPortDelim = strrchr(info->mServer, ':'); if (tPortDelim != NULL) *tPortDelim = '\0'; if (usingGateway) { if (info->mServer[0] == 0) strcpy(info->mServer, MSN_DEFAULT_LOGIN_SERVER); else if (info->mIsMainThread) strcpy(info->mGatewayIP, info->mServer); if (info->gatewayType) strcpy(info->mGatewayIP, info->mServer); else { if (info->mGatewayIP[0] == 0 && getStaticString(NULL, "GatewayServer", info->mGatewayIP, sizeof(info->mGatewayIP))) strcpy(info->mGatewayIP, MSN_DEFAULT_GATEWAY); } } else { if (info->mServer[0] == 0 && getStaticString(NULL, "DirectServer", info->mServer, sizeof(info->mServer))) 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.szHost = info->mServer; tConn.wPort = MSN_DEFAULT_PORT; if (tPortDelim != NULL) { int tPortNumber = atoi(tPortDelim + 1); if (tPortNumber) tConn.wPort = (WORD)tPortNumber; } } debugLogA("Thread started: server='%s:%d', type=%d", tConn.szHost, tConn.wPort, info->mType); info->s = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)m_hNetlibUser, (LPARAM)&tConn); if (info->s == NULL) { debugLogA("Connection Failed (%d) server='%s:%d'", WSAGetLastError(), tConn.szHost, tConn.wPort); switch (info->mType) { case SERVER_NOTIFICATION: goto LBL_Exit; break; case SERVER_SWITCHBOARD: if (info->mCaller) msnNsThread->sendPacket("XFR", "SB"); break; } return; } if (usingGateway) CallService(MS_NETLIB_SETPOLLINGTIMEOUT, WPARAM(info->s), info->mGatewayTimeout); debugLogA("Connected with handle=%08X", info->s); if (info->mType == SERVER_NOTIFICATION) { info->sendPacket("VER", "MSNP18 MSNP17 CVR0"); } else if (info->mType == SERVER_SWITCHBOARD) { info->sendPacket(info->mCaller ? "USR" : "ANS", "%s;%s %s", MyOptions.szEmail, MyOptions.szMachineGuid, info->mCookie); } else if (info->mType == SERVER_FILETRANS && info->mCaller == 0) { info->send("VER MSNFTP\r\n", 12); } if (info->mIsMainThread) { msnNsThread = info; } debugLogA("Entering main recv loop"); info->mBytesInData = 0; for (;;) { int recvResult = info->recv(info->mData + info->mBytesInData, sizeof(info->mData) - 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; if (info->mCaller == 1 && info->mType == SERVER_FILETRANS) { if (MSN_HandleMSNFTP(info, info->mData)) break; } else { for (;;) { char* peol = strchr(info->mData, '\r'); if (peol == NULL) break; if (info->mBytesInData < peol-info->mData + 2) break; //wait for full line end char msg[sizeof(info->mData)]; memcpy(msg, info->mData, peol-info->mData); msg[peol-info->mData] = 0; 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; } if (info->mType != SERVER_FILETRANS) { 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(); } } else if (MSN_HandleMSNFTP(info, msg)) goto LBL_Exit; } } if (info->mBytesInData == sizeof(info->mData)) { debugLogA("sizeof(data) is too small: the longest line won't fit"); break; } } LBL_Exit: if (info->mIsMainThread) { if (!isConnectSuccess && !usingGateway && m_iDesiredStatus != ID_STATUS_OFFLINE) { msnNsThread = NULL; usingGateway = true; ThreadData* newThread = new ThreadData; newThread->mType = SERVER_NOTIFICATION; newThread->mIsMainThread = true; newThread->startThread(&CMsnProto::MSNServerThread, this); } else { if (hKeepAliveThreadEvt) { msnPingTimeout *= -1; SetEvent(hKeepAliveThreadEvt); } if (info->s == NULL) ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NONETWORK); else { p2p_cancelAllSessions(); MSN_CloseConnections(); } if (hHttpsConnection) { Netlib_CloseHandle(hHttpsConnection); hHttpsConnection = NULL; } MSN_GoOffline(); msnNsThread = NULL; } } debugLogA("Thread [%08X] ending now", GetCurrentThreadId()); } void CMsnProto::MSN_InitThreads(void) { InitializeCriticalSection(&sttLock); } void CMsnProto::MSN_CloseConnections(void) { EnterCriticalSection(&sttLock); NETLIBSELECTEX nls = {0}; nls.cbSize = sizeof(nls); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; switch (T->mType) { case SERVER_NOTIFICATION : case SERVER_SWITCHBOARD : if (T->s != NULL && !T->sessionClosed && !T->termPending) { nls.hReadConns[0] = T->s; int res = CallService(MS_NETLIB_SELECTEX, 0, (LPARAM)&nls); if (res >= 0 || nls.hReadStatus[0] == 0) T->sendTerminate(); } break; case SERVER_P2P_DIRECT : CallService(MS_NETLIB_SHUTDOWN, (WPARAM)T->s, 0); break; } } LeaveCriticalSection(&sttLock); if (hHttpsConnection) CallService(MS_NETLIB_SHUTDOWN, (WPARAM)hHttpsConnection, 0); } void CMsnProto::Threads_Uninit(void) { EnterCriticalSection(&sttLock); sttThreads.destroy(); LeaveCriticalSection(&sttLock); DeleteCriticalSection(&sttLock); } ThreadData* CMsnProto::MSN_GetThreadByContact(const char* wlid, TInfoType type) { ThreadData* result = NULL; EnterCriticalSection(&sttLock); if (type == SERVER_P2P_DIRECT) { for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType != SERVER_P2P_DIRECT || !T->mJoinedIdentContactsWLID.getCount() || T->s == NULL) continue; if (_stricmp(T->mJoinedIdentContactsWLID[0], wlid) == 0) { result = T; break; } } } if (result == NULL) { char *szEmail = NULL; parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType != type || !T->mJoinedContactsWLID.getCount() || T->mInitialContactWLID || T->s == NULL) continue; if (_stricmp(T->mJoinedContactsWLID[0], szEmail) == 0 && T->mChatID[0] == 0) { result = T; break; } } } LeaveCriticalSection(&sttLock); return result; } ThreadData* CMsnProto::MSN_GetThreadByChatId(const TCHAR* chatId) { ThreadData* result = NULL; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (_tcsicmp(T->mChatID, chatId) == 0) { result = T; break; } } LeaveCriticalSection(&sttLock); return result; } ThreadData* CMsnProto::MSN_GetThreadByTimer(UINT timerId) { ThreadData* result = NULL; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType == SERVER_SWITCHBOARD && T->mTimerId == timerId) { result = T; break; } } LeaveCriticalSection(&sttLock); return result; } ThreadData* CMsnProto::MSN_GetP2PThreadByContact(const char *wlid) { ThreadData *result = NULL; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType != SERVER_P2P_DIRECT || !T->mJoinedIdentContactsWLID.getCount()) continue; if (_stricmp(T->mJoinedIdentContactsWLID[0], wlid) == 0) { result = T; break; } } if (result == NULL) { char *szEmail = NULL; parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mJoinedContactsWLID.getCount() && !T->mInitialContactWLID && _stricmp(T->mJoinedContactsWLID[0], szEmail) == 0) { if (T->mType == SERVER_P2P_DIRECT) { result = T; break; } else if (T->mType == SERVER_SWITCHBOARD) result = T; } } } LeaveCriticalSection(&sttLock); return result; } void CMsnProto::MSN_StartP2PTransferByContact(const char* wlid) { EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType == SERVER_FILETRANS && T->hWaitEvent != INVALID_HANDLE_VALUE) { if ((T->mInitialContactWLID && !_stricmp(T->mInitialContactWLID, wlid)) || (T->mJoinedContactsWLID.getCount() && !_stricmp(T->mJoinedContactsWLID[0], wlid)) || (T->mJoinedIdentContactsWLID.getCount() && !_stricmp(T->mJoinedIdentContactsWLID[0], wlid))) ReleaseSemaphore(T->hWaitEvent, 1, NULL); } } LeaveCriticalSection(&sttLock); } ThreadData* CMsnProto::MSN_GetOtherContactThread(ThreadData* thread) { ThreadData* result = NULL; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mJoinedContactsWLID.getCount() == 0 || T->s == NULL) continue; if (T != thread && _stricmp(T->mJoinedContactsWLID[0], thread->mJoinedContactsWLID[0]) == 0) { result = T; break; } } LeaveCriticalSection(&sttLock); return result; } ThreadData* CMsnProto::MSN_GetUnconnectedThread(const char* wlid, TInfoType type) { ThreadData* result = NULL; EnterCriticalSection(&sttLock); char* szEmail = (char*)wlid; if (type == SERVER_SWITCHBOARD && strchr(wlid, ';')) parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType == type && T->mInitialContactWLID && _stricmp(T->mInitialContactWLID, szEmail) == 0) { result = T; break; } } LeaveCriticalSection(&sttLock); return result; } ThreadData* CMsnProto::MSN_StartSB(const char* wlid, bool& isOffline) { isOffline = false; ThreadData* thread = MSN_GetThreadByContact(wlid); if (thread == NULL) { HANDLE hContact = MSN_HContactFromEmail(wlid); WORD wStatus = getWord(hContact, "Status", ID_STATUS_OFFLINE); if (wStatus != ID_STATUS_OFFLINE) { if (MSN_GetUnconnectedThread(wlid) == NULL && MsgQueue_CheckContact(wlid, 5) == NULL) msnNsThread->sendPacket("XFR", "SB"); } else isOffline = true; } return thread; } int CMsnProto::MSN_GetActiveThreads(ThreadData** parResult) { int tCount = 0; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mType == SERVER_SWITCHBOARD && T->mJoinedContactsWLID.getCount() != 0 && T->mJoinedContactsWLID.getCount()) parResult[tCount++] = T; } LeaveCriticalSection(&sttLock); return tCount; } ThreadData* CMsnProto::MSN_GetThreadByConnection(HANDLE s) { ThreadData* tResult = NULL; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->s == s) { tResult = T; break; } } LeaveCriticalSection(&sttLock); return tResult; } ThreadData* CMsnProto::MSN_GetThreadByPort(WORD wPort) { ThreadData* result = NULL; EnterCriticalSection(&sttLock); for (int i=0; i < sttThreads.getCount(); i++) { ThreadData* T = &sttThreads[i]; if (T->mIncomingPort == wPort) { result = T; break; } } LeaveCriticalSection(&sttLock); return result; } ///////////////////////////////////////////////////////////////////////////////////////// // class ThreadData members ThreadData::ThreadData() { memset(&mInitialContactWLID, 0, sizeof(ThreadData) - 2*sizeof(STRLIST)); mGatewayTimeout = 2; resetTimeout(); hWaitEvent = CreateSemaphore(NULL, 0, MSN_PACKETS_COMBINE, NULL); } ThreadData::~ThreadData() { int i; if (s != NULL) { proto->debugLogA("Closing connection handle %08X", s); Netlib_CloseHandle(s); } if (mIncomingBoundPort != NULL) { Netlib_CloseHandle(mIncomingBoundPort); } if (mMsnFtp != NULL) { delete mMsnFtp; mMsnFtp = NULL; } if (hWaitEvent != INVALID_HANDLE_VALUE) CloseHandle(hWaitEvent); if (mTimerId != 0) KillTimer(NULL, mTimerId); if (mType == SERVER_SWITCHBOARD) { for (i=0; iMSN_HContactFromEmail(wlid); int temp_status = proto->getWord(hContact, "Status", ID_STATUS_OFFLINE); if (temp_status == ID_STATUS_INVISIBLE && proto->MSN_GetThreadByContact(wlid) == NULL) proto->setWord(hContact, "Status", ID_STATUS_OFFLINE); } } mJoinedContactsWLID.destroy(); mJoinedIdentContactsWLID.destroy(); const char* wlid = NEWSTR_ALLOCA(mInitialContactWLID); mir_free(mInitialContactWLID); mInitialContactWLID = NULL; if (proto && mType == SERVER_P2P_DIRECT) proto->p2p_clearDormantSessions(); if (wlid != NULL && mType == SERVER_SWITCHBOARD && proto->MSN_GetThreadByContact(wlid) == NULL && proto->MSN_GetUnconnectedThread(wlid) == NULL) { proto->MsgQueue_Clear(wlid, true); } } void ThreadData::applyGatewayData(HANDLE hConn, bool isPoll) { char szHttpPostUrl[300]; getGatewayUrl(szHttpPostUrl, sizeof(szHttpPostUrl), isPoll); proto->debugLogA("applying '%s' to %08X [%08X]", szHttpPostUrl, this, GetCurrentThreadId()); NETLIBHTTPPROXYINFO nlhpi = {0}; nlhpi.cbSize = sizeof(nlhpi); nlhpi.flags = NLHPIF_HTTP11; nlhpi.szHttpGetUrl = NULL; nlhpi.szHttpPostUrl = szHttpPostUrl; nlhpi.combinePackets = 5; CallService(MS_NETLIB_SETHTTPPROXYINFO, (WPARAM)hConn, (LPARAM)&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 == NULL) return; *tDelim = 0; tDelim += 2; if (!sscanf(xMsgr, "SessionID=%s", tSessionID)) return; char* tDelim2 = strchr(tDelim, ';'); if (tDelim2 != NULL) *tDelim2 = '\0'; if (xHost) strcpy(tGateIP, xHost); else if (!sscanf(tDelim, "GW-IP=%s", tGateIP)) return; strcpy(mGatewayIP, tGateIP); if (gatewayType) strcpy(mServer, tGateIP); 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); EnterCriticalSection(&sttLock); sttThreads.LIST::remove(info); LeaveCriticalSection(&sttLock); delete info; } void ThreadData::startThread(MsnThreadFunc parFunc, CMsnProto *prt) { mFunc = parFunc; proto = prt; EnterCriticalSection(&proto->sttLock); proto->sttThreads.insert(this); LeaveCriticalSection(&proto->sttLock); 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; } BYTE* HReadBuffer::surelyRead(size_t parBytes) { const size_t bufferSize = sizeof(owner->mData); if ((startOffset + parBytes) > bufferSize) { if (totalDataSize > startOffset) memmove(buffer, buffer + startOffset, (totalDataSize -= startOffset)); else totalDataSize = 0; startOffset = 0; if (parBytes > bufferSize) { owner->proto->debugLogA("HReadBuffer::surelyRead: not enough memory, %d %d %d", parBytes, bufferSize, startOffset); return NULL; } } while ((startOffset + parBytes) > totalDataSize) { int recvResult = owner->recv((char*)buffer + totalDataSize, bufferSize - totalDataSize); if (recvResult <= 0) return NULL; totalDataSize += recvResult; } BYTE* result = buffer + startOffset; startOffset += parBytes; return result; }