/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2005 - 2015, ioquake3 contributors
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
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, see .
===========================================================================
*/
#include "server.h"
#include "ghoul2/ghoul2_shared.h"
#include "sv_gameapi.h"
serverStatic_t svs; // persistant server info
server_t sv; // local server
cvar_t *sv_snapsMin; // minimum snapshots/sec a client can request, also limited by sv_snapsMax
cvar_t *sv_snapsMax; // maximum snapshots/sec a client can request, also limited by sv_fps
cvar_t *sv_snapsPolicy; // 0-2
cvar_t *sv_fps = NULL; // 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;
cvar_t *sv_allowDownload;
cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
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_ratePolicy; // 1-2
cvar_t *sv_clientRate;
cvar_t *sv_minRate;
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_floodProtectSlow;
cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
cvar_t *sv_needpass;
cvar_t *sv_filterCommands; // strict filtering on commands (1: strip ['\r', '\n'], 2: also strip ';')
cvar_t *sv_autoDemo;
cvar_t *sv_autoDemoBots;
cvar_t *sv_autoDemoMaxMaps;
cvar_t *sv_legacyFixes;
cvar_t *sv_banFile;
cvar_t *sv_maxOOBRate;
cvar_t *sv_maxOOBRateIP;
cvar_t *sv_autoWhitelist;
serverBan_t serverBans[SERVER_MAXBANS];
int serverBansCount = 0;
/*
=============================================================================
EVENT MESSAGES
=============================================================================
*/
/*
===============
SV_ExpandNewlines
Converts newlines to "\n" so a line prints nicer
===============
*/
char *SV_ExpandNewlines( char *in ) {
static char string[1024];
size_t 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_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;
// do not send commands until the gamestate has been sent
if ( client->state < CS_PRIMED ) {
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);
Q_vsnprintf((char *)message, sizeof(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 && !Q_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++) {
SV_AddServerCommand( client, (char *)message );
}
}
/*
==============================================================================
MASTER SERVER FUNCTIONS
==============================================================================
*/
#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 %s\n", sv_master[i]->string, NET_AdrToString(adr[i]) );
SVC_WhitelistAdr( adr[i] );
}
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
}
/*
==============================================================================
CONNECTIONLESS COMMANDS
==============================================================================
*/
/*
================
SVC_BucketForAddress
Find or allocate a bucket for an address
================
*/
#include