/*
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; cnt
email.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