/*
Jabber Protocol Plugin for Miranda NG
Copyright (c) 2002-04 Santithorn Bunchua
Copyright (c) 2005-12 George Hazan
Copyright (c) 2007 Maxim Mluhov
Copyright (c) 2012-18 Miranda NG team
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdafx.h"
#include "jabber_iq.h"
#include "jabber_rc.h"
#include "version.h"
BOOL CJabberProto::OnIqRequestVersion(HXML, CJabberIqInfo *pInfo)
{
if (!pInfo->GetFrom())
return TRUE;
if (!m_bAllowVersionRequests)
return FALSE;
XmlNodeIq iq(L"result", pInfo);
HXML query = iq << XQUERY(JABBER_FEAT_VERSION);
query << XCHILD(L"name", L"Miranda NG Jabber");
query << XCHILD(L"version", szCoreVersion);
if (m_bShowOSVersion) {
wchar_t os[256] = { 0 };
if (!GetOSDisplayString(os, _countof(os)))
mir_wstrncpy(os, L"Microsoft Windows", _countof(os));
query << XCHILD(L"os", os);
}
m_ThreadInfo->send(iq);
return TRUE;
}
// last activity (XEP-0012) support
BOOL CJabberProto::OnIqRequestLastActivity(HXML, CJabberIqInfo *pInfo)
{
m_ThreadInfo->send(
XmlNodeIq(L"result", pInfo) << XQUERY(JABBER_FEAT_LAST_ACTIVITY)
<< XATTRI(L"seconds", m_tmJabberIdleStartTime ? time(0) - m_tmJabberIdleStartTime : 0));
return TRUE;
}
// XEP-0199: XMPP Ping support
BOOL CJabberProto::OnIqRequestPing(HXML, CJabberIqInfo *pInfo)
{
m_ThreadInfo->send(XmlNodeIq(L"result", pInfo) << XATTR(L"from", m_ThreadInfo->fullJID));
return TRUE;
}
// Returns the current GMT offset in seconds
int GetGMTOffset(void)
{
TIME_ZONE_INFORMATION tzinfo;
int nOffset = 0;
DWORD dwResult = GetTimeZoneInformation(&tzinfo);
switch (dwResult) {
case TIME_ZONE_ID_STANDARD:
nOffset = tzinfo.Bias + tzinfo.StandardBias;
break;
case TIME_ZONE_ID_DAYLIGHT:
nOffset = tzinfo.Bias + tzinfo.DaylightBias;
break;
case TIME_ZONE_ID_UNKNOWN:
nOffset = tzinfo.Bias;
break;
case TIME_ZONE_ID_INVALID:
default:
nOffset = 0;
break;
}
return -nOffset;
}
// entity time (XEP-0202) support
BOOL CJabberProto::OnIqRequestTime(HXML, CJabberIqInfo *pInfo)
{
wchar_t stime[100];
wchar_t szTZ[10];
TimeZone_PrintDateTime(UTC_TIME_HANDLE, L"I", stime, _countof(stime), 0);
int nGmtOffset = GetGMTOffset();
mir_snwprintf(szTZ, L"%+03d:%02d", nGmtOffset / 60, nGmtOffset % 60);
XmlNodeIq iq(L"result", pInfo);
HXML timeNode = iq << XCHILDNS(L"time", JABBER_FEAT_ENTITY_TIME);
timeNode << XCHILD(L"utc", stime); timeNode << XCHILD(L"tzo", szTZ);
const wchar_t *szTZName = TimeZone_GetName(nullptr);
if (szTZName)
timeNode << XCHILD(L"tz", szTZName);
m_ThreadInfo->send(iq);
return TRUE;
}
BOOL CJabberProto::OnIqProcessIqOldTime(HXML, CJabberIqInfo *pInfo)
{
struct tm *gmt;
time_t ltime;
wchar_t stime[100], *dtime;
_tzset();
time(<ime);
gmt = gmtime(<ime);
mir_snwprintf(stime, L"%.4i%.2i%.2iT%.2i:%.2i:%.2i",
gmt->tm_year + 1900, gmt->tm_mon + 1,
gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
dtime = _wctime(<ime);
dtime[24] = 0;
XmlNodeIq iq(L"result", pInfo);
HXML queryNode = iq << XQUERY(JABBER_FEAT_ENTITY_TIME_OLD);
queryNode << XCHILD(L"utc", stime);
const wchar_t *szTZName = TimeZone_GetName(nullptr);
if (szTZName)
queryNode << XCHILD(L"tz", szTZName);
queryNode << XCHILD(L"display", dtime);
m_ThreadInfo->send(iq);
return TRUE;
}
BOOL CJabberProto::OnIqRequestAvatar(HXML, CJabberIqInfo *pInfo)
{
if (!m_bEnableAvatars)
return TRUE;
int pictureType = m_bAvatarType;
if (pictureType == PA_FORMAT_UNKNOWN)
return TRUE;
const wchar_t *szMimeType = ProtoGetAvatarMimeType(pictureType);
if (szMimeType == nullptr)
return TRUE;
wchar_t szFileName[MAX_PATH];
GetAvatarFileName(0, szFileName, _countof(szFileName));
FILE* in = _wfopen(szFileName, L"rb");
if (in == nullptr)
return TRUE;
long bytes = _filelength(_fileno(in));
ptrA buffer((char*)mir_alloc(bytes * 4 / 3 + bytes + 1000));
if (buffer == nullptr) {
fclose(in);
return TRUE;
}
fread(buffer, bytes, 1, in);
fclose(in);
ptrA str(mir_base64_encode(buffer, bytes));
m_ThreadInfo->send(XmlNodeIq(L"result", pInfo) << XQUERY(JABBER_FEAT_AVATAR) << XCHILD(L"query", _A2T(str)) << XATTR(L"mimetype", szMimeType));
return TRUE;
}
BOOL CJabberProto::OnSiRequest(HXML node, CJabberIqInfo *pInfo)
{
const wchar_t *szProfile = XmlGetAttrValue(pInfo->GetChildNode(), L"profile");
if (szProfile && !mir_wstrcmp(szProfile, JABBER_FEAT_SI_FT))
FtHandleSiRequest(node);
else {
XmlNodeIq iq(L"error", pInfo);
HXML error = iq << XCHILD(L"error") << XATTRI(L"code", 400) << XATTR(L"type", L"cancel");
error << XCHILDNS(L"bad-request", L"urn:ietf:params:xml:ns:xmpp-stanzas");
error << XCHILD(L"bad-profile");
m_ThreadInfo->send(iq);
}
return TRUE;
}
BOOL CJabberProto::OnRosterPushRequest(HXML, CJabberIqInfo *pInfo)
{
HXML queryNode = pInfo->GetChildNode();
// RFC 3921 #7.2 Business Rules
if (pInfo->GetFrom()) {
wchar_t *szFrom = JabberPrepareJid(pInfo->GetFrom());
if (!szFrom)
return TRUE;
wchar_t *szTo = JabberPrepareJid(m_ThreadInfo->fullJID);
if (!szTo) {
mir_free(szFrom);
return TRUE;
}
wchar_t *pDelimiter = wcschr(szFrom, '/');
if (pDelimiter) *pDelimiter = 0;
pDelimiter = wcschr(szTo, '/');
if (pDelimiter) *pDelimiter = 0;
BOOL bRetVal = mir_wstrcmp(szFrom, szTo) == 0;
mir_free(szFrom);
mir_free(szTo);
// invalid JID
if (!bRetVal) {
debugLogW(L" attempt to hack via roster push from %s", pInfo->GetFrom());
return TRUE;
}
}
debugLogA(" Got roster push, query has %d children", XmlGetChildCount(queryNode));
for (int i = 0;; i++) {
HXML itemNode = XmlGetChild(queryNode, i);
if (!itemNode)
break;
if (mir_wstrcmp(XmlGetName(itemNode), L"item") != 0)
continue;
const wchar_t *jid = XmlGetAttrValue(itemNode, L"jid"), *str = XmlGetAttrValue(itemNode, L"subscription");
if (jid == nullptr || str == nullptr)
continue;
// we will not add new account when subscription=remove
if (!mir_wstrcmp(str, L"to") || !mir_wstrcmp(str, L"both") || !mir_wstrcmp(str, L"from") || !mir_wstrcmp(str, L"none")) {
const wchar_t *name = XmlGetAttrValue(itemNode, L"name");
ptrW nick((name != nullptr) ? mir_wstrdup(name) : JabberNickFromJID(jid));
if (nick != nullptr) {
MCONTACT hContact = HContactFromJID(jid, false);
if (hContact == 0)
hContact = DBCreateContact(jid, nick, false, false);
else
setWString(hContact, "jid", jid);
JABBER_LIST_ITEM *item = ListAdd(LIST_ROSTER, jid, hContact);
replaceStrW(item->nick, nick);
item->bRealContact = true;
HXML groupNode = XmlGetChild(itemNode, "group");
replaceStrW(item->group, XmlGetText(groupNode));
if (name != nullptr) {
ptrW tszNick(getWStringA(hContact, "Nick"));
if (tszNick != nullptr) {
if (mir_wstrcmp(nick, tszNick) != 0)
db_set_ws(hContact, "CList", "MyHandle", nick);
else
db_unset(hContact, "CList", "MyHandle");
}
else db_set_ws(hContact, "CList", "MyHandle", nick);
}
else db_unset(hContact, "CList", "MyHandle");
if (!m_bIgnoreRosterGroups) {
if (item->group != nullptr) {
Clist_GroupCreate(0, item->group);
db_set_ws(hContact, "CList", "Group", item->group);
}
else db_unset(hContact, "CList", "Group");
}
}
}
if (JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_ROSTER, jid)) {
if (!mir_wstrcmp(str, L"both"))
item->subscription = SUB_BOTH;
else if (!mir_wstrcmp(str, L"to"))
item->subscription = SUB_TO;
else if (!mir_wstrcmp(str, L"from"))
item->subscription = SUB_FROM;
else
item->subscription = SUB_NONE;
debugLogW(L"Roster push for jid=%s (hContact=%d), set subscription to %s", jid, item->hContact, str);
// subscription = remove is to remove from roster list
// but we will just set the contact to offline and not actually
// remove, so that history will be retained.
if (!mir_wstrcmp(str, L"remove")) {
SetContactOfflineStatus(item->hContact);
UpdateSubscriptionInfo(item->hContact, item);
}
else if (isChatRoom(item->hContact))
db_unset(item->hContact, "CList", "Hidden");
else
UpdateSubscriptionInfo(item->hContact, item);
}
}
UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_TRANSPORT_REFRESH);
RebuildInfoFrame();
return TRUE;
}
BOOL CJabberProto::OnIqRequestOOB(HXML, CJabberIqInfo *pInfo)
{
if (!pInfo->GetFrom() || !pInfo->GetHContact())
return TRUE;
HXML n = XmlGetChild(pInfo->GetChildNode(), "url");
if (!n || !XmlGetText(n))
return TRUE;
if (m_bBsOnlyIBB) {
// reject
XmlNodeIq iq(L"error", pInfo);
HXML e = XmlAddChild(iq, L"error", L"File transfer refused"); XmlAddAttr(e, L"code", 406);
m_ThreadInfo->send(iq);
return TRUE;
}
filetransfer *ft = new filetransfer(this);
ft->std.totalFiles = 1;
ft->jid = mir_wstrdup(pInfo->GetFrom());
ft->std.hContact = pInfo->GetHContact();
ft->type = FT_OOB;
ft->httpHostName = nullptr;
ft->httpPort = 80;
ft->httpPath = nullptr;
// Parse the URL
wchar_t *str = (wchar_t*)XmlGetText(n); // URL of the file to get
if (!wcsnicmp(str, L"http://", 7)) {
wchar_t *p = str + 7, *q;
if ((q = wcschr(p, '/')) != nullptr) {
wchar_t text[1024];
if (q - p < _countof(text)) {
wcsncpy_s(text, p, q - p);
text[q - p] = '\0';
if ((p = wcschr(text, ':')) != nullptr) {
ft->httpPort = (WORD)_wtoi(p + 1);
*p = '\0';
}
ft->httpHostName = mir_u2a(text);
}
}
}
if (pInfo->GetIdStr())
ft->szId = JabberId2string(pInfo->GetIqId());
if (ft->httpHostName && ft->httpPath) {
wchar_t *desc = nullptr;
debugLogA("Host=%s Port=%d Path=%s", ft->httpHostName, ft->httpPort, ft->httpPath);
if ((n = XmlGetChild(pInfo->GetChildNode(), "desc")) != nullptr)
desc = (wchar_t*)XmlGetText(n);
wchar_t *str2;
debugLogW(L"description = %s", desc);
if ((str2 = wcsrchr(ft->httpPath, '/')) != nullptr)
str2++;
else
str2 = ft->httpPath;
str2 = mir_wstrdup(str2);
JabberHttpUrlDecode(str2);
PROTORECVFILE pre;
pre.dwFlags = PRFF_UNICODE;
pre.timestamp = time(0);
pre.descr.w = desc;
pre.files.w = &str2;
pre.fileCount = 1;
pre.lParam = (LPARAM)ft;
ProtoChainRecvFile(ft->std.hContact, &pre);
mir_free(str2);
}
else {
// reject
XmlNodeIq iq(L"error", pInfo);
HXML e = XmlAddChild(iq, L"error", L"File transfer refused"); XmlAddAttr(e, L"code", 406);
m_ThreadInfo->send(iq);
delete ft;
}
return TRUE;
}
BOOL CJabberProto::OnHandleDiscoInfoRequest(HXML iqNode, CJabberIqInfo *pInfo)
{
if (!pInfo->GetChildNode())
return TRUE;
const wchar_t *szNode = XmlGetAttrValue(pInfo->GetChildNode(), L"node");
// caps hack
if (m_clientCapsManager.HandleInfoRequest(iqNode, pInfo, szNode))
return TRUE;
// ad-hoc hack:
if (szNode && m_adhocManager.HandleInfoRequest(iqNode, pInfo, szNode))
return TRUE;
// another request, send empty result
m_ThreadInfo->send(
XmlNodeIq(L"error", pInfo)
<< XCHILD(L"error") << XATTRI(L"code", 404) << XATTR(L"type", L"cancel")
<< XCHILDNS(L"item-not-found", L"urn:ietf:params:xml:ns:xmpp-stanzas"));
return TRUE;
}
BOOL CJabberProto::OnHandleDiscoItemsRequest(HXML iqNode, CJabberIqInfo *pInfo)
{
if (!pInfo->GetChildNode())
return TRUE;
// ad-hoc commands check:
const wchar_t *szNode = XmlGetAttrValue(pInfo->GetChildNode(), L"node");
if (szNode && m_adhocManager.HandleItemsRequest(iqNode, pInfo, szNode))
return TRUE;
// another request, send empty result
XmlNodeIq iq(L"result", pInfo);
HXML resultQuery = iq << XQUERY(JABBER_FEAT_DISCO_ITEMS);
if (szNode)
XmlAddAttr(resultQuery, L"node", szNode);
if (!szNode && m_bEnableRemoteControl)
resultQuery << XCHILD(L"item") << XATTR(L"jid", m_ThreadInfo->fullJID)
<< XATTR(L"node", JABBER_FEAT_COMMANDS) << XATTR(L"name", L"Ad-hoc commands");
m_ThreadInfo->send(iq);
return TRUE;
}
BOOL CJabberProto::AddClistHttpAuthEvent(CJabberHttpAuthParams *pParams)
{
char szService[256];
mir_snprintf(szService, "%s%s", m_szModuleName, JS_HTTP_AUTH);
CLISTEVENT cle = {};
cle.hIcon = (HICON)LoadIconEx("openid");
cle.flags = CLEF_PROTOCOLGLOBAL | CLEF_UNICODE;
cle.hDbEvent = -99;
cle.lParam = (LPARAM)pParams;
cle.pszService = szService;
cle.szTooltip.w = TranslateT("Http authentication request received");
g_clistApi.pfnAddEvent(&cle);
return TRUE;
}
BOOL CJabberProto::OnIqHttpAuth(HXML node, CJabberIqInfo *pInfo)
{
if (!m_bAcceptHttpAuth)
return TRUE;
if (!node || !pInfo->GetChildNode() || !pInfo->GetFrom() || !pInfo->GetIdStr())
return TRUE;
HXML pConfirm = XmlGetChild(node, "confirm");
if (!pConfirm)
return TRUE;
const wchar_t *szId = XmlGetAttrValue(pConfirm, L"id");
const wchar_t *szMethod = XmlGetAttrValue(pConfirm, L"method");
const wchar_t *szUrl = XmlGetAttrValue(pConfirm, L"url");
if (!szId || !szMethod || !szUrl)
return TRUE;
CJabberHttpAuthParams *pParams = (CJabberHttpAuthParams*)mir_calloc(sizeof(CJabberHttpAuthParams));
if (pParams) {
pParams->m_nType = CJabberHttpAuthParams::IQ;
pParams->m_szFrom = mir_wstrdup(pInfo->GetFrom());
pParams->m_szId = mir_wstrdup(szId);
pParams->m_szMethod = mir_wstrdup(szMethod);
pParams->m_szUrl = mir_wstrdup(szUrl);
AddClistHttpAuthEvent(pParams);
}
return TRUE;
}