mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-26 14:11:20 +00:00
983705084a
CVE-2005-0983 Quake 3 engine, as used in multiple games, allows remote attackers to cause a denial of service (client disconnect) via a long message, which is not properly truncated and causes the engine to process the remaining data as if it were network data. Luigi Auriemma q3msgboom from Tim Angus in ioquake svn 95 git 33a48a0336865a9d21983e4836920cd9f3401101 Fixed q3msgboom from http://www.quakesrc.org/forums/viewtopic.php?t=5374
957 lines
25 KiB
C++
957 lines
25 KiB
C++
//Anything above this #include will be ignored by the compiler
|
|
#include "../qcommon/exe_headers.h"
|
|
|
|
#include "server.h"
|
|
|
|
//rww - RAGDOLL_BEGIN
|
|
#include "../ghoul2/ghoul2_shared.h"
|
|
//rww - RAGDOLL_END
|
|
|
|
serverStatic_t svs; // persistant server info
|
|
server_t sv; // local server
|
|
vm_t *gvm = NULL; // game virtual machine // bk001212 init
|
|
|
|
cvar_t *sv_fps; // time rate for running non-clients
|
|
cvar_t *sv_timeout; // seconds without any message
|
|
cvar_t *sv_zombietime; // seconds to sink messages after disconnect
|
|
cvar_t *sv_rconPassword; // password for remote server commands
|
|
cvar_t *sv_privatePassword; // password for the privateClient slots
|
|
cvar_t *sv_maxclients;
|
|
cvar_t *sv_privateClients; // number of clients reserved for password
|
|
cvar_t *sv_hostname;
|
|
#ifndef _XBOX // No master or downloads on Xbox
|
|
cvar_t *sv_allowDownload;
|
|
cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
|
|
#endif
|
|
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
|
|
cvar_t *sv_showghoultraces; // report ghoul2 traces
|
|
cvar_t *sv_showloss; // report when usercmds are lost
|
|
cvar_t *sv_padPackets; // add nop bytes to messages
|
|
cvar_t *sv_killserver; // menu system can set to 1 to shut server down
|
|
cvar_t *sv_mapname;
|
|
cvar_t *sv_mapChecksum;
|
|
cvar_t *sv_serverid;
|
|
cvar_t *sv_maxRate;
|
|
cvar_t *sv_minPing;
|
|
cvar_t *sv_maxPing;
|
|
cvar_t *sv_gametype;
|
|
cvar_t *sv_pure;
|
|
cvar_t *sv_floodProtect;
|
|
cvar_t *sv_needpass;
|
|
#ifdef USE_CD_KEY
|
|
cvar_t *sv_allowAnonymous;
|
|
#endif
|
|
/*
|
|
=============================================================================
|
|
|
|
EVENT MESSAGES
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
SV_ExpandNewlines
|
|
|
|
Converts newlines to "\n" so a line prints nicer
|
|
===============
|
|
*/
|
|
char *SV_ExpandNewlines( char *in ) {
|
|
static char string[1024];
|
|
int l;
|
|
|
|
l = 0;
|
|
while ( *in && l < sizeof(string) - 3 ) {
|
|
if ( *in == '\n' ) {
|
|
string[l++] = '\\';
|
|
string[l++] = 'n';
|
|
} else {
|
|
string[l++] = *in;
|
|
}
|
|
in++;
|
|
}
|
|
string[l] = 0;
|
|
|
|
return string;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
SV_ReplacePendingServerCommands
|
|
|
|
This is ugly
|
|
======================
|
|
*/
|
|
int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
|
|
int i, index, csnum1, csnum2;
|
|
|
|
for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
|
|
index = i & ( MAX_RELIABLE_COMMANDS - 1 );
|
|
//
|
|
if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
|
|
sscanf(cmd, "cs %i", &csnum1);
|
|
sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
|
|
if ( csnum1 == csnum2 ) {
|
|
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
|
|
/*
|
|
if ( client->netchan.remoteAddress.type != NA_BOT ) {
|
|
Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
|
|
}
|
|
*/
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
SV_AddServerCommand
|
|
|
|
The given command will be transmitted to the client, and is guaranteed to
|
|
not have future snapshot_t executed before it is executed
|
|
======================
|
|
*/
|
|
void SV_AddServerCommand( client_t *client, const char *cmd ) {
|
|
int index, i;
|
|
|
|
// this is very ugly but it's also a waste to for instance send multiple config string updates
|
|
// for the same config string index in one snapshot
|
|
// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
|
|
// return;
|
|
// }
|
|
|
|
client->reliableSequence++;
|
|
// if we would be losing an old command that hasn't been acknowledged,
|
|
// we must drop the connection
|
|
// we check == instead of >= so a broadcast print added by SV_DropClient()
|
|
// doesn't cause a recursive drop client
|
|
if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
|
|
Com_Printf( "===== pending server commands =====\n" );
|
|
for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
|
|
Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
|
|
}
|
|
Com_Printf( "cmd %5d: %s\n", i, cmd );
|
|
SV_DropClient( client, "Server command overflow" );
|
|
return;
|
|
}
|
|
index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
|
|
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_SendServerCommand
|
|
|
|
Sends a reliable command string to be interpreted by
|
|
the client game module: "cp", "print", "chat", etc
|
|
A NULL client will broadcast to all clients
|
|
=================
|
|
*/
|
|
void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
|
|
va_list argptr;
|
|
byte message[MAX_MSGLEN];
|
|
client_t *client;
|
|
int j;
|
|
|
|
va_start (argptr,fmt);
|
|
vsprintf ((char *)message, fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
// Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
|
|
// The actual cause of the bug is probably further downstream
|
|
// and should maybe be addressed later, but this certainly
|
|
// fixes the problem for now
|
|
if ( strlen ((char *)message) > 1022 ) {
|
|
return;
|
|
}
|
|
|
|
if ( cl != NULL ) {
|
|
SV_AddServerCommand( cl, (char *)message );
|
|
return;
|
|
}
|
|
|
|
// hack to echo broadcast prints to console
|
|
if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
|
|
Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
|
|
}
|
|
|
|
// send the data to all relevent clients
|
|
for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
|
|
if ( client->state < CS_PRIMED ) {
|
|
continue;
|
|
}
|
|
SV_AddServerCommand( client, (char *)message );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
MASTER SERVER FUNCTIONS
|
|
|
|
==============================================================================
|
|
*/
|
|
#ifndef _XBOX // No master on Xbox
|
|
#define NEW_RESOLVE_DURATION 86400000 //24 hours
|
|
static int g_lastResolveTime[MAX_MASTER_SERVERS];
|
|
|
|
static inline bool SV_MasterNeedsResolving(int server, int time)
|
|
{ //refresh every so often regardless of if the actual address was modified -rww
|
|
if (g_lastResolveTime[server] > time)
|
|
{ //time flowed backwards?
|
|
return true;
|
|
}
|
|
|
|
if ((time-g_lastResolveTime[server]) > NEW_RESOLVE_DURATION)
|
|
{ //it's time again
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_MasterHeartbeat
|
|
|
|
Send a message to the masters every few minutes to
|
|
let it know we are alive, and log information.
|
|
We will also have a heartbeat sent when a server
|
|
changes from empty to non-empty, and full to non-full,
|
|
but not on every player enter or exit.
|
|
================
|
|
*/
|
|
#define HEARTBEAT_MSEC 300*1000
|
|
#define HEARTBEAT_GAME "QuakeArena-1"
|
|
void SV_MasterHeartbeat( void ) {
|
|
static netadr_t adr[MAX_MASTER_SERVERS];
|
|
int i;
|
|
int time;
|
|
|
|
// "dedicated 1" is for lan play, "dedicated 2" is for inet public play
|
|
if ( !com_dedicated || com_dedicated->integer != 2 ) {
|
|
return; // only dedicated servers send heartbeats
|
|
}
|
|
|
|
// if not time yet, don't send anything
|
|
if ( svs.time < svs.nextHeartbeatTime ) {
|
|
return;
|
|
}
|
|
svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
|
|
|
|
//we need to use this instead of svs.time since svs.time resets over map changes (or rather
|
|
//every time the game restarts), and we don't really need to resolve every map change
|
|
time = Com_Milliseconds();
|
|
|
|
// send to group masters
|
|
for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
|
|
if ( !sv_master[i]->string[0] ) {
|
|
continue;
|
|
}
|
|
|
|
// see if we haven't already resolved the name
|
|
// resolving usually causes hitches on win95, so only
|
|
// do it when needed
|
|
if ( sv_master[i]->modified || SV_MasterNeedsResolving(i, time) ) {
|
|
sv_master[i]->modified = qfalse;
|
|
|
|
g_lastResolveTime[i] = time;
|
|
|
|
Com_Printf( "Resolving %s\n", sv_master[i]->string );
|
|
if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) {
|
|
// if the address failed to resolve, clear it
|
|
// so we don't take repeated dns hits
|
|
Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
|
|
Cvar_Set( sv_master[i]->name, "" );
|
|
sv_master[i]->modified = qfalse;
|
|
continue;
|
|
}
|
|
if ( !strstr( ":", sv_master[i]->string ) ) {
|
|
adr[i].port = BigShort( PORT_MASTER );
|
|
}
|
|
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string,
|
|
adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3],
|
|
BigShort( adr[i].port ) );
|
|
}
|
|
|
|
|
|
Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
|
|
// this command should be changed if the server info / status format
|
|
// ever incompatably changes
|
|
NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SV_MasterShutdown
|
|
|
|
Informs all masters that this server is going down
|
|
=================
|
|
*/
|
|
void SV_MasterShutdown( void ) {
|
|
// send a hearbeat right now
|
|
svs.nextHeartbeatTime = -9999;
|
|
SV_MasterHeartbeat();
|
|
|
|
// send it again to minimize chance of drops
|
|
svs.nextHeartbeatTime = -9999;
|
|
SV_MasterHeartbeat();
|
|
|
|
// when the master tries to poll the server, it won't respond, so
|
|
// it will be removed from the list
|
|
}
|
|
#endif // _XBOX - No master on Xbox
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
CONNECTIONLESS COMMANDS
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
SVC_Status
|
|
|
|
Responds with all the info that qplug or qspy can see about the server
|
|
and all connected players. Used for getting detailed information after
|
|
the simple info query.
|
|
================
|
|
*/
|
|
void SVC_Status( netadr_t from ) {
|
|
char player[1024];
|
|
char status[MAX_MSGLEN];
|
|
int i;
|
|
client_t *cl;
|
|
playerState_t *ps;
|
|
int statusLength;
|
|
int playerLength;
|
|
char infostring[MAX_INFO_STRING];
|
|
|
|
// ignore if we are in single player
|
|
/*
|
|
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
|
|
return;
|
|
}
|
|
*/
|
|
|
|
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
|
|
|
|
// echo back the parameter to status. so master servers can use it as a challenge
|
|
// to prevent timed spoofed reply packets that add ghost servers
|
|
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
|
|
|
|
// add "demo" to the sv_keywords if restricted
|
|
if ( Cvar_VariableValue( "fs_restrict" ) ) {
|
|
char keywords[MAX_INFO_STRING];
|
|
|
|
Com_sprintf( keywords, sizeof( keywords ), "demo %s",
|
|
Info_ValueForKey( infostring, "sv_keywords" ) );
|
|
Info_SetValueForKey( infostring, "sv_keywords", keywords );
|
|
}
|
|
|
|
status[0] = 0;
|
|
statusLength = 0;
|
|
|
|
for (i=0 ; i < sv_maxclients->integer ; i++) {
|
|
cl = &svs.clients[i];
|
|
if ( cl->state >= CS_CONNECTED ) {
|
|
ps = SV_GameClientNum( i );
|
|
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
|
|
ps->persistant[PERS_SCORE], cl->ping, cl->name);
|
|
playerLength = strlen(player);
|
|
if (statusLength + playerLength >= sizeof(status) ) {
|
|
break; // can't hold any more
|
|
}
|
|
strcpy (status + statusLength, player);
|
|
statusLength += playerLength;
|
|
}
|
|
}
|
|
|
|
NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_Info
|
|
|
|
Responds with a short info message that should be enough to determine
|
|
if a user is interested in a server to do a full status
|
|
================
|
|
*/
|
|
void SVC_Info( netadr_t from ) {
|
|
int i, count, wDisable;
|
|
char *gamedir;
|
|
char infostring[MAX_INFO_STRING];
|
|
|
|
// ignore if we are in single player
|
|
/*
|
|
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
|
|
return;
|
|
}
|
|
*/
|
|
|
|
#ifdef _XBOX
|
|
// don't send system link info if in Xbox Live
|
|
if (logged_on)
|
|
return;
|
|
#endif
|
|
|
|
if (Cvar_VariableValue("ui_singlePlayerActive"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
|
|
* to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
|
|
*/
|
|
|
|
// A maximum challenge length of 128 should be more than plenty.
|
|
if(strlen(Cmd_Argv(1)) > 128)
|
|
return;
|
|
|
|
// don't count privateclients
|
|
count = 0;
|
|
for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) {
|
|
if ( svs.clients[i].state >= CS_CONNECTED ) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
infostring[0] = 0;
|
|
|
|
// echo back the parameter to status. so servers can use it as a challenge
|
|
// to prevent timed spoofed reply packets that add ghost servers
|
|
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
|
|
|
|
Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
|
|
Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
|
|
Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
|
|
Info_SetValueForKey( infostring, "clients", va("%i", count) );
|
|
Info_SetValueForKey( infostring, "sv_maxclients",
|
|
va("%i", sv_maxclients->integer - sv_privateClients->integer ) );
|
|
Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
|
|
Info_SetValueForKey( infostring, "needpass", va("%i", sv_needpass->integer ) );
|
|
Info_SetValueForKey( infostring, "truejedi", va("%i", Cvar_VariableIntegerValue( "g_jediVmerc" ) ) );
|
|
if ( sv_gametype->integer == GT_DUEL || sv_gametype->integer == GT_POWERDUEL )
|
|
{
|
|
wDisable = Cvar_VariableIntegerValue( "g_duelWeaponDisable" );
|
|
}
|
|
else
|
|
{
|
|
wDisable = Cvar_VariableIntegerValue( "g_weaponDisable" );
|
|
}
|
|
Info_SetValueForKey( infostring, "wdisable", va("%i", wDisable ) );
|
|
Info_SetValueForKey( infostring, "fdisable", va("%i", Cvar_VariableIntegerValue( "g_forcePowerDisable" ) ) );
|
|
//Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
|
|
|
|
if( sv_minPing->integer ) {
|
|
Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
|
|
}
|
|
if( sv_maxPing->integer ) {
|
|
Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
|
|
}
|
|
gamedir = Cvar_VariableString( "fs_game" );
|
|
if( *gamedir ) {
|
|
Info_SetValueForKey( infostring, "game", gamedir );
|
|
}
|
|
#ifdef USE_CD_KEY
|
|
Info_SetValueForKey( infostring, "sv_allowAnonymous", va("%i", sv_allowAnonymous->integer) );
|
|
#endif
|
|
|
|
#ifdef _XBOX
|
|
// Include Xbox specific networking info
|
|
char sxnkid[XNKID_STRING_LEN];
|
|
XNKIDToString(SysLink_GetXNKID(), sxnkid);
|
|
Info_SetValueForKey(infostring, "xnkid", sxnkid);
|
|
|
|
char sxnkey[XNKEY_STRING_LEN];
|
|
XNKEYToString(SysLink_GetXNKEY(), sxnkey);
|
|
Info_SetValueForKey(infostring, "xnkey", sxnkey);
|
|
|
|
char sxnaddr[XNADDR_STRING_LEN];
|
|
XnAddrToString(Net_GetXNADDR(), sxnaddr);
|
|
Info_SetValueForKey(infostring, "xnaddr", sxnaddr);
|
|
#endif
|
|
|
|
NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_FlushRedirect
|
|
|
|
================
|
|
*/
|
|
void SV_FlushRedirect( char *outputbuf ) {
|
|
NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SVC_RemoteCommand
|
|
|
|
An rcon packet arrived from the network.
|
|
Shift down the remaining args
|
|
Redirect all printfs
|
|
===============
|
|
*/
|
|
void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
|
|
qboolean valid;
|
|
unsigned int i, time;
|
|
char remaining[1024];
|
|
#define SV_OUTPUTBUF_LENGTH (MAX_MSGLEN - 16)
|
|
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
|
|
static unsigned int lasttime = 0;
|
|
|
|
time = Com_Milliseconds();
|
|
if (time<(lasttime+500)) {
|
|
return;
|
|
}
|
|
lasttime = time;
|
|
|
|
if ( !strlen( sv_rconPassword->string ) ||
|
|
strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
|
|
valid = qfalse;
|
|
Com_DPrintf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
|
|
} else {
|
|
valid = qtrue;
|
|
Com_DPrintf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
|
|
}
|
|
|
|
// start redirecting all print outputs to the packet
|
|
svs.redirectAddress = from;
|
|
Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
|
|
|
|
if ( !strlen( sv_rconPassword->string ) ) {
|
|
Com_Printf ("No rconpassword set.\n");
|
|
} else if ( !valid ) {
|
|
Com_Printf ("Bad rconpassword.\n");
|
|
} else {
|
|
remaining[0] = 0;
|
|
|
|
for (i=2 ; i<Cmd_Argc() ; i++) {
|
|
strcat (remaining, Cmd_Argv(i) );
|
|
strcat (remaining, " ");
|
|
}
|
|
|
|
Cmd_ExecuteString (remaining);
|
|
}
|
|
|
|
Com_EndRedirect ();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SV_ConnectionlessPacket
|
|
|
|
A connectionless packet has four leading 0xff
|
|
characters to distinguish it from a game channel.
|
|
Clients that are in the game can still send
|
|
connectionless packets.
|
|
=================
|
|
*/
|
|
void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
|
|
char *s;
|
|
char *c;
|
|
|
|
MSG_BeginReadingOOB( msg );
|
|
MSG_ReadLong( msg ); // skip the -1 marker
|
|
|
|
if (!Q_strncmp("connect", (const char *)&msg->data[4], 7)) {
|
|
Huff_Decompress(msg, 12);
|
|
}
|
|
|
|
s = MSG_ReadStringLine( msg );
|
|
Cmd_TokenizeString( s );
|
|
|
|
c = Cmd_Argv(0);
|
|
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
|
|
|
|
if (!Q_stricmp(c, "getstatus")) {
|
|
SVC_Status( from );
|
|
} else if (!Q_stricmp(c, "getinfo")) {
|
|
SVC_Info( from );
|
|
} else if (!Q_stricmp(c, "getchallenge")) {
|
|
SV_GetChallenge( from );
|
|
} else if (!Q_stricmp(c, "connect")) {
|
|
SV_DirectConnect( from );
|
|
#ifndef _XBOX // No authorization on Xbox
|
|
} else if (!Q_stricmp(c, "ipAuthorize")) {
|
|
SV_AuthorizeIpPacket( from );
|
|
#endif
|
|
} else if (!Q_stricmp(c, "rcon")) {
|
|
SVC_RemoteCommand( from, msg );
|
|
} else if (!Q_stricmp(c, "disconnect")) {
|
|
// if a client starts up a local server, we may see some spurious
|
|
// server disconnect messages when their new server sees our final
|
|
// sequenced messages to the old client
|
|
} else {
|
|
Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
|
|
, NET_AdrToString (from), s);
|
|
}
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=================
|
|
SV_ReadPackets
|
|
=================
|
|
*/
|
|
void SV_PacketEvent( netadr_t from, msg_t *msg ) {
|
|
int i;
|
|
client_t *cl;
|
|
int qport;
|
|
|
|
// check for connectionless packet (0xffffffff) first
|
|
if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
|
|
SV_ConnectionlessPacket( from, msg );
|
|
return;
|
|
}
|
|
|
|
// read the qport out of the message so we can fix up
|
|
// stupid address translating routers
|
|
MSG_BeginReadingOOB( msg );
|
|
MSG_ReadLong( msg ); // sequence number
|
|
qport = MSG_ReadShort( msg ) & 0xffff;
|
|
|
|
// find which client the message is from
|
|
for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
|
|
if (cl->state == CS_FREE) {
|
|
continue;
|
|
}
|
|
if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
|
|
continue;
|
|
}
|
|
// it is possible to have multiple clients from a single IP
|
|
// address, so they are differentiated by the qport variable
|
|
if (cl->netchan.qport != qport) {
|
|
continue;
|
|
}
|
|
|
|
// the IP port can't be used to differentiate them, because
|
|
// some address translating routers periodically change UDP
|
|
// port assignments
|
|
if (cl->netchan.remoteAddress.port != from.port) {
|
|
Com_Printf( "SV_ReadPackets: fixing up a translated port\n" );
|
|
cl->netchan.remoteAddress.port = from.port;
|
|
}
|
|
|
|
// make sure it is a valid, in sequence packet
|
|
if (SV_Netchan_Process(cl, msg)) {
|
|
// zombie clients still need to do the Netchan_Process
|
|
// to make sure they don't need to retransmit the final
|
|
// reliable message, but they don't do any other processing
|
|
if (cl->state != CS_ZOMBIE) {
|
|
cl->lastPacketTime = svs.time; // don't timeout
|
|
SV_ExecuteClientMessage( cl, msg );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if we received a sequenced packet from an address we don't reckognize,
|
|
// send an out of band disconnect packet to it
|
|
NET_OutOfBandPrint( NS_SERVER, from, "disconnect" );
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
SV_CalcPings
|
|
|
|
Updates the cl->ping variables
|
|
===================
|
|
*/
|
|
void SV_CalcPings( void ) {
|
|
int i, j;
|
|
client_t *cl;
|
|
int total, count;
|
|
int delta;
|
|
playerState_t *ps;
|
|
|
|
for (i=0 ; i < sv_maxclients->integer ; i++) {
|
|
cl = &svs.clients[i];
|
|
if ( cl->state != CS_ACTIVE ) {
|
|
cl->ping = 999;
|
|
continue;
|
|
}
|
|
if ( !cl->gentity ) {
|
|
cl->ping = 999;
|
|
continue;
|
|
}
|
|
if ( cl->gentity->r.svFlags & SVF_BOT ) {
|
|
cl->ping = 0;
|
|
continue;
|
|
}
|
|
|
|
total = 0;
|
|
count = 0;
|
|
for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
|
|
if ( cl->frames[j].messageAcked <= 0 ) {
|
|
continue;
|
|
}
|
|
delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
|
|
count++;
|
|
total += delta;
|
|
}
|
|
if (!count) {
|
|
cl->ping = 999;
|
|
} else {
|
|
cl->ping = total/count;
|
|
if ( cl->ping > 999 ) {
|
|
cl->ping = 999;
|
|
}
|
|
}
|
|
|
|
// let the game dll know about the ping
|
|
ps = SV_GameClientNum( i );
|
|
ps->ping = cl->ping;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_CheckTimeouts
|
|
|
|
If a packet has not been received from a client for timeout->integer
|
|
seconds, drop the conneciton. Server time is used instead of
|
|
realtime to avoid dropping the local client while debugging.
|
|
|
|
When a client is normally dropped, the client_t goes into a zombie state
|
|
for a few seconds to make sure any final reliable message gets resent
|
|
if necessary
|
|
==================
|
|
*/
|
|
void SV_CheckTimeouts( void ) {
|
|
int i;
|
|
client_t *cl;
|
|
int droppoint;
|
|
int zombiepoint;
|
|
|
|
droppoint = svs.time - 1000 * sv_timeout->integer;
|
|
zombiepoint = svs.time - 1000 * sv_zombietime->integer;
|
|
|
|
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
|
|
// message times may be wrong across a changelevel
|
|
if (cl->lastPacketTime > svs.time) {
|
|
cl->lastPacketTime = svs.time;
|
|
}
|
|
|
|
if (cl->state == CS_ZOMBIE
|
|
&& cl->lastPacketTime < zombiepoint) {
|
|
Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for %s\n", cl->name );
|
|
cl->state = CS_FREE; // can now be reused
|
|
continue;
|
|
}
|
|
if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
|
|
// wait several frames so a debugger session doesn't
|
|
// cause a timeout
|
|
if ( ++cl->timeoutCount > 5 ) {
|
|
SV_DropClient (cl, "timed out");
|
|
cl->state = CS_FREE; // don't bother with zombie state
|
|
}
|
|
} else {
|
|
cl->timeoutCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SV_CheckPaused
|
|
==================
|
|
*/
|
|
qboolean SV_CheckPaused( void ) {
|
|
int count;
|
|
client_t *cl;
|
|
int i;
|
|
|
|
if ( !cl_paused->integer ) {
|
|
return qfalse;
|
|
}
|
|
|
|
// only pause if there is just a single client connected
|
|
count = 0;
|
|
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
|
|
if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if ( count > 1 ) {
|
|
// don't pause
|
|
sv_paused->integer = 0;
|
|
return qfalse;
|
|
}
|
|
|
|
sv_paused->integer = 1;
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_CheckCvars
|
|
==================
|
|
*/
|
|
void SV_CheckCvars( void ) {
|
|
static int lastMod = -1;
|
|
qboolean changed = qfalse;
|
|
|
|
if ( sv_hostname->modificationCount != lastMod ) {
|
|
char hostname[MAX_INFO_STRING];
|
|
char *c = hostname;
|
|
lastMod = sv_hostname->modificationCount;
|
|
|
|
strcpy( hostname, sv_hostname->string );
|
|
while( *c )
|
|
{
|
|
if ( (*c == '\\') || (*c == ';') || (*c == '"'))
|
|
{
|
|
*c = '.';
|
|
changed = qtrue;
|
|
}
|
|
c++;
|
|
}
|
|
if( changed )
|
|
{
|
|
Cvar_Set("sv_hostname", hostname );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Frame
|
|
|
|
Player movement occurs as a result of packet events, which
|
|
happen before SV_Frame is called
|
|
==================
|
|
*/
|
|
void SV_Frame( int msec ) {
|
|
int frameMsec;
|
|
int startTime;
|
|
|
|
// the menu kills the server with this cvar
|
|
if ( sv_killserver->integer ) {
|
|
SV_Shutdown ("Server was killed.\n");
|
|
Cvar_Set( "sv_killserver", "0" );
|
|
return;
|
|
}
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
return;
|
|
}
|
|
|
|
// allow pause if only the local client is connected
|
|
if ( SV_CheckPaused() ) {
|
|
return;
|
|
}
|
|
|
|
// if it isn't time for the next frame, do nothing
|
|
if ( sv_fps->integer < 1 ) {
|
|
Cvar_Set( "sv_fps", "10" );
|
|
}
|
|
frameMsec = 1000 / sv_fps->integer ;
|
|
|
|
sv.timeResidual += msec;
|
|
|
|
if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );
|
|
|
|
if ( com_dedicated->integer && sv.timeResidual < frameMsec && (!com_timescale || com_timescale->value >= 1) ) {
|
|
// NET_Sleep will give the OS time slices until either get a packet
|
|
// or time enough for a server frame has gone by
|
|
NET_Sleep(frameMsec - sv.timeResidual);
|
|
return;
|
|
}
|
|
|
|
// if time is about to hit the 32nd bit, kick all clients
|
|
// and clear sv.time, rather
|
|
// than checking for negative time wraparound everywhere.
|
|
// 2giga-milliseconds = 23 days, so it won't be too often
|
|
if ( svs.time > 0x70000000 ) {
|
|
SV_Shutdown( "Restarting server due to time wrapping" );
|
|
//Cbuf_AddText( "vstr nextmap\n" );
|
|
Cbuf_AddText( "map_restart 0\n" );
|
|
return;
|
|
}
|
|
// this can happen considerably earlier when lots of clients play and the map doesn't change
|
|
if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
|
|
SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
|
|
//Cbuf_AddText( "vstr nextmap\n" );
|
|
Cbuf_AddText( "map_restart 0\n" );
|
|
return;
|
|
}
|
|
|
|
if( sv.restartTime && svs.time >= sv.restartTime ) {
|
|
sv.restartTime = 0;
|
|
Cbuf_AddText( "map_restart 0\n" );
|
|
return;
|
|
}
|
|
|
|
// update infostrings if anything has been changed
|
|
if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
|
|
SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
|
|
cvar_modifiedFlags &= ~CVAR_SERVERINFO;
|
|
}
|
|
if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
|
|
SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
|
|
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
|
|
}
|
|
|
|
if ( com_speeds->integer ) {
|
|
startTime = Sys_Milliseconds ();
|
|
} else {
|
|
startTime = 0; // quite a compiler warning
|
|
}
|
|
|
|
// update ping based on the all received frames
|
|
SV_CalcPings();
|
|
|
|
if (com_dedicated->integer) SV_BotFrame( svs.time );
|
|
|
|
// run the game simulation in chunks
|
|
while ( sv.timeResidual >= frameMsec ) {
|
|
sv.timeResidual -= frameMsec;
|
|
svs.time += frameMsec;
|
|
|
|
// let everything in the world think and move
|
|
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
|
|
}
|
|
|
|
//rww - RAGDOLL_BEGIN
|
|
G2API_SetTime(svs.time,0);
|
|
//rww - RAGDOLL_END
|
|
|
|
if ( com_speeds->integer ) {
|
|
time_game = Sys_Milliseconds () - startTime;
|
|
}
|
|
|
|
// check timeouts
|
|
SV_CheckTimeouts();
|
|
|
|
// send messages back to the clients
|
|
SV_SendClientMessages();
|
|
|
|
SV_CheckCvars();
|
|
|
|
// send a heartbeat to the master if needed
|
|
#ifndef _XBOX // No master on Xbox
|
|
SV_MasterHeartbeat();
|
|
#endif
|
|
}
|
|
|
|
//============================================================================
|
|
|