da322c528f
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4400 fc73d0e0-1445-4013-8a0c-d673dee63da5
2281 lines
62 KiB
C
2281 lines
62 KiB
C
//Released under the terms of the gpl as this file uses a bit of quake derived code. All sections of the like are marked as such
|
|
|
|
#include "../plugin.h"
|
|
#include <time.h>
|
|
#include "../../engine/common/netinc.h"
|
|
#include "xml.h"
|
|
|
|
#define NOICE
|
|
|
|
#define QUAKEMEDIATYPE "quake"
|
|
#define QUAKEMEDIAXMLNS "fteqw.com:netmedia"
|
|
|
|
struct icestate_s *(QDECL *pICE_Create)(void *module, char *conname, char *peername, enum icemode_e mode); //doesn't start pinging anything.
|
|
struct icestate_s *(QDECL *pICE_Find)(void *module, char *conname);
|
|
void (QDECL *pICE_Begin)(struct icestate_s *con, char *stunip, int stunport); //begins sending stun packets and stuff as required.
|
|
struct icecandidate_s *(QDECL *pICE_GetLCandidateInfo)(struct icestate_s *con); //stuff that needs reporting to the peer.
|
|
void (QDECL *pICE_AddRCandidateInfo)(struct icestate_s *con, struct icecandidate_s *cand); //stuff that came from the peer.
|
|
void (QDECL *pICE_Close)(struct icestate_s *con); //bye then.
|
|
|
|
|
|
#define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0)
|
|
|
|
#define JCL_BUILD "3"
|
|
|
|
|
|
#define ARGNAMES ,sock,certhostname
|
|
BUILTINR(int, Net_SetTLSClient, (qhandle_t sock, const char *certhostname));
|
|
#undef ARGNAMES
|
|
|
|
#define ARGNAMES ,funcname
|
|
BUILTINR(void *, Plug_GetNativePointer, (const char *funcname));
|
|
#undef ARGNAMES
|
|
|
|
void (*Con_TrySubPrint)(const char *conname, const char *message);
|
|
void Fallback_ConPrint(const char *conname, const char *message)
|
|
{
|
|
pCon_Print(message);
|
|
}
|
|
|
|
void Con_SubPrintf(char *subname, char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
static char string[1024];
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf (string, sizeof(string), format,argptr);
|
|
va_end (argptr);
|
|
|
|
Con_TrySubPrint(subname, string);
|
|
}
|
|
|
|
|
|
//porting zone:
|
|
|
|
|
|
|
|
|
|
#define COLOURGREEN "^2"
|
|
#define COLORWHITE "^7"
|
|
#define COLOURWHITE "^7" // word
|
|
#define COLOURRED "^1"
|
|
#define COLOURYELLOW "^3"
|
|
#define COLOURPURPLE "^5"
|
|
#define COMMANDPREFIX "xmpp"
|
|
#define COMMANDPREFIX2 "jab"
|
|
#define COMMANDPREFIX3 "jabbercl"
|
|
#define playsound(s)
|
|
|
|
|
|
#define TL_NETGETPACKETERROR "NET_GetPacket Error %s\n"
|
|
|
|
static char *JCL_ParseOut (char *data, char *buf, int bufsize) //this is taken out of quake
|
|
{
|
|
int c;
|
|
int len;
|
|
|
|
len = 0;
|
|
buf[0] = 0;
|
|
|
|
if (!data)
|
|
return NULL;
|
|
|
|
// skip whitespace
|
|
while ( (c = *data) <= ' ')
|
|
{
|
|
if (c == 0)
|
|
return NULL; // end of file;
|
|
data++;
|
|
}
|
|
|
|
// handle quoted strings specially
|
|
if (c == '\"')
|
|
{
|
|
data++;
|
|
while (1)
|
|
{
|
|
if (len >= bufsize-1)
|
|
return data;
|
|
|
|
c = *data++;
|
|
if (c=='\"' || !c)
|
|
{
|
|
buf[len] = 0;
|
|
return data;
|
|
}
|
|
buf[len] = c;
|
|
len++;
|
|
}
|
|
}
|
|
|
|
// parse a regular word
|
|
do
|
|
{
|
|
if (len >= bufsize-1)
|
|
return data;
|
|
|
|
buf[len] = c;
|
|
data++;
|
|
len++;
|
|
c = *data;
|
|
} while (c>32);
|
|
|
|
buf[len] = 0;
|
|
return data;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RenameConsole(char *totrim);
|
|
void JCL_Command(char *consolename);
|
|
void JCL_LoadConfig(void);
|
|
void JCL_WriteConfig(void);
|
|
|
|
qintptr_t JCL_ExecuteCommand(qintptr_t *args)
|
|
{
|
|
char cmd[256];
|
|
pCmd_Argv(0, cmd, sizeof(cmd));
|
|
if (!strcmp(cmd, COMMANDPREFIX) || !strcmp(cmd, COMMANDPREFIX2) || !strcmp(cmd, COMMANDPREFIX3))
|
|
{
|
|
if (!args[0])
|
|
JCL_Command("");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qintptr_t JCL_ConsoleLink(qintptr_t *args);
|
|
qintptr_t JCL_ConExecuteCommand(qintptr_t *args);
|
|
|
|
qintptr_t JCL_Frame(qintptr_t *args);
|
|
|
|
qintptr_t Plug_Init(qintptr_t *args)
|
|
{
|
|
if ( Plug_Export("Tick", JCL_Frame) &&
|
|
Plug_Export("ExecuteCommand", JCL_ExecuteCommand))
|
|
{
|
|
CHECKBUILTIN(Net_SetTLSClient);
|
|
if (!BUILTINISVALID(Net_SetTLSClient))
|
|
Con_Printf("XMPP Plugin Loaded ^1without^7 TLS\n");
|
|
else
|
|
Con_Printf("XMPP Plugin Loaded. For help, use: ^[/"COMMANDPREFIX" /help^]\n");
|
|
|
|
Plug_Export("ConsoleLink", JCL_ConsoleLink);
|
|
|
|
if (!Plug_Export("ConExecuteCommand", JCL_ConExecuteCommand))
|
|
{
|
|
Con_Printf("XMPP plugin in single-console mode\n");
|
|
Con_TrySubPrint = Fallback_ConPrint;
|
|
}
|
|
else
|
|
Con_TrySubPrint = pCon_SubPrint;
|
|
|
|
pCmd_AddCommand(COMMANDPREFIX);
|
|
pCmd_AddCommand(COMMANDPREFIX2);
|
|
pCmd_AddCommand(COMMANDPREFIX3);
|
|
|
|
|
|
CHECKBUILTIN(Plug_GetNativePointer);
|
|
if (BUILTINISVALID(Plug_GetNativePointer))
|
|
{
|
|
pICE_Create = pPlug_GetNativePointer("ICE_Create");
|
|
pICE_Find = pPlug_GetNativePointer("ICE_Find");
|
|
pICE_Begin = pPlug_GetNativePointer("ICE_Begin");
|
|
pICE_GetLCandidateInfo = pPlug_GetNativePointer("ICE_GetLCandidateInfo");
|
|
pICE_AddRCandidateInfo = pPlug_GetNativePointer("ICE_AddRCandidateInfo");
|
|
pICE_Close = pPlug_GetNativePointer("ICE_Close");
|
|
}
|
|
|
|
|
|
JCL_LoadConfig();
|
|
return 1;
|
|
}
|
|
else
|
|
Con_Printf("JCL Client Plugin failed\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//\r\n is used to end a line.
|
|
//meaning \0s are valid.
|
|
//but never used cos it breaks strings
|
|
|
|
|
|
#define JCL_MAXMSGLEN 10000
|
|
|
|
typedef struct bresource_s
|
|
{
|
|
char bstatus[128]; //basic status
|
|
char fstatus[128]; //full status
|
|
char server[256];
|
|
int servertype; //0=none, 1=already a client, 2=joinable
|
|
|
|
struct bresource_s *next;
|
|
|
|
char resource[1];
|
|
} bresource_t;
|
|
typedef struct buddy_s
|
|
{
|
|
bresource_t *resources;
|
|
bresource_t *defaultresource; //this is the one that last replied
|
|
int defaulttimestamp;
|
|
qboolean friended;
|
|
|
|
char name[256];
|
|
|
|
struct buddy_s *next;
|
|
char accountdomain[1]; //no resource on there
|
|
} buddy_t;
|
|
typedef struct jclient_s
|
|
{
|
|
char server[64];
|
|
int port;
|
|
|
|
qhandle_t socket;
|
|
|
|
char *outbuf;
|
|
int outbufpos;
|
|
int outbuflen;
|
|
int outbufmax;
|
|
char bufferedinmessage[JCL_MAXMSGLEN+1]; //servers are required to be able to handle messages no shorter than a specific size.
|
|
//which means we need to be able to handle messages when they get to us.
|
|
//servers can still handle larger messages if they choose, so this might not be enough.
|
|
int bufferedinammount;
|
|
|
|
char defaultdest[256];
|
|
|
|
char domain[256];
|
|
char username[256];
|
|
char password[256];
|
|
char resource[256];
|
|
char jid[256]; //this is our full username@domain/resource string
|
|
|
|
int tagdepth;
|
|
int openbracket;
|
|
int instreampos;
|
|
|
|
qboolean tlsconnect; //the old tls method on port 5223.
|
|
qboolean connected; //fully on server and authed and everything.
|
|
qboolean issecure; //tls enabled
|
|
qboolean streamdebug; //echo the stream to subconsoles
|
|
|
|
qboolean preapproval;
|
|
|
|
char curquakeserver[2048];
|
|
char defaultnamespace[2048]; //should be 'jabber:client' or blank (and spammy with all the extra xmlns attribs)
|
|
|
|
struct iq_s {
|
|
struct iq_s *next;
|
|
char id[64];
|
|
int timeout;
|
|
qboolean (*callback) (struct jclient_s *jcl, struct subtree_s *tree);
|
|
} *pendingiqs;
|
|
|
|
struct c2c_s
|
|
{
|
|
struct c2c_s *next;
|
|
char *sessionname;
|
|
buddy_t *tob;
|
|
bresource_t *tor;
|
|
} *p2p;
|
|
|
|
buddy_t *buddies;
|
|
} jclient_t;
|
|
jclient_t *jclient;
|
|
|
|
struct subtree_s;
|
|
|
|
void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...);
|
|
qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres);
|
|
void JCL_GeneratePresence(qboolean force);
|
|
void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, struct subtree_s *tree), char *iqtype, char *target, char *fmt, ...);
|
|
void JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, xmltree_t *node, qboolean destroynode);
|
|
|
|
void JCL_Join(jclient_t *jcl, char *target)
|
|
{
|
|
xmltree_t *jingle;
|
|
struct icestate_s *ice;
|
|
if (!jcl)
|
|
return;
|
|
|
|
#ifdef NOICE
|
|
ice = pICE_Create(NULL, NULL, target, ICE_RAW);
|
|
#else
|
|
ice = pICE_Create(NULL, NULL, target, ICE_ICE);
|
|
#endif
|
|
if (!ice)
|
|
{
|
|
Con_Printf("Unable to connect to %s (dedicated servers cannot initiate connections)\n", target);
|
|
return;
|
|
}
|
|
//FIXME: record the session name
|
|
|
|
jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", "");
|
|
XML_AddParameter(jingle, "sid", ice->conname);
|
|
XML_AddParameter(jingle, "responder", target);
|
|
XML_AddParameter(jingle, "initiator", jcl->jid);
|
|
XML_AddParameter(jingle, "action", "session-initiate");
|
|
{
|
|
xmltree_t *content = XML_CreateNode(jingle, "content", "", "");
|
|
XML_AddParameter(content, "senders", "both");
|
|
XML_AddParameter(content, "name", "some-old-quake-game");
|
|
XML_AddParameter(content, "creator", "initiator");
|
|
{
|
|
xmltree_t *description;
|
|
xmltree_t *transport;
|
|
if (ice->mode == ICE_RAW)
|
|
{
|
|
transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:raw-udp:1", "");
|
|
{
|
|
xmltree_t *candidate;
|
|
struct icecandidate_s *b = NULL;
|
|
struct icecandidate_s *c;
|
|
while ((c = pICE_GetLCandidateInfo(ice)))
|
|
{
|
|
if (!b || b->priority < c->priority)
|
|
b = c;
|
|
}
|
|
|
|
if (b)
|
|
{
|
|
candidate = XML_CreateNode(transport, "candidate", "", "");
|
|
XML_AddParameter(candidate, "ip", b->addr);
|
|
XML_AddParameteri(candidate, "port", b->port);
|
|
XML_AddParameter(candidate, "id", b->candidateid);
|
|
XML_AddParameteri(candidate, "generation", b->generation);
|
|
XML_AddParameteri(candidate, "component", b->component);
|
|
}
|
|
}
|
|
}
|
|
else if (ice->mode == ICE_ICE)
|
|
{
|
|
transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:ice-udp:1", "");
|
|
XML_AddParameter(transport, "ufrag", ice->lfrag);
|
|
XML_AddParameter(transport, "pwd", ice->lpwd);
|
|
{
|
|
struct icecandidate_s *c;
|
|
while ((c = pICE_GetLCandidateInfo(ice)))
|
|
{
|
|
char *ctypename[]={"host", "srflx", "prflx", "relay"};
|
|
xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", "");
|
|
XML_AddParameter(candidate, "type", ctypename[c->type]);
|
|
XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously?
|
|
XML_AddParameteri(candidate, "priority", c->priority);
|
|
XML_AddParameteri(candidate, "port", c->port);
|
|
XML_AddParameteri(candidate, "network", c->network);
|
|
XML_AddParameter(candidate, "ip", c->addr);
|
|
XML_AddParameter(candidate, "id", c->candidateid);
|
|
XML_AddParameteri(candidate, "generation", c->generation);
|
|
XML_AddParameteri(candidate, "foundation", c->foundation);
|
|
XML_AddParameteri(candidate, "component", c->component);
|
|
}
|
|
}
|
|
}
|
|
description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, "");
|
|
XML_AddParameter(description, "media", QUAKEMEDIATYPE);
|
|
{
|
|
/*
|
|
xmltree_t *candidate = XML_CreateNode(description, "payload-type", "", "");
|
|
XML_AddParameter(candidate, "channels", "1");
|
|
XML_AddParameter(candidate, "clockrate", "8000");
|
|
XML_AddParameter(candidate, "id", "104");
|
|
XML_AddParameter(candidate, "name", "SPEEX");
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
Con_Printf("Sending connection start:\n");
|
|
XML_ConPrintTree(jingle, 0);
|
|
JCL_SendIQNode(jcl, NULL, "set", target, jingle, true);
|
|
}
|
|
|
|
void JCL_JingleParsePeerPorts(jclient_t *jcl, xmltree_t *inj, char *from)
|
|
{
|
|
xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0);
|
|
xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0);
|
|
xmltree_t *incandidate;
|
|
struct icestate_s *ice;
|
|
struct icecandidate_s rem;
|
|
int i;
|
|
|
|
ice = pICE_Find(NULL, XML_GetParameter(inj, "sid", ""));
|
|
if (ice && strcmp(ice->friendlyname, from))
|
|
{
|
|
Con_Printf("%s is trying to mess with our connections...\n", from);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; (incandidate = XML_ChildOfTree(intransport, "candidate", i)); i++)
|
|
{
|
|
char *s;
|
|
memset(&rem, 0, sizeof(rem));
|
|
rem.addr = XML_GetParameter(incandidate, "ip", "");
|
|
rem.candidateid = XML_GetParameter(incandidate, "id", "");
|
|
|
|
s = XML_GetParameter(incandidate, "type", "");
|
|
if (s && !strcmp(s, "srflx"))
|
|
rem.type = ICE_SRFLX;
|
|
else if (s && !strcmp(s, "prflx"))
|
|
rem.type = ICE_PRFLX;
|
|
else if (s && !strcmp(s, "relay"))
|
|
rem.type = ICE_RELAY;
|
|
else
|
|
rem.type = ICE_HOST;
|
|
rem.port = atoi(XML_GetParameter(incandidate, "port", "0"));
|
|
rem.priority = atoi(XML_GetParameter(incandidate, "priority", "0"));
|
|
rem.network = atoi(XML_GetParameter(incandidate, "network", "0"));
|
|
rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0"));
|
|
rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0"));
|
|
rem.component = atoi(XML_GetParameter(incandidate, "component", "0"));
|
|
s = XML_GetParameter(incandidate, "protocol", "udp");
|
|
if (s && !strcmp(s, "udp"))
|
|
rem.transport = 0;
|
|
else
|
|
rem.transport = 0;
|
|
pICE_AddRCandidateInfo(ice, &rem);
|
|
}
|
|
}
|
|
struct icestate_s *JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from)
|
|
{
|
|
/*inj contains something like:
|
|
<jingle sid='purplea84196dc' responder='me@example.com/Quake' initiator='them@example.com/Quake' action='session-initiate' xmlns='urn:xmpp:jingle:1'>
|
|
<content senders='both' name='audio-session' creator='initiator'>
|
|
<transport ufrag='SES2' pwd='3XkwverVxJLy2lRXD1lOAb' xmlns='urn:xmpp:jingle:transports:ice-udp:1'>
|
|
<candidate type='host' protocol='udp' priority='2013266431' port='53177' network='0' ip='192.168.0.182' id='purplea84196de' generation='0' foundation='1' component='1'/>
|
|
<candidate type='host' protocol='udp' priority='2013266430' port='36480' network='0' ip='192.168.0.182' id='purplea84196dd' generation='0' foundation='1' component='2'/>
|
|
</transport>
|
|
<description media='audio' xmlns='urn:xmpp:jingle:apps:rtp:1'>
|
|
<payload-type channels='1' clockrate='8000' id='104' name='SPEEX'/>
|
|
<payload-type channels='1' clockrate='16000' id='103' name='SPEEX'/>
|
|
</description>
|
|
</content>
|
|
</jingle>
|
|
*/
|
|
|
|
xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0);
|
|
xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0);
|
|
xmltree_t *indescription = XML_ChildOfTree(incontent, "description", 0);
|
|
char *transportxmlns = intransport?intransport->xmlns:"";
|
|
char *descriptionxmlns = indescription?indescription->xmlns:"";
|
|
char *descriptionmedia = XML_GetParameter(indescription, "media", "");
|
|
|
|
xmltree_t *jingle;
|
|
struct icestate_s *ice;
|
|
qboolean accepted = false;
|
|
enum icemode_e imode;
|
|
|
|
imode = strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICE_ICE:ICE_RAW;
|
|
|
|
if (!incontent || strcmp(descriptionmedia, QUAKEMEDIATYPE) || strcmp(descriptionxmlns, QUAKEMEDIAXMLNS))
|
|
{
|
|
//decline it
|
|
ice = NULL;
|
|
}
|
|
else
|
|
ice = pICE_Create(NULL, XML_GetParameter(inj, "sid", ""), from, imode);
|
|
|
|
jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", "");
|
|
XML_AddParameter(jingle, "sid", ice->conname);
|
|
XML_AddParameter(jingle, "responder", XML_GetParameter(inj, "responder", ""));
|
|
XML_AddParameter(jingle, "initiator", XML_GetParameter(inj, "initiator", ""));
|
|
if (!ice)
|
|
XML_AddParameter(jingle, "action", "session-terminate");
|
|
else
|
|
{
|
|
xmltree_t *content = XML_CreateNode(jingle, "content", "", "");
|
|
#ifdef NOICE
|
|
if (imode != ICE_RAW)
|
|
{
|
|
char buf[256];
|
|
XML_AddParameter(jingle, "action", "transport-replace");
|
|
Q_snprintf(buf, sizeof(buf), "raw-%s", XML_GetParameter(incontent, "name", ""));
|
|
XML_AddParameter(content, "name", buf);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
XML_AddParameter(jingle, "action", "session-accept");
|
|
XML_AddParameter(content, "name", XML_GetParameter(incontent, "name", ""));
|
|
accepted = true;
|
|
}
|
|
XML_AddParameter(content, "senders", "both");
|
|
XML_AddParameter(content, "creator", "initiator");
|
|
{
|
|
xmltree_t *description;
|
|
xmltree_t *transport;
|
|
transport = XML_CreateNode(content, "transport", transportxmlns, "");
|
|
if (imode == ICE_RAW)
|
|
{
|
|
//raw-udp can send only one candidate
|
|
xmltree_t *candidate;
|
|
struct icecandidate_s *b = NULL;
|
|
struct icecandidate_s *c;
|
|
while ((c = pICE_GetLCandidateInfo(ice)))
|
|
{
|
|
if (!b || b->priority < c->priority)
|
|
b = c;
|
|
}
|
|
if (b)
|
|
{
|
|
candidate = XML_CreateNode(transport, "candidate", "", "");
|
|
XML_AddParameter(candidate, "ip", b->addr);
|
|
XML_AddParameteri(candidate, "port", b->port);
|
|
XML_AddParameter(candidate, "id", b->candidateid);
|
|
XML_AddParameteri(candidate, "generation", b->generation);
|
|
XML_AddParameteri(candidate, "component", b->component);
|
|
}
|
|
}
|
|
else if (imode == ICE_ICE)
|
|
{
|
|
//ice can send multiple candidates
|
|
struct icecandidate_s *c;
|
|
XML_AddParameter(transport, "ufrag", ice->lfrag);
|
|
XML_AddParameter(transport, "pwd", ice->lpwd);
|
|
while ((c = pICE_GetLCandidateInfo(ice)))
|
|
{
|
|
char *ctypename[]={"host", "srflx", "prflx", "relay"};
|
|
xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", "");
|
|
XML_AddParameter(candidate, "type", ctypename[c->type]);
|
|
XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously?
|
|
XML_AddParameteri(candidate, "priority", c->priority);
|
|
XML_AddParameteri(candidate, "port", c->port);
|
|
XML_AddParameteri(candidate, "network", c->network);
|
|
XML_AddParameter(candidate, "ip", c->addr);
|
|
XML_AddParameter(candidate, "id", c->candidateid);
|
|
XML_AddParameteri(candidate, "generation", c->generation);
|
|
XML_AddParameteri(candidate, "foundation", c->foundation);
|
|
XML_AddParameteri(candidate, "component", c->component);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//egads! can't cope with that.
|
|
}
|
|
description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, "");
|
|
XML_AddParameter(description, "media", QUAKEMEDIATYPE);
|
|
{
|
|
/*
|
|
xmltree_t *candidate = XML_CreateNode(description, "payload-type", "", "");
|
|
XML_AddParameter(candidate, "channels", "1");
|
|
XML_AddParameter(candidate, "clockrate", "8000");
|
|
XML_AddParameter(candidate, "id", "104");
|
|
XML_AddParameter(candidate, "name", "SPEEX");
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
if (!ice)
|
|
Con_Printf("Sending reject:\n");
|
|
else
|
|
Con_Printf("Sending accept:\n");
|
|
XML_ConPrintTree(jingle, 0);
|
|
JCL_SendIQNode(jcl, NULL, "set", from, jingle, true);
|
|
|
|
if (ice)
|
|
JCL_JingleParsePeerPorts(jcl, inj, from);
|
|
|
|
//if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted
|
|
if (ice && accepted)
|
|
pICE_Begin(ice, NULL, 0);
|
|
return ice;
|
|
}
|
|
|
|
void JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id)
|
|
{
|
|
char *action = XML_GetParameter(tree, "action", "");
|
|
// char *initiator = XML_GetParameter(tree, "initiator", "");
|
|
// char *responder = XML_GetParameter(tree, "responder", "");
|
|
char *sid = XML_GetParameter(tree, "sid", "");
|
|
|
|
//validate sender
|
|
struct icestate_s *ice = pICE_Find(NULL, sid);
|
|
if (ice && strcmp(ice->friendlyname, from))
|
|
{
|
|
Con_Printf("%s is trying to mess with our connections...\n", from);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(action, "session-terminate"))
|
|
{
|
|
if (ice)
|
|
pICE_Close(ice);
|
|
|
|
Con_Printf("Session ended\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
else if (!strcmp(action, "session-accept"))
|
|
{
|
|
if (!ice)
|
|
{
|
|
Con_Printf("Cannot accept a session that was never created\n");
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Session Accepted!\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
|
|
if (ice)
|
|
JCL_JingleParsePeerPorts(jcl, tree, from);
|
|
|
|
//if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted
|
|
if (ice)
|
|
pICE_Begin(ice, NULL, 0);
|
|
}
|
|
}
|
|
else if (!strcmp(action, "session-initiate"))
|
|
{
|
|
Con_Printf("Peer initiating connection!\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
|
|
ice = JCL_JingleHandleInitiate(jcl, tree, from);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Unknown jingle action: %s\n", action);
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
|
|
JCL_AddClientMessagef(jcl,
|
|
"<iq type='result' to='%s' id='%s' />", from, id);
|
|
}
|
|
|
|
|
|
qintptr_t JCL_ConsoleLink(qintptr_t *args)
|
|
{
|
|
char text[256];
|
|
char link[256];
|
|
pCmd_Argv(0, text, sizeof(text));
|
|
pCmd_Argv(1, link, sizeof(link));
|
|
|
|
if (!strncmp(link, "\\xmppauth\\", 6))
|
|
{
|
|
//we should friend them too.
|
|
if (jclient)
|
|
JCL_AddClientMessagef(jclient, "<presence to='%s' type='subscribed'/>", link+10);
|
|
return true;
|
|
}
|
|
if (!strncmp(link, "\\xmppdeny\\", 6))
|
|
{
|
|
if (jclient)
|
|
JCL_AddClientMessagef(jclient, "<presence to='%s' type='unsubscribed'/>", link+10);
|
|
return true;
|
|
}
|
|
|
|
if (!strncmp(link, "\\xmppjoin\\", 6))
|
|
{
|
|
JCL_Join(jclient, link+10);
|
|
return false;
|
|
}
|
|
if (!strncmp(link, "\\xmpp\\", 6))
|
|
{
|
|
if (jclient)
|
|
{
|
|
char *f;
|
|
buddy_t *b;
|
|
bresource_t *br;
|
|
|
|
JCL_FindBuddy(jclient, link+6, &b, &br);
|
|
f = b->name;
|
|
b->defaultresource = br;
|
|
|
|
if (BUILTINISVALID(Con_SubPrint))
|
|
pCon_SubPrint(f, "");
|
|
if (BUILTINISVALID(Con_SetActive))
|
|
pCon_SetActive(f);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qintptr_t JCL_ConExecuteCommand(qintptr_t *args)
|
|
{
|
|
buddy_t *b;
|
|
char consolename[256];
|
|
if (!jclient)
|
|
{
|
|
char buffer[256];
|
|
pCmd_Argv(0, buffer, sizeof(buffer));
|
|
Con_SubPrintf(buffer, "You were disconnected\n");
|
|
return true;
|
|
}
|
|
pCmd_Argv(0, consolename, sizeof(consolename));
|
|
for (b = jclient->buddies; b; b = b->next)
|
|
{
|
|
if (!strcmp(b->name, consolename))
|
|
{
|
|
if (b->defaultresource)
|
|
Q_snprintf(jclient->defaultdest, sizeof(jclient->defaultdest), "%s/%s", b->accountdomain, b->defaultresource->resource);
|
|
else
|
|
Q_snprintf(jclient->defaultdest, sizeof(jclient->defaultdest), "%s", b->accountdomain);
|
|
break;
|
|
}
|
|
}
|
|
JCL_Command(consolename);
|
|
return true;
|
|
}
|
|
|
|
void JCL_FlushOutgoing(jclient_t *jcl)
|
|
{
|
|
int sent;
|
|
if (!jcl || !jcl->outbuflen)
|
|
return;
|
|
|
|
sent = pNet_Send(jcl->socket, jcl->outbuf + jcl->outbufpos, jcl->outbuflen); //FIXME: This needs rewriting to cope with errors.
|
|
if (sent > 0)
|
|
{
|
|
//and print it on some subconsole if we're debugging
|
|
if (jcl->streamdebug)
|
|
{
|
|
char t = jcl->outbuf[jcl->outbufpos+sent];
|
|
jcl->outbuf[jcl->outbufpos+sent] = 0;
|
|
Con_SubPrintf("xmppout", COLOURYELLOW "%s\n", jcl->outbuf + jcl->outbufpos);
|
|
jcl->outbuf[jcl->outbufpos+sent] = t;
|
|
}
|
|
|
|
jcl->outbufpos += sent;
|
|
jcl->outbuflen -= sent;
|
|
}
|
|
else
|
|
Con_Printf("Unable to send anything\n");
|
|
}
|
|
void JCL_AddClientMessage(jclient_t *jcl, char *msg, int datalen)
|
|
{
|
|
//handle overflows
|
|
if (jcl->outbufpos+jcl->outbuflen+datalen > jcl->outbufmax)
|
|
{
|
|
if (jcl->outbuflen+datalen <= jcl->outbufmax)
|
|
{
|
|
//can get away with just moving the data
|
|
memmove(jcl->outbuf, jcl->outbuf + jcl->outbufpos, jcl->outbuflen);
|
|
jcl->outbufpos = 0;
|
|
}
|
|
else
|
|
{
|
|
//need to expand the buffer.
|
|
int newmax = (jcl->outbuflen+datalen)*2;
|
|
char *newbuf;
|
|
|
|
if (newmax < jcl->outbuflen)
|
|
newbuf = NULL; //eep... some special kind of evil overflow.
|
|
else
|
|
newbuf = malloc(newmax+1);
|
|
|
|
if (newbuf)
|
|
{
|
|
memcpy(newbuf, jcl->outbuf + jcl->outbufpos, jcl->outbuflen);
|
|
jcl->outbufmax = newmax;
|
|
jcl->outbufpos = 0;
|
|
jcl->outbuf = newbuf;
|
|
}
|
|
else
|
|
datalen = 0; //eep!
|
|
}
|
|
}
|
|
//and write our data to it
|
|
memcpy(jcl->outbuf + jcl->outbufpos + jcl->outbuflen, msg, datalen);
|
|
jcl->outbuflen += datalen;
|
|
|
|
//try and flush it now
|
|
JCL_FlushOutgoing(jcl);
|
|
}
|
|
void JCL_AddClientMessageString(jclient_t *jcl, char *msg)
|
|
{
|
|
JCL_AddClientMessage(jcl, msg, strlen(msg));
|
|
}
|
|
void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char body[2048];
|
|
|
|
va_start (argptr, fmt);
|
|
Q_vsnprintf (body, sizeof(body), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
JCL_AddClientMessageString(jcl, body);
|
|
}
|
|
jclient_t *JCL_Connect(char *server, int defport, qboolean usesecure, char *account, char *password)
|
|
{
|
|
jclient_t *jcl;
|
|
char *at;
|
|
|
|
if (usesecure)
|
|
{
|
|
if (!BUILTINISVALID(Net_SetTLSClient))
|
|
{
|
|
Con_Printf("JCL_OpenSocket: TLS is not supported\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
at = strchr(account, '@');
|
|
if (!at)
|
|
return NULL;
|
|
|
|
|
|
jcl = malloc(sizeof(jclient_t));
|
|
if (!jcl)
|
|
return NULL;
|
|
|
|
memset(jcl, 0, sizeof(jclient_t));
|
|
|
|
|
|
jcl->socket = pNet_TCPConnect(server, defport); //port is only used if the url doesn't contain one. It's a default.
|
|
|
|
//not yet blocking. So no frequent attempts please...
|
|
//non blocking prevents connect from returning worthwhile sensible value.
|
|
if ((int)jcl->socket < 0)
|
|
{
|
|
Con_Printf("JCL_OpenSocket: couldn't connect\n");
|
|
free(jcl);
|
|
return NULL;
|
|
}
|
|
|
|
if (usesecure)
|
|
{
|
|
if (pNet_SetTLSClient(jcl->socket, server)<0)
|
|
{
|
|
pNet_Close(jcl->socket);
|
|
free(jcl);
|
|
jcl = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
jcl->issecure = true;
|
|
}
|
|
else
|
|
jcl->issecure = false;
|
|
|
|
|
|
// gethostname(jcl->hostname, sizeof(jcl->hostname));
|
|
// jcl->hostname[sizeof(jcl->hostname)-1] = 0;
|
|
|
|
jcl->tlsconnect = usesecure;
|
|
jcl->streamdebug = !!pCvar_GetFloat("xmpp_debug");
|
|
|
|
*at = '\0';
|
|
Q_strlcpy(jcl->server, server, sizeof(jcl->server));
|
|
Q_strlcpy(jcl->username, account, sizeof(jcl->username));
|
|
Q_strlcpy(jcl->domain, at+1, sizeof(jcl->domain));
|
|
Q_strlcpy(jcl->password, password, sizeof(jcl->password));
|
|
|
|
Q_strlcpy(jcl->resource, "Quake", sizeof(jcl->password));
|
|
|
|
Con_Printf("Trying to connect to %s\n", at+1);
|
|
JCL_AddClientMessageString(jcl,
|
|
"<?xml version='1.0' ?>"
|
|
"<stream:stream to='");
|
|
JCL_AddClientMessageString(jcl, jcl->domain);
|
|
JCL_AddClientMessageString(jcl, "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
|
|
|
|
return jcl;
|
|
}
|
|
|
|
char base64[512+1];
|
|
unsigned int base64_len; //current output length
|
|
unsigned int base64_cur; //current pending value
|
|
unsigned int base64_bits;//current pending bits
|
|
char Base64_From64(int byt)
|
|
{
|
|
if (byt >= 0 && byt < 26)
|
|
return 'A' + byt - 0;
|
|
if (byt >= 26 && byt < 52)
|
|
return 'a' + byt - 26;
|
|
if (byt >= 52 && byt < 62)
|
|
return '0' + byt - 52;
|
|
if (byt == 62)
|
|
return '+';
|
|
if (byt == 63)
|
|
return '/';
|
|
return '!';
|
|
}
|
|
void Base64_Byte(unsigned int byt)
|
|
{
|
|
if (base64_len+8>=sizeof(base64)-1)
|
|
return;
|
|
base64_cur |= byt<<(16- base64_bits);//first byte fills highest bits
|
|
base64_bits += 8;
|
|
if (base64_bits == 24)
|
|
{
|
|
base64[base64_len++] = Base64_From64((base64_cur>>18)&63);
|
|
base64[base64_len++] = Base64_From64((base64_cur>>12)&63);
|
|
base64[base64_len++] = Base64_From64((base64_cur>>6)&63);
|
|
base64[base64_len++] = Base64_From64((base64_cur>>0)&63);
|
|
base64[base64_len] = '\0';
|
|
// Con_Printf("base64: %s\n", base64+base64_len-4);
|
|
base64_bits = 0;
|
|
base64_cur = 0;
|
|
}
|
|
}
|
|
|
|
void Base64_Add(char *s, int len)
|
|
{
|
|
unsigned char *us = (unsigned char *)s;
|
|
while(len-->0)
|
|
Base64_Byte(*us++);
|
|
}
|
|
|
|
void Base64_Finish(void)
|
|
{
|
|
//output is always a multiple of four
|
|
|
|
//0(0)->0(0)
|
|
//1(8)->2(12)
|
|
//2(16)->3(18)
|
|
//3(24)->4(24)
|
|
|
|
if (base64_bits != 0)
|
|
{
|
|
base64[base64_len++]=Base64_From64((base64_cur>>18)&63);
|
|
base64[base64_len++]=Base64_From64((base64_cur>>12)&63);
|
|
if (base64_bits == 8)
|
|
{
|
|
base64[base64_len++]= '=';
|
|
base64[base64_len++]= '=';
|
|
}
|
|
else
|
|
{
|
|
base64[base64_len++]=Base64_From64((base64_cur>>6)&63);
|
|
if (base64_bits == 16)
|
|
base64[base64_len++]= '=';
|
|
else
|
|
base64[base64_len++]=Base64_From64((base64_cur>>0)&63);
|
|
}
|
|
}
|
|
base64[base64_len++] = '\0';
|
|
|
|
base64_len = 0; //for next time (use strlen)
|
|
base64_bits = 0;
|
|
base64_cur = 0;
|
|
}
|
|
|
|
char *TrimResourceFromJid(char *jid)
|
|
{
|
|
char *slash;
|
|
slash = strchr(jid, '/');
|
|
if (slash)
|
|
{
|
|
*slash = '\0';
|
|
return slash+1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres)
|
|
{
|
|
char name[256];
|
|
char *res;
|
|
buddy_t *b;
|
|
bresource_t *r = NULL;
|
|
|
|
Q_strlcpy(name, jid, sizeof(name));
|
|
res = TrimResourceFromJid(name);
|
|
|
|
for (b = jcl->buddies; b; b = b->next)
|
|
{
|
|
if (!strcmp(b->accountdomain, name))
|
|
break;
|
|
}
|
|
if (!b)
|
|
{
|
|
b = malloc(sizeof(*b) + strlen(name));
|
|
memset(b, 0, sizeof(*b));
|
|
b->next = jcl->buddies;
|
|
jcl->buddies = b;
|
|
strcpy(b->accountdomain, name);
|
|
Q_strlcpy(b->name, name, sizeof(b->name)); //default
|
|
}
|
|
*buddy = b;
|
|
if (res && bres)
|
|
{
|
|
for (r = b->resources; r; r = r->next)
|
|
{
|
|
if (!strcmp(r->resource, res))
|
|
break;
|
|
}
|
|
if (!r)
|
|
{
|
|
r = malloc(sizeof(*r) + strlen(res));
|
|
memset(r, 0, sizeof(*r));
|
|
r->next = b->resources;
|
|
b->resources = r;
|
|
strcpy(r->resource, res);
|
|
}
|
|
*bres = r;
|
|
}
|
|
else if (bres)
|
|
*bres = NULL;
|
|
return false;
|
|
}
|
|
|
|
void JCL_SendIQ(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, char *body)
|
|
{
|
|
struct iq_s *iq;
|
|
|
|
iq = malloc(sizeof(*iq));
|
|
iq->next = jcl->pendingiqs;
|
|
jcl->pendingiqs = iq;
|
|
Q_snprintf(iq->id, sizeof(iq->id), "%i", rand());
|
|
iq->callback = callback;
|
|
|
|
if (target)
|
|
{
|
|
if (*jcl->jid)
|
|
JCL_AddClientMessagef(jcl, "<iq type='%s' id='%s' from='%s' to='%s'>", iqtype, iq->id, jcl->jid, target);
|
|
else
|
|
JCL_AddClientMessagef(jcl, "<iq type='%s' id='%s' to='%s'>", iqtype, iq->id, target);
|
|
}
|
|
else
|
|
{
|
|
if (*jcl->jid)
|
|
JCL_AddClientMessagef(jcl, "<iq type='%s' id='%s' from='%s'>", iqtype, iq->id, jcl->jid);
|
|
else
|
|
JCL_AddClientMessagef(jcl, "<iq type='%s' id='%s'>", iqtype, iq->id);
|
|
}
|
|
JCL_AddClientMessageString(jcl, body);
|
|
JCL_AddClientMessageString(jcl, "</iq>");
|
|
}
|
|
void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char body[2048];
|
|
|
|
va_start (argptr, fmt);
|
|
Q_vsnprintf (body, sizeof(body), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
JCL_SendIQ(jcl, callback, iqtype, target, body);
|
|
}
|
|
void JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, xmltree_t *node, qboolean destroynode)
|
|
{
|
|
char *s = XML_GenerateString(node);
|
|
JCL_SendIQ(jcl, callback, iqtype, target, s);
|
|
free(s);
|
|
if (destroynode)
|
|
XML_Destroy(node);
|
|
}
|
|
static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp)
|
|
{
|
|
xmltree_t *i;
|
|
buddy_t *buddy;
|
|
int cnum = 0;
|
|
while ((i = XML_ChildOfTree(listp, "item", cnum++)))
|
|
{
|
|
char *name = XML_GetParameter(i, "name", "");
|
|
char *jid = XML_GetParameter(i, "jid", "");
|
|
// char *sub = XML_GetParameter(i, "subscription", "");
|
|
JCL_FindBuddy(jcl, jid, &buddy, NULL);
|
|
|
|
if (*name)
|
|
Q_strlcpy(buddy->name, name, sizeof(buddy->name));
|
|
buddy->friended = true;
|
|
}
|
|
}
|
|
static qboolean JCL_RosterReply(jclient_t *jcl, xmltree_t *tree)
|
|
{
|
|
xmltree_t *c;
|
|
c = XML_ChildOfTree(tree, "query", 0);
|
|
if (c)
|
|
{
|
|
JCL_RosterUpdate(jcl, c);
|
|
return true;
|
|
}
|
|
JCL_GeneratePresence(true);
|
|
return false;
|
|
}
|
|
|
|
static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree)
|
|
{
|
|
xmltree_t *c;
|
|
c = XML_ChildOfTree(tree, "bind", 0);
|
|
if (c)
|
|
{
|
|
c = XML_ChildOfTree(c, "jid", 0);
|
|
if (c)
|
|
{
|
|
Q_strlcpy(jcl->jid, c->body, sizeof(jcl->jid));
|
|
Con_Printf("Bound to jid ^[[%s]\\xmpp\\%s^]\n", jcl->jid, jcl->jid);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static qboolean JCL_SessionReply(jclient_t *jcl, xmltree_t *tree)
|
|
{
|
|
JCL_SendIQf(jcl, JCL_RosterReply, "get", NULL, "<query xmlns='jabber:iq:roster'/>");
|
|
return true;
|
|
}
|
|
|
|
|
|
static char *caps[] =
|
|
{
|
|
#if 1
|
|
"http://jabber.org/protocol/caps",
|
|
"http://jabber.org/protocol/disco#info",
|
|
// "http://jabber.org/protocol/disco#items",
|
|
|
|
// "http://www.google.com/xmpp/protocol/camera/v1",
|
|
// "http://www.google.com/xmpp/protocol/session",
|
|
// "http://www.google.com/xmpp/protocol/voice/v1",
|
|
// "http://www.google.com/xmpp/protocol/video/v1",
|
|
|
|
"jabber:iq:version",
|
|
"urn:xmpp:jingle:1",
|
|
QUAKEMEDIAXMLNS,
|
|
// "urn:xmpp:jingle:apps:rtp:1", //we don't support rtp video/voice chat
|
|
// "urn:xmpp:jingle:apps:rtp:audio",//we don't support rtp voice chat
|
|
// "urn:xmpp:jingle:apps:rtp:video",//we don't support rtp video chat
|
|
"urn:xmpp:jingle:transports:raw-udp:1",
|
|
#ifndef NOICE
|
|
"urn:xmpp:jingle:transports:ice-udp:1",
|
|
#endif
|
|
#ifndef Q3_VM
|
|
"urn:xmpp:time",
|
|
#endif
|
|
"urn:xmpp:ping", //FIXME: I'm not keen on this. I only added support to stop errors from pidgin when trying to debug.
|
|
|
|
#else
|
|
//for testing, this is the list of features pidgin supports (which is the other client I'm testing against).
|
|
|
|
"jabber:iq:last",
|
|
"jabber:iq:oob",
|
|
"urn:xmpp:time",
|
|
"jabber:iq:version",
|
|
"jabber:x:conference",
|
|
"http://jabber.org/protocol/bytestreams",
|
|
"http://jabber.org/protocol/caps",
|
|
"http://jabber.org/protocol/chatstates",
|
|
"http://jabber.org/protocol/disco#info",
|
|
"http://jabber.org/protocol/disco#items",
|
|
"http://jabber.org/protocol/muc",
|
|
"http://jabber.org/protocol/muc#user",
|
|
"http://jabber.org/protocol/si",
|
|
// "http://jabber.org/protocol/si/profile/file-transfer",
|
|
"http://jabber.org/protocol/xhtml-im",
|
|
"urn:xmpp:ping",
|
|
"urn:xmpp:attention:0",
|
|
"urn:xmpp:bob",
|
|
"urn:xmpp:jingle:1",
|
|
"http://www.google.com/xmpp/protocol/session",
|
|
"http://www.google.com/xmpp/protocol/voice/v1",
|
|
"http://www.google.com/xmpp/protocol/video/v1",
|
|
"http://www.google.com/xmpp/protocol/camera/v1",
|
|
"urn:xmpp:jingle:apps:rtp:1",
|
|
"urn:xmpp:jingle:apps:rtp:audio",
|
|
"urn:xmpp:jingle:apps:rtp:video",
|
|
"urn:xmpp:jingle:transports:raw-udp:1",
|
|
"urn:xmpp:jingle:transports:ice-udp:1",
|
|
"urn:xmpp:avatar:metadata",
|
|
"urn:xmpp:avatar:data",
|
|
"urn:xmpp:avatar:metadata+notify",
|
|
"http://jabber.org/protocol/mood",
|
|
"http://jabber.org/protocol/mood+notify",
|
|
"http://jabber.org/protocol/tune",
|
|
"http://jabber.org/protocol/tune+notify",
|
|
"http://jabber.org/protocol/nick",
|
|
"http://jabber.org/protocol/nick+notify",
|
|
"http://jabber.org/protocol/ibb",
|
|
#endif
|
|
NULL
|
|
};
|
|
static void buildcaps(char *out, int outlen)
|
|
{
|
|
int i;
|
|
Q_strncpyz(out, "<identity category='client' type='pc' name='FTEQW'/>", outlen);
|
|
|
|
for (i = 0; caps[i]; i++)
|
|
{
|
|
Q_strlcat(out, "<feature var='", outlen);
|
|
Q_strlcat(out, caps[i], outlen);
|
|
Q_strlcat(out, "'/>", outlen);
|
|
}
|
|
}
|
|
|
|
static int qsortcaps(const void *va, const void *vb)
|
|
{
|
|
char *a = *(char**)va;
|
|
char *b = *(char**)vb;
|
|
return strcmp(a, b);
|
|
}
|
|
int SHA1(char *digest, int maxdigestsize, char *string, int stringlen);
|
|
char *buildcapshash(void)
|
|
{
|
|
int i, l;
|
|
char out[8192];
|
|
int outlen = sizeof(out);
|
|
unsigned char digest[64];
|
|
|
|
Q_strlcpy(out, "client/pc//FTEQW<", outlen);
|
|
|
|
qsort(caps, sizeof(caps)/sizeof(caps[0]) - 1, sizeof(char*), qsortcaps);
|
|
for (i = 0; caps[i]; i++)
|
|
{
|
|
Q_strlcat(out, caps[i], outlen);
|
|
Q_strlcat(out, "<", outlen);
|
|
}
|
|
l = SHA1(digest, sizeof(digest), out, strlen(out));
|
|
|
|
for (i = 0; i < l; i++)
|
|
Base64_Byte(digest[i]);
|
|
Base64_Finish();
|
|
return base64;
|
|
}
|
|
|
|
#define JCL_DONE 0
|
|
#define JCL_CONTINUE 1
|
|
#define JCL_KILL 2
|
|
int JCL_ClientFrame(jclient_t *jcl)
|
|
{
|
|
int pos;
|
|
xmltree_t *tree, *ot;
|
|
char *f;
|
|
int ret;
|
|
qboolean unparsable;
|
|
|
|
int olddepth;
|
|
|
|
ret = pNet_Recv(jcl->socket, jcl->bufferedinmessage+jcl->bufferedinammount, sizeof(jcl->bufferedinmessage)-1 - jcl->bufferedinammount);
|
|
if (ret == 0)
|
|
{
|
|
if (!jcl->bufferedinammount) //if we are half way through a message, read any possible conjunctions.
|
|
return JCL_DONE; //remove
|
|
}
|
|
if (ret < 0)
|
|
{
|
|
Con_Printf("JCL: socket error\n");
|
|
return JCL_KILL;
|
|
}
|
|
|
|
if (ret>0)
|
|
{
|
|
jcl->bufferedinammount+=ret;
|
|
jcl->bufferedinmessage[jcl->bufferedinammount] = 0;
|
|
}
|
|
|
|
olddepth = jcl->tagdepth;
|
|
|
|
//we never end parsing in the middle of a < >
|
|
//this means we can filter out the <? ?>, <!-- --> and < /> stuff properly
|
|
for (pos = jcl->instreampos; pos < jcl->bufferedinammount; pos++)
|
|
{
|
|
if (jcl->bufferedinmessage[pos] == '<')
|
|
{
|
|
jcl->instreampos = pos;
|
|
}
|
|
else if (jcl->bufferedinmessage[pos] == '>')
|
|
{
|
|
if (pos < 1)
|
|
break; //erm...
|
|
|
|
if (jcl->bufferedinmessage[pos-1] != '/') //<blah/> is a tag without a body
|
|
{
|
|
if (jcl->bufferedinmessage[jcl->instreampos+1] != '?') //<? blah ?> is a tag without a body
|
|
{
|
|
if (jcl->bufferedinmessage[pos-1] != '?')
|
|
{
|
|
if (jcl->bufferedinmessage[jcl->instreampos+1] == '/') //</blah> is the end of a tag with a body
|
|
jcl->tagdepth--;
|
|
else
|
|
jcl->tagdepth++; //<blah> is the start of a tag with a body
|
|
}
|
|
}
|
|
}
|
|
|
|
jcl->instreampos=pos+1;
|
|
}
|
|
}
|
|
|
|
if (jcl->tagdepth == 1 && olddepth == 0)
|
|
{ //first bit of info
|
|
|
|
pos = 0;
|
|
tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, true, "");
|
|
while (tree && !strcmp(tree->name, "?xml"))
|
|
{
|
|
XML_Destroy(tree);
|
|
tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, true, "");
|
|
}
|
|
|
|
if (jcl->streamdebug)
|
|
{
|
|
char t = jcl->bufferedinmessage[pos];
|
|
jcl->bufferedinmessage[pos] = 0;
|
|
Con_TrySubPrint("xmppin", jcl->bufferedinmessage);
|
|
Con_TrySubPrint("xmppin", "\n");
|
|
jcl->bufferedinmessage[pos] = t;
|
|
}
|
|
|
|
if (!tree)
|
|
{
|
|
Con_Printf("Not an xml stream\n");
|
|
return JCL_KILL;
|
|
}
|
|
if (strcmp(tree->name, "stream") || strcmp(tree->xmlns, "http://etherx.jabber.org/streams"))
|
|
{
|
|
Con_Printf("Not an xmpp stream\n");
|
|
return JCL_KILL;
|
|
}
|
|
Q_strlcpy(jcl->defaultnamespace, tree->xmlns_dflt, sizeof(jcl->defaultnamespace));
|
|
|
|
ot = tree;
|
|
tree = tree->child;
|
|
ot->child = NULL;
|
|
|
|
// Con_Printf("Discard\n");
|
|
// XML_ConPrintTree(ot, 0);
|
|
XML_Destroy(ot);
|
|
|
|
if (!tree)
|
|
{
|
|
memmove(jcl->bufferedinmessage, jcl->bufferedinmessage+pos, jcl->bufferedinammount - (pos));
|
|
jcl->bufferedinammount-=pos;
|
|
jcl->instreampos-=pos;
|
|
|
|
return JCL_DONE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (jcl->tagdepth != 1)
|
|
{
|
|
if (jcl->tagdepth < 1 && jcl->bufferedinammount==jcl->instreampos)
|
|
{
|
|
Con_Printf("End of XML stream\n");
|
|
return JCL_KILL;
|
|
}
|
|
return JCL_DONE;
|
|
}
|
|
|
|
pos = 0;
|
|
tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, false, jcl->defaultnamespace);
|
|
|
|
if (jcl->streamdebug)
|
|
{
|
|
char t = jcl->bufferedinmessage[pos];
|
|
jcl->bufferedinmessage[pos] = 0;
|
|
Con_TrySubPrint("xmppin", jcl->bufferedinmessage);
|
|
Con_TrySubPrint("xmppin", "\n");
|
|
jcl->bufferedinmessage[pos] = t;
|
|
}
|
|
|
|
if (!tree)
|
|
{
|
|
// Con_Printf("No input tree: %s", jcl->bufferedinmessage);
|
|
return JCL_DONE;
|
|
}
|
|
}
|
|
|
|
// Con_Printf("read\n");
|
|
// XML_ConPrintTree(tree, 0);
|
|
|
|
|
|
unparsable = true;
|
|
if (!strcmp(tree->name, "features"))
|
|
{
|
|
if ((ot=XML_ChildOfTree(tree, "bind", 0)))
|
|
{
|
|
unparsable = false;
|
|
JCL_SendIQf(jcl, JCL_BindReply, "set", NULL, "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>%s</resource></bind>", jcl->resource);
|
|
}
|
|
if ((ot=XML_ChildOfTree(tree, "session", 0)))
|
|
{
|
|
unparsable = false;
|
|
JCL_SendIQf(jcl, JCL_SessionReply, "set", NULL, "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
|
|
jcl->connected = true;
|
|
|
|
JCL_WriteConfig();
|
|
|
|
// JCL_AddClientMessageString(jcl, "<iq type='get' to='gmail.com' id='H_2'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
|
|
}
|
|
|
|
|
|
if (unparsable)
|
|
{
|
|
if ((!jclient->issecure) && BUILTINISVALID(Net_SetTLSClient) && XML_ChildOfTree(tree, "starttls", 0) != NULL && !pCvar_GetFloat("xmpp_disabletls"))
|
|
{
|
|
Con_Printf("Attempting to switch to TLS\n");
|
|
JCL_AddClientMessageString(jcl, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />");
|
|
unparsable = false;
|
|
}
|
|
else if ((ot=XML_ChildOfTree(tree, "mechanisms", 0)))
|
|
{
|
|
for(ot = ot->child; ot; ot = ot->sibling)
|
|
{
|
|
if (!strcmp(ot->body, "PLAIN"))
|
|
{
|
|
char msg[2048];
|
|
if (!jclient->issecure && !pCvar_GetFloat("xmpp_allowplainauth")) //probably don't send plain without tls.
|
|
{
|
|
//plain can still be read with man-in-the-middle attacks, of course, even with tls if the certificate is spoofed.
|
|
Con_Printf("Ignoring auth \'%s\'\n", ot->body);
|
|
continue;
|
|
}
|
|
Con_Printf("Authing with \'%s\'%s\n", ot->body, jclient->issecure?" over tls/ssl":"");
|
|
|
|
// Base64_Add(jclient->username, strlen(jcl->username));
|
|
// Base64_Add("@", 1);
|
|
// Base64_Add(jclient->domain, strlen(jcl->domain));
|
|
Base64_Add("", 1);
|
|
Base64_Add(jclient->username, strlen(jcl->username));
|
|
Base64_Add("", 1);
|
|
Base64_Add(jcl->password, strlen(jcl->password));
|
|
Base64_Finish();
|
|
Q_snprintf(msg, sizeof(msg), "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>", base64);
|
|
JCL_AddClientMessageString(jcl, msg);
|
|
// JCL_AddClientMessageString(jcl, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>");
|
|
// JCL_AddClientMessageString(jcl, base64);
|
|
// JCL_AddClientMessageString(jcl, "</auth>");
|
|
unparsable = false;
|
|
break;
|
|
}
|
|
else
|
|
Con_Printf("Unable to use auth method \'%s\'\n", ot->body);
|
|
}
|
|
if (!ot)
|
|
{
|
|
Con_Printf("JCL: No suitable auth methods\n");
|
|
unparsable = true;
|
|
}
|
|
}
|
|
else //we cannot auth, no suitable method.
|
|
{
|
|
Con_Printf("JCL: Neither SASL or TLS are usable\n");
|
|
unparsable = true;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(tree->name, "proceed"))
|
|
{
|
|
//switch to TLS, if we can
|
|
|
|
//Restart everything, basically.
|
|
jcl->bufferedinammount = 0;
|
|
jcl->instreampos = 0;
|
|
jcl->tagdepth = 0;
|
|
|
|
if (!BUILTINISVALID(Net_SetTLSClient))
|
|
{
|
|
Con_Printf("JCL: proceed without TLS\n");
|
|
XML_Destroy(tree);
|
|
return JCL_KILL;
|
|
}
|
|
|
|
if (pNet_SetTLSClient(jcl->socket, jcl->domain)<0)
|
|
{
|
|
Con_Printf("JCL: failed to switch to TLS\n");
|
|
XML_Destroy(tree);
|
|
return JCL_KILL;
|
|
}
|
|
jclient->issecure = true;
|
|
|
|
JCL_AddClientMessageString(jcl,
|
|
"<?xml version='1.0' ?>"
|
|
"<stream:stream to='");
|
|
JCL_AddClientMessageString(jcl, jcl->domain);
|
|
JCL_AddClientMessageString(jcl, "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
|
|
|
|
XML_Destroy(tree);
|
|
return JCL_DONE;
|
|
}
|
|
else if (!strcmp(tree->name, "stream:error"))
|
|
{
|
|
}
|
|
else if (!strcmp(tree->name, "failure"))
|
|
{
|
|
if (tree->child)
|
|
Con_Printf("JCL: Failure: %s\n", tree->child->name);
|
|
else
|
|
Con_Printf("JCL: Unknown failure\n");
|
|
XML_Destroy(tree);
|
|
return JCL_KILL;
|
|
}
|
|
else if (!strcmp(tree->name, "success"))
|
|
{
|
|
//Restart everything, basically, AGAIN! (third time lucky?)
|
|
jcl->bufferedinammount = 0;
|
|
jcl->instreampos = 0;
|
|
jcl->tagdepth = 0;
|
|
|
|
JCL_AddClientMessageString(jcl,
|
|
"<?xml version='1.0' ?>"
|
|
"<stream:stream to='");
|
|
JCL_AddClientMessageString(jcl, jcl->domain);
|
|
JCL_AddClientMessageString(jcl, "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
|
|
|
|
return JCL_DONE;
|
|
}
|
|
else if (!strcmp(tree->name, "iq"))
|
|
{
|
|
char *from;
|
|
// char *to;
|
|
char *id;
|
|
|
|
id = XML_GetParameter(tree, "id", "");
|
|
from = XML_GetParameter(tree, "from", "");
|
|
// to = XML_GetParameter(tree, "to", "");
|
|
|
|
f = XML_GetParameter(tree, "type", "");
|
|
if (!strcmp(f, "get"))
|
|
{
|
|
ot = XML_ChildOfTree(tree, "query", 0);
|
|
if (ot)
|
|
{
|
|
if (from && !strcmp(ot->xmlns, "http://jabber.org/protocol/disco#info"))
|
|
{ //http://xmpp.org/extensions/xep-0030.html
|
|
char msg[2048];
|
|
char *hash;
|
|
unparsable = false;
|
|
|
|
buildcaps(msg, sizeof(msg));
|
|
hash = buildcapshash();
|
|
|
|
JCL_AddClientMessagef(jcl,
|
|
"<iq type='result' to='%s' id='%s'>"
|
|
"<query xmlns='http://jabber.org/protocol/disco#info' node='http://fteqw.com/ftexmppplug#%s'>"
|
|
"%s"
|
|
"</query>"
|
|
"</iq>", from, id, hash, msg);
|
|
}
|
|
else if (from && !strcmp(ot->xmlns, "jabber:iq:version"))
|
|
{ //client->client version request
|
|
char msg[2048];
|
|
unparsable = false;
|
|
|
|
Q_snprintf(msg, sizeof(msg),
|
|
"<iq type='result' to='%s' id='%s'>"
|
|
"<query xmlns='jabber:iq:version'>"
|
|
"<name>FTEQW XMPP</name>"
|
|
"<version>V"JCL_BUILD"</version>"
|
|
#ifdef Q3_VM
|
|
"<os>QVM plugin</os>"
|
|
#else
|
|
//don't specify the os otherwise, as it gives away required base addresses etc for exploits
|
|
#endif
|
|
"</query>"
|
|
"</iq>", from, id);
|
|
|
|
JCL_AddClientMessageString(jcl, msg);
|
|
}
|
|
/* else if (from && !strcmp(ot->xmlns, "jabber:iq:last"))
|
|
{ //http://xmpp.org/extensions/xep-0012.html
|
|
char msg[2048];
|
|
int idletime = 0;
|
|
unparsable = false;
|
|
|
|
//last activity
|
|
Q_snprintf(msg, sizeof(msg),
|
|
"<iq type='result' to='%s' id='%s'>"
|
|
"<query xmlns='jabber:iq:last' seconds='%i'/>"
|
|
"</iq>", from, id, idletime);
|
|
|
|
JCL_AddClientMessageString(jcl, msg);
|
|
}
|
|
*/
|
|
}
|
|
#ifndef Q3_VM
|
|
ot = XML_ChildOfTree(tree, "time", 0);
|
|
if (ot && !strcmp(ot->xmlns, "urn:xmpp:time"))
|
|
{ //http://xmpp.org/extensions/xep-0202.html
|
|
char msg[2048];
|
|
char tz[256];
|
|
char timestamp[256];
|
|
struct tm * timeinfo;
|
|
int tzh, tzm;
|
|
time_t rawtime;
|
|
time (&rawtime);
|
|
timeinfo = localtime(&rawtime);
|
|
tzh = timeinfo->tm_hour;
|
|
tzm = timeinfo->tm_min;
|
|
timeinfo = gmtime (&rawtime);
|
|
tzh -= timeinfo->tm_hour;
|
|
tzm -= timeinfo->tm_min;
|
|
Q_snprintf(tz, sizeof(tz), "%+i:%i", tzh, tzm);
|
|
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", timeinfo);
|
|
unparsable = false;
|
|
//strftime
|
|
Q_snprintf(msg, sizeof(msg),
|
|
"<iq type='result' to='%s' id='%s'>"
|
|
"<time xmlns='urn:xmpp:time'>"
|
|
"<tzo>%s</tzo>"
|
|
"<utc>%s</utc>"
|
|
"</time>"
|
|
"</iq>", from, id, tz, timestamp);
|
|
JCL_AddClientMessageString(jcl, msg);
|
|
}
|
|
#endif
|
|
|
|
ot = XML_ChildOfTree(tree, "ping", 0);
|
|
if (ot && !strcmp(ot->xmlns, "urn:xmpp:ping"))
|
|
{
|
|
JCL_AddClientMessagef(jcl, "<iq type='result' to='%s' id='%s' />", from, id);
|
|
}
|
|
|
|
if (unparsable)
|
|
{ //unsupported stuff
|
|
char msg[2048];
|
|
unparsable = false;
|
|
|
|
Con_Printf("Unsupported iq get\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
|
|
//tell them OH NOES, instead of requiring some timeout.
|
|
Q_snprintf(msg, sizeof(msg),
|
|
"<iq type='error' to='%s' id='%s'>"
|
|
"<error type='cancel'>"
|
|
"<service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
|
|
"</error>"
|
|
"</iq>", from, id);
|
|
JCL_AddClientMessageString(jcl, msg);
|
|
}
|
|
}
|
|
else if (!strcmp(f, "set"))
|
|
{
|
|
xmltree_t *c;
|
|
|
|
c = XML_ChildOfTree(tree, "query", 0);
|
|
if (c && !strcmp(c->xmlns, "jabber:iq:roster"))
|
|
{
|
|
unparsable = false;
|
|
JCL_RosterUpdate(jcl, c);
|
|
}
|
|
|
|
c = XML_ChildOfTree(tree, "jingle", 0);
|
|
if (c && !strcmp(c->xmlns, "urn:xmpp:jingle:1"))
|
|
{
|
|
JCL_ParseJingle(jcl, c, from, id);
|
|
unparsable = false;
|
|
}
|
|
}
|
|
else if (!strcmp(f, "result") || !strcmp(f, "error"))
|
|
{
|
|
char *id = XML_GetParameter(tree, "id", "");
|
|
struct iq_s **link, *iq;
|
|
unparsable = false;
|
|
for (link = &jcl->pendingiqs; *link; link = &(*link)->next)
|
|
{
|
|
iq = *link;
|
|
if (!strcmp(iq->id, id))
|
|
break;
|
|
}
|
|
if (*link)
|
|
{
|
|
iq = *link;
|
|
*link = iq->next;
|
|
|
|
if (iq->callback)
|
|
{
|
|
if (!iq->callback(jcl, !strcmp(f, "error")?NULL:tree))
|
|
{
|
|
Con_Printf("Invalid iq result\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
}
|
|
free(iq);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Unrecognised iq result\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
}
|
|
|
|
if (unparsable)
|
|
{
|
|
unparsable = false;
|
|
Con_Printf("Unrecognised iq type\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
}
|
|
else if (!strcmp(tree->name, "message"))
|
|
{
|
|
f = XML_GetParameter(tree, "from", NULL);
|
|
|
|
if (f && !strcmp(f, jcl->jid))
|
|
unparsable = false;
|
|
else
|
|
{
|
|
if (f)
|
|
{
|
|
buddy_t *b;
|
|
bresource_t *br;
|
|
Q_strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest));
|
|
|
|
JCL_FindBuddy(jcl, f, &b, &br);
|
|
f = b->name;
|
|
b->defaultresource = br;
|
|
}
|
|
|
|
if (f)
|
|
{
|
|
ot = XML_ChildOfTree(tree, "composing", 0);
|
|
if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates"))
|
|
{
|
|
unparsable = false;
|
|
Con_SubPrintf(f, "%s is typing\r", f);
|
|
}
|
|
ot = XML_ChildOfTree(tree, "paused", 0);
|
|
if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates"))
|
|
{
|
|
unparsable = false;
|
|
Con_SubPrintf(f, "%s has stopped typing\r", f);
|
|
}
|
|
ot = XML_ChildOfTree(tree, "inactive", 0);
|
|
if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates"))
|
|
{
|
|
unparsable = false;
|
|
Con_SubPrintf(f, "\r", f);
|
|
}
|
|
ot = XML_ChildOfTree(tree, "active", 0);
|
|
if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates"))
|
|
{
|
|
unparsable = false;
|
|
Con_SubPrintf(f, "\r", f);
|
|
}
|
|
ot = XML_ChildOfTree(tree, "gone", 0);
|
|
if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates"))
|
|
{
|
|
unparsable = false;
|
|
Con_SubPrintf(f, "%s has gone away\r", f);
|
|
}
|
|
}
|
|
|
|
ot = XML_ChildOfTree(tree, "body", 0);
|
|
if (ot)
|
|
{
|
|
unparsable = false;
|
|
if (f)
|
|
Con_SubPrintf(f, "%s: %s\n", f, ot->body);
|
|
else
|
|
Con_Printf(ot->body);
|
|
|
|
if (BUILTINISVALID(LocalSound))
|
|
pLocalSound("misc/talk.wav");
|
|
}
|
|
|
|
if (unparsable)
|
|
{
|
|
unparsable = false;
|
|
if (jcl->streamdebug)
|
|
{
|
|
Con_Printf("Received a message without a body\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(tree->name, "presence"))
|
|
{
|
|
buddy_t *buddy;
|
|
bresource_t *bres;
|
|
|
|
char *from = XML_GetParameter(tree, "from", "");
|
|
xmltree_t *show = XML_ChildOfTree(tree, "show", 0);
|
|
xmltree_t *status = XML_ChildOfTree(tree, "status", 0);
|
|
xmltree_t *quake = XML_ChildOfTree(tree, "quake", 0);
|
|
char *type = XML_GetParameter(tree, "type", "");
|
|
char *serverip = NULL;
|
|
char *servermap = NULL;
|
|
|
|
if (quake && !strcmp(quake->xmlns, "fteqw.com:game"))
|
|
{
|
|
serverip = XML_GetParameter(quake, "serverip", NULL);
|
|
servermap = XML_GetParameter(quake, "servermap", NULL);
|
|
}
|
|
|
|
if (type && !strcmp(type, "subscribe"))
|
|
{
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] wants to be your friend! ^[[Authorize]\\xmppauth\\%s^] ^[[Deny]\\xmppdeny\\%s^]\n", from, from, from, from);
|
|
}
|
|
else if (type && !strcmp(type, "unsubscribe"))
|
|
{
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] has unfriended you\n", from, from);
|
|
}
|
|
else if (type && !strcmp(type, "unsubscribed"))
|
|
{
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] is no longer unfriended you\n", from, from);
|
|
}
|
|
else
|
|
{
|
|
JCL_FindBuddy(jcl, from, &buddy, &bres);
|
|
|
|
if (bres)
|
|
{
|
|
if (servermap)
|
|
{
|
|
bres->servertype = 2;
|
|
Q_strlcpy(bres->server, servermap, sizeof(bres->server));
|
|
}
|
|
else if (serverip)
|
|
{
|
|
bres->servertype = 1;
|
|
Q_strlcpy(bres->server, serverip, sizeof(bres->server));
|
|
}
|
|
else
|
|
{
|
|
bres->servertype = 0;
|
|
Q_strlcpy(bres->server, "", sizeof(bres->server));
|
|
}
|
|
Q_strlcpy(bres->fstatus, (status && *status->body)?status->body:"", sizeof(bres->fstatus));
|
|
if (!tree->child)
|
|
Q_strlcpy(bres->bstatus, "offline", sizeof(bres->bstatus));
|
|
else
|
|
Q_strlcpy(bres->bstatus, (show && *show->body)?show->body:"present", sizeof(bres->bstatus));
|
|
|
|
if (bres->servertype == 2)
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\xmppjoin\\%s^]\n", buddy->name, from, bres->server, from);
|
|
else if (bres->servertype == 1)
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\observe\\%s^]\n", buddy->name, from, bres->server, bres->server);
|
|
else if (*bres->fstatus)
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] is now %s: %s\n", buddy->name, from, bres->bstatus, bres->fstatus);
|
|
else
|
|
Con_Printf("^[[%s]\\xmpp\\%s^] is now %s\n", buddy->name, from, bres->bstatus);
|
|
|
|
if (!tree->child)
|
|
{
|
|
//remove this buddy resource
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Weird presence:\n");
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
}
|
|
|
|
//we should keep a list of the people that we know of.
|
|
unparsable = false;
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("JCL unrecognised stanza: %s\n", tree->name);
|
|
XML_ConPrintTree(tree, 0);
|
|
}
|
|
|
|
XML_Destroy(tree);
|
|
|
|
|
|
memmove(jcl->bufferedinmessage, jcl->bufferedinmessage+pos, jcl->bufferedinammount-pos);
|
|
jcl->bufferedinammount -= pos;
|
|
jcl->instreampos -= pos;
|
|
|
|
if (unparsable)
|
|
{
|
|
Con_Printf("JCL: Input corrupt, urecognised, or unusable. Disconnecting.");
|
|
return JCL_KILL;
|
|
}
|
|
|
|
return JCL_CONTINUE;
|
|
}
|
|
|
|
void JCL_CloseConnection(jclient_t *jcl)
|
|
{
|
|
Con_Printf("JCL: Disconnected from %s@%s\n", jcl->username, jcl->domain);
|
|
JCL_AddClientMessageString(jcl, "</stream:stream>");
|
|
pNet_Close(jcl->socket);
|
|
free(jcl);
|
|
}
|
|
|
|
//can be polled for server address updates
|
|
void JCL_GeneratePresence(qboolean force)
|
|
{
|
|
int dummystat;
|
|
char serveraddr[1024*16];
|
|
char servermap[1024*16];
|
|
//get the last server address
|
|
|
|
serveraddr[0] = 0;
|
|
servermap[0] = 0;
|
|
|
|
if (!pCvar_GetFloat("xmpp_nostatus"))
|
|
{
|
|
if (pCvar_GetFloat("sv.state"))
|
|
{
|
|
pCvar_GetString("sv.mapname", servermap, sizeof(servermap));
|
|
}
|
|
else
|
|
{
|
|
if (!pCvar_GetString("cl_serveraddress", serveraddr, sizeof(serveraddr)))
|
|
serveraddr[0] = 0;
|
|
if (BUILTINISVALID(CL_GetStats))
|
|
{
|
|
//if we can't get any stats, its because we're not actually on the server.
|
|
if (!pCL_GetStats(0, &dummystat, 1))
|
|
serveraddr[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (force || strcmp(jclient->curquakeserver, *servermap?servermap:serveraddr))
|
|
{
|
|
char caps[256];
|
|
Q_strlcpy(jclient->curquakeserver, *servermap?servermap:serveraddr, sizeof(jclient->curquakeserver));
|
|
|
|
//note: ext='voice-v1 camera-v1 video-v1' is some legacy nonsense, and is required for voice calls with googletalk clients or something stupid like that
|
|
Q_snprintf(caps, sizeof(caps), "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://fteqw.com/ftexmppplugin' ver='%s'/>", buildcapshash());
|
|
|
|
if (!*jclient->curquakeserver)
|
|
JCL_AddClientMessagef(jclient,
|
|
"<presence>"
|
|
"%s"
|
|
"</presence>", caps);
|
|
else if (*servermap) //if we're running a server, say so
|
|
JCL_AddClientMessagef(jclient,
|
|
"<presence>"
|
|
"<quake xmlns='fteqw.com:game' servermap='%s'/>"
|
|
"%s"
|
|
"</presence>"
|
|
, servermap, caps);
|
|
else //if we're connected to a server, say so
|
|
JCL_AddClientMessagef(jclient,
|
|
"<presence>"
|
|
"<quake xmlns='fteqw.com:game' serverip='%s' />"
|
|
"%s"
|
|
"</presence>"
|
|
,jclient->curquakeserver, caps);
|
|
}
|
|
}
|
|
|
|
//functions above this line allow connections to multiple servers.
|
|
//it is just the control functions that only allow one server.
|
|
|
|
qintptr_t JCL_Frame(qintptr_t *args)
|
|
{
|
|
int stat = JCL_CONTINUE;
|
|
if (jclient)
|
|
{
|
|
if (jclient->connected)
|
|
{
|
|
JCL_GeneratePresence(false);
|
|
}
|
|
|
|
while(stat == JCL_CONTINUE)
|
|
stat = JCL_ClientFrame(jclient);
|
|
if (stat == JCL_KILL)
|
|
{
|
|
JCL_CloseConnection(jclient);
|
|
|
|
jclient = NULL;
|
|
}
|
|
|
|
JCL_FlushOutgoing(jclient);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void JCL_WriteConfig(void)
|
|
{
|
|
if (jclient->connected)
|
|
{
|
|
qhandle_t config;
|
|
pFS_Open("**plugconfig", &config, 2);
|
|
if (config >= 0)
|
|
{
|
|
char buffer[8192];
|
|
Q_snprintf(buffer, sizeof(buffer), "%i \"%s\" \"%s@%s\" \"%s\"\n",
|
|
jclient->tlsconnect, jclient->server, jclient->username, jclient->domain, jclient->password);
|
|
pFS_Write(config, buffer, strlen(buffer));
|
|
pFS_Close(config);
|
|
}
|
|
}
|
|
}
|
|
void JCL_LoadConfig(void)
|
|
{
|
|
if (!jclient)
|
|
{
|
|
int len;
|
|
qhandle_t config;
|
|
char buf[8192];
|
|
char tls[256];
|
|
char server[256];
|
|
char account[256];
|
|
char password[256];
|
|
char *line = buf;
|
|
qboolean oldtls;
|
|
len = pFS_Open("**plugconfig", &config, 1);
|
|
if (config >= 0)
|
|
{
|
|
if (len >= sizeof(buf))
|
|
len = sizeof(buf)-1;
|
|
buf[len] = 0;
|
|
pFS_Read(config, buf, len);
|
|
pFS_Close(config);
|
|
|
|
line = JCL_ParseOut(line, tls, sizeof(tls));
|
|
line = JCL_ParseOut(line, server, sizeof(server));
|
|
line = JCL_ParseOut(line, account, sizeof(account));
|
|
line = JCL_ParseOut(line, password, sizeof(password));
|
|
|
|
oldtls = atoi(tls);
|
|
|
|
jclient = JCL_Connect(server, oldtls?5223:5222, oldtls, account, password);
|
|
}
|
|
}
|
|
}
|
|
void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all)
|
|
{
|
|
buddy_t *b;
|
|
bresource_t *r;
|
|
if (!jcl->buddies)
|
|
Con_SubPrintf(console, "You have no friends\n");
|
|
for (b = jcl->buddies; b; b = b->next)
|
|
{
|
|
//if we don't actually know them, don't list them.
|
|
if (!b->friended)
|
|
continue;
|
|
|
|
if (!b->resources) //offline
|
|
{
|
|
if (all)
|
|
Con_SubPrintf(console, "^[^7[%s]\\xmpp\\%s^]: offline\n", b->name, b->accountdomain);
|
|
}
|
|
else if (b->resources->next)
|
|
{ //multiple potential resources
|
|
Con_SubPrintf(console, "^[[%s]\\xmpp\\%s^]\n", b->name, b->accountdomain);
|
|
for (r = b->resources; r; r = r->next)
|
|
{
|
|
if (r->servertype == 2)
|
|
Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\xmppjoin\\%s/%s^]\n", r->resource, b->accountdomain, r->resource, r->server, b->accountdomain, r->resource);
|
|
else if (r->servertype)
|
|
Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\observe\\%s^]\n", r->resource, b->accountdomain, r->resource, r->server, r->server);
|
|
else if (*r->fstatus)
|
|
Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: %s - %s\n", r->resource, b->accountdomain, r->resource, r->bstatus, r->fstatus);
|
|
else
|
|
Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: %s\n", r->resource, b->accountdomain, r->resource, r->bstatus);
|
|
}
|
|
}
|
|
else //only one resource
|
|
{
|
|
r = b->resources;
|
|
if (!strcmp(r->server, "-"))
|
|
Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake]\\xmppjoin\\%s/%s^]\n", b->name, b->accountdomain, r->resource, b->accountdomain, r->resource);
|
|
else if (*r->server)
|
|
Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\observe\\%s^]\n", b->name, b->accountdomain, r->resource, r->server, r->server);
|
|
else if (*r->fstatus)
|
|
Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: %s - %s\n", b->name, b->accountdomain, r->resource, r->bstatus, r->fstatus);
|
|
else
|
|
Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: %s\n", b->name, b->accountdomain, r->resource, r->bstatus);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JCL_SendMessage(jclient_t *jcl, char *to, char *msg)
|
|
{
|
|
char markup[256];
|
|
buddy_t *b;
|
|
bresource_t *br;
|
|
JCL_FindBuddy(jcl, to, &b, &br);
|
|
if (br)
|
|
JCL_AddClientMessagef(jcl, "<message to='%s/%s'><body>", b->accountdomain, br->resource);
|
|
else
|
|
JCL_AddClientMessagef(jcl, "<message to='%s'><body>", b->accountdomain);
|
|
JCL_AddClientMessage(jcl, markup, XML_Markup(msg, markup, sizeof(markup)) - markup);
|
|
JCL_AddClientMessageString(jcl, "</body></message>");
|
|
Con_SubPrintf(b->name, "%s: "COLOURYELLOW"%s\n", ">>", msg);
|
|
}
|
|
|
|
|
|
void JCL_Command(char *console)
|
|
{
|
|
char imsg[8192];
|
|
char arg[6][256];
|
|
char *msg;
|
|
int i;
|
|
|
|
pCmd_Args(imsg, sizeof(imsg));
|
|
|
|
msg = imsg;
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
if (!msg)
|
|
continue;
|
|
msg = JCL_ParseOut(msg, arg[i], sizeof(arg[i]));
|
|
}
|
|
|
|
if (*arg[0] == '/')
|
|
{
|
|
if (!strcmp(arg[0]+1, "tlsopen") || !strcmp(arg[0]+1, "tlsconnect"))
|
|
{ //tlsconnect is 'old'.
|
|
if (!*arg[1])
|
|
{
|
|
Con_TrySubPrint(console, "tlsopen [server] [account] [password]\n");
|
|
return;
|
|
}
|
|
|
|
if (jclient)
|
|
{
|
|
Con_TrySubPrint(console, "You are already connected\nPlease /quit first\n");
|
|
return;
|
|
}
|
|
if (!*arg[1])
|
|
{
|
|
Con_SubPrintf(console, "%s <server[:port]> <account[@domain]> <password>\n", arg[0]+1);
|
|
return;
|
|
}
|
|
jclient = JCL_Connect(arg[1], 5223, true, arg[2], arg[3]);
|
|
if (!jclient)
|
|
{
|
|
Con_TrySubPrint(console, "Connect failed\n");
|
|
return;
|
|
}
|
|
}
|
|
else if (!strcmp(arg[0]+1, "open") || !strcmp(arg[0]+1, "connect"))
|
|
{
|
|
if (!*arg[1])
|
|
{
|
|
Con_TrySubPrint(console, "open [server] [account] [password]\n");
|
|
return;
|
|
}
|
|
|
|
if (jclient)
|
|
{
|
|
Con_TrySubPrint(console, "You are already connected\nPlease /quit first\n");
|
|
return;
|
|
}
|
|
if (!*arg[1])
|
|
{
|
|
Con_SubPrintf(console, "%s <server[:port]> <account[@domain]> <password>\n", arg[0]+1);
|
|
return;
|
|
}
|
|
jclient = JCL_Connect(arg[1], 5222, false, arg[2], arg[3]);
|
|
if (!jclient)
|
|
{
|
|
Con_TrySubPrint(console, "Connect failed\n");
|
|
return;
|
|
}
|
|
}
|
|
else if (!strcmp(arg[0]+1, "help"))
|
|
{
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /tlsconnect XMPPSERVER USERNAME@DOMAIN PASSWORD^]\n");
|
|
if (BUILTINISVALID(Net_SetTLSClient))
|
|
Con_Printf("for example: ^[/" COMMANDPREFIX " /tlsconnect talk.google.com myusername@gmail.com mypassword^]\n"
|
|
"Note that this info will be used the next time you start quake.\n");
|
|
|
|
//small note:
|
|
//for the account 'me@example.com' the server to connect to can be displayed with:
|
|
//nslookup -querytype=SRV _xmpp-client._tcp.example.com
|
|
//srv resolving seems to be non-standard on each system, I don't like having to special case things.
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /help^]\n"
|
|
"This text...\n");
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /raw <XML STANZAS/>^]\n"
|
|
"For debug hackery.\n");
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /friend accountname friendlyname^]\n"
|
|
"Befriends accountname, and shows them in your various lists using the friendly name. Can also be used to rename friends.\n");
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /unfriend accountname^]\n"
|
|
"Ostracise your new best enemy. You will no longer see them and they won't be able to contact you.\n");
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /blist^]\n"
|
|
"Show all your friends! Names are clickable and will begin conversations.\n");
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /quit^]\n"
|
|
"Disconnect from the XMPP server, noone will be able to hear you scream.\n");
|
|
Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /msg ACCOUNTNAME your message goes here^]\n"
|
|
"Sends a message to the named person. If given a resource postfix, your message will be sent only to that resource.\n");
|
|
Con_TrySubPrint(console, "If no arguments, will print out your friends list. If no /command is used, the arguments will be sent as a message to the person you last sent a message to.\n");
|
|
}
|
|
else if (!jclient)
|
|
{
|
|
Con_SubPrintf(console, "You are not connected. Cannot %s\n", arg[0]);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "quit"))
|
|
{
|
|
//disconnect from the xmpp server.
|
|
JCL_CloseConnection(jclient);
|
|
jclient = NULL;
|
|
}
|
|
else if (!strcmp(arg[0]+1, "blist"))
|
|
{
|
|
//print out a full list of everyone, even those offline.
|
|
JCL_PrintBuddyList(console, jclient, true);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "clear"))
|
|
{
|
|
//just clears the current console.
|
|
if (*console)
|
|
{
|
|
pCon_Destroy(console);
|
|
Con_SubPrintf(console, "");
|
|
pCon_SetActive(console);
|
|
}
|
|
else
|
|
pCmd_AddText("\nclear\n", true);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "msg"))
|
|
{
|
|
//FIXME: validate the dest. deal with xml markup in dest.
|
|
Q_strlcpy(jclient->defaultdest, arg[1], sizeof(jclient->defaultdest));
|
|
msg = arg[2];
|
|
|
|
JCL_SendMessage(jclient, jclient->defaultdest, msg);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "friend"))
|
|
{
|
|
//FIXME: validate the name. deal with xml markup.
|
|
|
|
//can also rename. We should probably read back the groups for the update.
|
|
JCL_SendIQf(jclient, NULL, "set", NULL, "<query xmlns='jabber:iq:roster'><item jid='%s' name='%s'></item></query>", arg[1], arg[2]);
|
|
|
|
//start looking for em
|
|
JCL_AddClientMessagef(jclient, "<presence to='%s' type='subscribe'/>", arg[1]);
|
|
|
|
//let em see us
|
|
if (jclient->preapproval)
|
|
JCL_AddClientMessagef(jclient, "<presence to='%s' type='subscribed'/>", arg[1]);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "unfriend"))
|
|
{
|
|
//FIXME: validate the name. deal with xml markup.
|
|
|
|
//hide from em
|
|
JCL_AddClientMessagef(jclient, "<presence to='%s' type='unsubscribed'/>", arg[1]);
|
|
|
|
//stop looking for em
|
|
JCL_AddClientMessagef(jclient, "<presence to='%s' type='unsubscribe'/>", arg[1]);
|
|
|
|
//stop listing em
|
|
JCL_SendIQf(jclient, NULL, "set", NULL, "<query xmlns='jabber:iq:roster'><item jid='%s' subscription='remove' /></query>", arg[1]);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "join"))
|
|
{
|
|
JCL_Join(jclient, arg[1]);
|
|
}
|
|
else if (!strcmp(arg[0]+1, "raw"))
|
|
{
|
|
jclient->streamdebug = true;
|
|
JCL_AddClientMessageString(jclient, arg[1]);
|
|
}
|
|
else
|
|
Con_SubPrintf(console, "Unrecognised command: %s\n", arg[0]);
|
|
}
|
|
else
|
|
{
|
|
if (jclient)
|
|
{
|
|
msg = imsg;
|
|
|
|
if (!*msg)
|
|
{
|
|
if (!*console)
|
|
{
|
|
JCL_PrintBuddyList(console, jclient, false);
|
|
Con_TrySubPrint(console, "For help, type \"^[/" COMMANDPREFIX " /help^]\"\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
JCL_SendMessage(jclient, jclient->defaultdest, msg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_TrySubPrint(console, "Not connected. For help, type \"^[/" COMMANDPREFIX " /help^]\"\n");
|
|
}
|
|
}
|
|
}
|