ioq3quest/code/server/sv_ccmds.c
2017-07-24 20:55:14 -05:00

1599 lines
34 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code 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 of the License,
or (at your option) any later version.
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "server.h"
/*
===============================================================================
OPERATOR CONSOLE ONLY COMMANDS
These commands can only be entered from stdin or by a remote operator datagram
===============================================================================
*/
/*
==================
SV_GetPlayerByHandle
Returns the player with player id or name from Cmd_Argv(1)
==================
*/
static client_t *SV_GetPlayerByHandle( void ) {
client_t *cl;
int i;
char *s;
char cleanName[64];
// make sure server is running
if ( !com_sv_running->integer ) {
return NULL;
}
if ( Cmd_Argc() < 2 ) {
Com_Printf( "No player specified.\n" );
return NULL;
}
s = Cmd_Argv(1);
// Check whether this is a numeric player handle
for(i = 0; s[i] >= '0' && s[i] <= '9'; i++);
if(!s[i])
{
int plid = atoi(s);
// Check for numeric playerid match
if(plid >= 0 && plid < sv_maxclients->integer)
{
cl = &svs.clients[plid];
if(cl->state)
return cl;
}
}
// check for a name match
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if ( !Q_stricmp( cl->name, s ) ) {
return cl;
}
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
Q_CleanStr( cleanName );
if ( !Q_stricmp( cleanName, s ) ) {
return cl;
}
}
Com_Printf( "Player %s is not on the server\n", s );
return NULL;
}
/*
==================
SV_GetPlayerByNum
Returns the player with idnum from Cmd_Argv(1)
==================
*/
static client_t *SV_GetPlayerByNum( void ) {
client_t *cl;
int i;
int idnum;
char *s;
// make sure server is running
if ( !com_sv_running->integer ) {
return NULL;
}
if ( Cmd_Argc() < 2 ) {
Com_Printf( "No player specified.\n" );
return NULL;
}
s = Cmd_Argv(1);
for (i = 0; s[i]; i++) {
if (s[i] < '0' || s[i] > '9') {
Com_Printf( "Bad slot number: %s\n", s);
return NULL;
}
}
idnum = atoi( s );
if ( idnum < 0 || idnum >= sv_maxclients->integer ) {
Com_Printf( "Bad client slot: %i\n", idnum );
return NULL;
}
cl = &svs.clients[idnum];
if ( !cl->state ) {
Com_Printf( "Client %i is not active\n", idnum );
return NULL;
}
return cl;
}
//=========================================================
/*
==================
SV_Map_f
Restart the server on a different map
==================
*/
static void SV_Map_f( void ) {
char *cmd;
char *map;
qboolean killBots, cheat;
char expanded[MAX_QPATH];
char mapname[MAX_QPATH];
map = Cmd_Argv(1);
if ( !map ) {
return;
}
// make sure the level exists before trying to change, so that
// a typo at the server console won't end the game
Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
if ( FS_ReadFile (expanded, NULL) == -1 ) {
Com_Printf ("Can't find map %s\n", expanded);
return;
}
// force latched values to get set
Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH );
cmd = Cmd_Argv(0);
if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) {
Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER );
Cvar_SetValue( "g_doWarmup", 0 );
// may not set sv_maxclients directly, always set latched
Cvar_SetLatched( "sv_maxclients", "8" );
cmd += 2;
if (!Q_stricmp( cmd, "devmap" ) ) {
cheat = qtrue;
} else {
cheat = qfalse;
}
killBots = qtrue;
}
else {
if ( !Q_stricmp( cmd, "devmap" ) ) {
cheat = qtrue;
killBots = qtrue;
} else {
cheat = qfalse;
killBots = qfalse;
}
if( sv_gametype->integer == GT_SINGLE_PLAYER ) {
Cvar_SetValue( "g_gametype", GT_FFA );
}
}
// save the map name here cause on a map restart we reload the q3config.cfg
// and thus nuke the arguments of the map command
Q_strncpyz(mapname, map, sizeof(mapname));
// start up the map
SV_SpawnServer( mapname, killBots );
// set the cheat value
// if the level was started with "map <levelname>", then
// cheats will not be allowed. If started with "devmap <levelname>"
// then cheats will be allowed
if ( cheat ) {
Cvar_Set( "sv_cheats", "1" );
} else {
Cvar_Set( "sv_cheats", "0" );
}
}
/*
================
SV_MapRestart_f
Completely restarts a level, but doesn't send a new gamestate to the clients.
This allows fair starts with variable load times.
================
*/
static void SV_MapRestart_f( void ) {
int i;
client_t *client;
char *denied;
qboolean isBot;
int delay;
// make sure we aren't restarting twice in the same frame
if ( com_frameTime == sv.serverId ) {
return;
}
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( sv.restartTime ) {
return;
}
if (Cmd_Argc() > 1 ) {
delay = atoi( Cmd_Argv(1) );
}
else {
delay = 5;
}
if( delay && !Cvar_VariableValue("g_doWarmup") ) {
sv.restartTime = sv.time + delay * 1000;
SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) );
return;
}
// check for changes in variables that can't just be restarted
// check for maxclients change
if ( sv_maxclients->modified || sv_gametype->modified ) {
char mapname[MAX_QPATH];
Com_Printf( "variable change -- restarting.\n" );
// restart the map the slow way
Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) );
SV_SpawnServer( mapname, qfalse );
return;
}
// toggle the server bit so clients can detect that a
// map_restart has happened
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
// generate a new serverid
// TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart
sv.serverId = com_frameTime;
Cvar_Set( "sv_serverid", va("%i", sv.serverId ) );
// if a map_restart occurs while a client is changing maps, we need
// to give them the correct time so that when they finish loading
// they don't violate the backwards time check in cl_cgame.c
for (i=0 ; i<sv_maxclients->integer ; i++) {
if (svs.clients[i].state == CS_PRIMED) {
svs.clients[i].oldServerTime = sv.restartTime;
}
}
// reset all the vm data in place without changing memory allocation
// note that we do NOT set sv.state = SS_LOADING, so configstrings that
// had been changed from their default values will generate broadcast updates
sv.state = SS_LOADING;
sv.restarting = qtrue;
SV_RestartGameProgs();
// run a few frames to allow everything to settle
for (i = 0; i < 3; i++)
{
VM_Call (gvm, GAME_RUN_FRAME, sv.time);
sv.time += 100;
svs.time += 100;
}
sv.state = SS_GAME;
sv.restarting = qfalse;
// connect and begin all the clients
for (i=0 ; i<sv_maxclients->integer ; i++) {
client = &svs.clients[i];
// send the new gamestate to all connected clients
if ( client->state < CS_CONNECTED) {
continue;
}
if ( client->netchan.remoteAddress.type == NA_BOT ) {
isBot = qtrue;
} else {
isBot = qfalse;
}
// add the map_restart command
SV_AddServerCommand( client, "map_restart\n" );
// connect the client again, without the firstTime flag
denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) );
if ( denied ) {
// this generally shouldn't happen, because the client
// was connected before the level change
SV_DropClient( client, denied );
Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i );
continue;
}
if(client->state == CS_ACTIVE)
SV_ClientEnterWorld(client, &client->lastUsercmd);
else
{
// If we don't reset client->lastUsercmd and are restarting during map load,
// the client will hang because we'll use the last Usercmd from the previous map,
// which is wrong obviously.
SV_ClientEnterWorld(client, NULL);
}
}
// run another frame to allow things to look at all the players
VM_Call (gvm, GAME_RUN_FRAME, sv.time);
sv.time += 100;
svs.time += 100;
}
//===============================================================
/*
==================
SV_Kick_f
Kick a user off of the server
==================
*/
static void SV_Kick_f( void ) {
client_t *cl;
int i;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n");
return;
}
cl = SV_GetPlayerByHandle();
if ( !cl ) {
if ( !Q_stricmp(Cmd_Argv(1), "all") ) {
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
continue;
}
SV_DropClient( cl, "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) {
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type != NA_BOT ) {
continue;
}
SV_DropClient( cl, "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
return;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
Com_Printf("Cannot kick host player\n");
return;
}
SV_DropClient( cl, "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
/*
==================
SV_KickBots_f
Kick all bots off of the server
==================
*/
static void SV_KickBots_f( void ) {
client_t *cl;
int i;
// make sure server is running
if( !com_sv_running->integer ) {
Com_Printf("Server is not running.\n");
return;
}
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) {
if( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type != NA_BOT ) {
continue;
}
SV_DropClient( cl, "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
/*
==================
SV_KickAll_f
Kick all users off of the server
==================
*/
static void SV_KickAll_f( void ) {
client_t *cl;
int i;
// make sure server is running
if( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) {
if( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
continue;
}
SV_DropClient( cl, "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
/*
==================
SV_KickNum_f
Kick a user off of the server
==================
*/
static void SV_KickNum_f( void ) {
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: %s <client number>\n", Cmd_Argv(0));
return;
}
cl = SV_GetPlayerByNum();
if ( !cl ) {
return;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
Com_Printf("Cannot kick host player\n");
return;
}
SV_DropClient( cl, "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
#ifndef STANDALONE
// these functions require the auth server which of course is not available anymore for stand-alone games.
/*
==================
SV_Ban_f
Ban a user from being able to play on this server through the auth
server
==================
*/
static void SV_Ban_f( void ) {
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: banUser <player name>\n");
return;
}
cl = SV_GetPlayerByHandle();
if (!cl) {
return;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
Com_Printf("Cannot kick host player\n");
return;
}
// look up the authorize server's IP
if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP ) ) {
Com_Printf( "Couldn't resolve address\n" );
return;
}
svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
BigShort( svs.authorizeAddress.port ) );
}
// otherwise send their ip to the authorize server
if ( svs.authorizeAddress.type != NA_BAD ) {
NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
"banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
Com_Printf("%s was banned from coming back\n", cl->name);
}
}
/*
==================
SV_BanNum_f
Ban a user from being able to play on this server through the auth
server
==================
*/
static void SV_BanNum_f( void ) {
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: banClient <client number>\n");
return;
}
cl = SV_GetPlayerByNum();
if ( !cl ) {
return;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
Com_Printf("Cannot kick host player\n");
return;
}
// look up the authorize server's IP
if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP ) ) {
Com_Printf( "Couldn't resolve address\n" );
return;
}
svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
BigShort( svs.authorizeAddress.port ) );
}
// otherwise send their ip to the authorize server
if ( svs.authorizeAddress.type != NA_BAD ) {
NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
"banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
Com_Printf("%s was banned from coming back\n", cl->name);
}
}
#endif
/*
==================
SV_RehashBans_f
Load saved bans from file.
==================
*/
static void SV_RehashBans_f(void)
{
int index, filelen;
fileHandle_t readfrom;
char *textbuf, *curpos, *maskpos, *newlinepos, *endpos;
char filepath[MAX_QPATH];
// make sure server is running
if ( !com_sv_running->integer ) {
return;
}
serverBansCount = 0;
if(!sv_banFile->string || !*sv_banFile->string)
return;
Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
if((filelen = FS_SV_FOpenFileRead(filepath, &readfrom)) >= 0)
{
if(filelen < 2)
{
// Don't bother if file is too short.
FS_FCloseFile(readfrom);
return;
}
curpos = textbuf = Z_Malloc(filelen);
filelen = FS_Read(textbuf, filelen, readfrom);
FS_FCloseFile(readfrom);
endpos = textbuf + filelen;
for(index = 0; index < SERVER_MAXBANS && curpos + 2 < endpos; index++)
{
// find the end of the address string
for(maskpos = curpos + 2; maskpos < endpos && *maskpos != ' '; maskpos++);
if(maskpos + 1 >= endpos)
break;
*maskpos = '\0';
maskpos++;
// find the end of the subnet specifier
for(newlinepos = maskpos; newlinepos < endpos && *newlinepos != '\n'; newlinepos++);
if(newlinepos >= endpos)
break;
*newlinepos = '\0';
if(NET_StringToAdr(curpos + 2, &serverBans[index].ip, NA_UNSPEC))
{
serverBans[index].isexception = (curpos[0] != '0');
serverBans[index].subnet = atoi(maskpos);
if(serverBans[index].ip.type == NA_IP &&
(serverBans[index].subnet < 1 || serverBans[index].subnet > 32))
{
serverBans[index].subnet = 32;
}
else if(serverBans[index].ip.type == NA_IP6 &&
(serverBans[index].subnet < 1 || serverBans[index].subnet > 128))
{
serverBans[index].subnet = 128;
}
}
curpos = newlinepos + 1;
}
serverBansCount = index;
Z_Free(textbuf);
}
}
/*
==================
SV_WriteBans
Save bans to file.
==================
*/
static void SV_WriteBans(void)
{
int index;
fileHandle_t writeto;
char filepath[MAX_QPATH];
if(!sv_banFile->string || !*sv_banFile->string)
return;
Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
if((writeto = FS_SV_FOpenFileWrite(filepath)))
{
char writebuf[128];
serverBan_t *curban;
for(index = 0; index < serverBansCount; index++)
{
curban = &serverBans[index];
Com_sprintf(writebuf, sizeof(writebuf), "%d %s %d\n",
curban->isexception, NET_AdrToString(curban->ip), curban->subnet);
FS_Write(writebuf, strlen(writebuf), writeto);
}
FS_FCloseFile(writeto);
}
}
/*
==================
SV_DelBanEntryFromList
Remove a ban or an exception from the list.
==================
*/
static qboolean SV_DelBanEntryFromList(int index)
{
if(index == serverBansCount - 1)
serverBansCount--;
else if(index < ARRAY_LEN(serverBans) - 1)
{
memmove(serverBans + index, serverBans + index + 1, (serverBansCount - index - 1) * sizeof(*serverBans));
serverBansCount--;
}
else
return qtrue;
return qfalse;
}
/*
==================
SV_ParseCIDRNotation
Parse a CIDR notation type string and return a netadr_t and suffix by reference
==================
*/
static qboolean SV_ParseCIDRNotation(netadr_t *dest, int *mask, char *adrstr)
{
char *suffix;
suffix = strchr(adrstr, '/');
if(suffix)
{
*suffix = '\0';
suffix++;
}
if(!NET_StringToAdr(adrstr, dest, NA_UNSPEC))
return qtrue;
if(suffix)
{
*mask = atoi(suffix);
if(dest->type == NA_IP)
{
if(*mask < 1 || *mask > 32)
*mask = 32;
}
else
{
if(*mask < 1 || *mask > 128)
*mask = 128;
}
}
else if(dest->type == NA_IP)
*mask = 32;
else
*mask = 128;
return qfalse;
}
/*
==================
SV_AddBanToList
Ban a user from being able to play on this server based on his ip address.
==================
*/
static void SV_AddBanToList(qboolean isexception)
{
char *banstring;
char addy2[NET_ADDRSTRMAXLEN];
netadr_t ip;
int index, argc, mask;
serverBan_t *curban;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
argc = Cmd_Argc();
if(argc < 2 || argc > 3)
{
Com_Printf ("Usage: %s (ip[/subnet] | clientnum [subnet])\n", Cmd_Argv(0));
return;
}
if(serverBansCount >= ARRAY_LEN(serverBans))
{
Com_Printf ("Error: Maximum number of bans/exceptions exceeded.\n");
return;
}
banstring = Cmd_Argv(1);
if(strchr(banstring, '.') || strchr(banstring, ':'))
{
// This is an ip address, not a client num.
if(SV_ParseCIDRNotation(&ip, &mask, banstring))
{
Com_Printf("Error: Invalid address %s\n", banstring);
return;
}
}
else
{
client_t *cl;
// client num.
cl = SV_GetPlayerByNum();
if(!cl)
{
Com_Printf("Error: Playernum %s does not exist.\n", Cmd_Argv(1));
return;
}
ip = cl->netchan.remoteAddress;
if(argc == 3)
{
mask = atoi(Cmd_Argv(2));
if(ip.type == NA_IP)
{
if(mask < 1 || mask > 32)
mask = 32;
}
else
{
if(mask < 1 || mask > 128)
mask = 128;
}
}
else
mask = (ip.type == NA_IP6) ? 128 : 32;
}
if(ip.type != NA_IP && ip.type != NA_IP6)
{
Com_Printf("Error: Can ban players connected via the internet only.\n");
return;
}
// first check whether a conflicting ban exists that would supersede the new one.
for(index = 0; index < serverBansCount; index++)
{
curban = &serverBans[index];
if(curban->subnet <= mask)
{
if((curban->isexception || !isexception) && NET_CompareBaseAdrMask(curban->ip, ip, curban->subnet))
{
Q_strncpyz(addy2, NET_AdrToString(ip), sizeof(addy2));
Com_Printf("Error: %s %s/%d supersedes %s %s/%d\n", curban->isexception ? "Exception" : "Ban",
NET_AdrToString(curban->ip), curban->subnet,
isexception ? "exception" : "ban", addy2, mask);
return;
}
}
if(curban->subnet >= mask)
{
if(!curban->isexception && isexception && NET_CompareBaseAdrMask(curban->ip, ip, mask))
{
Q_strncpyz(addy2, NET_AdrToString(curban->ip), sizeof(addy2));
Com_Printf("Error: %s %s/%d supersedes already existing %s %s/%d\n", isexception ? "Exception" : "Ban",
NET_AdrToString(ip), mask,
curban->isexception ? "exception" : "ban", addy2, curban->subnet);
return;
}
}
}
// now delete bans that are superseded by the new one
index = 0;
while(index < serverBansCount)
{
curban = &serverBans[index];
if(curban->subnet > mask && (!curban->isexception || isexception) && NET_CompareBaseAdrMask(curban->ip, ip, mask))
SV_DelBanEntryFromList(index);
else
index++;
}
serverBans[serverBansCount].ip = ip;
serverBans[serverBansCount].subnet = mask;
serverBans[serverBansCount].isexception = isexception;
serverBansCount++;
SV_WriteBans();
Com_Printf("Added %s: %s/%d\n", isexception ? "ban exception" : "ban",
NET_AdrToString(ip), mask);
}
/*
==================
SV_DelBanFromList
Remove a ban or an exception from the list.
==================
*/
static void SV_DelBanFromList(qboolean isexception)
{
int index, count = 0, todel, mask;
netadr_t ip;
char *banstring;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if(Cmd_Argc() != 2)
{
Com_Printf ("Usage: %s (ip[/subnet] | num)\n", Cmd_Argv(0));
return;
}
banstring = Cmd_Argv(1);
if(strchr(banstring, '.') || strchr(banstring, ':'))
{
serverBan_t *curban;
if(SV_ParseCIDRNotation(&ip, &mask, banstring))
{
Com_Printf("Error: Invalid address %s\n", banstring);
return;
}
index = 0;
while(index < serverBansCount)
{
curban = &serverBans[index];
if(curban->isexception == isexception &&
curban->subnet >= mask &&
NET_CompareBaseAdrMask(curban->ip, ip, mask))
{
Com_Printf("Deleting %s %s/%d\n",
isexception ? "exception" : "ban",
NET_AdrToString(curban->ip), curban->subnet);
SV_DelBanEntryFromList(index);
}
else
index++;
}
}
else
{
todel = atoi(Cmd_Argv(1));
if(todel < 1 || todel > serverBansCount)
{
Com_Printf("Error: Invalid ban number given\n");
return;
}
for(index = 0; index < serverBansCount; index++)
{
if(serverBans[index].isexception == isexception)
{
count++;
if(count == todel)
{
Com_Printf("Deleting %s %s/%d\n",
isexception ? "exception" : "ban",
NET_AdrToString(serverBans[index].ip), serverBans[index].subnet);
SV_DelBanEntryFromList(index);
break;
}
}
}
}
SV_WriteBans();
}
/*
==================
SV_ListBans_f
List all bans and exceptions on console
==================
*/
static void SV_ListBans_f(void)
{
int index, count;
serverBan_t *ban;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
// List all bans
for(index = count = 0; index < serverBansCount; index++)
{
ban = &serverBans[index];
if(!ban->isexception)
{
count++;
Com_Printf("Ban #%d: %s/%d\n", count,
NET_AdrToString(ban->ip), ban->subnet);
}
}
// List all exceptions
for(index = count = 0; index < serverBansCount; index++)
{
ban = &serverBans[index];
if(ban->isexception)
{
count++;
Com_Printf("Except #%d: %s/%d\n", count,
NET_AdrToString(ban->ip), ban->subnet);
}
}
}
/*
==================
SV_FlushBans_f
Delete all bans and exceptions.
==================
*/
static void SV_FlushBans_f(void)
{
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
serverBansCount = 0;
// empty the ban file.
SV_WriteBans();
Com_Printf("All bans and exceptions have been deleted.\n");
}
static void SV_BanAddr_f(void)
{
SV_AddBanToList(qfalse);
}
static void SV_ExceptAddr_f(void)
{
SV_AddBanToList(qtrue);
}
static void SV_BanDel_f(void)
{
SV_DelBanFromList(qfalse);
}
static void SV_ExceptDel_f(void)
{
SV_DelBanFromList(qtrue);
}
/*
** SV_Strlen -- skips color escape codes
*/
static int SV_Strlen( const char *str ) {
const char *s = str;
int count = 0;
while ( *s ) {
if ( Q_IsColorString( s ) ) {
s += 2;
} else {
count++;
s++;
}
}
return count;
}
/*
================
SV_Status_f
================
*/
static void SV_Status_f( void ) {
int i, j, l;
client_t *cl;
playerState_t *ps;
const char *s;
int ping;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
Com_Printf ("map: %s\n", sv_mapname->string );
Com_Printf ("cl score ping name address rate \n");
Com_Printf ("-- ----- ---- --------------- --------------------------------------- -----\n");
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++)
{
if (!cl->state)
continue;
Com_Printf ("%2i ", i);
ps = SV_GameClientNum( i );
Com_Printf ("%5i ", ps->persistant[PERS_SCORE]);
if (cl->state == CS_CONNECTED)
Com_Printf ("CON ");
else if (cl->state == CS_ZOMBIE)
Com_Printf ("ZMB ");
else
{
ping = cl->ping < 9999 ? cl->ping : 9999;
Com_Printf ("%4i ", ping);
}
Com_Printf ("%s", cl->name);
l = 16 - SV_Strlen(cl->name);
j = 0;
do
{
Com_Printf (" ");
j++;
} while(j < l);
// TTimo adding a ^7 to reset the color
s = NET_AdrToString( cl->netchan.remoteAddress );
Com_Printf ("^7%s", s);
l = 39 - strlen(s);
j = 0;
do
{
Com_Printf(" ");
j++;
} while(j < l);
Com_Printf (" %5i", cl->rate);
Com_Printf ("\n");
}
Com_Printf ("\n");
}
/*
==================
SV_ConSay_f
==================
*/
static void SV_ConSay_f(void) {
char *p;
char text[1024];
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc () < 2 ) {
return;
}
strcpy (text, "console: ");
p = Cmd_Args();
if ( *p == '"' ) {
p++;
p[strlen(p)-1] = 0;
}
strcat(text, p);
Com_Printf("%s\n", text);
SV_SendServerCommand(NULL, "chat \"%s\"", text);
}
/*
==================
SV_ConTell_f
==================
*/
static void SV_ConTell_f(void) {
char *p;
char text[1024];
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() < 3 ) {
Com_Printf ("Usage: tell <client number> <text>\n");
return;
}
cl = SV_GetPlayerByNum();
if ( !cl ) {
return;
}
strcpy (text, "console_tell: ");
p = Cmd_ArgsFrom(2);
if ( *p == '"' ) {
p++;
p[strlen(p)-1] = 0;
}
strcat(text, p);
Com_Printf("%s\n", text);
SV_SendServerCommand(cl, "chat \"%s\"", text);
}
/*
==================
SV_ConSayto_f
==================
*/
static void SV_ConSayto_f(void) {
char *p;
char text[1024];
client_t *cl;
char *rawname;
char name[MAX_NAME_LENGTH];
char cleanName[MAX_NAME_LENGTH];
client_t *saytocl;
int i;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() < 3 ) {
Com_Printf ("Usage: sayto <player name> <text>\n");
return;
}
rawname = Cmd_Argv(1);
//allowing special characters in the console
//with hex strings for player names
Com_FieldStringToPlayerName( name, MAX_NAME_LENGTH, rawname );
saytocl = NULL;
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
Q_CleanStr( cleanName );
if ( !Q_stricmp( cleanName, name ) ) {
saytocl = cl;
break;
}
}
if( !saytocl )
{
Com_Printf ("No such player name: %s.\n", name);
return;
}
strcpy (text, "console_sayto: ");
p = Cmd_ArgsFrom(2);
if ( *p == '"' ) {
p++;
p[strlen(p)-1] = 0;
}
strcat(text, p);
Com_Printf("%s\n", text);
SV_SendServerCommand(saytocl, "chat \"%s\"", text);
}
/*
==================
SV_Heartbeat_f
Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer
==================
*/
void SV_Heartbeat_f( void ) {
svs.nextHeartbeatTime = -9999999;
}
/*
===========
SV_Serverinfo_f
Examine the serverinfo string
===========
*/
static void SV_Serverinfo_f( void ) {
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
Com_Printf ("Server info settings:\n");
Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) );
}
/*
===========
SV_Systeminfo_f
Examine the systeminfo string
===========
*/
static void SV_Systeminfo_f( void ) {
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
Com_Printf ("System info settings:\n");
Info_Print ( Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
}
/*
===========
SV_DumpUser_f
Examine all a users info strings
===========
*/
static void SV_DumpUser_f( void ) {
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: dumpuser <userid>\n");
return;
}
cl = SV_GetPlayerByHandle();
if ( !cl ) {
return;
}
Com_Printf( "userinfo\n" );
Com_Printf( "--------\n" );
Info_Print( cl->userinfo );
}
/*
=================
SV_KillServer
=================
*/
static void SV_KillServer_f( void ) {
SV_Shutdown( "killserver" );
}
//===========================================================
/*
==================
SV_CompleteMapName
==================
*/
static void SV_CompleteMapName( char *args, int argNum ) {
if( argNum == 2 ) {
Field_CompleteFilename( "maps", "bsp", qtrue, qfalse );
}
}
/*
==================
SV_CompletePlayerName
==================
*/
static void SV_CompletePlayerName( char *args, int argNum ) {
if( argNum == 2 ) {
char names[MAX_CLIENTS][MAX_NAME_LENGTH];
const char *namesPtr[MAX_CLIENTS];
client_t *cl;
int i;
int nameCount;
int clientCount;
nameCount = 0;
clientCount = sv_maxclients->integer;
for ( i=0, cl=svs.clients ; i < clientCount; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if( i >= MAX_CLIENTS ) {
break;
}
Q_strncpyz( names[nameCount], cl->name, sizeof(names[nameCount]) );
Q_CleanStr( names[nameCount] );
namesPtr[nameCount] = names[nameCount];
nameCount++;
}
qsort( (void*)namesPtr, nameCount, sizeof( namesPtr[0] ), Com_strCompare );
Field_CompletePlayerName( namesPtr, nameCount );
}
}
/*
==================
SV_AddOperatorCommands
==================
*/
void SV_AddOperatorCommands( void ) {
static qboolean initialized;
if ( initialized ) {
return;
}
initialized = qtrue;
Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);
Cmd_AddCommand ("kick", SV_Kick_f);
#ifndef STANDALONE
if(!com_standalone->integer)
{
Cmd_AddCommand ("banUser", SV_Ban_f);
Cmd_AddCommand ("banClient", SV_BanNum_f);
}
#endif
Cmd_AddCommand ("kickbots", SV_KickBots_f);
Cmd_AddCommand ("kickall", SV_KickAll_f);
Cmd_AddCommand ("kicknum", SV_KickNum_f);
Cmd_AddCommand ("clientkick", SV_KickNum_f); // Legacy command
Cmd_AddCommand ("status", SV_Status_f);
Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
Cmd_AddCommand ("systeminfo", SV_Systeminfo_f);
Cmd_AddCommand ("dumpuser", SV_DumpUser_f);
Cmd_AddCommand ("map_restart", SV_MapRestart_f);
Cmd_AddCommand ("sectorlist", SV_SectorList_f);
Cmd_AddCommand ("map", SV_Map_f);
Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName );
#ifndef PRE_RELEASE_DEMO
Cmd_AddCommand ("devmap", SV_Map_f);
Cmd_SetCommandCompletionFunc( "devmap", SV_CompleteMapName );
Cmd_AddCommand ("spmap", SV_Map_f);
Cmd_SetCommandCompletionFunc( "spmap", SV_CompleteMapName );
Cmd_AddCommand ("spdevmap", SV_Map_f);
Cmd_SetCommandCompletionFunc( "spdevmap", SV_CompleteMapName );
#endif
Cmd_AddCommand ("killserver", SV_KillServer_f);
if( com_dedicated->integer ) {
Cmd_AddCommand ("say", SV_ConSay_f);
Cmd_AddCommand ("tell", SV_ConTell_f);
Cmd_AddCommand ("sayto", SV_ConSayto_f);
Cmd_SetCommandCompletionFunc( "sayto", SV_CompletePlayerName );
}
Cmd_AddCommand("rehashbans", SV_RehashBans_f);
Cmd_AddCommand("listbans", SV_ListBans_f);
Cmd_AddCommand("banaddr", SV_BanAddr_f);
Cmd_AddCommand("exceptaddr", SV_ExceptAddr_f);
Cmd_AddCommand("bandel", SV_BanDel_f);
Cmd_AddCommand("exceptdel", SV_ExceptDel_f);
Cmd_AddCommand("flushbans", SV_FlushBans_f);
}
/*
==================
SV_RemoveOperatorCommands
==================
*/
void SV_RemoveOperatorCommands( void ) {
#if 0
// removing these won't let the server start again
Cmd_RemoveCommand ("heartbeat");
Cmd_RemoveCommand ("kick");
Cmd_RemoveCommand ("kicknum");
Cmd_RemoveCommand ("clientkick");
Cmd_RemoveCommand ("kickall");
Cmd_RemoveCommand ("kickbots");
Cmd_RemoveCommand ("banUser");
Cmd_RemoveCommand ("banClient");
Cmd_RemoveCommand ("status");
Cmd_RemoveCommand ("serverinfo");
Cmd_RemoveCommand ("systeminfo");
Cmd_RemoveCommand ("dumpuser");
Cmd_RemoveCommand ("map_restart");
Cmd_RemoveCommand ("sectorlist");
Cmd_RemoveCommand ("say");
#endif
}