Index: code/client/client.h =================================================================== --- code/client/client.h 2011-04-27 16:17:19.904299000 -0500 +++ code/client/client.h 2011-04-27 16:41:05.087963000 -0500 @@ -260,6 +260,10 @@ float voipPower; #endif +#ifdef PROTOCOL_SUPPORT_OLD + qboolean compat; +#endif + // big stuff at end of structure so most offsets are 15 bits or less netchan_t netchan; } clientConnection_t; Index: code/client/cl_main.c =================================================================== --- code/client/cl_main.c 2011-04-27 16:27:58.121838000 -0500 +++ code/client/cl_main.c 2011-04-27 16:41:05.099668000 -0500 @@ -635,6 +635,11 @@ if ( Cmd_Argc() == 2 ) { s = Cmd_Argv(1); Q_strncpyz( demoName, s, sizeof( demoName ) ); +#ifdef PROTOCOL_SUPPORT_OLD + if(clc.compat) + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_oldprotocol->integer); + else +#endif Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer); } else { int number; @@ -642,6 +647,11 @@ // scan for a free demo name for ( number = 0 ; number <= 9999 ; number++ ) { CL_DemoFilename( number, demoName ); +#ifdef PROTOCOL_SUPPORT_OLD + if(clc.compat) + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_oldprotocol->integer); + else +#endif Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer); if (!FS_FileExists(name)) @@ -892,6 +902,22 @@ int i = 0; *demofile = 0; +#ifdef PROTOCOL_SUPPORT_OLD + if(com_oldprotocol->integer > 0) + { + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_oldprotocol->integer); + FS_FOpenFileRead(name, demofile, qtrue); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return com_oldprotocol->integer; + } + } + + if(com_protocol->integer != com_oldprotocol->integer) +#endif + { Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_protocol->integer); FS_FOpenFileRead(name, demofile, qtrue); @@ -900,11 +926,16 @@ Com_Printf("Demo file: %s\n", name); return com_protocol->integer; } + } Com_Printf("Not found: %s\n", name); while(demo_protocols[i]) { +#ifdef PROTOCOL_SUPPORT_OLD + if(demo_protocols[i] == com_oldprotocol->integer) + continue; +#endif if(demo_protocols[i] == com_protocol->integer) continue; @@ -981,7 +1012,11 @@ break; } - if(demo_protocols[i] || protocol == com_protocol->integer) + if(demo_protocols[i] || protocol == com_protocol->integer +#ifdef PROTOCOL_SUPPORT_OLD + || protocol == com_oldprotocol->integer +#endif + ) { Com_sprintf(name, sizeof(name), "demos/%s", arg); FS_FOpenFileRead(name, &clc.demofile, qtrue); @@ -998,11 +1033,11 @@ Q_strncpyz(retry, arg, len + 1); retry[len] = '\0'; - CL_WalkDemoExt(retry, name, &clc.demofile); + protocol = CL_WalkDemoExt(retry, name, &clc.demofile); } } else - CL_WalkDemoExt(arg, name, &clc.demofile); + protocol = CL_WalkDemoExt(arg, name, &clc.demofile); if (!clc.demofile) { Com_Error( ERR_DROP, "couldn't open %s", name); @@ -1016,6 +1051,13 @@ clc.demoplaying = qtrue; Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); +#ifdef PROTOCOL_SUPPORT_OLD + if(protocol <= com_oldprotocol->integer) + clc.compat = qtrue; + else + clc.compat = qfalse; +#endif + // read demo messages until connected while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { CL_ReadDemoMessage(); @@ -2156,6 +2198,15 @@ port = Cvar_VariableValue ("net_qport"); Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + +#ifdef PROTOCOL_SUPPORT_OLD + if(com_oldprotocol->integer == com_protocol->integer) + clc.compat = qtrue; + + if(clc.compat) + Info_SetValueForKey(info, "protocol", va("%i", com_oldprotocol->integer)); + else +#endif Info_SetValueForKey(info, "protocol", va("%i", com_protocol->integer)); Info_SetValueForKey( info, "qport", va("%i", port ) ); Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); @@ -2423,6 +2474,18 @@ if(*c) challenge = atoi(c); +#ifdef PROTOCOL_SUPPORT_OLD + if(!clc.compat) + { + if(!*c || challenge != clc.challenge) + { + Com_Printf("Bad challenge for challengeResponse. Ignored.\n"); + return; + } + } + else +#endif + { if(!NET_CompareAdr(from, clc.serverAddress)) { // This challenge response is not coming from the expected address. @@ -2435,6 +2498,7 @@ return; } } + } // start sending challenge response instead of challenge request packets clc.challenge = atoi(Cmd_Argv(1)); @@ -2464,7 +2528,32 @@ return; } - Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport")); +#ifdef PROTOCOL_SUPPORT_OLD + if(!clc.compat) + { + c = Cmd_Argv(1); + + if(*c) + challenge = atoi(c); + else + { + Com_Printf("Bad connectResponse received. Ignored.\n"); + return; + } + + if(challenge != clc.challenge) + { + Com_Printf("ConnectResponse with bad challenge received. Ignored.\n"); + return; + } + } + + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge, clc.compat); +#else + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge); +#endif cls.state = CA_CONNECTED; clc.lastPacketSentTime = -9999; // send first packet immediately @@ -2505,6 +2594,27 @@ if(!Q_stricmp(c, "print")){ s = MSG_ReadString( msg ); + #ifdef PROTOCOL_SUPPORT_OLD + // Hack to detect legacy server protocol + if(cls.state == CA_CHALLENGING && com_oldprotocol->integer > 0) + { + char buf[128]; + int len; + + len = Com_sprintf(buf, sizeof(buf), "Server uses protocol version %d", + com_oldprotocol->integer); + + if(len < sizeof(buf) && !Q_strncmp(s, buf, len) && !isdigit(s[len])) + { + // This is an old, but compatible protocol version. + // Go back to connecting state. + clc.compat = qtrue; + cls.state = CA_CONNECTING; + return; + } + } + #endif + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); Com_Printf( "%s", s ); @@ -3449,7 +3559,11 @@ // if this isn't the correct protocol version, ignore it prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); - if(prot != com_protocol->integer) + if(prot != com_protocol->integer +#ifdef PROTOCOL_SUPPORT_OLD + && prot != com_oldprotocol->integer +#endif + ) { Com_DPrintf( "Different protocol info packet: %s\n", infoString ); return; Index: code/qcommon/common.c =================================================================== --- code/qcommon/common.c 2011-04-27 16:17:38.101853000 -0500 +++ code/qcommon/common.c 2011-04-27 16:41:05.084544000 -0500 @@ -32,7 +32,7 @@ #endif int demo_protocols[] = -{ 66, 67, 68, 0 }; +{ 67, 66, 0 }; #define MAX_NUM_ARGVS 50 @@ -86,6 +86,9 @@ cvar_t *com_abnormalExit; cvar_t *com_standalone; cvar_t *com_protocol; +#ifdef PROTOCOL_SUPPORT_OLD +cvar_t *com_oldprotocol; +#endif cvar_t *com_basegame; cvar_t *com_homepath; cvar_t *com_busyWait; @@ -2710,6 +2713,9 @@ s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ ); com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); com_protocol = Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_INIT); +#ifdef PROTOCOL_SUPPORT_OLD + com_oldprotocol = Cvar_Get ("oldprotocol", va("%i", PROTOCOL_OLD_VERSION), CVAR_INIT); +#endif Sys_Init(); Index: code/qcommon/files.c =================================================================== --- code/qcommon/files.c 2011-04-27 16:17:46.951955000 -0500 +++ code/qcommon/files.c 2011-04-27 16:41:05.071416000 -0500 @@ -1030,6 +1030,11 @@ if(protocol == com_protocol->integer) return qtrue; +#ifdef PROTOCOL_SUPPORT_OLD + if(protocol == PROTOCOL_OLD_VERSION) + return qtrue; +#endif + for(index = 0; demo_protocols[index]; index++) { if(demo_protocols[index] == protocol) Index: code/qcommon/net_chan.c =================================================================== --- code/qcommon/net_chan.c 2011-04-27 16:10:45.727408000 -0500 +++ code/qcommon/net_chan.c 2011-04-27 16:41:05.051095000 -0500 @@ -83,7 +83,11 @@ called to open a channel to a remote system ============== */ -void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport) +void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge +#ifdef PROTOCOL_SUPPORT_OLD + , qboolean compat +#endif + ) { Com_Memset (chan, 0, sizeof(*chan)); @@ -92,6 +96,10 @@ chan->qport = qport; chan->incomingSequence = 0; chan->outgoingSequence = 1; +#ifdef PROTOCOL_SUPPORT_OLD + chan->compat = compat; + chan->challenge = challenge; +#endif } // TTimo: unused, commenting out to make gcc happy @@ -204,6 +212,11 @@ MSG_WriteShort( &send, qport->integer ); } +#ifdef PROTOCOL_SUPPORT_OLD + if(!chan->compat) +#endif + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + // copy the reliable message to the packet first fragmentLength = FRAGMENT_SIZE; if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { @@ -276,6 +289,11 @@ if(chan->sock == NS_CLIENT) MSG_WriteShort(&send, qport->integer); +#ifdef PROTOCOL_SUPPORT_OLD + if(!chan->compat) +#endif + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + chan->outgoingSequence++; MSG_WriteData( &send, data, length ); @@ -330,6 +348,17 @@ qport = MSG_ReadShort( msg ); } +#ifdef PROTOCOL_SUPPORT_OLD + if(!chan->compat) +#endif + { + int checksum = MSG_ReadLong(msg); + + // UDP spoofing protection + if(NETCHAN_GENCHECKSUM(chan->challenge, sequence) != checksum) + return qfalse; + } + // read the fragment information if ( fragmented ) { fragmentStart = MSG_ReadShort( msg ); Index: code/qcommon/qcommon.h =================================================================== --- code/qcommon/qcommon.h 2011-04-27 16:19:13.089009000 -0500 +++ code/qcommon/qcommon.h 2011-04-27 16:41:05.076983000 -0500 @@ -195,6 +195,8 @@ #define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames #define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks +#define NETCHAN_GENCHECKSUM(challenge, sequence) ((challenge) ^ ((sequence) * (challenge))) + /* Netchan handles packet fragmentation and out of order / duplicate suppression */ @@ -222,10 +224,20 @@ int unsentFragmentStart; int unsentLength; byte unsentBuffer[MAX_MSGLEN]; + + int challenge; + +#ifdef PROTOCOL_SUPPORT_OLD + qboolean compat; +#endif } netchan_t; void Netchan_Init( int qport ); -void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport); +void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge +#ifdef PROTOCOL_SUPPORT_OLD + , qboolean compat +#endif +); void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); void Netchan_TransmitNextFragment( netchan_t *chan ); @@ -241,7 +253,8 @@ ============================================================== */ -#define PROTOCOL_VERSION 68 +#define PROTOCOL_VERSION 69 +#define PROTOCOL_OLD_VERSION 68 // 1.31 - 67 // maintain a list of compatible protocols for demo playing @@ -856,6 +869,9 @@ extern cvar_t *sv_packetdelay; extern cvar_t *com_protocol; +#ifdef PROTOCOL_SUPPORT_OLD +extern cvar_t *com_oldprotocol; +#endif // com_speeds times extern int time_game; Index: code/qcommon/q_shared.h =================================================================== --- code/qcommon/q_shared.h 2011-04-27 16:18:22.441883000 -0500 +++ code/qcommon/q_shared.h 2011-04-27 16:41:05.059512000 -0500 @@ -34,6 +34,7 @@ #define GAMENAME_FOR_MASTER "iofoo3" // must NOT contain whitespaces #define HEARTBEAT_FOR_MASTER GAMENAME_FOR_MASTER #define FLATLINE_FOR_MASTER GAMENAME_FOR_MASTER "dead" +// #define PROTOCOL_SUPPORT_OLD // You probably don't need this for your standalone game #else #define PRODUCT_NAME "ioq3" #define BASEGAME "baseq3" @@ -42,6 +43,7 @@ #define GAMENAME_FOR_MASTER "Quake3Arena" #define HEARTBEAT_FOR_MASTER "QuakeArena-1" #define FLATLINE_FOR_MASTER HEARTBEAT_FOR_MASTER + #define PROTOCOL_SUPPORT_OLD #endif #define BASETA "missionpack" Index: code/server/server.h =================================================================== --- code/server/server.h 2011-04-27 16:19:25.983433000 -0500 +++ code/server/server.h 2011-04-27 16:41:05.048389000 -0500 @@ -189,6 +189,10 @@ int oldServerTime; qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + +#ifdef PROTOCOL_SUPPORT_OLD + qboolean compat; +#endif } client_t; //============================================================================= Index: code/server/sv_client.c =================================================================== --- code/server/sv_client.c 2011-04-27 16:36:36.852664000 -0500 +++ code/server/sv_client.c 2011-04-27 16:41:05.045123000 -0500 @@ -301,6 +301,9 @@ intptr_t denied; int count; char *ip; +#ifdef PROTOCOL_SUPPORT_OLD + qboolean compat = qfalse; +#endif Com_DPrintf ("SVC_DirectConnect ()\n"); @@ -315,6 +318,12 @@ version = atoi(Info_ValueForKey(userinfo, "protocol")); +#ifdef PROTOCOL_SUPPORT_OLD + if(version > 0 && com_oldprotocol->integer == version) + compat = qtrue; + else +#endif + { if(version != com_protocol->integer) { NET_OutOfBandPrint(NS_SERVER, from, "print\nServer uses protocol version %i " @@ -322,6 +331,7 @@ Com_DPrintf(" rejected connect from version %i\n", version); return; } + } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); @@ -502,7 +512,12 @@ newcl->challenge = challenge; // save the address - Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport); +#ifdef PROTOCOL_SUPPORT_OLD + newcl->compat = compat; + Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge, compat); +#else + Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge); +#endif // init the netchan queue newcl->netchan_end_queue = &newcl->netchan_start_queue; @@ -523,7 +538,7 @@ SV_UserinfoChanged( newcl ); // send the connect packet to the client - NET_OutOfBandPrint(NS_SERVER, from, "connectResponse"); + NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %d", challenge); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); Index: README =================================================================== --- README 2011-04-27 16:09:52.377222000 -0500 +++ README 2011-04-27 16:41:05.039575000 -0500 @@ -214,9 +214,15 @@ ipv6 servers on the local network net_mcastiface - outgoing interface to use for scan - protocol - Allow changing protocol version + oldprotocol - when encountering a server/client that + only supports the version configured in + this cvar, ioquake3 will use the old and + less secure protocol from quake3 1.32c. (startup only) + protocol - Allow changing protocol version that is + sent to the server (startup only) + r_allowResize - make window resizable (SDL only) r_ext_texture_filter_anisotropic - anisotropic texture filtering r_zProj - distance of observer camera to projection @@ -563,6 +569,36 @@ maps) created by yourself are your property and can be sold like every other game you find in stores. +Network protocols + There are now two cvars that give you some degree of freedom over the reported + protocol versions between clients and servers: "protocol" and "oldprotocol". + The reason for this is that some standalone games increased the protocol + number even though nothing really changed in their protocol and the ioquake3 + engine is still fully compatible. + + In order to fix a vulnerability in the network protocol as outlined in + + http://aluigi.altervista.org/papers/q3noclient.txt + + a new network protocol was introduced that defends against such attacks. + Unfortunately, this protocol will be incompatible to the original quake3 1.32c + which is the latest official release from id. + Luckily, ioquake3 has backwards compatibility, on the client as well as on the + server. This means ioquake3 players can play on old servers just as ioquake3 + servers are able to service old clients. + + The cvar "protocol" denotes the protocol version for the new hardened + protocol, whereas the "oldprotocol" cvar denotes the protocol version for the + legacy protocol. + If the value for "oldprotocol" and "protocol" is identical, then the legacy + protocol is always used. If oldprotocol is set to 0, then support for the + legacy protocol is disabled. + + Mods that use a standalone engine obviously do not require dual protocol + support, and it is turned off if the engine is compiled with STANDALONE per + default. You can enable it in q_shared.h if desired by defining + PROTOCOL_SUPPORT_OLD. + cl_guid Support cl_guid is a cvar which is part of the client's USERINFO string. Its value is a 32 character string made up of [a-f] and [0-9] characters. This