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-06-23 02:33:52 +00:00
# include "xml.h"
2013-06-29 16:01:07 +00:00
//#define NOICE
# define VOIP_SPEEX
2013-07-13 12:14:32 +00:00
//#define FILETRANSFERS //IBB only, speeds suck. autoaccept is forced on. no protection from mods stuffcmding sendfile commands. needs more extensive testing
# define JINGLE
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
# ifdef VOIP_SPEEX
# define VOIP
# endif
2013-07-14 16:39:21 +00:00
# ifdef JINGLE
# include "../../engine/common/netinc.h"
# endif
2013-06-29 16:01:07 +00:00
# define DEFAULTDOMAIN ""
# define DEFAULTRESOURCE "Quake"
2013-06-23 02:33:52 +00:00
# define QUAKEMEDIATYPE "quake"
# define QUAKEMEDIAXMLNS "fteqw.com:netmedia"
2013-06-29 16:01:07 +00:00
# define DEFAULTICEMODE ICEM_ICE
2013-06-23 02:33:52 +00:00
2013-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-06-23 02:33:52 +00:00
# define JCL_BUILD "3"
2005-12-11 20:11:22 +00:00
2013-03-31 04:21:08 +00:00
# define ARGNAMES ,sock,certhostname
BUILTINR ( int , Net_SetTLSClient , ( qhandle_t sock , const char * certhostname ) ) ;
2005-12-11 20:11:22 +00:00
# undef ARGNAMES
2013-06-23 02:33:52 +00:00
# define ARGNAMES ,funcname
BUILTINR ( void * , Plug_GetNativePointer , ( const char * funcname ) ) ;
# undef ARGNAMES
2013-03-31 04:21:08 +00:00
void ( * Con_TrySubPrint ) ( const char * conname , const char * message ) ;
void Fallback_ConPrint ( const char * conname , const char * message )
{
2013-06-23 18:43:59 +00:00
pCon_Print ( message ) ;
2013-03-31 04:21:08 +00:00
}
2005-12-11 20:11:22 +00:00
void Con_SubPrintf ( char * subname , char * format , . . . )
{
va_list argptr ;
static char string [ 1024 ] ;
va_start ( argptr , format ) ;
2013-05-11 14:02:55 +00:00
Q_vsnprintf ( string , sizeof ( string ) , format , argptr ) ;
2005-12-11 20:11:22 +00:00
va_end ( argptr ) ;
2013-03-31 04:21:08 +00:00
Con_TrySubPrint ( subname , string ) ;
2005-12-11 20:11:22 +00:00
}
//porting zone:
# define COLOURGREEN "^2"
# define COLORWHITE "^7"
# define COLOURWHITE "^7" // word
# define COLOURRED "^1"
# define COLOURYELLOW "^3"
# define COLOURPURPLE "^5"
2013-06-23 02:33:52 +00:00
# define COMMANDPREFIX "xmpp"
# define COMMANDPREFIX2 "jab"
# define COMMANDPREFIX3 "jabbercl"
2005-12-11 20:11:22 +00:00
# define playsound(s)
# define TL_NETGETPACKETERROR "NET_GetPacket Error %s\n"
2013-06-23 18:43:59 +00:00
static char * JCL_ParseOut ( char * data , char * buf , int bufsize ) //this is taken out of quake
2005-12-11 20:11:22 +00:00
{
int c ;
int len ;
len = 0 ;
2013-06-23 02:33:52 +00:00
buf [ 0 ] = 0 ;
2005-12-11 20:11:22 +00:00
if ( ! data )
return NULL ;
// skip whitespace
while ( ( c = * data ) < = ' ' )
{
if ( c = = 0 )
return NULL ; // end of file;
data + + ;
}
// handle quoted strings specially
if ( c = = ' \" ' )
{
data + + ;
while ( 1 )
{
2013-06-23 02:33:52 +00:00
if ( len > = bufsize - 1 )
2005-12-11 20:11:22 +00:00
return data ;
c = * data + + ;
if ( c = = ' \" ' | | ! c )
{
2013-06-23 02:33:52 +00:00
buf [ len ] = 0 ;
2005-12-11 20:11:22 +00:00
return data ;
}
2013-06-23 02:33:52 +00:00
buf [ len ] = c ;
2005-12-11 20:11:22 +00:00
len + + ;
}
}
// parse a regular word
do
{
2013-06-23 02:33:52 +00:00
if ( len > = bufsize - 1 )
2005-12-11 20:11:22 +00:00
return data ;
2013-06-23 02:33:52 +00:00
buf [ len ] = c ;
2005-12-11 20:11:22 +00:00
data + + ;
len + + ;
c = * data ;
} while ( c > 32 ) ;
2013-06-23 02:33:52 +00:00
buf [ len ] = 0 ;
2005-12-11 20:11:22 +00:00
return data ;
}
2013-06-29 16:01:07 +00:00
char * JCL_Info_ValueForKey ( char * s , const char * key , char * valuebuf , int valuelen )
{
char pkey [ 1024 ] ;
char * o ;
if ( * s = = ' \\ ' )
s + + ;
while ( 1 )
{
o = pkey ;
while ( * s ! = ' \\ ' )
{
if ( ! * s )
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
* o + + = * s + + ;
if ( o + 2 > = pkey + sizeof ( pkey ) ) //hrm. hackers at work..
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
}
* o = 0 ;
s + + ;
o = valuebuf ;
while ( * s ! = ' \\ ' & & * s )
{
if ( ! * s )
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
* o + + = * s + + ;
if ( o + 2 > = valuebuf + valuelen ) //hrm. hackers at work..
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
}
* o = 0 ;
if ( ! strcmp ( key , pkey ) )
return valuebuf ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
if ( ! * s )
{
* valuebuf = ' \0 ' ;
return valuebuf ;
}
s + + ;
}
}
2005-12-11 20:11:22 +00:00
2013-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 " ) ;
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-03-31 04:21:08 +00:00
# define JCL_MAXMSGLEN 10000
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
# define CAP_QUERIED 1 //a query is pending or something.
# define CAP_VOICE 2 //supports voice
# define CAP_INVITE 4 //supports game invites.
2013-06-29 21:08:09 +00:00
# define CAP_POKE 8 //can be slapped.
2013-07-13 12:14:32 +00:00
# define CAP_SIFT 16 //non-jingle file transfers
2013-06-29 16:01:07 +00:00
2013-06-23 02:33:52 +00:00
typedef struct bresource_s
{
char bstatus [ 128 ] ; //basic status
char fstatus [ 128 ] ; //full status
char server [ 256 ] ;
int servertype ; //0=none, 1=already a client, 2=joinable
2013-06-29 16:01:07 +00:00
unsigned int caps ;
2013-06-23 02:33:52 +00:00
struct bresource_s * next ;
char resource [ 1 ] ;
} bresource_t ;
typedef struct buddy_s
{
bresource_t * resources ;
bresource_t * defaultresource ; //this is the one that last replied
int defaulttimestamp ;
qboolean friended ;
2013-07-13 12:14:32 +00:00
qboolean chatroom ; //chatrooms are bizzare things that need special handling.
2013-06-23 02:33:52 +00:00
char name [ 256 ] ;
2005-12-11 20:11:22 +00:00
2013-06-23 02:33:52 +00:00
struct buddy_s * next ;
char accountdomain [ 1 ] ; //no resource on there
} buddy_t ;
typedef struct jclient_s
{
2013-07-13 12:14:32 +00:00
int accountnum ; //a private id to track which client links are associated with
char redirserveraddr [ 64 ] ; //if empty, do an srv lookup.
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
enum
{
2013-07-13 12:14:32 +00:00
JCL_INACTIVE , //not trying to connect.
2013-06-29 16:01:07 +00:00
JCL_DEAD , //not connected. connection died or something.
JCL_AUTHING , //connected, but not able to send any info on it other than to auth
JCL_ACTIVE //we're connected, we got a buddy list and everything
} status ;
unsigned int timeout ; //reconnect/ping timer
2005-12-11 20:11:22 +00:00
qhandle_t socket ;
2013-06-29 16:01:07 +00:00
//we buffer output for times when the outgoing socket is full.
//mostly this only happens at the start of the connection when the socket isn't actually open yet.
2013-06-23 02:33:52 +00:00
char * outbuf ;
int outbufpos ;
int outbuflen ;
int outbufmax ;
2013-06-29 16:01:07 +00:00
2013-03-31 04:21:08 +00:00
char bufferedinmessage [ JCL_MAXMSGLEN + 1 ] ; //servers are required to be able to handle messages no shorter than a specific size.
//which means we need to be able to handle messages when they get to us.
//servers can still handle larger messages if they choose, so this might not be enough.
2005-12-11 20:11:22 +00:00
int bufferedinammount ;
char defaultdest [ 256 ] ;
2013-07-13 12:14:32 +00:00
//config info
char serveraddr [ 64 ] ; //if empty, do an srv lookup.
int serverport ;
2005-12-11 20:11:22 +00:00
char domain [ 256 ] ;
char username [ 256 ] ;
char password [ 256 ] ;
char resource [ 256 ] ;
2013-07-13 12:14:32 +00:00
char certificatedomain [ 256 ] ;
int forcetls ; //-1=off, 0=ifpossible, 1=fail if can't upgrade, 2=old-style tls
qboolean allowauth_plainnontls ; //allow plain plain
qboolean allowauth_plaintls ; //allow tls plain
qboolean allowauth_digestmd5 ; //allow digest-md5 auth
qboolean allowauth_scramsha1 ; //allow scram-sha-1 auth
qboolean allowauth_oauth2 ; //use oauth2 where possible
2013-06-23 02:33:52 +00:00
char jid [ 256 ] ; //this is our full username@domain/resource string
2013-06-29 16:01:07 +00:00
char localalias [ 256 ] ; //this is what's shown infront of outgoing messages. >> by default until we can get our name.
2005-12-11 20:11:22 +00:00
2013-07-13 12:14:32 +00:00
char authnonce [ 256 ] ;
int authmode ;
2005-12-11 20:11:22 +00:00
int tagdepth ;
int openbracket ;
int instreampos ;
2013-03-31 04:21:08 +00:00
qboolean connected ; //fully on server and authed and everything.
2013-06-29 16:01:07 +00:00
qboolean issecure ; //tls enabled (either upgraded or initially)
2013-06-23 02:33:52 +00:00
qboolean streamdebug ; //echo the stream to subconsoles
2013-06-29 16:01:07 +00:00
qboolean preapproval ; //server supports presence preapproval
2013-03-31 04:21:08 +00:00
char curquakeserver [ 2048 ] ;
char defaultnamespace [ 2048 ] ; //should be 'jabber:client' or blank (and spammy with all the extra xmlns attribs)
2013-06-23 02:33:52 +00:00
2013-07-13 12:14:32 +00:00
struct
{
char saslmethod [ 64 ] ;
char obtainurl [ 256 ] ;
char refreshurl [ 256 ] ;
char clientid [ 256 ] ;
char clientsecret [ 256 ] ;
char * useraccount ;
char * scope ;
char * accesstoken ; //one-shot access token
char * refreshtoken ; //long-term token that we can use to get new access tokens
char * authtoken ; //short-term authorisation token, usable to get an access token (and a refresh token if we're lucky)
} oauth2 ;
2013-06-29 16:01:07 +00:00
struct iq_s
{
2013-06-23 02:33:52 +00:00
struct iq_s * next ;
char id [ 64 ] ;
int timeout ;
2013-06-29 16:01:07 +00:00
qboolean ( * callback ) ( struct jclient_s * jcl , struct subtree_s * tree , struct iq_s * iq ) ;
void * usrptr ;
2013-06-23 02:33:52 +00:00
} * pendingiqs ;
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
2013-06-23 02:33:52 +00:00
struct c2c_s
{
struct c2c_s * next ;
2013-06-29 16:01:07 +00:00
enum iceproto_e mediatype ;
enum icemode_e method ; //ICE_RAW or ICE_ICE. this is what the peer asked for. updated if we degrade it.
qboolean accepted ; //connection is going
qboolean creator ; //true if we're the creator.
struct icestate_s * ice ;
char * peeraddr ;
int peerport ;
char * with ;
char sid [ 1 ] ;
} * c2c ;
2013-07-13 12:14:32 +00:00
# endif
# ifdef FILETRANSFERS
struct ft_s
{
struct ft_s * next ;
char fname [ MAX_QPATH ] ;
int size ;
char * with ;
char md5hash [ 16 ] ;
char sid [ 64 ] ;
int blocksize ;
unsigned short seq ;
qhandle_t file ;
qboolean begun ;
qboolean transmitting ;
enum
{
FT_IBB , //in-band bytestreams
FT_BYTESTREAM //aka: relay
} method ;
} * ft ;
# endif
2013-06-23 02:33:52 +00:00
buddy_t * buddies ;
2005-12-11 20:11:22 +00:00
} jclient_t ;
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 ) ) ;
Con_Printf ( " Please visit ^[^4%s \\ url \\ %s^] and then enter: \n ^[/ " COMMANDPREFIX " %i /oa2token <TOKEN>^] \n " , url , url , jcl - > accountnum ) ;
//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
return - 1 ;
}
//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
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
2013-06-29 16:01:07 +00:00
static struct c2c_s * JCL_JingleCreateSession ( jclient_t * jcl , char * with , qboolean creator , char * sid , int method , int mediatype )
{
struct icestate_s * ice = NULL ;
struct c2c_s * c2c ;
char generatedname [ 64 ] ;
2013-07-13 12:14:32 +00:00
char stunhost [ 256 ] ;
2013-06-29 16:01:07 +00:00
if ( piceapi )
ice = piceapi - > ICE_Create ( NULL , sid , with , method , mediatype ) ;
if ( ice )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
piceapi - > ICE_Get ( ice , " sid " , generatedname , sizeof ( generatedname ) ) ;
sid = generatedname ;
2013-07-13 12:14:32 +00:00
//note: the engine will ignore codecs it does not support.
2013-06-29 16:01:07 +00:00
piceapi - > ICE_Set ( ice , " codec96 " , " speex@8000 " ) ;
piceapi - > ICE_Set ( ice , " codec97 " , " speex@16000 " ) ;
piceapi - > ICE_Set ( ice , " codec98 " , " opus " ) ;
}
else
return NULL ; //no way to get the local ip otherwise, which means things won't work proper
c2c = malloc ( sizeof ( * c2c ) + strlen ( sid ) ) ;
memset ( c2c , 0 , sizeof ( * c2c ) ) ;
c2c - > next = jcl - > c2c ;
jcl - > c2c = c2c ;
strcpy ( c2c - > sid , sid ) ;
c2c - > mediatype = mediatype ;
c2c - > creator = creator ;
c2c - > method = method ;
2013-07-13 12:14:32 +00:00
//query dns to see if there's a stunserver hosted by the same domain
//nslookup -querytype=SRV _stun._udp.example.com
//for msn, live.com has one, messanger.live.com has one, but messenger.live.com does NOT. seriously, the typo has more services. wtf microsoft?
//google doesn't provide a stun srv entry
//facebook doesn't provide a stun srv entry
Q_snprintf ( stunhost , sizeof ( stunhost ) , " _stun._udp.%s " , jcl - > domain ) ;
if ( NET_DNSLookup_SRV ( stunhost , stunhost , sizeof ( stunhost ) ) )
piceapi - > ICE_Set ( ice , " stunip " , stunhost ) ;
else
{
//there is no real way to query stun servers from the xmpp server.
//while there is some extdisco extension (aka: the 'standard' way), it has some huge big fat do-not-implement message (and googletalk does not implement it).
//google provide their own jingleinfo extension. Which also has some huge fat 'do-not-implement' message. hardcoding the address should provide equivelent longevity. :(
//google also don't provide stun srv records.
//so we're basically screwed if we want to work with the googletalk xmpp service long term.
//more methods are best, I suppose, but I'm lazy.
//yes, hardcoding means that other services might 'borrow' googles' stun servers.
piceapi - > ICE_Set ( ice , " stunport " , " 19302 " ) ;
piceapi - > ICE_Set ( ice , " stunip " , " stun.l.google.com " ) ;
}
2013-06-29 16:01:07 +00:00
//copy out the interesting parameters
c2c - > with = strdup ( with ) ;
c2c - > ice = ice ;
return c2c ;
}
static qboolean JCL_JingleAcceptAck ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
{
struct c2c_s * c2c ;
if ( tree )
{
for ( c2c = jcl - > c2c ; c2c ; c2c = c2c - > next )
{
if ( c2c = = iq - > usrptr )
{
if ( c2c - > ice )
piceapi - > ICE_Set ( c2c - > ice , " state " , STRINGIFY ( ICE_CONNECTING ) ) ;
}
}
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
return true ;
}
/*
sends a jingle message to the peer .
action should be one of multiple things :
session - terminate - totally not acceptable . this also closes the c2c
session - accept - details are okay . this also begins ice polling ( on iq ack , once we ' re sure the peer got our message )
( internally generated ) transport - replace - details are okay , except we want a different transport method .
*/
qboolean JCL_JingleSend ( jclient_t * jcl , struct c2c_s * c2c , char * action )
{
qboolean result ;
xmltree_t * jingle ;
struct icestate_s * ice = c2c - > ice ;
qboolean wasaccept = false ;
int transportmode = ICEM_ICE ;
if ( ! ice )
action = " session-terminate " ;
2013-06-23 02:33:52 +00:00
jingle = XML_CreateNode ( NULL , " jingle " , " urn:xmpp:jingle:1 " , " " ) ;
2013-06-29 16:01:07 +00:00
XML_AddParameter ( jingle , " sid " , c2c - > sid ) ;
if ( ! strcmp ( action , " session-initiate " ) )
{ //these attributes are meant to only be present in initiate. for call forwarding etc. which we don't properly support.
XML_AddParameter ( jingle , " initiator " , jcl - > jid ) ;
}
if ( ! strcmp ( action , " session-terminate " ) )
{
struct c2c_s * * link ;
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( * link = = c2c )
{
* link = c2c - > next ;
break ;
}
}
if ( c2c - > ice )
piceapi - > ICE_Close ( c2c - > ice ) ;
result = false ;
}
else
2013-06-23 02:33:52 +00:00
{
xmltree_t * content = XML_CreateNode ( jingle , " content " , " " , " " ) ;
2013-06-29 16:01:07 +00:00
result = true ;
if ( ! strcmp ( action , " session-accept " ) )
{
if ( c2c - > method = = transportmode )
{
XML_AddParameter ( jingle , " responder " , jcl - > jid ) ;
c2c - > accepted = wasaccept = true ;
}
else
action = " transport-replace " ;
}
2013-06-23 02:33:52 +00:00
{
xmltree_t * description ;
xmltree_t * transport ;
2013-06-29 16:01:07 +00:00
if ( transportmode = = ICEM_RAW )
2013-06-23 02:33:52 +00:00
{
transport = XML_CreateNode ( content , " transport " , " urn:xmpp:jingle:transports:raw-udp:1 " , " " ) ;
{
xmltree_t * candidate ;
2013-06-29 16:01:07 +00:00
struct icecandinfo_s * b = NULL ;
struct icecandinfo_s * c ;
while ( ( c = piceapi - > ICE_GetLCandidateInfo ( ice ) ) )
2013-06-23 02:33:52 +00:00
{
if ( ! b | | b - > priority < c - > priority )
b = c ;
}
if ( b )
{
candidate = XML_CreateNode ( transport , " candidate " , " " , " " ) ;
XML_AddParameter ( candidate , " ip " , b - > addr ) ;
XML_AddParameteri ( candidate , " port " , b - > port ) ;
XML_AddParameter ( candidate , " id " , b - > candidateid ) ;
XML_AddParameteri ( candidate , " generation " , b - > generation ) ;
XML_AddParameteri ( candidate , " component " , b - > component ) ;
}
}
}
2013-06-29 16:01:07 +00:00
else if ( transportmode = = ICEM_ICE )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char val [ 64 ] ;
2013-06-23 02:33:52 +00:00
transport = XML_CreateNode ( content , " transport " , " urn:xmpp:jingle:transports:ice-udp:1 " , " " ) ;
2013-06-29 16:01:07 +00:00
piceapi - > ICE_Get ( ice , " lufrag " , val , sizeof ( val ) ) ;
XML_AddParameter ( transport , " ufrag " , val ) ;
piceapi - > ICE_Get ( ice , " lpwd " , val , sizeof ( val ) ) ;
XML_AddParameter ( transport , " pwd " , val ) ;
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
struct icecandinfo_s * c ;
while ( ( c = piceapi - > ICE_GetLCandidateInfo ( ice ) ) )
2013-06-23 02:33:52 +00:00
{
char * ctypename [ ] = { " host " , " srflx " , " prflx " , " relay " } ;
xmltree_t * candidate = XML_CreateNode ( transport , " candidate " , " " , " " ) ;
XML_AddParameter ( candidate , " type " , ctypename [ c - > type ] ) ;
XML_AddParameter ( candidate , " protocol " , " udp " ) ; //is this not just a little bit redundant? ice-udp? seriously?
XML_AddParameteri ( candidate , " priority " , c - > priority ) ;
XML_AddParameteri ( candidate , " port " , c - > port ) ;
XML_AddParameteri ( candidate , " network " , c - > network ) ;
XML_AddParameter ( candidate , " ip " , c - > addr ) ;
XML_AddParameter ( candidate , " id " , c - > candidateid ) ;
XML_AddParameteri ( candidate , " generation " , c - > generation ) ;
XML_AddParameteri ( candidate , " foundation " , c - > foundation ) ;
XML_AddParameteri ( candidate , " component " , c - > component ) ;
}
}
}
2013-06-29 16:01:07 +00:00
# ifdef VOIP
if ( c2c - > mediatype = = ICEP_VOICE )
{
xmltree_t * payload ;
int i ;
XML_AddParameter ( content , " senders " , " both " ) ;
XML_AddParameter ( content , " name " , " audio-session " ) ;
XML_AddParameter ( content , " creator " , " initiator " ) ;
description = XML_CreateNode ( content , " description " , " urn:xmpp:jingle:apps:rtp:1 " , " " ) ;
XML_AddParameter ( description , " media " , " audio " ) ;
for ( i = 96 ; i < = 127 ; i + + )
{
char codecname [ 64 ] ;
char argn [ 64 ] ;
Q_snprintf ( argn , sizeof ( argn ) , " codec%i " , i ) ;
piceapi - > ICE_Get ( ice , argn , codecname , sizeof ( codecname ) ) ;
if ( ! strcmp ( codecname , " speex@8000 " ) )
{ //speex narrowband
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " clockrate " , " 8000 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " SPEEX " ) ;
}
else if ( ! strcmp ( codecname , " speex@16000 " ) )
{ //speex wideband
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " clockrate " , " 16000 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " SPEEX " ) ;
}
else if ( ! strcmp ( codecname , " speex@32000 " ) )
{ //speex ultrawideband
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " clockrate " , " 32000 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " SPEEX " ) ;
}
else if ( ! strcmp ( codecname , " opus " ) )
{ //opus codec.
payload = XML_CreateNode ( description , " payload-type " , " " , " " ) ;
XML_AddParameter ( payload , " channels " , " 1 " ) ;
XML_AddParameter ( payload , " id " , argn + 5 ) ;
XML_AddParameter ( payload , " name " , " OPUS " ) ;
}
}
}
# endif
else
{
description = XML_CreateNode ( content , " description " , QUAKEMEDIAXMLNS , " " ) ;
XML_AddParameter ( description , " media " , QUAKEMEDIATYPE ) ;
if ( c2c - > mediatype = = ICEP_QWSERVER )
XML_AddParameter ( description , " host " , " me " ) ;
else if ( c2c - > mediatype = = ICEP_QWCLIENT )
XML_AddParameter ( description , " host " , " you " ) ;
}
}
}
XML_AddParameter ( jingle , " action " , action ) ;
// Con_Printf("Sending Jingle:\n");
// XML_ConPrintTree(jingle, 1);
JCL_SendIQNode ( jcl , wasaccept ? JCL_JingleAcceptAck : NULL , " set " , c2c - > with , jingle , true ) - > usrptr = c2c ;
return result ;
}
void JCL_JingleTimeouts ( jclient_t * jcl , qboolean killall )
{
struct c2c_s * c2c ;
for ( c2c = jcl - > c2c ; c2c ; c2c = c2c - > next )
{
struct icecandinfo_s * lc ;
if ( c2c - > method = = ICEM_ICE )
{
char bah [ 2 ] ;
piceapi - > ICE_Get ( c2c - > ice , " newlc " , bah , sizeof ( bah ) ) ;
if ( atoi ( bah ) )
{
Con_DPrintf ( " Sending updated local addresses \n " ) ;
JCL_JingleSend ( jcl , c2c , " transport-info " ) ;
}
}
}
}
void JCL_Join ( jclient_t * jcl , char * target , char * sid , qboolean allow , int protocol )
{
struct c2c_s * c2c = NULL , * * link ;
xmltree_t * jingle ;
struct icestate_s * ice ;
char autotarget [ 256 ] ;
2013-06-29 21:08:09 +00:00
buddy_t * b ;
bresource_t * br ;
2013-06-29 16:01:07 +00:00
if ( ! jcl )
return ;
2013-06-29 21:08:09 +00:00
JCL_FindBuddy ( jcl , target , & b , & br ) ;
if ( ! br )
br = b - > defaultresource ;
if ( ! br )
br = b - > resources ;
2013-06-29 16:01:07 +00:00
2013-06-29 21:08:09 +00:00
if ( ! strchr ( target , ' / ' ) )
{
2013-06-29 16:01:07 +00:00
if ( ! br )
{
Con_Printf ( " User name not valid \n " ) ;
return ;
}
Q_snprintf ( autotarget , sizeof ( autotarget ) , " %s/%s " , b - > accountdomain , br - > resource ) ;
target = autotarget ;
}
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( ! strcmp ( ( * link ) - > with , target ) & & ( ! sid | | ! strcmp ( ( * link ) - > sid , sid ) ) & & ( ( * link ) - > mediatype = = protocol | | protocol = = ICEP_INVALID ) )
{
c2c = * link ;
break ;
}
}
if ( allow )
{
if ( ! c2c )
{
if ( ! sid )
2013-06-23 02:33:52 +00:00
{
2013-07-13 12:14:32 +00:00
char convolink [ 512 ] , hanguplink [ 512 ] ;
2013-06-29 16:01:07 +00:00
c2c = JCL_JingleCreateSession ( jcl , target , true , sid , DEFAULTICEMODE , ( ( protocol = = ICEP_INVALID ) ? ICEP_QWCLIENT : protocol ) ) ;
JCL_JingleSend ( jcl , c2c , " session-initiate " ) ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , target , NULL , NULL , " %s " , target ) ;
JCL_GenLink ( jcl , hanguplink , sizeof ( hanguplink ) , " jdeny " , target , NULL , c2c - > sid , " %s " , " Hang Up " ) ;
Con_SubPrintf ( b - > name , " %s %s %s. \n " , protocol = = ICEP_VOICE ? " Calling " : " Requesting session with " , convolink , hanguplink ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name , " That session has expired. \n " ) ;
2013-06-29 16:01:07 +00:00
}
else if ( c2c - > creator )
{
2013-07-13 12:14:32 +00:00
char convolink [ 512 ] ;
2013-06-29 16:01:07 +00:00
//resend initiate if they've not acked it... I dunno...
JCL_JingleSend ( jcl , c2c , " session-initiate " ) ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , target , NULL , NULL , " %s " , target ) ;
Con_SubPrintf ( b - > name , " Restarting session with %s. \n " , convolink ) ;
2013-06-29 16:01:07 +00:00
}
else if ( c2c - > accepted )
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name , " That session was already accepted. \n " ) ;
2013-06-29 16:01:07 +00:00
else
{
2013-07-13 12:14:32 +00:00
char convolink [ 512 ] ;
2013-06-29 16:01:07 +00:00
JCL_JingleSend ( jcl , c2c , " session-accept " ) ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , target , NULL , NULL , " %s " , target ) ;
Con_SubPrintf ( b - > name , " Accepting session from %s. \n " , convolink ) ;
2013-06-29 16:01:07 +00:00
}
}
else
{
if ( c2c )
{
2013-07-13 12:14:32 +00:00
char convolink [ 512 ] ;
2013-06-29 16:01:07 +00:00
JCL_JingleSend ( jcl , c2c , " session-terminate " ) ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , target , NULL , NULL , " %s " , target ) ;
Con_SubPrintf ( b - > name , " Terminating session with %s. \n " , convolink ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name , " That session has already expired. \n " ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-06-29 16:01:07 +00:00
void JCL_JingleParsePeerPorts ( jclient_t * jcl , struct c2c_s * c2c , xmltree_t * inj , char * from )
2013-06-23 02:33:52 +00:00
{
xmltree_t * incontent = XML_ChildOfTree ( inj , " content " , 0 ) ;
xmltree_t * intransport = XML_ChildOfTree ( incontent , " transport " , 0 ) ;
xmltree_t * incandidate ;
2013-06-29 16:01:07 +00:00
struct icecandinfo_s rem ;
2013-06-23 02:33:52 +00:00
int i ;
2013-06-29 16:01:07 +00:00
if ( strcmp ( c2c - > with , from ) | | strcmp ( c2c - > sid , XML_GetParameter ( inj , " sid " , " " ) ) )
2013-06-23 02:33:52 +00:00
{
Con_Printf ( " %s is trying to mess with our connections... \n " , from ) ;
return ;
}
2013-06-29 16:01:07 +00:00
if ( ! c2c - > sid )
return ;
if ( ! intransport )
return ;
if ( ! c2c - > ice )
return ;
piceapi - > ICE_Set ( c2c - > ice , " rufrag " , XML_GetParameter ( intransport , " ufrag " , " " ) ) ;
piceapi - > ICE_Set ( c2c - > ice , " rpwd " , XML_GetParameter ( intransport , " pwd " , " " ) ) ;
2013-06-23 18:43:59 +00:00
for ( i = 0 ; ( incandidate = XML_ChildOfTree ( intransport , " candidate " , i ) ) ; i + + )
2013-06-23 02:33:52 +00:00
{
char * s ;
memset ( & rem , 0 , sizeof ( rem ) ) ;
2013-06-29 16:01:07 +00:00
Q_strlcpy ( rem . addr , XML_GetParameter ( incandidate , " ip " , " " ) , sizeof ( rem . addr ) ) ;
Q_strlcpy ( rem . candidateid , XML_GetParameter ( incandidate , " id " , " " ) , sizeof ( rem . candidateid ) ) ;
2013-06-23 02:33:52 +00:00
s = XML_GetParameter ( incandidate , " type " , " " ) ;
if ( s & & ! strcmp ( s , " srflx " ) )
rem . type = ICE_SRFLX ;
else if ( s & & ! strcmp ( s , " prflx " ) )
rem . type = ICE_PRFLX ;
else if ( s & & ! strcmp ( s , " relay " ) )
rem . type = ICE_RELAY ;
else
rem . type = ICE_HOST ;
rem . port = atoi ( XML_GetParameter ( incandidate , " port " , " 0 " ) ) ;
rem . priority = atoi ( XML_GetParameter ( incandidate , " priority " , " 0 " ) ) ;
rem . network = atoi ( XML_GetParameter ( incandidate , " network " , " 0 " ) ) ;
rem . generation = atoi ( XML_GetParameter ( incandidate , " generation " , " 0 " ) ) ;
rem . foundation = atoi ( XML_GetParameter ( incandidate , " foundation " , " 0 " ) ) ;
rem . component = atoi ( XML_GetParameter ( incandidate , " component " , " 0 " ) ) ;
s = XML_GetParameter ( incandidate , " protocol " , " udp " ) ;
if ( s & & ! strcmp ( s , " udp " ) )
rem . transport = 0 ;
else
rem . transport = 0 ;
2013-06-29 16:01:07 +00:00
piceapi - > ICE_AddRCandidateInfo ( c2c - > ice , & rem ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-06-29 16:01:07 +00:00
qboolean JCL_JingleHandleInitiate ( jclient_t * jcl , xmltree_t * inj , char * from )
2013-06-23 02:33:52 +00:00
{
xmltree_t * incontent = XML_ChildOfTree ( inj , " content " , 0 ) ;
xmltree_t * intransport = XML_ChildOfTree ( incontent , " transport " , 0 ) ;
xmltree_t * indescription = XML_ChildOfTree ( incontent , " description " , 0 ) ;
char * transportxmlns = intransport ? intransport - > xmlns : " " ;
char * descriptionxmlns = indescription ? indescription - > xmlns : " " ;
char * descriptionmedia = XML_GetParameter ( indescription , " media " , " " ) ;
2013-06-29 16:01:07 +00:00
char * sid = XML_GetParameter ( inj , " sid " , " " ) ;
2013-06-23 02:33:52 +00:00
xmltree_t * jingle ;
struct icestate_s * ice ;
qboolean accepted = false ;
enum icemode_e imode ;
2013-06-29 16:01:07 +00:00
char * response = " session-terminate " ;
char * offer = " pwn you " ;
char * autocvar = " xmpp_autoaccepthax " ;
char * initiator ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
struct c2c_s * c2c = NULL ;
int mt = ICEP_INVALID ;
2013-06-29 21:08:09 +00:00
buddy_t * b ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
//FIXME: add support for session forwarding so that we might forward the connection to the real server. for now we just reject it.
initiator = XML_GetParameter ( inj , " initiator " , " " ) ;
if ( strcmp ( initiator , from ) )
return false ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
if ( incontent & & ! strcmp ( descriptionmedia , QUAKEMEDIATYPE ) & & ! strcmp ( descriptionxmlns , QUAKEMEDIAXMLNS ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char * host = XML_GetParameter ( indescription , " host " , " you " ) ;
if ( ! strcmp ( host , " you " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
mt = ICEP_QWSERVER ;
2013-07-13 12:14:32 +00:00
offer = " wants to join your game " ;
2013-06-29 16:01:07 +00:00
autocvar = " xmpp_autoacceptjoins " ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( host , " me " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
mt = ICEP_QWCLIENT ;
2013-07-13 12:14:32 +00:00
offer = " wants to invite you to thier game " ;
2013-06-29 16:01:07 +00:00
autocvar = " xmpp_autoacceptinvites " ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
}
if ( incontent & & ! strcmp ( descriptionmedia , " audio " ) & & ! strcmp ( descriptionxmlns , " urn:xmpp:jingle:apps:rtp:1 " ) )
{
mt = ICEP_VOICE ;
2013-07-13 12:14:32 +00:00
offer = " is trying to call you " ;
2013-06-29 16:01:07 +00:00
autocvar = " xmpp_autoacceptvoice " ;
}
if ( mt = = ICEP_INVALID )
return false ;
//FIXME: if both people try to establish a connection to the other simultaneously, the higher session id is meant to be canceled, and the lower accepted automagically.
c2c = JCL_JingleCreateSession ( jcl , from , false ,
sid ,
strcmp ( transportxmlns , " urn:xmpp:jingle:transports:raw-udp:1 " ) ? ICEM_ICE : ICEM_RAW ,
mt
) ;
if ( ! c2c )
return false ;
2013-06-29 21:08:09 +00:00
JCL_FindBuddy ( jcl , from , & b , NULL ) ;
2013-06-29 16:01:07 +00:00
if ( c2c - > mediatype = = ICEP_VOICE )
{
qboolean okay = false ;
int i = 0 ;
xmltree_t * payload ;
//chuck it at the engine and see what sticks. at least one must...
while ( ( payload = XML_ChildOfTree ( indescription , " payload-type " , i + + ) ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
char * name = XML_GetParameter ( payload , " name " , " " ) ;
char * clock = XML_GetParameter ( payload , " clockrate " , " " ) ;
char * id = XML_GetParameter ( payload , " id " , " " ) ;
char parm [ 64 ] ;
char val [ 64 ] ;
2013-07-13 12:14:32 +00:00
//note: the engine will ignore codecs it does not support, returning false.
2013-06-29 16:01:07 +00:00
if ( ! strcmp ( name , " SPEEX " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
Q_snprintf ( parm , sizeof ( parm ) , " codec%i " , atoi ( id ) ) ;
Q_snprintf ( val , sizeof ( val ) , " speex@%i " , atoi ( clock ) ) ;
okay | = piceapi - > ICE_Set ( c2c - > ice , parm , val ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
else if ( ! strcmp ( name , " OPUS " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
Q_snprintf ( parm , sizeof ( parm ) , " codec%i " , atoi ( id ) ) ;
okay | = piceapi - > ICE_Set ( c2c - > ice , parm , " opus " ) ;
2013-06-23 02:33:52 +00:00
}
}
2013-06-29 16:01:07 +00:00
//don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something.
2013-07-13 12:14:32 +00:00
//we really ought to give a reason, but we're rude.
2013-06-29 16:01:07 +00:00
if ( ! okay )
{
2013-07-13 12:14:32 +00:00
char convolink [ 512 ] ;
2013-06-29 16:01:07 +00:00
JCL_JingleSend ( jcl , c2c , " session-terminate " ) ;
2013-07-13 12:14:32 +00:00
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , from , NULL , NULL , " %s " , b - > name ) ;
Con_SubPrintf ( b - > name , " %s does not support any compatible audio codecs, and is unable to call you. \n " , convolink ) ;
2013-06-29 16:01:07 +00:00
return false ;
}
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
JCL_JingleParsePeerPorts ( jcl , c2c , inj , from ) ;
if ( c2c - > mediatype ! = ICEP_INVALID )
{
2013-07-13 12:14:32 +00:00
char convolink [ 512 ] ;
JCL_GenLink ( jcl , convolink , sizeof ( convolink ) , NULL , from , NULL , NULL , " %s " , b - > name ) ;
2013-06-29 16:01:07 +00:00
if ( ! pCvar_GetFloat ( autocvar ) )
{
2013-07-13 12:14:32 +00:00
char authlink [ 512 ] ;
char denylink [ 512 ] ;
JCL_GenLink ( jcl , authlink , sizeof ( authlink ) , " jauth " , from , NULL , sid , " %s " , " Accept " ) ;
JCL_GenLink ( jcl , denylink , sizeof ( denylink ) , " jdeny " , from , NULL , sid , " %s " , " Reject " ) ;
2013-06-29 16:01:07 +00:00
//show a prompt for it, send the reply when the user decides.
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name ,
2013-07-13 12:14:32 +00:00
" %s %s. %s %s \n " , convolink , offer , authlink , denylink ) ;
2013-06-29 21:08:09 +00:00
pCon_SetActive ( b - > name ) ;
2013-06-29 16:01:07 +00:00
return true ;
}
else
{
2013-07-13 12:14:32 +00:00
Con_SubPrintf ( b - > name , " Auto-accepting session from %s \n " , convolink ) ;
2013-06-29 16:01:07 +00:00
response = " session-accept " ;
}
}
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
JCL_JingleSend ( jcl , c2c , response ) ;
return true ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
qboolean JCL_ParseJingle ( jclient_t * jcl , xmltree_t * tree , char * from , char * id )
2013-06-23 02:33:52 +00:00
{
char * action = XML_GetParameter ( tree , " action " , " " ) ;
char * sid = XML_GetParameter ( tree , " sid " , " " ) ;
2013-06-29 16:01:07 +00:00
struct c2c_s * c2c = NULL , * * link ;
2013-06-29 21:08:09 +00:00
buddy_t * b ;
2013-06-29 16:01:07 +00:00
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( ! strcmp ( ( * link ) - > sid , sid ) )
{
c2c = * link ;
if ( ! c2c - > accepted )
break ;
}
}
2013-06-29 21:08:09 +00:00
JCL_FindBuddy ( jcl , from , & b , NULL ) ;
2013-06-23 02:33:52 +00:00
//validate sender
2013-06-29 16:01:07 +00:00
if ( c2c & & strcmp ( c2c - > with , from ) )
2013-06-23 02:33:52 +00:00
{
Con_Printf ( " %s is trying to mess with our connections... \n " , from ) ;
2013-06-29 16:01:07 +00:00
return false ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
//FIXME: transport-info, transport-replace
2013-06-23 02:33:52 +00:00
if ( ! strcmp ( action , " session-terminate " ) )
{
2013-06-29 16:01:07 +00:00
xmltree_t * reason = XML_ChildOfTree ( tree , " reason " , 0 ) ;
if ( ! c2c )
{
Con_Printf ( " Received session-terminate without an active session \n " ) ;
return false ;
}
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
if ( reason & & reason - > child )
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name , " Session ended: %s \n " , reason - > child - > name ) ;
2013-06-29 16:01:07 +00:00
else
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name , " Session ended \n " ) ;
2013-06-29 16:01:07 +00:00
//unlink it
for ( link = & jcl - > c2c ; * link ; link = & ( * link ) - > next )
{
if ( * link = = c2c )
{
* link = c2c - > next ;
break ;
}
}
// XML_ConPrintTree(tree, 0);
if ( c2c - > ice )
piceapi - > ICE_Close ( c2c - > ice ) ;
free ( c2c ) ;
}
//content-accept
//content-add
//content-modify
//content-reject
//content-remove
//description-info
//security-info
//
else if ( ! strcmp ( action , " transport-info " ) )
{ //peer wants to add ports.
if ( c2c )
JCL_JingleParsePeerPorts ( jcl , c2c , tree , from ) ;
else
Con_DPrintf ( " Received transport-info without an active session \n " ) ;
}
//FIXME: we need to add support for this to downgrade to raw if someone tries calling through a SIP gateway
else if ( ! strcmp ( action , " transport-replace " ) )
{
if ( c2c )
{
if ( 1 )
JCL_JingleSend ( jcl , c2c , " transport-reject " ) ;
else
{
JCL_JingleParsePeerPorts ( jcl , c2c , tree , from ) ;
JCL_JingleSend ( jcl , c2c , " transport-accept " ) ;
}
}
}
else if ( ! strcmp ( action , " transport-reject " ) )
{
JCL_JingleSend ( jcl , c2c , " session-terminate " ) ;
2013-06-23 02:33:52 +00:00
}
else if ( ! strcmp ( action , " session-accept " ) )
{
2013-06-29 16:01:07 +00:00
if ( ! c2c )
{
Con_DPrintf ( " Unknown session acceptance \n " ) ;
return false ;
}
else if ( ! c2c - > creator )
{
Con_DPrintf ( " Peer tried to accept a session that *they* created! \n " ) ;
return false ;
}
else if ( c2c - > accepted )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
//pidgin is buggy and can dupe-accept sessions multiple times.
Con_DPrintf ( " Duplicate session-accept from peer. \n " ) ;
//XML_ConPrintTree(tree, 0);
return false ;
2013-06-23 02:33:52 +00:00
}
else
{
2013-06-29 16:01:07 +00:00
char * responder = XML_GetParameter ( tree , " responder " , from ) ;
if ( strcmp ( responder , from ) )
{
return false ;
}
2013-06-29 21:08:09 +00:00
Con_SubPrintf ( b - > name , " Session Accepted! \n " ) ;
2013-06-29 16:01:07 +00:00
// XML_ConPrintTree(tree, 0);
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
JCL_JingleParsePeerPorts ( jcl , c2c , tree , from ) ;
c2c - > accepted = true ;
2013-06-23 02:33:52 +00:00
//if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted
2013-06-29 16:01:07 +00:00
if ( c2c - > ice )
piceapi - > ICE_Set ( c2c - > ice , " state " , STRINGIFY ( ICE_CONNECTING ) ) ;
2013-06-23 02:33:52 +00:00
}
}
else if ( ! strcmp ( action , " session-initiate " ) )
{
2013-06-29 16:01:07 +00:00
// Con_Printf("Peer initiating connection!\n");
// XML_ConPrintTree(tree, 0);
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
if ( ! JCL_JingleHandleInitiate ( jcl , tree , from ) )
return false ;
2013-06-23 02:33:52 +00:00
}
else
{
Con_Printf ( " Unknown jingle action: %s \n " , action ) ;
2013-06-29 16:01:07 +00:00
// XML_ConPrintTree(tree, 0);
2013-06-23 02:33:52 +00:00
}
JCL_AddClientMessagef ( jcl ,
" <iq type='result' to='%s' id='%s' /> " , from , id ) ;
2013-06-29 16:01:07 +00:00
return true ;
2013-06-23 02:33:52 +00:00
}
2013-07-13 12:14:32 +00:00
# endif
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 ) ) )
{
Con_Printf ( " XMPP: Trying to connect to %s (%s) \n " , jcl - > domain , srvserver ) ;
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
{
Con_Printf ( " XMPP: Trying to connect to %s \n " , jcl - > domain ) ;
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-06-29 16:01:07 +00:00
struct iq_s * JCL_SendIQ ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , char * body )
2013-06-23 02:33:52 +00:00
{
struct iq_s * iq ;
iq = malloc ( sizeof ( * iq ) ) ;
iq - > next = jcl - > pendingiqs ;
jcl - > pendingiqs = iq ;
2013-06-23 18:43:59 +00:00
Q_snprintf ( iq - > id , sizeof ( iq - > id ) , " %i " , rand ( ) ) ;
2013-06-23 02:33:52 +00:00
iq - > callback = callback ;
if ( target )
{
if ( * jcl - > jid )
JCL_AddClientMessagef ( jcl , " <iq type='%s' id='%s' from='%s' to='%s'> " , iqtype , iq - > id , jcl - > jid , target ) ;
else
JCL_AddClientMessagef ( jcl , " <iq type='%s' id='%s' to='%s'> " , iqtype , iq - > id , target ) ;
}
else
{
if ( * jcl - > jid )
JCL_AddClientMessagef ( jcl , " <iq type='%s' id='%s' from='%s'> " , iqtype , iq - > id , jcl - > jid ) ;
else
JCL_AddClientMessagef ( jcl , " <iq type='%s' id='%s'> " , iqtype , iq - > id ) ;
}
JCL_AddClientMessageString ( jcl , body ) ;
JCL_AddClientMessageString ( jcl , " </iq> " ) ;
2013-06-29 16:01:07 +00:00
return iq ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
struct iq_s * JCL_SendIQf ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , char * fmt , . . . )
2013-06-23 02:33:52 +00:00
{
va_list argptr ;
char body [ 2048 ] ;
va_start ( argptr , fmt ) ;
2013-06-23 18:43:59 +00:00
Q_vsnprintf ( body , sizeof ( body ) , fmt , argptr ) ;
2013-06-23 02:33:52 +00:00
va_end ( argptr ) ;
2013-06-29 16:01:07 +00:00
return JCL_SendIQ ( jcl , callback , iqtype , target , body ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
struct iq_s * JCL_SendIQNode ( jclient_t * jcl , qboolean ( * callback ) ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq ) , char * iqtype , char * target , xmltree_t * node , qboolean destroynode )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
struct iq_s * n ;
2013-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 ) ;
Con_Printf ( " Bound to jid %s \n " , jcl - > jid , 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",
// "http://www.google.com/xmpp/protocol/camera/v1",
// "http://www.google.com/xmpp/protocol/session",
// "http://www.google.com/xmpp/protocol/voice/v1",
// "http://www.google.com/xmpp/protocol/video/v1",
" jabber:iq:version " ,
2013-07-13 12:14:32 +00:00
# ifdef JINGLE
" urn:xmpp:jingle:1 " ,
QUAKEMEDIAXMLNS ,
# ifdef VOIP
" urn:xmpp:jingle:apps:rtp:1 " ,
" urn:xmpp:jingle:apps:rtp:audio " ,
# 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 ] ;
char * hash ;
unparsable = false ;
2013-03-31 04:21:08 +00:00
2013-06-29 16:01:07 +00:00
buildcaps ( msg , sizeof ( msg ) ) ;
hash = buildcapshash ( ) ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl ,
" <iq type='result' to='%s' id='%s'> "
" <query xmlns='http://jabber.org/protocol/disco#info' node='http://fteqw.com/ftexmppplug#%s'> "
" %s "
" </query> "
" </iq> " , from , id , hash , msg ) ;
}
else if ( from & & ! strcmp ( ot - > xmlns , " jabber:iq:version " ) )
{ //client->client version request
char msg [ 2048 ] ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
Q_snprintf ( msg , sizeof ( msg ) ,
" <iq type='result' to='%s' id='%s'> "
" <query xmlns='jabber:iq:version'> "
" <name>FTEQW XMPP</name> "
" <version>V " JCL_BUILD " </version> "
# ifdef Q3_VM
" <os>QVM plugin</os> "
# else
//don't specify the os otherwise, as it gives away required base addresses etc for exploits
# endif
" </query> "
" </iq> " , from , id ) ;
JCL_AddClientMessageString ( jcl , msg ) ;
}
else if ( from & & ! strcmp ( ot - > xmlns , " jabber:iq:last " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
unparsable = false ;
JCL_AddClientMessagef ( jcl ,
" <iq type='error' to='%s' id='%s'> "
" <error type='cancel'> "
" <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> "
" </error> "
" </iq> " , from , id ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
/* else if (from && !strcmp(ot->xmlns, "jabber:iq:last"))
{ //http://xmpp.org/extensions/xep-0012.html
char msg [ 2048 ] ;
int idletime = 0 ;
unparsable = false ;
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
//last activity
Q_snprintf ( msg , sizeof ( msg ) ,
" <iq type='result' to='%s' id='%s'> "
" <query xmlns='jabber:iq:last' seconds='%i'/> "
" </iq> " , from , id , idletime ) ;
JCL_AddClientMessageString ( jcl , msg ) ;
}
*/
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
# ifndef Q3_VM
ot = XML_ChildOfTree ( tree , " time " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " urn:xmpp:time " ) )
{ //http://xmpp.org/extensions/xep-0202.html
char msg [ 2048 ] ;
char tz [ 256 ] ;
char timestamp [ 256 ] ;
struct tm * timeinfo ;
int tzh , tzm ;
time_t rawtime ;
time ( & rawtime ) ;
timeinfo = localtime ( & rawtime ) ;
tzh = timeinfo - > tm_hour ;
tzm = timeinfo - > tm_min ;
timeinfo = gmtime ( & rawtime ) ;
tzh - = timeinfo - > tm_hour ;
tzm - = timeinfo - > tm_min ;
Q_snprintf ( tz , sizeof ( tz ) , " %+i:%i " , tzh , tzm ) ;
strftime ( timestamp , sizeof ( timestamp ) , " %Y-%m-%dT%H:%M:%SZ " , timeinfo ) ;
unparsable = false ;
//strftime
Q_snprintf ( msg , sizeof ( msg ) ,
" <iq type='result' to='%s' id='%s'> "
" <time xmlns='urn:xmpp:time'> "
" <tzo>%s</tzo> "
" <utc>%s</utc> "
" </time> "
" </iq> " , from , id , tz , timestamp ) ;
JCL_AddClientMessageString ( jcl , msg ) ;
2005-12-11 20:11:22 +00:00
}
2013-06-29 16:01:07 +00:00
# endif
2005-12-11 20:11:22 +00:00
2013-06-29 16:01:07 +00:00
ot = XML_ChildOfTree ( tree , " ping " , 0 ) ;
if ( ot & & ! strcmp ( ot - > xmlns , " urn:xmpp:ping " ) )
2013-06-23 02:33:52 +00:00
{
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl , " <iq type='result' to='%s' id='%s' /> " , from , id ) ;
2013-06-23 02:33:52 +00:00
}
2013-06-29 16:01:07 +00:00
if ( unparsable )
{ //unsupported stuff
char msg [ 2048 ] ;
unparsable = false ;
2013-06-23 02:33:52 +00:00
2013-06-29 16:01:07 +00:00
Con_Printf ( " Unsupported iq get \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
//tell them OH NOES, instead of requiring some timeout.
Q_snprintf ( msg , sizeof ( msg ) ,
" <iq type='error' to='%s' id='%s'> "
" <error type='cancel'> "
" <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> "
" </error> "
" </iq> " , from , id ) ;
JCL_AddClientMessageString ( jcl , msg ) ;
2005-12-11 20:11:22 +00:00
}
2013-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-06-29 16:01:07 +00:00
c = XML_ChildOfTree ( tree , " jingle " , 0 ) ;
if ( c & & ! strcmp ( c - > xmlns , " urn:xmpp:jingle:1 " ) )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
unparsable = ! JCL_ParseJingle ( jcl , c , from , id ) ;
}
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 ;
unparsable = false ;
for ( link = & jcl - > pendingiqs ; * link ; link = & ( * link ) - > next )
2005-12-11 20:11:22 +00:00
{
2013-06-29 16:01:07 +00:00
iq = * link ;
if ( ! strcmp ( iq - > id , id ) )
break ;
}
if ( * link )
{
iq = * link ;
* link = iq - > next ;
if ( iq - > callback )
{
if ( ! iq - > callback ( jcl , ! strcmp ( f , " error " ) ? NULL : tree , iq ) )
{
Con_Printf ( " Invalid iq result \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
}
}
free ( iq ) ;
}
else
{
Con_Printf ( " Unrecognised iq result \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
}
}
if ( unparsable )
{
unparsable = false ;
Con_Printf ( " Unrecognised iq type \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
}
}
void JCL_ParseMessage ( jclient_t * jcl , xmltree_t * tree )
{
xmltree_t * ot ;
qboolean unparsable = true ;
char * f = XML_GetParameter ( tree , " from " , NULL ) ;
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 ) ;
}
}
}
}
qboolean JCL_ClientDiscoInfo ( jclient_t * jcl , xmltree_t * tree , struct iq_s * iq )
{
xmltree_t * query = XML_ChildOfTree ( tree , " query " , 0 ) ;
xmltree_t * feature ;
char * var ;
int i = 0 ;
unsigned int caps = 0 ;
qboolean rtp = false ;
qboolean rtpaudio = false ;
qboolean quake = false ;
qboolean ice = false ;
qboolean raw = false ;
qboolean jingle = false ;
buddy_t * b ;
bresource_t * r ;
JCL_FindBuddy ( jcl , XML_GetParameter ( tree , " from " , " " ) , & b , & r ) ;
while ( ( feature = XML_ChildOfTree ( query , " feature " , i + + ) ) )
{
var = XML_GetParameter ( feature , " var " , " " ) ;
//check ones we recognise.
if ( ! strcmp ( var , QUAKEMEDIAXMLNS ) )
quake = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:apps:rtp:audio " ) )
rtpaudio = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:apps:rtp:1 " ) )
rtp = true ; //kinda implied, but ensures version is okay
if ( ! strcmp ( var , " urn:xmpp:jingle:transports:ice-udp:1 " ) )
ice = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:transports:raw-udp:1 " ) )
raw = true ;
if ( ! strcmp ( var , " urn:xmpp:jingle:1 " ) )
jingle = true ; //kinda implied, but ensures version is okay
}
if ( ( ice | | raw ) & & jingle )
{
if ( rtpaudio & & rtp )
caps | = CAP_VOICE ;
if ( quake )
caps | = CAP_INVITE ;
}
if ( b & & r )
r - > caps = ( r - > caps & CAP_QUERIED ) | caps ;
return true ;
}
void JCL_ParsePresence ( jclient_t * jcl , xmltree_t * tree )
{
buddy_t * buddy ;
bresource_t * bres ;
char * from = XML_GetParameter ( tree , " from " , " " ) ;
xmltree_t * show = XML_ChildOfTree ( tree , " show " , 0 ) ;
xmltree_t * status = XML_ChildOfTree ( tree , " status " , 0 ) ;
xmltree_t * quake = XML_ChildOfTree ( tree , " quake " , 0 ) ;
2013-07-13 12:14:32 +00:00
xmltree_t * muc = XML_ChildOfTreeNS ( tree , " http://jabber.org/protocol/muc#user " , " x " , 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-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 ) ) ;
if ( ! ( bres - > caps & CAP_QUERIED ) )
{
bres - > caps | = CAP_QUERIED ;
JCL_SendIQ ( jcl , JCL_ClientDiscoInfo , " get " , from , " <query xmlns='http://jabber.org/protocol/disco#info'/> " ) ;
}
}
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 ) ;
else if ( strcmp ( oldbstatus , bres - > bstatus ) | | strcmp ( oldfstatus , bres - > fstatus ) )
{
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-07-13 12:14:32 +00:00
Con_Printf ( " 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-07-13 12:14:32 +00:00
Con_Printf ( " XMPP: Unable to switch to TLS. \n " ) ;
XML_ConPrintTree ( tree , 0 ) ;
XML_Destroy ( tree ) ;
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 ) ;
return JCL_KILL ;
}
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
Con_Printf ( " XMPP: Authing with %s%s. \n " , method , jcl - > issecure ? " over tls " : " without encription " ) ;
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 ) ;
return JCL_KILL ;
2005-12-11 20:11:22 +00:00
}
}
else //we cannot auth, no suitable method.
{
2013-06-24 09:04:00 +00:00
Con_Printf ( " XMPP: Neither SASL or TLS are usable \n " ) ;
XML_Destroy ( tree ) ;
return JCL_KILL ;
2005-12-11 20:11:22 +00:00
}
}
}
2013-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-06-23 02:33:52 +00:00
//note: ext='voice-v1 camera-v1 video-v1' is some legacy nonsense, and is required for voice calls with googletalk clients or something stupid like that
Q_snprintf ( caps , sizeof ( caps ) , " <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://fteqw.com/ftexmppplugin' ver='%s'/> " , buildcapshash ( ) ) ;
2013-06-29 16:01:07 +00:00
if ( ! * jcl - > curquakeserver )
JCL_AddClientMessagef ( jcl ,
2013-06-23 02:33:52 +00:00
" <presence> "
" %s "
" </presence> " , caps ) ;
else if ( * servermap ) //if we're running a server, say so
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl ,
2013-06-23 02:33:52 +00:00
" <presence> "
" <quake xmlns='fteqw.com:game' servermap='%s'/> "
" %s "
" </presence> "
, servermap , caps ) ;
else //if we're connected to a server, say so
2013-06-29 16:01:07 +00:00
JCL_AddClientMessagef ( jcl ,
2013-06-23 02:33:52 +00:00
" <presence> "
" <quake xmlns='fteqw.com:game' serverip='%s' /> "
" %s "
" </presence> "
2013-06-29 16:01:07 +00:00
, jcl - > curquakeserver , caps ) ;
2013-06-23 02:33:52 +00:00
}
}
2005-12-11 20:11:22 +00:00
//functions above this line allow connections to multiple servers.
//it is just the control functions that only allow one server.
2013-03-31 04:21:08 +00:00
qintptr_t JCL_Frame ( qintptr_t * args )
2005-12-11 20:11:22 +00:00
{
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 ;
# ifdef JINGLE
JCL_JingleTimeouts ( jcl , false ) ;
# endif
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 ) ;
}
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 ) ;
}
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-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 ;
Q_snprintf ( caps , sizeof ( caps ) , " <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://fteqw.com/ftexmppplugin' ver='%s'/> " , buildcapshash ( ) ) ;
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-06-29 16:01:07 +00:00
if ( ! strcmp ( arg [ 0 ] + 1 , " open " ) | | ! strcmp ( arg [ 0 ] + 1 , " connect " ) | | ! strcmp ( arg [ 0 ] + 1 , " tlsopen " ) | | ! strcmp ( arg [ 0 ] + 1 , " tlsconnect " ) )
2013-06-23 02:33:52 +00:00
{ //tlsconnect is 'old'.
2009-04-19 01:48:10 +00:00
if ( ! * arg [ 1 ] )
{
2013-06-29 16:01:07 +00:00
Con_SubPrintf ( console , " %s <account@domain/resource> <password> <server> \n " , arg [ 0 ] + 1 ) ;
2009-04-19 01:48:10 +00:00
return ;
}
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-07-13 12:14:32 +00:00
jcl = jclients [ accid ] = JCL_Connect ( accid , arg [ 3 ] , ! strncmp ( arg [ 0 ] + 1 , " tls " , 3 ) , arg [ 1 ] , arg [ 2 ] ) ;
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-07-13 12:14:32 +00:00
Con_Printf ( " eg for gmail: ^[/ " COMMANDPREFIX " /connect myusername@gmail.com^] (using oauth2) \n " ) ;
Con_Printf ( " eg for gmail: ^[/ " COMMANDPREFIX " /connect myusername@gmail.com mypassword talk.google.com^] (warning: password will be saved locally in plain text) \n " ) ;
// Con_Printf("eg for facebook: ^[/" COMMANDPREFIX " /connect myusername@chat.facebook.com mypassword chat.facebook.com^]\n");
// Con_Printf("eg for msn: ^[/" COMMANDPREFIX " /connect myusername@messanger.live.com mypassword^]\n");
2013-06-29 16:01:07 +00:00
}
Con_Printf ( " Note that this info will be used the next time you start quake. \n " ) ;
2013-06-23 02:33:52 +00:00
//small note:
//for the account 'me@example.com' the server to connect to can be displayed with:
//nslookup -querytype=SRV _xmpp-client._tcp.example.com
//srv resolving seems to be non-standard on each system, I don't like having to special case things.
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /help^] \n "
" This text... \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /raw <XML STANZAS/>^] \n "
" For debug hackery. \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /friend accountname friendlyname^] \n "
" Befriends accountname, and shows them in your various lists using the friendly name. Can also be used to rename friends. \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /unfriend accountname^] \n "
" Ostracise your new best enemy. You will no longer see them and they won't be able to contact you. \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /blist^] \n "
" Show all your friends! Names are clickable and will begin conversations. \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /quit^] \n "
" Disconnect from the XMPP server, noone will be able to hear you scream. \n " ) ;
2013-06-29 16:01:07 +00:00
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /join accountname^] \n "
" Joins your friends game (they will be prompted). \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /invite accountname^] \n "
" Invite someone to join your game (they will be prompted). \n " ) ;
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /voice accountname^] \n "
" Begin a bi-directional peer-to-peer voice conversation with someone (they will be prompted). \n " ) ;
2013-06-23 02:33:52 +00:00
Con_TrySubPrint ( console , " ^[/ " COMMANDPREFIX " /msg ACCOUNTNAME your message goes here^] \n "
" Sends a message to the named person. If given a resource postfix, your message will be sent only to that resource. \n " ) ;
Con_TrySubPrint ( console , " If no arguments, will print out your friends list. If no /command is used, the arguments will be sent as a message to the person you last sent a message to. \n " ) ;
}
else if ( ! strcmp ( arg [ 0 ] + 1 , " clear " ) )
{
//just clears the current console.
if ( * console )
{
2013-06-23 18:43:59 +00:00
pCon_Destroy ( console ) ;
Con_SubPrintf ( console , " " ) ;
pCon_SetActive ( console ) ;
2013-06-23 02:33:52 +00:00
}
else
2013-06-23 18:43:59 +00:00
pCmd_AddText ( " \n clear \n " , true ) ;
2013-06-23 02:33:52 +00:00
}
2013-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
}
}