fteqw/plugins/jabber/jabberclient.c
2007-09-17 20:55:15 +00:00

1167 lines
24 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"
#define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0)
#define JCL_BUILD "1"
#define ARGNAMES ,sock
BUILTINR(int, Net_SetTLSClient, (qhandle_t sock));
#undef ARGNAMES
void Con_SubPrintf(char *subname, char *format, ...)
{
va_list argptr;
static char string[1024];
va_start (argptr, format);
vsnprintf (string, sizeof(string), format,argptr);
va_end (argptr);
Con_SubPrint(subname, string);
}
//porting zone:
#define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0)
#define COLOURGREEN "^2"
#define COLORWHITE "^7"
#define COLOURWHITE "^7" // word
#define COLOURRED "^1"
#define COLOURYELLOW "^3"
#define COLOURPURPLE "^5"
#define COMMANDPREFIX "jabbercl"
#define playsound(s)
#define TL_NETGETPACKETERROR "NET_GetPacket Error %s\n"
#define TOKENSIZE 1024
char com_token[TOKENSIZE];
char *COM_Parse (char *data) //this is taken out of quake
{
int c;
int len;
len = 0;
com_token[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ( (c = *data) <= ' ')
{
if (c == 0)
return NULL; // end of file;
data++;
}
// skip // comments
if (c=='/')
{
if (data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
}
// handle quoted strings specially
if (c == '\"')
{
data++;
while (1)
{
if (len >= TOKENSIZE-1)
return data;
c = *data++;
if (c=='\"' || !c)
{
com_token[len] = 0;
return data;
}
com_token[len] = c;
len++;
}
}
// parse a regular word
do
{
if (len >= TOKENSIZE-1)
return data;
com_token[len] = c;
data++;
len++;
c = *data;
} while (c>32);
com_token[len] = 0;
return data;
}
void JCL_Command(void);
int JCL_ExecuteCommand(int *args)
{
char cmd[256];
Cmd_Argv(0, cmd, sizeof(cmd));
if (!strcmp(cmd, COMMANDPREFIX))
{
JCL_Command();
return true;
}
return false;
}
int JCL_ConExecuteCommand(int *args);
int JCL_Frame(int *args);
int (*Con_TrySubPrint)(char *conname, char *message);
int Plug_Init(int *args)
{
if ( Plug_Export("Tick", JCL_Frame) &&
Plug_Export("ExecuteCommand", JCL_ExecuteCommand))
{
CHECKBUILTIN(Net_SetTLSClient);
if (!BUILTINISVALID(Net_SetTLSClient))
Con_Print("Jabber Client Plugin Loaded ^1without^7 TLS\n");
else
Con_Print("Jabber Client Plugin Loaded with TLS\n");
if (!Plug_Export("ConExecuteCommand", JCL_ConExecuteCommand))
{
Con_Printf("Jabber client plugin in single-console mode\n");
Con_TrySubPrint = Con_Print;
}
else
Con_TrySubPrint = Con_SubPrint;
Cmd_AddCommand(COMMANDPREFIX);
return 1;
}
else
Con_Print("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 2048
typedef struct {
char server[64];
int port;
qhandle_t socket;
qhandle_t inlog;
qhandle_t outlog;
char bufferedinmessage[JCL_MAXMSGLEN+1]; //there is a max size for protocol. (conveinient eh?) (and it's text format)
int bufferedinammount;
char defaultdest[256];
char domain[256];
char username[256];
char password[256];
char resource[256];
int tagdepth;
int openbracket;
int instreampos;
qboolean noplain;
qboolean issecure;
} jclient_t;
jclient_t *jclient;
int JCL_ConExecuteCommand(int *args)
{
if (!jclient)
{
char buffer[256];
Cmd_Argv(0, buffer, sizeof(buffer));
Con_SubPrint(buffer, "You were disconnected\n");
return true;
}
Cmd_Argv(0, jclient->defaultdest, sizeof(jclient->defaultdest));
JCL_Command();
return true;
}
void JCL_AddClientMessage(jclient_t *jcl, char *msg, int datalen)
{
Net_Send(jcl->socket, msg, datalen); //FIXME: This needs rewriting to cope with errors.
Con_Printf(COLOURYELLOW "<< %s \n",msg);
}
void JCL_AddClientMessageString(jclient_t *jcl, char *msg)
{
JCL_AddClientMessage(jcl, msg, strlen(msg));
}
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 = Net_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 (Net_SetTLSClient(jclient->socket)<0)
{
Net_Close(jclient->socket);
free(jclient);
jclient = NULL;
return NULL;
}
jcl->issecure = true;
}
else
jcl->issecure = false;
// gethostname(jcl->hostname, sizeof(jcl->hostname));
// jcl->hostname[sizeof(jcl->hostname)-1] = 0;
jcl->noplain = true;
*at = '\0';
strlcpy(jcl->username, account, sizeof(jcl->username));
strlcpy(jcl->domain, at+1, sizeof(jcl->domain));
strlcpy(jcl->password, password, sizeof(jcl->password));
strcpy(jcl->resource, "Quake");
Con_Printf("Trying to connect\n");
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;
}
typedef struct xmlparams_s {
char name[64];
char val[256];
struct xmlparams_s *next;
} xmlparams_t;
typedef struct subtree_s {
char name[64];
char body[2048];
xmlparams_t *params;
struct subtree_s *child;
struct subtree_s *sibling;
} xmltree_t;
void XML_Destroy(xmltree_t *t);
xmltree_t *XML_Parse(char *buffer, int *startpos, int maxpos, qboolean headeronly)
{
xmlparams_t *p;
xmltree_t *child;
xmltree_t *ret;
int bodypos;
int pos;
char *tagend;
char *tagstart;
pos = *startpos;
while (buffer[pos] >= '\0' && buffer[pos] <= ' ')
{
if (pos >= maxpos)
break;
pos++;
}
//expect a <
if (buffer[pos] != '<')
{
Con_Printf("Missing open bracket\n");
return NULL; //should never happen
}
if (buffer[pos+1] == '/')
{
Con_Printf("Unexpected close tag.\n");
return NULL; //err, terminating a parent tag
}
tagend = strchr(buffer+pos, '>');
if (!tagend)
{
Con_Printf("Missing close bracket\n");
return NULL; //should never happen
}
*tagend = '\0';
tagend++;
//assume no nulls in the tag header.
tagstart = buffer+pos+1;
tagstart = COM_Parse(tagstart);
if (!tagstart)
{
Con_Printf("EOF on tag name\n");
return NULL;
}
pos = tagend - buffer;
ret = malloc(sizeof(xmltree_t));
memset(ret, 0, sizeof(*ret));
strlcpy(ret->name, com_token, sizeof(ret->name));
// FIXME:parse the parameters
while(*tagstart)
{
int nlen;
while(*tagstart <= ' ' && *tagstart)
tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer)
if (!*tagstart)
break;
p = malloc(sizeof(xmlparams_t));
nlen = 0;
while (nlen < sizeof(p->name)-2)
{
if(*tagstart <= ' ')
break;
if (*tagstart == '=')
break;
p->name[nlen++] = *tagstart++;
}
p->name[nlen++] = '\0';
while(*tagstart <= ' ' && *tagstart)
tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer)
if (*tagstart != '=')
continue;
tagstart++;
while(*tagstart <= ' ' && *tagstart)
tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer)
nlen = 0;
if (*tagstart == '\'')
{
tagstart++;
while (*tagstart && nlen < sizeof(p->name)-2)
{
if(*tagstart == '\'')
break;
p->val[nlen++] = *tagstart++;
}
tagstart++;
p->val[nlen++] = '\0';
}
else if (*tagstart == '\"')
{
tagstart++;
while (*tagstart && nlen < sizeof(p->name)-2)
{
if(*tagstart == '\"')
break;
p->val[nlen++] = *tagstart++;
}
tagstart++;
p->val[nlen++] = '\0';
}
else
{
while (*tagstart && nlen < sizeof(p->name)-2)
{
if(*tagstart <= ' ')
break;
p->val[nlen++] = *tagstart++;
}
p->val[nlen++] = '\0';
}
p->next = ret->params;
ret->params = p;
}
tagend[-1] = '>';
if (tagend[-2] == '/')
{ //no body
*startpos = pos;
return ret;
}
if (ret->name[0] == '?')
{
//no body either
if (tagend[-2] == '?')
{
*startpos = pos;
return ret;
}
}
if (headeronly)
{
*startpos = pos;
return ret;
}
//does it have a body, or is it child tags?
bodypos = 0;
while(1)
{
if (pos == maxpos)
{ //malformed
Con_Printf("tree is malfored\n");
XML_Destroy(ret);
return NULL;
}
if (buffer[pos] == '<')
{
if (buffer[pos+1] == '/')
{ //the end of this block
//FIXME: check name
tagend = strchr(buffer+pos, '>');
if (!tagend)
{
Con_Printf("No close tag\n");
XML_Destroy(ret);
return NULL; //should never happen
}
tagend++;
pos = tagend - buffer;
break;
}
child = XML_Parse(buffer, &pos, maxpos, false);
if (!child)
{
Con_Printf("Child block is unparsable\n");
XML_Destroy(ret);
return NULL;
}
child->sibling = ret->child;
ret->child = child;
}
else
ret->body[bodypos++] = buffer[pos++];
}
ret->body[bodypos++] = '\0';
*startpos = pos;
return ret;
}
char *XML_ParameterOfTree(xmltree_t *t, char *paramname)
{
xmlparams_t *p;
for (p = t->params; p; p = p->next)
if (!strcmp(p->name, paramname))
return p->val;
return NULL;
}
void XML_Destroy(xmltree_t *t)
{
xmlparams_t *p, *np;
if (t->child)
XML_Destroy(t->child);
if (t->sibling)
XML_Destroy(t->sibling);
for (p = t->params; p; p = np)
{
np = p->next;
free(p);
}
free(t);
}
xmltree_t *XML_ChildOfTree(xmltree_t *t, char *name, int childnum)
{
for (t = t->child; t; t = t->sibling)
{
if (!strcmp(t->name, name))
{
if (childnum-- == 0)
return t;
}
}
return NULL;
}
void XML_ConPrintTree(xmltree_t *t, int indent)
{
xmltree_t *c;
xmlparams_t *p;
int i;
for (i = 0; i < indent; i++) Con_Printf(" ");
Con_Printf("<%s", t->name);
for (p = t->params; p; p = p->next)
Con_Printf(" %s='%s'", p->name, p->val);
if (t->child)
{
Con_Printf(">\n");
for (c = t->child; c; c = c->sibling)
XML_ConPrintTree(c , indent+2);
for (i = 0; i < indent; i++) Con_Printf(" ");
Con_Printf("</%s>\n", t->name);
}
else if (*t->body)
Con_Printf(">%s</%s>\n", t->body, t->name);
else
Con_Printf("/>\n");
}
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;
int i;
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;
}
void RenameConsole(char *f)
{
//note that this function has a sideeffect
//if I send a message to blah@blah.com, and they reply, the reply comes from blah@blah.com/resource
//so, if we rename the old console before printing, we don't spawn random extra consoles.
char old[256];
char *slash;
strlcpy(old, f, sizeof(old));
slash = strchr(f, '/');
if (slash)
{
*slash = '\0';
Con_RenameSub(f, old);
}
}
#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 = Net_Recv(jcl->socket, jcl->bufferedinmessage+jcl->bufferedinammount, sizeof(jcl->bufferedinmessage)-1 - jcl->bufferedinammount);
if (ret == 0)
{
Con_Printf("JCL: Remote host disconnected\n");
return JCL_KILL;
}
if (ret < 0)
{
if (ret == N_WOULDBLOCK)
{
if (!jcl->bufferedinammount) //if we are half way through a message, read any possible conjunctions.
return JCL_DONE; //remove
}
else
{
Con_Printf("JCL: socket error\n");
return JCL_KILL;
}
}
if (ret>0)
jcl->bufferedinammount+=ret;
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 (!tree)
{
Con_Printf("Not an xml stream\n");
return JCL_KILL;
}
if (strcmp(tree->name, "stream:stream"))
{
Con_Printf("Not an xmpp stream\n");
return JCL_KILL;
}
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)
{
Con_Printf("End of XML stream\n");
return JCL_KILL;
}
return JCL_DONE;
}
pos = 0;
tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, false);
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, "stream:features"))
{
if ((ot=XML_ChildOfTree(tree, "bind", 0)))
{
unparsable = false;
JCL_AddClientMessageString(jcl, "<iq type='set' id='H_0'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>");
JCL_AddClientMessageString(jcl, jcl->resource);
JCL_AddClientMessageString(jcl, "</resource></bind></iq>");
}
if ((ot=XML_ChildOfTree(tree, "session", 0)))
{
unparsable = false;
JCL_AddClientMessageString(jcl, "<iq type='set' id='H_1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>");
JCL_AddClientMessageString(jcl, "<presence/>");
}
if (unparsable)
{
if ((!jclient->issecure) && BUILTINISVALID(Net_SetTLSClient) && XML_ChildOfTree(tree, "starttls", 0) != NULL)
{
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"))
{
if (jclient->noplain && !jclient->issecure) //probably don't send plain without tls.
{
//plain can still be read with man-in-the-middle attacks, of course, even with stl.
Con_Printf("Ignoring auth \'%s\'\n", ot->body);
continue;
}
Con_Printf("Authing with \'%s\'\n", ot->body);
JCL_AddClientMessageString(jcl, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>");
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();
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");
return JCL_KILL;
}
if (Net_SetTLSClient(jcl->socket)<0)
{
Con_Printf("JCL: failed to switch to TLS\n");
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'>");
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");
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;
unparsable = false;
id = XML_ParameterOfTree(tree, "id");
from = XML_ParameterOfTree(tree, "from");
to = XML_ParameterOfTree(tree, "to");
f = XML_ParameterOfTree(tree, "type");
if (f && !strcmp(f, "get"))
{
ot = XML_ChildOfTree(tree, "query", 0);
if (ot)
{
f = XML_ParameterOfTree(tree, "xmlns");
if (f && to && from && !strcmp(f, "jabber:iq:version"))
{ //client->client version request
JCL_AddClientMessageString(jcl, "<iq type='result' to='");
JCL_AddClientMessageString(jcl, from);
JCL_AddClientMessageString(jcl, "' from='");
JCL_AddClientMessageString(jcl, to);
JCL_AddClientMessageString(jcl, "' id='");
JCL_AddClientMessageString(jcl, id);
JCL_AddClientMessageString(jcl, "'>");
JCL_AddClientMessageString(jcl, "<query xmlns='jabber:iq:version'>"
"<name>FTEQW Jabber Plugin</name>"
"<version>"JCL_BUILD"</version>"
#ifdef Q3_VM
"<os>QVM plugin</os>"
#endif
"</query>");
JCL_AddClientMessageString(jcl, "</iq>");
}
}
}
else
Con_Print("Unrecognised iq type\n");
}
else if (!strcmp(tree->name, "message"))
{
unparsable = false;
ot = XML_ChildOfTree(tree, "body", 0);
if (ot)
{
f = XML_ParameterOfTree(tree, "from");
if (f)
{
strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest));
RenameConsole(f);
Con_SubPrintf(f, "%s: %s\n", f, ot->body);
}
else
Con_Print(ot->body);
LocalSound("misc/talk.wav");
}
else
Con_Print("Received a message without a body\n");
}
else if (!strcmp(tree->name, "presence"))
{
//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_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>");
Net_Close(jcl->socket);
free(jcl);
}
//functions above this line allow connections to multiple servers.
//it is just the control functions that only allow one server.
int JCL_Frame(int *args)
{
int stat = JCL_CONTINUE;
if (jclient)
{
while(stat == JCL_CONTINUE)
stat = JCL_ClientFrame(jclient);
if (stat == JCL_KILL)
{
JCL_CloseConnection(jclient);
jclient = NULL;
}
}
return 0;
}
void JCL_Command(void)
{
char imsg[8192];
char arg[6][256];
char *msg;
int i;
Cmd_Args(imsg, sizeof(imsg));
msg = imsg;
for (i = 0; i < 6; i++)
{
if (!msg)
continue;
msg = COM_Parse(msg);
strlcpy(arg[i], com_token, sizeof(arg[i]));
}
if (*arg[0] == '/')
{
if (!strcmp(arg[0]+1, "tlsopen") || !strcmp(arg[0]+1, "tlsconnect"))
{
if (jclient)
{
Con_Printf("You are already connected\nPlease /quit first\n");
return;
}
if (!*arg[1])
{
Con_Printf("%s <server[:port]> <account[@domain]> <password>\n", arg[0]+1);
return;
}
jclient = JCL_Connect(arg[1], 5223, true, arg[2], arg[3]);
}
else if (!strcmp(arg[0]+1, "open") || !strcmp(arg[0]+1, "connect"))
{
if (jclient)
{
Con_Printf("You are already connected\nPlease /quit first\n");
return;
}
if (!*arg[1])
{
Con_Printf("%s <server[:port]> <account[@domain]> <password>\n", arg[0]+1);
return;
}
jclient = JCL_Connect(arg[1], 5222, false, arg[2], arg[3]);
}
else if (!jclient)
{
Con_Printf("You are not connected. Cannot %s\n", arg[0]);
}
else if (!strcmp(arg[0]+1, "quit"))
{
JCL_CloseConnection(jclient);
jclient = NULL;
}
else if (!strcmp(arg[0]+1, "msg"))
{
strlcpy(jclient->defaultdest, arg[1], sizeof(jclient->defaultdest));
msg = arg[2];
JCL_AddClientMessageString(jclient, "<message to='");
JCL_AddClientMessageString(jclient, jclient->defaultdest);
JCL_AddClientMessageString(jclient, "'><body>");
JCL_AddClientMessageString(jclient, msg);
JCL_AddClientMessageString(jclient, "</body></message>");
Con_SubPrintf(jclient->defaultdest, "%s: "COLOURYELLOW"%s\n", ">>", msg);
}
else
Con_Printf("Unrecognised command: %s\n", arg[0]);
}
else
{
if (jclient)
{
msg = imsg;
JCL_AddClientMessageString(jclient, "<message to='");
JCL_AddClientMessageString(jclient, jclient->defaultdest);
JCL_AddClientMessageString(jclient, "'><body>");
JCL_AddClientMessageString(jclient, msg);
JCL_AddClientMessageString(jclient, "</body></message>");
Con_SubPrintf(jclient->defaultdest, "%s: "COLOURYELLOW"%s\n", ">>", msg);
}
else
{
Con_Printf("Not connected\ntype \"" COMMANDPREFIX " /connect JABBERSERVER USERNAME@DOMAIN PASSWORD\" to connect\n");
if (BUILTINISVALID(Net_SetTLSClient))
Con_Printf("eg: " COMMANDPREFIX " /tlsconnect talk.google.com myusername@gmail.com mypassword\n");
}
}
}