2005-12-11 20:11:22 +00:00
//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"
2013-03-31 04:21:08 +00:00
# include <time.h>
2013-06-23 02:33:52 +00:00
# include "../../engine/common/netinc.h"
# include "xml.h"
2013-06-29 16:01:07 +00:00
//#define NOICE
# define VOIP_SPEEX
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
# ifdef VOIP_SPEEX
# define VOIP
# endif
# define DEFAULTDOMAIN ""
# define DEFAULTRESOURCE "Quake"
2013-06-23 02:33:52 +00:00
# define QUAKEMEDIATYPE "quake"
# define QUAKEMEDIAXMLNS "fteqw.com:netmedia"
2013-06-29 16:01:07 +00:00
# define DEFAULTICEMODE ICEM_ICE
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
icefuncs_t * piceapi ;
2013-06-23 02:33:52 +00:00
2005-12-11 20:11:22 +00:00
# define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0)
2013-06-23 02:33:52 +00:00
# define JCL_BUILD "3"
2005-12-11 20:11:22 +00:00
2013-03-31 04:21:08 +00:00
# define ARGNAMES ,sock,certhostname
BUILTINR ( int , Net_SetTLSClient , ( qhandle_t sock , const char * certhostname ) ) ;
2005-12-11 20:11:22 +00:00
# undef ARGNAMES
2013-06-23 02:33:52 +00:00
# define ARGNAMES ,funcname
BUILTINR ( void * , Plug_GetNativePointer , ( const char * funcname ) ) ;
# undef ARGNAMES
2013-03-31 04:21:08 +00:00
void ( * Con_TrySubPrint ) ( const char * conname , const char * message ) ;
void Fallback_ConPrint ( const char * conname , const char * message )
{
2013-06-23 18:43:59 +00:00
pCon_Print ( message ) ;
2013-03-31 04:21:08 +00:00
}
2005-12-11 20:11:22 +00:00
void Con_SubPrintf ( char * subname , char * format , . . . )
{
va_list argptr ;
static char string [ 1024 ] ;
va_start ( argptr , format ) ;
2013-05-11 14:02:55 +00:00
Q_vsnprintf ( string , sizeof ( string ) , format , argptr ) ;
2005-12-11 20:11:22 +00:00
va_end ( argptr ) ;
2013-03-31 04:21:08 +00:00
Con_TrySubPrint ( subname , string ) ;
2005-12-11 20:11:22 +00:00
}
//porting zone:
# define COLOURGREEN "^2"
# define COLORWHITE "^7"
# define COLOURWHITE "^7" // word
# define COLOURRED "^1"
# define COLOURYELLOW "^3"
# define COLOURPURPLE "^5"
2013-06-23 02:33:52 +00:00
# define COMMANDPREFIX "xmpp"
# define COMMANDPREFIX2 "jab"
# define COMMANDPREFIX3 "jabbercl"
2005-12-11 20:11:22 +00:00
# define playsound(s)
# define TL_NETGETPACKETERROR "NET_GetPacket Error %s\n"
2013-06-23 18:43:59 +00:00
static char * JCL_ParseOut ( char * data , char * buf , int bufsize ) //this is taken out of quake
2005-12-11 20:11:22 +00:00
{
int c ;
int len ;
len = 0 ;
2013-06-23 02:33:52 +00:00
buf [ 0 ] = 0 ;
2005-12-11 20:11:22 +00:00
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 )
{
2013-06-23 02:33:52 +00:00
if ( len > = bufsize - 1 )
2005-12-11 20:11:22 +00:00
return data ;
c = * data + + ;
if ( c = = ' \" ' | | ! c )
{
2013-06-23 02:33:52 +00:00
buf [ len ] = 0 ;
2005-12-11 20:11:22 +00:00
return data ;
}
2013-06-23 02:33:52 +00:00
buf [ len ] = c ;
2005-12-11 20:11:22 +00:00
len + + ;
}
}
// parse a regular word
do
{
2013-06-23 02:33:52 +00:00
if ( len > = bufsize - 1 )
2005-12-11 20:11:22 +00:00
return data ;
2013-06-23 02:33:52 +00:00
buf [ len ] = c ;
2005-12-11 20:11:22 +00:00
data + + ;
len + + ;
c = * data ;
} while ( c > 32 ) ;
2013-06-23 02:33:52 +00:00
buf [ len ] = 0 ;
2005-12-11 20:11:22 +00:00
return data ;
}
2013-06-29 16:01:07 +00:00
char * JCL_Info_ValueForKey ( char * s , const char * key , char * valuebuf , int valuelen )
{
char pkey [ 1024 ] ;
char * o ;
if ( * s = = ' \\ ' )
s + + ;
while ( 1 )
{
o = pkey ;
while ( * s ! = ' \\ ' )
{
if ( ! * s )
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
* o + + = * s + + ;
if ( o + 2 > = pkey + sizeof ( pkey ) ) //hrm. hackers at work..
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
}
* o = 0 ;
s + + ;
o = valuebuf ;
while ( * s ! = ' \\ ' & & * s )
{
if ( ! * s )
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
* o + + = * s + + ;
if ( o + 2 > = valuebuf + valuelen ) //hrm. hackers at work..
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
}
* o = 0 ;
if ( ! strcmp ( key , pkey ) )
return valuebuf ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
if ( ! * s )
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
s + + ;
}
}
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
void RenameConsole ( char * totrim ) ;
void JCL_Command ( char * consolename ) ;
void JCL_LoadConfig ( void ) ;
void JCL_WriteConfig ( void ) ;
2005-12-11 20:11:22 +00:00
2013-03-31 04:21:08 +00:00
qintptr_t JCL_ExecuteCommand ( qintptr_t * args )
2005-12-11 20:11:22 +00:00
{
2007-09-17 20:55:15 +00:00
char cmd [ 256 ] ;
2013-06-23 18:43:59 +00:00
pCmd_Argv ( 0 , cmd , sizeof ( cmd ) ) ;
2013-06-23 02:33:52 +00:00
if ( ! strcmp ( cmd , COMMANDPREFIX ) | | ! strcmp ( cmd , COMMANDPREFIX2 ) | | ! strcmp ( cmd , COMMANDPREFIX3 ) )
2005-12-11 20:11:22 +00:00
{
2013-06-23 02:33:52 +00:00
if ( ! args [ 0 ] )
JCL_Command ( " " ) ;
2005-12-11 20:11:22 +00:00
return true ;
}
return false ;
}
2013-06-23 02:33:52 +00:00
qintptr_t JCL_ConsoleLink ( qintptr_t * args ) ;
2013-03-31 04:21:08 +00:00
qintptr_t JCL_ConExecuteCommand ( qintptr_t * args ) ;
2005-12-11 20:11:22 +00:00
2013-03-31 04:21:08 +00:00
qintptr_t JCL_Frame ( qintptr_t * args ) ;
2005-12-11 20:11:22 +00:00
2013-03-31 04:21:08 +00:00
qintptr_t Plug_Init ( qintptr_t * args )
2005-12-11 20:11:22 +00:00
{
if ( Plug_Export ( " Tick " , JCL_Frame ) & &
2005-12-15 02:01:57 +00:00
Plug_Export ( " ExecuteCommand " , JCL_ExecuteCommand ) )
2005-12-11 20:11:22 +00:00
{
CHECKBUILTIN ( Net_SetTLSClient ) ;
if ( ! BUILTINISVALID ( Net_SetTLSClient ) )
2013-06-23 18:43:59 +00:00
Con_Printf ( " XMPP Plugin Loaded ^1without^7 TLS \n " ) ;
2005-12-15 02:01:57 +00:00
else
2013-06-23 18:43:59 +00:00
Con_Printf ( " XMPP Plugin Loaded. For help, use: ^[/ " COMMANDPREFIX " /help^] \n " ) ;
2013-06-23 02:33:52 +00:00
Plug_Export ( " ConsoleLink " , JCL_ConsoleLink ) ;
2005-12-15 02:01:57 +00:00
if ( ! Plug_Export ( " ConExecuteCommand " , JCL_ConExecuteCommand ) )
{
2013-06-23 02:33:52 +00:00
Con_Printf ( " XMPP plugin in single-console mode \n " ) ;
2013-03-31 04:21:08 +00:00
Con_TrySubPrint = Fallback_ConPrint ;
2005-12-15 02:01:57 +00:00
}
2005-12-11 20:11:22 +00:00
else
2013-06-23 18:43:59 +00:00
Con_TrySubPrint = pCon_SubPrint ;
2005-12-15 02:01:57 +00:00
2013-06-23 18:43:59 +00:00
pCmd_AddCommand ( COMMANDPREFIX ) ;
pCmd_AddCommand ( COMMANDPREFIX2 ) ;
pCmd_AddCommand ( COMMANDPREFIX3 ) ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
//flags&1 == archive
pCvar_Register ( " xmpp_nostatus " , " 0 " , 0 , " xmpp " ) ;
pCvar_Register ( " xmpp_autoacceptjoins " , " 0 " , 0 , " xmpp " ) ;
pCvar_Register ( " xmpp_autoacceptinvites " , " 0 " , 0 , " xmpp " ) ;
pCvar_Register ( " xmpp_autoacceptvoice " , " 0 " , 0 , " xmpp " ) ;
pCvar_Register ( " xmpp_debug " , " 0 " , 0 , " xmpp " ) ;
pCvar_Register ( " xmpp_disabletls " , " 0 " , CVAR_NOTFROMSERVER , " xmpp " ) ;
pCvar_Register ( " xmpp_allowplainauth " , " 0 " , CVAR_NOTFROMSERVER , " xmpp " ) ;
2013-06-23 02:33:52 +00:00
CHECKBUILTIN ( Plug_GetNativePointer ) ;
if ( BUILTINISVALID ( Plug_GetNativePointer ) )
2013-06-29 16:01:07 +00:00
piceapi = pPlug_GetNativePointer ( ICE_API_CURRENT ) ;
2013-06-23 02:33:52 +00:00
JCL_LoadConfig ( ) ;
2005-12-11 20:11:22 +00:00
return 1 ;
}
else
2013-06-23 18:43:59 +00:00
Con_Printf ( " JCL Client Plugin failed \n " ) ;
2005-12-11 20:11:22 +00:00
return 0 ;
}
//\r\n is used to end a line.
//meaning \0s are valid.
//but never used cos it breaks strings
2013-03-31 04:21:08 +00:00
# define JCL_MAXMSGLEN 10000
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
# define CAP_QUERIED 1 //a query is pending or something.
# define CAP_VOICE 2 //supports voice
# define CAP_INVITE 4 //supports game invites.
2013-06-23 02:33:52 +00:00
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
2013-06-29 16:01:07 +00:00
unsigned int caps ;
2013-06-23 02:33:52 +00:00
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 ] ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
struct buddy_s * next ;
char accountdomain [ 1 ] ; //no resource on there
} buddy_t ;
typedef struct jclient_s
{
2005-12-11 20:11:22 +00:00
char server [ 64 ] ;
int port ;
2013-06-29 16:01:07 +00:00
enum
{
JCL_DEAD , //not connected. connection died or something.
JCL_AUTHING , //connected, but not able to send any info on it other than to auth
JCL_ACTIVE //we're connected, we got a buddy list and everything
} status ;
unsigned int timeout ; //reconnect/ping timer
2005-12-11 20:11:22 +00:00
qhandle_t socket ;
2013-06-29 16:01:07 +00:00
//we buffer output for times when the outgoing socket is full.
//mostly this only happens at the start of the connection when the socket isn't actually open yet.
2013-06-23 02:33:52 +00:00
char * outbuf ;
int outbufpos ;
int outbuflen ;
int outbufmax ;
2013-06-29 16:01:07 +00:00
2013-03-31 04:21:08 +00:00
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.
2005-12-11 20:11:22 +00:00
int bufferedinammount ;
char defaultdest [ 256 ] ;
char domain [ 256 ] ;
char username [ 256 ] ;
char password [ 256 ] ;
char resource [ 256 ] ;
2013-06-23 02:33:52 +00:00
char jid [ 256 ] ; //this is our full username@domain/resource string
2013-06-29 16:01:07 +00:00
char localalias [ 256 ] ; //this is what's shown infront of outgoing messages. >> by default until we can get our name.
2005-12-11 20:11:22 +00:00
int tagdepth ;
int openbracket ;
int instreampos ;
2013-06-29 16:01:07 +00:00
qboolean tlsconnect ; //use the old tls method on port 5223.
2013-03-31 04:21:08 +00:00
qboolean connected ; //fully on server and authed and everything.
2013-06-29 16:01:07 +00:00
qboolean issecure ; //tls enabled (either upgraded or initially)
2013-06-23 02:33:52 +00:00
qboolean streamdebug ; //echo the stream to subconsoles
2013-06-29 16:01:07 +00:00
qboolean preapproval ; //server supports presence preapproval
2013-03-31 04:21:08 +00:00
char curquakeserver [ 2048 ] ;
char defaultnamespace [ 2048 ] ; //should be 'jabber:client' or blank (and spammy with all the extra xmlns attribs)
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
struct iq_s
{
2013-06-23 02:33:52 +00:00
struct iq_s * next ;
char id [ 64 ] ;
int timeout ;
2013-06-29 16:01:07 +00:00
qboolean ( * callback ) ( struct jclient_s * jcl , struct subtree_s * tree , struct iq_s * iq ) ;
void * usrptr ;
2013-06-23 02:33:52 +00:00
} * pendingiqs ;
struct c2c_s
{
struct c2c_s * next ;
2013-06-29 16:01:07 +00:00
enum iceproto_e mediatype ;
enum icemode_e method ; //ICE_RAW or ICE_ICE. this is what the peer asked for. updated if we degrade it.
qboolean accepted ; //connection is going
qboolean creator ; //true if we're the creator.
struct icestate_s * ice ;
char * peeraddr ;
int peerport ;
char * with ;
char sid [ 1 ] ;
} * c2c ;
2013-06-23 02:33:52 +00:00
buddy_t * buddies ;
2005-12-11 20:11:22 +00:00
} jclient_t ;
jclient_t * jclient ;
2013-06-29 16:01:07 +00:00
int jclient_curtime ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
struct subtree_s ;
void JCL_AddClientMessagef ( jclient_t * jcl , char * fmt , . . . ) ;
2013-06-23 18:43:59 +00:00
qboolean JCL_FindBuddy ( jclient_t * jcl , char * jid , buddy_t * * buddy , bresource_t * * bres ) ;
2013-06-29 16:01:07 +00:00
void JCL_GeneratePresence ( jclient_t * jcl , qboolean force ) ;
struct iq_s * JCL_SendIQf ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , struct subtree_s * tree , struct iq_s * iq ) , char * iqtype , char * target , char * fmt , . . . ) ;
struct iq_s * JCL_SendIQNode ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , xmltree_t * node , qboolean destroynode ) ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
char * TrimResourceFromJid ( char * jid )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char * slash ;
slash = strchr ( jid , ' / ' ) ;
if ( slash )
{
* slash = ' \0 ' ;
return slash + 1 ;
}
return NULL ;
}
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
static struct c2c_s * JCL_JingleCreateSession ( jclient_t * jcl , char * with , qboolean creator , char * sid , int method , int mediatype )
{
struct icestate_s * ice = NULL ;
struct c2c_s * c2c ;
char generatedname [ 64 ] ;
if ( piceapi )
ice = piceapi - > ICE_Create ( NULL , sid , with , method , mediatype ) ;
if ( ice )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
piceapi - > ICE_Get ( ice , " sid " , generatedname , sizeof ( generatedname ) ) ;
sid = generatedname ;
piceapi - > ICE_Set ( ice , " codec96 " , " speex@8000 " ) ;
piceapi - > ICE_Set ( ice , " codec97 " , " speex@16000 " ) ;
piceapi - > ICE_Set ( ice , " codec98 " , " opus " ) ;
}
else
return NULL ; //no way to get the local ip otherwise, which means things won't work proper
c2c = malloc ( sizeof ( * c2c ) + strlen ( sid ) ) ;
memset ( c2c , 0 , sizeof ( * c2c ) ) ;
c2c - > next = jcl - > c2c ;
jcl - > c2c = c2c ;
strcpy ( c2c - > sid , sid ) ;
c2c - > mediatype = mediatype ;
c2c - > creator = creator ;
c2c - > method = method ;
//FIXME: we need to query this from the server.
//however, google don't implement the 'standard' way
//the 'standard' way contains a huge big fat do-not-implement message.
//and google's way equally says 'don't implement'... so...
//as I kinda expect most users to use google's network anyway, I *hope* they won't mind too much. I doubt they'll get much traffic anyway. its just stun.
piceapi - > ICE_Set ( ice , " stunport " , " 19302 " ) ;
piceapi - > ICE_Set ( ice , " stunip " , " stun.l.google.com " ) ;
//copy out the interesting parameters
c2c - > with = strdup ( with ) ;
c2c - > ice = ice ;
return c2c ;
}
static qboolean JCL_JingleAcceptAck ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
{
struct c2c_s * c2c ;
if ( tree )
{
for ( c2c = jcl - > c2c ; c2c ; c2c = c2c - > next )
{
if ( c2c = = iq - > usrptr )
{
if ( c2c - > ice )
piceapi - > ICE_Set ( c2c - > ice , " state " , STRINGIFY ( ICE_CONNECTING ) ) ;
}
}
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
return true ;
}
/*
sends a jingle message to the peer .
action should be one of multiple things :
session - terminate - totally not acceptable . this also closes the c2c
session - accept - details are okay . this also begins ice polling ( on iq ack , once we ' re sure the peer got our message )
( internally generated ) transport - replace - details are okay , except we want a different transport method .
*/
qboolean JCL_JingleSend ( jclient_t * jcl , struct c2c_s * c2c , char * action )
{
qboolean result ;
xmltree_t * jingle ;
struct icestate_s * ice = c2c - > ice ;
qboolean wasaccept = false ;
int transportmode = ICEM_ICE ;
if ( ! ice )
action = " session-terminate " ;
2013-06-23 02:33:52 +00:00
jingle = XML_CreateNode ( NULL , " jingle " , " urn:xmpp:jingle:1 " , " " ) ;
2013-06-29 16:01:07 +00:00
XML_AddParameter ( jingle , " sid " , c2c - > sid ) ;
if ( ! strcmp ( action , " session-initiate " ) )
{ //these attributes are meant to only be present in initiate. for call forwarding etc. which we don't properly support.
XML_AddParameter ( jingle , " initiator " , jcl - > jid ) ;
}
if ( ! strcmp ( action , " session-terminate " ) )
{
struct c2c_s * * link ;
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( * link = = c2c )
{
* link = c2c - > next ;
break ;
}
}
if ( c2c - > ice )
piceapi - > ICE_Close ( c2c - > ice ) ;
result = false ;
}
else
2013-06-23 02:33:52 +00:00
{
xmltree_t * content = XML_CreateNode ( jingle , " content " , " " , " " ) ;
2013-06-29 16:01:07 +00:00
result = true ;
if ( ! strcmp ( action , " session-accept " ) )
{
if ( c2c - > method = = transportmode )
{
XML_AddParameter ( jingle , " responder " , jcl - > jid ) ;
c2c - > accepted = wasaccept = true ;
}
else
action = " transport-replace " ;
}
2013-06-23 02:33:52 +00:00
{
xmltree_t * description ;
xmltree_t * transport ;
2013-06-29 16:01:07 +00:00
if ( transportmode = = ICEM_RAW )
2013-06-23 02:33:52 +00:00
{
transport = XML_CreateNode ( content , " transport " , " urn:xmpp:jingle:transports:raw-udp:1 " , " " ) ;
{
xmltree_t * candidate ;
2013-06-29 16:01:07 +00:00
struct icecandinfo_s * b = NULL ;
struct icecandinfo_s * c ;
while ( ( c = piceapi - > ICE_GetLCandidateInfo ( ice ) ) )
2013-06-23 02:33:52 +00:00
{
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 ) ;
}
}
}
2013-06-29 16:01:07 +00:00
else if ( transportmode = = ICEM_ICE )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char val [ 64 ] ;
2013-06-23 02:33:52 +00:00
transport = XML_CreateNode ( content , " transport " , " urn:xmpp:jingle:transports:ice-udp:1 " , " " ) ;
2013-06-29 16:01:07 +00:00
piceapi - > ICE_Get ( ice , " lufrag " , val , sizeof ( val ) ) ;
XML_AddParameter ( transport , " ufrag " , val ) ;
piceapi - > ICE_Get ( ice , " lpwd " , val , sizeof ( val ) ) ;
XML_AddParameter ( transport , " pwd " , val ) ;
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
struct icecandinfo_s * c ;
while ( ( c = piceapi - > ICE_GetLCandidateInfo ( ice ) ) )
2013-06-23 02:33:52 +00:00
{
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 ) ;
}
}
}
2013-06-29 16:01:07 +00:00
# ifdef VOIP
if ( c2c - > mediatype = = ICEP_VOICE )
{
xmltree_t * payload ;
int i ;
XML_AddParameter ( content , " senders " , " both " ) ;
XML_AddParameter ( content , " name " , " audio-session " ) ;
XML_AddParameter ( content , " creator " , " initiator " ) ;
description = XML_CreateNode ( content , " description " , " urn:xmpp:jingle:apps:rtp:1 " , " " ) ;
XML_AddParameter ( description , " media " , " audio " ) ;
for ( i = 96 ; i < = 127 ; i + + )
{
char codecname [ 64 ] ;
char argn [ 64 ] ;
Q_snprintf ( argn , sizeof ( argn ) , " codec%i " , i ) ;
piceapi - > ICE_Get ( ice , argn , codecname , sizeof ( codecname ) ) ;
if ( ! strcmp ( codecname , " speex@8000 " ) )
{ //speex narrowband
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " clockrate " , " 8000 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " SPEEX " ) ;
}
else if ( ! strcmp ( codecname , " speex@16000 " ) )
{ //speex wideband
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " clockrate " , " 16000 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " SPEEX " ) ;
}
else if ( ! strcmp ( codecname , " speex@32000 " ) )
{ //speex ultrawideband
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " clockrate " , " 32000 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " SPEEX " ) ;
}
else if ( ! strcmp ( codecname , " opus " ) )
{ //opus codec.
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " OPUS " ) ;
}
}
}
# endif
else
{
description = XML_CreateNode ( content , " description " , QUAKEMEDIAXMLNS , " " ) ;
XML_AddParameter ( description , " media " , QUAKEMEDIATYPE ) ;
if ( c2c - > mediatype = = ICEP_QWSERVER )
XML_AddParameter ( description , " host " , " me " ) ;
else if ( c2c - > mediatype = = ICEP_QWCLIENT )
XML_AddParameter ( description , " host " , " you " ) ;
}
}
}
XML_AddParameter ( jingle , " action " , action ) ;
// Con_Printf("Sending Jingle:\n");
// XML_ConPrintTree(jingle, 1);
JCL_SendIQNode ( jcl , wasaccept ? JCL_JingleAcceptAck : NULL , " set " , c2c - > with , jingle , true ) - > usrptr = c2c ;
return result ;
}
void JCL_JingleTimeouts ( jclient_t * jcl , qboolean killall )
{
struct c2c_s * c2c ;
for ( c2c = jcl - > c2c ; c2c ; c2c = c2c - > next )
{
struct icecandinfo_s * lc ;
if ( c2c - > method = = ICEM_ICE )
{
char bah [ 2 ] ;
piceapi - > ICE_Get ( c2c - > ice , " newlc " , bah , sizeof ( bah ) ) ;
if ( atoi ( bah ) )
{
Con_DPrintf ( " Sending updated local addresses \n " ) ;
JCL_JingleSend ( jcl , c2c , " transport-info " ) ;
}
}
}
}
void JCL_Join ( jclient_t * jcl , char * target , char * sid , qboolean allow , int protocol )
{
struct c2c_s * c2c = NULL , * * link ;
xmltree_t * jingle ;
struct icestate_s * ice ;
char autotarget [ 256 ] ;
if ( ! jcl )
return ;
if ( ! strchr ( target , ' / ' ) )
{
buddy_t * b ;
bresource_t * br ;
JCL_FindBuddy ( jcl , target , & b , & br ) ;
if ( ! br )
br = b - > defaultresource ;
if ( ! br )
br = b - > resources ;
if ( ! br )
{
Con_Printf ( " User name not valid \n " ) ;
return ;
}
Q_snprintf ( autotarget , sizeof ( autotarget ) , " %s/%s " , b - > accountdomain , br - > resource ) ;
target = autotarget ;
}
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( ! strcmp ( ( * link ) - > with , target ) & & ( ! sid | | ! strcmp ( ( * link ) - > sid , sid ) ) & & ( ( * link ) - > mediatype = = protocol | | protocol = = ICEP_INVALID ) )
{
c2c = * link ;
break ;
}
}
if ( allow )
{
if ( ! c2c )
{
if ( ! sid )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
c2c = JCL_JingleCreateSession ( jcl , target , true , sid , DEFAULTICEMODE , ( ( protocol = = ICEP_INVALID ) ? ICEP_QWCLIENT : protocol ) ) ;
JCL_JingleSend ( jcl , c2c , " session-initiate " ) ;
Con_Printf ( " %s ^[[%s] \\ xmpp \\ %s^] ^[[Hang Up] \\ xmppact \\ jdeny \\ xmpp \\ %s \\ xmppsid \\ %s^]. \n " , protocol = = ICEP_VOICE ? " Calling " : " Requesting session with " , target , target , target , c2c - > sid ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else
Con_Printf ( " That session has expired. \n " ) ;
}
else if ( c2c - > creator )
{
//resend initiate if they've not acked it... I dunno...
JCL_JingleSend ( jcl , c2c , " session-initiate " ) ;
Con_Printf ( " Restarting session with ^[[%s] \\ xmpp \\ %s^]. \n " , target , target ) ;
}
else if ( c2c - > accepted )
Con_Printf ( " That session was already accepted. \n " ) ;
else
{
JCL_JingleSend ( jcl , c2c , " session-accept " ) ;
Con_Printf ( " Accepting session from ^[[%s] \\ xmpp \\ %s^]. \n " , target , target ) ;
}
}
else
{
if ( c2c )
{
JCL_JingleSend ( jcl , c2c , " session-terminate " ) ;
Con_Printf ( " Terminating session with ^[[%s] \\ xmpp \\ %s^]. \n " , target , target ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else
Con_Printf ( " That session has already expired. \n " ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-06-29 16:01:07 +00:00
void JCL_JingleParsePeerPorts ( jclient_t * jcl , struct c2c_s * c2c , xmltree_t * inj , char * from )
2013-06-23 02:33:52 +00:00
{
xmltree_t * incontent = XML_ChildOfTree ( inj , " content " , 0 ) ;
xmltree_t * intransport = XML_ChildOfTree ( incontent , " transport " , 0 ) ;
xmltree_t * incandidate ;
2013-06-29 16:01:07 +00:00
struct icecandinfo_s rem ;
2013-06-23 02:33:52 +00:00
int i ;
2013-06-29 16:01:07 +00:00
if ( strcmp ( c2c - > with , from ) | | strcmp ( c2c - > sid , XML_GetParameter ( inj , " sid " , " " ) ) )
2013-06-23 02:33:52 +00:00
{
Con_Printf ( " %s is trying to mess with our connections... \n " , from ) ;
return ;
}
2013-06-29 16:01:07 +00:00
if ( ! c2c - > sid )
return ;
if ( ! intransport )
return ;
if ( ! c2c - > ice )
return ;
piceapi - > ICE_Set ( c2c - > ice , " rufrag " , XML_GetParameter ( intransport , " ufrag " , " " ) ) ;
piceapi - > ICE_Set ( c2c - > ice , " rpwd " , XML_GetParameter ( intransport , " pwd " , " " ) ) ;
2013-06-23 18:43:59 +00:00
for ( i = 0 ; ( incandidate = XML_ChildOfTree ( intransport , " candidate " , i ) ) ; i + + )
2013-06-23 02:33:52 +00:00
{
char * s ;
memset ( & rem , 0 , sizeof ( rem ) ) ;
2013-06-29 16:01:07 +00:00
Q_strlcpy ( rem . addr , XML_GetParameter ( incandidate , " ip " , " " ) , sizeof ( rem . addr ) ) ;
Q_strlcpy ( rem . candidateid , XML_GetParameter ( incandidate , " id " , " " ) , sizeof ( rem . candidateid ) ) ;
2013-06-23 02:33:52 +00:00
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 ;
2013-06-29 16:01:07 +00:00
piceapi - > ICE_AddRCandidateInfo ( c2c - > ice , & rem ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-06-29 16:01:07 +00:00
qboolean JCL_JingleHandleInitiate ( jclient_t * jcl , xmltree_t * inj , char * from )
2013-06-23 02:33:52 +00:00
{
/*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 = ' 3 XkwverVxJLy2lRXD1lOAb ' 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 " , " " ) ;
2013-06-29 16:01:07 +00:00
char * sid = XML_GetParameter ( inj , " sid " , " " ) ;
2013-06-23 02:33:52 +00:00
xmltree_t * jingle ;
struct icestate_s * ice ;
qboolean accepted = false ;
enum icemode_e imode ;
2013-06-29 16:01:07 +00:00
char * response = " session-terminate " ;
char * offer = " pwn you " ;
char * autocvar = " xmpp_autoaccepthax " ;
char * initiator ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
struct c2c_s * c2c = NULL ;
int mt = ICEP_INVALID ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
//FIXME: add support for session forwarding so that we might forward the connection to the real server. for now we just reject it.
initiator = XML_GetParameter ( inj , " initiator " , " " ) ;
if ( strcmp ( initiator , from ) )
return false ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
if ( incontent & & ! strcmp ( descriptionmedia , QUAKEMEDIATYPE ) & & ! strcmp ( descriptionxmlns , QUAKEMEDIAXMLNS ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char * host = XML_GetParameter ( indescription , " host " , " you " ) ;
if ( ! strcmp ( host , " you " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
mt = ICEP_QWSERVER ;
offer = " join your game " ;
autocvar = " xmpp_autoacceptjoins " ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( host , " me " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
mt = ICEP_QWCLIENT ;
offer = " invite you to thier game " ;
autocvar = " xmpp_autoacceptinvites " ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
}
if ( incontent & & ! strcmp ( descriptionmedia , " audio " ) & & ! strcmp ( descriptionxmlns , " urn:xmpp:jingle:apps:rtp:1 " ) )
{
mt = ICEP_VOICE ;
offer = " have a natter with you " ;
autocvar = " xmpp_autoacceptvoice " ;
}
if ( mt = = ICEP_INVALID )
return false ;
//FIXME: if both people try to establish a connection to the other simultaneously, the higher session id is meant to be canceled, and the lower accepted automagically.
c2c = JCL_JingleCreateSession ( jcl , from , false ,
sid ,
strcmp ( transportxmlns , " urn:xmpp:jingle:transports:raw-udp:1 " ) ? ICEM_ICE : ICEM_RAW ,
mt
) ;
if ( ! c2c )
return false ;
if ( c2c - > mediatype = = ICEP_VOICE )
{
qboolean okay = false ;
int i = 0 ;
xmltree_t * payload ;
//chuck it at the engine and see what sticks. at least one must...
while ( ( payload = XML_ChildOfTree ( indescription , " payload-type " , i + + ) ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char * name = XML_GetParameter ( payload , " name " , " " ) ;
char * clock = XML_GetParameter ( payload , " clockrate " , " " ) ;
char * id = XML_GetParameter ( payload , " id " , " " ) ;
char parm [ 64 ] ;
char val [ 64 ] ;
if ( ! strcmp ( name , " SPEEX " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
Q_snprintf ( parm , sizeof ( parm ) , " codec%i " , atoi ( id ) ) ;
Q_snprintf ( val , sizeof ( val ) , " speex@%i " , atoi ( clock ) ) ;
okay | = piceapi - > ICE_Set ( c2c - > ice , parm , val ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( name , " OPUS " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
Q_snprintf ( parm , sizeof ( parm ) , " codec%i " , atoi ( id ) ) ;
okay | = piceapi - > ICE_Set ( c2c - > ice , parm , " opus " ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-06-29 16:01:07 +00:00
//don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something.
if ( ! okay )
{
JCL_JingleSend ( jcl , c2c , " session-terminate " ) ;
return false ;
}
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
JCL_JingleParsePeerPorts ( jcl , c2c , inj , from ) ;
if ( c2c - > mediatype ! = ICEP_INVALID )
{
if ( ! pCvar_GetFloat ( autocvar ) )
{
//show a prompt for it, send the reply when the user decides.
Con_Printf (
" ^[[%s] \\ xmpp \\ %s^] wants to %s. "
" ^[[Authorise] \\ xmppact \\ jauth \\ xmpp \\ %s \\ xmppsid \\ %s^] "
" ^[[Deny] \\ xmppact \\ jdeny \\ xmpp \\ %s \\ xmppsid \\ %s^] \n " ,
from , from ,
offer ,
from , sid ,
from , sid ) ;
return true ;
}
else
{
Con_Printf ( " Auto-accepting session from ^[[%s] \\ xmpp \\ %s^] \n " , from , from ) ;
response = " session-accept " ;
}
}
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
JCL_JingleSend ( jcl , c2c , response ) ;
return true ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
qboolean JCL_ParseJingle ( jclient_t * jcl , xmltree_t * tree , char * from , char * id )
2013-06-23 02:33:52 +00:00
{
char * action = XML_GetParameter ( tree , " action " , " " ) ;
char * sid = XML_GetParameter ( tree , " sid " , " " ) ;
2013-06-29 16:01:07 +00:00
struct c2c_s * c2c = NULL , * * link ;
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( ! strcmp ( ( * link ) - > sid , sid ) )
{
c2c = * link ;
if ( ! c2c - > accepted )
break ;
}
}
2013-06-23 02:33:52 +00:00
//validate sender
2013-06-29 16:01:07 +00:00
if ( c2c & & strcmp ( c2c - > with , from ) )
2013-06-23 02:33:52 +00:00
{
Con_Printf ( " %s is trying to mess with our connections... \n " , from ) ;
2013-06-29 16:01:07 +00:00
return false ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
//FIXME: transport-info, transport-replace
2013-06-23 02:33:52 +00:00
if ( ! strcmp ( action , " session-terminate " ) )
{
2013-06-29 16:01:07 +00:00
xmltree_t * reason = XML_ChildOfTree ( tree , " reason " , 0 ) ;
if ( ! c2c )
{
Con_Printf ( " Received session-terminate without an active session \n " ) ;
return false ;
}
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
if ( reason & & reason - > child )
Con_Printf ( " Session ended: %s \n " , reason - > child - > name ) ;
else
Con_Printf ( " Session ended \n " ) ;
//unlink it
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( * link = = c2c )
{
* link = c2c - > next ;
break ;
}
}
// XML_ConPrintTree(tree, 0);
if ( c2c - > ice )
piceapi - > ICE_Close ( c2c - > ice ) ;
free ( c2c ) ;
}
//content-accept
//content-add
//content-modify
//content-reject
//content-remove
//description-info
//security-info
//
else if ( ! strcmp ( action , " transport-info " ) )
{ //peer wants to add ports.
if ( c2c )
JCL_JingleParsePeerPorts ( jcl , c2c , tree , from ) ;
else
Con_DPrintf ( " Received transport-info without an active session \n " ) ;
}
//FIXME: we need to add support for this to downgrade to raw if someone tries calling through a SIP gateway
else if ( ! strcmp ( action , " transport-replace " ) )
{
if ( c2c )
{
if ( 1 )
JCL_JingleSend ( jcl , c2c , " transport-reject " ) ;
else
{
JCL_JingleParsePeerPorts ( jcl , c2c , tree , from ) ;
JCL_JingleSend ( jcl , c2c , " transport-accept " ) ;
}
}
}
else if ( ! strcmp ( action , " transport-reject " ) )
{
JCL_JingleSend ( jcl , c2c , " session-terminate " ) ;
2013-06-23 02:33:52 +00:00
}
else if ( ! strcmp ( action , " session-accept " ) )
{
2013-06-29 16:01:07 +00:00
if ( ! c2c )
{
Con_DPrintf ( " Unknown session acceptance \n " ) ;
return false ;
}
else if ( ! c2c - > creator )
{
Con_DPrintf ( " Peer tried to accept a session that *they* created! \n " ) ;
return false ;
}
else if ( c2c - > accepted )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
//pidgin is buggy and can dupe-accept sessions multiple times.
Con_DPrintf ( " Duplicate session-accept from peer. \n " ) ;
//XML_ConPrintTree(tree, 0);
return false ;
2013-06-23 02:33:52 +00:00
}
else
{
2013-06-29 16:01:07 +00:00
char * responder = XML_GetParameter ( tree , " responder " , from ) ;
if ( strcmp ( responder , from ) )
{
return false ;
}
2013-06-23 02:33:52 +00:00
Con_Printf ( " Session Accepted! \n " ) ;
2013-06-29 16:01:07 +00:00
// XML_ConPrintTree(tree, 0);
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
JCL_JingleParsePeerPorts ( jcl , c2c , tree , from ) ;
c2c - > accepted = true ;
2013-06-23 02:33:52 +00:00
//if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted
2013-06-29 16:01:07 +00:00
if ( c2c - > ice )
piceapi - > ICE_Set ( c2c - > ice , " state " , STRINGIFY ( ICE_CONNECTING ) ) ;
2013-06-23 02:33:52 +00:00
}
}
else if ( ! strcmp ( action , " session-initiate " ) )
{
2013-06-29 16:01:07 +00:00
// Con_Printf("Peer initiating connection!\n");
// XML_ConPrintTree(tree, 0);
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
if ( ! JCL_JingleHandleInitiate ( jcl , tree , from ) )
return false ;
2013-06-23 02:33:52 +00:00
}
else
{
Con_Printf ( " Unknown jingle action: %s \n " , action ) ;
2013-06-29 16:01:07 +00:00
// XML_ConPrintTree(tree, 0);
2013-06-23 02:33:52 +00:00
}
JCL_AddClientMessagef ( jcl ,
" <iq type='result' to='%s' id='%s' /> " , from , id ) ;
2013-06-29 16:01:07 +00:00
return true ;
2013-06-23 02:33:52 +00:00
}
qintptr_t JCL_ConsoleLink ( qintptr_t * args )
{
char text [ 256 ] ;
char link [ 256 ] ;
2013-06-29 16:01:07 +00:00
char who [ 256 ] ;
char what [ 256 ] ;
// pCmd_Argv(0, text, sizeof(text));
2013-06-23 18:43:59 +00:00
pCmd_Argv ( 1 , link , sizeof ( link ) ) ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
JCL_Info_ValueForKey ( link , " xmpp " , who , sizeof ( who ) ) ;
JCL_Info_ValueForKey ( link , " xmppact " , what , sizeof ( what ) ) ;
if ( ! * who & & ! * what )
return false ;
if ( ! strcmp ( what , " pauth " ) )
2013-06-23 02:33:52 +00:00
{
//we should friend them too.
2013-06-29 16:01:07 +00:00
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_AddClientMessagef ( jclient , " <presence to='%s' type='subscribed'/> " , who ) ;
2013-06-23 02:33:52 +00:00
return true ;
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( what , " pdeny " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_AddClientMessagef ( jclient , " <presence to='%s' type='unsubscribed'/> " , who ) ;
2013-06-23 02:33:52 +00:00
return true ;
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( what , " jauth " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
JCL_Info_ValueForKey ( link , " xmppsid " , what , sizeof ( what ) ) ;
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_Join ( jclient , who , what , true , ICEP_INVALID ) ;
return true ;
}
else if ( ! strcmp ( what , " jdeny " ) )
{
JCL_Info_ValueForKey ( link , " xmppsid " , what , sizeof ( what ) ) ;
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_Join ( jclient , who , what , false , ICEP_INVALID ) ;
return true ;
}
else if ( ! strcmp ( what , " join " ) )
{
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_Join ( jclient , who , NULL , true , ICEP_QWCLIENT ) ;
return true ;
}
else if ( ! strcmp ( what , " invite " ) )
{
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_Join ( jclient , who , NULL , true , ICEP_QWSERVER ) ;
return true ;
}
else if ( ! strcmp ( what , " call " ) )
{
if ( jclient & & jclient - > status = = JCL_ACTIVE )
JCL_Join ( jclient , who , NULL , true , ICEP_VOICE ) ;
return true ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else if ( ( * who & & ! * what ) | | ! strcmp ( what , " msg " ) )
2013-06-23 02:33:52 +00:00
{
if ( jclient )
{
char * f ;
buddy_t * b ;
bresource_t * br ;
2013-06-29 16:01:07 +00:00
JCL_FindBuddy ( jclient , * who ? who : jclient - > defaultdest , & b , & br ) ;
2013-06-23 02:33:52 +00:00
f = b - > name ;
b - > defaultresource = br ;
if ( BUILTINISVALID ( Con_SubPrint ) )
2013-06-23 18:43:59 +00:00
pCon_SubPrint ( f , " " ) ;
2013-06-23 02:33:52 +00:00
if ( BUILTINISVALID ( Con_SetActive ) )
2013-06-23 18:43:59 +00:00
pCon_SetActive ( f ) ;
2013-06-23 02:33:52 +00:00
}
return true ;
}
2013-06-29 16:01:07 +00:00
else
{
Con_Printf ( " Unsupported xmpp action (%s) in link \n " , what ) ;
}
2013-06-23 02:33:52 +00:00
return false ;
}
2013-03-31 04:21:08 +00:00
qintptr_t JCL_ConExecuteCommand ( qintptr_t * args )
2005-12-11 20:11:22 +00:00
{
2013-06-23 02:33:52 +00:00
buddy_t * b ;
char consolename [ 256 ] ;
2005-12-11 20:11:22 +00:00
if ( ! jclient )
{
char buffer [ 256 ] ;
2013-06-23 18:43:59 +00:00
pCmd_Argv ( 0 , buffer , sizeof ( buffer ) ) ;
Con_SubPrintf ( buffer , " You were disconnected \n " ) ;
2005-12-11 20:11:22 +00:00
return true ;
}
2013-06-23 18:43:59 +00:00
pCmd_Argv ( 0 , consolename , sizeof ( consolename ) ) ;
2013-06-23 02:33:52 +00:00
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 ) ;
2005-12-11 20:11:22 +00:00
return true ;
}
2013-06-23 02:33:52 +00:00
void JCL_FlushOutgoing ( jclient_t * jcl )
{
int sent ;
if ( ! jcl | | ! jcl - > outbuflen )
return ;
2013-06-23 18:43:59 +00:00
sent = pNet_Send ( jcl - > socket , jcl - > outbuf + jcl - > outbufpos , jcl - > outbuflen ) ; //FIXME: This needs rewriting to cope with errors.
2013-06-23 02:33:52 +00:00
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 ;
}
2013-06-24 09:04:00 +00:00
// else
// Con_Printf("Unable to send anything\n");
2013-06-23 02:33:52 +00:00
}
2005-12-11 20:11:22 +00:00
void JCL_AddClientMessage ( jclient_t * jcl , char * msg , int datalen )
{
2013-06-23 02:33:52 +00:00
//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 ) ;
2005-12-11 20:11:22 +00:00
}
void JCL_AddClientMessageString ( jclient_t * jcl , char * msg )
{
JCL_AddClientMessage ( jcl , msg , strlen ( msg ) ) ;
}
2013-06-23 02:33:52 +00:00
void JCL_AddClientMessagef ( jclient_t * jcl , char * fmt , . . . )
{
va_list argptr ;
char body [ 2048 ] ;
2013-06-23 18:43:59 +00:00
2013-06-23 02:33:52 +00:00
va_start ( argptr , fmt ) ;
2013-06-23 18:43:59 +00:00
Q_vsnprintf ( body , sizeof ( body ) , fmt , argptr ) ;
2013-06-23 02:33:52 +00:00
va_end ( argptr ) ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
JCL_AddClientMessageString ( jcl , body ) ;
}
2013-06-29 16:01:07 +00:00
qboolean JCL_Reconnect ( jclient_t * jcl )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
//destroy any data that never got sent
free ( jcl - > outbuf ) ;
jcl - > outbuf = NULL ;
jcl - > outbuflen = 0 ;
jcl - > outbufpos = 0 ;
jcl - > outbufmax = 0 ;
jcl - > instreampos = 0 ;
jcl - > bufferedinammount = 0 ;
jcl - > tagdepth = 0 ;
Q_strlcpy ( jcl - > localalias , " >> " , sizeof ( jcl - > localalias ) ) ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: Trying to connect to %s \n " , jcl - > domain ) ;
jcl - > socket = pNet_TCPConnect ( jcl - > server , jcl - > tlsconnect ? 5223 : 5222 ) ; //port is only used if the url doesn't contain one. It's a default.
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
//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 " ) ;
return false ;
}
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
jcl - > issecure = false ;
if ( jcl - > tlsconnect )
if ( pNet_SetTLSClient ( jcl - > socket , jcl - > server ) > = 0 )
jcl - > issecure = true ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
jcl - > status = JCL_AUTHING ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
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'> " ) ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
return true ;
}
jclient_t * JCL_Connect ( char * server , qboolean usesecure , char * account , char * password )
{
char gamename [ 64 ] ;
jclient_t * jcl ;
char * domain ;
char * res ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
res = TrimResourceFromJid ( account ) ;
if ( ! res )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
//the default resource matches the game that they're trying to play.
if ( pCvar_GetString ( " fs_gamename " , gamename , sizeof ( gamename ) ) )
{
//strip out any weird chars (namely whitespace)
char * o ;
for ( o = gamename , res = gamename ; * res ; )
{
if ( * res = = ' ' | | * res = = ' \t ' )
res + + ;
else
* o + + = * res + + ;
}
res = gamename ;
}
2005-12-11 20:11:22 +00:00
}
if ( usesecure )
{
2013-06-29 16:01:07 +00:00
if ( ! BUILTINISVALID ( Net_SetTLSClient ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: TLS is not supported \n " ) ;
2005-12-11 20:11:22 +00:00
return NULL ;
}
2013-06-29 16:01:07 +00:00
}
domain = strchr ( account , ' @ ' ) ;
if ( domain )
{
* domain = ' \0 ' ;
domain + + ;
2005-12-11 20:11:22 +00:00
}
else
2013-06-29 16:01:07 +00:00
{
domain = DEFAULTDOMAIN ;
if ( domain & & * domain )
Con_Printf ( " XMPP: domain not specified, assuming %s \n " , domain ) ;
else
{
Con_Printf ( " XMPP: domain not specified \n " ) ;
return NULL ;
}
}
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
jcl = malloc ( sizeof ( jclient_t ) ) ;
if ( ! jcl )
return NULL ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
memset ( jcl , 0 , sizeof ( jclient_t ) ) ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
jcl - > tlsconnect = usesecure ;
2013-06-23 18:43:59 +00:00
jcl - > streamdebug = ! ! pCvar_GetFloat ( " xmpp_debug " ) ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
Q_strlcpy ( jcl - > server , server , sizeof ( jcl - > server ) ) ;
2013-05-11 14:02:55 +00:00
Q_strlcpy ( jcl - > username , account , sizeof ( jcl - > username ) ) ;
2013-06-29 16:01:07 +00:00
Q_strlcpy ( jcl - > domain , domain , sizeof ( jcl - > domain ) ) ;
2013-05-11 14:02:55 +00:00
Q_strlcpy ( jcl - > password , password , sizeof ( jcl - > password ) ) ;
2013-06-29 16:01:07 +00:00
Q_strlcpy ( jcl - > resource , ( res & & * res ) ? res : " FTE " , sizeof ( jcl - > password ) ) ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
if ( ! JCL_Reconnect ( jcl ) )
{
free ( jcl ) ;
jcl = NULL ;
}
2005-12-11 20:11:22 +00:00
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 ' ;
2013-03-31 04:21:08 +00:00
// Con_Printf("base64: %s\n", base64+base64_len-4);
2005-12-11 20:11:22 +00:00
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 )
{
2013-06-23 02:33:52 +00:00
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 ;
}
2013-06-24 09:04:00 +00:00
//decode a base64 byte to a 0-63 value. Cannot cope with =.
static int Base64_DecodeByte ( char byt )
{
if ( byt > = ' A ' & & byt < = ' Z ' )
return ( byt - ' A ' ) + 0 ;
if ( byt > = ' a ' & & byt < = ' z ' )
return ( byt - ' a ' ) + 26 ;
if ( byt > = ' 0 ' & & byt < = ' 9 ' )
return ( byt - ' 0 ' ) + 52 ;
if ( byt = = ' + ' )
return 62 ;
if ( byt = = ' / ' )
return 63 ;
return - 1 ;
}
int Base64_Decode ( char * out , int outlen , char * src , int srclen )
{
int len = 0 ;
int result ;
//4 input chars give 3 output chars
while ( srclen > = 4 )
{
if ( len + 3 > outlen )
break ;
result = Base64_DecodeByte ( src [ 0 ] ) < < 18 ;
result | = Base64_DecodeByte ( src [ 1 ] ) < < 12 ;
out [ len + + ] = ( result > > 16 ) & 0xff ;
if ( src [ 2 ] ! = ' = ' )
{
result | = Base64_DecodeByte ( src [ 2 ] ) < < 6 ;
out [ len + + ] = ( result > > 8 ) & 0xff ;
if ( src [ 3 ] ! = ' = ' )
{
result | = Base64_DecodeByte ( src [ 3 ] ) < < 0 ;
out [ len + + ] = ( result > > 0 ) & 0xff ;
}
}
if ( result & 0xff000000 )
return 0 ; //some kind of invalid char
src + = 4 ;
srclen - = 4 ;
}
//some kind of error
if ( srclen )
return 0 ;
return len ;
}
2013-06-29 16:01:07 +00:00
void JCL_ForgetBuddyResource ( jclient_t * jcl , buddy_t * buddy , bresource_t * bres )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
bresource_t * * link ;
bresource_t * r ;
for ( link = & buddy - > resources ; * link ; )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
r = * link ;
if ( ! bres | | bres = = r )
{
* link = r - > next ;
free ( r ) ;
if ( bres )
break ;
}
else
link = & r - > next ;
}
}
void JCL_ForgetBuddy ( jclient_t * jcl , buddy_t * buddy , bresource_t * bres )
{
buddy_t * * link ;
buddy_t * b ;
for ( link = & jcl - > buddies ; * link ; )
{
b = * link ;
if ( ! buddy | | buddy = = b )
{
* link = b - > next ;
JCL_ForgetBuddyResource ( jcl , b , bres ) ;
free ( b ) ;
if ( buddy )
break ;
}
else
link = & b - > next ;
2013-06-23 02:33:52 +00:00
}
}
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 ;
}
2013-06-29 16:01:07 +00:00
struct iq_s * JCL_SendIQ ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , char * body )
2013-06-23 02:33:52 +00:00
{
struct iq_s * iq ;
iq = malloc ( sizeof ( * iq ) ) ;
iq - > next = jcl - > pendingiqs ;
jcl - > pendingiqs = iq ;
2013-06-23 18:43:59 +00:00
Q_snprintf ( iq - > id , sizeof ( iq - > id ) , " %i " , rand ( ) ) ;
2013-06-23 02:33:52 +00:00
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> " ) ;
2013-06-29 16:01:07 +00:00
return iq ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
struct iq_s * JCL_SendIQf ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , char * fmt , . . . )
2013-06-23 02:33:52 +00:00
{
va_list argptr ;
char body [ 2048 ] ;
va_start ( argptr , fmt ) ;
2013-06-23 18:43:59 +00:00
Q_vsnprintf ( body , sizeof ( body ) , fmt , argptr ) ;
2013-06-23 02:33:52 +00:00
va_end ( argptr ) ;
2013-06-29 16:01:07 +00:00
return JCL_SendIQ ( jcl , callback , iqtype , target , body ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
struct iq_s * JCL_SendIQNode ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , xmltree_t * node , qboolean destroynode )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
struct iq_s * n ;
2013-06-23 02:33:52 +00:00
char * s = XML_GenerateString ( node ) ;
2013-06-29 16:01:07 +00:00
n = JCL_SendIQ ( jcl , callback , iqtype , target , s ) ;
2013-06-23 02:33:52 +00:00
free ( s ) ;
if ( destroynode )
XML_Destroy ( node ) ;
2013-06-29 16:01:07 +00:00
return n ;
2013-06-23 02:33:52 +00:00
}
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 " , " " ) ;
2013-06-23 18:43:59 +00:00
// char *sub = XML_GetParameter(i, "subscription", "");
2013-06-23 02:33:52 +00:00
JCL_FindBuddy ( jcl , jid , & buddy , NULL ) ;
if ( * name )
Q_strlcpy ( buddy - > name , name , sizeof ( buddy - > name ) ) ;
buddy - > friended = true ;
}
}
2013-06-29 16:01:07 +00:00
static qboolean JCL_RosterReply ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
2013-06-23 02:33:52 +00:00
{
2013-06-23 18:43:59 +00:00
xmltree_t * c ;
2013-06-29 16:01:07 +00:00
//we're probably connected once we've had this reply.
jcl - > status = JCL_ACTIVE ;
JCL_GeneratePresence ( jcl , true ) ;
2013-06-23 02:33:52 +00:00
c = XML_ChildOfTree ( tree , " query " , 0 ) ;
if ( c )
{
JCL_RosterUpdate ( jcl , c ) ;
return true ;
}
return false ;
}
2013-06-29 16:01:07 +00:00
static qboolean JCL_BindReply ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
2013-06-23 02:33:52 +00:00
{
xmltree_t * c ;
c = XML_ChildOfTree ( tree , " bind " , 0 ) ;
if ( c )
{
c = XML_ChildOfTree ( c , " jid " , 0 ) ;
if ( c )
2005-12-11 20:11:22 +00:00
{
2013-06-23 02:33:52 +00:00
Q_strlcpy ( jcl - > jid , c - > body , sizeof ( jcl - > jid ) ) ;
Con_Printf ( " Bound to jid ^[[%s] \\ xmpp \\ %s^] \n " , jcl - > jid , jcl - > jid ) ;
return true ;
2005-12-11 20:11:22 +00:00
}
}
2013-06-23 02:33:52 +00:00
return false ;
}
2013-06-29 16:01:07 +00:00
static qboolean JCL_VCardReply ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
{
xmltree_t * vc , * fn , * nickname ;
vc = XML_ChildOfTree ( tree , " vCard " , 0 ) ;
fn = XML_ChildOfTree ( vc , " FN " , 0 ) ;
nickname = XML_ChildOfTree ( vc , " NICKNAME " , 0 ) ;
if ( nickname & & * nickname - > body )
Q_strlcpy ( jcl - > localalias , nickname - > body , sizeof ( jcl - > localalias ) ) ;
else if ( fn & & * fn - > body )
Q_strlcpy ( jcl - > localalias , fn - > body , sizeof ( jcl - > localalias ) ) ;
return true ;
}
static qboolean JCL_SessionReply ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
2013-06-23 02:33:52 +00:00
{
JCL_SendIQf ( jcl , JCL_RosterReply , " get " , NULL , " <query xmlns='jabber:iq:roster'/> " ) ;
2013-06-29 16:01:07 +00:00
JCL_SendIQf ( jcl , JCL_VCardReply , " get " , NULL , " <vCard xmlns='vcard-temp'/> " ) ;
2013-06-23 02:33:52 +00:00
return true ;
2005-12-11 20:11:22 +00:00
}
2013-06-23 02:33:52 +00:00
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 ,
2013-06-29 16:01:07 +00:00
# ifdef VOIP
" urn:xmpp:jingle:apps:rtp:1 " ,
" urn:xmpp:jingle:apps:rtp:audio " ,
# endif
2013-06-23 02:33:52 +00:00
// "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 )
2005-12-11 20:11:22 +00:00
{
2013-06-23 02:33:52 +00:00
int i ;
Q_strncpyz ( out , " <identity category='client' type='pc' name='FTEQW'/> " , outlen ) ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
for ( i = 0 ; caps [ i ] ; i + + )
2005-12-11 20:11:22 +00:00
{
2013-06-23 02:33:52 +00:00
Q_strlcat ( out , " <feature var=' " , outlen ) ;
Q_strlcat ( out , caps [ i ] , outlen ) ;
Q_strlcat ( out , " '/> " , outlen ) ;
2005-12-11 20:11:22 +00:00
}
}
2013-06-23 02:33:52 +00:00
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 ;
}
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
void JCL_ParseIQ ( jclient_t * jcl , xmltree_t * tree )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
qboolean unparsable = true ;
char * from ;
// char *to;
char * id ;
2005-12-11 20:11:22 +00:00
char * f ;
2013-06-29 16:01:07 +00:00
xmltree_t * ot ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
//FIXME: block from people who we don't know.
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
id = XML_GetParameter ( tree , " id " , " " ) ;
from = XML_GetParameter ( tree , " from " , " " ) ;
// to = XML_GetParameter(tree, "to", "");
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
f = XML_GetParameter ( tree , " type " , " " ) ;
if ( ! strcmp ( f , " get " ) )
2013-03-31 04:21:08 +00:00
{
2013-06-29 16:01:07 +00:00
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 ;
2013-03-31 04:21:08 +00:00
2013-06-29 16:01:07 +00:00
buildcaps ( msg , sizeof ( msg ) ) ;
hash = buildcapshash ( ) ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
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 ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
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 " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
unparsable = false ;
JCL_AddClientMessagef ( jcl ,
" <iq type='error' to='%s' id='%s'> "
" <error type='cancel'> "
" <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> "
" </error> "
" </iq> " , from , id ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
/* else if (from && !strcmp(ot->xmlns, "jabber:iq:last"))
{ //http://xmpp.org/extensions/xep-0012.html
char msg [ 2048 ] ;
int idletime = 0 ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
//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 ) ;
}
*/
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
# 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 ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
# endif
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
ot = XML_ChildOfTree ( tree , " ping " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " urn:xmpp:ping " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl , " <iq type='result' to='%s' id='%s' /> " , from , id ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
if ( unparsable )
{ //unsupported stuff
char msg [ 2048 ] ;
unparsable = false ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
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 ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
}
else if ( ! strcmp ( f , " set " ) )
{
xmltree_t * c ;
c = XML_ChildOfTree ( tree , " query " , 0 ) ;
if ( c & & ! strcmp ( c - > xmlns , " jabber:iq:roster " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
unparsable = false ;
JCL_RosterUpdate ( jcl , c ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
c = XML_ChildOfTree ( tree , " jingle " , 0 ) ;
if ( c & & ! strcmp ( c - > xmlns , " urn:xmpp:jingle:1 " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
unparsable = ! JCL_ParseJingle ( jcl , c , from , id ) ;
}
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
if ( unparsable )
{
char msg [ 2048 ] ;
//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 ) ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
}
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( f , " result " ) | | ! strcmp ( f , " error " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
char * id = XML_GetParameter ( tree , " id " , " " ) ;
struct iq_s * * link , * iq ;
unparsable = false ;
for ( link = & jcl - > pendingiqs ; * link ; link = & ( * link ) - > next )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
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 , iq ) )
{
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 ) ;
}
}
void JCL_ParseMessage ( jclient_t * jcl , xmltree_t * tree )
{
xmltree_t * ot ;
qboolean unparsable = true ;
char * 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 )
{
if ( ! strncmp ( ot - > body , " /me " , 4 ) )
Con_SubPrintf ( f , " *^2%s^7%s \n " , f , ot - > body + 3 ) ;
else
Con_SubPrintf ( f , " ^2%s^7: %s \n " , f , ot - > body ) ;
}
else
Con_Printf ( " NOTICE: %s \n " , 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 ) ;
}
}
}
}
qboolean JCL_ClientDiscoInfo ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
{
xmltree_t * query = XML_ChildOfTree ( tree , " query " , 0 ) ;
xmltree_t * feature ;
char * var ;
int i = 0 ;
unsigned int caps = 0 ;
qboolean rtp = false ;
qboolean rtpaudio = false ;
qboolean quake = false ;
qboolean ice = false ;
qboolean raw = false ;
qboolean jingle = false ;
buddy_t * b ;
bresource_t * r ;
JCL_FindBuddy ( jcl , XML_GetParameter ( tree , " from " , " " ) , & b , & r ) ;
while ( ( feature = XML_ChildOfTree ( query , " feature " , i + + ) ) )
{
var = XML_GetParameter ( feature , " var " , " " ) ;
//check ones we recognise.
if ( ! strcmp ( var , QUAKEMEDIAXMLNS ) )
quake = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:apps:rtp:audio " ) )
rtpaudio = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:apps:rtp:1 " ) )
rtp = true ; //kinda implied, but ensures version is okay
if ( ! strcmp ( var , " urn:xmpp:jingle:transports:ice-udp:1 " ) )
ice = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:transports:raw-udp:1 " ) )
raw = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:1 " ) )
jingle = true ; //kinda implied, but ensures version is okay
}
if ( ( ice | | raw ) & & jingle )
{
if ( rtpaudio & & rtp )
caps | = CAP_VOICE ;
if ( quake )
caps | = CAP_INVITE ;
}
if ( b & & r )
r - > caps = ( r - > caps & CAP_QUERIED ) | caps ;
return true ;
}
void JCL_ParsePresence ( jclient_t * jcl , xmltree_t * tree )
{
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] \\ xmpp \\ %s \\ xmppact \\ pauth^] ^[[Deny] \\ xmpp \\ %s \\ xmppact \\ pdeny^] \n " , from , from , from , from ) ;
}
else if ( type & & ! strcmp ( type , " subscribed " ) )
{
Con_Printf ( " ^[[%s] \\ xmpp \\ %s^] is now your friend! \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 ) ) ;
bres - > caps = 0 ;
}
else
{
Q_strlcpy ( bres - > bstatus , ( show & & * show - > body ) ? show - > body : " present " , sizeof ( bres - > bstatus ) ) ;
if ( ! ( bres - > caps & CAP_QUERIED ) )
{
bres - > caps | = CAP_QUERIED ;
JCL_SendIQ ( jcl , JCL_ClientDiscoInfo , " get " , from , " <query xmlns='http://jabber.org/protocol/disco#info'/> " ) ;
}
}
if ( bres - > servertype = = 2 )
Con_Printf ( " ^[[%s] \\ xmpp \\ %s^] is now ^[[Playing Quake - %s] \\ xmpp \\ %s \\ xmppact \\ join^] \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 ) ;
}
}
}
# define JCL_DONE 0 //no more data available for now.
# define JCL_CONTINUE 1 //more data needs parsing.
# define JCL_KILL 2 //some error, needs reconnecting.
# define JCL_NUKEFROMORBIT 3 //permanent error (or logged on from elsewhere)
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 ; //nothing more this frame
}
if ( ret < 0 )
{
Con_Printf ( " XMPP: 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 ) ;
if ( tree )
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 ;
2005-12-11 20:11:22 +00:00
}
return JCL_DONE ;
}
pos = 0 ;
2013-03-31 04:21:08 +00:00
tree = XML_Parse ( jcl - > bufferedinmessage , & pos , jcl - > instreampos , false , jcl - > defaultnamespace ) ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
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 ;
}
2005-12-11 20:11:22 +00:00
if ( ! tree )
{
// Con_Printf("No input tree: %s", jcl->bufferedinmessage);
return JCL_DONE ;
}
}
2013-03-31 04:21:08 +00:00
// Con_Printf("read\n");
// XML_ConPrintTree(tree, 0);
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
jcl - > timeout = jclient_curtime + 60 * 1000 ;
2005-12-11 20:11:22 +00:00
unparsable = true ;
2013-03-31 04:21:08 +00:00
if ( ! strcmp ( tree - > name , " features " ) )
2005-12-11 20:11:22 +00:00
{
if ( ( ot = XML_ChildOfTree ( tree , " bind " , 0 ) ) )
{
unparsable = false ;
2013-06-23 02:33:52 +00:00
JCL_SendIQf ( jcl , JCL_BindReply , " set " , NULL , " <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>%s</resource></bind> " , jcl - > resource ) ;
2005-12-11 20:11:22 +00:00
}
if ( ( ot = XML_ChildOfTree ( tree , " session " , 0 ) ) )
{
unparsable = false ;
2013-06-23 02:33:52 +00:00
JCL_SendIQf ( jcl , JCL_SessionReply , " set " , NULL , " <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> " ) ;
2013-03-31 04:21:08 +00:00
jcl - > connected = true ;
2013-06-23 02:33:52 +00:00
JCL_WriteConfig ( ) ;
// JCL_AddClientMessageString(jcl, "<iq type='get' to='gmail.com' id='H_2'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
2005-12-11 20:11:22 +00:00
}
if ( unparsable )
{
2013-06-23 18:43:59 +00:00
if ( ( ! jclient - > issecure ) & & BUILTINISVALID ( Net_SetTLSClient ) & & XML_ChildOfTree ( tree , " starttls " , 0 ) ! = NULL & & ! pCvar_GetFloat ( " xmpp_disabletls " ) )
2005-12-11 20:11:22 +00:00
{
2013-03-31 04:21:08 +00:00
Con_Printf ( " Attempting to switch to TLS \n " ) ;
2005-12-11 20:11:22 +00:00
JCL_AddClientMessageString ( jcl , " <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /> " ) ;
unparsable = false ;
}
else if ( ( ot = XML_ChildOfTree ( tree , " mechanisms " , 0 ) ) )
{
2013-06-24 09:04:00 +00:00
qboolean canplain = false ;
// qboolean canmd5 = false;
// qboolean canscramsha1 = false;
// qboolean canxoath2 = false;
2005-12-11 20:11:22 +00:00
for ( ot = ot - > child ; ot ; ot = ot - > sibling )
{
if ( ! strcmp ( ot - > body , " PLAIN " ) )
2013-06-24 09:04:00 +00:00
canplain = true ;
// else if (!strcmp(ot->body, "SCRAM-SHA-1"))
// cansha1 = true;
// else if (!strcmp(ot->body, "DIGEST-MD5"))
// canmd5 = true;
// else if (!strcmp(ot->body, "X-OAUTH2"))
// canxoath2 = true;
// else
// Con_Printf("Unknown auth method \'%s\'\n", ot->body);
}
/*
if ( canscramsha1 )
{
Con_Printf ( " Using scram-sha-1%s \n " , jclient - > issecure ? " over tls/ssl " : " " ) ;
strcpy ( jcl - > authnonce , " abcdefghijklmnopqrstuvwxyz " ) ; //FIXME: should be random
Base64_Add ( " n,,n= " , 5 ) ;
Base64_Add ( jclient - > username , strlen ( jcl - > username ) ) ;
Base64_Add ( " ,r= " , 3 ) ;
Base64_Add ( jcl - > authnonce , strlen ( jcl - > authnonce ) ) ; //must be random ascii.
Base64_Finish ( ) ;
JCL_AddClientMessagef ( jcl , " <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>%s</auth> " , base64 ) ;
unparsable = false ;
}
else
*/
if ( canplain & & ( jclient - > issecure | | pCvar_GetFloat ( " xmpp_allowplainauth " ) ) )
{
//plain can still be read with man-in-the-middle attacks, of course, even with tls if the certificate is spoofed, so this should always be the lowest priority.
//we just hope that the tls certificate cannot be spoofed.
Con_Printf ( " Using plain auth%s \n " , jclient - > issecure ? " over tls/ssl " : " " ) ;
Base64_Add ( " " , 1 ) ;
Base64_Add ( jclient - > username , strlen ( jcl - > username ) ) ;
Base64_Add ( " " , 1 ) ;
Base64_Add ( jcl - > password , strlen ( jcl - > password ) ) ;
Base64_Finish ( ) ;
JCL_AddClientMessagef ( jcl , " <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth> " , base64 ) ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
}
2013-06-24 09:04:00 +00:00
else
2005-12-11 20:11:22 +00:00
{
2013-06-24 09:04:00 +00:00
Con_Printf ( " XMPP: No suitable auth methods. Unable to connect. \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
XML_Destroy ( tree ) ;
return JCL_KILL ;
2005-12-11 20:11:22 +00:00
}
}
else //we cannot auth, no suitable method.
{
2013-06-24 09:04:00 +00:00
Con_Printf ( " XMPP: Neither SASL or TLS are usable \n " ) ;
XML_Destroy ( tree ) ;
return JCL_KILL ;
2005-12-11 20:11:22 +00:00
}
}
}
2013-06-24 09:04:00 +00:00
/*
else if ( ! strcmp ( tree - > name , " challenge " ) & & ! strcmp ( tree - > xmlns , " urn:ietf:params:xml:ns:xmpp-sasl " ) )
{
//sasl SCRAM-SHA-1 challenge
//send back the same 'r' attribute
buf saslchal ;
int l , i ;
buf salt ;
buf csn ;
buf itr ;
buf final ;
buf sigkey ;
char salted_password [ 20 ] ;
char proof [ 20 ] ;
char proof64 [ 30 ] ;
char clientkey [ 20 ] ;
char storedkey [ 20 ] ;
void hmacsha1 ( char * out , char * key , int keysize , char * data , int datalen ) ;
void Hi ( char * out , char * password , buf salt , int i ) ;
saslchal . len = Base64_Decode ( saslchal . buf , sizeof ( saslchal . buf ) , tree - > body , strlen ( tree - > body ) ) ;
//be warned, these CAN contain nulls.
csn = saslattr ( & saslchal , ' r ' ) ;
salt = saslattr ( & saslchal , ' s ' ) ;
itr = saslattr ( & saslchal , ' i ' ) ;
//this is the first part of the message we're about to send, with no proof.
//c(channel) is mandatory but nulled and forms part of the hash
final . len = 0 ;
buf_cat ( & final , " c= " , 2 ) ;
Base64_Add ( " n,, " , 3 ) ) ;
Base64_Finish ( ) ;
final . cat ( & final , base64 , strlen ( base64 ) ) ;
final . cat ( & final , " r= " , 2 ) ;
final . cat ( & final , csn . buf , csn . len ) ;
//our original message + ',' + challenge + ',' + the message we're about to send.
sigkey . len = 0 ;
buf_cat ( & sigkey , " n,,n= " , 5 ) ;
buf_cat ( & sigkey , jcl - > username , strlen ( jcl - > username ) ) ;
buf_cat ( & sigkey , " r= " , 2 ) ;
buf_cat ( & sigkey , jcl - > authnonce , strlen ( jcl - > authnonce ) ) ;
buf_cat ( & sigkey , " , " , 1 ) ;
buf_cat ( & sigkey , saslchal . buf , saslchal . len ) ;
buf_cat ( & sigkey , " , " , 1 ) ;
buf_cat ( & sigkey , final . buf , final . len ) ;
Hi ( salted_password , password , salt , atoi ( itr ) ) ;
hmacsha1 ( clientkey , salted_password , sizeof ( salted_password ) , " Client Key " , strlen ( " Client Key " ) ) ;
storedkey = sha1 ( clientkey , sizeof ( clientkey ) ) ;
hmacsha1 ( clientsignature , storedkey , sizeof ( storedkey ) , sigkey . buf , sigkey . len ) ;
for ( i = 0 ; i < sizeof ( proof ) ; i + + )
proof [ i ] = clientkey [ i ] ^ clientsignature [ i ] ;
Base64_Add ( proof , sizeof ( proof ) ) ;
Base64_Finish ( ) ;
strcpy ( proof64 , base64 ) ;
Base64_Add ( final , buflen ( final ) ) ;
Base64_Add ( " ,p= " , 3 ) ;
Base64_Add ( proof64 , strlen ( proof64 ) ) ;
Base64_Finish ( ) ;
JCL_AddClientMessagef ( jcl , " <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response> " , base64 ) ;
}
*/
2005-12-11 20:11:22 +00:00
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 ) )
{
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: proceed without TLS \n " ) ;
2013-03-31 04:21:08 +00:00
XML_Destroy ( tree ) ;
2005-12-11 20:11:22 +00:00
return JCL_KILL ;
}
2013-06-23 18:43:59 +00:00
if ( pNet_SetTLSClient ( jcl - > socket , jcl - > domain ) < 0 )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: failed to switch to TLS \n " ) ;
2013-03-31 04:21:08 +00:00
XML_Destroy ( tree ) ;
2005-12-11 20:11:22 +00:00
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'> " ) ;
2013-03-31 04:21:08 +00:00
XML_Destroy ( tree ) ;
2005-12-11 20:11:22 +00:00
return JCL_DONE ;
}
else if ( ! strcmp ( tree - > name , " failure " ) )
{
if ( tree - > child )
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: Failure: %s \n " , tree - > child - > name ) ;
2005-12-11 20:11:22 +00:00
else
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: Unknown failure \n " ) ;
2013-03-31 04:21:08 +00:00
XML_Destroy ( tree ) ;
2005-12-11 20:11:22 +00:00
return JCL_KILL ;
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( tree - > name , " error " ) )
{
ot = XML_ChildOfTree ( tree , " text " , 0 ) ;
if ( ot )
Con_Printf ( " XMPP: %s \n " , ot - > body ) ;
else
Con_Printf ( " XMPP: Unknown error \n " ) ;
ot = XML_ChildOfTree ( tree , " conflict " , 0 ) ;
XML_Destroy ( tree ) ;
if ( ot )
return JCL_NUKEFROMORBIT ;
else
return JCL_KILL ;
}
2005-12-11 20:11:22 +00:00
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 " ) )
{
2013-06-29 16:01:07 +00:00
JCL_ParseIQ ( jcl , tree ) ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
}
else if ( ! strcmp ( tree - > name , " message " ) )
{
2013-06-29 16:01:07 +00:00
JCL_ParseMessage ( jcl , tree ) ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
}
else if ( ! strcmp ( tree - > name , " presence " ) )
{
2013-06-29 16:01:07 +00:00
JCL_ParsePresence ( jcl , tree ) ;
2005-12-11 20:11:22 +00:00
//we should keep a list of the people that we know of.
unparsable = false ;
}
else
2013-06-23 02:33:52 +00:00
{
2005-12-11 20:11:22 +00:00
Con_Printf ( " JCL unrecognised stanza: %s \n " , tree - > name ) ;
2013-06-23 02:33:52 +00:00
XML_ConPrintTree ( tree , 0 ) ;
}
2005-12-11 20:11:22 +00:00
XML_Destroy ( tree ) ;
memmove ( jcl - > bufferedinmessage , jcl - > bufferedinmessage + pos , jcl - > bufferedinammount - pos ) ;
jcl - > bufferedinammount - = pos ;
jcl - > instreampos - = pos ;
if ( unparsable )
{
2013-06-29 16:01:07 +00:00
Con_Printf ( " XMPP: Input corrupt, urecognised, or unusable. Disconnecting. \n " ) ;
2005-12-11 20:11:22 +00:00
return JCL_KILL ;
}
return JCL_CONTINUE ;
}
2013-06-29 16:01:07 +00:00
void JCL_CloseConnection ( jclient_t * jcl , qboolean reconnect )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
//send our signoff to the server, if we're still alive.
Con_Printf ( " XMPP: Disconnected from %s@%s \n " , jcl - > username , jcl - > domain ) ;
if ( jcl - > status = = JCL_ACTIVE )
JCL_AddClientMessageString ( jcl , " <presence type='unavailable'/> " ) ;
2005-12-11 20:11:22 +00:00
JCL_AddClientMessageString ( jcl , " </stream:stream> " ) ;
2013-06-29 16:01:07 +00:00
JCL_FlushOutgoing ( jcl ) ;
//forget all our friends.
JCL_ForgetBuddy ( jcl , NULL , NULL ) ;
//destroy any data that never got sent
free ( jcl - > outbuf ) ;
jcl - > outbuf = NULL ;
jcl - > outbuflen = 0 ;
jcl - > outbufpos = 0 ;
jcl - > outbufmax = 0 ;
2013-06-23 18:43:59 +00:00
pNet_Close ( jcl - > socket ) ;
2013-06-29 16:01:07 +00:00
jcl - > socket = - 1 ;
jcl - > status = JCL_DEAD ;
jcl - > timeout = jclient_curtime + 30 * 1000 ; //wait 30 secs before reconnecting, to avoid flood-prot-protection issues.
if ( ! reconnect )
{
free ( jcl ) ;
if ( jclient = = jcl )
jclient = NULL ;
}
2005-12-11 20:11:22 +00:00
}
2013-06-23 02:33:52 +00:00
//can be polled for server address updates
2013-06-29 16:01:07 +00:00
void JCL_GeneratePresence ( jclient_t * jcl , qboolean force )
2013-06-23 02:33:52 +00:00
{
int dummystat ;
char serveraddr [ 1024 * 16 ] ;
char servermap [ 1024 * 16 ] ;
//get the last server address
serveraddr [ 0 ] = 0 ;
servermap [ 0 ] = 0 ;
2013-06-23 18:43:59 +00:00
if ( ! pCvar_GetFloat ( " xmpp_nostatus " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-23 18:43:59 +00:00
if ( pCvar_GetFloat ( " sv.state " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-23 18:43:59 +00:00
pCvar_GetString ( " sv.mapname " , servermap , sizeof ( servermap ) ) ;
2013-06-23 02:33:52 +00:00
}
else
{
2013-06-23 18:43:59 +00:00
if ( ! pCvar_GetString ( " cl_serveraddress " , serveraddr , sizeof ( serveraddr ) ) )
2013-06-23 02:33:52 +00:00
serveraddr [ 0 ] = 0 ;
if ( BUILTINISVALID ( CL_GetStats ) )
{
//if we can't get any stats, its because we're not actually on the server.
2013-06-23 18:43:59 +00:00
if ( ! pCL_GetStats ( 0 , & dummystat , 1 ) )
2013-06-23 02:33:52 +00:00
serveraddr [ 0 ] = 0 ;
}
}
}
2013-06-29 16:01:07 +00:00
if ( force | | strcmp ( jcl - > curquakeserver , * servermap ? servermap : serveraddr ) )
2013-06-23 02:33:52 +00:00
{
char caps [ 256 ] ;
2013-06-29 16:01:07 +00:00
Q_strlcpy ( jcl - > curquakeserver , * servermap ? servermap : serveraddr , sizeof ( jcl - > curquakeserver ) ) ;
Con_DPrintf ( " Sending presence %s \n " , jcl - > curquakeserver ) ;
2013-06-23 02:33:52 +00:00
//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 ( ) ) ;
2013-06-29 16:01:07 +00:00
if ( ! * jcl - > curquakeserver )
JCL_AddClientMessagef ( jcl ,
2013-06-23 02:33:52 +00:00
" <presence> "
" %s "
" </presence> " , caps ) ;
else if ( * servermap ) //if we're running a server, say so
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl ,
2013-06-23 02:33:52 +00:00
" <presence> "
" <quake xmlns='fteqw.com:game' servermap='%s'/> "
" %s "
" </presence> "
, servermap , caps ) ;
else //if we're connected to a server, say so
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl ,
2013-06-23 02:33:52 +00:00
" <presence> "
" <quake xmlns='fteqw.com:game' serverip='%s' /> "
" %s "
" </presence> "
2013-06-29 16:01:07 +00:00
, jcl - > curquakeserver , caps ) ;
2013-06-23 02:33:52 +00:00
}
}
2005-12-11 20:11:22 +00:00
//functions above this line allow connections to multiple servers.
//it is just the control functions that only allow one server.
2013-03-31 04:21:08 +00:00
qintptr_t JCL_Frame ( qintptr_t * args )
2005-12-11 20:11:22 +00:00
{
int stat = JCL_CONTINUE ;
2013-06-29 16:01:07 +00:00
jclient_t * jcl = jclient ;
jclient_curtime = args [ 0 ] ;
if ( jcl )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
JCL_JingleTimeouts ( jcl , false ) ;
if ( jcl - > status = = JCL_DEAD )
2013-03-31 04:21:08 +00:00
{
2013-06-29 16:01:07 +00:00
if ( jclient_curtime > jcl - > timeout )
{
JCL_Reconnect ( jcl ) ;
jcl - > timeout = jclient_curtime + 60 * 1000 ;
}
2013-03-31 04:21:08 +00:00
}
2013-06-29 16:01:07 +00:00
else
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
if ( jcl - > connected )
JCL_GeneratePresence ( jcl , false ) ;
while ( stat = = JCL_CONTINUE )
stat = JCL_ClientFrame ( jcl ) ;
if ( stat = = JCL_NUKEFROMORBIT )
JCL_CloseConnection ( jcl , false ) ;
else if ( stat = = JCL_KILL )
JCL_CloseConnection ( jcl , true ) ;
else
JCL_FlushOutgoing ( jcl ) ;
2005-12-11 20:11:22 +00:00
}
}
return 0 ;
}
2013-06-23 02:33:52 +00:00
void JCL_WriteConfig ( void )
{
if ( jclient - > connected )
{
qhandle_t config ;
2013-06-23 18:43:59 +00:00
pFS_Open ( " **plugconfig " , & config , 2 ) ;
2013-06-23 02:33:52 +00:00
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 ) ;
2013-06-23 18:43:59 +00:00
pFS_Write ( config , buffer , strlen ( buffer ) ) ;
pFS_Close ( config ) ;
2013-06-23 02:33:52 +00:00
}
}
}
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 ;
2013-06-23 18:43:59 +00:00
len = pFS_Open ( " **plugconfig " , & config , 1 ) ;
2013-06-23 02:33:52 +00:00
if ( config > = 0 )
{
if ( len > = sizeof ( buf ) )
len = sizeof ( buf ) - 1 ;
buf [ len ] = 0 ;
2013-06-23 18:43:59 +00:00
pFS_Read ( config , buf , len ) ;
pFS_Close ( config ) ;
2013-06-23 02:33:52 +00:00
2013-06-23 18:43:59 +00:00
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 ) ) ;
2013-06-23 02:33:52 +00:00
oldtls = atoi ( tls ) ;
2013-06-29 16:01:07 +00:00
jclient = JCL_Connect ( server , oldtls , account , password ) ;
2013-06-23 02:33:52 +00:00
}
}
}
2013-06-29 16:01:07 +00:00
static void JCL_PrintBuddyStatus ( char * console , buddy_t * b , bresource_t * r )
{
if ( r - > servertype = = 2 )
Con_SubPrintf ( console , " ^[[Playing Quake - %s] \\ xmpp \\ %s/%s \\ xmppact \\ join^] " , r - > server , b - > accountdomain , r - > resource ) ;
else if ( r - > servertype )
Con_SubPrintf ( console , " ^[[Playing Quake - %s] \\ observe \\ %s^] " , r - > server , r - > server ) ;
else if ( * r - > fstatus )
Con_SubPrintf ( console , " %s - %s " , r - > bstatus , r - > fstatus ) ;
else
Con_SubPrintf ( console , " %s " , r - > bstatus ) ;
if ( ( r - > caps & CAP_INVITE ) & & ! r - > servertype )
Con_SubPrintf ( console , " ^[[Invite] \\ xmpp \\ %s/%s \\ xmppact \\ invite^] " , b - > accountdomain , r - > resource ) ;
if ( r - > caps & CAP_VOICE )
Con_SubPrintf ( console , " ^[[Call] \\ xmpp \\ %s/%s \\ xmppact \\ call^] " , b - > accountdomain , r - > resource ) ;
}
2013-06-23 02:33:52 +00:00
void JCL_PrintBuddyList ( char * console , jclient_t * jcl , qboolean all )
{
buddy_t * b ;
bresource_t * r ;
2013-06-29 16:01:07 +00:00
struct c2c_s * c2c ;
2013-06-23 02:33:52 +00:00
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 )
{
2013-06-29 16:01:07 +00:00
Con_SubPrintf ( console , " ^[[%s] \\ xmpp \\ %s/%s^]: " , r - > resource , b - > accountdomain , r - > resource ) ;
JCL_PrintBuddyStatus ( console , b , r ) ;
Con_SubPrintf ( console , " \n " ) ;
2013-06-23 02:33:52 +00:00
}
}
else //only one resource
{
r = b - > resources ;
2013-06-29 16:01:07 +00:00
Con_SubPrintf ( console , " ^[[%s] \\ xmpp \\ %s/%s^]: " , b - > name , b - > accountdomain , r - > resource ) ;
JCL_PrintBuddyStatus ( console , b , r ) ;
Con_SubPrintf ( console , " \n " ) ;
}
}
if ( jcl - > c2c )
Con_SubPrintf ( console , " Active sessions: \n " ) ;
for ( c2c = jcl - > c2c ; c2c ; c2c = c2c - > next )
{
JCL_FindBuddy ( jcl , c2c - > with , & b , & r ) ;
switch ( c2c - > mediatype )
{
case ICEP_VOICE :
Con_SubPrintf ( console , " ^[[%s] \\ xmpp \\ %s/%s^]: voice ^[[Hang Up] \\ xmppact \\ jdeny \\ xmpp \\ %s \\ xmppsid \\ %s^] \n " , b - > name , b - > accountdomain , r - > resource , c2c - > with , c2c - > sid ) ;
break ;
case ICEP_QWSERVER :
Con_SubPrintf ( console , " ^[[%s] \\ xmpp \\ %s/%s^]: server ^[[Kick] \\ xmppact \\ jdeny \\ xmpp \\ %s \\ xmppsid \\ %s^] \n " , b - > name , b - > accountdomain , r - > resource , c2c - > with , c2c - > sid ) ;
break ;
case ICEP_QWCLIENT :
Con_SubPrintf ( console , " ^[[%s] \\ xmpp \\ %s/%s^]: client ^[[Disconnect] \\ xmppact \\ jdeny \\ xmpp \\ %s \\ xmppsid \\ %s^] \n " , b - > name , b - > accountdomain , r - > resource , c2c - > with , c2c - > sid ) ;
break ;
2013-06-23 02:33:52 +00:00
}
}
}
void JCL_SendMessage ( jclient_t * jcl , char * to , char * msg )
{
2013-06-29 16:01:07 +00:00
char markup [ 1024 ] ;
2013-06-23 02:33:52 +00:00
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> " ) ;
2013-06-29 16:01:07 +00:00
if ( ! strncmp ( msg , " /me " , 4 ) )
Con_SubPrintf ( b - > name , " *^5%s^7 " COLOURYELLOW " %s \n " , ( ( ! strcmp ( jcl - > localalias , " >> " ) ) ? " me " : jcl - > localalias ) , msg + 3 ) ;
else
Con_SubPrintf ( b - > name , " ^5%s^7: " COLOURYELLOW " %s \n " , jcl - > localalias , msg ) ;
2013-06-23 02:33:52 +00:00
}
void JCL_Command ( char * console )
2005-12-11 20:11:22 +00:00
{
char imsg [ 8192 ] ;
char arg [ 6 ] [ 256 ] ;
char * msg ;
int i ;
2013-06-23 18:43:59 +00:00
pCmd_Args ( imsg , sizeof ( imsg ) ) ;
2005-12-11 20:11:22 +00:00
msg = imsg ;
for ( i = 0 ; i < 6 ; i + + )
{
if ( ! msg )
continue ;
2013-06-23 18:43:59 +00:00
msg = JCL_ParseOut ( msg , arg [ i ] , sizeof ( arg [ i ] ) ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
if ( arg [ 0 ] [ 0 ] = = ' / ' & & arg [ 0 ] [ 1 ] ! = ' / ' & & strcmp ( arg [ 0 ] + 1 , " me " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
if ( ! strcmp ( arg [ 0 ] + 1 , " open " ) | | ! strcmp ( arg [ 0 ] + 1 , " connect " ) | | ! strcmp ( arg [ 0 ] + 1 , " tlsopen " ) | | ! strcmp ( arg [ 0 ] + 1 , " tlsconnect " ) )
2013-06-23 02:33:52 +00:00
{ //tlsconnect is 'old'.
2009-04-19 01:48:10 +00:00
if ( ! * arg [ 1 ] )
{
2013-06-29 16:01:07 +00:00
Con_SubPrintf ( console , " %s <account@domain/resource> <password> <server> \n " , arg [ 0 ] + 1 ) ;
2009-04-19 01:48:10 +00:00
return ;
}
2005-12-11 20:11:22 +00:00
if ( jclient )
{
2013-06-23 02:33:52 +00:00
Con_TrySubPrint ( console , " You are already connected \n Please /quit first \n " ) ;
2005-12-11 20:11:22 +00:00
return ;
}
2013-06-29 16:01:07 +00:00
jclient = JCL_Connect ( arg [ 3 ] , ! strncmp ( arg [ 0 ] + 1 , " tls " , 3 ) , arg [ 1 ] , arg [ 2 ] ) ;
2009-04-19 01:48:10 +00:00
if ( ! jclient )
{
2013-06-23 02:33:52 +00:00
Con_TrySubPrint ( console , " Connect failed \n " ) ;
2009-04-19 01:48:10 +00:00
return ;
}
2005-12-11 20:11:22 +00:00
}
2013-06-23 02:33:52 +00:00
else if ( ! strcmp ( arg [ 0 ] + 1 , " help " ) )
{
2013-06-29 16:01:07 +00:00
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /connect XMPPSERVER USERNAME@DOMAIN/RESOURCE PASSWORD^] \n " ) ;
2013-06-23 02:33:52 +00:00
if ( BUILTINISVALID ( Net_SetTLSClient ) )
2013-06-29 16:01:07 +00:00
{
Con_Printf ( " eg for gmail: ^[/ " COMMANDPREFIX " /connect myusername@gmail.com mypassword talk.google.com^] \n " ) ;
Con_Printf ( " eg for facebook: ^[/ " COMMANDPREFIX " /connect myusername@chat.facebook.com mypassword chat.facebook.com^] \n " ) ;
}
Con_Printf ( " Note that this info will be used the next time you start quake. \n " ) ;
2013-06-23 02:33:52 +00:00
//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 " ) ;
2013-06-29 16:01:07 +00:00
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /join accountname^] \n "
" Joins your friends game (they will be prompted). \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /invite accountname^] \n "
" Invite someone to join your game (they will be prompted). \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /voice accountname^] \n "
" Begin a bi-directional peer-to-peer voice conversation with someone (they will be prompted). \n " ) ;
2013-06-23 02:33:52 +00:00
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 ( ! strcmp ( arg [ 0 ] + 1 , " clear " ) )
{
//just clears the current console.
if ( * console )
{
2013-06-23 18:43:59 +00:00
pCon_Destroy ( console ) ;
Con_SubPrintf ( console , " " ) ;
pCon_SetActive ( console ) ;
2013-06-23 02:33:52 +00:00
}
else
2013-06-23 18:43:59 +00:00
pCmd_AddText ( " \n clear \n " , true ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else if ( ! jclient )
{
Con_SubPrintf ( console , " No account specified. Cannot %s \n " , arg [ 0 ] ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " quit " ) )
{
//disconnect from the xmpp server.
JCL_CloseConnection ( jclient , false ) ;
}
else if ( jclient - > status ! = JCL_ACTIVE )
{
Con_SubPrintf ( console , " You are not authed. Please wait. \n " , arg [ 0 ] ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " blist " ) )
{
//print out a full list of everyone, even those offline.
JCL_PrintBuddyList ( console , jclient , true ) ;
}
2005-12-11 20:11:22 +00:00
else if ( ! strcmp ( arg [ 0 ] + 1 , " msg " ) )
{
2013-06-23 02:33:52 +00:00
//FIXME: validate the dest. deal with xml markup in dest.
2013-05-11 14:02:55 +00:00
Q_strlcpy ( jclient - > defaultdest , arg [ 1 ] , sizeof ( jclient - > defaultdest ) ) ;
2005-12-11 20:11:22 +00:00
msg = arg [ 2 ] ;
2013-06-23 02:33:52 +00:00
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 ] ) ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
//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 " ) )
{
2013-06-29 16:01:07 +00:00
JCL_Join ( jclient , * arg [ 1 ] ? arg [ 1 ] : console , NULL , true , ICEP_QWCLIENT ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " invite " ) )
{
JCL_Join ( jclient , * arg [ 1 ] ? arg [ 1 ] : console , NULL , true , ICEP_QWSERVER ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " voice " ) | | ! strcmp ( arg [ 0 ] + 1 , " call " ) )
{
JCL_Join ( jclient , * arg [ 1 ] ? arg [ 1 ] : console , NULL , true , ICEP_VOICE ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " kick " ) )
{
JCL_Join ( jclient , * arg [ 1 ] ? arg [ 1 ] : console , NULL , false , ICEP_INVALID ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-23 02:33:52 +00:00
else if ( ! strcmp ( arg [ 0 ] + 1 , " raw " ) )
2013-03-31 04:21:08 +00:00
{
2013-06-23 02:33:52 +00:00
jclient - > streamdebug = true ;
2013-03-31 04:21:08 +00:00
JCL_AddClientMessageString ( jclient , arg [ 1 ] ) ;
}
2005-12-11 20:11:22 +00:00
else
2013-06-23 02:33:52 +00:00
Con_SubPrintf ( console , " Unrecognised command: %s \n " , arg [ 0 ] ) ;
2005-12-11 20:11:22 +00:00
}
else
{
if ( jclient )
{
msg = imsg ;
2013-06-23 02:33:52 +00:00
if ( ! * msg )
{
if ( ! * console )
{
JCL_PrintBuddyList ( console , jclient , false ) ;
2013-06-29 16:01:07 +00:00
//Con_TrySubPrint(console, "For help, type \"^[/" COMMANDPREFIX " /help^]\"\n");
2013-06-23 02:33:52 +00:00
}
}
else
{
JCL_SendMessage ( jclient , jclient - > defaultdest , msg ) ;
}
2005-12-11 20:11:22 +00:00
}
else
2007-09-17 20:55:15 +00:00
{
2013-06-23 02:33:52 +00:00
Con_TrySubPrint ( console , " Not connected. For help, type \" ^[/ " COMMANDPREFIX " /help^] \" \n " ) ;
2007-09-17 20:55:15 +00:00
}
2005-12-11 20:11:22 +00:00
}
}