#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 = netfuncs->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'. netfuncs->Send(ft->stream, "\x05\x01\x00", 3); } else { JCL_AddClientMessagef(jcl, "" "" "" "" "", ft->iqid, ft->with); ft->streamstatus = STRM_IDLE; } continue; } else { char data[8192]; int len; if (ft->streamstatus == STRM_ACTIVE) { len = netfuncs->Recv(ft->stream, data, sizeof(data)-1); if (len > 0) filefuncs->Write(ft->file, data, len); } else { len = netfuncs->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->fulljid); 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, (int)strlen(domain), domain, 0, 0); netfuncs->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 (filefuncs->Open(ft->fname, &ft->file, 2) < 0) { len = -1; JCL_AddClientMessagef(jcl, "", ft->iqid, ft->with); } else { ft->streamstatus = STRM_ACTIVE; JCL_AddClientMessagef(jcl, "" "" "" "" "", ft->iqid, ft->with, ft->sid, ft->streamhosts[ft->nexthost].jid); } } else len = -1; //err, this is someone else... } } } if (len == -1) { netfuncs->Close(ft->stream); ft->stream = -1; if (ft->streamstatus == STRM_ACTIVE) { int size; if (ft->file != -1) filefuncs->Close(ft->file); ft->file = -1; size = filefuncs->Open(ft->fname, &ft->file, 1); if (ft->file != -1) filefuncs->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, "" "" "" "Offer Declined" "" "", ft->with, ft->iqid); } else { //FIXME: send a proper cancel } if (ft->file != -1) filefuncs->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) { const 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 = filefuncs->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) filefuncs->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; const 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) filefuncs->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; const 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) filefuncs->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, const char *console, const char *to, const 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->allowed = true; ft->transmitting = true; ft->blocksize = 4096; Q_strlcpy(ft->fname, fname, sizeof(ft->fname)); if (Q_snprintf(ft->sid, sizeof(ft->sid), "%x%s", rand(), ft->fname) >= sizeof(ft->sid)) /*doesn't matter so long as its unique*/; Q_strlcpy(ft->md5hash, "", sizeof(ft->md5hash)); ft->size = filefuncs->Open(ft->fname, &ft->file, 1); ft->with = strdup(to); ft->method = FT_IBB; ft->begun = false; ft->next = jcl->ft; jcl->ft = ft; //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, const char *iqfrom, const 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; const 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) { const char *jid; const char *host; int port; const 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, "", iqid, iqfrom); return true; } ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "open", 0); if (ot) { struct ft_s *ft; const char *sid = XML_GetParameter(ot, "sid", ""); int blocksize = atoi(XML_GetParameter(ot, "block-size", "4096")); //technically this is required. const 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, "" "" "" "" "" , iqid, iqfrom); } else if (blocksize > 4096) { //ask for smaller chunks JCL_AddClientMessagef(jcl, "" "" "" "" "" , iqid, iqfrom); } else { //it looks okay filefuncs->Open(ft->fname, &ft->file, 2); ft->method = FT_IBB; ft->blocksize = blocksize; ft->begun = true; //if its okay... JCL_AddClientMessagef(jcl, "", iqid, iqfrom); } break; } } } return false; } ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "close", 0); if (ot) { struct ft_s **link, *ft; const 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) filefuncs->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 = filefuncs->Open(ft->fname, &ft->file, 1); if (ft->file != -1) filefuncs->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, "", 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]; const char *sid = XML_GetParameter(ot, "sid", ""); // unsigned short seq = atoi(XML_GetParameter(ot, "seq", "0")); int blocksize; struct ft_s *ft; //FIXME: validate the sequence! 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) { filefuncs->Write(ft->file, block, blocksize); JCL_AddClientMessagef(jcl, "", 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) { const 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"); const char *sid = XML_GetParameter(ot, "id", ""); xmltree_t *file = XML_ChildOfTreeNS(ot, "http://jabber.org/protocol/si/profile/file-transfer", "file", 0); const char *fname = XML_GetParameter(file, "name", "file.txt"); // char *date = XML_GetParameter(file, "date", ""); const 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, "" "" "" "" "" "", iqfrom, iqid); } return true; } return false; } #endif