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