/* 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 "stdafx.h" #include "msn_proto.h" 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); } ///////////////////////////////////////////////////////////////////////////////////////// // 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; TCHAR *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_a2t(tHeader["To"]); if (_tcsncmp(mChatID, _T("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"]; // 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) MSN_SetMirVer(cont->hContact, cont->cap1, true); } 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_TCHAR; psr[cnt]->id.t = psr[cnt]->nick.t = psr[cnt]->email.t = mir_a2t(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.t); 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=GetSkypeToken(true)) && xmli) { /* FIXME: As soon as core has functions to POST images in a conversation AND gives the possibility to supply a * callback for fetching thta 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_GENERATEHOST | NLHRF_PERSISTENT | NLHRF_SMARTAUTHHEADER; 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"); } ft->std.tszCurrentFile = mir_utf8decodeT(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); TCHAR tComment[40]; mir_sntprintf(tComment, _countof(tComment), TranslateT("%I64u bytes"), ft->std.currentFileSize); PROTORECVFILET pre = { 0 }; pre.dwFlags = PRFF_TCHAR; pre.fileCount = 1; pre.timestamp = time(NULL); pre.descr.t = (desc = ezxml_child(xmli, "Description"))?mir_utf8decodeT(desc->txt):tComment; pre.files.t = &ft->std.tszCurrentFile; pre.lParam = (LPARAM)ft; ProtoChainRecvFile(ft->std.hContact, &pre); if (desc) mir_free(pre.descr.t); } 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 }; pre.szMessage = (char*)ezxml_txt(xmli); 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, _countof(szEmail), "%s@%s", szCont, szDom); const char *szNetId = ezxml_attr(cont, "t"); if (msnSearchId != NULL) { if (szNetId != NULL) { ptrT szEmailT(mir_utf8decodeT(szEmail)); PROTOSEARCHRESULT psr = { 0 }; psr.cbSize = sizeof(psr); psr.flags = PSR_TCHAR; psr.id.t = szEmailT; psr.nick.t = szEmailT; psr.email.t = 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; parseWLID(NEWSTR_ALLOCA(wlid), &szNet, &szEmail, NULL); 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; } if (lastStatus == ID_STATUS_OFFLINE) { DBVARIANT dbv; bool always = getString(hContact, "MirVer", &dbv) != 0; if (!always) db_free(&dbv); MSN_SetMirVer(hContact, cont->cap1, always); } 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] = ""; 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; parseWLID(NEWSTR_ALLOCA(wlid), &szNetId, &szEmail, NULL); 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); } } { ptrT tszStatus(mir_utf8decodeT(szStatMsg)); 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_utf8encodeT((TCHAR *)CallService(MS_LISTENINGTO_GETUNKNOWNTEXT, 0, 0)); for (unsigned i = 4; i < pCount; i++) { char part[16]; size_t lenPart = mir_snprintf(part, _countof(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_utf8decodeT(parts[4]); if (pCount > 5) lti.ptszArtist = mir_utf8decodeT(parts[5]); if (pCount > 6) lti.ptszAlbum = mir_utf8decodeT(parts[6]); if (pCount > 7) lti.ptszTrack = mir_utf8decodeT(parts[7]); if (pCount > 8) lti.ptszYear = mir_utf8decodeT(parts[8]); if (pCount > 9) lti.ptszGenre = mir_utf8decodeT(parts[9]); if (pCount > 10) lti.ptszLength = mir_utf8decodeT(parts[10]); if (pCount > 11) lti.ptszPlayer = mir_utf8decodeT(parts[11]); else lti.ptszPlayer = mir_utf8decodeT(parts[0]); if (pCount > 12) lti.ptszType = mir_utf8decodeT(parts[12]); else lti.ptszType = mir_utf8decodeT(parts[1]); TCHAR *cm = (TCHAR *)CallService(MS_LISTENINGTO_GETPARSEDTEXT, (WPARAM)_T("%title% - %artist%"), (LPARAM)<i); setTString(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); TCHAR* alrt = mir_utf8decodeT(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 (!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); } } 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); char* msgBody = tHeader.readFromBuffer((char*)buf.surelyRead(atol(data.strMsgBytes))); 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 */ switch (MSN_AuthOAuth()) { case 1: info->sendPacketPayload("ATH", "CON\\USER", "t=%s" "%s" "%s%s\r\n", authSSLToken ? ptrA(HtmlEncode(authSSLToken)) : "", authUIC, GetMyUsername(NETID_MSN), GetMyUsername(NETID_SKYPE)); break; case 2: info->sendPacketPayload("ATH", "CON\\USER", "%s" "%s" "chatservice.live.com" "\r\n", authStrToken ? ptrA(HtmlEncode(authStrToken)) : "", authUIC); break; default: m_iDesiredStatus = ID_STATUS_OFFLINE; return 1; } } else { /* Skype username/pass login */ 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); info->sendPacketPayload("ATH", "CON\\USER", "%s%s\r\n", szUIC, MyOptions.szEmail); } ezxml_free(xmlcnt); } 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); char* msgBody = tHeader.readFromBuffer((char*)buf.surelyRead(atol(data.strMsgBytes))); 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); char* msgBody = tHeader.readFromBuffer((char*)buf.surelyRead(atol(data.strMsgBytes))); 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); char* msgBody = tHeader.readFromBuffer((char*)buf.surelyRead(atol(data.strMsgBytes))); 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_utf8decodeT(Appfile); ft->std.totalBytes = ft->std.currentFileSize = _atoi64(Appfilesize); ft->std.totalFiles = 1; ft->szInvcookie = mir_strdup(Invcookie); ft->p2p_dest = mir_strdup(email); TCHAR tComment[40]; mir_sntprintf(tComment, _countof(tComment), TranslateT("%I64u bytes"), ft->std.currentFileSize); PROTORECVFILET pre = { 0 }; pre.dwFlags = PRFF_TCHAR; pre.fileCount = 1; pre.timestamp = time(NULL); pre.descr.t = tComment; pre.files.t = &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, _countof(newThread->mServer), "%s:%s", IPAddress, Port); else mir_snprintf(newThread->mServer, _countof(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")) { TCHAR text[512], *tszEmail = mir_a2t(email); mir_sntprintf(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, _countof(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, _countof(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)) { CallService(MS_DB_CONTACT_DELETE, (WPARAM)msc->hContact, 0); 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)); TCHAR path[MAX_PATH]; MSN_GetCustomSmileyFileName(hContact, path, _countof(path), smileyName, iSmileyType); ft->std.tszCurrentFile = mir_tstrdup(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, _countof(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_a2t(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_a2t(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, _countof(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