fteqw/plugins/jabber/sift.c
Spoike f08489f141 .po support for qc.
rewrote messagemode to support utf8 properly, as well as left-arrow etc keys.
support for mouse-over images on console links and stuff.
added r_lerpmuzzlehack for certain viewmodels.
use libtool's dlopen stuff on cygwin to try to cover some cygwin path differences.
try to support utf-8 in filenames even in windows (only in nt, 9x is still ascii only).
added certificate validation for gnutls. gnutls now enabled by default in linux.
d3d11 tweaks. shadowmapping works.
tweaks for updated terrain format, to try to fix some inefficiencies/limitations.
xmpp plugin can now display avatars (xmpp /set avatars 1)
xmpp file transfers supported by default, but capability is disabled by default (can be enabled by hacking config).

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4523 fc73d0e0-1445-4013-8a0c-d673dee63da5
2013-11-21 23:02:28 +00:00

607 lines
18 KiB
C

#include "xmpp.h"
#ifdef FILETRANSFERS
void XMPP_FT_Frame(jclient_t *jcl)
{
static char *hex = "0123456789abcdef";
char digest[20];
char domain[sizeof(digest)*2+1];
int j;
char *req;
struct ft_s *ft;
for (ft = jcl->ft; ft; ft = ft->next)
{
if (ft->streamstatus == STRM_IDLE)
continue;
if (ft->stream < 0)
{
if (ft->nexthost > 0)
{
ft->nexthost--;
ft->stream = pNet_TCPConnect(ft->streamhosts[ft->nexthost].host, ft->streamhosts[ft->nexthost].port);
if (ft->stream == -1)
continue;
ft->streamstatus = STRM_AUTH;
//'authenticate' with socks5 proxy. tell it that we only support 'authless'.
pNet_Send(ft->stream, "\x05\x01\x00", 3);
}
else
{
JCL_AddClientMessagef(jcl,
"<iq id='%s' to='%s' type='error'>"
"<error type='cancel'>"
"<item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"</error>"
"</iq>", ft->iqid, ft->with);
ft->streamstatus = STRM_IDLE;
}
continue;
}
else
{
char data[8192];
int len;
if (ft->streamstatus == STRM_ACTIVE)
{
len = pNet_Recv(ft->stream, data, sizeof(data)-1);
if (len > 0)
pFS_Write(ft->file, data, len);
}
else
{
len = pNet_Recv(ft->stream, data, sizeof(data)-1);
if (len > 0)
{
if (ft->streamstatus == STRM_AUTH)
{
if (len == 2 && data[0] == 0x05 && data[1] == 0x00)
{
//server has accepted us, woo.
//sid+requester(them)+target(us)
req = va("%s%s%s", ft->sid, ft->with, jcl->jid);
SHA1(digest, sizeof(digest), req, strlen(req));
//in hex
for (req = domain, j=0; j < 20; j++)
{
*req++ = hex[(digest[j]>>4) & 0xf];
*req++ = hex[(digest[j]>>0) & 0xf];
}
*req = 0;
//connect with hostname(3).
req = va("\x05\x01%c\x03""%c%s%c%c", 0, strlen(domain), domain, 0, 0);
pNet_Send(ft->stream, req, strlen(domain)+7);
ft->streamstatus = STRM_AUTHED;
}
else
len = -1;
}
else if (ft->streamstatus == STRM_AUTHED)
{
if (data[0] == 0x05 && data[1] == 0x00)
{
if (pFS_Open(ft->fname, &ft->file, 2) < 0)
{
len = -1;
JCL_AddClientMessagef(jcl, "<iq id='%s' to='%s' type='error'/>", ft->iqid, ft->with);
}
else
{
ft->streamstatus = STRM_ACTIVE;
JCL_AddClientMessagef(jcl,
"<iq id='%s' to='%s' type='result'>"
"<query xmlns='http://jabber.org/protocol/bytestreams' sid='%s'>"
"<streamhost-used jid='%s'/>"
"</query>"
"</iq>", ft->iqid, ft->with, ft->sid, ft->streamhosts[ft->nexthost].jid);
}
}
else
len = -1; //err, this is someone else...
}
}
}
if (len == -1)
{
pNet_Close(ft->stream);
ft->stream = -1;
if (ft->streamstatus == STRM_ACTIVE)
{
int size;
if (ft->file != -1)
pFS_Close(ft->file);
ft->file = -1;
size = pFS_Open(ft->fname, &ft->file, 1);
if (ft->file != -1)
pFS_Close(ft->file);
if (size == ft->size)
{
Con_Printf("File Transfer Completed\n");
continue;
}
else
{
Con_Printf("File Transfer Aborted by peer\n");
continue;
}
}
Con_Printf("File Transfer connection to %s:%i failed\n", ft->streamhosts[ft->nexthost].host, ft->streamhosts[ft->nexthost].port);
}
}
}
}
void XMPP_FT_AcceptFile(jclient_t *jcl, int fileid, qboolean accept)
{
struct ft_s *ft, **link;
char *s;
xmltree_t *repiq, *repsi, *c;
for (link = &jcl->ft; ft=*link; link = &(*link)->next)
{
if (ft->privateid == fileid)
break;
}
if (!ft)
{
Con_Printf("File not known\n");
return;
}
if (!accept)
{
Con_Printf("Declining file \"%s\" from \"%s\" (%i bytes)\n", ft->fname, ft->with, ft->size);
if (!ft->allowed)
{
JCL_AddClientMessagef(jcl,
"<iq type='error' to='%s' id='%s'>"
"<error type='cancel'>"
"<forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"<text>Offer Declined</text>"
"</error>"
"</iq>", ft->with, ft->iqid);
}
else
{
//FIXME: send a proper cancel
}
if (ft->file != -1)
pFS_Close(ft->file);
*link = ft->next;
free(ft);
}
else
{
Con_Printf("Receiving file \"%s\" from \"%s\" (%i bytes)\n", ft->fname, ft->with, ft->size);
ft->allowed = true;
//generate a response.
//FIXME: we ought to delay response until after we prompt.
repiq = XML_CreateNode(NULL, "iq", "", "");
XML_AddParameter(repiq, "type", "result");
XML_AddParameter(repiq, "to", ft->with);
XML_AddParameter(repiq, "id", ft->iqid);
repsi = XML_CreateNode(repiq, "si", "http://jabber.org/protocol/si", "");
XML_CreateNode(repsi, "file", "http://jabber.org/protocol/si/profile/file-transfer", ""); //I don't really get the point of this.
c = XML_CreateNode(repsi, "feature", "http://jabber.org/protocol/feature-neg", "");
c = XML_CreateNode(c, "x", "jabber:x:data", "");
XML_AddParameter(c, "type", "submit");
c = XML_CreateNode(c, "field", "", "");
XML_AddParameter(c, "var", "stream-method");
if (ft->method == FT_IBB)
c = XML_CreateNode(c, "value", "", "http://jabber.org/protocol/ibb");
else if (ft->method == FT_BYTESTREAM)
c = XML_CreateNode(c, "value", "", "http://jabber.org/protocol/bytestreams");
s = XML_GenerateString(repiq, false);
JCL_AddClientMessageString(jcl, s);
free(s);
XML_Destroy(repiq);
}
}
static qboolean XMPP_FT_IBBChunked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq)
{
char *from = XML_GetParameter(x, "from", jcl->domain);
struct ft_s *ft = iq->usrptr, **link, *v;
for (link = &jcl->ft; (v=*link); link = &(*link)->next)
{
if (v == ft && !strcmp(ft->with, from))
{
//its still valid
if (x)
{
char *base64;
char rawbuf[4096];
int sz;
sz = pFS_Read(ft->file, rawbuf, ft->blocksize);
Base64_Add(rawbuf, sz);
base64 = Base64_Finish();
if (sz > 0)
{
ft->sizedone += sz;
if (ft->sizedone == ft->size)
ft->eof = true;
}
if (sz && strlen(base64))
{
x = XML_CreateNode(NULL, "data", "http://jabber.org/protocol/ibb", base64);
XML_AddParameteri(x, "seq", ft->seq++);
XML_AddParameter(x, "sid", ft->sid);
JCL_SendIQNode(jcl, XMPP_FT_IBBChunked, "set", from, x, true)->usrptr = ft;
return true;
}
//else eof
}
//errored or ended
if (x)
Con_Printf("Transfer of %s to %s completed\n", ft->fname, ft->with);
else
Con_Printf("%s aborted %s\n", ft->with, ft->fname);
x = XML_CreateNode(NULL, "close", "http://jabber.org/protocol/ibb", "");
XML_AddParameter(x, "sid", ft->sid);
JCL_SendIQNode(jcl, NULL, "set", from, x, true)->usrptr = ft;
//errored
if (ft->file != -1)
pFS_Close(ft->file);
*link = ft->next;
free(ft);
return true;
}
}
return true; //the ack can come after the bytestream has already finished sending. don't warn about that.
}
static qboolean XMPP_FT_IBBBegun(jclient_t *jcl, xmltree_t *x, struct iq_s *iq)
{
struct ft_s *ft = iq->usrptr, **link, *v;
char *from = XML_GetParameter(x, "from", jcl->domain);
for (link = &jcl->ft; (v=*link); link = &(*link)->next)
{
if (v == ft && !strcmp(ft->with, from))
{
//its still valid
if (!x)
{
Con_Printf("%s aborted %s\n", ft->with, ft->fname);
//errored
if (ft->file != -1)
pFS_Close(ft->file);
*link = ft->next;
free(ft);
}
else
{
ft->begun = true;
ft->method = FT_IBB;
XMPP_FT_IBBChunked(jcl, x, iq);
}
return true;
}
}
return false;
}
qboolean XMPP_FT_OfferAcked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq)
{
struct ft_s *ft = iq->usrptr, **link, *v;
char *from = XML_GetParameter(x, "from", jcl->domain);
for (link = &jcl->ft; (v=*link); link = &(*link)->next)
{
if (v == ft && !strcmp(ft->with, from))
{
//its still valid
if (!x)
{
Con_Printf("%s doesn't want %s\n", ft->with, ft->fname);
//errored
if (ft->file != -1)
pFS_Close(ft->file);
*link = ft->next;
free(ft);
}
else
{
xmltree_t *xo;
Con_Printf("%s accepted %s\n", ft->with, ft->fname);
xo = XML_CreateNode(NULL, "open", "http://jabber.org/protocol/ibb", "");
XML_AddParameter(xo, "sid", ft->sid);
XML_AddParameteri(xo, "block-size", ft->blocksize);
//XML_AddParameter(xo, "stanza", "iq");
JCL_SendIQNode(jcl, XMPP_FT_IBBBegun, "set", XML_GetParameter(x, "from", jcl->domain), xo, true)->usrptr = ft;
}
return true;
}
}
return false;
}
void XMPP_FT_SendFile(jclient_t *jcl, char *console, char *to, char *fname)
{
xmltree_t *xsi, *xfile, *c;
struct ft_s *ft;
Con_SubPrintf(console, "Offering %s to %s.\n", fname, to);
ft = malloc(sizeof(*ft));
memset(ft, 0, sizeof(*ft));
ft->stream = -1;
ft->next = jcl->ft;
jcl->ft = ft;
ft->allowed = true;
ft->transmitting = true;
ft->blocksize = 4096;
Q_strlcpy(ft->fname, fname, sizeof(ft->fname));
Q_snprintf(ft->sid, sizeof(ft->sid), "%x%s", rand(), ft->fname);
Q_strlcpy(ft->md5hash, "", sizeof(ft->md5hash));
ft->size = pFS_Open(ft->fname, &ft->file, 1);
ft->with = strdup(to);
ft->method = FT_IBB;
ft->begun = false;
//generate an offer.
xsi = XML_CreateNode(NULL, "si", "http://jabber.org/protocol/si", "");
XML_AddParameter(xsi, "profile", "http://jabber.org/protocol/si/profile/file-transfer");
XML_AddParameter(xsi, "id", ft->sid);
//XML_AddParameter(xsi, "mime-type", "text/plain");
xfile = XML_CreateNode(xsi, "file", "http://jabber.org/protocol/si/profile/file-transfer", ""); //I don't really get the point of this.
XML_AddParameter(xfile, "name", ft->fname);
XML_AddParameteri(xfile, "size", ft->size);
c = XML_CreateNode(xsi, "feature", "http://jabber.org/protocol/feature-neg", "");
c = XML_CreateNode(c, "x", "jabber:x:data", "");
XML_AddParameter(c, "type", "form");
c = XML_CreateNode(c, "field", "", "");
XML_AddParameter(c, "var", "stream-method");
XML_AddParameter(c, "type", "listsingle");
XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/ibb");
// XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/bytestreams");
JCL_SendIQNode(jcl, XMPP_FT_OfferAcked, "set", to, xsi, true)->usrptr = ft;
}
qboolean XMPP_FT_ParseIQSet(jclient_t *jcl, char *iqfrom, char *iqid, xmltree_t *tree)
{
xmltree_t *ot;
//if file transfers are not enabled in this build, reject all iqs
if (!(jcl->enabledcapabilities & CAP_SIFT))
return false;
ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/bytestreams", "query", 0);
if (ot)
{
xmltree_t *c;
struct ft_s *ft;
char *sid = XML_GetParameter(ot, "sid", "");
for (ft = jcl->ft; ft; ft = ft->next)
{
if (!strcmp(ft->sid, sid) && !strcmp(ft->with, iqfrom))
{
if (ft->allowed && !ft->begun && ft->transmitting == false)
{
char *jid;
char *host;
int port;
char *mode = XML_GetParameter(ot, "mode", "tcp");
int i;
if (strcmp(mode, "tcp"))
break;
for (i = 0; ; i++)
{
c = XML_ChildOfTree(ot, "streamhost", i);
if (!c)
break;
jid = XML_GetParameter(c, "jid", "");
host = XML_GetParameter(c, "host", "");
port = atoi(XML_GetParameter(c, "port", "0"));
if (port <= 0 || port > 65535)
continue;
if (ft->nexthost < sizeof(ft->streamhosts) / sizeof(ft->streamhosts[ft->nexthost]))
{
Q_strlcpy(ft->streamhosts[ft->nexthost].jid, jid, sizeof(ft->streamhosts[ft->nexthost].jid));
Q_strlcpy(ft->streamhosts[ft->nexthost].host, host, sizeof(ft->streamhosts[ft->nexthost].host));
ft->streamhosts[ft->nexthost].port = port;
ft->nexthost++;
}
}
ft->streamstatus = STRM_AUTH;
//iq gets acked when we finished connecting to the proxy. *then* the data can flow
Q_strlcpy(ft->iqid, iqid, sizeof(ft->iqid));
return true;
}
}
}
JCL_AddClientMessagef(jcl, "<iq id='%s' to='%s' type='error'/>", iqid, iqfrom);
return true;
}
ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "open", 0);
if (ot)
{
struct ft_s *ft;
char *sid = XML_GetParameter(ot, "sid", "");
int blocksize = atoi(XML_GetParameter(ot, "block-size", "4096")); //technically this is required.
char *stanza = XML_GetParameter(ot, "stanza", "iq");
for (ft = jcl->ft; ft; ft = ft->next)
{
if (!strcmp(ft->sid, sid) && !strcmp(ft->with, iqfrom))
{
if (ft->allowed && !ft->begun && ft->transmitting == false)
{
if (blocksize > 65536 || strcmp(stanza, "iq"))
{ //blocksize: MUST NOT be greater than 65535
JCL_AddClientMessagef(jcl,
"<iq id='%s' to='%s' type='error'>"
"<error type='modify'>"
"<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"</error>"
"</iq>"
, iqid, iqfrom);
}
else if (blocksize > 4096)
{ //ask for smaller chunks
JCL_AddClientMessagef(jcl,
"<iq id='%s' to='%s' type='error'>"
"<error type='modify'>"
"<resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"</error>"
"</iq>"
, iqid, iqfrom);
}
else
{ //it looks okay
pFS_Open(ft->fname, &ft->file, 2);
ft->method = FT_IBB;
ft->blocksize = blocksize;
ft->begun = true;
//if its okay...
JCL_AddClientMessagef(jcl, "<iq id='%s' to='%s' type='result'/>", iqid, iqfrom);
}
break;
}
}
}
return false;
}
ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "close", 0);
if (ot)
{
struct ft_s **link, *ft;
char *sid = XML_GetParameter(ot, "sid", "");
for (link = &jcl->ft; *link; link = &(*link)->next)
{
ft = *link;
if (!strcmp(ft->sid, sid) && !strcmp(ft->with, iqfrom))
{
if (ft->begun && ft->method == FT_IBB)
{
int size;
if (ft->file != -1)
pFS_Close(ft->file);
if (ft->transmitting)
{
if (ft->eof)
Con_Printf("Sent \"%s\" to \"%s\"\n", ft->fname, ft->with);
else
Con_Printf("%s aborted transfer of \"%s\"\n", iqfrom, ft->fname);
}
else
{
size = pFS_Open(ft->fname, &ft->file, 1);
if (ft->file != -1)
pFS_Close(ft->file);
if (size == ft->size)
Con_Printf("Received file \"%s\" successfully\n", ft->fname);
else
Con_Printf("%s aborted transfer of \"%s\"\n", iqfrom, ft->fname);
}
*link = ft->next;
free(ft);
//if its okay...
JCL_AddClientMessagef(jcl, "<iq id='%s' to='%s' type='result'/>", iqid, iqfrom);
return true;
}
}
}
return false; //some kind of error
}
ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "data", 0);
if (ot)
{
char block[65536];
char *sid = XML_GetParameter(ot, "sid", "");
unsigned short seq = atoi(XML_GetParameter(ot, "seq", "0"));
int blocksize;
struct ft_s *ft;
for (ft = jcl->ft; ft; ft = ft->next)
{
if (!strcmp(ft->sid, sid) && !ft->transmitting)
{
blocksize = Base64_Decode(block, sizeof(block), ot->body, strlen(ot->body));
if (blocksize && blocksize <= ft->blocksize)
{
pFS_Write(ft->file, block, blocksize);
JCL_AddClientMessagef(jcl, "<iq id='%s' to='%s' type='result'/>", iqid, iqfrom);
return true;
}
else
Con_Printf("XMPP: Invalid blocksize in file transfer from %s\n", iqfrom);
break;
}
}
return true;
}
ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/si", "si", 0);
if (ot)
{
char *profile = XML_GetParameter(ot, "profile", "");
if (!strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer"))
{
char *mimetype = XML_GetParameter(ot, "mime-type", "text/plain");
char *sid = XML_GetParameter(ot, "id", "");
xmltree_t *file = XML_ChildOfTreeNS(ot, "http://jabber.org/protocol/si/profile/file-transfer", "file", 0);
char *fname = XML_GetParameter(file, "name", "file.txt");
char *date = XML_GetParameter(file, "date", "");
char *md5hash = XML_GetParameter(file, "hash", "");
int fsize = strtoul(XML_GetParameter(file, "size", "0"), NULL, 0);
char *desc = XML_GetChildBody(file, "desc", "");
char authlink[512];
char denylink[512];
//file transfer offer
struct ft_s *ft = malloc(sizeof(*ft) + strlen(iqfrom)+1);
memset(ft, 0, sizeof(*ft));
ft->stream = -1;
ft->next = jcl->ft;
jcl->ft = ft;
ft->privateid = ++jcl->privateidseq;
ft->transmitting = false;
Q_strlcpy(ft->iqid, iqid, sizeof(ft->iqid));
Q_strlcpy(ft->sid, sid, sizeof(ft->sid));
Q_strlcpy(ft->fname, fname, sizeof(ft->sid));
Base64_Decode(ft->md5hash, sizeof(ft->md5hash), md5hash, strlen(md5hash));
ft->size = fsize;
ft->file = -1;
ft->with = (char*)(ft+1);
strcpy(ft->with, iqfrom);
ft->method = (fsize > 1024*128)?FT_BYTESTREAM:FT_IBB; //favour bytestreams for large files. for small files, just use ibb as it saves sorting out proxies.
ft->begun = false;
JCL_GenLink(jcl, authlink, sizeof(authlink), "fauth", iqfrom, NULL, va("%i", ft->privateid), "%s", "Accept");
JCL_GenLink(jcl, denylink, sizeof(denylink), "fdeny", iqfrom, NULL, va("%i", ft->privateid), "%s", "Deny");
Con_Printf("%s has offered to send you \"%s\" (%i bytes). %s %s\n", iqfrom, fname, fsize, authlink, denylink);
}
else
{
//profile not understood
JCL_AddClientMessagef(jcl,
"<iq type='error' to='%s' id='%s'>"
"<error code='400' type='cancel'>"
"<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"<bad-profile xmlns='http://jabber.org/protocol/si'/>"
"</error>"
"</iq>", iqfrom, iqid);
}
return true;
}
return false;
}
#endif