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
2013-07-13 12:14:32 +00:00
/*
Network limitations :
googletalk :
username : same as gmail ( foobar @ gmail . com ) .
FIXME : need to test foobar @ googlemail . com
auth mechanism : oauth2 ( tls + nontls ) or plain ( tls - only ) . no digests supported , so mitm can easily grab your password if they use certificate authority hackery , so DO NOT log in from work .
oauth2 : I ' ve registered a clientid for use with googletalk ' s network , but the whole web - browser - is - required crap makes it near unusable . We ' ll try it if they omit a password .
otherwise a complete implementation .
facebook :
username : foobar @ chat . facebook . com
auth mechanism : digest - md5 , x - facebook - platform .
gateway implementation : no arbitary iq support ( no invite / join / voice ) .
no roster control
completely untested . I ' ve no interest in signing up to be tracked constantly ( but somehow google is okay . . . go figure . . . I guess I ' m just trying to avoid a double - whammy )
oauth2 : no idea where to register a clientid , or what the correct addresses are . a google search implies they don ' t do refresh tokens properly . sticking with digest - md5 should work .
msn :
username : foobar @ messenger . live . com ( NOT foobar @ live . com - this will timeout )
auth mechansim : x - messenger - oath ONLY
non - standard unusable crap .
uses incorrect certificates . any client that doesn ' t warn about that is buggy as fuck .
probably doesn ' t have iq support , no idea , can ' t log in to test that
requires annoying see - other - host redirection .
no roster control
stun servers are listed in srv records for live . com and messanger . live . com but not messenger . live . com . retards .
oauth2 : too lazy to register a clientid . stupid crap . I hate having to register everywhere .
ejabberd :
auth mechanism : digest - md5 , scram - sha1 , plain .
complete implementation . no issues .
may be lacking srv entries , depends on installation .
2013-07-14 16:39:21 +00:00
may have self - signed certificate issues , depends on installation .
2013-07-13 12:14:32 +00:00
client compat :
googletalk :
implements old version of jingle . voice calls not compatible .
not tested by me .
pidgin :
( linux ) has issues with jingle + ice , and can be made to crash . voip uses speex . pidgin ' s ice seems vulnerable to dropped packets .
( windows ) doesn ' t support voice calls
otherwise works .
*/
2005-12-11 20:11:22 +00:00
# include "../plugin.h"
2013-03-31 04:21:08 +00:00
# include <time.h>
2013-08-21 07:42:42 +00:00
# include "xmpp.h"
2013-06-23 02:33:52 +00:00
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
2013-06-29 16:01:07 +00:00
icefuncs_t * piceapi ;
2013-07-13 12:14:32 +00:00
# endif
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-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
2013-08-21 07:42:42 +00:00
static char * JCL_ParseOut ( char * data , char * buf , int bufsize ) //GPL: 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-08-21 07:42:42 +00:00
//GPL: ripped from quake
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-07-13 12:14:32 +00:00
# ifdef _WIN32
# include "windns.h"
static DNS_STATUS ( WINAPI * pDnsQuery_UTF8 ) ( PCSTR pszName , WORD wType , DWORD Options , PIP4_ARRAY aipServers , PDNS_RECORD * ppQueryResults , PVOID * pReserved ) ;
static VOID ( WINAPI * pDnsRecordListFree ) ( PDNS_RECORD pRecordList , DNS_FREE_TYPE FreeType ) ;
static HMODULE dnsapi_lib ;
qboolean NET_DNSLookup_SRV ( char * host , char * out , int outlen )
{
HRESULT hr ;
DNS_RECORD * result = NULL ;
if ( ! dnsapi_lib )
{
dnsapi_lib = LoadLibrary ( " dnsapi.dll " ) ;
pDnsQuery_UTF8 = ( void * ) GetProcAddress ( dnsapi_lib , " DnsQuery_UTF8 " ) ;
pDnsRecordListFree = ( void * ) GetProcAddress ( dnsapi_lib , " DnsRecordListFree " ) ;
}
//win98?
if ( ! pDnsQuery_UTF8 | | ! pDnsRecordListFree )
return false ;
//do lookup
hr = pDnsQuery_UTF8 ( host , DNS_TYPE_SRV , DNS_QUERY_STANDARD , NULL , & result , NULL ) ;
if ( result )
{
Q_snprintf ( out , outlen , " [%s]:%i " , result - > Data . SRV . pNameTarget , result - > Data . SRV . wPort ) ;
pDnsRecordListFree ( result , DnsFreeRecordList ) ;
return true ;
}
return false ;
}
# else
qboolean NET_DNSLookup_SRV ( char * host , char * out , int outlen )
{
return false ;
}
# endif
char base64 [ ( ( 4096 + 3 ) * 4 / 3 ) + 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 + 4 > = sizeof ( base64 ) - 1 )
return ;
base64_cur | = byt < < ( 16 - base64_bits ) ; //first byte fills highest bits
base64_bits + = 8 ;
if ( base64_bits = = 24 )
{
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 18 ) & 63 ) ;
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 12 ) & 63 ) ;
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 6 ) & 63 ) ;
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 0 ) & 63 ) ;
base64 [ base64_len ] = ' \0 ' ;
// Con_Printf("base64: %s\n", base64+base64_len-4);
base64_bits = 0 ;
base64_cur = 0 ;
}
}
void Base64_Add ( char * s , int len )
{
unsigned char * us = ( unsigned char * ) s ;
while ( len - - > 0 )
Base64_Byte ( * us + + ) ;
}
void Base64_Finish ( void )
{
//output is always a multiple of four
//0(0)->0(0)
//1(8)->2(12)
//2(16)->3(18)
//3(24)->4(24)
if ( base64_bits ! = 0 )
{
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 18 ) & 63 ) ;
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 12 ) & 63 ) ;
if ( base64_bits = = 8 )
{
base64 [ base64_len + + ] = ' = ' ;
base64 [ base64_len + + ] = ' = ' ;
}
else
{
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 6 ) & 63 ) ;
if ( base64_bits = = 16 )
base64 [ base64_len + + ] = ' = ' ;
else
base64 [ base64_len + + ] = Base64_From64 ( ( base64_cur > > 0 ) & 63 ) ;
}
}
base64 [ base64_len + + ] = ' \0 ' ;
base64_len = 0 ; //for next time (use strlen)
base64_bits = 0 ;
base64_cur = 0 ;
}
//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 ;
}
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
void RenameConsole ( char * totrim ) ;
2013-07-13 12:14:32 +00:00
void JCL_Command ( int accid , char * consolename ) ;
2013-06-23 02:33:52 +00:00
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 ] )
2013-07-13 12:14:32 +00:00
JCL_Command ( 0 , " " ) ;
return true ;
}
if ( ! strncmp ( cmd , COMMANDPREFIX , strlen ( COMMANDPREFIX ) ) )
{
if ( ! args [ 0 ] )
JCL_Command ( atoi ( cmd + strlen ( COMMANDPREFIX ) ) , " " ) ;
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 ) ;
2013-07-13 12:14:32 +00:00
qintptr_t JCL_Shutdown ( 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 ) & &
2013-07-13 12:14:32 +00:00
Plug_Export ( " Shutdown " , JCL_Shutdown ) & &
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-07-13 12:14:32 +00:00
pCmd_AddCommand ( COMMANDPREFIX " 0 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 1 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 2 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 3 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 4 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 5 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 6 " ) ;
pCmd_AddCommand ( COMMANDPREFIX " 7 " ) ;
2013-06-29 16:01:07 +00:00
//flags&1 == archive
pCvar_Register ( " xmpp_nostatus " , " 0 " , 0 , " xmpp " ) ;
2013-08-21 07:42:42 +00:00
pCvar_Register ( " xmpp_hidestatusupdates " , " 0 " , 0 , " xmpp " ) ;
2013-06-29 16:01:07 +00:00
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 " ) ;
2013-06-23 02:33:52 +00:00
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
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-07-13 12:14:32 +00:00
# endif
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-07-13 12:14:32 +00:00
jclient_t * jclients [ 8 ] ;
2013-06-29 16:01:07 +00:00
int jclient_curtime ;
2013-07-13 12:14:32 +00:00
int jclient_poketime ;
typedef struct
{
char * method ;
int ( * sasl_initial ) ( jclient_t * jcl , char * buf , int bufsize ) ;
int ( * sasl_challenge ) ( jclient_t * jcl , char * inbuf , int insize , char * outbuf , int outsize ) ;
} saslmethod_t ;
//#define OAUTH_CLIENT_ID_MSN "0"
# ifdef OAUTH_CLIENT_ID_MSN
static int sasl_plain_initial ( jclient_t * jcl , char * buf , int bufsize )
{
//"https://oauth.live.com/authorize?client_id=" OAUTH_CLIENT_ID_MSN "&scope=wl.messenger,wl.basic,wl.offline_access,wl.contacts_create,wl.share&response_type=token&redirect_uri=http://localhost/";
}
# endif
//\0username\0password
static int sasl_plain_initial ( jclient_t * jcl , char * buf , int bufsize )
{
int len = 0 ;
if ( jcl - > issecure ? jcl - > allowauth_plaintls : jcl - > allowauth_plainnontls )
{
//realm isn't specified
buf [ len + + ] = 0 ;
memcpy ( buf + len , jcl - > username , strlen ( jcl - > username ) ) ;
len + = strlen ( jcl - > username ) ;
buf [ len + + ] = 0 ;
memcpy ( buf + len , jcl - > password , strlen ( jcl - > password ) ) ;
len + = strlen ( jcl - > password ) ;
return len ;
}
return - 1 ;
}
static int saslattr ( char * out , int outlen , char * srcbuf , int srclen , char * arg )
{
char * vn ;
char * s = srcbuf ;
char * e = s + srclen ;
char * vs ;
while ( s < e )
{
while ( s < e & & * s = = ' , ' )
s + + ;
vn = s ;
s + + ;
while ( s < e & & * s > = ' a ' & & * s < = ' z ' )
s + + ;
if ( * s = = ' = ' )
{
vs = + + s ;
if ( * s = = ' \" ' )
{
vs = + + s ;
while ( s < e & & * s ! = ' \" ' )
s + + ;
outlen = s - vs ;
s + + ;
}
else
{
while ( s < e & & * s ! = ' , ' )
s + + ;
outlen = s - vs ;
}
if ( ! strncmp ( vn , arg , strlen ( arg ) ) & & vn [ strlen ( arg ) ] = = ' = ' )
{
memcpy ( out , vs , outlen ) ;
out [ outlen ] = 0 ;
return outlen ;
}
}
}
out [ 0 ] = 0 ;
return 0 ;
}
static int sasl_digestmd5_initial ( jclient_t * jcl , char * buf , int bufsize )
{
if ( jcl - > allowauth_digestmd5 )
{
//FIXME: randomize the cnonce and check the auth key
//although really I'm not entirely sure what the point is.
//if we just authenticated with a mitm attacker relay, we're screwed either way.
strcpy ( jcl - > authnonce , " abcdefghijklmnopqrstuvwxyz " ) ;
//nothing. server does the initial data.
return 0 ;
}
return - 1 ;
}
char * MD5_ToHex ( char * input , int inputlen , char * ret , int retlen ) ;
char * MD5_ToBinary ( char * input , int inputlen , char * ret , int retlen ) ;
static int sasl_digestmd5_challenge ( jclient_t * jcl , char * in , int inlen , char * out , int outlen )
{
char * username = jcl - > username ;
char * password = jcl - > password ;
char * cnonce = jcl - > authnonce ;
char rspauth [ 512 ] ;
char realm [ 512 ] ;
char nonce [ 512 ] ;
char qop [ 512 ] ;
char charset [ 512 ] ;
char algorithm [ 512 ] ;
char X [ 512 ] ;
char Y [ 33 ] ;
char A1 [ 512 ] ;
char A2 [ 512 ] ;
char HA1 [ 33 ] ;
char HA2 [ 33 ] ;
char KD [ 512 ] ;
char Z [ 33 ] ;
char * nc = " 00000001 " ;
char digesturi [ 512 ] ;
char * authzid = " " ;
saslattr ( rspauth , sizeof ( rspauth ) , in , inlen , " rspauth " ) ;
if ( * rspauth )
{
return 0 ; //we don't actually send any data back, just an xml 'response' tag to tell the server that we accept it.
}
saslattr ( realm , sizeof ( realm ) , in , inlen , " realm " ) ;
saslattr ( nonce , sizeof ( nonce ) , in , inlen , " nonce " ) ;
saslattr ( qop , sizeof ( qop ) , in , inlen , " qop " ) ;
saslattr ( charset , sizeof ( charset ) , in , inlen , " charset " ) ;
saslattr ( algorithm , sizeof ( algorithm ) , in , inlen , " algorithm " ) ;
if ( ! * realm )
Q_strlcpy ( realm , jcl - > domain , sizeof ( realm ) ) ;
Q_snprintf ( digesturi , sizeof ( digesturi ) , " xmpp/%s " , realm ) ;
Q_snprintf ( X , sizeof ( X ) , " %s:%s:%s " , username , realm , password ) ;
MD5_ToBinary ( X , strlen ( X ) , Y , sizeof ( Y ) ) ;
memcpy ( A1 , Y , 16 ) ;
if ( * authzid )
Q_snprintf ( A1 + 16 , sizeof ( A1 ) - 16 , " :%s:%s:%s " , nonce , cnonce , authzid ) ;
else
Q_snprintf ( A1 + 16 , sizeof ( A1 ) - 16 , " :%s:%s " , nonce , cnonce ) ;
Q_snprintf ( A2 , sizeof ( A2 ) , " %s:%s " , " AUTHENTICATE " , digesturi ) ;
MD5_ToHex ( A1 , strlen ( A1 + 16 ) + 16 , HA1 , sizeof ( HA1 ) ) ;
MD5_ToHex ( A2 , strlen ( A2 ) , HA2 , sizeof ( HA2 ) ) ;
Q_snprintf ( KD , sizeof ( KD ) , " %s:%s:%s:%s:%s:%s " , HA1 , nonce , nc , cnonce , qop , HA2 ) ;
MD5_ToHex ( KD , strlen ( KD ) , Z , sizeof ( Z ) ) ;
if ( * authzid )
Q_snprintf ( out , outlen , " username= \" %s \" ,realm= \" %s \" ,nonce= \" %s \" ,cnonce= \" %s \" ,nc= \" %s \" ,qop= \" %s \" ,digest-uri= \" %s \" ,response= \" %s \" ,charset= \" %s \" ,authzid= \" %s \" " ,
username , realm , nonce , cnonce , nc , qop , digesturi , Z , charset , authzid ) ;
else
Q_snprintf ( out , outlen , " username= \" %s \" ,realm= \" %s \" ,nonce= \" %s \" ,cnonce= \" %s \" ,nc= \" %s \" ,qop= \" %s \" ,digest-uri= \" %s \" ,response= \" %s \" ,charset= \" %s \" " ,
username , realm , nonce , cnonce , nc , qop , digesturi , Z , charset ) ;
return strlen ( out ) ;
}
static int sasl_scramsha1_initial ( jclient_t * jcl , char * buf , int bufsize )
{
if ( jcl - > allowauth_scramsha1 )
{
strcpy ( jcl - > authnonce , " abcdefghijklmnopqrstuvwxyz " ) ; //FIXME: should be random, to validate that the server knows our password too
Q_snprintf ( buf , bufsize , " n,,n=%s,r=%s " , jcl - > username , jcl - > authnonce ) ;
return strlen ( buf ) ;
}
return - 1 ;
}
typedef struct
{
int len ;
char buf [ 512 ] ;
} buf_t ;
static void buf_cat ( buf_t * buf , char * data , int len )
{
memcpy ( buf - > buf + buf - > len , data , len ) ;
buf - > len + = len ;
buf - > buf [ buf - > len ] = 0 ;
}
static void buf_cats ( buf_t * buf , char * data )
{
buf_cat ( buf , data , strlen ( data ) ) ;
}
static void SHA1_Hi ( char * out , char * password , int passwordlen , buf_t * salt , int times )
{
char prev [ 20 ] ;
int i , j ;
//first iteration is special
buf_cat ( salt , " \0 \0 \0 \1 " , 4 ) ;
SHA1_HMAC ( prev , sizeof ( prev ) , salt - > buf , salt - > len , password , passwordlen ) ;
memcpy ( out , prev , sizeof ( prev ) ) ;
//later iterations just use the previous iteration
for ( i = 1 ; i < times ; i + + )
{
SHA1_HMAC ( prev , sizeof ( prev ) , prev , sizeof ( prev ) , password , passwordlen ) ;
for ( j = 0 ; j < sizeof ( prev ) ; j + + )
out [ j ] ^ = prev [ j ] ;
}
}
static int sasl_scramsha1_challenge ( jclient_t * jcl , char * in , int inlen , char * out , int outlen )
{
//sasl SCRAM-SHA-1 challenge
//send back the same 'r' attribute
buf_t saslchal ;
int l , i ;
buf_t salt ;
buf_t csn ;
buf_t itr ;
buf_t final ;
buf_t sigkey ;
char salted_password [ 20 ] ;
char proof [ 20 ] ;
char proof64 [ 30 ] ;
char clientkey [ 20 ] ;
char storedkey [ 20 ] ;
char clientsignature [ 20 ] ;
char * username = jcl - > username ;
char * password = jcl - > password ;
#if 0
/*hack zone*/
in = " r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096 " ;
inlen = strlen ( in ) ;
strcpy ( jcl - > authnonce , " wvDh8bTUrSc= " ) ; //"fyko+d2lbbFgONRv9qkxdawL");
username = " user " ;
password = " pencil " ;
//should result in "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="
# endif
saslchal . len = 0 ;
buf_cat ( & saslchal , in , inlen ) ;
//be warned, these CAN contain nulls.
csn . len = saslattr ( csn . buf , sizeof ( csn . buf ) , saslchal . buf , saslchal . len , " r " ) ;
salt . len = saslattr ( salt . buf , sizeof ( salt . buf ) , saslchal . buf , saslchal . len , " s " ) ;
itr . len = saslattr ( itr . buf , sizeof ( itr . buf ) , saslchal . buf , saslchal . len , " i " ) ;
salt . len = Base64_Decode ( salt . buf , sizeof ( salt . buf ) , salt . buf , salt . len ) ;
//FIXME: should we validate that csn is prefixed with our cnonce?
//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_cats ( & final , " c= " ) ;
Base64_Add ( " n,, " , 3 ) ;
Base64_Finish ( ) ;
buf_cat ( & final , base64 , strlen ( base64 ) ) ;
buf_cats ( & final , " ,r= " ) ;
buf_cat ( & final , csn . buf , csn . len ) ;
//our original message + ',' + challenge + ',' + the message we're about to send.
sigkey . len = 0 ;
buf_cats ( & sigkey , " n= " ) ;
buf_cats ( & sigkey , username ) ;
buf_cats ( & sigkey , " ,r= " ) ;
buf_cats ( & sigkey , jcl - > authnonce ) ;
buf_cats ( & sigkey , " , " ) ;
buf_cat ( & sigkey , saslchal . buf , saslchal . len ) ;
buf_cats ( & sigkey , " , " ) ;
buf_cat ( & sigkey , final . buf , final . len ) ;
SHA1_Hi ( salted_password , password , strlen ( password ) , & salt , atoi ( itr . buf ) ) ;
SHA1_HMAC ( clientkey , sizeof ( clientkey ) , " Client Key " , strlen ( " Client Key " ) , salted_password , sizeof ( salted_password ) ) ;
SHA1 ( storedkey , sizeof ( storedkey ) , clientkey , sizeof ( clientkey ) ) ;
SHA1_HMAC ( clientsignature , sizeof ( clientsignature ) , sigkey . buf , sigkey . len , storedkey , sizeof ( storedkey ) ) ;
for ( i = 0 ; i < sizeof ( proof ) ; i + + )
proof [ i ] = clientkey [ i ] ^ clientsignature [ i ] ;
Base64_Add ( proof , sizeof ( proof ) ) ;
Base64_Finish ( ) ;
//"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="
Q_snprintf ( out , outlen , " %s,p=%s " , final . buf , base64 ) ;
return strlen ( out ) ;
}
void URL_Split ( char * url , char * proto , int protosize , char * host , int hostsize , char * res , int ressize )
{
char * s ;
* proto = 0 ;
* host = 0 ;
* res = 0 ;
s = strchr ( url , ' : ' ) ;
if ( ! s )
return ;
protosize = min ( protosize - 1 , s - url ) ;
memcpy ( proto , url , protosize ) ;
proto [ protosize ] = 0 ;
s + + ;
if ( s [ 0 ] = = ' / ' & & s [ 1 ] = = ' / ' )
s + = 2 ;
url = s ;
s = strchr ( url , ' / ' ) ;
if ( ! s )
s = url + strlen ( url ) ;
hostsize = min ( hostsize - 1 , s - url ) ;
memcpy ( host , url , hostsize ) ;
host [ hostsize ] = 0 ;
url = s ;
s = url + strlen ( url ) ;
ressize = min ( ressize - 1 , s - url ) ;
memcpy ( res , url , ressize ) ;
res [ ressize ] = 0 ;
}
void Q_strlcat_urlencode ( char * d , const char * s , int n )
{
char hex [ 16 ] = " 0123456789ABCDEF " ;
int clen = strlen ( d ) ;
d + = clen ;
n - = clen ;
n - - ;
if ( s )
while ( * s )
{
if ( ( * s > = ' 0 ' & & * s < = ' 9 ' ) | |
( * s > = ' a ' & & * s < = ' z ' ) | |
( * s > = ' A ' & & * s < = ' Z ' ) | |
* s = = ' - ' | | * s = = ' _ ' | | * s = = ' . ' )
{
if ( ! n )
break ;
n - - ;
* d + + = * s + + ;
}
else
{
if ( n < 3 )
break ;
n - = 3 ;
* d + + = ' % ' ;
* d + + = hex [ * s > > 4 ] ;
* d + + = hex [ * s & 15 ] ;
s + + ;
}
}
* d = 0 ;
}
static int sasl_oauth2_initial ( jclient_t * jcl , char * buf , int bufsize )
{
char msg [ 4096 ] ;
char proto [ 256 ] ;
char host [ 256 ] ;
char resource [ 256 ] ;
int sock , l , rl = 0 ;
char result [ 8192 ] ;
xmltree_t * x ;
if ( * jcl - > password )
return - 1 ;
if ( 0 ) //*jcl->password)
{
char body [ 4096 ] ;
char header [ 4096 ] ;
URL_Split ( jcl - > oauth2 . refreshurl , proto , sizeof ( proto ) , host , sizeof ( host ) , resource , sizeof ( resource ) ) ;
* body = 0 ;
Q_strlcat ( body , " client_id= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . clientid , sizeof ( body ) ) ;
Q_strlcat ( body , " &client_secret= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . clientsecret , sizeof ( body ) ) ;
Q_strlcat ( body , " & " , sizeof ( body ) ) ;
Q_strlcat ( body , " grant_type=password&username= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . useraccount , sizeof ( body ) ) ;
Q_strlcat ( body , " &password= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > password , sizeof ( body ) ) ;
Q_strlcat ( body , " &response_type=code " , sizeof ( body ) ) ;
Q_strlcat ( body , " &redirect_uri= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , " urn:ietf:wg:oauth:2.0:oob " , sizeof ( body ) ) ;
Q_strlcat ( body , " &scope= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . scope , sizeof ( body ) ) ;
Q_snprintf ( header , sizeof ( header ) ,
" POST %s HTTP/1.1 \r \n "
" Host: %s \r \n "
//"Authorization: Basic %s\r\n"
" Content-length: %i \r \n "
" Content-Type: application/x-www-form-urlencoded \r \n "
" Connection: close \r \n "
" \r \n " ,
resource ,
host ,
strlen ( body ) ) ;
sock = pNet_TCPConnect ( host , 443 ) ;
pNet_SetTLSClient ( sock , host ) ;
pNet_Send ( sock , header , strlen ( header ) ) ;
pNet_Send ( sock , body , strlen ( body ) ) ;
while ( 1 )
{
l = pNet_Recv ( sock , result + rl , sizeof ( result ) - rl ) ;
if ( l < 0 )
break ;
else
rl + = l ;
}
pNet_Close ( sock ) ;
result [ rl ] = 0 ;
Con_Printf ( " Got %s \n " , result ) ;
}
//if we have nothing, load up a browser to ask for the first token
if ( ! * jcl - > oauth2 . refreshtoken & & ! * jcl - > oauth2 . authtoken )
{
char url [ 4096 ] ;
* url = 0 ;
Q_strlcat ( url , jcl - > oauth2 . obtainurl , sizeof ( url ) ) ;
Q_strlcat ( url , " ?redirect_uri= " , sizeof ( url ) ) ;
Q_strlcat_urlencode ( url , " urn:ietf:wg:oauth:2.0:oob " , sizeof ( url ) ) ;
Q_strlcat ( url , " &%72esponse_type=code&client_id= " , sizeof ( url ) ) ; //%72 = r. fucking ezquake colour codes. works with firefox anyway. no idea if that's the server changing it to an r or not. :s
Q_strlcat_urlencode ( url , jcl - > oauth2 . clientid , sizeof ( url ) ) ;
Q_strlcat ( url , " &scope= " , sizeof ( url ) ) ;
Q_strlcat_urlencode ( url , jcl - > oauth2 . scope , sizeof ( url ) ) ;
Q_strlcat ( url , " &access_type=offline " , sizeof ( url ) ) ;
Q_strlcat ( url , " &login_hint= " , sizeof ( url ) ) ;
Q_strlcat_urlencode ( url , jcl - > oauth2 . useraccount , sizeof ( url ) ) ;
2013-10-08 14:28:11 +00:00
Con_Printf ( " Please visit ^[^4%s \\ url \\ %s^] and then enter: \n ^[/ " COMMANDPREFIX " %i /oa2token <TOKEN>^] \n Note: you can right-click the link to copy it to your browser, and you can use ctrl+v to paste the resulting auth token as part of the given command. " , url , url , jcl - > accountnum ) ;
2013-07-13 12:14:32 +00:00
//wait for user to act.
return - 2 ;
}
//refresh token is not known, try and get one
if ( ! * jcl - > oauth2 . refreshtoken & & * jcl - > oauth2 . authtoken )
{
xmltree_t * x ;
char body [ 4096 ] ;
char header [ 4096 ] ;
//send a refresh request
* body = 0 ;
Q_strlcat ( body , " code= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . authtoken , sizeof ( body ) ) ;
Q_strlcat ( body , " &client_id= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . clientid , sizeof ( body ) ) ;
Q_strlcat ( body , " &client_secret= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . clientsecret , sizeof ( body ) ) ;
Q_strlcat ( body , " &redirect_uri= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , " urn:ietf:wg:oauth:2.0:oob " , sizeof ( body ) ) ;
Q_strlcat ( body , " &grant_type= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , " authorization_code " , sizeof ( body ) ) ;
URL_Split ( jcl - > oauth2 . refreshurl , proto , sizeof ( proto ) , host , sizeof ( host ) , resource , sizeof ( resource ) ) ;
Q_snprintf ( header , sizeof ( header ) ,
" POST %s HTTP/1.1 \r \n "
" Host: %s \r \n "
//"Authorization: Basic %s\r\n"
" Content-length: %i \r \n "
" Content-Type: application/x-www-form-urlencoded \r \n "
" user-agent: fteqw-plugin-xmpp \r \n "
" Connection: close \r \n "
" \r \n " ,
resource , host , strlen ( body ) ) ;
Con_Printf ( " XMPP: Requesting access token \n " ) ;
sock = pNet_TCPConnect ( host , 443 ) ;
pNet_SetTLSClient ( sock , host ) ;
pNet_Send ( sock , header , strlen ( header ) ) ;
pNet_Send ( sock , body , strlen ( body ) ) ;
while ( 1 )
{
l = pNet_Recv ( sock , result + rl , sizeof ( result ) - rl ) ;
if ( l < 0 )
break ;
else
rl + = l ;
}
result [ rl ] = 0 ;
pNet_Close ( sock ) ;
//should contain something like:
//{
//"access_token" : "ya29.AHES6ZR-_Sx0UpexZdgqQwR8LFqTx-GFi-Zrq4nKrcLLA98N7g",
//"token_type" : "Bearer",
//"expires_in" : 3600
//}
l = strstr ( result , " \r \n \r \n " ) - result ;
l + = 4 ;
if ( l < 0 | | l > rl )
l = rl ;
x = XML_FromJSON ( NULL , " oauth2 " , result , & l , rl ) ;
XML_ConPrintTree ( x , 1 ) ;
free ( jcl - > oauth2 . accesstoken ) ;
free ( jcl - > oauth2 . refreshtoken ) ;
jcl - > oauth2 . accesstoken = strdup ( XML_GetChildBody ( x , " access_token " , " " ) ) ;
// jcl->oauth2.token_type = strdup(XML_GetChildBody(x, "token_type", ""));
// jcl->oauth2.expires_in = strdup(XML_GetChildBody(x, "expires_in", ""));
jcl - > oauth2 . refreshtoken = strdup ( XML_GetChildBody ( x , " refresh_token " , " " ) ) ;
//in theory, the auth token is no longer valid/needed
free ( jcl - > oauth2 . authtoken ) ;
jcl - > oauth2 . authtoken = strdup ( " " ) ;
}
//refresh our refresh token, obtaining a usable sign-in token at the same time.
else if ( ! * jcl - > oauth2 . accesstoken )
{
char body [ 4096 ] ;
char header [ 4096 ] ;
char * newrefresh ;
//send a refresh request
* body = 0 ;
Q_strlcat ( body , " client_id= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . clientid , sizeof ( body ) ) ;
Q_strlcat ( body , " &client_secret= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . clientsecret , sizeof ( body ) ) ;
Q_strlcat ( body , " &grant_type= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , " refresh_token " , sizeof ( body ) ) ;
Q_strlcat ( body , " &refresh_token= " , sizeof ( body ) ) ;
Q_strlcat_urlencode ( body , jcl - > oauth2 . refreshtoken , sizeof ( body ) ) ;
URL_Split ( jcl - > oauth2 . refreshurl , proto , sizeof ( proto ) , host , sizeof ( host ) , resource , sizeof ( resource ) ) ;
Q_snprintf ( header , sizeof ( header ) ,
" POST %s HTTP/1.1 \r \n "
" Host: %s \r \n "
//"Authorization: Basic %s\r\n"
" Content-length: %i \r \n "
" Content-Type: application/x-www-form-urlencoded \r \n "
" user-agent: fteqw-plugin-xmpp \r \n "
" Connection: close \r \n "
" \r \n " ,
resource , host , strlen ( body ) ) ;
Con_Printf ( " XMPP: Refreshing access token \n " ) ;
sock = pNet_TCPConnect ( host , 443 ) ;
pNet_SetTLSClient ( sock , host ) ;
pNet_Send ( sock , header , strlen ( header ) ) ;
pNet_Send ( sock , body , strlen ( body ) ) ;
while ( 1 )
{
l = pNet_Recv ( sock , result + rl , sizeof ( result ) - rl ) ;
if ( l < 0 )
break ;
else
rl + = l ;
}
pNet_Close ( sock ) ;
result [ rl ] = 0 ;
l = strstr ( result , " \r \n \r \n " ) - result ;
l + = 4 ;
if ( l < 0 | | l > rl )
l = rl ;
x = XML_FromJSON ( NULL , " oauth2 " , result , & l , rl ) ;
XML_ConPrintTree ( x , 1 ) ;
newrefresh = XML_GetChildBody ( x , " refresh_token " , NULL ) ;
free ( jcl - > oauth2 . accesstoken ) ;
jcl - > oauth2 . accesstoken = strdup ( XML_GetChildBody ( x , " access_token " , " " ) ) ;
if ( newrefresh | | ! * jcl - > oauth2 . accesstoken )
{
free ( jcl - > oauth2 . refreshtoken ) ;
jcl - > oauth2 . refreshtoken = strdup ( XML_GetChildBody ( x , " refresh_token " , " " ) ) ;
}
// jcl->oauth2.token_type = strdup(XML_GetChildBody(x, "token_type", ""));
// jcl->oauth2.expires_in = strdup(XML_GetChildBody(x, "expires_in", ""));
//refresh token may mutate. follow the mutation.
}
else if ( * jcl - > oauth2 . accesstoken )
Con_Printf ( " XMPP: Using explicit access token \n " ) ;
if ( * jcl - > oauth2 . accesstoken )
{
int len = 0 ;
if ( * jcl - > oauth2 . useraccount )
{
//realm isn't specified
buf [ len + + ] = 0 ;
memcpy ( buf + len , jcl - > oauth2 . useraccount , strlen ( jcl - > oauth2 . useraccount ) ) ;
len + = strlen ( jcl - > oauth2 . useraccount ) ;
buf [ len + + ] = 0 ;
}
memcpy ( buf + len , jcl - > oauth2 . accesstoken , strlen ( jcl - > oauth2 . accesstoken ) ) ;
len + = strlen ( jcl - > oauth2 . accesstoken ) ;
Con_Printf ( " XMPP: Signing in \n " ) ;
free ( jcl - > oauth2 . accesstoken ) ;
jcl - > oauth2 . accesstoken = strdup ( " " ) ;
return len ;
}
//if the reply has a refresh token in it, clear out any password info
2013-08-21 08:59:26 +00:00
return - 2 ;
2013-07-13 12:14:32 +00:00
}
//in descending priority order
saslmethod_t saslmethods [ ] =
{
{ NULL , sasl_oauth2_initial , NULL } , //potentially avoids having to ask+store their password. a browser is required to obtain auth token for us.
{ " SCRAM-SHA-1 " , sasl_scramsha1_initial , sasl_scramsha1_challenge } , //lots of unreadable hashing
{ " DIGEST-MD5 " , sasl_digestmd5_initial , sasl_digestmd5_challenge } , //kinda silly
{ " PLAIN " , sasl_plain_initial , NULL } //realm\0username\0password
} ;
/*
pidgin ' s msn request
https : //oauth.live.com/authorize?client_id=000000004C07035A&scope=wl.messenger,wl.basic,wl.offline_access,wl.contacts_create,wl.share&response_type=token&redirect_uri=http://pidgin.im/
*/
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-07-13 12:14:32 +00:00
void JCL_CloseConnection ( jclient_t * jcl , qboolean reconnect ) ;
void JCL_JoinMUCChat ( jclient_t * jcl , char * room , char * server , char * myhandle , char * password ) ;
void JCL_GenLink ( jclient_t * jcl , char * out , int outlen , char * action , char * context , char * contextres , char * sid , char * txtfmt , . . . )
{
va_list argptr ;
qboolean textonly = false ;
* out = 0 ;
if ( ! strchr ( txtfmt , ' % ' ) )
{
Q_strlcpy ( out , " bad link text " , outlen ) ;
return ;
}
if ( textonly )
{
va_start ( argptr , txtfmt ) ;
Q_strlcat ( out , " [ " , outlen ) ;
Q_vsnprintf ( out , outlen , txtfmt , argptr ) ;
Q_strlcat ( out , " ] " , outlen ) ;
va_end ( argptr ) ;
return ;
}
Q_strlcat ( out , " ^[[ " , outlen ) ;
va_start ( argptr , txtfmt ) ;
Q_vsnprintf ( out + 3 , outlen - 3 , txtfmt , argptr ) ;
va_end ( argptr ) ;
Q_strlcat ( out , " ] " , outlen ) ;
if ( jcl & & jcl - > accountnum )
{
char acc [ 32 ] ;
Q_snprintf ( acc , sizeof ( acc ) , " %i " , jcl - > accountnum ) ;
Q_strlcat ( out , " \\ xmppacc \\ " , outlen ) ;
Q_strlcat ( out , acc , outlen ) ;
}
if ( action )
{
Q_strlcat ( out , " \\ xmppact \\ " , outlen ) ;
Q_strlcat ( out , action , outlen ) ;
}
if ( context )
{
Q_strlcat ( out , " \\ xmpp \\ " , outlen ) ;
Q_strlcat ( out , context , outlen ) ;
if ( contextres )
{
Q_strlcat ( out , " / " , outlen ) ;
Q_strlcat ( out , contextres , outlen ) ;
}
}
if ( sid )
{
Q_strlcat ( out , " \\ xmppsid \\ " , outlen ) ;
Q_strlcat ( out , sid , outlen ) ;
}
Q_strlcat ( out , " ^] " , outlen ) ;
}
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
qintptr_t JCL_ConsoleLink ( qintptr_t * args )
{
2013-07-13 12:14:32 +00:00
jclient_t * jcl ;
2013-06-23 02:33:52 +00:00
char text [ 256 ] ;
char link [ 256 ] ;
2013-06-29 16:01:07 +00:00
char who [ 256 ] ;
char what [ 256 ] ;
2013-07-13 12:14:32 +00:00
char which [ 256 ] ;
int i ;
2013-06-29 16:01:07 +00:00
// 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 ) ) ;
2013-07-13 12:14:32 +00:00
JCL_Info_ValueForKey ( link , " xmppacc " , which , sizeof ( which ) ) ;
2013-06-29 16:01:07 +00:00
if ( ! * who & & ! * what )
return false ;
2013-07-13 12:14:32 +00:00
i = atoi ( which ) ;
i = bound ( 0 , i , sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) ) ;
jcl = jclients [ i ] ;
2013-06-29 16:01:07 +00:00
if ( ! strcmp ( what , " pauth " ) )
2013-06-23 02:33:52 +00:00
{
//we should friend them too.
2013-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_AddClientMessagef ( jcl , " <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-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_AddClientMessagef ( jcl , " <presence to='%s' type='unsubscribed'/> " , who ) ;
2013-06-23 02:33:52 +00:00
return true ;
}
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
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 ) ) ;
2013-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_Join ( jcl , who , what , true , ICEP_INVALID ) ;
2013-06-29 16:01:07 +00:00
return true ;
}
else if ( ! strcmp ( what , " jdeny " ) )
{
JCL_Info_ValueForKey ( link , " xmppsid " , what , sizeof ( what ) ) ;
2013-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_Join ( jcl , who , what , false , ICEP_INVALID ) ;
2013-06-29 16:01:07 +00:00
return true ;
}
else if ( ! strcmp ( what , " join " ) )
{
2013-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_Join ( jcl , who , NULL , true , ICEP_QWCLIENT ) ;
2013-06-29 16:01:07 +00:00
return true ;
}
else if ( ! strcmp ( what , " invite " ) )
{
2013-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_Join ( jcl , who , NULL , true , ICEP_QWSERVER ) ;
2013-06-29 16:01:07 +00:00
return true ;
}
else if ( ! strcmp ( what , " call " ) )
{
2013-07-13 12:14:32 +00:00
if ( jcl & & jcl - > status = = JCL_ACTIVE )
JCL_Join ( jcl , who , NULL , true , ICEP_VOICE ) ;
2013-06-29 16:01:07 +00:00
return true ;
2013-06-23 02:33:52 +00:00
}
2013-07-13 12:14:32 +00:00
# endif
else if ( ! strcmp ( what , " mucjoin " ) )
{ //conference/chat join
JCL_Info_ValueForKey ( link , " xmppsid " , what , sizeof ( what ) ) ;
JCL_JoinMUCChat ( jcl , who , NULL , NULL , what ) ;
}
2013-06-29 16:01:07 +00:00
else if ( ( * who & & ! * what ) | | ! strcmp ( what , " msg " ) )
2013-06-23 02:33:52 +00:00
{
2013-07-13 12:14:32 +00:00
if ( jcl )
2013-06-23 02:33:52 +00:00
{
char * f ;
buddy_t * b ;
bresource_t * br ;
2013-07-13 12:14:32 +00:00
JCL_FindBuddy ( jcl , * who ? who : jcl - > 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 ] ;
2013-07-13 12:14:32 +00:00
jclient_t * jcl = jclients [ 0 ] ;
if ( ! jcl )
2005-12-11 20:11:22 +00:00
{
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-07-13 12:14:32 +00:00
for ( b = jcl - > buddies ; b ; b = b - > next )
2013-06-23 02:33:52 +00:00
{
if ( ! strcmp ( b - > name , consolename ) )
{
if ( b - > defaultresource )
2013-07-13 12:14:32 +00:00
Q_snprintf ( jcl - > defaultdest , sizeof ( jcl - > defaultdest ) , " %s/%s " , b - > accountdomain , b - > defaultresource - > resource ) ;
2013-06-23 02:33:52 +00:00
else
2013-07-13 12:14:32 +00:00
Q_snprintf ( jcl - > defaultdest , sizeof ( jcl - > defaultdest ) , " %s " , b - > accountdomain ) ;
2013-06-23 02:33:52 +00:00
break ;
}
}
2013-07-13 12:14:32 +00:00
JCL_Command ( 0 , 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 ;
2013-07-13 12:14:32 +00:00
if ( ! jcl | | ! jcl - > outbuflen | | jcl - > socket = = - 1 )
2013-06-23 02:33:52 +00:00
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-07-13 12:14:32 +00:00
else if ( sent < 0 )
Con_Printf ( " Error sending \n " ) ;
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 ;
2013-07-13 12:14:32 +00:00
char body [ 4096 ] ;
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-07-13 12:14:32 +00:00
char * serveraddr ;
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 ) ) ;
2013-07-13 12:14:32 +00:00
jcl - > authmode = - 1 ;
2005-12-11 20:11:22 +00:00
2013-07-13 12:14:32 +00:00
if ( * jcl - > redirserveraddr )
serveraddr = jcl - > redirserveraddr ;
else
serveraddr = jcl - > serveraddr ;
2005-12-11 20:11:22 +00:00
2013-07-13 12:14:32 +00:00
if ( ! * serveraddr )
{
//jcl->tlsconnect requires an explicit hostname, so should not be able to take this path.
char srv [ 256 ] ;
char srvserver [ 256 ] ;
Q_snprintf ( srv , sizeof ( srv ) , " _xmpp-client._tcp.%s " , jcl - > domain ) ;
if ( NET_DNSLookup_SRV ( srv , srvserver , sizeof ( srvserver ) ) )
{
2013-08-21 07:42:42 +00:00
Con_DPrintf ( " XMPP: Trying to connect to %s (%s) \n " , jcl - > domain , srvserver ) ;
2013-07-13 12:14:32 +00:00
jcl - > socket = pNet_TCPConnect ( srvserver , jcl - > serverport ) ; //port is should already be part of the srvserver name
}
else
{
2013-07-14 12:22:51 +00:00
Con_Printf ( " XMPP: Unable to determine service (%s). Check internet connection. \n " , jcl - > domain ) ;
2013-07-13 12:14:32 +00:00
return false ;
}
}
else
{
2013-08-21 07:42:42 +00:00
Con_DPrintf ( " XMPP: Trying to connect to %s \n " , jcl - > domain ) ;
2013-07-13 12:14:32 +00:00
jcl - > socket = pNet_TCPConnect ( serveraddr , jcl - > serverport ) ; //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 ;
2013-07-13 12:14:32 +00:00
if ( jcl - > forcetls = = 2 )
if ( pNet_SetTLSClient ( jcl - > socket , jcl - > certificatedomain ) > = 0 )
2013-06-29 16:01:07 +00:00
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 ;
}
2013-07-13 12:14:32 +00:00
jclient_t * JCL_ConnectXML ( xmltree_t * acc )
{
//this is a list of xmpp networks that use oauth2 for which we have a clientid registered. this allows us to get the right oauth defaults.
struct
{
char * domain ;
char * saslmethod ;
char * obtainurl ;
char * refreshurl ;
char * revokeurl ;
char * scope ;
char * clientid ;
char * clientsecret ;
//char *signouturl;
//char *clientregistrationurl;
} * oa , dfltoauth [ ] = {
{
" gmail.com " ,
" X-OAUTH2 " ,
" https://accounts.google.com/o/oauth2/auth " , //authorize
" https://accounts.google.com/o/oauth2/token " , //refresh
" https://accounts.google.com/o/oauth2/revoke " , //revoke
" https://www.googleapis.com/auth/googletalk " , //scope
" 1060926168015.apps.googleusercontent.com " , //clientid
" mptCRRTE5I626npsnoZ_RqoG " //client secrit. there really is no securing this. I'll just have to avoid any pay-for google apis. *shrug*
//"https://accounts.google.com/IssuedAuthSubTokens"
//"https://code.google.com/apis/console"
} ,
/*
{
" messenger.live.com " ,
" X-MESSENGER-OAUTH2 " ,
" https://oauth.live.com/authorize " ,
" https://oauth.live.com/token " ,
" " , //FIXME fill in revoke url
" wl.messenger,wl.basic,wl.offline_access,wl.contacts_create,wl.share " , //no idea what's actually needed.
" " , //client-id - none registered. go register it yourself.
" " //client-secret - client not registered, go do it yourself.
//""
//"https://manage.dev.live.com/"
} ,
*/
/*
{
" messenger.live.com " ,
" X-FACEBOOK-PLATFORM " ,
" " , //FIXME fill in obtain url
" " , //FIXME fill in refresh url
" " , //FIXME fill in revoke url
" " , //FIXME: fill in scope
" " , //client-id - none registered. go register it yourself.
" " //client-secret - client not registered, go do it yourself.
//""
//""
} ,
*/
{
NULL ,
" " ,
" " ,
" " ,
" " ,
" " ,
" " ,
" "
}
} ;
jclient_t * jcl ;
xmltree_t * oauth2 ;
char oauthname [ 256 ] ;
jcl = malloc ( sizeof ( jclient_t ) ) ;
if ( ! jcl )
return NULL ;
memset ( jcl , 0 , sizeof ( jclient_t ) ) ;
jcl - > socket = - 1 ;
jcl - > accountnum = atoi ( XML_GetParameter ( acc , " id " , " 1 " ) ) ;
//make sure dependant properties are listed beneath their dependancies...
jcl - > forcetls = atoi ( XML_GetChildBody ( acc , " forcetls " , " 1 " ) ) ;
jcl - > streamdebug = ! ! atoi ( XML_GetChildBody ( acc , " streamdebug " , " 0 " ) ) ;
Q_strlcpy ( jcl - > serveraddr , XML_GetChildBody ( acc , " serveraddr " , " " ) , sizeof ( jcl - > serveraddr ) ) ;
jcl - > serverport = atoi ( XML_GetChildBody ( acc , " serverport " , ( jcl - > forcetls = = 2 ) ? " 5223 " : " 5222 " ) ) ;
Q_strlcpy ( jcl - > username , XML_GetChildBody ( acc , " username " , " user " ) , sizeof ( jcl - > username ) ) ;
Q_strlcpy ( jcl - > domain , XML_GetChildBody ( acc , " domain " , " localhost " ) , sizeof ( jcl - > domain ) ) ;
Q_strlcpy ( jcl - > resource , XML_GetChildBody ( acc , " resource " , " " ) , sizeof ( jcl - > resource ) ) ;
//half these networks seem to have weird domains. especially microsoft.
if ( strchr ( jcl - > username , ' @ ' ) )
Q_strlcpy ( oauthname , jcl - > username , sizeof ( oauthname ) ) ;
else
Q_snprintf ( oauthname , sizeof ( oauthname ) , " %s@%s " , jcl - > username , jcl - > domain ) ;
for ( oa = dfltoauth ; oa - > domain ; oa + + )
{
if ( ! strcmp ( oa - > domain , jcl - > domain ) )
break ;
}
oauth2 = XML_ChildOfTree ( acc , " oauth2 " , 0 ) ;
Q_strlcpy ( jcl - > oauth2 . saslmethod , XML_GetParameter ( oauth2 , " method " , oa - > saslmethod ) , sizeof ( jcl - > oauth2 . saslmethod ) ) ;
Q_strlcpy ( jcl - > oauth2 . obtainurl , XML_GetChildBody ( oauth2 , " obtain-url " , oa - > obtainurl ) , sizeof ( jcl - > oauth2 . obtainurl ) ) ;
Q_strlcpy ( jcl - > oauth2 . refreshurl , XML_GetChildBody ( oauth2 , " refresh-url " , oa - > refreshurl ) , sizeof ( jcl - > oauth2 . refreshurl ) ) ;
Q_strlcpy ( jcl - > oauth2 . clientid , XML_GetChildBody ( oauth2 , " client-id " , oa - > clientid ) , sizeof ( jcl - > oauth2 . clientid ) ) ;
Q_strlcpy ( jcl - > oauth2 . clientsecret , XML_GetChildBody ( oauth2 , " client-secret " , oa - > clientsecret ) , sizeof ( jcl - > oauth2 . clientsecret ) ) ;
jcl - > oauth2 . scope = strdup ( XML_GetChildBody ( oauth2 , " scope " , oa - > scope ) ) ;
jcl - > oauth2 . useraccount = strdup ( XML_GetChildBody ( oauth2 , " authname " , oauthname ) ) ;
jcl - > oauth2 . authtoken = strdup ( XML_GetChildBody ( oauth2 , " auth-token " , " " ) ) ;
jcl - > oauth2 . refreshtoken = strdup ( XML_GetChildBody ( oauth2 , " refresh-token " , " " ) ) ;
jcl - > oauth2 . accesstoken = strdup ( XML_GetChildBody ( oauth2 , " access-token " , " " ) ) ;
Q_strlcpy ( jcl - > password , XML_GetChildBody ( acc , " password " , " " ) , sizeof ( jcl - > password ) ) ;
if ( ! * jcl - > resource )
{ //the default resource matches the game that they're trying to play.
char gamename [ 64 ] , * res , * o ;
if ( pCvar_GetString ( " fs_gamename " , gamename , sizeof ( gamename ) ) )
{
//strip out any weird chars (namely whitespace)
for ( o = gamename , res = gamename ; * res ; )
{
if ( * res = = ' ' | | * res = = ' \t ' )
res + + ;
else
* o + + = * res + + ;
}
if ( ! * gamename )
Q_strlcpy ( jcl - > resource , " FTE " , sizeof ( jcl - > resource ) ) ;
else
Q_strlcpy ( jcl - > resource , gamename , sizeof ( jcl - > resource ) ) ;
}
}
Q_strlcpy ( jcl - > certificatedomain , XML_GetChildBody ( acc , " certificatedomain " , jcl - > domain ) , sizeof ( jcl - > certificatedomain ) ) ;
jcl - > status = atoi ( XML_GetChildBody ( acc , " inactive " , " 0 " ) ) ? JCL_INACTIVE : JCL_DEAD ;
jcl - > allowauth_plainnontls = atoi ( XML_GetChildBody ( acc , " allowauth_plain_nontls " , " 0 " ) ) ;
jcl - > allowauth_plaintls = atoi ( XML_GetChildBody ( acc , " allowauth_plain_tls " , " 1 " ) ) ; //required 1 for googletalk, otherwise I'd set it to 0.
jcl - > allowauth_digestmd5 = atoi ( XML_GetChildBody ( acc , " allowauth_digest_md5 " , " 1 " ) ) ;
jcl - > allowauth_scramsha1 = atoi ( XML_GetChildBody ( acc , " allowauth_scram_sha_1 " , " 1 " ) ) ;
jcl - > allowauth_oauth2 = atoi ( XML_GetChildBody ( acc , " allowauth_oauth2 " , jcl - > oauth2 . saslmethod ? " 1 " : " 0 " ) ) ;
return jcl ;
}
jclient_t * JCL_Connect ( int accnum , char * server , int forcetls , char * account , char * password )
2013-06-29 16:01:07 +00:00
{
2013-07-13 12:14:32 +00:00
char srvserver [ 256 ] ;
2013-06-29 16:01:07 +00:00
char gamename [ 64 ] ;
jclient_t * jcl ;
char * domain ;
char * res ;
2013-07-13 12:14:32 +00:00
xmltree_t * x ;
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
}
2013-07-13 12:14:32 +00:00
if ( forcetls > 0 )
2005-12-11 20:11:22 +00:00
{
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-07-13 12:14:32 +00:00
x = XML_CreateNode ( NULL , " account " , " " , " " ) ;
XML_AddParameteri ( x , " id " , accnum ) ;
2013-07-14 12:22:51 +00:00
XML_CreateNode ( x , " serveraddr " , " " , server ) ;
2013-07-13 12:14:32 +00:00
XML_CreateNode ( x , " username " , " " , account ) ;
XML_CreateNode ( x , " domain " , " " , domain ) ;
XML_CreateNode ( x , " resource " , " " , res ) ;
XML_CreateNode ( x , " password " , " " , password ) ;
jcl = JCL_ConnectXML ( x ) ;
XML_Destroy ( x ) ;
2005-12-11 20:11:22 +00:00
return jcl ;
}
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
}
}
2013-07-13 12:14:32 +00:00
//FIXME: add flags to avoid creation
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 ;
2013-07-13 12:14:32 +00:00
if ( ! jid )
{
if ( buddy )
* buddy = NULL ;
if ( bres )
* bres = NULL ;
return false ;
}
2013-06-23 02:33:52 +00:00
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-08-21 07:42:42 +00:00
void JCL_IQTimeouts ( jclient_t * jcl )
{
struct iq_s * iq , * * link ;
for ( link = & jcl - > pendingiqs ; * link ; )
{
iq = * link ;
if ( iq - > timeout < jclient_curtime )
{
iq = * link ;
* link = iq - > next ;
if ( iq - > callback )
{
Con_DPrintf ( " IQ timeout with %s \n " , iq - > to ) ;
iq - > callback ( jcl , NULL , iq ) ;
}
free ( iq ) ;
}
else
link = & ( * link ) - > next ;
}
}
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 ;
2013-08-21 07:42:42 +00:00
if ( ! target )
target = " " ;
iq = malloc ( sizeof ( * iq ) + strlen ( target ) ) ;
2013-06-23 02:33:52 +00:00
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 ;
2013-08-21 07:42:42 +00:00
iq - > timeout = jclient_curtime + 30 * 1000 ;
strcpy ( iq - > to , target ) ;
2013-06-23 02:33:52 +00:00
2013-08-21 07:42:42 +00:00
if ( * target )
2013-06-23 02:33:52 +00:00
{
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-07-13 12:14:32 +00:00
char * s = XML_GenerateString ( node , false ) ;
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-07-13 12:14:32 +00:00
char myjid [ 512 ] ;
2013-06-23 02:33:52 +00:00
Q_strlcpy ( jcl - > jid , c - > body , sizeof ( jcl - > jid ) ) ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , myjid , sizeof ( myjid ) , NULL , jcl - > jid , NULL , NULL , " %s " , jcl - > jid ) ;
2013-08-21 07:42:42 +00:00
Con_DPrintf ( " Bound to jid %s \n " , jcl - > jid ) ;
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
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",
" jabber:iq:version " ,
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
" urn:xmpp:jingle:1 " ,
QUAKEMEDIAXMLNS ,
# ifdef VOIP
2013-08-21 07:42:42 +00:00
# ifdef VOIP_LEGACY
" http://www.google.com/xmpp/protocol/session " , //so google's non-standard clients can chat with us
" http://www.google.com/xmpp/protocol/voice/v1 " , //so google's non-standard clients can chat with us
// "http://www.google.com/xmpp/protocol/camera/v1", //can send video
// "http://www.google.com/xmpp/protocol/video/v1", //can receive video
# endif
# ifndef VOIP_LEGACY_ONLY
" urn:xmpp:jingle:apps:rtp:1 " ,
" urn:xmpp:jingle:apps:rtp:audio " ,
# endif
2013-07-13 12:14:32 +00:00
# endif
//"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
# endif
# ifndef Q3_VM
" urn:xmpp:time " ,
# endif
2013-06-23 02:33:52 +00:00
" urn:xmpp:ping " , //FIXME: I'm not keen on this. I only added support to stop errors from pidgin when trying to debug.
2013-06-29 21:08:09 +00:00
" urn:xmpp:attention:0 " , //poke.
2013-06-23 02:33:52 +00:00
2013-07-13 12:14:32 +00:00
//file transfer
# ifdef FILETRANSFER
" http://jabber.org/protocol/si " ,
" http://jabber.org/protocol/si/profile/file-transfer " ,
" http://jabber.org/protocol/ibb " ,
//"http://jabber.org/protocol/bytestreams",
# endif
2013-06-23 02:33:52 +00:00
# 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 " ,
2013-07-13 12:14:32 +00:00
" http://jabber.org/protocol/si/profile/file-transfer " ,
2013-06-23 02:33:52 +00:00
" 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 ] ;
2013-08-21 07:42:42 +00:00
char hashednode [ 1024 ] ;
char * node = XML_GetParameter ( ot , " node " , NULL ) ;
2013-06-29 16:01:07 +00:00
unparsable = false ;
2013-03-31 04:21:08 +00:00
2013-06-29 16:01:07 +00:00
buildcaps ( msg , sizeof ( msg ) ) ;
2013-08-21 07:42:42 +00:00
Q_snprintf ( hashednode , sizeof ( hashednode ) , DISCONODE " #%s " , buildcapshash ( ) ) ;
2005-12-11 20:11:22 +00:00
2013-08-21 07:42:42 +00:00
if ( ! node | | ! strcmp ( node , hashednode ) )
{
JCL_AddClientMessagef ( jcl ,
" <iq type='result' to='%s' id='%s'> "
" <query xmlns='http://jabber.org/protocol/disco#info' node='%s'> "
" %s "
" </query> "
" </iq> " , from , id , node ? node : hashednode , msg ) ;
}
# ifdef VOIP_LEGACY
else if ( ! strcmp ( node , DISCONODE " #voice-v1 " ) )
{
JCL_AddClientMessagef ( jcl ,
" <iq type='result' to='%s' id='%s'> "
" <query xmlns='http://jabber.org/protocol/disco#info' node='%s'> "
" <feature var='http://www.google.com/xmpp/protocol/voice/v1'/> "
" </query> "
" </iq> " , from , id , node , msg ) ;
}
# endif
else
{
JCL_AddClientMessagef ( jcl ,
" <iq type='error' to='%s' id='%s'> "
" <error code=404 type='cancel'> "
" <item-not-found/> "
" </error> "
" </iq> " , from , id ) ;
}
2013-06-29 16:01:07 +00:00
}
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-07-13 12:14:32 +00:00
}
else if ( ! strcmp ( f , " set " ) )
{
xmltree_t * c ;
# ifdef FILETRANSFERS
ot = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/ibb " , " open " , 0 ) ;
if ( ot )
{
struct ft_s * ft ;
char * sid = XML_GetParameter ( ot , " sid " , " " ) ;
int blocksize = atoi ( XML_GetParameter ( ot , " block-size " , " 4096 " ) ) ; //technically this is required.
char * stanza = XML_GetParameter ( ot , " stanza " , " iq " ) ;
for ( ft = jcl - > ft ; ft ; ft = ft - > next )
{
if ( ! strcmp ( ft - > sid , sid ) )
{
if ( ! ft - > begun & & ft - > transmitting = = false )
{
if ( blocksize > 65536 | | strcmp ( stanza , " iq " ) )
{ //blocksize: MUST NOT be greater than 65535
JCL_AddClientMessagef ( jcl ,
" <iq id='%s' to='%s' type='error'> "
" <error type='modify'> "
" <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> "
" </error> "
" </iq> "
, id , from ) ;
}
else if ( blocksize > 4096 )
{ //ask for smaller chunks
JCL_AddClientMessagef ( jcl ,
" <iq id='%s' to='%s' type='error'> "
" <error type='modify'> "
" <resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> "
" </error> "
" </iq> "
, id , from ) ;
}
else
{ //it looks okay
pFS_Open ( ft - > fname , & ft - > file , 2 ) ;
ft - > method = FT_IBB ;
ft - > blocksize = blocksize ;
ft - > begun = true ;
//if its okay...
JCL_AddClientMessagef ( jcl , " <iq id='%s' to='%s' type='result'/> " , id , from ) ;
}
return ;
}
}
}
}
ot = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/ibb " , " close " , 0 ) ;
if ( ot )
{
struct ft_s * * link , * ft ;
char * sid = XML_GetParameter ( ot , " sid " , " " ) ;
for ( link = & jcl - > ft ; * link ; link = & ( * link ) - > next )
{
ft = * link ;
if ( ! strcmp ( ft - > sid , sid ) )
{
if ( ft - > begun & & ft - > method = = FT_IBB )
{
int size ;
pFS_Close ( ft - > file ) ;
if ( ft - > transmitting )
{
Con_Printf ( " %s aborted transfer of \" %s \" \n " , from , ft - > fname ) ;
}
else
{
size = pFS_Open ( ft - > fname , & ft - > file , 1 ) ;
pFS_Close ( ft - > file ) ;
if ( size = = ft - > size )
Con_Printf ( " Received file \" %s \" successfully \n " , ft - > fname ) ;
else
Con_Printf ( " %s aborted transfer of \" %s \" \n " , from , ft - > fname ) ;
}
* link = ft - > next ;
free ( ft ) ;
//if its okay...
JCL_AddClientMessagef ( jcl , " <iq id='%s' to='%s' type='result'/> " , id , from ) ;
return ;
}
}
}
}
ot = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/ibb " , " data " , 0 ) ;
if ( ot )
{
char block [ 65536 ] ;
char * sid = XML_GetParameter ( ot , " sid " , " " ) ;
unsigned short seq = atoi ( XML_GetParameter ( ot , " seq " , " 0 " ) ) ;
int blocksize ;
struct ft_s * ft ;
for ( ft = jcl - > ft ; ft ; ft = ft - > next )
{
if ( ! strcmp ( ft - > sid , sid ) & & ! ft - > transmitting )
{
blocksize = Base64_Decode ( block , sizeof ( block ) , ot - > body , strlen ( ot - > body ) ) ;
if ( blocksize & & blocksize < = ft - > blocksize )
{
pFS_Write ( ft - > file , block , blocksize ) ;
JCL_AddClientMessagef ( jcl , " <iq id='%s' to='%s' type='result'/> " , id , from ) ;
return ;
}
else
Con_Printf ( " XMPP: Invalid blocksize in file transfer from %s \n " , from ) ;
break ;
}
}
}
ot = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/si " , " si " , 0 ) ;
if ( ot )
{
char * profile = XML_GetParameter ( ot , " profile " , " " ) ;
unparsable = false ;
if ( ! strcmp ( profile , " http://jabber.org/protocol/si/profile/file-transfer " ) )
{
char * s ;
xmltree_t * repiq , * repsi , * c ;
char * mimetype = XML_GetParameter ( ot , " mime-type " , " text/plain " ) ;
char * sid = XML_GetParameter ( ot , " id " , " " ) ;
xmltree_t * file = XML_ChildOfTreeNS ( ot , " http://jabber.org/protocol/si/profile/file-transfer " , " file " , 0 ) ;
char * fname = XML_GetParameter ( file , " name " , " file.txt " ) ;
char * date = XML_GetParameter ( file , " date " , " " ) ;
char * md5hash = XML_GetParameter ( file , " hash " , " " ) ;
int fsize = strtoul ( XML_GetParameter ( file , " size " , " 0 " ) , NULL , 0 ) ;
char * desc = XML_GetChildBody ( file , " desc " , " " ) ;
//file transfer offer
struct ft_s * ft = malloc ( sizeof ( * ft ) ) ;
memset ( ft , 0 , sizeof ( * ft ) ) ;
ft - > next = jcl - > ft ;
jcl - > ft = ft ;
ft - > transmitting = false ;
Q_strlcpy ( ft - > sid , sid , sizeof ( ft - > sid ) ) ;
Q_strlcpy ( ft - > fname , fname , sizeof ( ft - > sid ) ) ;
Base64_Decode ( ft - > md5hash , sizeof ( ft - > md5hash ) , md5hash , strlen ( md5hash ) ) ;
ft - > size = fsize ;
ft - > file = - 1 ;
// ft->with =
ft - > method = FT_IBB ;
ft - > begun = false ;
Con_Printf ( " Receiving file \" %s \" from \" %s \" (%i bytes) \n " , fname , from , fsize ) ;
//generate a response.
//FIXME: we ought to delay response until after we prompt.
repiq = XML_CreateNode ( NULL , " iq " , " " , " " ) ;
XML_AddParameter ( repiq , " type " , " result " ) ;
XML_AddParameter ( repiq , " to " , from ) ;
XML_AddParameter ( repiq , " id " , id ) ;
repsi = XML_CreateNode ( repiq , " si " , " http://jabber.org/protocol/si " , " " ) ;
XML_CreateNode ( repsi , " file " , " http://jabber.org/protocol/si/profile/file-transfer " , " " ) ; //I don't really get the point of this.
c = XML_CreateNode ( repsi , " feature " , " http://jabber.org/protocol/feature-neg " , " " ) ;
c = XML_CreateNode ( c , " x " , " jabber:x:data " , " " ) ;
XML_AddParameter ( c , " type " , " submit " ) ;
c = XML_CreateNode ( c , " field " , " " , " " ) ;
XML_AddParameter ( c , " var " , " stream-method " ) ;
if ( ft - > method = = FT_IBB )
c = XML_CreateNode ( c , " value " , " " , " http://jabber.org/protocol/ibb " ) ;
else if ( ft - > method = = FT_BYTESTREAM )
c = XML_CreateNode ( c , " value " , " " , " http://jabber.org/protocol/bytestreams " ) ;
s = XML_GenerateString ( repiq , false ) ;
JCL_AddClientMessageString ( jcl , s ) ;
free ( s ) ;
XML_Destroy ( repiq ) ;
}
else
{
//profile not understood
JCL_AddClientMessagef ( jcl ,
" <iq type='error' to='%s' id='%s'> "
" <error code='400' type='cancel'> "
" <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> "
" <bad-profile xmlns='http://jabber.org/protocol/si'/> "
" </error> "
" </iq> " , from , id ) ;
}
}
# endif
2013-06-29 16:01:07 +00:00
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-07-13 12:14:32 +00:00
# ifdef JINGLE
2013-08-21 07:42:42 +00:00
c = XML_ChildOfTreeNS ( tree , " urn:xmpp:jingle:1 " , " jingle " , 0 ) ;
if ( c )
2013-06-29 16:01:07 +00:00
unparsable = ! JCL_ParseJingle ( jcl , c , from , id ) ;
2013-08-21 07:42:42 +00:00
# ifdef VOIP_LEGACY
c = XML_ChildOfTreeNS ( tree , " http://www.google.com/session " , " session " , 0 ) ;
if ( c )
unparsable = ! JCL_HandleGoogleSession ( jcl , c , from , id ) ;
# endif
2013-07-13 12:14:32 +00:00
# endif
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 ;
2013-08-21 07:42:42 +00:00
char * from = XML_GetParameter ( tree , " from " , " " ) ;
2013-06-29 16:01:07 +00:00
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 ;
2013-08-21 07:42:42 +00:00
if ( ! strcmp ( iq - > id , id ) & & ! strcmp ( iq - > to , from ) )
2013-06-29 16:01:07 +00:00
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 ) ;
2013-07-13 12:14:32 +00:00
char * type = XML_GetParameter ( tree , " type " , " normal " ) ;
char * ctx ;
2013-06-29 16:01:07 +00:00
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 ) ;
2013-07-13 12:14:32 +00:00
ctx = b - > name ;
if ( ! strcmp ( type , " groupchat " ) )
f = br - > resource ;
else if ( b - > chatroom ) //need to use the full resource for private chat within a room
{
ctx = f ;
f = br - > resource ;
}
else
{
f = b - > name ;
b - > defaultresource = br ;
}
2013-06-29 16:01:07 +00:00
}
if ( f )
{
ot = XML_ChildOfTree ( tree , " composing " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " http://jabber.org/protocol/chatstates " ) )
{
unparsable = false ;
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " %s is typing \r " , f ) ;
2013-06-29 16:01:07 +00:00
}
ot = XML_ChildOfTree ( tree , " paused " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " http://jabber.org/protocol/chatstates " ) )
{
unparsable = false ;
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " %s has stopped typing \r " , f ) ;
2013-06-29 16:01:07 +00:00
}
ot = XML_ChildOfTree ( tree , " inactive " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " http://jabber.org/protocol/chatstates " ) )
{
unparsable = false ;
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " \r " , f ) ;
2013-06-29 16:01:07 +00:00
}
ot = XML_ChildOfTree ( tree , " active " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " http://jabber.org/protocol/chatstates " ) )
{
unparsable = false ;
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " \r " , f ) ;
2013-06-29 16:01:07 +00:00
}
ot = XML_ChildOfTree ( tree , " gone " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " http://jabber.org/protocol/chatstates " ) )
{
unparsable = false ;
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " %s has gone away \r " , f ) ;
2013-06-29 16:01:07 +00:00
}
}
2013-06-29 21:08:09 +00:00
ot = XML_ChildOfTree ( tree , " attention " , 0 ) ;
if ( ot )
{
2013-07-13 12:14:32 +00:00
if ( jclient_poketime < jclient_curtime ) //throttle these.
{
jclient_poketime = jclient_curtime + 10 * 1000 ;
Con_SubPrintf ( ctx , " %s is an attention whore. \n " , f ) ;
pCon_SetActive ( ctx ) ;
if ( BUILTINISVALID ( LocalSound ) )
pLocalSound ( " misc/talk.wav " ) ;
}
}
ot = XML_ChildOfTree ( tree , " subject " , 0 ) ;
if ( ot & & ! strcmp ( type , " groupchat " ) )
{
unparsable = false ;
Con_SubPrintf ( ctx , " ^2%s^7 has set the topic to: %s \n " , f , ot - > body ) ;
}
ot = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/muc#user " , " x " , 0 ) ;
if ( ot & & f & & ! strchr ( f , ' / ' ) )
{
//this is an appaling extension protocol. we really have no way to know if someone's just making this shit up just to see our presence.
//this message came from the groupchat server.
xmltree_t * inv = XML_ChildOfTree ( ot , " invite " , 0 ) ;
if ( inv )
{
char * who = XML_GetParameter ( inv , " from " , " " ) ;
char * reason = XML_GetChildBody ( inv , " reason " , NULL ) ;
char * password = XML_GetChildBody ( ot , " password " , 0 ) ;
char link [ 512 ] ;
buddy_t * b ;
JCL_FindBuddy ( jcl , f , & b , NULL ) ;
if ( b - > chatroom )
return ; //we already know about it. don't spam.
JCL_GenLink ( jcl , link , sizeof ( link ) , " mucjoin " , f , NULL , password , " %s " , f ) ;
if ( reason )
Con_SubPrintf ( ctx , " * ^2%s^7 has invited you to join %s: %s. \n " , who , link , reason ) ;
else
Con_SubPrintf ( ctx , " * ^2%s^7 has invited you to join %s. \n " , who , link ) ;
return ; //ignore any body/jabber:x:conference
}
}
ot = XML_ChildOfTreeNS ( tree , " jabber:x:conference " , " x " , 0 ) ;
if ( ot )
{
char link [ 512 ] ;
char * chatjit = XML_GetParameter ( ot , " jid " , " " ) ;
char * reason = XML_GetParameter ( ot , " reason " , NULL ) ;
char * password = XML_GetParameter ( ot , " password " , NULL ) ;
unparsable = false ;
JCL_GenLink ( jcl , link , sizeof ( link ) , " mucjoin " , chatjit , NULL , password , " %s " , chatjit ) ;
if ( reason )
Con_SubPrintf ( ctx , " * ^2%s^7 has invited you to join %s: %s. \n " , f , link , reason ) ;
else
Con_SubPrintf ( ctx , " * ^2%s^7 has invited you to join %s. \n " , f , link ) ;
return ; //ignore any body
2013-06-29 21:08:09 +00:00
}
2013-06-29 16:01:07 +00:00
ot = XML_ChildOfTree ( tree , " body " , 0 ) ;
if ( ot )
{
unparsable = false ;
if ( f )
{
if ( ! strncmp ( ot - > body , " /me " , 4 ) )
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " * ^2%s^7%s \n " , f , ot - > body + 3 ) ;
2013-06-29 16:01:07 +00:00
else
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( ctx , " ^2%s^7: %s \n " , f , ot - > body ) ;
2013-06-29 16:01:07 +00:00
}
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 ) ;
}
}
}
}
2013-08-21 07:42:42 +00:00
unsigned int JCL_ParseCaps ( char * account , char * resource , xmltree_t * query )
2013-06-29 16:01:07 +00:00
{
xmltree_t * feature ;
unsigned int caps = 0 ;
qboolean rtp = false ;
qboolean rtpaudio = false ;
qboolean quake = false ;
qboolean ice = false ;
qboolean raw = false ;
qboolean jingle = false ;
2013-08-21 07:42:42 +00:00
qboolean si = false ;
qboolean sift = false ;
qboolean ibb = false ;
int i = 0 ;
char * var ;
// XML_ConPrintTree(query, 0);
2013-06-29 16:01:07 +00:00
while ( ( feature = XML_ChildOfTree ( query , " feature " , i + + ) ) )
{
var = XML_GetParameter ( feature , " var " , " " ) ;
//check ones we recognise.
2013-08-21 07:42:42 +00:00
// Con_Printf("%s/%s: %s\n", account, resource, var);
2013-06-29 16:01:07 +00:00
if ( ! strcmp ( var , QUAKEMEDIAXMLNS ) )
quake = true ;
2013-08-21 07:42:42 +00:00
# ifndef VOIP_LEGACY_ONLY
2013-06-29 16:01:07 +00:00
if ( ! strcmp ( var , " urn:xmpp:jingle:apps:rtp:audio " ) )
rtpaudio = true ;
2013-08-21 07:42:42 +00:00
# endif
2013-06-29 16:01:07 +00:00
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
2013-08-21 07:42:42 +00:00
# ifdef VOIP_LEGACY
if ( ! strcmp ( var , " http://www.google.com/xmpp/protocol/voice/v1 " ) )
caps | = CAP_FUGOOG_VOICE ; //legacy crap, so we can call google's official clients, which do not follow current xmpp standards.
if ( ! strcmp ( var , " http://www.google.com/xmpp/protocol/session " ) )
caps | = CAP_FUGOOG_SESSION ; //legacy crap, so we can call google's official clients, which do not follow current xmpp standards.
# endif
if ( ! strcmp ( var , " http://jabber.org/protocol/si " ) )
si = true ;
if ( ! strcmp ( var , " http://jabber.org/protocol/si/profile/file-transfer " ) )
sift = true ;
if ( ! strcmp ( var , " http://jabber.org/protocol/ibb " ) )
ibb = true ;
2013-06-29 16:01:07 +00:00
}
2013-08-21 07:42:42 +00:00
2013-06-29 16:01:07 +00:00
if ( ( ice | | raw ) & & jingle )
{
if ( rtpaudio & & rtp )
caps | = CAP_VOICE ;
if ( quake )
caps | = CAP_INVITE ;
}
2013-08-21 07:42:42 +00:00
if ( si & & sift & & ibb )
caps | = CAP_SIFT ;
return caps ;
}
void JCL_CheckClientCaps ( jclient_t * jcl , buddy_t * buddy , bresource_t * bres ) ;
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 ;
buddy_t * b , * ob ;
bresource_t * r , * or ;
JCL_FindBuddy ( jcl , iq - > to , & b , & r ) ;
if ( ! query )
caps = CAP_QUERYFAILED ;
else
{
// XML_ConPrintTree(tree, 0);
caps = JCL_ParseCaps ( b - > accountdomain , r - > resource , query ) ;
}
2013-06-29 16:01:07 +00:00
if ( b & & r )
2013-08-21 07:42:42 +00:00
{
if ( ! ( caps & CAP_QUERYFAILED ) )
{
// Con_Printf("%s/%s caps = %x\n", b->accountdomain, r->resource, caps);
if ( ! ( r - > caps & CAP_QUERIED ) )
r - > caps = CAP_QUERIED ; //reset it
r - > caps | = CAP_QUERIED | caps ;
//as we got a valid response, make sure other resources running the same software get the same caps
for ( ob = jcl - > buddies ; ob ; ob = ob - > next )
{
for ( or = ob - > resources ; or ; or = or - > next )
{
//ignore evil clients.
if ( r = = or | | ( or - > caps & ( CAP_QUERYFAILED | CAP_QUERIED | CAP_QUERYING ) ) | | ! or - > client_node )
continue ;
//all resources with the same details then get the same caps flags.
if ( ! strcmp ( r - > client_node , or - > client_node ) & & ! strcmp ( r - > client_ver , or - > client_ver ) & & ! strcmp ( r - > client_hash , or - > client_hash ) & & ! strcmp ( r - > client_ext , or - > client_ext ) )
{
// Con_Printf("%s/%s matches (updated) %s/%s (%s#%s)\n", ob->accountdomain, or->resource, b->accountdomain, r->resource, r->client_node, r->client_ver);
or - > caps = r - > caps ;
}
}
}
}
else
{
if ( ! ( r - > caps & ( CAP_QUERIED | CAP_QUERYFAILED ) ) )
{
r - > caps = CAP_QUERYFAILED ;
//as the response is invalid, we need to ensure that other resources that claim to use the same software are still queried anyway.
//(this is needed in case the one that we asked was spoofing)
for ( ob = jcl - > buddies ; ob ; ob = ob - > next )
{
for ( or = ob - > resources ; or ; or = or - > next )
{
//ignore evil clients.
if ( r = = or | | ( or - > caps & ( CAP_QUERYFAILED | CAP_QUERIED | CAP_QUERYING ) ) | | ! or - > client_node )
continue ;
//all resources with the same details then get the same caps flags.
if ( r - > client_node & & ! strcmp ( r - > client_node , or - > client_node ) & & ! strcmp ( r - > client_ver , or - > client_ver ) & & ! strcmp ( r - > client_hash , or - > client_hash ) & & ! strcmp ( r - > client_ext , or - > client_ext ) )
{
JCL_CheckClientCaps ( jcl , ob , or ) ;
return true ;
}
}
}
}
}
}
2013-06-29 16:01:07 +00:00
return true ;
}
2013-08-21 07:42:42 +00:00
void JCL_CheckClientCaps ( jclient_t * jcl , buddy_t * buddy , bresource_t * bres )
{
buddy_t * b ;
bresource_t * r ;
char extname [ 64 ] , * ext ;
//ignore it if we're already asking them...
if ( bres - > caps & ( CAP_QUERYING | CAP_QUERIED | CAP_QUERYFAILED ) )
return ;
//look for another resource that we might already know about that is the same client+ver and thus has the same caps.
if ( bres - > client_node )
for ( b = jcl - > buddies ; b ; b = b - > next )
{
for ( r = b - > resources ; r ; r = r - > next )
{
if ( r = = bres )
continue ;
//ignore evil clients.
if ( ( r - > caps & CAP_QUERYFAILED ) | | ! r - > client_node )
continue ;
if ( ! strcmp ( r - > client_node , bres - > client_node ) & & ! strcmp ( r - > client_ver , bres - > client_ver ) & & ! strcmp ( r - > client_hash , bres - > client_hash ) & & ! strcmp ( r - > client_ext , bres - > client_ext ) )
{
if ( r - > caps & CAP_QUERIED )
{
bres - > caps = r - > caps ;
// Con_Printf("%s/%s matches (known) %s/%s (%s#%s)\n", buddy->accountdomain, bres->resource, b->accountdomain, r->resource, r->client_node, r->client_ver);
return ;
}
if ( r - > caps & CAP_QUERYING )
{
//we're already asking one client with the same software. don't ask for dupes.
bres - > caps = 0 ;
// Con_Printf("%s/%s matches (pending) %s/%s (%s#%s)\n", buddy->accountdomain, bres->resource, b->accountdomain, r->resource, r->client_node, r->client_ver);
return ;
}
}
}
}
// Con_Printf("%s/%s querying (%s#%s)\n", buddy->accountdomain, bres->resource, bres->client_node, bres->client_ver);
//okay, this is the first time we've seen that software version. ask it what it supports, and hope we get a valid response...
bres - > caps = CAP_QUERYING ;
JCL_SendIQf ( jcl , JCL_ClientDiscoInfo , " get " , va ( " %s/%s " , buddy - > accountdomain , bres - > resource ) , " <query xmlns='http://jabber.org/protocol/disco#info' node='%s#%s'/> " , bres - > client_node , bres - > client_ver ) ;
//and ask for info about each extension too. which should only be used if the specified version isn't a hash.
if ( bres - > client_hash & & ! * bres - > client_hash )
{
for ( ext = bres - > client_ext ; ext ; )
{
ext = JCL_ParseOut ( ext , extname , sizeof ( extname ) ) ;
if ( * extname )
JCL_SendIQf ( jcl , JCL_ClientDiscoInfo , " get " , va ( " %s/%s " , buddy - > accountdomain , bres - > resource ) , " <query xmlns='http://jabber.org/protocol/disco#info' node='%s#%s'/> " , bres - > client_node , extname ) ;
}
}
}
2013-06-29 16:01:07 +00:00
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 ) ;
2013-07-13 12:14:32 +00:00
xmltree_t * muc = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/muc#user " , " x " , 0 ) ;
2013-08-21 07:42:42 +00:00
xmltree_t * caps = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/caps " , " c " , 0 ) ;
2013-06-29 16:01:07 +00:00
char * type = XML_GetParameter ( tree , " type " , " " ) ;
char * serverip = NULL ;
char * servermap = NULL ;
2013-07-13 12:14:32 +00:00
char startconvo [ 512 ] ;
char oldbstatus [ 128 ] ;
char oldfstatus [ 128 ] ;
2013-06-29 16:01:07 +00:00
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 " ) )
{
2013-07-13 12:14:32 +00:00
char pauth [ 512 ] , pdeny [ 512 ] ;
JCL_GenLink ( jcl , startconvo , sizeof ( startconvo ) , NULL , from , NULL , NULL , " %s " , from ) ;
JCL_GenLink ( jcl , pauth , sizeof ( pauth ) , " pauth " , from , NULL , NULL , " %s " , " Authorize " ) ;
JCL_GenLink ( jcl , pdeny , sizeof ( pdeny ) , " pdeny " , from , NULL , NULL , " %s " , " Deny " ) ;
Con_Printf ( " %s wants to be your friend! %s %s \n " , startconvo , pauth , pdeny ) ;
2013-06-29 16:01:07 +00:00
}
else if ( type & & ! strcmp ( type , " subscribed " ) )
{
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , startconvo , sizeof ( startconvo ) , NULL , from , NULL , NULL , " %s " , from ) ;
Con_Printf ( " %s is now your friend! \n " , startconvo ) ;
2013-06-29 16:01:07 +00:00
}
else if ( type & & ! strcmp ( type , " unsubscribe " ) )
{
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , startconvo , sizeof ( startconvo ) , NULL , from , NULL , NULL , " %s " , from ) ;
Con_Printf ( " %s has unfriended you \n " , startconvo ) ;
2013-06-29 16:01:07 +00:00
}
else if ( type & & ! strcmp ( type , " unsubscribed " ) )
{
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , startconvo , sizeof ( startconvo ) , NULL , from , NULL , NULL , " %s " , from ) ;
Con_Printf ( " %s is no longer unfriended you \n " , startconvo ) ;
2013-06-29 16:01:07 +00:00
}
else
{
JCL_FindBuddy ( jcl , from , & buddy , & bres ) ;
2013-10-08 14:28:11 +00:00
if ( ! bres )
{
JCL_FindBuddy ( jcl , va ( " %s/ " , from ) , & buddy , & bres ) ;
}
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , startconvo , sizeof ( startconvo ) , NULL , from , NULL , NULL , " %s " , buddy - > name ) ;
2013-06-29 16:01:07 +00:00
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 ) ) ;
}
2013-07-13 12:14:32 +00:00
Q_strlcpy ( oldbstatus , bres - > bstatus , sizeof ( oldbstatus ) ) ;
Q_strlcpy ( oldfstatus , bres - > fstatus , sizeof ( oldfstatus ) ) ;
2013-06-29 16:01:07 +00:00
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 ) ) ;
2013-08-21 07:42:42 +00:00
if ( caps )
2013-06-29 16:01:07 +00:00
{
2013-08-21 07:42:42 +00:00
char * ext = XML_GetParameter ( caps , " ext " , " " ) ; //deprecated
char * ver = XML_GetParameter ( caps , " ver " , " " ) ;
char * node = XML_GetParameter ( caps , " node " , " " ) ;
char * hash = XML_GetParameter ( caps , " hash " , " " ) ;
if ( ! bres - > client_hash | | strcmp ( ext , bres - > client_ext ) | | strcmp ( hash , bres - > client_hash ) | | strcmp ( node , bres - > client_node ) | | strcmp ( ver , bres - > client_ver ) )
{
bres - > caps & = ~ ( CAP_QUERIED | CAP_QUERYING | CAP_QUERYFAILED ) ; //no idea what the new caps are.
free ( bres - > client_ext ) ;
free ( bres - > client_hash ) ;
free ( bres - > client_node ) ;
free ( bres - > client_ver ) ;
bres - > client_ext = strdup ( ext ) ;
bres - > client_hash = strdup ( hash ) ;
bres - > client_node = strdup ( node ) ;
bres - > client_ver = strdup ( ver ) ;
}
2013-06-29 16:01:07 +00:00
}
2013-08-21 07:42:42 +00:00
JCL_CheckClientCaps ( jcl , buddy , bres ) ;
2013-06-29 16:01:07 +00:00
}
2013-07-13 12:14:32 +00:00
if ( muc )
{
JCL_GenLink ( jcl , startconvo , sizeof ( startconvo ) , NULL , from , NULL , NULL , " %s " , bres - > resource ) ;
if ( type & & ! strcmp ( type , " unavailable " ) )
Con_SubPrintf ( buddy - > name , " %s has left the conversation \n " , bres - > resource ) ;
else if ( strcmp ( oldbstatus , bres - > bstatus ) )
Con_SubPrintf ( buddy - > name , " %s is now %s \n " , startconvo , bres - > bstatus ) ;
}
2013-06-29 16:01:07 +00:00
else
2013-07-13 12:14:32 +00:00
{
if ( bres - > servertype = = 2 )
{
char joinlink [ 512 ] ;
JCL_GenLink ( jcl , joinlink , sizeof ( joinlink ) , " join " , from , NULL , NULL , " Playing Quake - %s " , bres - > server ) ;
Con_Printf ( " %s is now %s \n " , startconvo , joinlink ) ;
}
else if ( bres - > servertype = = 1 )
Con_Printf ( " %s is now ^[[Playing Quake - %s] \\ observe \\ %s^] \n " , startconvo , bres - > server , bres - > server ) ;
2013-08-21 07:42:42 +00:00
else if ( ! pCvar_GetFloat ( " xmpp_hidestatusupdates " ) & & ( strcmp ( oldbstatus , bres - > bstatus ) | | strcmp ( oldfstatus , bres - > fstatus ) ) )
2013-07-13 12:14:32 +00:00
{
if ( * bres - > fstatus )
Con_Printf ( " %s is now %s: %s \n " , startconvo , bres - > bstatus , bres - > fstatus ) ;
else
Con_Printf ( " %s is now %s \n " , startconvo , bres - > bstatus ) ;
}
}
2013-06-29 16:01:07 +00:00
2013-07-13 12:14:32 +00:00
if ( type & & ! strcmp ( type , " unavailable " ) )
2013-06-29 16:01:07 +00:00
{
//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-07-13 12:14:32 +00:00
if ( jcl - > streamdebug & & tree )
2013-06-23 02:33:52 +00:00
{
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-07-13 12:14:32 +00:00
if ( ( ! jcl - > issecure ) & & BUILTINISVALID ( Net_SetTLSClient ) & & XML_ChildOfTree ( tree , " starttls " , 0 ) ! = NULL & & jcl - > forcetls > = 0 )
2005-12-11 20:11:22 +00:00
{
2013-08-21 07:42:42 +00:00
Con_DPrintf ( " XMPP: 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-07-13 12:14:32 +00:00
xmltree_t * m ;
int sm ;
char out [ 512 ] ;
char * method = NULL ;
int outlen = - 1 ;
if ( jcl - > forcetls > 0 & & ! jcl - > issecure )
2005-12-11 20:11:22 +00:00
{
2013-10-08 14:28:11 +00:00
if ( BUILTINISVALID ( Net_SetTLSClient ) )
{
Con_Printf ( " XMPP: Unable to switch to TLS. You are probably being man-in-the-middle attacked. \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
}
else
{
Con_Printf ( " XMPP: Unable to switch to TLS. Your engine does not provide the feature. You can use the xmpp /autoconnect command to register your account instead, as this does not enforce the use of tls (but does still use it in case your future engine versions support it). \n " ) ;
}
2013-07-13 12:14:32 +00:00
XML_Destroy ( tree ) ;
2013-10-08 14:28:11 +00:00
return JCL_KILL ;
2013-06-24 09:04:00 +00:00
}
2013-07-13 12:14:32 +00:00
for ( sm = 0 ; sm < sizeof ( saslmethods ) / sizeof ( saslmethods [ 0 ] ) ; sm + + )
2013-06-24 09:04:00 +00:00
{
2013-07-13 12:14:32 +00:00
method = saslmethods [ sm ] . method ? saslmethods [ sm ] . method : jcl - > oauth2 . saslmethod ;
if ( ! * method )
continue ;
for ( m = ot - > child ; m ; m = m - > sibling )
{
if ( ! strcmp ( m - > body , method ) )
{
outlen = saslmethods [ sm ] . sasl_initial ( jcl , out , sizeof ( out ) ) ;
if ( outlen ! = - 1 )
break ;
}
}
if ( outlen ! = - 1 )
break ;
2013-06-24 09:04:00 +00:00
}
2013-07-13 12:14:32 +00:00
if ( outlen = = - 2 )
{
XML_Destroy ( tree ) ;
2013-10-08 14:28:11 +00:00
return JCL_KILL ;
2013-07-13 12:14:32 +00:00
}
if ( outlen > = 0 )
2013-06-24 09:04:00 +00:00
{
2013-07-13 12:14:32 +00:00
jcl - > authmode = sm ;
Base64_Add ( out , outlen ) ;
2013-06-24 09:04:00 +00:00
Base64_Finish ( ) ;
2013-07-13 12:14:32 +00:00
2013-08-21 07:42:42 +00:00
Con_DPrintf ( " XMPP: Authing with %s%s. \n " , method , jcl - > issecure ? " over tls " : " without encription " ) ;
2013-07-13 12:14:32 +00:00
JCL_AddClientMessagef ( jcl , " <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='%s' "
" auth:service='oauth2' "
" xmlns:auth='http://www.google.com/talk/protocol/auth' "
" >%s</auth> " , method , base64 ) ;
2013-06-24 09:04:00 +00:00
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 ) ;
2013-10-08 14:28:11 +00:00
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 ) ;
2013-10-08 14:28:11 +00:00
return JCL_KILL ;
2005-12-11 20:11:22 +00:00
}
}
}
2013-07-13 12:14:32 +00:00
else if ( ! strcmp ( tree - > name , " challenge " ) & & ! strcmp ( tree - > xmlns , " urn:ietf:params:xml:ns:xmpp-sasl " ) & & jcl - > authmode > = 0 )
{
char in [ 512 ] ;
int inlen ;
char out [ 512 ] ;
int outlen ;
inlen = Base64_Decode ( in , sizeof ( in ) , tree - > body , strlen ( tree - > body ) ) ;
outlen = saslmethods [ jcl - > authmode ] . sasl_challenge ( jcl , in , inlen , out , sizeof ( out ) ) ;
if ( outlen < 0 )
{
Con_Printf ( " XMPP: unable to auth with server \n " ) ;
XML_Destroy ( tree ) ;
return JCL_KILL ;
}
Base64_Add ( out , outlen ) ;
2013-06-24 09:04:00 +00:00
Base64_Finish ( ) ;
JCL_AddClientMessagef ( jcl , " <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response> " , base64 ) ;
2013-07-13 12:14:32 +00:00
unparsable = false ;
2013-06-24 09:04:00 +00:00
}
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-07-13 12:14:32 +00:00
//when using srv records, the certificate must match the user's domain, rather than merely the hostname of the server.
//if you want to match the hostname of the server, use (oldstyle) tlsconnect directly instead.
if ( pNet_SetTLSClient ( jcl - > socket , jcl - > certificatedomain ) < 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 ;
}
2013-07-13 12:14:32 +00:00
if ( ! * jcl - > certificatedomain )
Con_Printf ( " XMPP: WARNING: Connecting via TLS without validating certificate \n " ) ;
jcl - > issecure = true ;
2005-12-11 20:11:22 +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'> " ) ;
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 " ) )
{
2013-07-13 12:14:32 +00:00
ot = XML_ChildOfTree ( tree , " see-other-host " , 0 ) ;
2013-06-29 16:01:07 +00:00
if ( ot )
2013-07-13 12:14:32 +00:00
{
//msn needs this, apparently
Q_strlcpy ( jcl - > redirserveraddr , ot - > body , sizeof ( jcl - > redirserveraddr ) ) ;
JCL_CloseConnection ( jcl , true ) ;
if ( ! JCL_Reconnect ( jcl ) )
return JCL_KILL ;
return JCL_CONTINUE ;
}
2013-06-29 16:01:07 +00:00
else
2013-07-13 12:14:32 +00:00
{
ot = XML_ChildOfTree ( tree , " text " , 0 ) ;
if ( ot )
Con_Printf ( " XMPP: %s \n " , ot - > body ) ;
else
Con_Printf ( " XMPP: Unknown error \n " ) ;
2013-06-29 16:01:07 +00:00
2013-07-13 12:14:32 +00:00
ot = XML_ChildOfTree ( tree , " conflict " , 0 ) ;
XML_Destroy ( tree ) ;
2013-06-29 16:01:07 +00:00
2013-07-13 12:14:32 +00:00
if ( ot )
return JCL_NUKEFROMORBIT ;
else
return JCL_KILL ;
}
2013-06-29 16:01:07 +00:00
}
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
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 " ) ;
2013-07-13 12:14:32 +00:00
XML_ConPrintTree ( tree , 0 ) ;
XML_Destroy ( tree ) ;
2005-12-11 20:11:22 +00:00
return JCL_KILL ;
}
2013-07-13 12:14:32 +00:00
XML_Destroy ( tree ) ;
2005-12-11 20:11:22 +00:00
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.
2013-07-13 12:14:32 +00:00
if ( jcl - > status ! = JCL_DEAD & & jcl - > status ! = JCL_INACTIVE )
Con_Printf ( " XMPP: Disconnected from %s@%s \n " , jcl - > username , jcl - > domain ) ;
2013-06-29 16:01:07 +00:00
if ( jcl - > status = = JCL_ACTIVE )
JCL_AddClientMessageString ( jcl , " <presence type='unavailable'/> " ) ;
2013-07-13 12:14:32 +00:00
if ( jcl - > status ! = JCL_DEAD & & jcl - > status ! = JCL_INACTIVE )
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-07-13 12:14:32 +00:00
if ( jcl - > socket ! = - 1 )
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 )
{
2013-07-13 12:14:32 +00:00
int i ;
2013-06-29 16:01:07 +00:00
free ( jcl ) ;
2013-07-13 12:14:32 +00:00
for ( i = 0 ; i < sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) ; i + + )
{
if ( jclients [ i ] = = jcl )
jclients [ i ] = NULL ;
}
2013-06-29 16:01:07 +00:00
}
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-08-21 07:42:42 +00:00
//xep-0115 1.4+
Q_snprintf ( caps , sizeof ( caps ) ,
" <c xmlns='http://jabber.org/protocol/caps' "
" hash='sha-1' "
" node=' " DISCONODE " ' "
" ver='%s' "
# ifdef VOIP_LEGACY
" ext='voice-v1' "
# endif
" /> " , buildcapshash ( ) ) ;
2013-06-23 02:33:52 +00:00
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
{
2013-07-13 12:14:32 +00:00
int i ;
2013-06-29 16:01:07 +00:00
jclient_curtime = args [ 0 ] ;
2013-07-13 12:14:32 +00:00
for ( i = 0 ; i < sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) ; i + + )
2005-12-11 20:11:22 +00:00
{
2013-07-13 12:14:32 +00:00
jclient_t * jcl = jclients [ i ] ;
if ( jcl & & jcl - > status ! = JCL_INACTIVE )
2013-03-31 04:21:08 +00:00
{
2013-07-13 12:14:32 +00:00
int stat = JCL_CONTINUE ;
if ( jcl - > status = = JCL_DEAD )
2013-06-29 16:01:07 +00:00
{
2013-07-13 12:14:32 +00:00
if ( jclient_curtime > jcl - > timeout )
{
JCL_Reconnect ( jcl ) ;
jcl - > timeout = jclient_curtime + 60 * 1000 ;
}
2013-06-29 16:01:07 +00:00
}
else
2013-07-13 12:14:32 +00:00
{
if ( jcl - > connected )
JCL_GeneratePresence ( jcl , false ) ;
while ( stat = = JCL_CONTINUE )
stat = JCL_ClientFrame ( jcl ) ;
if ( stat = = JCL_NUKEFROMORBIT )
{
JCL_CloseConnection ( jcl , true ) ;
jcl - > status = JCL_INACTIVE ;
}
else if ( stat = = JCL_KILL )
JCL_CloseConnection ( jcl , true ) ;
else
JCL_FlushOutgoing ( jcl ) ;
}
2013-08-21 07:42:42 +00:00
# ifdef JINGLE
JCL_JingleTimeouts ( jcl , false ) ;
# endif
JCL_IQTimeouts ( jcl ) ;
2005-12-11 20:11:22 +00:00
}
}
return 0 ;
}
2013-06-23 02:33:52 +00:00
void JCL_WriteConfig ( void )
{
2013-07-13 12:14:32 +00:00
xmltree_t * m , * n , * oauth2 ;
int i ;
qhandle_t config ;
m = XML_CreateNode ( NULL , " xmppaccounts " , " " , " " ) ;
for ( i = 0 ; i < sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) ; i + + )
2013-06-23 02:33:52 +00:00
{
2013-07-13 12:14:32 +00:00
jclient_t * jcl = jclients [ i ] ;
if ( jcl )
2013-06-23 02:33:52 +00:00
{
2013-07-13 12:14:32 +00:00
char foo [ 64 ] ;
n = XML_CreateNode ( m , " account " , " " , " " ) ;
XML_AddParameteri ( n , " id " , i ) ;
XML_CreateNode ( n , " streamdebug " , " " , jcl - > streamdebug ? " 1 " : " 0 " ) ;
Q_snprintf ( foo , sizeof ( foo ) , " %i " , jcl - > forcetls ) ;
XML_CreateNode ( n , " forcetls " , " " , foo ) ;
XML_CreateNode ( n , " allowauth_plain_nontls " , " " , jcl - > allowauth_plainnontls ? " 1 " : " 0 " ) ;
XML_CreateNode ( n , " allowauth_plain_tls " , " " , jcl - > allowauth_plaintls ? " 1 " : " 0 " ) ;
XML_CreateNode ( n , " allowauth_digest_md5 " , " " , jcl - > allowauth_digestmd5 ? " 1 " : " 0 " ) ;
XML_CreateNode ( n , " allowauth_scram_sha_1 " , " " , jcl - > allowauth_scramsha1 ? " 1 " : " 0 " ) ;
if ( * jcl - > oauth2 . saslmethod )
{
XML_CreateNode ( n , " allowauth_oauth2 " , " " , jcl - > allowauth_oauth2 ? " 1 " : " 0 " ) ;
oauth2 = XML_CreateNode ( n , " oauth2 " , " " , " " ) ;
XML_AddParameter ( oauth2 , " method " , jcl - > oauth2 . saslmethod ) ;
XML_CreateNode ( oauth2 , " obtain-url " , " " , jcl - > oauth2 . obtainurl ) ;
XML_CreateNode ( oauth2 , " refresh-url " , " " , jcl - > oauth2 . refreshurl ) ;
XML_CreateNode ( oauth2 , " client-id " , " " , jcl - > oauth2 . clientid ) ;
XML_CreateNode ( oauth2 , " client-secret " , " " , jcl - > oauth2 . clientsecret ) ;
XML_CreateNode ( oauth2 , " scope " , " " , jcl - > oauth2 . scope ) ;
XML_CreateNode ( oauth2 , " auth-token " , " " , jcl - > oauth2 . authtoken ) ;
XML_CreateNode ( oauth2 , " refresh-token " , " " , jcl - > oauth2 . refreshtoken ) ;
XML_CreateNode ( oauth2 , " access-token " , " " , jcl - > oauth2 . accesstoken ) ;
}
XML_CreateNode ( n , " username " , " " , jcl - > username ) ;
XML_CreateNode ( n , " domain " , " " , jcl - > domain ) ;
XML_CreateNode ( n , " resource " , " " , jcl - > resource ) ;
if ( ! * jcl - > oauth2 . saslmethod | | ! jcl - > allowauth_oauth2 | | * jcl - > password ) //avoid writing password lest we encourage someone to supply it when its not useful
XML_CreateNode ( n , " password " , " " , jcl - > password ) ; //FIXME: should we base64 this just to obscure it?
XML_CreateNode ( n , " serveraddr " , " " , jcl - > serveraddr ) ;
Q_snprintf ( foo , sizeof ( foo ) , " %i " , jcl - > serverport ) ;
XML_CreateNode ( n , " serverport " , " " , foo ) ;
XML_CreateNode ( n , " certificatedomain " , " " , jcl - > certificatedomain ) ;
XML_CreateNode ( n , " inactive " , " " , jcl - > status = = JCL_INACTIVE ? " 1 " : " 0 " ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-07-13 12:14:32 +00:00
pFS_Open ( " **plugconfig " , & config , 2 ) ;
if ( config > = 0 )
{
char * s = XML_GenerateString ( m , true ) ;
pFS_Write ( config , s , strlen ( s ) ) ;
free ( s ) ;
pFS_Close ( config ) ;
}
XML_Destroy ( m ) ;
2013-06-23 02:33:52 +00:00
}
void JCL_LoadConfig ( void )
{
2013-07-13 12:14:32 +00:00
if ( ! jclients [ 0 ] )
2013-06-23 02:33:52 +00:00
{
int len ;
qhandle_t config ;
char buf [ 8192 ] ;
qboolean oldtls ;
2013-06-23 18:43:59 +00:00
len = pFS_Open ( " **plugconfig " , & config , 1 ) ;
2013-07-14 12:22:51 +00:00
if ( len > = 0 )
2013-06-23 02:33:52 +00:00
{
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-07-14 12:22:51 +00:00
if ( len & & * buf ! = ' < ' )
2013-07-13 12:14:32 +00:00
{ //legacy code, to be removed
char * line = buf ;
char tls [ 256 ] ;
char server [ 256 ] ;
char account [ 256 ] ;
char password [ 256 ] ;
line = JCL_ParseOut ( line , tls , sizeof ( tls ) ) ;
line = JCL_ParseOut ( line , server , sizeof ( server ) ) ;
line = JCL_ParseOut ( line , account , sizeof ( account ) ) ;
line = JCL_ParseOut ( line , password , sizeof ( password ) ) ;
oldtls = atoi ( tls ) ;
2013-07-14 12:22:51 +00:00
Con_Printf ( " Legacy config: %s (%i) \n " , buf , len ) ;
2013-07-13 12:14:32 +00:00
jclients [ 0 ] = JCL_Connect ( 0 , server , oldtls , account , password ) ;
}
else
{
xmltree_t * accs ;
int start = 0 ;
accs = XML_Parse ( buf , & start , len , false , " " ) ;
if ( accs )
{
int i ;
xmltree_t * acc ;
for ( i = 0 ; ( acc = XML_ChildOfTree ( accs , " account " , i ) ) ; i + + )
{
int id = atoi ( XML_GetParameter ( acc , " id " , " 0 " ) ) ;
if ( id < 0 | | id > = sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) | | jclients [ id ] )
continue ;
2013-06-23 02:33:52 +00:00
2013-07-13 12:14:32 +00:00
jclients [ id ] = JCL_ConnectXML ( acc ) ;
}
XML_Destroy ( accs ) ;
}
}
2013-06-23 02:33:52 +00:00
}
}
}
2013-07-13 12:14:32 +00:00
//on shutdown, write config and close connections.
qintptr_t JCL_Shutdown ( qintptr_t * args )
{
jclient_t * jcl ;
int i ;
JCL_WriteConfig ( ) ;
for ( i = 0 ; i < sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) ; i + + )
{
jcl = jclients [ i ] ;
if ( jcl )
JCL_CloseConnection ( jcl , false ) ;
}
2013-07-29 17:36:44 +00:00
// if (_CrtDumpMemoryLeaks())
// OutputDebugStringA("Leaks detected\n");
2013-07-13 12:14:32 +00:00
return true ;
}
static void JCL_PrintBuddyStatus ( char * console , jclient_t * jcl , buddy_t * b , bresource_t * r )
2013-06-29 16:01:07 +00:00
{
if ( r - > servertype = = 2 )
2013-07-13 12:14:32 +00:00
{
char joinlink [ 512 ] ;
JCL_GenLink ( jcl , joinlink , sizeof ( joinlink ) , " join " , b - > accountdomain , r - > resource , NULL , " Playing Quake - %s " , r - > server ) ;
Con_SubPrintf ( console , " %s " , joinlink ) ;
}
2013-06-29 16:01:07 +00:00
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 )
2013-07-13 12:14:32 +00:00
{
char invitelink [ 512 ] ;
JCL_GenLink ( jcl , invitelink , sizeof ( invitelink ) , " invite " , b - > accountdomain , r - > resource , NULL , " %s " , " Invite " ) ;
Con_SubPrintf ( console , " %s " , invitelink ) ;
}
2013-06-29 16:01:07 +00:00
if ( r - > caps & CAP_VOICE )
2013-07-13 12:14:32 +00:00
{
char calllink [ 512 ] ;
JCL_GenLink ( jcl , calllink , sizeof ( calllink ) , " call " , b - > accountdomain , r - > resource , NULL , " %s " , " Call " ) ;
Con_SubPrintf ( console , " %s " , calllink ) ;
}
2013-08-21 07:42:42 +00:00
else if ( ( r - > caps & CAP_FUGOOG_VOICE ) & & ( r - > caps & CAP_FUGOOG_SESSION ) )
{
char calllink [ 512 ] ;
JCL_GenLink ( jcl , calllink , sizeof ( calllink ) , " call " , b - > accountdomain , r - > resource , NULL , " %s " , " Call(NS) " ) ;
Con_SubPrintf ( console , " %s " , calllink ) ;
}
2013-06-29 16:01:07 +00:00
}
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-07-13 12:14:32 +00:00
struct ft_s * ft ;
char convolink [ 512 ] , actlink [ 512 ] ;
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 )
2013-07-13 12:14:32 +00:00
{
2013-06-23 02:33:52 +00:00
//if we don't actually know them, don't list them.
2013-07-13 12:14:32 +00:00
if ( ! b - > friended & & ! b - > chatroom )
2013-06-23 02:33:52 +00:00
continue ;
if ( ! b - > resources ) //offline
{
if ( all )
2013-07-13 12:14:32 +00:00
{
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , NULL , NULL , " ^s^7%s^r " , b - > name ) ;
Con_SubPrintf ( console , " %s: offline \n " , convolink ) ;
}
2013-06-23 02:33:52 +00:00
}
else if ( b - > resources - > next )
{ //multiple potential resources
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , NULL , NULL , " %s " , b - > name ) ;
Con_SubPrintf ( console , " %s: \n " , convolink ) ;
2013-06-23 02:33:52 +00:00
for ( r = b - > resources ; r ; r = r - > next )
{
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , r - > resource , NULL , " %s " , r - > resource ) ;
Con_SubPrintf ( console , " %s: " , convolink ) ;
JCL_PrintBuddyStatus ( console , jcl , b , r ) ;
2013-06-29 16:01:07 +00:00
Con_SubPrintf ( console , " \n " ) ;
2013-06-23 02:33:52 +00:00
}
}
else //only one resource
{
r = b - > resources ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , r - > resource , NULL , " %s " , b - > name ) ;
Con_SubPrintf ( console , " %s: " , convolink ) ;
JCL_PrintBuddyStatus ( console , jcl , b , r ) ;
2013-06-29 16:01:07 +00:00
Con_SubPrintf ( console , " \n " ) ;
}
}
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
2013-06-29 16:01:07 +00:00
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 :
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , r - > resource , NULL , " %s " , b - > name ) ;
JCL_GenLink ( jcl , actlink , sizeof ( actlink ) , " jdeny " , c2c - > with , NULL , c2c - > sid , " %s " , " Hang Up " ) ;
Con_SubPrintf ( console , " %s: voice %s \n " , convolink , actlink ) ;
2013-06-29 16:01:07 +00:00
break ;
case ICEP_QWSERVER :
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , r - > resource , NULL , " %s " , b - > name ) ;
JCL_GenLink ( jcl , actlink , sizeof ( actlink ) , " jdeny " , c2c - > with , NULL , c2c - > sid , " %s " , " Kick " ) ;
Con_SubPrintf ( console , " %s: server %s \n " , convolink , actlink ) ;
2013-06-29 16:01:07 +00:00
break ;
case ICEP_QWCLIENT :
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , r - > resource , NULL , " %s " , b - > name ) ;
JCL_GenLink ( jcl , actlink , sizeof ( actlink ) , " jdeny " , c2c - > with , NULL , c2c - > sid , " %s " , " Disconnect " ) ;
Con_SubPrintf ( console , " %s: client %s \n " , convolink , actlink ) ;
2013-06-29 16:01:07 +00:00
break ;
2013-06-23 02:33:52 +00:00
}
}
2013-07-13 12:14:32 +00:00
# endif
# ifdef FILETRANSFERS
if ( jcl - > ft )
Con_SubPrintf ( console , " Active file transfers: \n " ) ;
for ( ft = jcl - > ft ; ft ; ft = ft - > next )
{
JCL_FindBuddy ( jcl , ft - > with , & b , & r ) ;
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , b - > accountdomain , r - > resource , NULL , " %s " , b - > name ) ;
JCL_GenLink ( jcl , actlink , sizeof ( actlink ) , " ftdeny " , ft - > with , NULL , ft - > sid , " %s " , " Hang Up " ) ;
Con_SubPrintf ( console , " %s: %s \n " , convolink , ft - > fname ) ;
}
# endif
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-07-13 12:14:32 +00:00
char * con ;
2013-06-23 02:33:52 +00:00
buddy_t * b ;
bresource_t * br ;
JCL_FindBuddy ( jcl , to , & b , & br ) ;
2013-07-13 12:14:32 +00:00
if ( b - > chatroom )
{
if ( br )
{
JCL_AddClientMessagef ( jcl , " <message to='%s/%s' type='chat'><body> " , b - > accountdomain , br - > resource ) ;
con = to ;
}
else
{
JCL_AddClientMessagef ( jcl , " <message to='%s' type='groupchat'><body> " , b - > accountdomain ) ;
con = b - > name ;
}
}
2013-06-23 02:33:52 +00:00
else
2013-07-13 12:14:32 +00:00
{
con = b - > name ;
if ( ! br )
br = b - > defaultresource ;
if ( br )
JCL_AddClientMessagef ( jcl , " <message to='%s/%s'><body> " , b - > accountdomain , br - > resource ) ;
else
JCL_AddClientMessagef ( jcl , " <message to='%s'><body> " , b - > accountdomain ) ;
}
2013-06-23 02:33:52 +00:00
JCL_AddClientMessage ( jcl , markup , XML_Markup ( msg , markup , sizeof ( markup ) ) - markup ) ;
JCL_AddClientMessageString ( jcl , " </body></message> " ) ;
2013-07-13 12:14:32 +00:00
if ( b - > chatroom & & ! br )
return ;
2013-06-29 16:01:07 +00:00
if ( ! strncmp ( msg , " /me " , 4 ) )
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( con , " * ^5%s^7 " COLOURYELLOW " %s \n " , ( ( ! strcmp ( jcl - > localalias , " >> " ) ) ? " me " : jcl - > localalias ) , msg + 3 ) ;
2013-06-29 16:01:07 +00:00
else
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( con , " ^5%s^7: " COLOURYELLOW " %s \n " , jcl - > localalias , msg ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 21:08:09 +00:00
void JCL_AttentionMessage ( jclient_t * jcl , char * to , char * msg )
{
char fullto [ 256 ] ;
buddy_t * b ;
bresource_t * br ;
xmltree_t * m ;
char * s ;
2013-06-23 02:33:52 +00:00
2013-06-29 21:08:09 +00:00
JCL_FindBuddy ( jcl , to , & b , & br ) ;
if ( ! br )
br = b - > defaultresource ;
if ( ! br )
br = b - > resources ;
if ( ! br )
{
Con_SubPrintf ( b - > name , " User is not online \n " ) ;
return ;
}
Q_snprintf ( fullto , sizeof ( fullto ) , " %s/%s " , b - > accountdomain , br - > resource ) ;
m = XML_CreateNode ( NULL , " message " , " " , " " ) ;
XML_AddParameter ( m , " to " , fullto ) ;
// XML_AddParameter(m, "type", "headline");
XML_CreateNode ( m , " attention " , " urn:xmpp:attention:0 " , " " ) ;
if ( msg )
XML_CreateNode ( m , " body " , " " , msg ) ;
2013-07-13 12:14:32 +00:00
s = XML_GenerateString ( m , false ) ;
2013-06-29 21:08:09 +00:00
JCL_AddClientMessageString ( jcl , s ) ;
free ( s ) ;
XML_Destroy ( m ) ;
if ( msg )
{
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 ) ;
}
}
void JCL_ToJID ( jclient_t * jcl , char * in , char * out , int outsize )
{
//decompose links first
if ( in [ 0 ] = = ' ^ ' & & in [ 1 ] = = ' [ ' )
{
char * sl ;
char * le ;
sl = in + 2 ;
sl = strchr ( in , ' \\ ' ) ;
if ( sl )
{
le = strstr ( sl , " ^] " ) ;
if ( le )
{
* le = 0 ;
JCL_Info_ValueForKey ( in , " xmpp " , out , outsize ) ;
* le = ' ^ ' ;
return ;
}
}
}
2013-06-29 21:29:56 +00:00
if ( ! strchr ( in , ' @ ' ) & & jcl )
2013-06-29 21:08:09 +00:00
{
//no @? probably its an alias, but could also be a server/domain perhaps. not sure we care. you'll just have to rename your friend.
//check to see if we can find a friend going by that name
//fixme: allow resources to make it through here
buddy_t * b ;
for ( b = jcl - > buddies ; b ; b = b - > next )
{
if ( ! strcasecmp ( b - > name , in ) )
{
2013-07-13 12:14:32 +00:00
if ( b - > defaultresource )
Q_snprintf ( out , outsize , " %s/%s " , b - > accountdomain , b - > defaultresource - > resource ) ;
else
Q_strlcpy ( out , b - > accountdomain , outsize ) ;
2013-06-29 21:08:09 +00:00
return ;
}
}
}
//a regular jabber account name
Q_strlcpy ( out , in , outsize ) ;
}
2013-06-23 02:33:52 +00:00
2013-07-13 12:14:32 +00:00
//server may be null, in which case its expected to be folded into room.
void JCL_JoinMUCChat ( jclient_t * jcl , char * room , char * server , char * myhandle , char * password )
{
char caps [ 512 ] ;
char roomserverhandle [ 512 ] ;
buddy_t * b ;
bresource_t * r ;
if ( ! myhandle )
myhandle = jcl - > username ;
if ( server )
Q_snprintf ( roomserverhandle , sizeof ( roomserverhandle ) , " %s@%s/%s " , room , server , myhandle ) ;
else
Q_snprintf ( roomserverhandle , sizeof ( roomserverhandle ) , " %s/%s " , room , myhandle ) ;
JCL_FindBuddy ( jcl , roomserverhandle , & b , & r ) ;
b - > chatroom = true ;
2013-08-21 07:42:42 +00:00
Q_snprintf ( caps , sizeof ( caps ) ,
" <c xmlns='http://jabber.org/protocol/caps' "
" hash='sha-1' "
" node=' " DISCONODE " ' "
" ver='%s' "
# ifdef VOIP_LEGACY
" ext='voice-v1' "
# endif
" /> " , buildcapshash ( ) ) ;
2013-07-13 12:14:32 +00:00
JCL_AddClientMessagef ( jcl , " <presence to='%s'><x xmlns='http://jabber.org/protocol/muc'><password>%s</password></x>%s</presence> " , roomserverhandle , password , caps ) ;
}
# ifdef FILETRANSFERS
qboolean JCL_FT_IBBChunked ( jclient_t * jcl , xmltree_t * x , struct iq_s * iq )
{
char * from = XML_GetParameter ( x , " from " , " " ) ;
struct ft_s * ft = iq - > usrptr , * * link , * v ;
for ( link = & jcl - > ft ; ( v = * link ) ; link = & ( * link ) - > next )
{
if ( v = = ft )
{
//its still valid
if ( x )
{
char rawbuf [ 4096 ] ;
int sz ;
sz = pFS_Read ( ft - > file , rawbuf , ft - > blocksize ) ;
Base64_Add ( rawbuf , sz ) ;
Base64_Finish ( ) ;
if ( sz & & strlen ( base64 ) )
{
x = XML_CreateNode ( NULL , " data " , " http://jabber.org/protocol/ibb " , base64 ) ;
XML_AddParameteri ( x , " seq " , ft - > seq + + ) ;
XML_AddParameter ( x , " sid " , ft - > sid ) ;
JCL_SendIQNode ( jcl , JCL_FT_IBBChunked , " set " , from , x , true ) - > usrptr = ft ;
return true ;
}
//else eof
}
//errored or ended
if ( x )
Con_Printf ( " Transfer of %s to %s completed \n " , ft - > fname , ft - > with ) ;
else
Con_Printf ( " %s aborted %s \n " , ft - > with , ft - > fname ) ;
x = XML_CreateNode ( NULL , " close " , " http://jabber.org/protocol/ibb " , " " ) ;
XML_AddParameter ( x , " sid " , ft - > sid ) ;
JCL_SendIQNode ( jcl , NULL , " set " , from , x , true ) - > usrptr = ft ;
//errored
pFS_Close ( ft - > file ) ;
* link = ft - > next ;
free ( ft ) ;
return true ;
}
}
return false ;
}
qboolean JCL_FT_IBBBegun ( jclient_t * jcl , xmltree_t * x , struct iq_s * iq )
{
struct ft_s * ft = iq - > usrptr , * * link , * v ;
for ( link = & jcl - > ft ; ( v = * link ) ; link = & ( * link ) - > next )
{
if ( v = = ft )
{
//its still valid
if ( ! x )
{
Con_Printf ( " %s aborted %s \n " , ft - > with , ft - > fname ) ;
//errored
pFS_Close ( ft - > file ) ;
* link = ft - > next ;
free ( ft ) ;
}
else
{
ft - > begun = true ;
ft - > method = FT_IBB ;
JCL_FT_IBBChunked ( jcl , x , iq ) ;
}
return true ;
}
}
return false ;
}
qboolean JCL_FT_OfferAcked ( jclient_t * jcl , xmltree_t * x , struct iq_s * iq )
{
struct ft_s * ft = iq - > usrptr , * * link , * v ;
for ( link = & jcl - > ft ; ( v = * link ) ; link = & ( * link ) - > next )
{
if ( v = = ft )
{
//its still valid
if ( ! x )
{
Con_Printf ( " %s doesn't want %s \n " , ft - > with , ft - > fname ) ;
//errored
pFS_Close ( ft - > file ) ;
* link = ft - > next ;
free ( ft ) ;
}
else
{
xmltree_t * xo ;
Con_Printf ( " %s accepted %s \n " , ft - > with , ft - > fname ) ;
xo = XML_CreateNode ( NULL , " open " , " http://jabber.org/protocol/ibb " , " " ) ;
XML_AddParameter ( xo , " sid " , ft - > sid ) ;
XML_AddParameteri ( xo , " block-size " , ft - > blocksize ) ;
//XML_AddParameter(xo, "stanza", "iq");
JCL_SendIQNode ( jcl , JCL_FT_IBBBegun , " set " , XML_GetParameter ( x , " from " , " " ) , xo , true ) - > usrptr = ft ;
}
return true ;
}
}
return false ;
}
# endif
void JCL_Command ( int accid , char * console )
2005-12-11 20:11:22 +00:00
{
char imsg [ 8192 ] ;
2013-07-13 12:14:32 +00:00
char arg [ 6 ] [ 1024 ] ;
2005-12-11 20:11:22 +00:00
char * msg ;
int i ;
2013-06-29 21:08:09 +00:00
char nname [ 256 ] ;
2013-07-13 12:14:32 +00:00
jclient_t * jcl ;
if ( accid < 0 | | accid > = sizeof ( jclients ) / sizeof ( jclients [ 0 ] ) )
return ;
jcl = jclients [ accid ] ;
2005-12-11 20:11:22 +00:00
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-10-08 14:28:11 +00:00
if ( ! strcmp ( arg [ 0 ] + 1 , " open " ) | | ! strcmp ( arg [ 0 ] + 1 , " connect " ) | | ! strcmp ( arg [ 0 ] + 1 , " autoopen " ) | | ! strcmp ( arg [ 0 ] + 1 , " autoconnect " ) | | ! strcmp ( arg [ 0 ] + 1 , " plainopen " ) | | ! strcmp ( arg [ 0 ] + 1 , " plainconnect " ) | | ! strcmp ( arg [ 0 ] + 1 , " tlsopen " ) | | ! strcmp ( arg [ 0 ] + 1 , " tlsconnect " ) )
2013-06-23 02:33:52 +00:00
{ //tlsconnect is 'old'.
2013-10-08 14:28:11 +00:00
int tls ;
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 ;
}
2013-07-13 12:14:32 +00:00
if ( jcl )
2005-12-11 20:11:22 +00:00
{
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-10-08 14:28:11 +00:00
if ( ! strncmp ( arg [ 0 ] + 1 , " tls " , 3 ) )
tls = 2 ; //old initial-tls connect
else if ( ! strncmp ( arg [ 0 ] + 1 , " plain " , 5 ) )
tls = - 1 ; //don't bother with tls. at all.
else if ( ! strncmp ( arg [ 0 ] + 1 , " auto " , 4 ) )
tls = 0 ; //use tls if its available, but don't really care otherwise.
else
tls = 1 ; //require tls
jcl = jclients [ accid ] = JCL_Connect ( accid , arg [ 3 ] , tls , arg [ 1 ] , arg [ 2 ] ) ;
2013-07-13 12:14:32 +00:00
if ( ! jclients [ accid ] )
2009-04-19 01:48:10 +00:00
{
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-07-13 12:14:32 +00:00
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /connect USERNAME@DOMAIN/RESOURCE [PASSWORD] [XMPPSERVER]^] \n " ) ;
2013-06-23 02:33:52 +00:00
if ( BUILTINISVALID ( Net_SetTLSClient ) )
2013-06-29 16:01:07 +00:00
{
2013-10-08 14:28:11 +00:00
Con_TrySubPrint ( console , " eg for gmail: ^[/ " COMMANDPREFIX " /connect myusername@gmail.com^] (using oauth2) \n " ) ;
Con_TrySubPrint ( console , " eg for gmail: ^[/ " COMMANDPREFIX " /connect myusername@gmail.com mypassword talk.google.com^] (warning: password will be saved locally in plain text) \n " ) ;
// Con_TrySubPrint(console, "eg for facebook: ^[/" COMMANDPREFIX " /connect myusername@chat.facebook.com mypassword chat.facebook.com^]\n");
// Con_TrySubPrint(console, "eg for msn: ^[/" COMMANDPREFIX " /connect myusername@messanger.live.com mypassword^]\n");
}
else
{ //if we don't have tls support, we can still connect to google with oauth2
//no idea about other networks.
//however, the regular 'connect' command will insist on tls, so make sure the helpful command displayed is different
Con_TrySubPrint ( console , " eg for gmail: ^[/ " COMMANDPREFIX " /autoconnect myusername@gmail.com^] (using oauth2) \n " ) ;
2013-06-29 16:01:07 +00:00
}
2013-10-08 14:28:11 +00:00
Con_TrySubPrint ( console , " 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-07-13 12:14:32 +00:00
else if ( ! jcl )
2013-06-29 16:01:07 +00:00
{
Con_SubPrintf ( console , " No account specified. Cannot %s \n " , arg [ 0 ] ) ;
}
2013-07-13 12:14:32 +00:00
else if ( ! strcmp ( arg [ 0 ] + 1 , " oa2token " ) )
{
free ( jcl - > oauth2 . authtoken ) ;
jcl - > oauth2 . authtoken = strdup ( arg [ 1 ] ) ;
if ( jcl - > status = = JCL_INACTIVE )
jcl - > status = JCL_DEAD ;
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( arg [ 0 ] + 1 , " quit " ) )
{
//disconnect from the xmpp server.
2013-07-13 12:14:32 +00:00
JCL_CloseConnection ( jcl , false ) ;
2013-06-29 16:01:07 +00:00
}
2013-07-13 12:14:32 +00:00
else if ( jcl - > status ! = JCL_ACTIVE )
2013-06-29 16:01:07 +00:00
{
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.
2013-07-13 12:14:32 +00:00
JCL_PrintBuddyList ( console , jcl , true ) ;
2013-06-29 16:01:07 +00:00
}
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-07-13 12:14:32 +00:00
Q_strlcpy ( jcl - > defaultdest , arg [ 1 ] , sizeof ( jcl - > defaultdest ) ) ;
2005-12-11 20:11:22 +00:00
msg = arg [ 2 ] ;
2013-07-13 12:14:32 +00:00
JCL_SendMessage ( jcl , jcl - > defaultdest , msg ) ;
2013-06-23 02:33:52 +00:00
}
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.
2013-07-13 12:14:32 +00:00
JCL_SendIQf ( jcl , 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
2013-07-13 12:14:32 +00:00
JCL_AddClientMessagef ( jcl , " <presence to='%s' type='subscribe'/> " , arg [ 1 ] ) ;
2013-06-23 02:33:52 +00:00
//let em see us
2013-07-13 12:14:32 +00:00
if ( jcl - > preapproval )
JCL_AddClientMessagef ( jcl , " <presence to='%s' type='subscribed'/> " , arg [ 1 ] ) ;
2013-06-23 02:33:52 +00:00
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " unfriend " ) )
{
//FIXME: validate the name. deal with xml markup.
//hide from em
2013-07-13 12:14:32 +00:00
JCL_AddClientMessagef ( jcl , " <presence to='%s' type='unsubscribed'/> " , arg [ 1 ] ) ;
2013-06-23 02:33:52 +00:00
//stop looking for em
2013-07-13 12:14:32 +00:00
JCL_AddClientMessagef ( jcl , " <presence to='%s' type='unsubscribe'/> " , arg [ 1 ] ) ;
2013-06-23 02:33:52 +00:00
//stop listing em
2013-07-13 12:14:32 +00:00
JCL_SendIQf ( jcl , NULL , " set " , NULL , " <query xmlns='jabber:iq:roster'><item jid='%s' subscription='remove' /></query> " , arg [ 1 ] ) ;
2013-06-23 02:33:52 +00:00
}
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
2013-06-23 02:33:52 +00:00
else if ( ! strcmp ( arg [ 0 ] + 1 , " join " ) )
{
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_Join ( jcl , nname , NULL , true , ICEP_QWCLIENT ) ;
2013-06-29 16:01:07 +00:00
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " invite " ) )
{
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_Join ( jcl , nname , NULL , true , ICEP_QWSERVER ) ;
2013-06-29 16:01:07 +00:00
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " voice " ) | | ! strcmp ( arg [ 0 ] + 1 , " call " ) )
{
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_Join ( jcl , nname , NULL , true , ICEP_VOICE ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " kick " ) )
{
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_Join ( jcl , nname , NULL , false , ICEP_INVALID ) ;
2013-06-29 16:01:07 +00:00
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " kick " ) )
{
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_Join ( jcl , nname , NULL , false , ICEP_INVALID ) ;
}
# endif
# ifdef FILETRANSFERS
else if ( ! strcmp ( arg [ 0 ] + 1 , " sendfile " ) )
{
xmltree_t * xfile , * xsi , * c ;
char * fname = arg [ 1 ] ;
//file transfer offer
struct ft_s * ft ;
if ( ! * fname | | strchr ( fname , ' * ' ) )
{
Con_SubPrintf ( console , " XMPP: /sendfile FILENAME [to] \n " ) ;
return ;
}
else
{
JCL_ToJID ( jcl , * arg [ 2 ] ? arg [ 2 ] : console , nname , sizeof ( nname ) ) ;
Con_SubPrintf ( console , " Offering %s to %s. \n " , fname , nname ) ;
ft = malloc ( sizeof ( * ft ) ) ;
memset ( ft , 0 , sizeof ( * ft ) ) ;
ft - > next = jcl - > ft ;
jcl - > ft = ft ;
ft - > transmitting = true ;
ft - > blocksize = 4096 ;
Q_strlcpy ( ft - > fname , fname , sizeof ( ft - > fname ) ) ;
Q_snprintf ( ft - > sid , sizeof ( ft - > sid ) , " %x%s " , rand ( ) , ft - > fname ) ;
Q_strlcpy ( ft - > md5hash , " " , sizeof ( ft - > md5hash ) ) ;
ft - > size = pFS_Open ( ft - > fname , & ft - > file , 1 ) ;
ft - > with = strdup ( nname ) ;
ft - > method = FT_IBB ;
ft - > begun = false ;
//generate an offer.
xsi = XML_CreateNode ( NULL , " si " , " http://jabber.org/protocol/si " , " " ) ;
XML_AddParameter ( xsi , " profile " , " http://jabber.org/protocol/si/profile/file-transfer " ) ;
XML_AddParameter ( xsi , " id " , ft - > sid ) ;
//XML_AddParameter(xsi, "mime-type", "text/plain");
xfile = XML_CreateNode ( xsi , " file " , " http://jabber.org/protocol/si/profile/file-transfer " , " " ) ; //I don't really get the point of this.
XML_AddParameter ( xfile , " name " , ft - > fname ) ;
XML_AddParameteri ( xfile , " size " , ft - > size ) ;
c = XML_CreateNode ( xsi , " feature " , " http://jabber.org/protocol/feature-neg " , " " ) ;
c = XML_CreateNode ( c , " x " , " jabber:x:data " , " " ) ;
XML_AddParameter ( c , " type " , " form " ) ;
c = XML_CreateNode ( c , " field " , " " , " " ) ;
XML_AddParameter ( c , " var " , " stream-method " ) ;
XML_AddParameter ( c , " type " , " listsingle " ) ;
XML_CreateNode ( XML_CreateNode ( c , " option " , " " , " " ) , " value " , " " , " http://jabber.org/protocol/ibb " ) ;
// XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/bytestreams");
JCL_SendIQNode ( jcl , JCL_FT_OfferAcked , " set " , nname , xsi , true ) - > usrptr = ft ;
}
}
# endif
else if ( ! strcmp ( arg [ 0 ] + 1 , " joinchatroom " ) | | ! strcmp ( arg [ 0 ] + 1 , " muc " ) | | ! strcmp ( arg [ 0 ] + 1 , " joinmuc " ) )
{
JCL_JoinMUCChat ( jcl , arg [ 1 ] , arg [ 2 ] , arg [ 3 ] , arg [ 4 ] ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " leavechatroom " ) | | ! strcmp ( arg [ 0 ] + 1 , " leavemuc " ) )
{
char roomserverhandle [ 512 ] ;
buddy_t * b ;
bresource_t * r ;
Q_snprintf ( roomserverhandle , sizeof ( roomserverhandle ) , " %s@%s/%s " , arg [ 1 ] , arg [ 2 ] , arg [ 3 ] ) ;
JCL_FindBuddy ( jcl , roomserverhandle , & b , & r ) ;
b - > chatroom = true ;
JCL_AddClientMessagef ( jcl , " <presence to='%s' type='unavailable'/> " , roomserverhandle ) ;
JCL_ForgetBuddy ( jcl , b , NULL ) ;
2013-06-29 21:08:09 +00:00
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " slap " ) )
{
char * msgtab [ ] =
{
" /me slaps you around a bit with a large trout " ,
" /me slaps you around a bit with a large tunafish " ,
" /me slaps you around a bit with a slimy hagfish " ,
" /me slaps a large trout around a bit with your face " ,
" /me gets eaten by a rabid shark while trying to slap you with it " ,
" /me gets crushed under the weight of a large whale " ,
" /me searches for a fresh fish, but gets crabs instead " ,
" /me searches for a fish, but there are no more fish in the sea " ,
" /me tickles you around a bit with a large fish finger " ,
" /me goes to order cod and chips. brb " ,
" /me goes to watch some monty python "
} ;
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_AttentionMessage ( jcl , nname , msgtab [ rand ( ) % ( sizeof ( msgtab ) / sizeof ( msgtab [ 0 ] ) ) ] ) ;
2013-06-29 21:08:09 +00:00
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " poke " ) )
{
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * arg [ 1 ] ? arg [ 1 ] : console , nname , sizeof ( nname ) ) ;
JCL_AttentionMessage ( jcl , nname , NULL ) ;
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-07-13 12:14:32 +00:00
//parse it first, so noone ever generates invalid xml and gets kicked... too obviously.
int pos = 0 ;
int maxpos = strlen ( arg [ 1 ] ) ;
xmltree_t * t ;
while ( pos ! = maxpos )
{
t = XML_Parse ( arg [ 1 ] , & pos , maxpos , false , " " ) ;
if ( t )
XML_Destroy ( t ) ;
else
break ;
}
if ( pos = = maxpos )
{
jcl - > streamdebug = true ;
JCL_AddClientMessageString ( jcl , arg [ 1 ] ) ;
}
else
Con_Printf ( " XML not well formed \n " ) ;
2013-03-31 04:21:08 +00:00
}
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
{
2013-07-13 12:14:32 +00:00
if ( jcl )
2005-12-11 20:11:22 +00:00
{
msg = imsg ;
2013-06-23 02:33:52 +00:00
if ( ! * msg )
{
if ( ! * console )
{
2013-07-13 12:14:32 +00:00
JCL_PrintBuddyList ( console , jcl , 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
{
2013-07-13 12:14:32 +00:00
JCL_ToJID ( jcl , * console ? console : jcl - > defaultdest , nname , sizeof ( nname ) ) ;
JCL_SendMessage ( jcl , nname , msg ) ;
2013-06-23 02:33:52 +00:00
}
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
}
}