/* Plugin of Miranda IM for communicating with users of the MSN Messenger protocol. Copyright (c) 2012-2016 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" #ifdef OBSOLETE void CMsnProto::MSN_SetMirVer(MCONTACT hContact, DWORD dwValue, bool always) { static const char* MirVerStr[] = { "MSN 4.x-5.x", "MSN 6.0", "MSN 6.1", "MSN 6.2", "MSN 7.0", "MSN 7.5", "WLM 8.0", "WLM 8.1", "WLM 8.5", "WLM 9.0 Beta", "WLM 2009", "WLM 2011", "WLM 2012", "WLM Unknown", }; LPCSTR szVersion; if (dwValue == 0) szVersion = "Windows Phone"; else if (dwValue & 0x1) szVersion = "MSN Mobile"; else if (dwValue & 0x200) szVersion = "Webmessenger"; else if (dwValue == 0x800800) szVersion = "Yahoo"; else if (dwValue == 0x800) szVersion = "LCS"; else if (dwValue == 0x50000000) szVersion = "Miranda IM 0.5.x (MSN v.0.5.x)"; else if (dwValue == 0x30000024) szVersion = "Miranda IM 0.4.x (MSN v.0.4.x)"; else if (always || getByte(hContact, "StdMirVer", 0)) { unsigned wlmId = min(dwValue >> 28 & 0xff, _countof(MirVerStr) - 1); szVersion = MirVerStr[wlmId]; } else return; setString(hContact, "MirVer", szVersion); setByte(hContact, "StdMirVer", 1); } #endif void CMsnProto::MSN_SetMirVer(MCONTACT hContact, MsnPlace *place) { static const char* MirVerStr[] = { "Messenger (Windows)", "Web", "Windows Phone", "Xbox", "Zune", "Messenger (iPhone) ", "Messenger (Mac)", "Messenger (SMS)", "Messenger (Modern)", "Skype", "Skype (Windows)", "Skype (Windows 8)", "Skype (Mac)", "Skype (Linux)", "Skype (Windows Phone)", "Skype (iOS)", "Skype (Android)", "Skype" }; char szVersion[64]; if (!place || !place->client) return; mir_snprintf(szVersion, sizeof(szVersion), "%s (%s)", MirVerStr[place->client>=sizeof(MirVerStr)/sizeof(MirVerStr[0])?9:place->client-1], place->szClientVer); setString(hContact, "MirVer", szVersion); setByte(hContact, "StdMirVer", 1); } ///////////////////////////////////////////////////////////////////////////////////////// // MSN_ReceiveMessage - receives message or a file from the server ///////////////////////////////////////////////////////////////////////////////////////// void CMsnProto::MSN_ReceiveMessage(ThreadData* info, char* cmdString, char* params) { union { char* tWords[6]; struct { char *fromEmail, *fromNick, *strMsgBytes; } data; struct { char *fromEmail, *fromNetId, *toEmail, *toNetId, *typeId, *strMsgBytes; } datau; struct { char *typeId, *strMsgBytes; } datas; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) { debugLogA("Invalid %.3s command, ignoring", cmdString); return; } int msgBytes; char *nick = NULL, *email = NULL; wchar_t *mChatID = NULL; bool ubmMsg = strncmp(cmdString, "UBM", 3) == 0; bool sdgMsg = strncmp(cmdString, "SDG", 3) == 0; bool nfyMsg = strncmp(cmdString, "NFY", 3) == 0; bool sentMsg = false; if (sdgMsg) { msgBytes = atol(datas.strMsgBytes); if (mir_strcmpi(datas.typeId, "MSGR")) return; } else if (nfyMsg) { msgBytes = atol(datas.strMsgBytes); if (mir_strcmpi(datas.typeId, "MSGR\\HOTMAIL")) return; } else { if (ubmMsg) { msgBytes = atol(datau.strMsgBytes); nick = datau.fromEmail; email = datau.fromEmail; } else { msgBytes = atol(data.strMsgBytes); nick = data.fromNick; email = data.fromEmail; UrlDecode(nick); } stripBBCode(nick); stripColorCode(nick); } char* msg = (char*)alloca(msgBytes+1); HReadBuffer buf(info, 0); BYTE* msgb = buf.surelyRead(msgBytes); if (msgb == NULL) return; memcpy(msg, msgb, msgBytes); msg[msgBytes] = 0; debugLogA("Message:\n%s", msg); MimeHeaders tHeader; char* msgBody = tHeader.readFromBuffer(msg); if (sdgMsg) { if (tHeader["Ack-Id"]) { CMStringA szBody; szBody.AppendFormat("Ack-Id: %s\r\n", tHeader["Ack-Id"]); if (msnRegistration) szBody.AppendFormat("Registration: %s\r\n", msnRegistration); szBody.AppendFormat("\r\n"); msnNsThread->sendPacket("ACK", "MSGR %d\r\n%s", mir_strlen(szBody), szBody); } msgBody = tHeader.readFromBuffer(msgBody); if (!(email = NEWSTR_ALLOCA(tHeader["From"]))) return; mChatID = mir_a2u(tHeader["To"]); if (wcsncmp(mChatID, L"19:", 3)) mChatID[0]=0; // NETID_THREAD msgBody = tHeader.readFromBuffer(msgBody); msgBody = tHeader.readFromBuffer(msgBody); nick = NEWSTR_ALLOCA(tHeader["IM-Display-Name"]); if (!mir_strcmp(tHeader["Message-Type"], "RichText")) { msgBody = NEWSTR_ALLOCA(msgBody); stripHTML(msgBody); HtmlDecode(msgBody); } } else if (nfyMsg) msgBody = tHeader.readFromBuffer(msgBody); else mChatID = info->mChatID; const char* tMsgId = tHeader["Message-ID"]; if (tMsgId) lastMsgId=_atoi64(tMsgId); // Chunked message char* newbody = NULL; if (!sdgMsg && tMsgId) { int idx; const char* tChunks = tHeader["Chunks"]; if (tChunks) idx = addCachedMsg(tMsgId, msg, 0, msgBytes, atol(tChunks), true); else idx = addCachedMsg(tMsgId, msgBody, 0, mir_strlen(msgBody), 0, true); size_t newsize; if (!getCachedMsg(idx, newbody, newsize)) return; msgBody = tHeader.readFromBuffer(newbody); } // message from the server (probably) if (!ubmMsg && !sdgMsg && !nfyMsg && strchr(email, '@') == NULL && _stricmp(email, "Hotmail")) return; const char* tContentType = tHeader["Content-Type"]; if (tContentType == NULL) return; if (nfyMsg) msgBody = tHeader.readFromBuffer(msgBody); if (!_strnicmp(tContentType, "text/x-clientcaps", 17)) { MimeHeaders tFileInfo; tFileInfo.readFromBuffer(msgBody); info->firstMsgRecv = true; MCONTACT hContact = MSN_HContactFromEmail(email); const char* mirver = tFileInfo["Client-Name"]; if (hContact != NULL && mirver != NULL) { setString(hContact, "MirVer", mirver); delSetting(hContact, "StdMirVer"); } } else if (!ubmMsg && !sdgMsg && !nfyMsg && !info->firstMsgRecv) { info->firstMsgRecv = true; MsnContact *cont = Lists_Get(email); if (cont && cont->hContact != NULL && cont->places.getCount() > 0) MSN_SetMirVer(cont->hContact, &cont->places[0]); } if (!_strnicmp(tContentType, "text/plain", 10) || (!_strnicmp(tContentType, "application/user+xml", 10) && tHeader["Message-Type"] && !strncmp(tHeader["Message-Type"], "RichText", 8))) { MCONTACT hContact = strncmp(email, "19:", 3)?MSN_HContactFromEmail(email, nick, true, true):NULL; if (!_stricmp(tHeader["Message-Type"], "RichText/UriObject")) { ezxml_t xmli = ezxml_parse_str(msgBody, strlen(msgBody)); if (xmli) { MSN_ProcessURIObject(hContact, xmli); ezxml_free(xmli); } } else if (!_stricmp(tHeader["Message-Type"], "RichText/Contacts")) { ezxml_t xmli = ezxml_parse_str(msgBody, mir_strlen(msgBody)); if (xmli) { if (!mir_strcmp(xmli->name, "contacts")) { ezxml_t c; int cnt; PROTOSEARCHRESULT **psr; for (c = ezxml_child(xmli, "c"), cnt=0; c; c = c->next) cnt++; if (psr = (PROTOSEARCHRESULT**)mir_calloc(sizeof(PROTOSEARCHRESULT*) * cnt)) { cnt=0; for (c = ezxml_child(xmli, "c"); c; c = c->next) { const char *t = ezxml_attr(c, "t"), *wlid; if (t && (wlid = ezxml_attr(c, t))) { switch (*t) { case 's': case 'p': psr[cnt] = (PROTOSEARCHRESULT*)mir_calloc(sizeof(PROTOSEARCHRESULT)); psr[cnt]->cbSize = sizeof(psr); psr[cnt]->flags = PSR_UNICODE; psr[cnt]->id.w = psr[cnt]->nick.w = psr[cnt]->email.w = mir_a2u(wlid); cnt++; } } } if (cnt) { PROTORECVEVENT pre = { 0 }; pre.timestamp = (DWORD)time(NULL); pre.szMessage = (char *)psr; pre.lParam = cnt; ProtoChainRecv(hContact, PSR_CONTACTS, 0, (LPARAM)&pre); for (cnt=0; cntemail.w); mir_free(psr[cnt]); } } mir_free(psr); } } ezxml_free(xmli); } } else if (!_stricmp(tHeader["Message-Type"], "Control/Typing")) CallService(MS_PROTO_CONTACTISTYPING, hContact, 7); else if (!_stricmp(tHeader["Message-Type"], "Control/ClearTyping")) CallService(MS_PROTO_CONTACTISTYPING, hContact, 0); else { const char* p = tHeader["X-MMS-IM-Format"]; bool isRtl = p != NULL && strstr(p, "RL=1") != NULL; /*if (info->mJoinedContactsWLID.getCount() > 1) MSN_ChatStart(info); else */{ char *szNet, *szEmail; parseWLID(NEWSTR_ALLOCA(email), &szNet, &szEmail, NULL); sentMsg = _stricmp(szEmail, GetMyUsername(atoi(szNet))) == 0; if (sentMsg) hContact = ubmMsg ? MSN_HContactFromEmail(datau.toEmail, nick) : info->getContactHandle(); } const char* tP4Context = tHeader["P4-Context"]; if (tP4Context) { size_t newlen = mir_strlen(msgBody) + mir_strlen(tP4Context) + 4; char* newMsgBody = (char*)mir_alloc(newlen); mir_snprintf(newMsgBody, newlen, "[%s] %s", tP4Context, msgBody); mir_free(newbody); msgBody = newbody = newMsgBody; } if (mChatID[0]) { if (!_strnicmp(tHeader["Message-Type"], "ThreadActivity/", 15)) { ezxml_t xmli = ezxml_parse_str(msgBody, mir_strlen(msgBody)); if (xmli) { MSN_GCProcessThreadActivity(xmli, mChatID); ezxml_free(xmli); } } else MSN_GCAddMessage(mChatID, hContact, email, time(NULL), sentMsg, msgBody); } else if (hContact) { if (!sentMsg) { CallService(MS_PROTO_CONTACTISTYPING, WPARAM(hContact), 0); PROTORECVEVENT pre = { 0 }; pre.szMessage = (char*)msgBody; pre.flags = (isRtl ? PREF_RTL : 0); pre.timestamp = (DWORD)time(NULL); pre.lParam = 0; ProtoChainRecvMsg(hContact, &pre); } else { bool haveWnd = MSN_MsgWndExist(hContact); DBEVENTINFO dbei = { 0 }; dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = DBEF_SENT | DBEF_UTF | (haveWnd ? 0 : DBEF_READ) | (isRtl ? DBEF_RTL : 0); dbei.szModule = m_szModuleName; dbei.timestamp = time(NULL); dbei.cbBlob = (unsigned)mir_strlen(msgBody) + 1; dbei.pBlob = (PBYTE)msgBody; db_event_add(hContact, &dbei); } } } } else if (!_strnicmp(tContentType, "text/x-msmsgsprofile", 20)) { replaceStr(msnExternalIP, tHeader["ClientIP"]); abchMigrated = atol(tHeader["ABCHMigrated"]); langpref = atol(tHeader["lang_preference"]); emailEnabled = atol(tHeader["EmailEnabled"]) != 0; if (!MSN_RefreshContactList()) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER); info->sendTerminate(); } else { MSN_SetServerStatus(m_iDesiredStatus); MSN_EnableMenuItems(true); } } else if (!_strnicmp(tContentType, "text/x-msmsgscontrol", 20) || (sdgMsg && !_strnicmp(tContentType, "Application/Message", 19))) { const char* tTypingUser = sdgMsg?email:tHeader["TypingUser"]; if (tTypingUser != NULL && info->mChatID[0] == 0 && _stricmp(email, MyOptions.szEmail)) { MCONTACT hContact = MSN_HContactFromEmail(tTypingUser, tTypingUser); CallService(MS_PROTO_CONTACTISTYPING, hContact, sdgMsg && !_stricmp(tHeader["Message-Type"], "Control/ClearTyping")?0:7); } } else if (!_strnicmp(tContentType, "text/x-msnmsgr-datacast", 23)) { //if (info->mJoinedContactsWLID.getCount()) { MCONTACT tContact; if (mChatID[0]) { GC_INFO gci = { 0 }; gci.Flags = GCF_HCONTACT; gci.pszModule = m_szModuleName; gci.pszID = mChatID; CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci); tContact = gci.hContact; } else tContact = MSN_HContactFromEmail(email, nick, true, true); if (!mir_strcmp(tHeader["Message-Type"], "Nudge")) NotifyEventHooks(hMSNNudge, (WPARAM)tContact, 0); /* Other msg types: * Wink * Voice * Data */ } else if (!_strnicmp(tContentType, "text/x-msmsgsemailnotification", 30)) sttNotificationMessage(msgBody, false); else if (!_strnicmp(tContentType, "text/x-msmsgsinitialemailnotification", 37)) sttNotificationMessage(msgBody, true); else if (!_strnicmp(tContentType, "text/x-msmsgsactivemailnotification", 35)) sttNotificationMessage(msgBody, false); else if (!_strnicmp(tContentType, "text/x-msmsgsinitialmdatanotification", 37)) sttNotificationMessage(msgBody, true); else if (!_strnicmp(tContentType, "text/x-msmsgsoimnotification", 28)) sttNotificationMessage(msgBody, false); #ifdef OBSOLETE else if (!_strnicmp(tContentType, "text/x-msmsgsinvite", 19)) MSN_InviteMessage(info, msgBody, email, nick); else if (!_strnicmp(tContentType, "application/x-msnmsgrp2p", 24)) { const char* dest = tHeader["P2P-Dest"]; if (dest) { char *szEmail, *szInst; parseWLID(NEWSTR_ALLOCA(dest), NULL, &szEmail, &szInst); if (mir_strcmpi(szEmail, MyOptions.szEmail) == 0) { const char* src = tHeader["P2P-Src"]; if (src == NULL) src = email; if (szInst == NULL) p2p_processMsg(info, msgBody, src); else if (mir_strcmpi(szInst, MyOptions.szMachineGuidP2P) == 0) p2p_processMsgV2(info, msgBody, src); } } } else if (!_strnicmp(tContentType, "text/x-mms-emoticon", 19)) MSN_CustomSmiley(msgBody, email, nick, MSN_APPID_CUSTOMSMILEY); else if (!_strnicmp(tContentType, "text/x-mms-animemoticon", 23)) MSN_CustomSmiley(msgBody, email, nick, MSN_APPID_CUSTOMANIMATEDSMILEY); #endif mir_free(mChatID); mir_free(newbody); } void CMsnProto::MSN_ProcessURIObject(MCONTACT hContact, ezxml_t xmli) { const char *pszSkypeToken; if ((pszSkypeToken=authSkypeToken.Token()) && xmli) { /* FIXME: As soon as core has functions to POST images in a conversation AND gives the possibility to supply a * callback for fetching that image, this may be possible, but currently due to required Auth-Header, this * is not possible and we just send an incoming file transfer const char *thumb = ezxml_attr(xmli, "url_thumbnail"); if (thumb && ServiceExists("IEVIEW/NewWindow")) { // Supply callback to detch thumb with auth-header and embed [img] BB-code? } */ char *uri = (char*)ezxml_attr(xmli, "uri"); if (uri) { // First get HTTP header of file to get content length unsigned __int64 fileSize = 0; NETLIBHTTPHEADER nlbhHeaders[2] = { 0 }; nlbhHeaders[0].szName = "User-Agent"; nlbhHeaders[0].szValue = (LPSTR)MSN_USER_AGENT; nlbhHeaders[1].szName = "Authorization"; nlbhHeaders[1].szValue = (char*)pszSkypeToken; NETLIBHTTPREQUEST nlhr = { 0 }, *nlhrReply; nlhr.cbSize = sizeof(nlhr); nlhr.requestType = REQUEST_GET; nlhr.flags = NLHRF_PERSISTENT; nlhr.szUrl = uri; nlhr.headers = (NETLIBHTTPHEADER*)&nlbhHeaders; nlhr.headersCount = _countof(nlbhHeaders); nlhr.nlc = hHttpsConnection; mHttpsTS = clock(); nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUserHttps, (LPARAM)&nlhr); mHttpsTS = clock(); if (nlhrReply) { hHttpsConnection = nlhrReply->nlc; if (nlhrReply->resultCode == 200) { char *pLength, *pEnd; if ((pLength = strstr(nlhrReply->pData, "\"contents\":")) && (pLength = strstr(pLength, "\"imgpsh\"")) && (pLength = strstr(pLength, "\"length\":")) && (pEnd = strchr(pLength+9, ','))) { pLength+=9; *pEnd = 0; fileSize=_atoi64(pLength); } } CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); } else hHttpsConnection = NULL; if (fileSize) { filetransfer* ft = new filetransfer(this); char *pszFile = ""; ezxml_t originalName, desc; ft->std.hContact = hContact; ft->tType = SERVER_HTTP; ft->p2p_appID = MSN_APPID_FILE; mir_free(ft->std.tszCurrentFile); if (!((originalName = ezxml_child(xmli, "OriginalName")) && (pszFile = (char*)ezxml_attr(originalName, "v")))) { if ((originalName = ezxml_child(xmli, "meta"))) pszFile = (char*)ezxml_attr(originalName, "originalName"); } if (!pszFile || !*pszFile) { if ((originalName = ezxml_child(xmli, "meta")) && (pszFile = (char*)ezxml_attr(originalName, "type"))) { if (!mir_strcmp(pszFile, "photo")) pszFile="IMG00001.JPG"; } if (!pszFile || !*pszFile) pszFile="file"; } ft->std.tszCurrentFile = mir_utf8decodeW(pszFile); ft->std.totalBytes = ft->std.currentFileSize = fileSize; ft->std.totalFiles = 1; ft->szInvcookie = (char*)mir_calloc(strlen(uri)+16); sprintf(ft->szInvcookie, "%s/content/imgpsh", uri); wchar_t tComment[40]; mir_snwprintf(tComment, TranslateT("%I64u bytes"), ft->std.currentFileSize); PROTORECVFILET pre = { 0 }; pre.dwFlags = PRFF_UNICODE; pre.fileCount = 1; pre.timestamp = time(NULL); pre.descr.w = (desc = ezxml_child(xmli, "Description"))?mir_utf8decodeW(desc->txt):tComment; pre.files.w = &ft->std.tszCurrentFile; pre.lParam = (LPARAM)ft; ProtoChainRecvFile(ft->std.hContact, &pre); if (desc) mir_free(pre.descr.w); } else uri=NULL; } if (uri == NULL) { // Fallback: Just filter out the link and post it as a message CallService(MS_PROTO_CONTACTISTYPING, WPARAM(hContact), 0); PROTORECVEVENT pre = { 0 }; CMStringA msgtxt((char*)ezxml_txt(xmli)); ezxml_t urllnk; if (urllnk=ezxml_child(xmli, "a")) msgtxt.AppendFormat(" %s", ezxml_txt(urllnk)); pre.szMessage = (char*)(const char*)msgtxt; pre.timestamp = (DWORD)time(NULL); ProtoChainRecvMsg(hContact, &pre); } } } ///////////////////////////////////////////////////////////////////////////////////////// // Process Yahoo Find void CMsnProto::MSN_ProcessYFind(char* buf, size_t len) { if (buf == NULL) return; ezxml_t xmli = ezxml_parse_str(buf, len); ezxml_t dom = ezxml_child(xmli, "d"); const char* szDom = ezxml_attr(dom, "n"); ezxml_t cont = ezxml_child(dom, "c"); const char* szCont = ezxml_attr(cont, "n"); char szEmail[128]; mir_snprintf(szEmail, "%s@%s", szCont, szDom); const char *szNetId = ezxml_attr(cont, "t"); if (msnSearchId != NULL) { if (szNetId != NULL) { ptrW szEmailT(mir_utf8decodeW(szEmail)); PROTOSEARCHRESULT psr = { 0 }; psr.cbSize = sizeof(psr); psr.flags = PSR_UNICODE; psr.id.w = szEmailT; psr.nick.w = szEmailT; psr.email.w = szEmailT; ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, msnSearchId, (LPARAM)&psr); } ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, msnSearchId, 0); msnSearchId = NULL; } else { if (szNetId != NULL) { int netId = atol(szNetId); MCONTACT hContact = MSN_HContactFromEmail(szEmail, szEmail, true, false); if (MSN_AddUser(hContact, szEmail, netId, LIST_FL)) { MSN_AddUser(hContact, szEmail, netId, LIST_PL + LIST_REMOVE); MSN_AddUser(hContact, szEmail, netId, LIST_BL + LIST_REMOVE); MSN_AddUser(hContact, szEmail, netId, LIST_AL); db_unset(hContact, "CList", "Hidden"); } MSN_SetContactDb(hContact, szEmail); } } ezxml_free(xmli); } ///////////////////////////////////////////////////////////////////////////////////////// // MSN_HandleCommands - process commands from the server ///////////////////////////////////////////////////////////////////////////////////////// void CMsnProto::MSN_ProcessNLN(const char *userStatus, const char *wlid, char *userNick, const char *objid, char *cmdstring) { if (userNick) { UrlDecode(userNick); stripBBCode(userNick); stripColorCode(userNick); } bool isMe = false; char* szEmail, *szNet, *szInst; parseWLID(NEWSTR_ALLOCA(wlid), &szNet, &szEmail, &szInst); if (!mir_strcmpi(szEmail, GetMyUsername(atoi(szNet)))) { if (!*userStatus) return; isMe = true; int newStatus = MSNStatusToMiranda(userStatus); if (newStatus != m_iStatus && newStatus != ID_STATUS_IDLE) { int oldMode = m_iStatus; m_iDesiredStatus = m_iStatus = newStatus; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldMode, m_iStatus); } } WORD lastStatus = ID_STATUS_OFFLINE; MsnContact *cont = Lists_Get(szEmail); MCONTACT hContact = NULL; if (!cont && !isMe) { hContact = MSN_HContactFromEmail(wlid, userNick, true, true); cont = Lists_Get(szEmail); } if (cont) hContact = cont->hContact; if (hContact != NULL) { if (userNick) setStringUtf(hContact, "Nick", userNick); lastStatus = getWord(hContact, "Status", ID_STATUS_OFFLINE); if (lastStatus == ID_STATUS_OFFLINE || lastStatus == ID_STATUS_INVISIBLE) db_unset(hContact, "CList", "StatusMsg"); int newStatus = MSNStatusToMiranda(userStatus); setWord(hContact, "Status", newStatus != ID_STATUS_IDLE ? newStatus : ID_STATUS_AWAY); setDword(hContact, "IdleTS", newStatus != ID_STATUS_IDLE ? 0 : time(NULL)); } if (cont) { if (objid) { char* end = NULL; cont->cap1 = strtoul(objid, &end, 10); cont->cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0; } MSN_SetMirVer(hContact, cont->places.find((MsnPlace*)&szInst)); char *pszUrl, *pszAvatarHash; if (cmdstring && *cmdstring && mir_strcmp(cmdstring, "0") && (pszAvatarHash = MSN_GetAvatarHash(cmdstring, &pszUrl))) { setString(hContact, "PictContext", cmdstring); setString(hContact, "AvatarHash", pszAvatarHash); if (pszUrl) setString(hContact, "AvatarUrl", pszUrl); else delSetting(hContact, "AvatarUrl"); if (hContact != NULL) { char szSavedHash[64] = ""; if (!db_get_static(hContact, m_szModuleName, "AvatarSavedHash", szSavedHash, sizeof(szSavedHash))) { if (mir_strcmpi(szSavedHash, pszAvatarHash)) pushAvatarRequest(hContact, pszUrl); else { char szSavedContext[64]; int result = db_get_static(hContact, m_szModuleName, "PictSavedContext", szSavedContext, sizeof(szSavedContext)); if (result || mir_strcmp(szSavedContext, cmdstring)) pushAvatarRequest(hContact, pszUrl); } } } mir_free(pszAvatarHash); mir_free(pszUrl); } else { delSetting(hContact, "AvatarHash"); delSetting(hContact, "AvatarSavedHash"); delSetting(hContact, "AvatarUrl"); delSetting(hContact, "PictContext"); delSetting(hContact, "PictSavedContext"); ProtoBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, 0); } } else if (lastStatus == ID_STATUS_OFFLINE) delSetting(hContact, "MirVer"); } void CMsnProto::MSN_ProcessStatusMessage(ezxml_t xmli, const char* wlid) { MCONTACT hContact = MSN_HContactFromEmail(wlid); if (hContact == NULL) return; char* szEmail, *szNetId, *szInst; parseWLID(NEWSTR_ALLOCA(wlid), &szNetId, &szEmail, &szInst); bool bHasPSM=false; char* szStatMsg = NULL; for (ezxml_t s = ezxml_child(xmli, "s"); s; s = s->next) { const char *n = ezxml_attr(s, "n"); if (!mir_strcmp(n, "SKP")) { szStatMsg = ezxml_txt(ezxml_child(s, "Mood")); if (*szStatMsg) db_set_utf(hContact, "CList", "StatusMsg", szStatMsg); else if (!bHasPSM) db_unset(hContact, "CList", "StatusMsg"); } else if (!mir_strcmp(n, "PE")) { szStatMsg = ezxml_txt(ezxml_child(s, "PSM")); if (*szStatMsg) { stripBBCode((char*)szStatMsg); stripColorCode((char*)szStatMsg); db_set_utf(hContact, "CList", "StatusMsg", szStatMsg); bHasPSM = true; } else db_unset(hContact, "CList", "StatusMsg"); } } // Add endpoints for (ezxml_t endp = ezxml_child(xmli, "sep"); endp; endp = ezxml_next(endp)) { const char *n = ezxml_attr(endp, "n"); if (!mir_strcmp(n, "IM")) { const char *id = ezxml_attr(endp, "epid"); const char *caps = ezxml_txt(ezxml_child(endp, "Capabilities")); char* end = NULL; unsigned cap1 = caps ? strtoul(caps, &end, 10) : 0; unsigned cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0; Lists_AddPlace(szEmail, id, cap1, cap2); } else if (!mir_strcmp(n, "PE")) { MsnPlace *place = Lists_GetPlace(szEmail, ezxml_attr(endp, "epid")); if (place) { place->client = atoi(ezxml_txt(ezxml_child(endp, "TYP"))); mir_strncpy(place->szClientVer, ezxml_txt(ezxml_child(endp, "VER")), sizeof(place->szClientVer)); } } } { ptrW tszStatus(mir_utf8decodeW(szStatMsg)); if (szInst) MSN_SetMirVer(hContact, Lists_GetPlace(szEmail, szInst)); else { MsnContact *cont = Lists_Get(hContact); if (cont->places.getCount() > 0) MSN_SetMirVer(hContact, &cont->places[0]); } ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, NULL, tszStatus); } #ifdef OBSOLETE // Process current media info const char* szCrntMda = ezxml_txt(ezxml_child(xmli, "CurrentMedia")); if (!*szCrntMda) { delSetting(hContact, "ListeningTo"); return; } // Get parts separeted by "\\0" char *parts[16]; unsigned pCount; char* p = (char*)szCrntMda; for (pCount = 0; pCount < _countof(parts); ++pCount) { parts[pCount] = p; char* p1 = strstr(p, "\\0"); if (p1 == NULL) break; *p1 = '\0'; p = p1 + 2; } // Now let's mount the final string if (pCount <= 4) { delSetting(hContact, "ListeningTo"); return; } // Check if there is any info in the string bool foundUsefullInfo = false; for (unsigned i = 4; i < pCount; i++) { if (parts[i][0] != '\0') { foundUsefullInfo = true; break; } } if (!foundUsefullInfo) { delSetting(hContact, "ListeningTo"); return; } if (!ServiceExists(MS_LISTENINGTO_GETPARSEDTEXT) || !ServiceExists(MS_LISTENINGTO_OVERRIDECONTACTOPTION) || !CallService(MS_LISTENINGTO_OVERRIDECONTACTOPTION, 0, hContact)) { // User contact options char *format = mir_strdup(parts[3]); char *unknown = NULL; if (ServiceExists(MS_LISTENINGTO_GETUNKNOWNTEXT)) unknown = mir_utf8encodeW((wchar_t *)CallService(MS_LISTENINGTO_GETUNKNOWNTEXT, 0, 0)); for (unsigned i = 4; i < pCount; i++) { char part[16]; size_t lenPart = mir_snprintf(part, "{%d}", i - 4); if (parts[i][0] == '\0' && unknown != NULL) parts[i] = unknown; size_t lenPartsI = mir_strlen(parts[i]); for (p = strstr(format, part); p; p = strstr(p + lenPartsI, part)) { if (lenPart < lenPartsI) { int loc = p - format; format = (char *)mir_realloc(format, mir_strlen(format) + (lenPartsI - lenPart) + 1); p = format + loc; } memmove(p + lenPartsI, p + lenPart, mir_strlen(p + lenPart) + 1); memmove(p, parts[i], lenPartsI); } } setStringUtf(hContact, "ListeningTo", format); mir_free(unknown); mir_free(format); } else { // Use user options LISTENINGTOINFO lti = { 0 }; lti.cbSize = sizeof(LISTENINGTOINFO); lti.ptszTitle = mir_utf8decodeW(parts[4]); if (pCount > 5) lti.ptszArtist = mir_utf8decodeW(parts[5]); if (pCount > 6) lti.ptszAlbum = mir_utf8decodeW(parts[6]); if (pCount > 7) lti.ptszTrack = mir_utf8decodeW(parts[7]); if (pCount > 8) lti.ptszYear = mir_utf8decodeW(parts[8]); if (pCount > 9) lti.ptszGenre = mir_utf8decodeW(parts[9]); if (pCount > 10) lti.ptszLength = mir_utf8decodeW(parts[10]); if (pCount > 11) lti.ptszPlayer = mir_utf8decodeW(parts[11]); else lti.ptszPlayer = mir_utf8decodeW(parts[0]); if (pCount > 12) lti.ptszType = mir_utf8decodeW(parts[12]); else lti.ptszType = mir_utf8decodeW(parts[1]); wchar_t *cm = (wchar_t *)CallService(MS_LISTENINGTO_GETPARSEDTEXT, (WPARAM)L"%title% - %artist%", (LPARAM)<i); setWString(hContact, "ListeningTo", cm); mir_free(cm); mir_free(lti.ptszArtist); mir_free(lti.ptszAlbum); mir_free(lti.ptszTitle); mir_free(lti.ptszTrack); mir_free(lti.ptszYear); mir_free(lti.ptszGenre); mir_free(lti.ptszLength); mir_free(lti.ptszPlayer); mir_free(lti.ptszType); } #endif } void CMsnProto::MSN_ProcessNotificationMessage(char* buf, size_t len) { if (buf == NULL) return; ezxml_t xmlnot = ezxml_parse_str(buf, len); if (mir_strcmp(ezxml_attr(xmlnot, "siteid"), "0") == 0) { ezxml_free(xmlnot); return; } ezxml_t xmlmsg = ezxml_child(xmlnot, "MSG"); ezxml_t xmlact = ezxml_child(xmlmsg, "ACTION"); ezxml_t xmlbdy = ezxml_child(xmlmsg, "BODY"); ezxml_t xmltxt = ezxml_child(xmlbdy, "TEXT"); if (xmltxt != NULL) { char fullurl[1024]; size_t sz = 0; const char* acturl = ezxml_attr(xmlact, "url"); if (acturl == NULL || strstr(acturl, "://") == NULL) sz += mir_snprintf((fullurl + sz), (_countof(fullurl) - sz), "%s", ezxml_attr(xmlnot, "siteurl")); sz += mir_snprintf((fullurl + sz), (_countof(fullurl) - sz), "%s", acturl); if (sz != 0 && fullurl[sz - 1] != '?') sz += mir_snprintf((fullurl + sz), (_countof(fullurl) - sz), "?"); mir_snprintf((fullurl + sz), (_countof(fullurl) - sz), "notification_id=%s&message_id=%s", ezxml_attr(xmlnot, "id"), ezxml_attr(xmlmsg, "id")); SkinPlaySound(alertsoundname); wchar_t* alrt = mir_utf8decodeW(ezxml_txt(xmltxt)); MSN_ShowPopup(TranslateT("MSN Alert"), alrt, MSN_ALERT_POPUP | MSN_ALLOW_MSGBOX, fullurl); mir_free(alrt); } else if (xmlbdy) { const char *txt = ezxml_txt(xmlbdy); if (strstr(txt, "ABCHInternal")) { MSN_SharingFindMembership(true); MSN_ABFind("ABFindContactsPaged", NULL, true); MSN_StoreGetProfile(); } } ezxml_free(xmlnot); } int CMsnProto::MSN_HandleCommands(ThreadData* info, char* cmdString) { char* params = ""; int trid = -1; if (cmdString[3]) { if (isdigit((BYTE)cmdString[4])) { trid = strtol(cmdString + 4, ¶ms, 10); switch (*params) { case ' ': case '\0': case '\t': case '\n': while (*params == ' ' || *params == '\t') params++; break; default: params = cmdString + 4; } } else params = cmdString + 4; } switch((*(PDWORD)cmdString & 0x00FFFFFF) | 0x20000000) { case ' SBS': break; case ' HTA': //********* ATH: MSNP21+ Authentication { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) { LBL_InvalidCommand: debugLogA("Invalid %.3s command, ignoring", cmdString); break; } HReadBuffer buf(info, 0); buf.surelyRead(atol(data.strMsgBytes)); if (!bIgnoreATH) { if (!bSentBND) { info->sendPacketPayload("BND", "CON\\MSGR", "%d%s%s%s" "%.*s\r\n", msnP24Ver, (msnP24Ver>1?"1":""), msnStoreAppId, msnProductVer, mir_strlen(MyOptions.szMachineGuid)-2, MyOptions.szMachineGuid+1); bSentBND = true; } else { msnLoggedIn = true; isConnectSuccess = true; emailEnabled = MyOptions.netId==NETID_MSN; // Let's assume it? MSN_SetServerStatus(m_iDesiredStatus); MSN_EnableMenuItems(true); // Fork refreshing and populating contact list to the background ForkThread(&CMsnProto::msn_loginThread, NULL); } } else bIgnoreATH = false; } break; case ' DNB': //********* BND: MSNP21+ bind request answer? { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) goto LBL_InvalidCommand; MimeHeaders tHeader; HReadBuffer buf(info, 0); BYTE *msgb = buf.surelyRead(atol(data.strMsgBytes)); if (!msgb) break; char* msgBody = tHeader.readFromBuffer((char*)msgb); replaceStr(msnRegistration,tHeader["Set-Registration"]); if (!mir_strcmp(data.typeId, "CON")) { ezxml_t xmlbnd = ezxml_parse_str(msgBody, mir_strlen(msgBody)); ezxml_t xmlbdy = ezxml_child(xmlbnd, "nonce"); if (xmlbdy) { char dgst[64]; MSN_MakeDigest(xmlbdy->txt, dgst); info->sendPacketPayload("PUT", "MSGR\\CHALLENGE", "%s%s\r\n", msnProductID, dgst); } ezxml_free(xmlbnd); } } break; case ' TNC': //********* CNT: Connect, MSNP21+ Authentication { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) goto LBL_InvalidCommand; HReadBuffer buf(info, 0); char* msgBody = (char*)buf.surelyRead(atol(data.strMsgBytes)); if (mir_strcmp(data.typeId, "CON")) break; if (MyOptions.netId!=NETID_SKYPE) { /* MSN account login */ if (MSN_AuthOAuth()<1) { m_iDesiredStatus = ID_STATUS_OFFLINE; return 1; } } else { /* Skype username/pass login */ if (!msgBody) break; ezxml_t xmlcnt = ezxml_parse_str(msgBody, mir_strlen(msgBody)); ezxml_t xmlnonce = ezxml_child(xmlcnt, "nonce"); if (xmlnonce) { char szUIC[1024]={0}; MSN_SkypeAuth(xmlnonce->txt, szUIC); replaceStr(authUIC, szUIC); } ezxml_free(xmlcnt); } MSN_SendATH(info); bSentBND = false; if (!hKeepAliveThreadEvt) ForkThread(&CMsnProto::msn_keepAliveThread, NULL); #ifdef OBSOLETE /* FIXME: Currently disabled, as P2P maybe not working anymore in MSNP24? */ ForkThread(&CMsnProto::MSNConnDetectThread, NULL); #endif } break; case ' TEG': //********* GET: MSNP21+ GET reply { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) goto LBL_InvalidCommand; MimeHeaders tHeader; HReadBuffer buf(info, 0); BYTE *msgb = buf.surelyRead(atol(data.strMsgBytes)); if (!msgb) break; char* msgBody = tHeader.readFromBuffer((char*)msgb); ezxml_t xmli; if (tHeader["Set-Registration"]) replaceStr(msnRegistration,tHeader["Set-Registration"]); if (xmli = ezxml_parse_str(msgBody, mir_strlen(msgBody))) { if (!mir_strcmp(xmli->name, "recentconversations-response")) { for (ezxml_t conv = ezxml_get(xmli, "conversations", 0, "conversation", -1); conv != NULL; conv = ezxml_next(conv)) { ezxml_t id; MCONTACT hContact; MEVENT hDbEvent; DWORD ts = 1; if (ezxml_get(conv, "messages", 0, "message", -1) && (id=ezxml_child(conv, "id"))) { if (strncmp(id->txt, "19:", 3) == 0) { /* This is a thread (Groupchat) * Find out about its current state on the server */ hContact = MSN_HContactFromChatID(id->txt); msnNsThread->sendPacketPayload("GET", "MSGR\\THREADS", "%s", id->txt); } else hContact = MSN_HContactFromEmail(id->txt, NULL, false, false); if (hContact) { // There are messages to be fetched if (hDbEvent = db_event_last(hContact)) { DBEVENTINFO dbei = { sizeof(dbei) }; db_event_get(hDbEvent, &dbei); ts = dbei.timestamp; } db_set_dw(hContact, m_szModuleName, "syncTS", ts); } msnNsThread->sendPacketPayload("GET", "MSGR\\MESSAGESBYCONVERSATION", "%s%llu100", id->txt, ((unsigned __int64)ts)*1000); } } } else if (!mir_strcmp(xmli->name, "messagesbyconversation-response")) { ezxml_t id; MCONTACT hContact; if ((id=ezxml_child(xmli, "id"))) { bool bIsChat = strncmp(id->txt, "19:", 3)==0; bool bHasMore = mir_strcmpi(ezxml_txt(ezxml_child(xmli, "hasmore")), "true") == 0; ezxml_t syncstate; hContact = MSN_HContactFromEmail(id->txt, NULL, false, false); if (!bHasMore && hContact) db_unset(hContact, m_szModuleName, "syncTS"); /* We have to traverse the list in reverse order as newest events are on top (which is the opposite direction of Groupchat) */ LIST msgs(10,PtrKeySortT); for (ezxml_t msg = ezxml_get(xmli, "messages", 0, "message", -1); msg != NULL; msg = ezxml_next(msg)) msgs.insert(msg, 0); for (int i=0; itxt); parseWLID(NEWSTR_ALLOCA(from->txt), &netId, &email, NULL); message=content->txt; sentMsg = mir_strcmpi(email, GetMyUsername(atoi(netId)))==0; if (msgtype) { if (!mir_strcmp(msgtype->txt, "RichText")) { message = NEWSTR_ALLOCA(message); stripHTML(message); HtmlDecode(message); } else if (!strncmp(msgtype->txt, "ThreadActivity/", 15)) { if (ezxml_t xmlact = ezxml_parse_str(content->txt, mir_strlen(content->txt))) { MSN_GCProcessThreadActivity(xmlact, _A2T(id->txt)); ezxml_free(xmlact); } continue; } else if (!mir_strcmp(msgtype->txt, "RichText/UriObject")) { if (ezxml_t xmlact = ezxml_parse_str(content->txt, mir_strlen(content->txt))) { MSN_ProcessURIObject(hContact, xmlact); ezxml_free(xmlact); } continue; } else if (mir_strcmp(msgtype->txt, "Text")) continue; /* TODO: Implement i.e. RichText/Files for announcement of file transfers */ } if (bIsChat) { hContact = MSN_HContactFromEmail(from->txt, NULL, false, false); if (hContact) db_unset(hContact, m_szModuleName, "syncTS"); MSN_GCAddMessage(_A2T(id->txt), hContact, email, ts, sentMsg, message); } else if (hContact) { /* Protect against double sync (Miranda MSGs only have granularity in seconds) */ MEVENT hDbEvent; bool bDuplicate = false; DBEVENTINFO dbei = { sizeof(dbei) }; DWORD cbBlob = (DWORD)mir_strlen(message); dbei.cbBlob = cbBlob; BYTE *pszMsgBuf = (BYTE*)mir_calloc(cbBlob); if (pszMsgBuf) { dbei.pBlob = pszMsgBuf; for((hDbEvent = db_event_last(hContact)); !bDuplicate && hDbEvent; hDbEvent=db_event_prev(hContact, hDbEvent)) { if (db_event_get(hDbEvent, &dbei) || dbei.timestamp > ts+1 || dbei.timestamp < ts) break; if (!memcmp((char*)dbei.pBlob, message, cbBlob)) bDuplicate = true; dbei.cbBlob = cbBlob; } mir_free(pszMsgBuf); if (bDuplicate) continue; } if (!sentMsg) { PROTORECVEVENT pre = { 0 }; pre.szMessage = (char*)message; pre.timestamp = (DWORD)ts; ProtoChainRecvMsg(hContact, &pre); } else { memset(&dbei, 0, sizeof(dbei)); dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = DBEF_SENT | DBEF_UTF; dbei.szModule = m_szModuleName; dbei.timestamp = ts; dbei.cbBlob = (unsigned)mir_strlen(message) + 1; dbei.pBlob = (PBYTE)message; db_event_add(hContact, &dbei); } } } /* In groupchat it wouldn't make much sense to sync more as older messages are coming now and that would jumble our log */ if (!bIsChat && bHasMore && (syncstate = ezxml_child(xmli, "messagessyncstate"))) { msnNsThread->sendPacketPayload("GET", "MSGR\\MESSAGESBYCONVERSATION", "%s%llu%s100", id->txt, ((unsigned __int64)db_get_dw(hContact, m_szModuleName, "syncTS", 1000))*1000, syncstate->txt); } msgs.destroy(); } } else if (!mir_strcmp(xmli->name, "threads-response")) { for (ezxml_t thread = ezxml_get(xmli, "threads", 0, "thread", -1); thread != NULL; thread = ezxml_next(thread)) MSN_ChatStart(thread); } ezxml_free(xmli); } } break; case ' YFN': //********* NFY: MSNP21+ Notifications { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) goto LBL_InvalidCommand; HReadBuffer buf(info, 0); char* msgBody = (char*)buf.surelyRead(atol(data.strMsgBytes)); if (msgBody == NULL) break; if (!mir_strcmp(data.typeId, "MSGR\\HOTMAIL")) { char szParam[128]; mir_snprintf(szParam, sizeof(szParam), "%s %s", data.typeId, data.strMsgBytes); MSN_ReceiveMessage(info, cmdString, szParam); break; } else if (!mir_strcmp(data.typeId, "MSGR\\ABCH")) { MimeHeaders tHeader; msgBody = tHeader.readFromBuffer(msgBody); MSN_ProcessNotificationMessage(msgBody, mir_strlen(msgBody)); break; } if (!mir_strcmp(data.typeId, "MSGR\\PUT") || !mir_strcmp(data.typeId, "MSGR\\DEL")) { MimeHeaders tHeader; int i; for (i=0; i<2; i++) msgBody = tHeader.readFromBuffer(msgBody); char *pszTo = NULL, *pszToNet; if (tHeader["To"]) parseWLID(NEWSTR_ALLOCA(tHeader["To"]), &pszToNet, &pszTo, NULL); const char *pszFrom = tHeader["From"]; for (i=0; i<2; i++) msgBody = tHeader.readFromBuffer(msgBody); if (pszFrom) { ezxml_t xmli; if (xmli = ezxml_parse_str(msgBody, mir_strlen(msgBody))) { if (!mir_strcmp(xmli->name, "user")) { ezxml_t xmlstatus = ezxml_get(xmli, "s", 0, "Status", -1); /* FIXME: MSGR\DEL: Instance of user with given EPID disconnected, not * sure if this implies that contact is offline now... */ if (xmlstatus || !mir_strcmp(data.typeId, "MSGR\\DEL")) { // These capabilities seem to be something different than in previous MSNP versions? //ezxml_t xmlcaps = ezxml_get(xmli, "sep", 0, "Capabilities", -1); ezxml_t usertile = ezxml_get(xmli, "s", 1, "UserTileLocation", -1); MSN_ProcessNLN(ezxml_txt(xmlstatus), pszFrom, NULL, NULL, usertile?usertile->txt:NULL); } MSN_ProcessStatusMessage(xmli, pszFrom); } ezxml_free(xmli); } } } else if (!mir_strcmp(data.typeId, "MSGR\\THREAD")) { MimeHeaders tHeader; char *szBody = tHeader.readFromBuffer(info->mData); ezxml_t xmli = ezxml_parse_str(szBody, mir_strlen(szBody)); if (xmli) { MSN_ChatStart(xmli); ezxml_free(xmli); } } } break; case ' TUP': //******** MSNP21+: PUT notifications case ' GNP': //******** MSNP21+: PNG reply { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) goto LBL_InvalidCommand; MimeHeaders tHeader; HReadBuffer buf(info, 0); BYTE *msgb = buf.surelyRead(atol(data.strMsgBytes)); if (!msgb) break; char* msgBody = tHeader.readFromBuffer((char*)msgb); if (tHeader["Set-Registration"]) replaceStr(msnRegistration,tHeader["Set-Registration"]); if (cmdString[1]=='N') { // PNG if (ezxml_t xmli = ezxml_parse_str(msgBody, mir_strlen(msgBody))) { if (ezxml_t wait = ezxml_child(xmli, "wait")) { msnPingTimeout = atoi(ezxml_txt(wait)); if (msnPingTimeout && hKeepAliveThreadEvt != NULL) SetEvent(hKeepAliveThreadEvt); } ezxml_free(xmli); } } else { // PUT ezxml_t xmli; if (*msgBody && (xmli = ezxml_parse_str(msgBody, mir_strlen(msgBody)))) { if (!mir_strcmp(xmli->name, "presence-response")) { ezxml_t user, from; if ((user = ezxml_child(xmli, "user")) && (from = ezxml_child(xmli, "from"))) { if (ezxml_t xmlstatus = ezxml_get(user, "s", 0, "Status", -1)) { ezxml_t usertile = ezxml_get(user, "s", 1, "UserTileLocation", -1); MSN_ProcessNLN(ezxml_txt(xmlstatus), from->txt, NULL, NULL, usertile?usertile->txt:NULL); } else { int oldMode = m_iStatus; m_iStatus = m_iDesiredStatus; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldMode, m_iStatus); } MSN_ProcessStatusMessage(user, from->txt); } } ezxml_free(xmli); } } } break; case ' GDS': // SDG: MSNP21+ Messaging MSN_ReceiveMessage(info, cmdString, params); break; case ' RFX': //******** XFR: sections 7.4 Referral, 8.1 Referral to Switchboard { union { char* tWords[2]; struct { char *typeId, *strMsgBytes; } data; }; if (sttDivideWords(params, _countof(tWords), tWords) < 2) goto LBL_InvalidCommand; MimeHeaders tHeader; HReadBuffer buf(info, 0); BYTE *msgb = buf.surelyRead(atol(data.strMsgBytes)); if (!msgb) break; char* msgBody = tHeader.readFromBuffer((char*)msgb); if (!mir_strcmp(data.typeId, "CON")) { ezxml_t xmlxfr = ezxml_parse_str(msgBody, mir_strlen(msgBody)); ezxml_t xmltgt = ezxml_child(xmlxfr, "target"); if (xmltgt) { ThreadData* newThread = new ThreadData; mir_strcpy(newThread->mServer, xmltgt->txt); mir_strcpy(newThread->mState, ezxml_txt(ezxml_child(xmlxfr, "state"))); newThread->mType = SERVER_NOTIFICATION; newThread->mTrid = info->mTrid; newThread->mIsMainThread = true; info->mIsMainThread = false; debugLogA("Switching to notification server '%s'...", xmltgt->txt); newThread->startThread(&CMsnProto::MSNServerThread, this); ezxml_free(xmlxfr); return 1; //kill the old thread } ezxml_free(xmlxfr); } } break; default: debugLogA("Unrecognised message: %s", cmdString); break; } return 0; } #ifdef OBSOLETE ///////////////////////////////////////////////////////////////////////////////////////// // Starts a file sending thread void MSN_ConnectionProc(HANDLE hNewConnection, DWORD /* dwRemoteIP */, void* extra) { CMsnProto *proto = (CMsnProto*)extra; proto->debugLogA("File transfer connection accepted"); NETLIBCONNINFO connInfo = { sizeof(connInfo) }; CallService(MS_NETLIB_GETCONNECTIONINFO, (WPARAM)hNewConnection, (LPARAM)&connInfo); ThreadData* T = proto->MSN_GetThreadByPort(connInfo.wPort); if (T != NULL && T->s == NULL) { T->s = hNewConnection; ReleaseSemaphore(T->hWaitEvent, 1, NULL); } else { proto->debugLogA("There's no registered file transfers for incoming port #%u, connection closed", connInfo.wPort); Netlib_CloseHandle(hNewConnection); } } ///////////////////////////////////////////////////////////////////////////////////////// // Processes various invitations void CMsnProto::MSN_InviteMessage(ThreadData* info, char* msgBody, char* email, char* nick) { MimeHeaders tFileInfo; tFileInfo.readFromBuffer(msgBody); const char* Appname = tFileInfo["Application-Name"]; const char* AppGUID = tFileInfo["Application-GUID"]; const char* Invcommand = tFileInfo["Invitation-Command"]; const char* Invcookie = tFileInfo["Invitation-Cookie"]; const char* Appfile = tFileInfo["Application-File"]; const char* Appfilesize = tFileInfo["Application-FileSize"]; const char* IPAddress = tFileInfo["IP-Address"]; const char* IPAddressInt = tFileInfo["IP-Address-Internal"]; const char* Port = tFileInfo["Port"]; const char* PortXInt = tFileInfo["PortX-Internal"]; const char* AuthCookie = tFileInfo["AuthCookie"]; const char* SessionID = tFileInfo["Session-ID"]; const char* SessionProtocol = tFileInfo["Session-Protocol"]; // const char* Connectivity = tFileInfo["Connectivity"]; if (AppGUID != NULL) { if (!mir_strcmp(AppGUID, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { MSN_ShowPopup(info->getContactHandle(), TranslateT("Contact tried to open an audio conference (not currently supported)"), MSN_ALLOW_MSGBOX); return; } } if (Invcommand && (mir_strcmp(Invcommand, "CANCEL") == 0)) { delete info->mMsnFtp; info->mMsnFtp = NULL; } if (Appname != NULL && Appfile != NULL && Appfilesize != NULL) // receive first { filetransfer* ft = info->mMsnFtp = new filetransfer(this); ft->std.hContact = MSN_HContactFromEmail(email, nick, true, true); mir_free(ft->std.tszCurrentFile); ft->std.tszCurrentFile = mir_utf8decodeW(Appfile); ft->std.totalBytes = ft->std.currentFileSize = _atoi64(Appfilesize); ft->std.totalFiles = 1; ft->szInvcookie = mir_strdup(Invcookie); ft->p2p_dest = mir_strdup(email); wchar_t tComment[40]; mir_snwprintf(tComment, TranslateT("%I64u bytes"), ft->std.currentFileSize); PROTORECVFILET pre = { 0 }; pre.dwFlags = PRFF_UNICODE; pre.fileCount = 1; pre.timestamp = time(NULL); pre.descr.w = tComment; pre.files.w = &ft->std.tszCurrentFile; pre.lParam = (LPARAM)ft; ProtoChainRecvFile(ft->std.hContact, &pre); return; } // receive Second if (IPAddress != NULL && Port != NULL && AuthCookie != NULL) { ThreadData* newThread = new ThreadData; if (inet_addr(IPAddress) != MyConnection.extIP || !IPAddressInt) mir_snprintf(newThread->mServer, "%s:%s", IPAddress, Port); else mir_snprintf(newThread->mServer, "%s:%u", IPAddressInt, atol(PortXInt) ^ 0x3141); newThread->mType = SERVER_FILETRANS; if (info->mMsnFtp == NULL) { ThreadData* otherThread = MSN_GetOtherContactThread(info); if (otherThread) { info->mMsnFtp = otherThread->mMsnFtp; otherThread->mMsnFtp = NULL; } } newThread->mMsnFtp = info->mMsnFtp; info->mMsnFtp = NULL; mir_strcpy(newThread->mCookie, AuthCookie); newThread->startThread(&CMsnProto::MSNServerThread, this); return; } // send 1 if (Invcommand != NULL && Invcookie != NULL && Port == NULL && AuthCookie == NULL && SessionID == NULL) { msnftp_startFileSend(info, Invcommand, Invcookie); return; } // netmeeting send 1 if (Appname == NULL && SessionID != NULL && SessionProtocol != NULL) { if (!_stricmp(Invcommand, "ACCEPT")) { ShellExecuteA(NULL, "open", "conf.exe", NULL, NULL, SW_SHOW); Sleep(3000); info->sendPacketPayload("MSG", "N", "MIME-Version: 1.0\r\n" "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n" "Invitation-Command: ACCEPT\r\n" "Invitation-Cookie: %s\r\n" "Session-ID: {1A879604-D1B8-11D7-9066-0003FF431510}\r\n" "Launch-Application: TRUE\r\n" "IP-Address: %s\r\n\r\n", Invcookie, MyConnection.GetMyExtIPStr()); } return; } // netmeeting receive 1 if (Appname != NULL && !_stricmp(Appname, "NetMeeting")) { wchar_t text[512], *tszEmail = mir_a2u(email); mir_snwprintf(text, TranslateT("Accept NetMeeting request from %s?"), tszEmail); mir_free(tszEmail); if (MessageBox(NULL, text, TranslateT("MSN Protocol"), MB_YESNO | MB_ICONQUESTION) == IDYES) { info->sendPacketPayload("MSG", "N", "MIME-Version: 1.0\r\n" "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n" "Invitation-Command: ACCEPT\r\n" "Invitation-Cookie: %s\r\n" "Session-ID: {A2ED5ACF-F784-4B47-A7D4-997CD8F643CC}\r\n" "Session-Protocol: SM1\r\n" "Launch-Application: TRUE\r\n" "Request-Data: IP-Address:\r\n" "IP-Address: %s\r\n\r\n", Invcookie, MyConnection.GetMyExtIPStr()); } else { info->sendPacketPayload("MSG", "N", "MIME-Version: 1.0\r\n" "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n" "Invitation-Command: CANCEL\r\n" "Invitation-Cookie: %s\r\n" "Cancel-Code: REJECT\r\n\r\n", Invcookie); } return; } if (IPAddress != NULL && Port == NULL && SessionID != NULL && SessionProtocol == NULL) { // netmeeting receive 2 char ipaddr[256]; mir_snprintf(ipaddr, "callto://%s", IPAddress); ShellExecuteA(NULL, "open", ipaddr, NULL, NULL, SW_SHOW); } } void CMsnProto::MSN_ProcessRemove(char* buf, size_t len) { ezxml_t xmli = ezxml_parse_str(buf, len); ezxml_t dom = ezxml_child(xmli, "d"); while (dom != NULL) { const char* szDom = ezxml_attr(dom, "n"); ezxml_t cont = ezxml_child(dom, "c"); while (cont != NULL) { const char* szCont = ezxml_attr(cont, "n"); int listId = atol(ezxml_attr(cont, "l")); char szEmail[128]; mir_snprintf(szEmail, "%s@%s", szCont, szDom); Lists_Remove(listId, szEmail); MsnContact* msc = Lists_Get(szEmail); if (msc == NULL || (msc->list & (LIST_RL | LIST_FL | LIST_LL)) == 0) { if (msc->hContact && _stricmp(szEmail, MyOptions.szEmail)) { db_delete_contact(msc->hContact); msc->hContact = NULL; } } cont = ezxml_next(cont); } dom = ezxml_next(dom); } ezxml_free(xmli); } ///////////////////////////////////////////////////////////////////////////////////////// // Processes custom smiley messages void CMsnProto::MSN_CustomSmiley(const char* msgBody, char* email, char* nick, int iSmileyType) { MCONTACT hContact = MSN_HContactFromEmail(email, nick, true, true); char smileyList[500] = ""; const char *tok1 = msgBody, *tok2; char *smlp = smileyList; char lastsml[50]; unsigned iCount = 0; bool parseSmiley = true; for (;;) { tok2 = strchr(tok1, '\t'); if (tok2 == NULL) break; size_t sz = tok2 - tok1; if (parseSmiley) { sz = min(sz, sizeof(lastsml)-1); memcpy(lastsml, tok1, sz); lastsml[sz] = 0; memcpy(smlp, tok1, sz); smlp += sz; *(smlp++) = '\n'; *smlp = 0; ++iCount; } else { filetransfer* ft = new filetransfer(this); ft->std.hContact = hContact; ft->p2p_object = (char*)mir_alloc(sz + 1); memcpy(ft->p2p_object, tok1, sz); ft->p2p_object[sz] = 0; size_t slen = mir_strlen(lastsml); ptrA buf(mir_base64_encode((PBYTE)lastsml, (unsigned)slen)); ptrA smileyName(mir_urlEncode(buf)); wchar_t path[MAX_PATH]; MSN_GetCustomSmileyFileName(hContact, path, _countof(path), smileyName, iSmileyType); ft->std.tszCurrentFile = mir_wstrdup(path); if (p2p_IsDlFileOk(ft)) delete ft; else { debugLogA("Custom Smiley p2p invite for object : %s", ft->p2p_object); p2p_invite(iSmileyType, ft, email); Sleep(3000); } } parseSmiley = !parseSmiley; tok1 = tok2 + 1; } } ///////////////////////////////////////////////////////////////////////////////////////// // Process user addition void CMsnProto::MSN_ProcessAdd(char* buf, size_t len) { if (buf == NULL) return; ezxml_t xmli = ezxml_parse_str(buf, len); ezxml_t dom = ezxml_child(xmli, "d"); while (dom != NULL) { const char* szDom = ezxml_attr(dom, "n"); ezxml_t cont = ezxml_child(dom, "c"); while (cont != NULL) { const char* szCont = ezxml_attr(cont, "n"); const char* szNick = ezxml_attr(cont, "f"); int listId = atol(ezxml_attr(cont, "l")); int netId = atol(ezxml_attr(cont, "t")); char szEmail[128]; mir_snprintf(szEmail, "%s@%s", szCont, szDom); UrlDecode((char*)szNick); if (listId == LIST_FL) { MCONTACT hContact = MSN_HContactFromEmail(szEmail, szNick, true, false); MSN_SetContactDb(hContact, szEmail); } if (listId == LIST_RL) MSN_SharingFindMembership(true); else MSN_AddUser(NULL, szEmail, netId, listId); MsnContact* msc = Lists_Get(szEmail); if (msc == NULL) { Lists_Add(listId, netId, szEmail); msc = Lists_Get(szEmail); } if (listId == LIST_RL) { if ((msc->list & (LIST_AL | LIST_BL)) == 0) { MSN_AddAuthRequest(szEmail, szNick, msc->invite); msc->netId = netId; } else MSN_AddUser(NULL, szEmail, netId, LIST_PL + LIST_REMOVE); } cont = ezxml_next(cont); } dom = ezxml_next(dom); } ezxml_free(xmli); } void CMsnProto::MSN_ProcessPage(char* buf, unsigned len) { if (buf == NULL) return; ezxml_t xmlnot = ezxml_parse_str(buf, len); ezxml_t xmlbdy = ezxml_get(xmlnot, "MSG", 0, "BODY", -1); const char* szMsg = ezxml_txt(ezxml_child(xmlbdy, "TEXT")); const char* szTel = ezxml_attr(ezxml_child(xmlnot, "FROM"), "name"); if (szTel && *szMsg) { PROTORECVEVENT pre = { 0 }; pre.szMessage = (char*)szMsg; pre.flags = PREF_UTF /*+ ((isRtl) ? PREF_RTL : 0)*/; pre.timestamp = time(NULL); ProtoChainRecvMsg(MSN_HContactFromEmail(szTel, szTel, true, true), &pre); } ezxml_free(xmlnot); } void CMsnProto::MSN_InitSB(ThreadData* info, const char* szEmail) { MsnContact *cont = Lists_Get(szEmail); if (cont->netId == NETID_MSN) info->sendCaps(); bool typing = false; for (int i = 3; --i;) { MsgQueueEntry E; while (MsgQueue_GetNext(szEmail, E)) { if (E.msgType == 'X') ; else if (E.msgType == 2571) typing = E.flags != 0; else if (E.msgSize == 0) { info->sendMessage(E.msgType, NULL, 1, E.message, E.flags); ProtoBroadcastAck(cont->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)E.seq, 0); } else { if (E.msgType == 'D' && !info->mBridgeInit) { //&& strchr(data.flags, ':')) { info->mBridgeInit = true; // P2PV2_Header hdrdata(E.message); // P2PV2_Header tHdr; // tHdr.mID = hdrdata.mID; // p2p_sendMsg(info, E.wlid, 0, tHdr, NULL, 0); } info->sendRawMessage(E.msgType, E.message, E.msgSize); } mir_free(E.message); mir_free(E.wlid); if (E.ft != NULL) info->mMsnFtp = E.ft; } mir_free(info->mInitialContactWLID); info->mInitialContactWLID = NULL; } if (typing) MSN_StartStopTyping(info, true); if (getByte("EnableDeliveryPopup", 0)) MSN_ShowPopup(cont->hContact, info->mCaller ? TranslateT("Chat session established by my request") : TranslateT("Chat session established by contact request"), 0); PROTO_AVATAR_INFORMATION ai = { 0 }; ai.hContact = cont->hContact; GetAvatarInfo(GAIF_FORCE, (LPARAM)&ai); } int CMsnProto::MSN_HandleCommands(ThreadData* info, char* cmdString) { char* params = ""; int trid = -1; if (cmdString[3]) { if (isdigit((BYTE)cmdString[4])) { trid = strtol(cmdString + 4, ¶ms, 10); switch (*params) { case ' ': case '\0': case '\t': case '\n': while (*params == ' ' || *params == '\t') params++; break; default: params = cmdString + 4; } } else params = cmdString + 4; } switch((*(PDWORD)cmdString & 0x00FFFFFF) | 0x20000000) { case ' KCA': //********* ACK: section 8.7 Instant Messages ReleaseSemaphore(info->hWaitEvent, 1, NULL); if (info->mJoinedContactsWLID.getCount() > 0 && MyOptions.SlowSend) ProtoBroadcastAck(info->getContactHandle(), ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)trid, 0); break; case ' YQF': //********* FQY: Find Yahoo User char* tWords[1]; if (sttDivideWords(params, 1, tWords) != 1) debugLogA("Invalid %.3s command, ignoring", cmdString); else { size_t len = atol(tWords[0]); MSN_ProcessYFind((char*)HReadBuffer(info, 0).surelyRead(len), len); } break; case ' LDA': //********* ADL: Add to the list { char* tWords[1]; if (sttDivideWords(params, 1, tWords) != 1) { LBL_InvalidCommand: debugLogA("Invalid %.3s command, ignoring", cmdString); break; } if (mir_strcmp(tWords[0], "OK") != 0) { size_t len = atol(tWords[0]); MSN_ProcessAdd((char*)HReadBuffer(info, 0).surelyRead(len), len); } } break; case ' SBS': break; case ' SNA': //********* ANS: section 8.4 Getting Invited to a Switchboard Session break; case ' PRP': break; case ' PLB': //********* BLP: section 7.6 List Retrieval And Property Management break; case ' EYB': //********* BYE: section 8.5 Session Participant Changes { union { char* tWords[2]; // modified for chat, orginally param2 = junk // param 2: quit due to idle = "1", normal quit = nothing struct { char *userEmail, *isIdle; } data; }; sttDivideWords(params, 2, tWords); UrlDecode(data.userEmail); if (strchr(data.userEmail, ';')) { if (info->mJoinedContactsWLID.getCount() == 1) p2p_clearThreadSessions((MCONTACT)info->mJoinedContactsWLID[0], info->mType); info->contactLeft(data.userEmail); break; } MCONTACT hContact = MSN_HContactFromEmail(data.userEmail); if (getByte("EnableSessionPopup", 0)) MSN_ShowPopup(hContact, TranslateT("Contact left channel"), 0); // modified for chat { GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_QUIT }; GCEVENT gce = { sizeof(gce), &gcd }; gce.dwFlags = GCEF_ADDTOLOG; gce.ptszNick = GetContactNameT(hContact); gce.ptszUID = mir_a2u(data.userEmail); gce.time = time(NULL); gce.bIsMe = FALSE; CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce); mir_free((void*)gce.ptszUID); } int personleft = info->contactLeft(data.userEmail); int temp_status = getWord(hContact, "Status", ID_STATUS_OFFLINE); if (temp_status == ID_STATUS_INVISIBLE && MSN_GetThreadByContact(data.userEmail) == NULL) setWord(hContact, "Status", ID_STATUS_OFFLINE); // see if the session is quit due to idleness if (info->mChatID[0] && personleft == 1) { if (!mir_strcmp(data.isIdle, "1")) { GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_INFORMATION }; GCEVENT gce = { sizeof(gce), &gcd }; gce.dwFlags = GCEF_ADDTOLOG; gce.bIsMe = FALSE; gce.time = time(NULL); gce.ptszText = TranslateT("This conversation has been inactive, participants will be removed."); CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce); gce.ptszText = TranslateT("To resume the conversation, please quit this session and start a new chat session."); CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce); } else { if (!g_bTerminated && MessageBox(NULL, TranslateT("There is only 1 person left in the chat, do you want to switch back to standard message window?"), TranslateT("MSN Chat"), MB_YESNO | MB_ICONQUESTION) == IDYES) { // kill chat dlg and open srmm dialog MSN_KillChatSession(info->mChatID); // open up srmm dialog when quit while 1 person left MCONTACT hContact = info->getContactHandle(); if (hContact) CallServiceSync(MS_MSG_SENDMESSAGE, hContact, 0); } } } // this is not in chat session, quit the session when everyone left else if (info->mJoinedContactsWLID.getCount() < 1) return 1; } break; case ' LAC': //********* CAL: section 8.3 Inviting Users to a Switchboard Session break; case ' GHC': //********* CHG: section 7.7 Client States { int oldStatus = m_iStatus; int newStatus = MSNStatusToMiranda(params); if (oldStatus <= ID_STATUS_OFFLINE) { isConnectSuccess = true; int count = -1; for (;;) { MsnContact *msc = Lists_GetNext(count); if (msc == NULL) break; if (msc->netId == NETID_MOB) setWord(msc->hContact, "Status", ID_STATUS_ONTHEPHONE); } } if (newStatus != ID_STATUS_IDLE) { m_iStatus = newStatus; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, newStatus); debugLogA("Status change acknowledged: %s", params); MSN_RemoveEmptyGroups(); } if (newStatus == ID_STATUS_OFFLINE) return 1; } break; case ' LHC': //********* CHL: Query from Server on MSNP7 { char* authChallengeInfo; if (sttDivideWords(params, 1, &authChallengeInfo) != 1) goto LBL_InvalidCommand; char dgst[64]; MSN_MakeDigest(authChallengeInfo, dgst); info->sendPacket("QRY", "%s 32\r\n%s", msnProductID, dgst); } break; case ' RVC': //********* CVR: MSNP8 break; case ' NLF': //********* FLN: section 7.9 Notification Messages { union { char* tWords[2]; struct { char *userEmail, *netId; } data; }; int tArgs = sttDivideWords(params, 2, tWords); if (tArgs < 2) goto LBL_InvalidCommand; MCONTACT hContact = MSN_HContactFromEmail(data.userEmail); if (hContact != NULL) { setWord(hContact, "Status", MSN_GetThreadByContact(data.userEmail) ? ID_STATUS_INVISIBLE : ID_STATUS_OFFLINE); setDword(hContact, "IdleTS", 0); ForkThread(&CMsnProto::MsgQueue_AllClearThread, mir_strdup(data.userEmail)); } } break; case ' NLI': case ' NLN': //********* ILN/NLN: section 7.9 Notification Messages { union { char* tWords[5]; struct { char *userStatus, *wlid, *userNick, *objid, *cmdstring; } data; }; int tArgs = sttDivideWords(params, 5, tWords); if (tArgs < 3) goto LBL_InvalidCommand; if (data.cmdstring) UrlDecode(data.cmdstring); MSN_ProcessNLN(data.userStatus, data.wlid, data.userNick, data.objid, data.cmdstring); } break; case ' ORI': //******** IRO: section 8.4 Getting Invited to a Switchboard Session { union { char* tWords[5]; struct { char *strThisContact, *totalContacts, *userEmail, *userNick, *flags; } data; }; int tNumTokens = sttDivideWords(params, 5, tWords); if (tNumTokens < 4) goto LBL_InvalidCommand; info->contactJoined(data.userEmail); if (!strchr(data.userEmail, ';')) { UrlDecode(data.userNick); MCONTACT hContact = MSN_HContactFromEmail(data.userEmail, data.userNick, true, true); if (tNumTokens == 5 && mir_strcmp(data.flags, "0:0")) { MsnContact *cont = Lists_Get(data.userEmail); if (cont) { char* end = NULL; cont->cap1 = strtoul(data.flags, &end, 10); cont->cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0; } } int temp_status = getWord(hContact, "Status", ID_STATUS_OFFLINE); if (temp_status == ID_STATUS_OFFLINE && Lists_IsInList(LIST_FL, data.userEmail)) setWord(hContact, "Status", ID_STATUS_INVISIBLE); // only start the chat session after all the IRO messages has been recieved if (info->mJoinedContactsWLID.getCount() > 1 && !mir_strcmp(data.strThisContact, data.totalContacts)) MSN_ChatStart(info); } } break;*/ case ' IOJ': //******** JOI: section 8.5 Session Participant Changes { union { char* tWords[3]; struct { char *userEmail, *userNick, *flags; } data; }; int tNumTokens = sttDivideWords(params, 3, tWords); if (tNumTokens < 2) goto LBL_InvalidCommand; UrlDecode(data.userEmail); if (strchr(data.userEmail, ';')) { info->contactJoined(data.userEmail); break; } if (_stricmp(MyOptions.szEmail, data.userEmail) == 0) { if (!info->mCaller) { if (info->mJoinedContactsWLID.getCount() == 1) { MSN_InitSB(info, info->mJoinedContactsWLID[0]); } else { info->sendCaps(); if (info->mInitialContactWLID && MsgQueue_CheckContact(info->mInitialContactWLID)) msnNsThread->sendPacket("XFR", "SB"); mir_free(info->mInitialContactWLID); info->mInitialContactWLID = NULL; } break; } const char* wlid; do { wlid = MsgQueue_GetNextRecipient(); } while (wlid != NULL && MSN_GetUnconnectedThread(wlid) != NULL); //can happen if both parties send first message at the same time if (wlid == NULL) { debugLogA("USR (SB) internal: thread created for no reason"); return 1; } if (mir_strcmp(wlid, "chat") == 0) { MsgQueueEntry E; MsgQueue_GetNext(wlid, E); for (int i = 0; i < E.cont->getCount(); ++i) info->sendPacket("CAL", (*E.cont)[i]); MSN_ChatStart(info); delete E.cont; mir_free(E.wlid); break; } char* szEmail; parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL); info->mInitialContactWLID = mir_strdup(szEmail); info->sendPacket("CAL", szEmail); break; } UrlDecode(data.userNick); stripBBCode(data.userNick); stripColorCode(data.userNick); MCONTACT hContact = MSN_HContactFromEmail(data.userEmail, data.userNick, true, true); if (tNumTokens == 3) { MsnContact *cont = Lists_Get(data.userEmail); if (cont) { char* end = NULL; cont->cap1 = strtoul(data.flags, &end, 10); cont->cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0; } } mir_utf8decode(data.userNick, NULL); debugLogA("New contact in channel %s %s", data.userEmail, data.userNick); if (info->contactJoined(data.userEmail) <= 1) MSN_InitSB(info, data.userEmail); else { bool chatCreated = info->mChatID[0] != 0; info->sendCaps(); if (chatCreated) { GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_JOIN }; GCEVENT gce = { sizeof(gce), &gcd }; gce.dwFlags = GCEF_ADDTOLOG; gce.ptszNick = GetContactNameT(hContact); gce.ptszUID = mir_a2u(data.userEmail); gce.ptszStatus = TranslateT("Others"); gce.time = time(NULL); gce.bIsMe = FALSE; CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce); mir_free((void*)gce.ptszUID); } else MSN_ChatStart(info); } } break;*/ case ' GSM': //********* MSG: sections 8.7 Instant Messages, 8.8 Receiving an Instant Message MSN_ReceiveMessage(info, cmdString, params); break; case ' MBU': MSN_ReceiveMessage(info, cmdString, params); break; case ' KAN': //********* NAK: section 8.7 Instant Messages if (info->mJoinedContactsWLID.getCount() > 0 && MyOptions.SlowSend) ProtoBroadcastAck(info->getContactHandle(), ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)trid, (LPARAM)Translate("Message delivery failed")); debugLogA("Message send failed (trid=%d)", trid); break; case ' TON': //********* NOT: notification message MSN_ProcessNotificationMessage((char*)HReadBuffer(info, 0).surelyRead(trid), trid); break; case ' GPI': //********* IPG: mobile page MSN_ProcessPage((char*)HReadBuffer(info, 0).surelyRead(trid), trid); break; case ' FCG': //********* GCF: HReadBuffer(info, 0).surelyRead(atol(params)); break; case ' TUO': //********* OUT: sections 7.10 Connection Close, 8.6 Leaving a Switchboard Session if (!_stricmp(params, "OTH")) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION); debugLogA("You have been disconnected from the MSN server because you logged on from another location using the same MSN passport."); } if (!_stricmp(params, "MIG")) // ignore it break; return 1; case ' YRQ': //********* QRY: break; case ' GNQ': //********* QNG: reply to PNG msnPingTimeout = trid; if (info->mType == SERVER_NOTIFICATION && hKeepAliveThreadEvt != NULL) SetEvent(hKeepAliveThreadEvt); break; case ' LMR': //********* RML: Remove from the list { char* tWords[1]; if (sttDivideWords(params, 1, tWords) != 1) goto LBL_InvalidCommand; if (mir_strcmp(tWords[0], "OK") != 0) { size_t len = atol(tWords[0]); MSN_ProcessRemove((char*)HReadBuffer(info, 0).surelyRead(len), len); } } break; case ' GNR': //********* RNG: section 8.4 Getting Invited to a Switchboard Session //note: unusual message encoding: trid==sessionid { union { char* tWords[8]; struct { char *newServer, *security, *authChallengeInfo, *callerEmail, *callerNick, *type, *srcUrl, *genGateway; } data; }; if (sttDivideWords(params, 8, tWords) != 8) goto LBL_InvalidCommand; UrlDecode(data.newServer); UrlDecode(data.callerEmail); UrlDecode(data.callerNick); stripBBCode(data.callerNick); stripColorCode(data.callerNick); if (mir_strcmp(data.security, "CKI")) { debugLogA("Unknown security package in RNG command: %s", data.security); break; } ThreadData* newThread = new ThreadData; mir_strcpy(newThread->mServer, data.newServer); newThread->gatewayType = atol(data.genGateway) != 0; newThread->mType = SERVER_SWITCHBOARD; newThread->mInitialContactWLID = mir_strdup(data.callerEmail); MSN_HContactFromEmail(data.callerEmail, data.callerNick, true, true); mir_snprintf(newThread->mCookie, "%s %d", data.authChallengeInfo, trid); ReleaseSemaphore(newThread->hWaitEvent, MSN_PACKETS_COMBINE, NULL); debugLogA("Opening caller's switchboard server '%s'...", data.newServer); newThread->startThread(&CMsnProto::MSNServerThread, this); } break; case ' XBU': // UBX : MSNP11+ User Status Message { union { char* tWords[2]; struct { char *wlid, *datalen; } data; }; if (sttDivideWords(params, 2, tWords) != 2) goto LBL_InvalidCommand; int len = atol(data.datalen); if (len < 0 || len > 4000) goto LBL_InvalidCommand; ezxml_t xmli = ezxml_parse_str((char*)HReadBuffer(info, 0).surelyRead(len), len); if (xmli) { MSN_ProcessStatusMessage(xmli, data.wlid); ezxml_free(xmli); } } break; case ' NBU': // UBN : MSNP13+ File sharing, P2P Bootstrap, TURN setup. { union { char* tWords[3]; struct { char *email, *typeId, *datalen; } data; }; if (sttDivideWords(params, 3, tWords) != 3) goto LBL_InvalidCommand; int len = atol(data.datalen); if (len < 0 || len > 4000) goto LBL_InvalidCommand; HReadBuffer buf(info, 0); char* msgBody = (char*)buf.surelyRead(len); char *szEmail = data.email; if (strstr(data.email, sttVoidUid)) parseWLID(NEWSTR_ALLOCA(data.email), NULL, &szEmail, NULL); switch (atol(data.typeId)) { case 1: // File sharing stuff // sttProcessFileSharing(msgBody, len, hContact); break; case 3: // P2P Bootstrap p2p_processSIP(info, msgBody, NULL, szEmail); break; case 4: case 8: ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION); debugLogA("You have been disconnected from the MSN server because you logged on from another location using the same MSN passport."); break; case 6: MSN_SharingFindMembership(true); MSN_ABFind("ABFindContactsPaged", NULL, true); break; case 10: // TURN setup p2p_processSIP(info, msgBody, NULL, szEmail); break; } } break; case ' NUU': // UUN : MSNP13+ File sharing, P2P Bootstrap, TURN setup. break; case ' RSU': //********* USR: sections 7.3 Authentication, 8.2 Switchboard Connections and Authentication if (info->mType == SERVER_SWITCHBOARD) { //(section 8.2) union { char* tWords[3]; struct { char *status, *userHandle, *friendlyName; } data; }; if (sttDivideWords(params, 3, tWords) != 3) goto LBL_InvalidCommand; UrlDecode(data.userHandle); UrlDecode(data.friendlyName); if (mir_strcmp(data.status, "OK")) { debugLogA("Unknown status to USR command (SB): '%s'", data.status); break; } info->sendPacket("CAL", MyOptions.szEmail); } else { //dispatch or notification server (section 7.3) union { char* tWords[4]; struct { char *security, *sequence, *authChallengeInfo, *nonce; } data; }; if (sttDivideWords(params, 4, tWords) != 4) goto LBL_InvalidCommand; if (!mir_strcmp(data.security, "SSO")) { if (MSN_GetPassportAuth()) { m_iDesiredStatus = ID_STATUS_OFFLINE; return 1; } char* sec = GenerateLoginBlob(data.nonce); info->sendPacket("USR", "SSO S %s %s %s", authStrToken ? authStrToken : "", sec, MyOptions.szMachineGuid); mir_free(sec); ForkThread(&CMsnProto::msn_keepAliveThread, NULL); ForkThread(&CMsnProto::MSNConnDetectThread, NULL); } else if (!mir_strcmp(data.security, "OK")) { } else { debugLogA("Unknown security package '%s'", data.security); if (info->mType == SERVER_NOTIFICATION) ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPROTOCOL); return 1; } } break; case ' SFR': // RFS: Refresh Contact List if (!MSN_RefreshContactList()) { MSN_ShowError("Cannot retrieve contact list"); return 1; } break; case ' XUU': // UUX: MSNP11 addition { char* tWords[1]; if (sttDivideWords(params, _countof(tWords), tWords) != _countof(tWords)) goto LBL_InvalidCommand; int len = atol(tWords[0]); if (len < 0 || len > 4000) goto LBL_InvalidCommand; HReadBuffer(info, 0).surelyRead(len); } break; case ' REV': //******** VER: section 7.1 Protocol Versioning { char* protocol1; if (sttDivideWords(params, 1, &protocol1) != 1) goto LBL_InvalidCommand; if (MyOptions.szEmail[0] == 0) { MSN_ShowError("You must specify your e-mail in Options/Network/MSN"); return 1; } OSVERSIONINFOEX osvi = {0}; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx((LPOSVERSIONINFO)&osvi); info->sendPacket("CVR","0x0409 %s %d.%d.%d i386 MSNMSGR %s msmsgs %s", osvi.dwPlatformId >= 2 ? "winnt" : "win", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, msnProductVer, MyOptions.szEmail); info->sendPacket("USR", "SSO I %s", MyOptions.szEmail); } break; case ' RFX': //******** XFR: sections 7.4 Referral, 8.1 Referral to Switchboard { union { char* tWords[7]; struct { char *type, *newServer, *security, *authChallengeInfo, *type2, *srcUrl, *genGateway; } data; }; int numWords = sttDivideWords(params, 7, tWords); if (numWords < 3) goto LBL_InvalidCommand; if (!mir_strcmp(data.type, "NS")) { //notification server UrlDecode(data.newServer); ThreadData* newThread = new ThreadData; mir_strcpy(newThread->mServer, data.newServer); newThread->mType = SERVER_NOTIFICATION; newThread->mTrid = info->mTrid; newThread->mIsMainThread = true; usingGateway |= (*data.security == 'G'); info->mIsMainThread = false; debugLogA("Switching to notification server '%s'...", data.newServer); newThread->startThread(&CMsnProto::MSNServerThread, this); return 1; //kill the old thread } if (!mir_strcmp(data.type, "SB")) { //switchboard server UrlDecode(data.newServer); if (numWords < 4) goto LBL_InvalidCommand; if (mir_strcmp(data.security, "CKI")) { debugLogA("Unknown XFR SB security package '%s'", data.security); break; } ThreadData* newThread = new ThreadData; mir_strcpy(newThread->mServer, data.newServer); newThread->gatewayType = data.genGateway && atol(data.genGateway) != 0; newThread->mType = SERVER_SWITCHBOARD; newThread->mCaller = 1; mir_strcpy(newThread->mCookie, data.authChallengeInfo); debugLogA("Opening switchboard server '%s'...", data.newServer); newThread->startThread(&CMsnProto::MSNServerThread, this); } else debugLogA("Unknown referral server: %s", data.type); } break; default: debugLogA("Unrecognised message: %s", cmdString); break; } return 0; } #endif