From 0823eb09688a6c8d8bf5f1bca682ade1e109d9b2 Mon Sep 17 00:00:00 2001 From: Spoike Date: Sat, 29 Jun 2013 16:01:07 +0000 Subject: [PATCH] xmpp NAT holepunching (ICE) is now implemented. it doesn't know how to do relays. xmpp voip support (speex only). just because. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4413 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_main.c | 3 +- engine/client/cl_pred.c | 2 + engine/client/console.c | 6 + engine/client/m_script.c | 4 +- engine/client/menu.c | 22 +- engine/client/p_classic.c | 3 - engine/client/p_script.c | 21 +- engine/client/snd_dma.c | 175 ++- engine/common/bothdefs.h | 2 +- engine/common/common.h | 3 + engine/common/console.h | 4 +- engine/common/net.h | 1 + engine/common/net_wins.c | 1292 +++++++++++++++---- engine/common/netinc.h | 72 +- engine/common/plugin.c | 14 +- engine/common/sha1.c | 87 ++ engine/server/sv_main.c | 1 + engine/server/sv_sys_win.c | 2 +- plugins/jabber/jabberclient.c | 2207 ++++++++++++++++++++++----------- plugins/jabber/xml.c | 2 + plugins/qvm_api.c | 12 +- 21 files changed, 2875 insertions(+), 1060 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 5b52b15e9..71a4a397e 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -3698,8 +3698,6 @@ void Host_DoRunFile(hrf_t *f) VFS_SEEK(f->srcfile, 0); - COM_StripExtension(COM_SkipPath(f->fname), qname, sizeof(qname)); - f->dstfile = FS_OpenVFS(qname, "rb", FS_GAME); if (f->dstfile) { @@ -3987,6 +3985,7 @@ double Host_Frame (double time) #ifdef PLUGINS Plug_Tick(); #endif + NET_Tick(); if (cl.paused) cl.gametimemark += time; diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index fab2c941f..3353c19ba 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -375,6 +375,8 @@ void CL_PredictUsercmd (int pnum, int entnum, player_state_t *from, player_state CL_PredictUsercmd (pnum, entnum, &temp, to, &split); return; } + if (!cl.worldmodel || cl.worldmodel->needload) + return; VectorCopy (from->origin, pmove.origin); VectorCopy (u->angles, pmove.angles); diff --git a/engine/client/console.c b/engine/client/console.c index 131e3b900..4d63c83a4 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -138,6 +138,8 @@ void Con_Destroy (console_t *con) console_t *Con_FindConsole(char *name) { console_t *con; + if (!strcmp(name, "current") && con_current) + return con_current; for (con = &con_main; con; con = con->next) { if (!strcmp(con->name, name)) @@ -149,6 +151,10 @@ console_t *Con_FindConsole(char *name) console_t *Con_Create(char *name, unsigned int flags) { console_t *con; + if (!strcmp(name, "current")) + return NULL; + if (!strcmp(name, "MAIN")) + return NULL; con = Z_Malloc(sizeof(console_t)); Q_strncpyz(con->name, name, sizeof(con->name)); diff --git a/engine/client/m_script.c b/engine/client/m_script.c index 049cf51e6..ffc4b2ceb 100644 --- a/engine/client/m_script.c +++ b/engine/client/m_script.c @@ -10,6 +10,7 @@ cvar_t menualias = SCVAR("menualias", ""); void M_Script_Remove (menu_t *menu) { menu_script = NULL; + Cbuf_AddText(va("set option cancel\n%s\n", menualias.string), RESTRICT_LOCAL); Cvar_Set(&menualias, ""); } qboolean M_Script_Key (int key, menu_t *menu) @@ -29,12 +30,11 @@ qboolean M_Script_Key (int key, menu_t *menu) void M_MenuS_Clear_f (void) { + Cvar_Set(&menualias, ""); if (menu_script) { M_RemoveMenu(menu_script); } - -// Cvar_Set(menualias.name, ""); } void M_MenuS_Script_f (void) //create a menu. diff --git a/engine/client/menu.c b/engine/client/menu.c index 22ccd5513..77c50e1f2 100644 --- a/engine/client/menu.c +++ b/engine/client/menu.c @@ -617,15 +617,35 @@ static void M_Menu_Prompt_Cancel (struct menu_s *gm) void M_Menu_Prompt (void (*callback)(void *, int), void *ctx, char *m1, char *m2, char *m3, char *optionyes, char *optionno, char *optioncancel) { promptmenu_t *m; + char *t; key_dest = key_menu; m_state = m_complex; - m = (promptmenu_t*)M_CreateMenuInfront(sizeof(*m) - sizeof(m->m)); + m = (promptmenu_t*)M_CreateMenuInfront(sizeof(*m) - sizeof(m->m) + strlen(m1)+strlen(m2)+strlen(m3)+strlen(optionyes)+strlen(optionyes)+strlen(optioncancel)+6); m->callback = callback; m->ctx = ctx; m->m.remove = M_Menu_Prompt_Cancel; + t = (char*)(m+1); + strcpy(t, m1); + m1 = t; + t += strlen(t)+1; + strcpy(t, m2); + m2 = t; + t += strlen(t)+1; + strcpy(t, m3); + m3 = t; + t += strlen(t)+1; + strcpy(t, optionyes); + optionyes = t; + t += strlen(t)+1; + strcpy(t, optionno); + optionno = t; + t += strlen(t)+1; + strcpy(t, optioncancel); + optioncancel = t; + MC_AddWhiteText(&m->m, 64, 84, m1, false); MC_AddWhiteText(&m->m, 64, 92, m2, false); MC_AddWhiteText(&m->m, 64, 100, m3, false); diff --git a/engine/client/p_classic.c b/engine/client/p_classic.c index c7e9c42d9..24c8dff18 100644 --- a/engine/client/p_classic.c +++ b/engine/client/p_classic.c @@ -341,9 +341,6 @@ static void PClassic_DrawParticles(void) static float oldtime; RSpeedMark(); - //make sure all ents are pushed through first - RQ_RenderBatchClear(); - if (!active_particles) { oldtime = cl.time; diff --git a/engine/client/p_script.c b/engine/client/p_script.c index 513bd6a88..4723be73e 100644 --- a/engine/client/p_script.c +++ b/engine/client/p_script.c @@ -1534,12 +1534,23 @@ static void P_ParticleEffect_f(void) if (!settype) { if (ptype->looks.type == PT_NORMAL && !*ptype->texname) - ptype->looks.type = PT_SPARK; - if (ptype->looks.type == PT_SPARK) + { + if (ptype->scale) + { + ptype->looks.type = PT_SPARKFAN; + Con_DPrintf("effect %s lacks a texture. assuming type sparkfan.\n", ptype->name); + } + else + { + ptype->looks.type = PT_SPARK; + Con_DPrintf("effect %s lacks a texture. assuming type spark.\n", ptype->name); + } + } + else if (ptype->looks.type == PT_SPARK) { if (*ptype->texname) ptype->looks.type = PT_TEXTUREDSPARK; - if (ptype->scale) + else if (ptype->scale) ptype->looks.type = PT_SPARKFAN; } } @@ -4836,8 +4847,8 @@ static void GL_DrawParticleBeam(int count, beamseg_t **blist, plooks_t *type) continue; p = b->p; - q->rgba[3] = 1; - p->rgba[3] = 1; +// q->rgba[3] = 1; +// p->rgba[3] = 1; VectorSubtract(r_refdef.vieworg, q->org, v); VectorNormalize(v); diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index 80c0c8a3f..29a288f35 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -225,9 +225,11 @@ void S_SoundInfo_f(void) enum { - VOIP_SPEEX = 0, //original supported codec - VOIP_RAW = 1, //support is not recommended. - VOIP_OPUS = 2, //supposed to be better than speex. + VOIP_SPEEX_OLD = 0, //original supported codec (with needless padding and at the wrong rate to keep quake implementations easy) + VOIP_RAW = 1, //support is not recommended. + VOIP_OPUS = 2, //supposed to be better than speex. + VOIP_SPEEX_NARROW = 3, //narrowband speex. packed data. + VOIP_SPEEX_WIDE = 4, //wideband speex. packed data. VOIP_INVALID = 16 //not currently generating audio. }; @@ -242,7 +244,8 @@ static struct SpeexBits encbits; SpeexBits decbits[MAX_CLIENTS]; - const SpeexMode *mode; + const SpeexMode *modenb; + const SpeexMode *modewb; } speex; struct @@ -280,6 +283,7 @@ static struct unsigned char capturebuf[32768]; /*pending data*/ unsigned int capturepos;/*amount of pending data*/ unsigned int encsequence;/*the outgoing sequence count*/ + unsigned int enctimestamp;/*for rtp streaming*/ unsigned int generation;/*incremented whenever capture is restarted*/ qboolean wantsend; /*set if we're capturing data to send*/ float voiplevel; /*your own voice level*/ @@ -439,7 +443,8 @@ static qboolean S_Speex_Init(void) } #endif - s_voip.speex.mode = qspeex_lib_get_mode(SPEEX_MODEID_NB); + s_voip.speex.modenb = qspeex_lib_get_mode(SPEEX_MODEID_NB); + s_voip.speex.modewb = qspeex_lib_get_mode(SPEEX_MODEID_WB); s_voip.speex.loaded = true; return s_voip.speex.loaded; @@ -498,7 +503,9 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un //make sure old state is closed properly. switch(s_voip.deccodec[sender]) { - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: qspeex_decoder_destroy(s_voip.decoder[sender]); break; case VOIP_OPUS: @@ -513,17 +520,23 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un { default: //codec not supported. return; - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: if (!S_Speex_Init()) return; //speex not usable. - - s_voip.decsamplerate[sender] = 11025; + if (codec == VOIP_SPEEX_NARROW) + s_voip.decsamplerate[sender] = 8000; + else if (codec == VOIP_SPEEX_WIDE) + s_voip.decsamplerate[sender] = 16000; + else + s_voip.decsamplerate[sender] = 11025; s_voip.decframesize[sender] = 160; if (!s_voip.decoder[sender]) { qspeex_bits_init(&s_voip.speex.decbits[sender]); qspeex_bits_reset(&s_voip.speex.decbits[sender]); - s_voip.decoder[sender] = qspeex_decoder_init(s_voip.speex.mode); + s_voip.decoder[sender] = qspeex_decoder_init(codec==VOIP_SPEEX_WIDE?s_voip.speex.modewb:s_voip.speex.modenb); if (!s_voip.decoder[sender]) return; } @@ -576,7 +589,9 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un } switch(codec) { - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: qspeex_decode_int(s_voip.decoder[sender], NULL, decodebuf + decodesamps); decodesamps += s_voip.decframesize[sender]; break; @@ -601,25 +616,46 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un default: bytes = 0; break; - case VOIP_SPEEX: - bytes--; - len = *start++; - if (bytes < len) - break; + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + if (codec == VOIP_SPEEX_OLD) + { //older versions support only this, and require this extra bit. + bytes--; + len = *start++; + if (bytes < len) + break; + } + else + len = bytes; qspeex_bits_read_from(&s_voip.speex.decbits[sender], start, len); bytes -= len; start += len; - qspeex_decode_int(s_voip.decoder[sender], &s_voip.speex.decbits[sender], decodebuf + decodesamps); - decodesamps += s_voip.decframesize[sender]; + while (qspeex_decode_int(s_voip.decoder[sender], &s_voip.speex.decbits[sender], decodebuf + decodesamps) == 0) + { + decodesamps += s_voip.decframesize[sender]; + s_voip.decseq[sender]++; + seq++; + if (decodesamps + s_voip.decframesize[sender] > sizeof(decodebuf)/sizeof(decodebuf[0])) + { + S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + decodesamps = 0; + } + } break; case VOIP_OPUS: + //FIXME: we shouldn't need this crap bytes--; len = *start++; if (bytes < len) break; r = qopus_decode(s_voip.decoder[sender], start, len, decodebuf + decodesamps, sizeof(decodebuf)/sizeof(decodebuf[0]) - decodesamps, false); if (r > 0) + { decodesamps += r; + s_voip.decseq[sender]++; + seq++; + } else if (r < 0) Con_Printf("Opus decoding error %i\n", r); @@ -627,8 +663,6 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un start += len; break; } - s_voip.decseq[sender]++; - seq++; } if (drops) @@ -638,6 +672,22 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); } +#ifdef SUPPORT_ICE +void S_Voip_RTP_Parse(unsigned short sequence, char *codec, unsigned char *data, unsigned int datalen) +{ + if (!strcmp(codec, "speex@8000")) + S_Voip_Decode(MAX_CLIENTS-1, VOIP_SPEEX_NARROW, 0, sequence, datalen, data); + if (!strcmp(codec, "speex@11025")) + S_Voip_Decode(MAX_CLIENTS-1, VOIP_SPEEX_OLD, 0, sequence, datalen, data); //very much non-standard rtp + if (!strcmp(codec, "speex@16000")) + S_Voip_Decode(MAX_CLIENTS-1, VOIP_SPEEX_WIDE, 0, sequence, datalen, data); +} +qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, char *codec, char *cdata, int clength); +qboolean NET_RTP_Active(void); +#else +#define NET_RTP_Active() false +#endif + void S_Voip_Parse(void) { unsigned int sender; @@ -674,7 +724,8 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) unsigned int outpos;//in bytes unsigned int encpos;//in bytes short *start; - unsigned char initseq;//in frames + unsigned int initseq;//in frames + unsigned int inittimestamp;//in samples unsigned int i; unsigned int samps; float level, f; @@ -682,6 +733,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) float micamp = cl_voip_micamp.value; qboolean voipsendenable = true; int voipcodec = cl_voip_codec.ival; + qboolean rtpstream = NET_RTP_Active(); if (buf) { @@ -693,6 +745,14 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } else voipsendenable = cl_voip_test.ival; + if (rtpstream) + { + voipsendenable = true; + //if rtp streaming is enabled, hack the codec to something better supported + if (voipcodec == VOIP_SPEEX_OLD) + voipcodec = VOIP_SPEEX_NARROW; + } + voicevolumemod = s_voip.lastspoke_any > realtime?cl_voip_ducking.value:1; @@ -714,7 +774,9 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } switch(s_voip.enccodec) { - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: break; case VOIP_OPUS: qopus_encoder_destroy(s_voip.encoder); @@ -749,7 +811,9 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) /*see if we can init our encoding codec...*/ switch(voipcodec) { - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: if (!S_Speex_Init()) { Con_Printf("Unable to use speex codec - not installed\n"); @@ -758,12 +822,17 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) qspeex_bits_init(&s_voip.speex.encbits); qspeex_bits_reset(&s_voip.speex.encbits); - s_voip.encoder = qspeex_encoder_init(s_voip.speex.mode); + s_voip.encoder = qspeex_encoder_init(voipcodec == VOIP_SPEEX_WIDE?s_voip.speex.modewb:s_voip.speex.modenb); if (!s_voip.encoder) return; qspeex_encoder_ctl(s_voip.encoder, SPEEX_GET_FRAME_SIZE, &s_voip.encframesize); qspeex_encoder_ctl(s_voip.encoder, SPEEX_GET_SAMPLING_RATE, &s_voip.encsamplerate); - s_voip.encsamplerate = 11025; + if (voipcodec == VOIP_SPEEX_NARROW) + s_voip.encsamplerate = 8000; + else if (voipcodec == VOIP_SPEEX_WIDE) + s_voip.encsamplerate = 16000; + else + s_voip.encsamplerate = 11025; qspeex_encoder_ctl(s_voip.encoder, SPEEX_SET_SAMPLING_RATE, &s_voip.encsamplerate); break; case VOIP_OPUS: @@ -833,7 +902,9 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) //reset codecs so they start with a clean slate when new audio blocks are generated. switch(s_voip.enccodec) { - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: qspeex_bits_reset(&s_voip.speex.encbits); break; case VOIP_OPUS: @@ -861,10 +932,11 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } initseq = s_voip.encsequence; + inittimestamp = s_voip.enctimestamp; level = 0; samps=0; //*2 for 16bit audio input. - for (encpos = 0, outpos = 0; s_voip.capturepos-encpos >= s_voip.encframesize*2 && sizeof(outbuf)-outpos > 64; s_voip.encsequence++) + for (encpos = 0, outpos = 0; s_voip.capturepos-encpos >= s_voip.encframesize*2 && sizeof(outbuf)-outpos > 64; ) { start = (short*)(s_voip.capturebuf + encpos); @@ -902,7 +974,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) switch(s_voip.enccodec) { - case VOIP_SPEEX: + case VOIP_SPEEX_OLD: qspeex_bits_reset(&s_voip.speex.encbits); qspeex_encode_int(s_voip.encoder, start, &s_voip.speex.encbits); len = qspeex_bits_write(&s_voip.speex.encbits, outbuf+(outpos+1), sizeof(outbuf) - (outpos+1)); @@ -910,6 +982,28 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) len = 0; outbuf[outpos] = len; outpos += 1+len; + s_voip.encsequence++; + s_voip.enctimestamp += s_voip.encframesize; + samps+=s_voip.encframesize; + encpos += s_voip.encframesize*2; + break; + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + qspeex_bits_reset(&s_voip.speex.encbits); + for (; s_voip.capturepos-encpos >= s_voip.encframesize*2 && sizeof(outbuf)-outpos > 64; ) + { + start = (short*)(s_voip.capturebuf + encpos); + qspeex_encode_int(s_voip.encoder, start, &s_voip.speex.encbits); + s_voip.encsequence++; + samps+=s_voip.encframesize; + s_voip.enctimestamp += s_voip.encframesize; + encpos += s_voip.encframesize*2; + + if (rtpstream) + break; + } + len = qspeex_bits_write(&s_voip.speex.encbits, outbuf+outpos, sizeof(outbuf) - outpos); + outpos += len; break; case VOIP_OPUS: len = qopus_encode(s_voip.encoder, start, s_voip.encframesize, outbuf+(outpos+1), max(255, sizeof(outbuf) - (outpos+1))); @@ -925,20 +1019,25 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) //error! Con_Printf("Opus encoding error: %i\n", len); } + s_voip.encsequence++; + samps+=s_voip.encframesize; + s_voip.enctimestamp += s_voip.encframesize; + encpos += s_voip.encframesize*2; break; default: outbuf[outpos] = 0; break; } - samps+=s_voip.encframesize; - encpos += s_voip.encframesize*2; + + if (rtpstream) + break; } if (samps) { float nl; nl = (3000*level) / (32767.0f*32767*samps); s_voip.voiplevel = (s_voip.voiplevel*7 + nl)/8; - if (s_voip.voiplevel < cl_voip_vad_threshhold.ival && !(cl_voip_send.ival & 2)) + if (s_voip.voiplevel < cl_voip_vad_threshhold.ival && !(cl_voip_send.ival & 6)) { /*try and dump it, it was too quiet, and they're not pressing +voip*/ if (s_voip.keeps > samps) @@ -965,7 +1064,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) if (outpos && (!buf || buf->maxsize - buf->cursize >= outpos+4)) { - if (buf) + if (buf && (cl_voip_send.ival & ~4)) { MSG_WriteByte(buf, clc); MSG_WriteByte(buf, (s_voip.enccodec<<4) | (s_voip.generation & 0x0f)); /*gonna leave that nibble clear here... in this version, the client will ignore packets with those bits set. can use them for codec or something*/ @@ -974,6 +1073,18 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) SZ_Write(buf, outbuf, outpos); } + switch(s_voip.enccodec) + { + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_OLD: + NET_RTP_Transmit(initseq, inittimestamp, va("speex@%i", s_voip.encsamplerate), outbuf, outpos); + break; + case VOIP_OPUS: + NET_RTP_Transmit(initseq, inittimestamp, "opus", outbuf, outpos); + break; + } + if (cl_voip_test.ival) S_Voip_Decode(cl.playerview[0].playernum, s_voip.enccodec, s_voip.generation & 0x0f, initseq, outpos, outbuf); diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 070bcf823..18ae11ee1 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -246,7 +246,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define IRCCONNECT //an ircconnect command, that allows the player to connect to irc-encapsulated qw protocols... yeah, really. #define PLUGINS //qvm/dll plugins. - #define SUPPORT_ICE //Internet Connection Establishment protocol, for peer-to-peer connections + #define SUPPORT_ICE //Interactive Connectivity Establishment protocol, for peer-to-peer connections #ifdef _DEBUG // #define OFFSCREENGECKO //FIXME: move to plugin and remove from engine diff --git a/engine/common/common.h b/engine/common/common.h index 556a3935b..b2785e2dd 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -510,6 +510,9 @@ qbyte COM_BlockSequenceCheckByte (qbyte *base, int length, int sequence, unsigne qbyte COM_BlockSequenceCRCByte (qbyte *base, int length, int sequence); qbyte Q2COM_BlockSequenceCRCByte (qbyte *base, int length, int sequence); +int SHA1(char *digest, int maxdigestsize, char *string, int stringlen); +int SHA1_HMAC(unsigned char *digest, int maxdigestsize, unsigned char *key, int keylen, unsigned char *data, int datalen); + int version_number(void); char *version_string(void); diff --git a/engine/common/console.h b/engine/common/console.h index e1c83983c..ab56d3f0c 100644 --- a/engine/common/console.h +++ b/engine/common/console.h @@ -106,8 +106,8 @@ typedef struct conline_s { float time; } conline_t; -#define CONF_HIDDEN 1 -#define CONF_NOTIFY 2 +#define CONF_HIDDEN 1 /*do not show in the console list (unless active)*/ +#define CONF_NOTIFY 2 /*text printed to console also appears as notify lines*/ #define CONF_NOTIFY_BOTTOM 4 /*align the bottom*/ #define CONF_NOTIMES 8 typedef struct console_s diff --git a/engine/common/net.h b/engine/common/net.h index 8ed8c5281..817b0975f 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -83,6 +83,7 @@ int TCP_OpenStream (netadr_t *remoteaddr); //makes things easier struct ftenet_connections_s; void NET_Init (void); +void NET_Tick (void); void SVNET_RegisterCvars(void); void NET_InitClient (void); void NET_InitServer (void); diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 9ee0eb9c2..e6848ffe4 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -2582,7 +2582,6 @@ void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen) } #include "fs.h" -int SHA1(char *digest, int maxdigestsize, char *string, int stringlen); qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; @@ -4869,8 +4868,196 @@ void NET_GetLocalAddress (int socket, netadr_t *out) #endif } + +typedef struct +{ + unsigned short msgtype; + unsigned short msglen; + unsigned int magiccookie; + unsigned int transactid[3]; +} stunhdr_t; +typedef struct +{ + unsigned short attrtype; + unsigned short attrlen; +} stunattr_t; +#define SUPPORT_ICE #ifdef SUPPORT_ICE +/* +Interactive Connectivity Establishment (rfc 5245) +find out your peer's potential ports. +spam your peer with stun packets. +see what sticks. +the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. +if no candidates are available, try using stun to find public nat addresses. + +in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. + +stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. +peers don't like it when those are missing. + +host candidates - addresses that are directly known +server reflexive candidates - addresses that we found from some public stun server +peer reflexive candidates - addresses that our peer finds out about as we spam them +relayed candidates - some sort of socks5 or something proxy. + +*/ + +struct icecandidate_s +{ + struct icecandinfo_s info; + + struct icecandidate_s *next; + + netadr_t peer; + //peer needs telling or something. + qboolean dirty; + + //these are bitmasks. one bit for each local socket. + unsigned int reachable; + unsigned int tried; +}; +struct icestate_s +{ + struct icestate_s *next; + void *module; + + netadr_t chosenpeer; + + netadr_t pubstunserver; + unsigned int stunretry; //once a second, extended to once a minite on reply + char *stunserver;//where to get our public ip from. + int stunport; + unsigned int stunrnd[3]; + + unsigned int timeout; //time when we consider the connection dead + unsigned int keepalive; //sent periodically... + unsigned int retries; //bumped after each round of connectivity checks. affects future intervals. + enum iceproto_e proto; + enum icemode_e mode; + qboolean controlled; //controller chooses final ports. + enum icestate_e state; + char *conname; //internal id. + char *friendlyname; //who you're talking to. + + struct icecandidate_s *lc; + char *lpwd; + char *lufrag; + + struct icecandidate_s *rc; + char *rpwd; + char *rufrag; + + unsigned int tiehigh; + unsigned int tielow; + + char *codec[32]; //96-127. don't really need to care about other ones. +}; static struct icestate_s *icelist; + + +#if !defined(SERVERONLY) && defined(VOICECHAT) +extern cvar_t cl_voip_send; +struct rtpheader_s +{ + unsigned char v2_p1_x1_cc4; + unsigned char m1_pt7; + unsigned short seq; + unsigned int timestamp; + unsigned int ssrc; + unsigned int csrc[1]; //sized according to cc +}; +void S_Voip_RTP_Parse(unsigned short sequence, char *codec, unsigned char *data, unsigned int datalen); +qboolean NET_RTP_Parse(void) +{ + struct rtpheader_s *rtpheader = (void*)net_message.data; + if (net_message.cursize >= sizeof(*rtpheader) && (rtpheader->v2_p1_x1_cc4 & 0xc0) == 0x80) + { + int hlen; + int padding = 0; + struct icestate_s *con; + int proto; + //make sure this really came from an accepted rtp stream + //note that an rtp connection equal to the game connection will likely mess up when sequences start to get big + //(especially problematic in sane clients that start with a random sequence) + for (con = icelist; con; con = con->next) + { + if (con->state != ICE_INACTIVE && con->proto == ICEP_VOICE && NET_CompareAdr(&net_from, &con->chosenpeer)) + break; + } + //and continue with parsing it if its okay. + if (con) + { + proto = rtpheader->m1_pt7 & 0x7f; + if (proto < 96 || proto > 127) + return false; + proto -= 96; + if (rtpheader->v2_p1_x1_cc4 & 0x20) + padding = net_message.data[net_message.cursize-1]; + hlen = sizeof(*rtpheader); + hlen += ((rtpheader->v2_p1_x1_cc4 & 0xf)-1) * sizeof(int); + S_Voip_RTP_Parse((unsigned short)BigShort(rtpheader->seq), con->codec[proto], hlen+(char*)(rtpheader), net_message.cursize - padding - hlen); + return true; + } + } + return false; +} +qboolean NET_RTP_Active(void) +{ + struct icestate_s *con; + for (con = icelist; con; con = con->next) + { + if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) + return true; + } + return false; +} +qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, char *codec, char *cdata, int clength) +{ + sizebuf_t buf; + char pdata[512]; + int i; + struct icestate_s *con; + qboolean built = false; + + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(pdata); + buf.cursize = 0; + buf.allowoverflow = true; + buf.data = pdata; + + for (con = icelist; con; con = con->next) + { + if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) + { + for (i = 0; i < sizeof(con->codec)/sizeof(con->codec[0]); i++) + { + if (con->codec[i] && !strcmp(con->codec[i], codec)) + { + if (!built) + { + built = true; + MSG_WriteByte(&buf, (2u<<6) | (0u<<5) | (0u<<4) | (0<<0)); //v2_p1_x1_cc4 + MSG_WriteByte(&buf, (0u<<7) | ((i+96)<<0)); //m1_pt7 + MSG_WriteShort(&buf, BigShort(sequence)); //seq + MSG_WriteLong(&buf, BigLong(timestamp)); //timestamp + MSG_WriteLong(&buf, BigLong(0)); //ssrc + SZ_Write(&buf, cdata, clength); + if (buf.overflowed) + return built; + } + NET_SendPacket(NS_CLIENT, buf.cursize, buf.data, &con->chosenpeer); + break; + } + } + } + } + return built; +} +#endif + + + struct icestate_s *QDECL ICE_Find(void *module, char *conname) { struct icestate_s *con; @@ -4882,45 +5069,52 @@ struct icestate_s *QDECL ICE_Find(void *module, char *conname) } return NULL; } -struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, enum icemode_e mode) +struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, enum icemode_e mode, enum iceproto_e proto) { ftenet_connections_t *collection; struct icestate_s *con; - int netsrc; - if (conname) - if (ICE_Find(module, conname)) - return NULL; + //only allow modes that we actually support. + if (mode != ICEM_RAW && mode != ICEM_ICE) + return NULL; + + //only allow protocols that we actually support. + switch(proto) + { + default: + return NULL; +#if !defined(SERVERONLY) && defined(VOICECHAT) + case ICEP_VOICE: + collection = cls.sockets; + break; +#endif +#ifndef SERVERONLY + case ICEP_QWCLIENT: + collection = cls.sockets; + break; +#endif +#ifndef CLIENTONLY + case ICEP_QWSERVER: + collection = svs.sockets; + break; +#endif + } if (!conname) { -#ifdef SERVERONLY - return NULL; -#else int rnd[2]; Sys_RandomBytes((void*)rnd, sizeof(rnd)); conname = va("fte%08x%08x", rnd[0], rnd[1]); - collection = cls.sockets; //initiator is ALWAYS the game client. - netsrc = NS_CLIENT; -#endif - } - else - { -#ifdef CLIENTONLY - return NULL; -#else - collection = svs.sockets; //responder is ALWAYS the game server. - netsrc = NS_SERVER; -#endif } con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); con->friendlyname = Z_StrDup(peername); - con->netsrc = netsrc; + con->proto = proto; + con->rpwd = Z_StrDup(""); + con->rufrag = Z_StrDup(""); con->mode = mode; - con->mode = ICE_RAW; con->next = icelist; icelist = con; @@ -4928,7 +5122,7 @@ struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, { int rnd[1]; //'must have at least 24 bits randomness' Sys_RandomBytes((void*)rnd, sizeof(rnd)); - con->lfrag = Z_StrDup(va("%08x", rnd[0])); + con->lufrag = Z_StrDup(va("%08x", rnd[0])); } { int rnd[4]; //'must have at least 128 bits randomness' @@ -4936,6 +5130,9 @@ struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, con->lpwd = Z_StrDup(va("%08x%08x%08x%08x", rnd[0], rnd[1], rnd[2], rnd[3])); } + Sys_RandomBytes((void*)&con->tiehigh, sizeof(con->tiehigh)); + Sys_RandomBytes((void*)&con->tielow, sizeof(con->tielow)); + if (collection) { int i; @@ -4958,20 +5155,20 @@ struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, if (adr.type == NA_IP || adr.type == NA_IPV6) { cand = Z_Malloc(sizeof(*cand)); - cand->network = net; - cand->port = ntohs(adr.port); + cand->info.network = net; + cand->info.port = ntohs(adr.port); adr.port = 0; //to make sure its not part of the string... - cand->addr = Z_StrDup(NET_AdrToString(adrbuf, sizeof(adrbuf), &adr)); - cand->generation = 0; - cand->component = 1; - cand->foundation = 1; - cand->priority = + Q_strncpyz(cand->info.addr, NET_AdrToString(adrbuf, sizeof(adrbuf), &adr), sizeof(cand->info.addr)); + cand->info.generation = 0; + cand->info.component = 1; + cand->info.foundation = 1; + cand->info.priority = (1<<24)*(126) + - (1<<8)*((adr.type == NA_IP?32768:0)+net*256+(255-adrcount)) + - (1<<0)*(256 - cand->component); + (1<<8)*((adr.type == NA_IP?32768:0)+net*256+(255-adrno)) + + (1<<0)*(256 - cand->info.component); Sys_RandomBytes((void*)rnd, sizeof(rnd)); - cand->candidateid = Z_StrDup(va("x%08x%08x", rnd[0], rnd[1])); + Q_strncpyz(cand->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(cand->info.candidateid)); cand->dirty = true; cand->next = con->lc; @@ -4985,28 +5182,321 @@ struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, return con; } -void QDECL ICE_Begin(struct icestate_s *con, char *stunip, int stunport) +#include "zlib.h" +ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) { - switch(con->mode) + switch(con->proto) { - case ICE_RAW: - //info is already as complete as it'll ever be... yeah, it sucks. sue me. - if (con->netsrc == NS_CLIENT) - { - struct icecandidate_s *rc; - rc = con->rc;//for (rc = con->rc; rc; - if (rc && (!strchr(rc->addr, ';') && !strchr(rc->addr, '\n'))) - Cbuf_AddText(va("connect [%s]:%i\n", rc->addr, rc->port), RESTRICT_LOCAL); - else - Con_Printf("Remote candidate is not valid\n"); - } - break; - case ICE_ICE: - //FIXME: schedule some stun requests to some public stun server + default: break; +#ifndef SERVERONLY + case ICEP_VOICE: + case ICEP_QWCLIENT: + return cls.sockets; +#endif +#ifndef CLIENTONLY + case ICEP_QWSERVER: + return svs.sockets; +#endif } + return NULL; } -struct icecandidate_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) +//if either remotecand is null, new packets will be sent to all. +static qboolean ICE_SendSpam(struct icestate_s *con) +{ + struct icecandidate_s *rc; + int i; + int bestlocal = -1; + struct icecandidate_s *bestpeer = NULL; + ftenet_connections_t *collection = ICE_PickConnection(con); + if (!collection) + return false; + + //only send one ping to each. + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (collection->conn[i]) + { + for(rc = con->rc; rc; rc = rc->next) + { + if (!(rc->tried & (1u<tried & (1u<info.priority < rc->info.priority) + { + bestpeer = rc; + bestlocal = i; + } + } + } + } + } + + + if (bestpeer && bestlocal >= 0) + { + netadr_t to; + sizebuf_t buf; + char data[512]; + char integ[20]; + int crc; + qboolean usecandidate = false; + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + bestpeer->tried |= (1u<info.addr, bestpeer->info.port, &to)) + return true; + Con_DPrintf("Spam %i -> %s:%i\n", bestlocal, bestpeer->info.addr, bestpeer->info.port); + + if (!con->controlled && NET_CompareAdr(&to, &con->chosenpeer)) + usecandidate = true; + + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, 0); //fill in later + MSG_WriteLong(&buf, BigLong(0x2112a442)); + MSG_WriteLong(&buf, BigLong(0)); //randomid + MSG_WriteLong(&buf, BigLong(0)); //randomid + MSG_WriteLong(&buf, BigLong(0x80000000|bestlocal)); //randomid + + if (usecandidate) + { + MSG_WriteShort(&buf, BigShort(0x25));//ICE-USE-CANDIDATE + MSG_WriteShort(&buf, BigShort(0)); + } + + //username + MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME + MSG_WriteShort(&buf, BigShort(strlen(con->rufrag) + 1 + strlen(con->lufrag))); + SZ_Write(&buf, con->rufrag, strlen(con->rufrag)); + MSG_WriteChar(&buf, ':'); + SZ_Write(&buf, con->lufrag, strlen(con->lufrag)); + while(buf.cursize&3) + MSG_WriteChar(&buf, 0); + + //priority + MSG_WriteShort(&buf, BigShort(0x24));//ICE-PRIORITY + MSG_WriteShort(&buf, BigShort(4)); + MSG_WriteLong(&buf, 0); //FIXME + + //these two attributes carry a random 64bit tie-breaker. + //the controller is the one with the highest number. + if (con->controlled) + { + MSG_WriteShort(&buf, BigShort(0x8029));//ICE-CONTROLLED + MSG_WriteShort(&buf, BigShort(8)); + MSG_WriteLong(&buf, BigLong(con->tiehigh)); + MSG_WriteLong(&buf, BigLong(con->tielow)); + } + else + { + MSG_WriteShort(&buf, BigShort(0x802A));//ICE-CONTROLLING + MSG_WriteShort(&buf, BigShort(8)); + MSG_WriteLong(&buf, BigLong(con->tiehigh)); + MSG_WriteLong(&buf, BigLong(con->tielow)); + } + + //message integrity is a bit annoying + data[2] = ((buf.cursize+4+sizeof(integ)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + data[3] = ((buf.cursize+4+sizeof(integ)-20)>>0)&0xff; + //but the hash is to the start of the attribute's header + SHA1_HMAC(integ, sizeof(integ), con->rpwd, strlen(con->rpwd), data, buf.cursize); + MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY + MSG_WriteShort(&buf, BigShort(20)); //sha1 key length + SZ_Write(&buf, integ, sizeof(integ)); //integrity data + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + //fill in the length (for the fourth time, after filling in the integrity and fingerprint) + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + + collection->conn[bestlocal]->SendPacket(collection->conn[bestlocal], buf.cursize, data, &to); + return true; + } + return false; +} + +void ICE_ToStunServer(struct icestate_s *con) +{ + sizebuf_t buf; + char data[512]; + int crc; + ftenet_connections_t *collection = ICE_PickConnection(con); + if (!collection) + return; + if (!con->stunrnd[0]) + Sys_RandomBytes((char*)con->stunrnd, sizeof(con->stunrnd)); + + Con_DPrintf("Spam stun %s\n", NET_AdrToString(data, sizeof(data), &con->pubstunserver)); + + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, 0); //fill in later + MSG_WriteLong(&buf, BigLong(0x2112a442)); + MSG_WriteLong(&buf, BigLong(con->stunrnd[0])); //randomid + MSG_WriteLong(&buf, BigLong(con->stunrnd[1])); //randomid + MSG_WriteLong(&buf, BigLong(con->stunrnd[2])); //randomid + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + //fill in the length (for the fourth time, after filling in the integrity and fingerprint) + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + + NET_SendPacket((con->proto==ICEP_QWSERVER)?NS_SERVER:NS_CLIENT, buf.cursize, data, &con->pubstunserver); +} + +qboolean QDECL ICE_Set(struct icestate_s *con, char *prop, char *value) +{ + if (!strcmp(prop, "state")) + { + int oldstate = con->state; + if (!strcmp(value, STRINGIFY(ICE_CONNECTING))) + con->state = ICE_CONNECTING; + else if (!strcmp(value, STRINGIFY(ICE_INACTIVE))) + con->state = ICE_INACTIVE; + else if (!strcmp(value, STRINGIFY(ICE_FAILED))) + con->state = ICE_FAILED; + else if (!strcmp(value, STRINGIFY(ICE_CONNECTED))) + con->state = ICE_CONNECTED; + else + { + Con_Printf("ICE_Set invalid state %s\n", value); + con->state = ICE_INACTIVE; + } + con->timeout = Sys_Milliseconds(); + + con->retries = 0; + + if (oldstate != con->state && con->state == ICE_CONNECTED) + { + if (con->chosenpeer.type == NA_INVALID) + { + con->state = ICE_FAILED; + Con_Printf("ICE failed. peer not valid.\n"); + } +#ifndef SERVERONLY + else if (con->proto == ICEP_QWCLIENT) + { + char msg[256]; +// Con_Printf("Try typing connect %s\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); + Cbuf_AddText(va("\nconnect \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); + } +#endif +#ifndef CLIENTONLY + else if (con->proto == ICEP_QWSERVER) + { + extern void SVC_GetChallenge(); + net_from = con->chosenpeer; + SVC_GetChallenge(); + } +#endif + if (con->state == ICE_CONNECTED) + Con_Printf("%s connection established.\n", con->proto == ICEP_VOICE?"voice":"Quake"); + } + +#if !defined(SERVERONLY) && defined(VOICECHAT) + cl_voip_send.ival = (cl_voip_send.ival & ~4) | (NET_RTP_Active()?4:0); +#endif + } + else if (!strcmp(prop, "controlled")) + con->controlled = !!atoi(value); + else if (!strcmp(prop, "controller")) + con->controlled = !atoi(value); + else if (!strncmp(prop, "codec", 5)) + { + int codec = atoi(prop+5); + if (codec < 96 || codec > 127) + return false; + if (strcmp(value, "speex@8000") && strcmp(value, "speex@16000"))// && strcmp(value, "opus")) + return false; + codec -= 96; + Z_Free(con->codec[codec]); + con->codec[codec] = Z_StrDup(value); + } + else if (!strcmp(prop, "rufrag")) + { + Z_Free(con->rufrag); + con->rufrag = Z_StrDup(value); + } + else if (!strcmp(prop, "rpwd")) + { + Z_Free(con->rpwd); + con->rpwd = Z_StrDup(value); + } + else if (!strcmp(prop, "stunip")) + { + Z_Free(con->stunserver); + con->stunserver = Z_StrDup(value); + NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); + } + else if (!strcmp(prop, "stunport")) + { + con->stunport = atoi(value); + if (con->stunserver) + NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); + } + else + return false; + return true; +} +qboolean QDECL ICE_Get(struct icestate_s *con, char *prop, char *value, int valuelen) +{ + if (!strcmp(prop, "sid")) + Q_strncpyz(value, con->conname, valuelen); + else if (!strcmp(prop, "state")) + Q_snprintfz(value, valuelen, "%i", con->state); + else if (!strcmp(prop, "lufrag")) + Q_strncpyz(value, con->lufrag, valuelen); + else if (!strcmp(prop, "lpwd")) + Q_strncpyz(value, con->lpwd, valuelen); + else if (!strncmp(prop, "codec", 5)) + { + int codec = atoi(prop+5); + if (codec < 96 || codec > 127) + return false; + codec -= 96; + if (con->codec[codec]) + Q_strncpyz(value, con->codec[codec], valuelen); + else + Q_strncpyz(value, "", valuelen); + } + else if (!strcmp(prop, "newlc")) + { + struct icecandidate_s *can; + Q_strncpyz(value, "0", valuelen); + for (can = con->lc; can; can = can->next) + { + if (can->dirty) + { + Q_strncpyz(value, "1", valuelen); + break; + } + } + } + else + return false; + return true; +} +struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) { struct icecandidate_s *can; for (can = con->lc; can; can = can->next) @@ -5014,17 +5504,27 @@ struct icecandidate_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) if (can->dirty) { can->dirty = false; - return can; + return &can->info; } } return NULL; } -void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandidate_s *n) +void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) { struct icecandidate_s *o; + qboolean isnew; + netadr_t peer; + //I don't give a damn about rtpc. + if (n->component != 1) + return; + + if (!NET_StringToAdr(n->addr, n->port, &peer)) + return; + for (o = con->rc; o; o = o->next) { - if (!strcmp(o->candidateid, n->candidateid)) + //not sure that updating candidates is particuarly useful tbh, but hey. + if (!strcmp(o->info.candidateid, n->candidateid)) break; } if (!o) @@ -5032,27 +5532,101 @@ void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandidate_s * o = Z_Malloc(sizeof(*o)); o->next = con->rc; con->rc = o; - o->candidateid = Z_StrDup(n->candidateid); + Q_strncpyz(o->info.candidateid, n->candidateid, sizeof(o->info.candidateid)); + + isnew = true; } else { - Z_Free(o->addr); + isnew = false; } - o->addr = Z_StrDup(n->addr); - o->port = n->port; - o->type = n->type; - o->priority = n->priority; - o->network = n->network; - o->generation = n->generation; - o->foundation = n->foundation; - o->component = n->component; - o->transport = n->transport; + Q_strncpyz(o->info.addr, n->addr, sizeof(o->info.addr)); + o->info.port = n->port; + o->info.type = n->type; + o->info.priority = n->priority; + o->info.network = n->network; + o->info.generation = n->generation; + o->info.foundation = n->foundation; + o->info.component = n->component; + o->info.transport = n->transport; + o->dirty = true; + o->peer = peer; + o->tried = 0; + o->reachable = 0; + + Con_DPrintf("%s remote candidate %s: [%s]:%i\n", isnew?"Added":"Updated", o->info.candidateid, o->info.addr, o->info.port); } static void ICE_Destroy(struct icestate_s *con) { //has already been unlinked Z_Free(con); } +static void ICE_Tick(void) +{ + struct icestate_s *con; + unsigned int curtime = Sys_Milliseconds(); + + for (con = icelist; con; con = con->next) + { + switch(con->mode) + { + case ICEM_RAW: + //raw doesn't do handshakes or keepalives. it should just directly connect. + //raw just uses the first (assumed only) option + if (con->state == ICE_CONNECTING) + { + struct icecandidate_s *rc; + rc = con->rc; + if (rc) + NET_StringToAdr(rc->info.addr, rc->info.port, &con->chosenpeer); + else + con->chosenpeer.type = NA_INVALID; + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + break; + case ICEM_ICE: + if (con->state == ICE_CONNECTING) + { + if (con->stunretry < curtime && con->pubstunserver.type != NA_INVALID) + { + ICE_ToStunServer(con); + con->stunretry = curtime + 2*1000; + } + if (con->keepalive < curtime) + { + if (!ICE_SendSpam(con)) + { + struct icecandidate_s *rc; + struct icecandidate_s *best = NULL; + for (rc = con->rc; rc; rc = rc->next) + { + if (rc->reachable && (!best || rc->info.priority > best->info.priority)) + best = rc; + } + if (best) + { + best->tried = ~best->reachable; + con->chosenpeer = best->peer; + ICE_SendSpam(con); + } + else + { + for (rc = con->rc; rc; rc = rc->next) + rc->tried = 0; + } + con->retries++; + if (con->retries > 32) + con->retries = 32; + con->keepalive = curtime + 200*(con->retries); //RTO + } + else + con->keepalive = curtime + 50*(con->retries+1); //Ta + } + } + break; + } + } +} void QDECL ICE_Close(struct icestate_s *con) { struct icestate_s **link; @@ -5085,6 +5659,408 @@ void QDECL ICE_CloseModule(void *module) link = &(*link)->next; } } +icefuncs_t iceapi = +{ + ICE_Create, + ICE_Set, + ICE_Get, + ICE_GetLCandidateInfo, + ICE_AddRCandidateInfo, + ICE_Close, + ICE_CloseModule +}; + +static qboolean NET_WasStun(netsrc_t netsrc) +{ +#if !defined(SERVERONLY) && defined(VOICECHAT) + if (netsrc == NS_CLIENT) + { + if (NET_RTP_Parse()) + return true; + } +#endif + + if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20) + { + stunhdr_t *stun = (stunhdr_t*)net_message.data; + int stunlen = BigShort(stun->msglen); + if ((stun->msgtype == BigShort(0x0101) || stun->msgtype == BigShort(0x0111)) && net_message.cursize == stunlen + sizeof(*stun)) + { + //binding reply (or error) + netadr_t adr = net_from; + char xor[16]; + short portxor; + stunattr_t *attr = (stunattr_t*)(stun+1); + int alen; + while(stunlen) + { + stunlen -= sizeof(*attr); + alen = (unsigned short)BigShort(attr->attrlen); + if (alen > stunlen) + return false; + stunlen -= alen; + switch(BigShort(attr->attrtype)) + { + default: + break; + case 1: + case 0x20: + if (BigShort(attr->attrtype) == 0x20) + { + portxor = *(short*)&stun->magiccookie; + memcpy(xor, &stun->magiccookie, sizeof(xor)); + } + else + { + portxor = 0; + memset(xor, 0, sizeof(xor)); + } + if (alen == 8 && ((qbyte*)attr)[5] == 1) //ipv4 MAPPED-ADDRESS + { + char str[256]; + adr.type = NA_IP; + adr.port = (((short*)attr)[3]) ^ portxor; + *(int*)adr.address.ip = *(int*)(&((qbyte*)attr)[8]) ^ *(int*)xor; + NET_AdrToString(str, sizeof(str), &adr); + } + else if (alen == 20 && ((qbyte*)attr)[5] == 2) //ipv6 MAPPED-ADDRESS + { + netadr_t adr; + char str[256]; + adr.type = NA_IPV6; + adr.port = (((short*)attr)[3]) ^ portxor; + ((int*)adr.address.ip6)[0] = ((int*)&((qbyte*)attr)[8])[0] ^ ((int*)xor)[0]; + ((int*)adr.address.ip6)[1] = ((int*)&((qbyte*)attr)[8])[1] ^ ((int*)xor)[1]; + ((int*)adr.address.ip6)[2] = ((int*)&((qbyte*)attr)[8])[2] ^ ((int*)xor)[2]; + ((int*)adr.address.ip6)[3] = ((int*)&((qbyte*)attr)[8])[3] ^ ((int*)xor)[3]; + NET_AdrToString(str, sizeof(str), &adr); + } + + { + struct icestate_s *con; + for (con = icelist; con; con = con->next) + { + char str[256]; + struct icecandidate_s *rc; + if (con->mode != ICEM_ICE) + continue; + + //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. + if (NET_CompareAdr(&net_from, &con->pubstunserver)) + { + for (rc = con->lc; rc; rc = rc->next) + { + if (NET_CompareAdr(&adr, &rc->peer)) + break; + } + if (!rc) + { + struct icecandidate_s *rc; + rc = Z_Malloc(sizeof(*rc)); + rc->next = con->lc; + con->lc = rc; + rc->peer = adr; + NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &adr); + rc->info.port = ntohs(adr.port); + rc->info.type = ICE_SRFLX; + rc->info.component = 1; + rc->dirty = true; + rc->info.priority = 1; //FIXME + + Con_DPrintf("Public address: %s\n", str); + } + con->stunretry = Sys_Milliseconds() + 60*1000; + } + else + { + for (rc = con->rc; rc; rc = rc->next) + { + if (NET_CompareAdr(&net_from, &rc->peer)) + { + if (!(rc->reachable & (1u<<(net_from.connum-1)))) + Con_DPrintf("We can reach %s\n", NET_AdrToString(str, sizeof(str), &net_from)); + rc->reachable |= 1u<<(net_from.connum-1); + + if (NET_CompareAdr(&net_from, &con->chosenpeer) && (stun->transactid[2] & BigLong(0x80000000))) + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + } + } + } + } + break; + case 9: + { + char msg[64]; + char sender[256]; + unsigned short len = BigShort(attr->attrlen)-4; + if (len > sizeof(msg)-1) + len = sizeof(msg)-1; + memcpy(msg, &((qbyte*)attr)[8], len); + msg[len] = 0; + Con_DPrintf("%s: Stun error code %u : %s\n", NET_AdrToString(sender, sizeof(sender), &net_from), ((qbyte*)attr)[7], msg); + if (((qbyte*)attr)[7] == 1) + { + //not authorised. + } + if (((qbyte*)attr)[7] == 87) + { + //role conflict. + } + } + break; + } + alen = (alen+3)&~3; + attr = (stunattr_t*)((char*)(attr+1) + alen); + } + return true; + } + else if (stun->msgtype == BigShort(0x0011) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + { + //binding indication. used as an rtp keepalive. + return true; + } + else if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + { + char username[256]; + char integrity[20]; + char *integritypos = NULL; + int role = 0; + struct icestate_s *con; + unsigned int tiehigh = 0; + unsigned int tielow = 0; + qboolean usecandidate = false; + int error = 0; + unsigned int priority = 0; + + //binding request + stunattr_t *attr = (stunattr_t*)(stun+1); + int alen; + *username = 0; + while(stunlen) + { + alen = (unsigned short)BigShort(attr->attrlen); + if (alen+sizeof(*attr) > stunlen) + return false; + switch((unsigned short)BigShort(attr->attrtype)) + { + default: + //unknown attributes < 0x8000 are 'mandatory to parse', and such packets must be dropped in their entirety. + //other ones are okay. + if (!((unsigned short)BigShort(attr->attrtype) & 0x8000)) + return false; + break; + case 0x6: + //username + if (alen < sizeof(username)) + { + memcpy(username, attr+1, alen); + username[alen] = 0; +// Con_Printf("Stun username = \"%s\"\n", username); + } + break; + case 0x8: + //message integrity + memcpy(integrity, attr+1, sizeof(integrity)); + integritypos = (char*)(attr+1); + break; + case 0x24: + //priority +// Con_Printf("priority = \"%i\"\n", priority); + priority = BigLong(*(int*)(attr+1)); + break; + case 0x25: + //USE-CANDIDATE + usecandidate = true; + break; + case 0x8028: + //fingerprint +// Con_Printf("fingerprint = \"%08x\"\n", BigLong(*(int*)(attr+1))); + break; + case 0x8029://ice controlled + case 0x802A://ice controlling + role = (unsigned short)BigShort(attr->attrtype); + //ice controlled + tiehigh = BigLong(((int*)(attr+1))[0]); + tielow = BigLong(((int*)(attr+1))[1]); + break; + } + alen = (alen+3)&~3; + attr = (stunattr_t*)((char*)(attr+1) + alen); + stunlen -= alen+sizeof(*attr); + } + + //we need to know which connection its from in order to validate the integrity + for (con = icelist; con; con = con->next) + { + if (!strcmp(va("%s:%s", con->lufrag, con->rufrag), username)) + break; + } + if (!con) + { + Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); + } + else + { + if (integritypos) + { + char key[20]; + //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. + stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); + SHA1_HMAC(key, sizeof(key), con->lpwd, strlen(con->lpwd), (qbyte*)stun, integritypos-4 - (char*)stun); + if (memcmp(key, integrity, sizeof(integrity))) + { + Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); + return true; + } + } + + if (con->state != ICE_INACTIVE) + { + sizebuf_t buf; + char data[512]; + int alen = 0, atype = 0, aofs = 0; + int crc; + struct icecandidate_s *rc; + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. + for (rc = con->rc; rc; rc = rc->next) + { + if (NET_CompareAdr(&net_from, &rc->peer)) + break; + } + if (!rc) + { + struct icecandidate_s *rc; + rc = Z_Malloc(sizeof(*rc)); + rc->next = con->rc; + con->rc = rc; + rc->peer = net_from; + NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &net_from); + rc->info.port = ntohs(net_from.port); + rc->info.type = ICE_PRFLX; + rc->dirty = true; + rc->info.priority = priority; + } + + //flip ice control role, if we're wrong. + if (role && role != (con->controlled?0x802A:0x8029)) + { + con->controlled = (tiehigh > con->tiehigh) || (tiehigh == con->tiehigh && tielow > con->tielow); + Con_DPrintf("role conflict detected. We should be %s\n", con->controlled?"controlled":"controlling"); + error = 87; + } + else if (usecandidate && con->controlled) + { + //in the controlled role, we're connected once we're told the pair to use (by the usecandidate flag). + //note that this 'nominates' candidate pairs, from which the highest priority is chosen. + //so we just pick select the highest. + //this is problematic, however, as we don't actually know the real priority that the peer thinks we'll nominate it with. + + if (con->chosenpeer.type != NA_INVALID && !NET_CompareAdr(&net_from, &con->chosenpeer)) + Con_DPrintf("Duplicate use-candidate\n"); + con->chosenpeer = net_from; + Con_DPrintf("use-candidate: %s\n", NET_AdrToString(data, sizeof(data), &net_from)); + + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + + if (net_from.type == NA_IP) + { + alen = 4; + atype = 1; + aofs = 0; + } + else if (net_from.type == NA_IPV6 && + !*(int*)&net_from.address.ip6[0] && + !*(int*)&net_from.address.ip6[4] && + !*(short*)&net_from.address.ip6[8] && + *(short*)&net_from.address.ip6[10] == (short)0xffff) + { //just because we use an ipv6 address for ipv4 internally doesn't mean we should tell the peer that they're on ipv6... + alen = 4; + atype = 1; + aofs = sizeof(net_from.address.ip6) - sizeof(net_from.address.ip); + } + else if (net_from.type == NA_IPV6) + { + alen = 16; + atype = 2; + aofs = 0; + } + + MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); + MSG_WriteShort(&buf, BigShort(0)); //fill in later + MSG_WriteLong(&buf, stun->magiccookie); + MSG_WriteLong(&buf, stun->transactid[0]); + MSG_WriteLong(&buf, stun->transactid[1]); + MSG_WriteLong(&buf, stun->transactid[2]); + + if (error) + { + char *txt = "Role Conflict"; + MSG_WriteShort(&buf, BigShort(0x0009)); + MSG_WriteShort(&buf, BigShort(4 + strlen(txt))); + MSG_WriteShort(&buf, 0); //reserved + MSG_WriteByte(&buf, 0); //class + MSG_WriteByte(&buf, error); //code + SZ_Write(&buf, txt, strlen(txt)); //readable + while(buf.cursize&3) //padding + MSG_WriteChar(&buf, 0); + } + else if (1) + { //xor mapped + MSG_WriteShort(&buf, BigShort(0x0020)); + MSG_WriteShort(&buf, BigShort(4+alen)); + MSG_WriteShort(&buf, BigShort(atype)); + MSG_WriteShort(&buf, net_from.port); + SZ_Write(&buf, (char*)&net_from.address + aofs, alen); + } + else + { //non-xor mapped + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, BigShort(4+alen)); + MSG_WriteShort(&buf, BigShort(atype)); + MSG_WriteShort(&buf, net_from.port); + SZ_Write(&buf, (char*)&net_from.address + aofs, alen); + } + + MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME + MSG_WriteShort(&buf, BigShort(strlen(username))); + SZ_Write(&buf, username, strlen(username)); + while(buf.cursize&3) + MSG_WriteChar(&buf, 0); + + //message integrity is a bit annoying + data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; + //but the hash is to the start of the attribute's header + SHA1_HMAC(integrity, sizeof(integrity), con->lpwd, strlen(con->lpwd), data, buf.cursize); + MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY + MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length + SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + NET_SendPacket(netsrc, buf.cursize, data, &net_from); + } + } + + return true; + } + } + return false; +} #endif #ifndef CLIENTONLY @@ -5114,155 +6090,6 @@ void SVNET_AddPort_f(void) FTENET_AddToCollection(svs.sockets, conname, *s?s:NULL, *s?NA_IP:NA_INVALID, true); } - -typedef struct -{ - unsigned short msgtype; - unsigned short msglen; - unsigned int magiccookie; - unsigned int transactid[3]; -} stunhdr_t; -typedef struct -{ - unsigned short attrtype; - unsigned short attrlen; -} stunattr_t; -static qboolean NET_WasStun(netsrc_t netsrc) -{ - if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20) - { - stunhdr_t *stun = (stunhdr_t*)net_message.data; - int stunlen = BigShort(stun->msglen); - if (stun->msgtype == BigShort(0x0101) && net_message.cursize == stunlen + sizeof(*stun)) - { - //binding reply - stunattr_t *attr = (stunattr_t*)(stun+1); - int alen; - while(stunlen) - { - stunlen -= sizeof(*attr); - alen = BigShort(attr->attrlen); - if (alen > stunlen) - return false; - stunlen -= alen; - switch(BigShort(attr->attrtype)) - { - case 1: - if (alen == 8 && ((qbyte*)attr)[5] == 1) //ipv4 MAPPED-ADDRESS - { - netadr_t adr; - char str[256]; - adr.type = NA_IP; - adr.port = (((short*)attr)[3]); - memcpy(adr.address.ip, &((qbyte*)attr)[8], 4); - NET_AdrToString(str, sizeof(str), &adr); - Con_Printf("Public address %s\n", str); - } - else if (alen == 20 && ((qbyte*)attr)[5] == 2) //ipv6 MAPPED-ADDRESS - { - netadr_t adr; - char str[256]; - adr.type = NA_IPV6; - adr.port = (((short*)attr)[3]); - memcpy(adr.address.ip6, &((qbyte*)attr)[8], 16); - NET_AdrToString(str, sizeof(str), &adr); - Con_Printf("Public address %s\n", str); - } - break; - } - attr = (stunattr_t*)((char*)(attr+1) + alen); - } - return true; - } - else if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun)) - { - char username[256]; - //binding request - stunattr_t *attr = (stunattr_t*)(stun+1); - int alen; - *username = 0; - while(stunlen) - { - alen = (unsigned short)BigShort(attr->attrlen); - if (alen+sizeof(*attr) > stunlen) - return false; - switch((unsigned short)BigShort(attr->attrtype)) - { - case 0x6: - //username - if (alen < sizeof(username)) - { - memcpy(username, attr+1, alen); - username[alen] = 0; - Con_Printf("Stun username = \"%s\"\n", username); - } - break; - case 0x8: - //message integrity - Con_Printf("integrity = \"%08x%08x%08x%08x%08x\"\n", BigLong(*(int*)(attr+1)), BigLong(*(int*)(attr+2)), BigLong(*(int*)(attr+3)), BigLong(*(int*)(attr+4)), BigLong(*(int*)(attr+5))); - break; - case 0x24: - //priority - Con_Printf("priority = \"%i\"\n", BigLong(*(int*)(attr+1))); - break; - case 0x8028: - //fingerprint - Con_Printf("fingerprint = \"%08x\"\n", BigLong(*(int*)(attr+1))); - break; - case 0x8029: - //ice controlled - Con_Printf("tie breaker = \"%08x%08x\"\n", BigLong(*(int*)(attr+1)), BigLong(*(int*)(attr+2))); - break; - } - alen = (alen+3)&~3; - attr = (stunattr_t*)((char*)(attr+1) + alen); - stunlen -= alen+sizeof(*attr); - } - - { - struct { - stunhdr_t hdr; - - stunattr_t unattr; - char uname[256]; - } stunmsg; - stunmsg.hdr.magiccookie = BigLong(0x2112a442); - Sys_RandomBytes((qbyte*)&stunmsg.hdr.transactid[0], sizeof(stunmsg.hdr.transactid)); // 'and SHOULD be cryptographically random' - stunmsg.hdr.msgtype = BigShort(0x0101); //binding result - strcpy(stunmsg.uname, username); - stunmsg.unattr.attrtype = BigShort(0x6); - stunmsg.unattr.attrlen = BigShort(strlen(stunmsg.uname)); - stunmsg.hdr.msglen = BigShort(sizeof(stunmsg.unattr)+((strlen(stunmsg.uname)+3)&~3)); - NET_SendPacket(netsrc, sizeof(stunmsg.hdr) + BigShort(stunmsg.hdr.msglen), &stunmsg, &net_from); - } - - return true; - } - } - return false; -} -void SVNET_Stun_f(void) -{ - char *stunserver = Cmd_Argv(1); - netadr_t stunserverip; - struct { - stunhdr_t hdr; - } stunmsg; - - //"stun.l.google.com:19302" - - if (!NET_StringToAdr(stunserver, 3478, &stunserverip) || stunserverip.type != NA_IP) - { - Con_Printf("Specified stun server was not resolved or was not udp v4\n"); - return; - } - - stunmsg.hdr.magiccookie = BigLong(0x2112a442); - Sys_RandomBytes((qbyte*)&stunmsg.hdr.transactid[0], sizeof(stunmsg.hdr.transactid)); // 'and SHOULD be cryptographically random' - stunmsg.hdr.msgtype = BigShort(0x0001); //binding request - stunmsg.hdr.msglen = BigShort(0); - NET_SendPacket(NS_SERVER, sizeof(stunmsg), &stunmsg, &stunserverip); -} #endif #ifndef SERVERONLY @@ -5290,11 +6117,8 @@ qboolean NET_WasSpecialPacket(netsrc_t netsrc) #endif } - -#ifndef CLIENTONLY if (NET_WasStun(netsrc)) return true; -#endif #ifdef HAVE_NATPMP if (NET_Was_NATPMP(collection)) return true; @@ -5339,7 +6163,6 @@ void NET_Init (void) #ifndef CLIENTONLY Cmd_AddCommand("sv_addport", SVNET_AddPort_f); - Cmd_AddCommand("sv_stun", SVNET_Stun_f); #endif #ifndef SERVERONLY Cmd_AddCommand("cl_addport", NET_ClientPort_f); @@ -5543,6 +6366,13 @@ void NET_InitServer(void) net_message.data = net_message_buffer; } #endif + +void NET_Tick(void) +{ +#ifdef SUPPORT_ICE + ICE_Tick(); +#endif +} /* ==================== NET_Shutdown @@ -5581,6 +6411,7 @@ typedef struct { char readbuffer[65536]; int readbuffered; + char peer[1]; } tcpfile_t; void VFSTCP_Error(tcpfile_t *f) { @@ -5598,13 +6429,16 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea if (tf->conpending) { - fd_set fd; + fd_set wr; + fd_set ex; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; - FD_ZERO(&fd); - FD_SET(tf->sock, &fd); - if (!select((int)tf->sock+1, NULL, &fd, NULL, &timeout)) + FD_ZERO(&wr); + FD_SET(tf->sock, &wr); + FD_ZERO(&ex); + FD_SET(tf->sock, &ex); + if (!select((int)tf->sock+1, NULL, &wr, &ex, &timeout)) return 0; tf->conpending = false; } @@ -5622,17 +6456,20 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea { switch(e) { + case ENOTCONN: + Con_Printf("connection to \"%s\" failed\n", tf->peer); + break; case ECONNABORTED: - Sys_Printf("connection aborted\n"); + Con_DPrintf("connection to \"%s\" aborted\n", tf->peer); break; case ECONNREFUSED: - Sys_Printf("connection refused\n"); + Con_DPrintf("connection to \"%s\" refused\n", tf->peer); break; case ECONNRESET: - Sys_Printf("connection reset\n"); + Con_DPrintf("connection to \"%s\" reset\n", tf->peer); break; default: - Sys_Printf("socket error %i\n", e); + Con_Printf("socket error %i\n", e); } VFSTCP_Error(tf); } @@ -5741,7 +6578,8 @@ vfsfile_t *FS_OpenTCP(const char *name, int defaultport) if (sock == INVALID_SOCKET) return NULL; - newf = Z_Malloc(sizeof(*newf)); + newf = Z_Malloc(sizeof(*newf) + strlen(name)); + strcpy(newf->peer, name); newf->conpending = true; newf->sock = sock; newf->funcs.Close = VFSTCP_Close; diff --git a/engine/common/netinc.h b/engine/common/netinc.h index d00feb5ec..a71af1644 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -130,10 +130,12 @@ #endif #define EWOULDBLOCK WSAEWOULDBLOCK + #define EINPROGRESS WSAEINPROGRESS #define EMSGSIZE WSAEMSGSIZE #define ECONNRESET WSAECONNRESET #define ECONNABORTED WSAECONNABORTED #define ECONNREFUSED WSAECONNREFUSED + #define ENOTCONN WSAENOTCONN #define EACCES WSAEACCES #define EADDRNOTAVAIL WSAEADDRNOTAVAIL #define EAFNOSUPPORT WSAEAFNOSUPPORT @@ -196,11 +198,10 @@ #endif #if 1//def SUPPORT_ICE -struct icecandidate_s +struct icecandinfo_s { - struct icecandidate_s *next; - char *candidateid; - char *addr; //v4/v6/fqdn. fqdn should prefer ipv6 + char candidateid[64]; + char addr[64]; //v4/v6/fqdn. fqdn should prefer ipv6 int port; int transport; //0=udp. other values not supported int foundation; //to figure out... @@ -213,42 +214,41 @@ struct icecandidate_s ICE_PRFLX=2, ICE_RELAY=3, } type; //says what sort of proxy is used. - char *reladdr; //when proxied, this is our local info + char reladdr[64]; //when proxied, this is our local info int relport; int generation; //for ice restarts. starts at 0. int network; //which network device this comes from. - - qboolean dirty; }; -struct icestate_s +enum iceproto_e { - struct icestate_s *next; - void *module; - - int netsrc; - enum icemode_e - { - ICE_RAW, //not actually interactive beyond a simple handshake. - ICE_ICE //rfc5245. meant to be able to holepunch, but not implemented properly yet. - } mode; - char *conname; //internal id. - char *friendlyname; //who you're talking to. - char *stunserver;//where to get our public ip from. - int stunport; - - struct icecandidate_s *lc; - char *lpwd; - char *lfrag; - - struct icecandidate_s *rc; - char *rpwd; - char *rfrag; + ICEP_INVALID, //not allowed.. + ICEP_QWSERVER, //we're server side + ICEP_QWCLIENT, //we're client side + ICEP_VOICE //speex. requires client. }; -struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, enum icemode_e mode); //doesn't start pinging anything. -struct icestate_s *QDECL ICE_Find(void *module, char *conname); -void QDECL ICE_Begin(struct icestate_s *con, char *stunip, int stunport); //begins sending stun packets and stuff as required. data flows automagically. caller should poll ICE_GetLCandidateInfo periodically to pick up new candidates that need to be reported to the peer. -struct icecandidate_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con); //retrieves candidates that need reporting to the peer. -void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandidate_s *cand); //stuff that came from the peer. -void QDECL ICE_Close(struct icestate_s *con); //bye then. -void QDECL ICE_CloseModule(void *module); //closes all unclosed connections, with warning. +enum icemode_e +{ + ICEM_RAW, //not actually interactive beyond a simple handshake. + ICEM_ICE //rfc5245. meant to be able to holepunch, but not implemented properly yet. +}; +enum icestate_e +{ + ICE_INACTIVE, //idle. + ICE_FAILED, + ICE_CONNECTING, //exchanging pings. + ICE_CONNECTED //media is flowing, supposedly. sending keepalives. +}; +struct icestate_s; +#define ICE_API_CURRENT "Internet Connectivity Establishment 0.0" +typedef struct +{ + struct icestate_s *(QDECL *ICE_Create)(void *module, char *conname, char *peername, enum icemode_e mode, enum iceproto_e proto); //doesn't start pinging anything. + qboolean (QDECL *ICE_Set)(struct icestate_s *con, char *prop, char *value); + qboolean (QDECL *ICE_Get)(struct icestate_s *con, char *prop, char *value, int valuesize); + struct icecandinfo_s *(QDECL *ICE_GetLCandidateInfo)(struct icestate_s *con); //retrieves candidates that need reporting to the peer. + void (QDECL *ICE_AddRCandidateInfo)(struct icestate_s *con, struct icecandinfo_s *cand); //stuff that came from the peer. + void (QDECL *ICE_Close)(struct icestate_s *con); //bye then. + void (QDECL *ICE_CloseModule)(void *module); //closes all unclosed connections, with warning. +} icefuncs_t; +extern icefuncs_t iceapi; #endif \ No newline at end of file diff --git a/engine/common/plugin.c b/engine/common/plugin.c index 4b8260e83..ab574abc2 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -120,18 +120,8 @@ static qintptr_t VARGS Plug_GetNativePointer(void *offset, quintptr_t mask, cons { char *p = (char *)VM_POINTER(args[0]); #ifdef SUPPORT_ICE - if (!strcmp(p, "ICE_Create")) - return (qintptr_t)ICE_Create; - if (!strcmp(p, "ICE_Find")) - return (qintptr_t)ICE_Find; - if (!strcmp(p, "ICE_Begin")) - return (qintptr_t)ICE_Begin; - if (!strcmp(p, "ICE_GetLCandidateInfo")) - return (qintptr_t)ICE_GetLCandidateInfo; - if (!strcmp(p, "ICE_AddRCandidateInfo")) - return (qintptr_t)ICE_AddRCandidateInfo; - if (!strcmp(p, "ICE_Close")) - return (qintptr_t)ICE_Close; + if (!strcmp(p, ICE_API_CURRENT)) + return (qintptr_t)&iceapi; #endif return (qintptr_t)NULL; diff --git a/engine/common/sha1.c b/engine/common/sha1.c index def7307e3..c4c68aa80 100644 --- a/engine/common/sha1.c +++ b/engine/common/sha1.c @@ -191,3 +191,90 @@ int SHA1(char *digest, int maxdigestsize, char *string, int stringlen) return DIGEST_SIZE; } + +/* hmac-sha1.c -- hashed message authentication codes +Copyright (C) 2005, 2006 Free Software Foundation, Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Simon Josefsson. +hacked up a bit by someone else... +*/ + +#define IPAD 0x36 +#define OPAD 0x5c + +static void memxor(char *dest, char *src, size_t length) +{ + size_t i; + for (i = 0; i < length; i++) + { + dest[i] ^= src[i]; + } +} + +int SHA1_HMAC(unsigned char *digest, int maxdigestsize, + unsigned char *key, int keylen, + unsigned char *data, int datalen) +{ + SHA1_CTX inner; + SHA1_CTX outer; + char optkeybuf[20]; + char block[64]; + char innerhash[20]; + + if (maxdigestsize < DIGEST_SIZE) + return 0; + + /* Reduce the key's size, so that it becomes <= 64 bytes large. */ + + if (keylen > 64) + { + SHA1_CTX keyhash; + + SHA1Init (&keyhash); + SHA1Update (&keyhash, key, keylen); + SHA1Final (optkeybuf, &keyhash); + + key = optkeybuf; + keylen = 20; + } + + /* Compute INNERHASH from KEY and IN. */ + + SHA1Init (&inner); + + memset (block, IPAD, sizeof (block)); + memxor (block, key, keylen); + + SHA1Update (&inner, block, 64); + SHA1Update (&inner, data, datalen); + + SHA1Final (innerhash, &inner); + + /* Compute result from KEY and INNERHASH. */ + + SHA1Init (&outer); + + memset (block, OPAD, sizeof (block)); + memxor (block, key, keylen); + + SHA1Update (&outer, block, 64); + SHA1Update (&outer, innerhash, 20); + + SHA1Final (digest, &outer); + + return DIGEST_SIZE; +} diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 7b3d78f67..47480e48a 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -4120,6 +4120,7 @@ void SV_MVDStream_Poll(void); #ifdef PLUGINS Plug_Tick(); #endif + NET_Tick(); SV_GetConsoleCommands (); diff --git a/engine/server/sv_sys_win.c b/engine/server/sv_sys_win.c index 6a8960a56..d89a74d74 100644 --- a/engine/server/sv_sys_win.c +++ b/engine/server/sv_sys_win.c @@ -309,7 +309,7 @@ qboolean Sys_Rename (char *oldfname, char *newfname) return !rename(oldfname, newfname); } -int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const char *fname, int fsize, void *parm, void *spath), void *parm, void *spath) +int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const char *fname, int fsize, void *parm, searchpathfuncs_t *spath), void *parm, searchpathfuncs_t *spath) { HANDLE r; WIN32_FIND_DATA fd; diff --git a/plugins/jabber/jabberclient.c b/plugins/jabber/jabberclient.c index cb777dae1..31ebd7d7b 100644 --- a/plugins/jabber/jabberclient.c +++ b/plugins/jabber/jabberclient.c @@ -5,17 +5,20 @@ #include "../../engine/common/netinc.h" #include "xml.h" -#define NOICE +//#define NOICE +#define VOIP_SPEEX +#ifdef VOIP_SPEEX +#define VOIP +#endif + +#define DEFAULTDOMAIN "" +#define DEFAULTRESOURCE "Quake" #define QUAKEMEDIATYPE "quake" #define QUAKEMEDIAXMLNS "fteqw.com:netmedia" +#define DEFAULTICEMODE ICEM_ICE -struct icestate_s *(QDECL *pICE_Create)(void *module, char *conname, char *peername, enum icemode_e mode); //doesn't start pinging anything. -struct icestate_s *(QDECL *pICE_Find)(void *module, char *conname); -void (QDECL *pICE_Begin)(struct icestate_s *con, char *stunip, int stunport); //begins sending stun packets and stuff as required. -struct icecandidate_s *(QDECL *pICE_GetLCandidateInfo)(struct icestate_s *con); //stuff that needs reporting to the peer. -void (QDECL *pICE_AddRCandidateInfo)(struct icestate_s *con, struct icecandidate_s *cand); //stuff that came from the peer. -void (QDECL *pICE_Close)(struct icestate_s *con); //bye then. +icefuncs_t *piceapi; #define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0) @@ -125,7 +128,63 @@ void Con_SubPrintf(char *subname, char *format, ...) } +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; + + if (!*s) + { + *valuebuf='\0'; + return valuebuf; + } + s++; + } +} void RenameConsole(char *totrim); @@ -176,18 +235,18 @@ qintptr_t Plug_Init(qintptr_t *args) pCmd_AddCommand(COMMANDPREFIX2); pCmd_AddCommand(COMMANDPREFIX3); + //flags&1 == archive + pCvar_Register("xmpp_nostatus", "0", 0, "xmpp"); + pCvar_Register("xmpp_autoacceptjoins", "0", 0, "xmpp"); + pCvar_Register("xmpp_autoacceptinvites", "0", 0, "xmpp"); + pCvar_Register("xmpp_autoacceptvoice", "0", 0, "xmpp"); + pCvar_Register("xmpp_debug", "0", 0, "xmpp"); + pCvar_Register("xmpp_disabletls", "0", CVAR_NOTFROMSERVER, "xmpp"); + pCvar_Register("xmpp_allowplainauth", "0", CVAR_NOTFROMSERVER, "xmpp"); CHECKBUILTIN(Plug_GetNativePointer); if (BUILTINISVALID(Plug_GetNativePointer)) - { - pICE_Create = pPlug_GetNativePointer("ICE_Create"); - pICE_Find = pPlug_GetNativePointer("ICE_Find"); - pICE_Begin = pPlug_GetNativePointer("ICE_Begin"); - pICE_GetLCandidateInfo = pPlug_GetNativePointer("ICE_GetLCandidateInfo"); - pICE_AddRCandidateInfo = pPlug_GetNativePointer("ICE_AddRCandidateInfo"); - pICE_Close = pPlug_GetNativePointer("ICE_Close"); - } - + piceapi = pPlug_GetNativePointer(ICE_API_CURRENT); JCL_LoadConfig(); return 1; @@ -213,6 +272,10 @@ qintptr_t Plug_Init(qintptr_t *args) #define JCL_MAXMSGLEN 10000 +#define CAP_QUERIED 1 //a query is pending or something. +#define CAP_VOICE 2 //supports voice +#define CAP_INVITE 4 //supports game invites. + typedef struct bresource_s { char bstatus[128]; //basic status @@ -220,6 +283,8 @@ typedef struct bresource_s char server[256]; int servertype; //0=none, 1=already a client, 2=joinable + unsigned int caps; + struct bresource_s *next; char resource[1]; @@ -241,12 +306,23 @@ typedef struct jclient_s char server[64]; int port; + enum + { + JCL_DEAD, //not connected. connection died or something. + JCL_AUTHING, //connected, but not able to send any info on it other than to auth + JCL_ACTIVE //we're connected, we got a buddy list and everything + } status; + unsigned int timeout; //reconnect/ping timer + qhandle_t socket; + //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. char *outbuf; int outbufpos; int outbuflen; int outbufmax; + char bufferedinmessage[JCL_MAXMSGLEN+1]; //servers are required to be able to handle messages no shorter than a specific size. //which means we need to be able to handle messages when they get to us. //servers can still handle larger messages if they choose, so this might not be enough. @@ -259,93 +335,206 @@ typedef struct jclient_s char password[256]; char resource[256]; char jid[256]; //this is our full username@domain/resource string + char localalias[256];//this is what's shown infront of outgoing messages. >> by default until we can get our name. int tagdepth; int openbracket; int instreampos; - qboolean tlsconnect; //the old tls method on port 5223. + qboolean tlsconnect; //use the old tls method on port 5223. qboolean connected; //fully on server and authed and everything. - qboolean issecure; //tls enabled + qboolean issecure; //tls enabled (either upgraded or initially) qboolean streamdebug; //echo the stream to subconsoles - qboolean preapproval; + qboolean preapproval; //server supports presence preapproval char curquakeserver[2048]; char defaultnamespace[2048]; //should be 'jabber:client' or blank (and spammy with all the extra xmlns attribs) - struct iq_s { + struct iq_s + { struct iq_s *next; char id[64]; int timeout; - qboolean (*callback) (struct jclient_s *jcl, struct subtree_s *tree); + qboolean (*callback) (struct jclient_s *jcl, struct subtree_s *tree, struct iq_s *iq); + void *usrptr; } *pendingiqs; struct c2c_s { struct c2c_s *next; - char *sessionname; - buddy_t *tob; - bresource_t *tor; - } *p2p; + 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; buddy_t *buddies; } jclient_t; jclient_t *jclient; +int jclient_curtime; struct subtree_s; void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...); qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres); -void JCL_GeneratePresence(qboolean force); -void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, struct subtree_s *tree), char *iqtype, char *target, char *fmt, ...); -void JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, xmltree_t *node, qboolean destroynode); +void JCL_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); -void JCL_Join(jclient_t *jcl, char *target) +char *TrimResourceFromJid(char *jid) { - xmltree_t *jingle; - struct icestate_s *ice; - if (!jcl) - return; - -#ifdef NOICE - ice = pICE_Create(NULL, NULL, target, ICE_RAW); -#else - ice = pICE_Create(NULL, NULL, target, ICE_ICE); -#endif - if (!ice) + char *slash; + slash = strchr(jid, '/'); + if (slash) { - Con_Printf("Unable to connect to %s (dedicated servers cannot initiate connections)\n", target); - return; + *slash = '\0'; + return slash+1; } - //FIXME: record the session name + return NULL; +} + +static struct c2c_s *JCL_JingleCreateSession(jclient_t *jcl, char *with, qboolean creator, char *sid, int method, int mediatype) +{ + struct icestate_s *ice = NULL; + struct c2c_s *c2c; + char generatedname[64]; + + if (piceapi) + ice = piceapi->ICE_Create(NULL, sid, with, method, mediatype); + if (ice) + { + piceapi->ICE_Get(ice, "sid", generatedname, sizeof(generatedname)); + sid = generatedname; + + piceapi->ICE_Set(ice, "codec96", "speex@8000"); + piceapi->ICE_Set(ice, "codec97", "speex@16000"); + piceapi->ICE_Set(ice, "codec98", "opus"); + } + else + return NULL; //no way to get the local ip otherwise, which means things won't work proper + + c2c = malloc(sizeof(*c2c) + strlen(sid)); + memset(c2c, 0, sizeof(*c2c)); + c2c->next = jcl->c2c; + jcl->c2c = c2c; + strcpy(c2c->sid, sid); + + c2c->mediatype = mediatype; + c2c->creator = creator; + c2c->method = method; + + //FIXME: we need to query this from the server. + //however, google don't implement the 'standard' way + //the 'standard' way contains a huge big fat do-not-implement message. + //and google's way equally says 'don't implement'... so... + //as I kinda expect most users to use google's network anyway, I *hope* they won't mind too much. I doubt they'll get much traffic anyway. its just stun. + piceapi->ICE_Set(ice, "stunport", "19302"); + piceapi->ICE_Set(ice, "stunip", "stun.l.google.com"); + + //copy out the interesting parameters + c2c->with = strdup(with); + c2c->ice = ice; + return c2c; +} +static qboolean JCL_JingleAcceptAck(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +{ + struct c2c_s *c2c; + if (tree) + { + for (c2c = jcl->c2c; c2c; c2c = c2c->next) + { + if (c2c == iq->usrptr) + { + if (c2c->ice) + piceapi->ICE_Set(c2c->ice, "state", STRINGIFY(ICE_CONNECTING)); + } + } + } + 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"; jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", ""); - XML_AddParameter(jingle, "sid", ice->conname); - XML_AddParameter(jingle, "responder", target); - XML_AddParameter(jingle, "initiator", jcl->jid); - XML_AddParameter(jingle, "action", "session-initiate"); + 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 { xmltree_t *content = XML_CreateNode(jingle, "content", "", ""); - XML_AddParameter(content, "senders", "both"); - XML_AddParameter(content, "name", "some-old-quake-game"); - XML_AddParameter(content, "creator", "initiator"); + + 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"; + } + { xmltree_t *description; xmltree_t *transport; - if (ice->mode == ICE_RAW) + if (transportmode == ICEM_RAW) { transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:raw-udp:1", ""); { xmltree_t *candidate; - struct icecandidate_s *b = NULL; - struct icecandidate_s *c; - while ((c = pICE_GetLCandidateInfo(ice))) + struct icecandinfo_s *b = NULL; + struct icecandinfo_s *c; + while ((c = piceapi->ICE_GetLCandidateInfo(ice))) { if (!b || b->priority < c->priority) b = c; } - if (b) { candidate = XML_CreateNode(transport, "candidate", "", ""); @@ -357,14 +546,17 @@ void JCL_Join(jclient_t *jcl, char *target) } } } - else if (ice->mode == ICE_ICE) + else if (transportmode == ICEM_ICE) { + char val[64]; transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:ice-udp:1", ""); - XML_AddParameter(transport, "ufrag", ice->lfrag); - XML_AddParameter(transport, "pwd", ice->lpwd); + 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); { - struct icecandidate_s *c; - while ((c = pICE_GetLCandidateInfo(ice))) + struct icecandinfo_s *c; + while ((c = piceapi->ICE_GetLCandidateInfo(ice))) { char *ctypename[]={"host", "srflx", "prflx", "relay"}; xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", ""); @@ -381,46 +573,208 @@ void JCL_Join(jclient_t *jcl, char *target) } } } - description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, ""); - XML_AddParameter(description, "media", QUAKEMEDIATYPE); +#ifdef VOIP + if (c2c->mediatype == ICEP_VOICE) { - /* - xmltree_t *candidate = XML_CreateNode(description, "payload-type", "", ""); - XML_AddParameter(candidate, "channels", "1"); - XML_AddParameter(candidate, "clockrate", "8000"); - XML_AddParameter(candidate, "id", "104"); - XML_AddParameter(candidate, "name", "SPEEX"); - */ + 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"); } } } - Con_Printf("Sending connection start:\n"); - XML_ConPrintTree(jingle, 0); - JCL_SendIQNode(jcl, NULL, "set", target, jingle, true); + + 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_JingleParsePeerPorts(jclient_t *jcl, xmltree_t *inj, char *from) +void JCL_JingleTimeouts(jclient_t *jcl, qboolean killall) +{ + struct c2c_s *c2c; + for (c2c = jcl->c2c; c2c; c2c = c2c->next) + { + struct icecandinfo_s *lc; + if (c2c->method == ICEM_ICE) + { + char bah[2]; + piceapi->ICE_Get(c2c->ice, "newlc", bah, sizeof(bah)); + if (atoi(bah)) + { + Con_DPrintf("Sending updated local addresses\n"); + JCL_JingleSend(jcl, c2c, "transport-info"); + } + } + } +} + +void JCL_Join(jclient_t *jcl, char *target, char *sid, qboolean allow, int protocol) +{ + struct c2c_s *c2c = NULL, **link; + xmltree_t *jingle; + struct icestate_s *ice; + char autotarget[256]; + if (!jcl) + return; + + if (!strchr(target, '/')) + { + buddy_t *b; + bresource_t *br; + JCL_FindBuddy(jcl, target, &b, &br); + if (!br) + br = b->defaultresource; + if (!br) + br = b->resources; + + if (!br) + { + Con_Printf("User name not valid\n"); + return; + } + Q_snprintf(autotarget, sizeof(autotarget), "%s/%s", b->accountdomain, br->resource); + target = autotarget; + } + + for (link = &jcl->c2c; *link; link = &(*link)->next) + { + if (!strcmp((*link)->with, target) && (!sid || !strcmp((*link)->sid, sid)) && ((*link)->mediatype == protocol || protocol == ICEP_INVALID)) + { + c2c = *link; + break; + } + } + if (allow) + { + if (!c2c) + { + if (!sid) + { + c2c = JCL_JingleCreateSession(jcl, target, true, sid, DEFAULTICEMODE, ((protocol == ICEP_INVALID)?ICEP_QWCLIENT:protocol)); + JCL_JingleSend(jcl, c2c, "session-initiate"); + + Con_Printf("%s ^[[%s]\\xmpp\\%s^] ^[[Hang Up]\\xmppact\\jdeny\\xmpp\\%s\\xmppsid\\%s^].\n", protocol==ICEP_VOICE?"Calling":"Requesting session with", target, target, target, c2c->sid); + } + else + Con_Printf("That session has expired.\n"); + } + else if (c2c->creator) + { + //resend initiate if they've not acked it... I dunno... + JCL_JingleSend(jcl, c2c, "session-initiate"); + Con_Printf("Restarting session with ^[[%s]\\xmpp\\%s^].\n", target, target); + } + else if (c2c->accepted) + Con_Printf("That session was already accepted.\n"); + else + { + JCL_JingleSend(jcl, c2c, "session-accept"); + Con_Printf("Accepting session from ^[[%s]\\xmpp\\%s^].\n", target, target); + } + } + else + { + if (c2c) + { + JCL_JingleSend(jcl, c2c, "session-terminate"); + Con_Printf("Terminating session with ^[[%s]\\xmpp\\%s^].\n", target, target); + } + else + Con_Printf("That session has already expired.\n"); + } +} + +void JCL_JingleParsePeerPorts(jclient_t *jcl, struct c2c_s *c2c, xmltree_t *inj, char *from) { xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0); xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0); xmltree_t *incandidate; - struct icestate_s *ice; - struct icecandidate_s rem; + struct icecandinfo_s rem; int i; - ice = pICE_Find(NULL, XML_GetParameter(inj, "sid", "")); - if (ice && strcmp(ice->friendlyname, from)) + if (strcmp(c2c->with, from) || strcmp(c2c->sid, XML_GetParameter(inj, "sid", ""))) { Con_Printf("%s is trying to mess with our connections...\n", from); return; } + 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", "")); + for (i = 0; (incandidate = XML_ChildOfTree(intransport, "candidate", i)); i++) { char *s; memset(&rem, 0, sizeof(rem)); - rem.addr = XML_GetParameter(incandidate, "ip", ""); - rem.candidateid = XML_GetParameter(incandidate, "id", ""); + Q_strlcpy(rem.addr, XML_GetParameter(incandidate, "ip", ""), sizeof(rem.addr)); + Q_strlcpy(rem.candidateid, XML_GetParameter(incandidate, "id", ""), sizeof(rem.candidateid)); s = XML_GetParameter(incandidate, "type", ""); if (s && !strcmp(s, "srflx")) @@ -442,10 +796,10 @@ void JCL_JingleParsePeerPorts(jclient_t *jcl, xmltree_t *inj, char *from) rem.transport = 0; else rem.transport = 0; - pICE_AddRCandidateInfo(ice, &rem); + piceapi->ICE_AddRCandidateInfo(c2c->ice, &rem); } } -struct icestate_s *JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from) +qboolean JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from) { /*inj contains something like: @@ -468,185 +822,264 @@ struct icestate_s *JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char char *transportxmlns = intransport?intransport->xmlns:""; char *descriptionxmlns = indescription?indescription->xmlns:""; char *descriptionmedia = XML_GetParameter(indescription, "media", ""); + char *sid = XML_GetParameter(inj, "sid", ""); xmltree_t *jingle; struct icestate_s *ice; qboolean accepted = false; enum icemode_e imode; + char *response = "session-terminate"; + char *offer = "pwn you"; + char *autocvar = "xmpp_autoaccepthax"; + char *initiator; - imode = strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICE_ICE:ICE_RAW; + struct c2c_s *c2c = NULL; + int mt = ICEP_INVALID; - if (!incontent || strcmp(descriptionmedia, QUAKEMEDIATYPE) || strcmp(descriptionxmlns, QUAKEMEDIAXMLNS)) + //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; + + if (incontent && !strcmp(descriptionmedia, QUAKEMEDIATYPE) && !strcmp(descriptionxmlns, QUAKEMEDIAXMLNS)) { - //decline it - ice = NULL; - } - else - ice = pICE_Create(NULL, XML_GetParameter(inj, "sid", ""), from, imode); - - jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", ""); - XML_AddParameter(jingle, "sid", ice->conname); - XML_AddParameter(jingle, "responder", XML_GetParameter(inj, "responder", "")); - XML_AddParameter(jingle, "initiator", XML_GetParameter(inj, "initiator", "")); - if (!ice) - XML_AddParameter(jingle, "action", "session-terminate"); - else - { - xmltree_t *content = XML_CreateNode(jingle, "content", "", ""); -#ifdef NOICE - if (imode != ICE_RAW) + char *host = XML_GetParameter(indescription, "host", "you"); + if (!strcmp(host, "you")) { - char buf[256]; - XML_AddParameter(jingle, "action", "transport-replace"); - Q_snprintf(buf, sizeof(buf), "raw-%s", XML_GetParameter(incontent, "name", "")); - XML_AddParameter(content, "name", buf); + mt = ICEP_QWSERVER; + offer = "join your game"; + autocvar = "xmpp_autoacceptjoins"; + } + else if (!strcmp(host, "me")) + { + mt = ICEP_QWCLIENT; + offer = "invite you to thier game"; + autocvar = "xmpp_autoacceptinvites"; + } + } + if (incontent && !strcmp(descriptionmedia, "audio") && !strcmp(descriptionxmlns, "urn:xmpp:jingle:apps:rtp:1")) + { + mt = ICEP_VOICE; + offer = "have a natter with you"; + autocvar = "xmpp_autoacceptvoice"; + } + if (mt == ICEP_INVALID) + return false; + + //FIXME: if both people try to establish a connection to the other simultaneously, the higher session id is meant to be canceled, and the lower accepted automagically. + + c2c = JCL_JingleCreateSession(jcl, from, false, + sid, + strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICEM_ICE:ICEM_RAW, + mt + ); + if (!c2c) + return false; + + if (c2c->mediatype == ICEP_VOICE) + { + qboolean okay = false; + int i = 0; + xmltree_t *payload; + //chuck it at the engine and see what sticks. at least one must... + while((payload = XML_ChildOfTree(indescription, "payload-type", i++))) + { + char *name = XML_GetParameter(payload, "name", ""); + char *clock = XML_GetParameter(payload, "clockrate", ""); + char *id = XML_GetParameter(payload, "id", ""); + char parm[64]; + char val[64]; + if (!strcmp(name, "SPEEX")) + { + 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); + } + else if (!strcmp(name, "OPUS")) + { + Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); + okay |= piceapi->ICE_Set(c2c->ice, parm, "opus"); + } + } + //don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something. + if (!okay) + { + JCL_JingleSend(jcl, c2c, "session-terminate"); + return false; + } + } + + JCL_JingleParsePeerPorts(jcl, c2c, inj, from); + + if (c2c->mediatype != ICEP_INVALID) + { + if (!pCvar_GetFloat(autocvar)) + { + //show a prompt for it, send the reply when the user decides. + Con_Printf( + "^[[%s]\\xmpp\\%s^] wants to %s. " + "^[[Authorise]\\xmppact\\jauth\\xmpp\\%s\\xmppsid\\%s^] " + "^[[Deny]\\xmppact\\jdeny\\xmpp\\%s\\xmppsid\\%s^]\n", + from, from, + offer, + from, sid, + from, sid); + return true; } else -#endif { - XML_AddParameter(jingle, "action", "session-accept"); - XML_AddParameter(content, "name", XML_GetParameter(incontent, "name", "")); - accepted = true; - } - XML_AddParameter(content, "senders", "both"); - XML_AddParameter(content, "creator", "initiator"); - { - xmltree_t *description; - xmltree_t *transport; - transport = XML_CreateNode(content, "transport", transportxmlns, ""); - if (imode == ICE_RAW) - { - //raw-udp can send only one candidate - xmltree_t *candidate; - struct icecandidate_s *b = NULL; - struct icecandidate_s *c; - while ((c = pICE_GetLCandidateInfo(ice))) - { - if (!b || b->priority < c->priority) - b = c; - } - if (b) - { - candidate = XML_CreateNode(transport, "candidate", "", ""); - XML_AddParameter(candidate, "ip", b->addr); - XML_AddParameteri(candidate, "port", b->port); - XML_AddParameter(candidate, "id", b->candidateid); - XML_AddParameteri(candidate, "generation", b->generation); - XML_AddParameteri(candidate, "component", b->component); - } - } - else if (imode == ICE_ICE) - { - //ice can send multiple candidates - struct icecandidate_s *c; - XML_AddParameter(transport, "ufrag", ice->lfrag); - XML_AddParameter(transport, "pwd", ice->lpwd); - while ((c = pICE_GetLCandidateInfo(ice))) - { - char *ctypename[]={"host", "srflx", "prflx", "relay"}; - xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", ""); - XML_AddParameter(candidate, "type", ctypename[c->type]); - XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously? - XML_AddParameteri(candidate, "priority", c->priority); - XML_AddParameteri(candidate, "port", c->port); - XML_AddParameteri(candidate, "network", c->network); - XML_AddParameter(candidate, "ip", c->addr); - XML_AddParameter(candidate, "id", c->candidateid); - XML_AddParameteri(candidate, "generation", c->generation); - XML_AddParameteri(candidate, "foundation", c->foundation); - XML_AddParameteri(candidate, "component", c->component); - } - } - else - { - //egads! can't cope with that. - } - description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, ""); - XML_AddParameter(description, "media", QUAKEMEDIATYPE); - { - /* - xmltree_t *candidate = XML_CreateNode(description, "payload-type", "", ""); - XML_AddParameter(candidate, "channels", "1"); - XML_AddParameter(candidate, "clockrate", "8000"); - XML_AddParameter(candidate, "id", "104"); - XML_AddParameter(candidate, "name", "SPEEX"); - */ - } + Con_Printf("Auto-accepting session from ^[[%s]\\xmpp\\%s^]\n", from, from); + response = "session-accept"; } } - if (!ice) - Con_Printf("Sending reject:\n"); - else - Con_Printf("Sending accept:\n"); - XML_ConPrintTree(jingle, 0); - JCL_SendIQNode(jcl, NULL, "set", from, jingle, true); - if (ice) - JCL_JingleParsePeerPorts(jcl, inj, from); - - //if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted - if (ice && accepted) - pICE_Begin(ice, NULL, 0); - return ice; + JCL_JingleSend(jcl, c2c, response); + return true; } -void JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id) +qboolean JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id) { char *action = XML_GetParameter(tree, "action", ""); -// char *initiator = XML_GetParameter(tree, "initiator", ""); -// char *responder = XML_GetParameter(tree, "responder", ""); char *sid = XML_GetParameter(tree, "sid", ""); - //validate sender - struct icestate_s *ice = pICE_Find(NULL, sid); - if (ice && strcmp(ice->friendlyname, from)) + struct c2c_s *c2c = NULL, **link; + + for (link = &jcl->c2c; *link; link = &(*link)->next) { - Con_Printf("%s is trying to mess with our connections...\n", from); - return; + if (!strcmp((*link)->sid, sid)) + { + c2c = *link; + if (!c2c->accepted) + break; + } } + //validate sender + if (c2c && strcmp(c2c->with, from)) + { + Con_Printf("%s is trying to mess with our connections...\n", from); + return false; + } + + //FIXME: transport-info, transport-replace if (!strcmp(action, "session-terminate")) { - if (ice) - pICE_Close(ice); + xmltree_t *reason = XML_ChildOfTree(tree, "reason", 0); + if (!c2c) + { + Con_Printf("Received session-terminate without an active session\n"); + return false; + } - Con_Printf("Session ended\n"); - XML_ConPrintTree(tree, 0); + if (reason && reason->child) + Con_Printf("Session ended: %s\n", reason->child->name); + else + Con_Printf("Session ended\n"); + + //unlink it + for (link = &jcl->c2c; *link; link = &(*link)->next) + { + if (*link == c2c) + { + *link = c2c->next; + break; + } + } + +// XML_ConPrintTree(tree, 0); + + if (c2c->ice) + piceapi->ICE_Close(c2c->ice); + free(c2c); + } + //content-accept + //content-add + //content-modify + //content-reject + //content-remove + //description-info + //security-info + // + else if (!strcmp(action, "transport-info")) + { //peer wants to add ports. + if (c2c) + JCL_JingleParsePeerPorts(jcl, c2c, tree, from); + else + Con_DPrintf("Received transport-info without an active session\n"); + } +//FIXME: we need to add support for this to downgrade to raw if someone tries calling through a SIP gateway + else if (!strcmp(action, "transport-replace")) + { + if (c2c) + { + if (1) + JCL_JingleSend(jcl, c2c, "transport-reject"); + else + { + JCL_JingleParsePeerPorts(jcl, c2c, tree, from); + JCL_JingleSend(jcl, c2c, "transport-accept"); + } + } + } + else if (!strcmp(action, "transport-reject")) + { + JCL_JingleSend(jcl, c2c, "session-terminate"); } else if (!strcmp(action, "session-accept")) { - if (!ice) + if (!c2c) { - Con_Printf("Cannot accept a session that was never created\n"); + 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) + { + //pidgin is buggy and can dupe-accept sessions multiple times. + Con_DPrintf("Duplicate session-accept from peer.\n"); + + //XML_ConPrintTree(tree, 0); + return false; } else { + char *responder = XML_GetParameter(tree, "responder", from); + if (strcmp(responder, from)) + { + return false; + } Con_Printf("Session Accepted!\n"); - XML_ConPrintTree(tree, 0); +// XML_ConPrintTree(tree, 0); - if (ice) - JCL_JingleParsePeerPorts(jcl, tree, from); + JCL_JingleParsePeerPorts(jcl, c2c, tree, from); + c2c->accepted = true; //if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted - if (ice) - pICE_Begin(ice, NULL, 0); + if (c2c->ice) + piceapi->ICE_Set(c2c->ice, "state", STRINGIFY(ICE_CONNECTING)); } } else if (!strcmp(action, "session-initiate")) { - Con_Printf("Peer initiating connection!\n"); - XML_ConPrintTree(tree, 0); +// Con_Printf("Peer initiating connection!\n"); +// XML_ConPrintTree(tree, 0); - ice = JCL_JingleHandleInitiate(jcl, tree, from); + if (!JCL_JingleHandleInitiate(jcl, tree, from)) + return false; } else { Con_Printf("Unknown jingle action: %s\n", action); - XML_ConPrintTree(tree, 0); +// XML_ConPrintTree(tree, 0); } JCL_AddClientMessagef(jcl, "", from, id); + return true; } @@ -654,29 +1087,63 @@ qintptr_t JCL_ConsoleLink(qintptr_t *args) { char text[256]; char link[256]; - pCmd_Argv(0, text, sizeof(text)); + char who[256]; + char what[256]; +// pCmd_Argv(0, text, sizeof(text)); pCmd_Argv(1, link, sizeof(link)); - if (!strncmp(link, "\\xmppauth\\", 6)) + JCL_Info_ValueForKey(link, "xmpp", who, sizeof(who)); + JCL_Info_ValueForKey(link, "xmppact", what, sizeof(what)); + + if (!*who && !*what) + return false; + + if (!strcmp(what, "pauth")) { //we should friend them too. - if (jclient) - JCL_AddClientMessagef(jclient, "", link+10); + if (jclient && jclient->status == JCL_ACTIVE) + JCL_AddClientMessagef(jclient, "", who); return true; } - if (!strncmp(link, "\\xmppdeny\\", 6)) + else if (!strcmp(what, "pdeny")) { - if (jclient) - JCL_AddClientMessagef(jclient, "", link+10); + if (jclient && jclient->status == JCL_ACTIVE) + JCL_AddClientMessagef(jclient, "", who); return true; } - - if (!strncmp(link, "\\xmppjoin\\", 6)) + else if (!strcmp(what, "jauth")) { - JCL_Join(jclient, link+10); - return false; + JCL_Info_ValueForKey(link, "xmppsid", what, sizeof(what)); + if (jclient && jclient->status == JCL_ACTIVE) + JCL_Join(jclient, who, what, true, ICEP_INVALID); + return true; } - if (!strncmp(link, "\\xmpp\\", 6)) + else if (!strcmp(what, "jdeny")) + { + JCL_Info_ValueForKey(link, "xmppsid", what, sizeof(what)); + if (jclient && jclient->status == JCL_ACTIVE) + JCL_Join(jclient, who, what, false, ICEP_INVALID); + return true; + } + else if (!strcmp(what, "join")) + { + if (jclient && jclient->status == JCL_ACTIVE) + JCL_Join(jclient, who, NULL, true, ICEP_QWCLIENT); + return true; + } + else if (!strcmp(what, "invite")) + { + if (jclient && jclient->status == JCL_ACTIVE) + JCL_Join(jclient, who, NULL, true, ICEP_QWSERVER); + return true; + } + else if (!strcmp(what, "call")) + { + if (jclient && jclient->status == JCL_ACTIVE) + JCL_Join(jclient, who, NULL, true, ICEP_VOICE); + return true; + } + else if ((*who && !*what) || !strcmp(what, "msg")) { if (jclient) { @@ -684,7 +1151,7 @@ qintptr_t JCL_ConsoleLink(qintptr_t *args) buddy_t *b; bresource_t *br; - JCL_FindBuddy(jclient, link+6, &b, &br); + JCL_FindBuddy(jclient, *who?who:jclient->defaultdest, &b, &br); f = b->name; b->defaultresource = br; @@ -695,6 +1162,11 @@ qintptr_t JCL_ConsoleLink(qintptr_t *args) } return true; } + else + { + Con_Printf("Unsupported xmpp action (%s) in link\n", what); + } + return false; } @@ -804,24 +1276,98 @@ void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...) JCL_AddClientMessageString(jcl, body); } -jclient_t *JCL_Connect(char *server, int defport, qboolean usesecure, char *account, char *password) +qboolean JCL_Reconnect(jclient_t *jcl) { + //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)); + + + Con_Printf("XMPP: Trying to connect to %s\n", jcl->domain); + jcl->socket = pNet_TCPConnect(jcl->server, jcl->tlsconnect?5223:5222); //port is only used if the url doesn't contain one. It's a default. + + //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; + } + + jcl->issecure = false; + if (jcl->tlsconnect) + if (pNet_SetTLSClient(jcl->socket, jcl->server)>=0) + jcl->issecure = true; + + jcl->status = JCL_AUTHING; + + JCL_AddClientMessageString(jcl, + "" + ""); + + return true; +} +jclient_t *JCL_Connect(char *server, qboolean usesecure, char *account, char *password) +{ + char gamename[64]; jclient_t *jcl; - char *at; + char *domain; + char *res; + + res = TrimResourceFromJid(account); + if (!res) + { + //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; + } + } if (usesecure) { if (!BUILTINISVALID(Net_SetTLSClient)) { - Con_Printf("JCL_OpenSocket: TLS is not supported\n"); + Con_Printf("XMPP: TLS is not supported\n"); return NULL; } } - at = strchr(account, '@'); - if (!at) - return NULL; - + domain = strchr(account, '@'); + if (domain) + { + *domain = '\0'; + domain++; + } + else + { + 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; + } + } jcl = malloc(sizeof(jclient_t)); if (!jcl) @@ -829,54 +1375,20 @@ jclient_t *JCL_Connect(char *server, int defport, qboolean usesecure, char *acco memset(jcl, 0, sizeof(jclient_t)); - - jcl->socket = pNet_TCPConnect(server, defport); //port is only used if the url doesn't contain one. It's a default. - - //not yet blocking. So no frequent attempts please... - //non blocking prevents connect from returning worthwhile sensible value. - if ((int)jcl->socket < 0) - { - Con_Printf("JCL_OpenSocket: couldn't connect\n"); - free(jcl); - return NULL; - } - - if (usesecure) - { - if (pNet_SetTLSClient(jcl->socket, server)<0) - { - pNet_Close(jcl->socket); - free(jcl); - jcl = NULL; - - return NULL; - } - jcl->issecure = true; - } - else - jcl->issecure = false; - - -// gethostname(jcl->hostname, sizeof(jcl->hostname)); -// jcl->hostname[sizeof(jcl->hostname)-1] = 0; - jcl->tlsconnect = usesecure; jcl->streamdebug = !!pCvar_GetFloat("xmpp_debug"); - *at = '\0'; Q_strlcpy(jcl->server, server, sizeof(jcl->server)); Q_strlcpy(jcl->username, account, sizeof(jcl->username)); - Q_strlcpy(jcl->domain, at+1, sizeof(jcl->domain)); + Q_strlcpy(jcl->domain, domain, sizeof(jcl->domain)); Q_strlcpy(jcl->password, password, sizeof(jcl->password)); + Q_strlcpy(jcl->resource, (res&&*res)?res:"FTE", sizeof(jcl->password)); - Q_strlcpy(jcl->resource, "Quake", sizeof(jcl->password)); - - Con_Printf("Trying to connect to %s\n", at+1); - JCL_AddClientMessageString(jcl, - "" - ""); + if (!JCL_Reconnect(jcl)) + { + free(jcl); + jcl = NULL; + } return jcl; } @@ -1011,16 +1523,42 @@ int Base64_Decode(char *out, int outlen, char *src, int srclen) return len; } -char *TrimResourceFromJid(char *jid) +void JCL_ForgetBuddyResource(jclient_t *jcl, buddy_t *buddy, bresource_t *bres) { - char *slash; - slash = strchr(jid, '/'); - if (slash) + bresource_t **link; + bresource_t *r; + for (link = &buddy->resources; *link; ) { - *slash = '\0'; - return slash+1; + 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; } - return NULL; } qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres) @@ -1070,7 +1608,7 @@ qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t * return false; } -void JCL_SendIQ(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, char *body) +struct iq_s *JCL_SendIQ(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree, struct iq_s *iq), char *iqtype, char *target, char *body) { struct iq_s *iq; @@ -1096,8 +1634,9 @@ void JCL_SendIQ(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t } JCL_AddClientMessageString(jcl, body); JCL_AddClientMessageString(jcl, ""); + return iq; } -void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, char *fmt, ...) +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, ...) { va_list argptr; char body[2048]; @@ -1106,15 +1645,17 @@ void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t Q_vsnprintf (body, sizeof(body), fmt, argptr); va_end (argptr); - JCL_SendIQ(jcl, callback, iqtype, target, body); + return JCL_SendIQ(jcl, callback, iqtype, target, body); } -void JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, xmltree_t *node, qboolean destroynode) +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) { + struct iq_s *n; char *s = XML_GenerateString(node); - JCL_SendIQ(jcl, callback, iqtype, target, s); + n = JCL_SendIQ(jcl, callback, iqtype, target, s); free(s); if (destroynode) XML_Destroy(node); + return n; } static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp) { @@ -1133,21 +1674,22 @@ static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp) buddy->friended = true; } } -static qboolean JCL_RosterReply(jclient_t *jcl, xmltree_t *tree) +static qboolean JCL_RosterReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { xmltree_t *c; + //we're probably connected once we've had this reply. + jcl->status = JCL_ACTIVE; + JCL_GeneratePresence(jcl, true); c = XML_ChildOfTree(tree, "query", 0); if (c) { JCL_RosterUpdate(jcl, c); - JCL_GeneratePresence(true); return true; } - JCL_GeneratePresence(true); return false; } -static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree) +static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { xmltree_t *c; c = XML_ChildOfTree(tree, "bind", 0); @@ -1163,9 +1705,23 @@ static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree) } return false; } -static qboolean JCL_SessionReply(jclient_t *jcl, xmltree_t *tree) +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) { JCL_SendIQf(jcl, JCL_RosterReply, "get", NULL, ""); + JCL_SendIQf(jcl, JCL_VCardReply, "get", NULL, ""); return true; } @@ -1185,8 +1741,10 @@ static char *caps[] = "jabber:iq:version", "urn:xmpp:jingle:1", QUAKEMEDIAXMLNS, -// "urn:xmpp:jingle:apps:rtp:1", //we don't support rtp video/voice chat -// "urn:xmpp:jingle:apps:rtp:audio",//we don't support rtp voice chat +#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 @@ -1253,7 +1811,6 @@ static void buildcaps(char *out, int outlen) Q_strlcat(out, "'/>", outlen); } } - static int qsortcaps(const void *va, const void *vb) { char *a = *(char**)va; @@ -1267,9 +1824,7 @@ char *buildcapshash(void) 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++) { @@ -1277,16 +1832,450 @@ char *buildcapshash(void) Q_strlcat(out, "<", outlen); } l = SHA1(digest, sizeof(digest), out, strlen(out)); - for (i = 0; i < l; i++) Base64_Byte(digest[i]); Base64_Finish(); return base64; } -#define JCL_DONE 0 -#define JCL_CONTINUE 1 -#define JCL_KILL 2 +void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) +{ + qboolean unparsable = true; + char *from; +// char *to; + char *id; + char *f; + xmltree_t *ot; + + //FIXME: block from people who we don't know. + + id = XML_GetParameter(tree, "id", ""); + from = XML_GetParameter(tree, "from", ""); +// to = XML_GetParameter(tree, "to", ""); + + f = XML_GetParameter(tree, "type", ""); + if (!strcmp(f, "get")) + { + ot = XML_ChildOfTree(tree, "query", 0); + if (ot) + { + if (from && !strcmp(ot->xmlns, "http://jabber.org/protocol/disco#info")) + { //http://xmpp.org/extensions/xep-0030.html + char msg[2048]; + char *hash; + unparsable = false; + + buildcaps(msg, sizeof(msg)); + hash = buildcapshash(); + + JCL_AddClientMessagef(jcl, + "" + "" + "%s" + "" + "", from, id, hash, msg); + } + else if (from && !strcmp(ot->xmlns, "jabber:iq:version")) + { //client->client version request + char msg[2048]; + unparsable = false; + + Q_snprintf(msg, sizeof(msg), + "" + "" + "FTEQW XMPP" + "V"JCL_BUILD"" +#ifdef Q3_VM + "QVM plugin" +#else + //don't specify the os otherwise, as it gives away required base addresses etc for exploits +#endif + "" + "", from, id); + + JCL_AddClientMessageString(jcl, msg); + } + else if (from && !strcmp(ot->xmlns, "jabber:iq:last")) + { + unparsable = false; + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "", from, id); + } +/* else if (from && !strcmp(ot->xmlns, "jabber:iq:last")) + { //http://xmpp.org/extensions/xep-0012.html + char msg[2048]; + int idletime = 0; + unparsable = false; + + //last activity + Q_snprintf(msg, sizeof(msg), + "" + "" + "", from, id, idletime); + + JCL_AddClientMessageString(jcl, msg); + } +*/ + } +#ifndef Q3_VM + ot = XML_ChildOfTree(tree, "time", 0); + if (ot && !strcmp(ot->xmlns, "urn:xmpp:time")) + { //http://xmpp.org/extensions/xep-0202.html + char msg[2048]; + char tz[256]; + char timestamp[256]; + struct tm * timeinfo; + int tzh, tzm; + time_t rawtime; + time (&rawtime); + timeinfo = localtime(&rawtime); + tzh = timeinfo->tm_hour; + tzm = timeinfo->tm_min; + timeinfo = gmtime (&rawtime); + tzh -= timeinfo->tm_hour; + tzm -= timeinfo->tm_min; + Q_snprintf(tz, sizeof(tz), "%+i:%i", tzh, tzm); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", timeinfo); + unparsable = false; + //strftime + Q_snprintf(msg, sizeof(msg), + "" + "" + "", from, id, tz, timestamp); + JCL_AddClientMessageString(jcl, msg); + } +#endif + + ot = XML_ChildOfTree(tree, "ping", 0); + if (ot && !strcmp(ot->xmlns, "urn:xmpp:ping")) + { + JCL_AddClientMessagef(jcl, "", from, id); + } + + if (unparsable) + { //unsupported stuff + char msg[2048]; + unparsable = false; + + Con_Printf("Unsupported iq get\n"); + XML_ConPrintTree(tree, 0); + + //tell them OH NOES, instead of requiring some timeout. + Q_snprintf(msg, sizeof(msg), + "" + "" + "" + "" + "", from, id); + JCL_AddClientMessageString(jcl, msg); + } + } + else if (!strcmp(f, "set")) + { + xmltree_t *c; + + c = XML_ChildOfTree(tree, "query", 0); + if (c && !strcmp(c->xmlns, "jabber:iq:roster")) + { + unparsable = false; + JCL_RosterUpdate(jcl, c); + } + + c = XML_ChildOfTree(tree, "jingle", 0); + if (c && !strcmp(c->xmlns, "urn:xmpp:jingle:1")) + { + unparsable = !JCL_ParseJingle(jcl, c, from, id); + } + + if (unparsable) + { + char msg[2048]; + //tell them OH NOES, instead of requiring some timeout. + Q_snprintf(msg, sizeof(msg), + "" + "" + "" + "" + "", from, id); + JCL_AddClientMessageString(jcl, msg); + unparsable = false; + } + } + else if (!strcmp(f, "result") || !strcmp(f, "error")) + { + char *id = XML_GetParameter(tree, "id", ""); + struct iq_s **link, *iq; + unparsable = false; + for (link = &jcl->pendingiqs; *link; link = &(*link)->next) + { + iq = *link; + if (!strcmp(iq->id, id)) + break; + } + if (*link) + { + iq = *link; + *link = iq->next; + + if (iq->callback) + { + if (!iq->callback(jcl, !strcmp(f, "error")?NULL:tree, iq)) + { + Con_Printf("Invalid iq result\n"); + XML_ConPrintTree(tree, 0); + } + } + free(iq); + } + else + { + Con_Printf("Unrecognised iq result\n"); + XML_ConPrintTree(tree, 0); + } + } + + if (unparsable) + { + unparsable = false; + Con_Printf("Unrecognised iq type\n"); + XML_ConPrintTree(tree, 0); + } +} +void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) +{ + xmltree_t *ot; + qboolean unparsable = true; + char *f = XML_GetParameter(tree, "from", NULL); + + if (f && !strcmp(f, jcl->jid)) + unparsable = false; + else + { + if (f) + { + buddy_t *b; + bresource_t *br; + Q_strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest)); + + JCL_FindBuddy(jcl, f, &b, &br); + f = b->name; + b->defaultresource = br; + } + + if (f) + { + ot = XML_ChildOfTree(tree, "composing", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "%s is typing\r", f); + } + ot = XML_ChildOfTree(tree, "paused", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "%s has stopped typing\r", f); + } + ot = XML_ChildOfTree(tree, "inactive", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "\r", f); + } + ot = XML_ChildOfTree(tree, "active", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "\r", f); + } + ot = XML_ChildOfTree(tree, "gone", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "%s has gone away\r", f); + } + } + + ot = XML_ChildOfTree(tree, "body", 0); + if (ot) + { + unparsable = false; + if (f) + { + if (!strncmp(ot->body, "/me ", 4)) + Con_SubPrintf(f, "*^2%s^7%s\n", f, ot->body+3); + else + Con_SubPrintf(f, "^2%s^7: %s\n", f, ot->body); + } + else + Con_Printf("NOTICE: %s\n", ot->body); + + if (BUILTINISVALID(LocalSound)) + pLocalSound("misc/talk.wav"); + } + + if (unparsable) + { + unparsable = false; + if (jcl->streamdebug) + { + Con_Printf("Received a message without a body\n"); + XML_ConPrintTree(tree, 0); + } + } + } +} + +qboolean JCL_ClientDiscoInfo(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +{ + xmltree_t *query = XML_ChildOfTree(tree, "query", 0); + xmltree_t *feature; + char *var; + int i = 0; + unsigned int caps = 0; + qboolean rtp = false; + qboolean rtpaudio = false; + qboolean quake = false; + qboolean ice = false; + qboolean raw = false; + qboolean jingle = false; + buddy_t *b; + bresource_t *r; + JCL_FindBuddy(jcl, XML_GetParameter(tree, "from", ""), &b, &r); + while((feature = XML_ChildOfTree(query, "feature", i++))) + { + var = XML_GetParameter(feature, "var", ""); + //check ones we recognise. + if (!strcmp(var, QUAKEMEDIAXMLNS)) + quake = true; + if (!strcmp(var, "urn:xmpp:jingle:apps:rtp:audio")) + rtpaudio = true; + if (!strcmp(var, "urn:xmpp:jingle:apps:rtp:1")) + rtp = true; //kinda implied, but ensures version is okay + if (!strcmp(var, "urn:xmpp:jingle:transports:ice-udp:1")) + ice = true; + if (!strcmp(var, "urn:xmpp:jingle:transports:raw-udp:1")) + raw = true; + if (!strcmp(var, "urn:xmpp:jingle:1")) + jingle = true; //kinda implied, but ensures version is okay + } + if ((ice||raw) && jingle) + { + if (rtpaudio && rtp) + caps |= CAP_VOICE; + if (quake) + caps |= CAP_INVITE; + } + + if (b && r) + r->caps = (r->caps & CAP_QUERIED) | caps; + return true; +} +void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) +{ + buddy_t *buddy; + bresource_t *bres; + + char *from = XML_GetParameter(tree, "from", ""); + xmltree_t *show = XML_ChildOfTree(tree, "show", 0); + xmltree_t *status = XML_ChildOfTree(tree, "status", 0); + xmltree_t *quake = XML_ChildOfTree(tree, "quake", 0); + char *type = XML_GetParameter(tree, "type", ""); + char *serverip = NULL; + char *servermap = NULL; + + if (quake && !strcmp(quake->xmlns, "fteqw.com:game")) + { + serverip = XML_GetParameter(quake, "serverip", NULL); + servermap = XML_GetParameter(quake, "servermap", NULL); + } + + if (type && !strcmp(type, "subscribe")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] wants to be your friend! ^[[Authorize]\\xmpp\\%s\\xmppact\\pauth^] ^[[Deny]\\xmpp\\%s\\xmppact\\pdeny^]\n", from, from, from, from); + } + else if (type && !strcmp(type, "subscribed")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] is now your friend!\n", from, from, from, from); + } + else if (type && !strcmp(type, "unsubscribe")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] has unfriended you\n", from, from); + } + else if (type && !strcmp(type, "unsubscribed")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] is no longer unfriended you\n", from, from); + } + else + { + JCL_FindBuddy(jcl, from, &buddy, &bres); + + if (bres) + { + if (servermap) + { + bres->servertype = 2; + Q_strlcpy(bres->server, servermap, sizeof(bres->server)); + } + else if (serverip) + { + bres->servertype = 1; + Q_strlcpy(bres->server, serverip, sizeof(bres->server)); + } + else + { + bres->servertype = 0; + Q_strlcpy(bres->server, "", sizeof(bres->server)); + } + Q_strlcpy(bres->fstatus, (status && *status->body)?status->body:"", sizeof(bres->fstatus)); + if (!tree->child) + { + Q_strlcpy(bres->bstatus, "offline", sizeof(bres->bstatus)); + bres->caps = 0; + } + else + { + Q_strlcpy(bres->bstatus, (show && *show->body)?show->body:"present", sizeof(bres->bstatus)); + if (!(bres->caps & CAP_QUERIED)) + { + bres->caps |= CAP_QUERIED; + JCL_SendIQ(jcl, JCL_ClientDiscoInfo, "get", from, ""); + } + } + + if (bres->servertype == 2) + Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\xmpp\\%s\\xmppact\\join^]\n", buddy->name, from, bres->server, from); + else if (bres->servertype == 1) + Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\observe\\%s^]\n", buddy->name, from, bres->server, bres->server); + else if (*bres->fstatus) + Con_Printf("^[[%s]\\xmpp\\%s^] is now %s: %s\n", buddy->name, from, bres->bstatus, bres->fstatus); + else + Con_Printf("^[[%s]\\xmpp\\%s^] is now %s\n", buddy->name, from, bres->bstatus); + + if (!tree->child) + { + //remove this buddy resource + } + } + else + { + Con_Printf("Weird presence:\n"); + XML_ConPrintTree(tree, 0); + } + } +} + +#define JCL_DONE 0 //no more data available for now. +#define JCL_CONTINUE 1 //more data needs parsing. +#define JCL_KILL 2 //some error, needs reconnecting. +#define JCL_NUKEFROMORBIT 3 //permanent error (or logged on from elsewhere) int JCL_ClientFrame(jclient_t *jcl) { int pos; @@ -1301,11 +2290,11 @@ int JCL_ClientFrame(jclient_t *jcl) if (ret == 0) { if (!jcl->bufferedinammount) //if we are half way through a message, read any possible conjunctions. - return JCL_DONE; //remove + return JCL_DONE; //nothing more this frame } if (ret < 0) { - Con_Printf("JCL: socket error\n"); + Con_Printf("XMPP: socket error\n"); return JCL_KILL; } @@ -1364,7 +2353,8 @@ int JCL_ClientFrame(jclient_t *jcl) char t = jcl->bufferedinmessage[pos]; jcl->bufferedinmessage[pos] = 0; Con_TrySubPrint("xmppin", jcl->bufferedinmessage); - Con_TrySubPrint("xmppin", "\n"); + if (tree) + Con_TrySubPrint("xmppin", "\n"); jcl->bufferedinmessage[pos] = t; } @@ -1431,6 +2421,7 @@ int JCL_ClientFrame(jclient_t *jcl) // Con_Printf("read\n"); // XML_ConPrintTree(tree, 0); + jcl->timeout = jclient_curtime + 60*1000; unparsable = true; if (!strcmp(tree->name, "features")) @@ -1603,14 +2594,14 @@ int JCL_ClientFrame(jclient_t *jcl) if (!BUILTINISVALID(Net_SetTLSClient)) { - Con_Printf("JCL: proceed without TLS\n"); + Con_Printf("XMPP: proceed without TLS\n"); XML_Destroy(tree); return JCL_KILL; } if (pNet_SetTLSClient(jcl->socket, jcl->domain)<0) { - Con_Printf("JCL: failed to switch to TLS\n"); + Con_Printf("XMPP: failed to switch to TLS\n"); XML_Destroy(tree); return JCL_KILL; } @@ -1625,18 +2616,31 @@ int JCL_ClientFrame(jclient_t *jcl) XML_Destroy(tree); return JCL_DONE; } - else if (!strcmp(tree->name, "stream:error")) - { - } else if (!strcmp(tree->name, "failure")) { if (tree->child) - Con_Printf("JCL: Failure: %s\n", tree->child->name); + Con_Printf("XMPP: Failure: %s\n", tree->child->name); else - Con_Printf("JCL: Unknown failure\n"); + Con_Printf("XMPP: Unknown failure\n"); XML_Destroy(tree); return JCL_KILL; } + else if (!strcmp(tree->name, "error")) + { + ot = XML_ChildOfTree(tree, "text", 0); + if (ot) + Con_Printf("XMPP: %s\n", ot->body); + else + Con_Printf("XMPP: Unknown error\n"); + + ot = XML_ChildOfTree(tree, "conflict", 0); + XML_Destroy(tree); + + if (ot) + return JCL_NUKEFROMORBIT; + else + return JCL_KILL; + } else if (!strcmp(tree->name, "success")) { //Restart everything, basically, AGAIN! (third time lucky?) @@ -1654,342 +2658,17 @@ int JCL_ClientFrame(jclient_t *jcl) } else if (!strcmp(tree->name, "iq")) { - char *from; -// char *to; - char *id; - - id = XML_GetParameter(tree, "id", ""); - from = XML_GetParameter(tree, "from", ""); -// to = XML_GetParameter(tree, "to", ""); - - f = XML_GetParameter(tree, "type", ""); - if (!strcmp(f, "get")) - { - ot = XML_ChildOfTree(tree, "query", 0); - if (ot) - { - if (from && !strcmp(ot->xmlns, "http://jabber.org/protocol/disco#info")) - { //http://xmpp.org/extensions/xep-0030.html - char msg[2048]; - char *hash; - unparsable = false; - - buildcaps(msg, sizeof(msg)); - hash = buildcapshash(); - - JCL_AddClientMessagef(jcl, - "" - "" - "%s" - "" - "", from, id, hash, msg); - } - else if (from && !strcmp(ot->xmlns, "jabber:iq:version")) - { //client->client version request - char msg[2048]; - unparsable = false; - - Q_snprintf(msg, sizeof(msg), - "" - "" - "FTEQW XMPP" - "V"JCL_BUILD"" -#ifdef Q3_VM - "QVM plugin" -#else - //don't specify the os otherwise, as it gives away required base addresses etc for exploits -#endif - "" - "", from, id); - - JCL_AddClientMessageString(jcl, msg); - } -/* else if (from && !strcmp(ot->xmlns, "jabber:iq:last")) - { //http://xmpp.org/extensions/xep-0012.html - char msg[2048]; - int idletime = 0; - unparsable = false; - - //last activity - Q_snprintf(msg, sizeof(msg), - "" - "" - "", from, id, idletime); - - JCL_AddClientMessageString(jcl, msg); - } -*/ - } -#ifndef Q3_VM - ot = XML_ChildOfTree(tree, "time", 0); - if (ot && !strcmp(ot->xmlns, "urn:xmpp:time")) - { //http://xmpp.org/extensions/xep-0202.html - char msg[2048]; - char tz[256]; - char timestamp[256]; - struct tm * timeinfo; - int tzh, tzm; - time_t rawtime; - time (&rawtime); - timeinfo = localtime(&rawtime); - tzh = timeinfo->tm_hour; - tzm = timeinfo->tm_min; - timeinfo = gmtime (&rawtime); - tzh -= timeinfo->tm_hour; - tzm -= timeinfo->tm_min; - Q_snprintf(tz, sizeof(tz), "%+i:%i", tzh, tzm); - strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", timeinfo); - unparsable = false; - //strftime - Q_snprintf(msg, sizeof(msg), - "" - "" - "", from, id, tz, timestamp); - JCL_AddClientMessageString(jcl, msg); - } -#endif - - ot = XML_ChildOfTree(tree, "ping", 0); - if (ot && !strcmp(ot->xmlns, "urn:xmpp:ping")) - { - JCL_AddClientMessagef(jcl, "", from, id); - } - - if (unparsable) - { //unsupported stuff - char msg[2048]; - unparsable = false; - - Con_Printf("Unsupported iq get\n"); - XML_ConPrintTree(tree, 0); - - //tell them OH NOES, instead of requiring some timeout. - Q_snprintf(msg, sizeof(msg), - "" - "" - "" - "" - "", from, id); - JCL_AddClientMessageString(jcl, msg); - } - } - else if (!strcmp(f, "set")) - { - xmltree_t *c; - - c = XML_ChildOfTree(tree, "query", 0); - if (c && !strcmp(c->xmlns, "jabber:iq:roster")) - { - unparsable = false; - JCL_RosterUpdate(jcl, c); - } - - c = XML_ChildOfTree(tree, "jingle", 0); - if (c && !strcmp(c->xmlns, "urn:xmpp:jingle:1")) - { - JCL_ParseJingle(jcl, c, from, id); - unparsable = false; - } - } - else if (!strcmp(f, "result") || !strcmp(f, "error")) - { - char *id = XML_GetParameter(tree, "id", ""); - struct iq_s **link, *iq; - unparsable = false; - for (link = &jcl->pendingiqs; *link; link = &(*link)->next) - { - iq = *link; - if (!strcmp(iq->id, id)) - break; - } - if (*link) - { - iq = *link; - *link = iq->next; - - if (iq->callback) - { - if (!iq->callback(jcl, !strcmp(f, "error")?NULL:tree)) - { - Con_Printf("Invalid iq result\n"); - XML_ConPrintTree(tree, 0); - } - } - free(iq); - } - else - { - Con_Printf("Unrecognised iq result\n"); - XML_ConPrintTree(tree, 0); - } - } - - if (unparsable) - { - unparsable = false; - Con_Printf("Unrecognised iq type\n"); - XML_ConPrintTree(tree, 0); - } + JCL_ParseIQ(jcl, tree); + unparsable = false; } else if (!strcmp(tree->name, "message")) { - f = XML_GetParameter(tree, "from", NULL); - - if (f && !strcmp(f, jcl->jid)) - unparsable = false; - else - { - if (f) - { - buddy_t *b; - bresource_t *br; - Q_strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest)); - - JCL_FindBuddy(jcl, f, &b, &br); - f = b->name; - b->defaultresource = br; - } - - if (f) - { - ot = XML_ChildOfTree(tree, "composing", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s is typing\r", f); - } - ot = XML_ChildOfTree(tree, "paused", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s has stopped typing\r", f); - } - ot = XML_ChildOfTree(tree, "inactive", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "\r", f); - } - ot = XML_ChildOfTree(tree, "active", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "\r", f); - } - ot = XML_ChildOfTree(tree, "gone", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s has gone away\r", f); - } - } - - ot = XML_ChildOfTree(tree, "body", 0); - if (ot) - { - unparsable = false; - if (f) - Con_SubPrintf(f, "%s: %s\n", f, ot->body); - else - Con_Printf(ot->body); - - if (BUILTINISVALID(LocalSound)) - pLocalSound("misc/talk.wav"); - } - - if (unparsable) - { - unparsable = false; - if (jcl->streamdebug) - { - Con_Printf("Received a message without a body\n"); - XML_ConPrintTree(tree, 0); - } - } - } + JCL_ParseMessage(jcl, tree); + unparsable = false; } else if (!strcmp(tree->name, "presence")) { - buddy_t *buddy; - bresource_t *bres; - - char *from = XML_GetParameter(tree, "from", ""); - xmltree_t *show = XML_ChildOfTree(tree, "show", 0); - xmltree_t *status = XML_ChildOfTree(tree, "status", 0); - xmltree_t *quake = XML_ChildOfTree(tree, "quake", 0); - char *type = XML_GetParameter(tree, "type", ""); - char *serverip = NULL; - char *servermap = NULL; - - if (quake && !strcmp(quake->xmlns, "fteqw.com:game")) - { - serverip = XML_GetParameter(quake, "serverip", NULL); - servermap = XML_GetParameter(quake, "servermap", NULL); - } - - if (type && !strcmp(type, "subscribe")) - { - Con_Printf("^[[%s]\\xmpp\\%s^] wants to be your friend! ^[[Authorize]\\xmppauth\\%s^] ^[[Deny]\\xmppdeny\\%s^]\n", from, from, from, from); - } - else if (type && !strcmp(type, "unsubscribe")) - { - Con_Printf("^[[%s]\\xmpp\\%s^] has unfriended you\n", from, from); - } - else if (type && !strcmp(type, "unsubscribed")) - { - Con_Printf("^[[%s]\\xmpp\\%s^] is no longer unfriended you\n", from, from); - } - else - { - JCL_FindBuddy(jcl, from, &buddy, &bres); - - if (bres) - { - if (servermap) - { - bres->servertype = 2; - Q_strlcpy(bres->server, servermap, sizeof(bres->server)); - } - else if (serverip) - { - bres->servertype = 1; - Q_strlcpy(bres->server, serverip, sizeof(bres->server)); - } - else - { - bres->servertype = 0; - Q_strlcpy(bres->server, "", sizeof(bres->server)); - } - Q_strlcpy(bres->fstatus, (status && *status->body)?status->body:"", sizeof(bres->fstatus)); - if (!tree->child) - Q_strlcpy(bres->bstatus, "offline", sizeof(bres->bstatus)); - else - Q_strlcpy(bres->bstatus, (show && *show->body)?show->body:"present", sizeof(bres->bstatus)); - - if (bres->servertype == 2) - Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\xmppjoin\\%s^]\n", buddy->name, from, bres->server, from); - else if (bres->servertype == 1) - Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\observe\\%s^]\n", buddy->name, from, bres->server, bres->server); - else if (*bres->fstatus) - Con_Printf("^[[%s]\\xmpp\\%s^] is now %s: %s\n", buddy->name, from, bres->bstatus, bres->fstatus); - else - Con_Printf("^[[%s]\\xmpp\\%s^] is now %s\n", buddy->name, from, bres->bstatus); - - if (!tree->child) - { - //remove this buddy resource - } - } - else - { - Con_Printf("Weird presence:\n"); - XML_ConPrintTree(tree, 0); - } - } - + JCL_ParsePresence(jcl, tree); //we should keep a list of the people that we know of. unparsable = false; } @@ -2001,30 +2680,55 @@ int JCL_ClientFrame(jclient_t *jcl) XML_Destroy(tree); - memmove(jcl->bufferedinmessage, jcl->bufferedinmessage+pos, jcl->bufferedinammount-pos); jcl->bufferedinammount -= pos; jcl->instreampos -= pos; if (unparsable) { - Con_Printf("JCL: Input corrupt, urecognised, or unusable. Disconnecting."); + Con_Printf("XMPP: Input corrupt, urecognised, or unusable. Disconnecting.\n"); return JCL_KILL; } return JCL_CONTINUE; } -void JCL_CloseConnection(jclient_t *jcl) +void JCL_CloseConnection(jclient_t *jcl, qboolean reconnect) { - Con_Printf("JCL: Disconnected from %s@%s\n", jcl->username, jcl->domain); + //send our signoff to the server, if we're still alive. + Con_Printf("XMPP: Disconnected from %s@%s\n", jcl->username, jcl->domain); + + if (jcl->status == JCL_ACTIVE) + JCL_AddClientMessageString(jcl, ""); JCL_AddClientMessageString(jcl, ""); + 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; + pNet_Close(jcl->socket); - free(jcl); + jcl->socket = -1; + jcl->status = JCL_DEAD; + + jcl->timeout = jclient_curtime + 30*1000; //wait 30 secs before reconnecting, to avoid flood-prot-protection issues. + + if (!reconnect) + { + free(jcl); + if (jclient == jcl) + jclient = NULL; + } } //can be polled for server address updates -void JCL_GeneratePresence(qboolean force) +void JCL_GeneratePresence(jclient_t *jcl, qboolean force) { int dummystat; char serveraddr[1024*16]; @@ -2053,33 +2757,33 @@ void JCL_GeneratePresence(qboolean force) } } - if (force || strcmp(jclient->curquakeserver, *servermap?servermap:serveraddr)) + if (force || strcmp(jcl->curquakeserver, *servermap?servermap:serveraddr)) { char caps[256]; - Q_strlcpy(jclient->curquakeserver, *servermap?servermap:serveraddr, sizeof(jclient->curquakeserver)); -Con_Printf("Sending presence %s\n", jclient->curquakeserver); + Q_strlcpy(jcl->curquakeserver, *servermap?servermap:serveraddr, sizeof(jcl->curquakeserver)); + Con_DPrintf("Sending presence %s\n", jcl->curquakeserver); //note: ext='voice-v1 camera-v1 video-v1' is some legacy nonsense, and is required for voice calls with googletalk clients or something stupid like that Q_snprintf(caps, sizeof(caps), "", buildcapshash()); - if (!*jclient->curquakeserver) - JCL_AddClientMessagef(jclient, + if (!*jcl->curquakeserver) + JCL_AddClientMessagef(jcl, "" "%s" "", caps); else if (*servermap) //if we're running a server, say so - JCL_AddClientMessagef(jclient, + JCL_AddClientMessagef(jcl, "" "" "%s" "" , servermap, caps); else //if we're connected to a server, say so - JCL_AddClientMessagef(jclient, + JCL_AddClientMessagef(jcl, "" "" "%s" "" - ,jclient->curquakeserver, caps); + ,jcl->curquakeserver, caps); } } @@ -2089,23 +2793,33 @@ Con_Printf("Sending presence %s\n", jclient->curquakeserver); qintptr_t JCL_Frame(qintptr_t *args) { int stat = JCL_CONTINUE; - if (jclient) + jclient_t *jcl = jclient; + + jclient_curtime = args[0]; + if (jcl) { - if (jclient->connected) + JCL_JingleTimeouts(jcl, false); + if (jcl->status == JCL_DEAD) { - JCL_GeneratePresence(false); + if (jclient_curtime > jcl->timeout) + { + JCL_Reconnect(jcl); + jcl->timeout = jclient_curtime + 60*1000; + } } - - while(stat == JCL_CONTINUE) - stat = JCL_ClientFrame(jclient); - if (stat == JCL_KILL) + else { - JCL_CloseConnection(jclient); - - jclient = NULL; + if (jcl->connected) + JCL_GeneratePresence(jcl, false); + while(stat == JCL_CONTINUE) + stat = JCL_ClientFrame(jcl); + if (stat == JCL_NUKEFROMORBIT) + JCL_CloseConnection(jcl, false); + else if (stat == JCL_KILL) + JCL_CloseConnection(jcl, true); + else + JCL_FlushOutgoing(jcl); } - - JCL_FlushOutgoing(jclient); } return 0; } @@ -2155,14 +2869,31 @@ void JCL_LoadConfig(void) oldtls = atoi(tls); - jclient = JCL_Connect(server, oldtls?5223:5222, oldtls, account, password); + jclient = JCL_Connect(server, oldtls, account, password); } } } +static void JCL_PrintBuddyStatus(char *console, buddy_t *b, bresource_t *r) +{ + if (r->servertype == 2) + Con_SubPrintf(console, "^[[Playing Quake - %s]\\xmpp\\%s/%s\\xmppact\\join^]", r->server, b->accountdomain, r->resource); + else if (r->servertype) + Con_SubPrintf(console, "^[[Playing Quake - %s]\\observe\\%s^]", r->server, r->server); + else if (*r->fstatus) + Con_SubPrintf(console, "%s - %s", r->bstatus, r->fstatus); + else + Con_SubPrintf(console, "%s", r->bstatus); + + if ((r->caps & CAP_INVITE) && !r->servertype) + Con_SubPrintf(console, " ^[[Invite]\\xmpp\\%s/%s\\xmppact\\invite^]", b->accountdomain, r->resource); + if (r->caps & CAP_VOICE) + Con_SubPrintf(console, " ^[[Call]\\xmpp\\%s/%s\\xmppact\\call^]", b->accountdomain, r->resource); +} void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all) { buddy_t *b; bresource_t *r; + struct c2c_s *c2c; if (!jcl->buddies) Con_SubPrintf(console, "You have no friends\n"); for (b = jcl->buddies; b; b = b->next) @@ -2181,34 +2912,43 @@ void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all) Con_SubPrintf(console, "^[[%s]\\xmpp\\%s^]\n", b->name, b->accountdomain); for (r = b->resources; r; r = r->next) { - if (r->servertype == 2) - Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\xmppjoin\\%s/%s^]\n", r->resource, b->accountdomain, r->resource, r->server, b->accountdomain, r->resource); - else if (r->servertype) - Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\observe\\%s^]\n", r->resource, b->accountdomain, r->resource, r->server, r->server); - else if (*r->fstatus) - Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: %s - %s\n", r->resource, b->accountdomain, r->resource, r->bstatus, r->fstatus); - else - Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: %s\n", r->resource, b->accountdomain, r->resource, r->bstatus); + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ", r->resource, b->accountdomain, r->resource); + JCL_PrintBuddyStatus(console, b, r); + Con_SubPrintf(console, "\n"); } } else //only one resource { r = b->resources; - if (!strcmp(r->server, "-")) - Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake]\\xmppjoin\\%s/%s^]\n", b->name, b->accountdomain, r->resource, b->accountdomain, r->resource); - else if (*r->server) - Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\observe\\%s^]\n", b->name, b->accountdomain, r->resource, r->server, r->server); - else if (*r->fstatus) - Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: %s - %s\n", b->name, b->accountdomain, r->resource, r->bstatus, r->fstatus); - else - Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: %s\n", b->name, b->accountdomain, r->resource, r->bstatus); + Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ", b->name, b->accountdomain, r->resource); + JCL_PrintBuddyStatus(console, b, r); + Con_SubPrintf(console, "\n"); + } + } + + if (jcl->c2c) + Con_SubPrintf(console, "Active sessions:\n"); + for (c2c = jcl->c2c; c2c; c2c = c2c->next) + { + JCL_FindBuddy(jcl, c2c->with, &b, &r); + switch(c2c->mediatype) + { + case ICEP_VOICE: + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: voice ^[[Hang Up]\\xmppact\\jdeny\\xmpp\\%s\\xmppsid\\%s^]\n", b->name, b->accountdomain, r->resource, c2c->with, c2c->sid); + break; + case ICEP_QWSERVER: + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: server ^[[Kick]\\xmppact\\jdeny\\xmpp\\%s\\xmppsid\\%s^]\n", b->name, b->accountdomain, r->resource, c2c->with, c2c->sid); + break; + case ICEP_QWCLIENT: + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: client ^[[Disconnect]\\xmppact\\jdeny\\xmpp\\%s\\xmppsid\\%s^]\n", b->name, b->accountdomain, r->resource, c2c->with, c2c->sid); + break; } } } void JCL_SendMessage(jclient_t *jcl, char *to, char *msg) { - char markup[256]; + char markup[1024]; buddy_t *b; bresource_t *br; JCL_FindBuddy(jcl, to, &b, &br); @@ -2218,7 +2958,10 @@ void JCL_SendMessage(jclient_t *jcl, char *to, char *msg) JCL_AddClientMessagef(jcl, "", b->accountdomain); JCL_AddClientMessage(jcl, markup, XML_Markup(msg, markup, sizeof(markup)) - markup); JCL_AddClientMessageString(jcl, ""); - Con_SubPrintf(b->name, "%s: "COLOURYELLOW"%s\n", ">>", 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); } @@ -2239,13 +2982,13 @@ void JCL_Command(char *console) msg = JCL_ParseOut(msg, arg[i], sizeof(arg[i])); } - if (*arg[0] == '/') + if (arg[0][0] == '/' && arg[0][1] != '/' && strcmp(arg[0]+1, "me")) { - if (!strcmp(arg[0]+1, "tlsopen") || !strcmp(arg[0]+1, "tlsconnect")) + if (!strcmp(arg[0]+1, "open") || !strcmp(arg[0]+1, "connect") || !strcmp(arg[0]+1, "tlsopen") || !strcmp(arg[0]+1, "tlsconnect")) { //tlsconnect is 'old'. if (!*arg[1]) { - Con_TrySubPrint(console, "tlsopen [server] [account] [password]\n"); + Con_SubPrintf(console, "%s \n", arg[0]+1); return; } @@ -2254,37 +2997,7 @@ void JCL_Command(char *console) Con_TrySubPrint(console, "You are already connected\nPlease /quit first\n"); return; } - if (!*arg[1]) - { - Con_SubPrintf(console, "%s \n", arg[0]+1); - return; - } - jclient = JCL_Connect(arg[1], 5223, true, arg[2], arg[3]); - if (!jclient) - { - Con_TrySubPrint(console, "Connect failed\n"); - return; - } - } - else if (!strcmp(arg[0]+1, "open") || !strcmp(arg[0]+1, "connect")) - { - if (!*arg[1]) - { - Con_TrySubPrint(console, "open [server] [account] [password]\n"); - return; - } - - if (jclient) - { - Con_TrySubPrint(console, "You are already connected\nPlease /quit first\n"); - return; - } - if (!*arg[1]) - { - Con_SubPrintf(console, "%s \n", arg[0]+1); - return; - } - jclient = JCL_Connect(arg[1], 5222, false, arg[2], arg[3]); + jclient = JCL_Connect(arg[3], !strncmp(arg[0]+1, "tls", 3), arg[1], arg[2]); if (!jclient) { Con_TrySubPrint(console, "Connect failed\n"); @@ -2293,10 +3006,13 @@ void JCL_Command(char *console) } else if (!strcmp(arg[0]+1, "help")) { - Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /tlsconnect XMPPSERVER USERNAME@DOMAIN PASSWORD^]\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /connect XMPPSERVER USERNAME@DOMAIN/RESOURCE PASSWORD^]\n"); if (BUILTINISVALID(Net_SetTLSClient)) - Con_Printf("for example: ^[/" COMMANDPREFIX " /tlsconnect talk.google.com myusername@gmail.com mypassword^]\n" - "Note that this info will be used the next time you start quake.\n"); + { + Con_Printf("eg for gmail: ^[/" COMMANDPREFIX " /connect myusername@gmail.com mypassword talk.google.com^]\n"); + Con_Printf("eg for facebook: ^[/" COMMANDPREFIX " /connect myusername@chat.facebook.com mypassword chat.facebook.com^]\n"); + } + Con_Printf("Note that this info will be used the next time you start quake.\n"); //small note: //for the account 'me@example.com' the server to connect to can be displayed with: @@ -2314,25 +3030,16 @@ void JCL_Command(char *console) "Show all your friends! Names are clickable and will begin conversations.\n"); Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /quit^]\n" "Disconnect from the XMPP server, noone will be able to hear you scream.\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /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"); Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /msg ACCOUNTNAME your message goes here^]\n" "Sends a message to the named person. If given a resource postfix, your message will be sent only to that resource.\n"); Con_TrySubPrint(console, "If no arguments, will print out your friends list. If no /command is used, the arguments will be sent as a message to the person you last sent a message to.\n"); } - else if (!jclient) - { - Con_SubPrintf(console, "You are not connected. Cannot %s\n", arg[0]); - } - else if (!strcmp(arg[0]+1, "quit")) - { - //disconnect from the xmpp server. - JCL_CloseConnection(jclient); - jclient = NULL; - } - else if (!strcmp(arg[0]+1, "blist")) - { - //print out a full list of everyone, even those offline. - JCL_PrintBuddyList(console, jclient, true); - } else if (!strcmp(arg[0]+1, "clear")) { //just clears the current console. @@ -2345,6 +3052,24 @@ void JCL_Command(char *console) else pCmd_AddText("\nclear\n", true); } + else if (!jclient) + { + Con_SubPrintf(console, "No account specified. Cannot %s\n", arg[0]); + } + else if (!strcmp(arg[0]+1, "quit")) + { + //disconnect from the xmpp server. + JCL_CloseConnection(jclient, false); + } + else if (jclient->status != JCL_ACTIVE) + { + Con_SubPrintf(console, "You are not authed. Please wait.\n", arg[0]); + } + else if (!strcmp(arg[0]+1, "blist")) + { + //print out a full list of everyone, even those offline. + JCL_PrintBuddyList(console, jclient, true); + } else if (!strcmp(arg[0]+1, "msg")) { //FIXME: validate the dest. deal with xml markup in dest. @@ -2382,7 +3107,19 @@ void JCL_Command(char *console) } else if (!strcmp(arg[0]+1, "join")) { - JCL_Join(jclient, arg[1]); + JCL_Join(jclient, *arg[1]?arg[1]:console, NULL, true, ICEP_QWCLIENT); + } + else if (!strcmp(arg[0]+1, "invite")) + { + JCL_Join(jclient, *arg[1]?arg[1]:console, NULL, true, ICEP_QWSERVER); + } + else if (!strcmp(arg[0]+1, "voice") || !strcmp(arg[0]+1, "call")) + { + JCL_Join(jclient, *arg[1]?arg[1]:console, NULL, true, ICEP_VOICE); + } + else if (!strcmp(arg[0]+1, "kick")) + { + JCL_Join(jclient, *arg[1]?arg[1]:console, NULL, false, ICEP_INVALID); } else if (!strcmp(arg[0]+1, "raw")) { @@ -2403,7 +3140,7 @@ void JCL_Command(char *console) if (!*console) { JCL_PrintBuddyList(console, jclient, false); - Con_TrySubPrint(console, "For help, type \"^[/" COMMANDPREFIX " /help^]\"\n"); + //Con_TrySubPrint(console, "For help, type \"^[/" COMMANDPREFIX " /help^]\"\n"); } } else diff --git a/plugins/jabber/xml.c b/plugins/jabber/xml.c index 64693d9cb..acee74812 100644 --- a/plugins/jabber/xml.c +++ b/plugins/jabber/xml.c @@ -103,11 +103,13 @@ char *XML_Markup(char *s, char *d, int dlen) d+=xmlchars[i].namelen; *d++ = ';'; s++; + dlen -= xmlchars[i].namelen+2; } else { if (!dlen) break; + dlen--; *d++ = *s++; } } diff --git a/plugins/qvm_api.c b/plugins/qvm_api.c index 7c2cbb87c..226a9f333 100644 --- a/plugins/qvm_api.c +++ b/plugins/qvm_api.c @@ -20,7 +20,7 @@ int Q_vsnprintf(char *buffer, size_t maxlen, const char *format, va_list vargs) float _float; int i; int use0s; - int precision, useprepad; + int precision, useprepad, plus; if (!maxlen) return 0; @@ -31,6 +31,7 @@ maxlen--; switch(*format) { case '%': + plus = 0; precision= 0; useprepad=0; use0s=0; @@ -40,6 +41,9 @@ retry: case '-': useprepad=true; goto retry; + case '+': + plus = true; + goto retry; case '0': if (!precision) { @@ -177,6 +181,12 @@ Con_Printf("%i bytes left\n", maxlen); *buffer++ = '-'; _int *= -1; } + else if (plus) + { + if (maxlen-- == 0) + {*buffer++='\0';return tokens;} + *buffer++ = '+'; + } i = sizeof(tempbuffer)-2; tempbuffer[sizeof(tempbuffer)-1] = '\0'; while(_int)