mirror of
https://git.code.sf.net/p/quake/quake2forge
synced 2024-12-12 13:42:21 +00:00
7457b38f1e
fix sv_user.c so that macro expansion isn't used on the server side, otherwise clients can potentially read rcon_password among others * Forgot to add net_udp6.c from the last relnev merge... oops.
667 lines
15 KiB
C
667 lines
15 KiB
C
/*
|
|
Copyright (C) 1997-2001 Id Software, 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
|
|
of the License, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
// sv_user.c -- server code for moving users
|
|
|
|
#include "server.h"
|
|
|
|
edict_t *sv_player;
|
|
|
|
/*
|
|
============================================================
|
|
|
|
USER STRINGCMD EXECUTION
|
|
|
|
sv_client and sv_player will be valid.
|
|
============================================================
|
|
*/
|
|
|
|
/*
|
|
==================
|
|
SV_BeginDemoServer
|
|
==================
|
|
*/
|
|
void SV_BeginDemoserver (void)
|
|
{
|
|
char name[MAX_OSPATH];
|
|
|
|
Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
|
|
FS_FOpenFile (name, &sv.demofile);
|
|
if (!sv.demofile)
|
|
Com_Error (ERR_DROP, "Couldn't open %s\n", name);
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_New_f
|
|
|
|
Sends the first message from the server to a connected client.
|
|
This will be sent on the initial connection and upon each server load.
|
|
================
|
|
*/
|
|
void SV_New_f (void)
|
|
{
|
|
char *gamedir;
|
|
int playernum;
|
|
edict_t *ent;
|
|
|
|
Com_DPrintf ("New() from %s\n", sv_client->name);
|
|
|
|
if (sv_client->state != cs_connected)
|
|
{
|
|
Com_Printf ("New not valid -- already spawned\n");
|
|
return;
|
|
}
|
|
|
|
// demo servers just dump the file message
|
|
if (sv.state == ss_demo)
|
|
{
|
|
SV_BeginDemoserver ();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// serverdata needs to go over for all types of servers
|
|
// to make sure the protocol is right, and to set the gamedir
|
|
//
|
|
gamedir = Cvar_VariableString ("gamedir");
|
|
|
|
// send the serverdata
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
|
|
MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
|
|
MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
|
|
MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
|
|
MSG_WriteString (&sv_client->netchan.message, gamedir);
|
|
|
|
if (sv.state == ss_cinematic || sv.state == ss_pic)
|
|
playernum = -1;
|
|
else
|
|
playernum = sv_client - svs.clients;
|
|
MSG_WriteShort (&sv_client->netchan.message, playernum);
|
|
|
|
// send full levelname
|
|
MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);
|
|
|
|
//
|
|
// game server
|
|
//
|
|
if (sv.state == ss_game)
|
|
{
|
|
// set up the entity for the client
|
|
ent = EDICT_NUM(playernum+1);
|
|
ent->s.number = playernum+1;
|
|
sv_client->edict = ent;
|
|
memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
|
|
|
|
// begin fetching configstrings
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Configstrings_f
|
|
==================
|
|
*/
|
|
void SV_Configstrings_f (void)
|
|
{
|
|
int start;
|
|
|
|
Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
|
|
|
|
if (sv_client->state != cs_connected)
|
|
{
|
|
Com_Printf ("configstrings not valid -- already spawned\n");
|
|
return;
|
|
}
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Com_Printf ("SV_Configstrings_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
start = atoi(Cmd_Argv(2));
|
|
|
|
// write a packet full of data
|
|
|
|
while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
|
|
&& start < MAX_CONFIGSTRINGS)
|
|
{
|
|
if (sv.configstrings[start][0])
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
|
|
MSG_WriteShort (&sv_client->netchan.message, start);
|
|
MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
|
|
}
|
|
start++;
|
|
}
|
|
|
|
// send next command
|
|
|
|
if (start == MAX_CONFIGSTRINGS)
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Baselines_f
|
|
==================
|
|
*/
|
|
void SV_Baselines_f (void)
|
|
{
|
|
int start;
|
|
entity_state_t nullstate;
|
|
entity_state_t *base;
|
|
|
|
Com_DPrintf ("Baselines() from %s\n", sv_client->name);
|
|
|
|
if (sv_client->state != cs_connected)
|
|
{
|
|
Com_Printf ("baselines not valid -- already spawned\n");
|
|
return;
|
|
}
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Com_Printf ("SV_Baselines_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
start = atoi(Cmd_Argv(2));
|
|
|
|
memset (&nullstate, 0, sizeof(nullstate));
|
|
|
|
// write a packet full of data
|
|
|
|
while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
|
|
&& start < MAX_EDICTS)
|
|
{
|
|
base = &sv.baselines[start];
|
|
if (base->modelindex || base->sound || base->effects)
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
|
|
MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
|
|
}
|
|
start++;
|
|
}
|
|
|
|
// send next command
|
|
|
|
if (start == MAX_EDICTS)
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Begin_f
|
|
==================
|
|
*/
|
|
void SV_Begin_f (void)
|
|
{
|
|
Com_DPrintf ("Begin() from %s\n", sv_client->name);
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Com_Printf ("SV_Begin_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
sv_client->state = cs_spawned;
|
|
|
|
// call the game begin function
|
|
ge->ClientBegin (sv_player);
|
|
|
|
Cbuf_InsertFromDefer ();
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
==================
|
|
SV_NextDownload_f
|
|
==================
|
|
*/
|
|
void SV_NextDownload_f (void)
|
|
{
|
|
int r;
|
|
int percent;
|
|
int size;
|
|
|
|
if (!sv_client->download)
|
|
return;
|
|
|
|
r = sv_client->downloadsize - sv_client->downloadcount;
|
|
if (r > 1024)
|
|
r = 1024;
|
|
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_download);
|
|
MSG_WriteShort (&sv_client->netchan.message, r);
|
|
|
|
sv_client->downloadcount += r;
|
|
size = sv_client->downloadsize;
|
|
if (!size)
|
|
size = 1;
|
|
percent = sv_client->downloadcount*100/size;
|
|
MSG_WriteByte (&sv_client->netchan.message, percent);
|
|
SZ_Write (&sv_client->netchan.message,
|
|
sv_client->download + sv_client->downloadcount - r, r);
|
|
|
|
if (sv_client->downloadcount != sv_client->downloadsize)
|
|
return;
|
|
|
|
FS_FreeFile (sv_client->download);
|
|
sv_client->download = NULL;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_BeginDownload_f
|
|
==================
|
|
*/
|
|
void SV_BeginDownload_f(void)
|
|
{
|
|
char *name;
|
|
extern cvar_t *allow_download;
|
|
extern cvar_t *allow_download_players;
|
|
extern cvar_t *allow_download_models;
|
|
extern cvar_t *allow_download_sounds;
|
|
extern cvar_t *allow_download_maps;
|
|
extern int file_from_pak; // ZOID did file come from pak?
|
|
int offset = 0;
|
|
|
|
name = Cmd_Argv(1);
|
|
|
|
if (Cmd_Argc() > 2)
|
|
offset = atoi(Cmd_Argv(2)); // downloaded offset
|
|
|
|
// hacked by zoid to allow more conrol over download
|
|
// first off, no .. or global allow check
|
|
if (strstr (name, "..") || !allow_download->value
|
|
// leading dot is no good
|
|
|| *name == '.'
|
|
// leading slash bad as well, must be in subdir
|
|
|| *name == '/'
|
|
// next up, skin check
|
|
|| (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
|
|
// now models
|
|
|| (strncmp(name, "models/", 6) == 0 && !allow_download_models->value)
|
|
// now sounds
|
|
|| (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
|
|
// now maps (note special case for maps, must not be in pak)
|
|
|| (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value)
|
|
// MUST be in a subdirectory
|
|
|| !strstr (name, "/") )
|
|
{ // don't allow anything with .. path
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_download);
|
|
MSG_WriteShort (&sv_client->netchan.message, -1);
|
|
MSG_WriteByte (&sv_client->netchan.message, 0);
|
|
return;
|
|
}
|
|
|
|
|
|
if (sv_client->download)
|
|
FS_FreeFile (sv_client->download);
|
|
|
|
sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
|
|
sv_client->downloadcount = offset;
|
|
|
|
if (offset > sv_client->downloadsize)
|
|
sv_client->downloadcount = sv_client->downloadsize;
|
|
|
|
if (!sv_client->download
|
|
// special check for maps, if it came from a pak file, don't allow
|
|
// download ZOID
|
|
|| (strncmp(name, "maps/", 5) == 0 && file_from_pak))
|
|
{
|
|
Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
|
|
if (sv_client->download) {
|
|
FS_FreeFile (sv_client->download);
|
|
sv_client->download = NULL;
|
|
}
|
|
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_download);
|
|
MSG_WriteShort (&sv_client->netchan.message, -1);
|
|
MSG_WriteByte (&sv_client->netchan.message, 0);
|
|
return;
|
|
}
|
|
|
|
SV_NextDownload_f ();
|
|
Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_Disconnect_f
|
|
|
|
The client is going to disconnect, so remove the connection immediately
|
|
=================
|
|
*/
|
|
void SV_Disconnect_f (void)
|
|
{
|
|
// SV_EndRedirect ();
|
|
SV_DropClient (sv_client);
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SV_ShowServerinfo_f
|
|
|
|
Dumps the serverinfo info string
|
|
==================
|
|
*/
|
|
void SV_ShowServerinfo_f (void)
|
|
{
|
|
Info_Print (Cvar_Serverinfo());
|
|
}
|
|
|
|
|
|
void SV_Nextserver (void)
|
|
{
|
|
char *v;
|
|
|
|
//ZOID, ss_pic can be nextserver'd in coop mode
|
|
if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
|
|
return; // can't nextserver while playing a normal game
|
|
|
|
svs.spawncount++; // make sure another doesn't sneak in
|
|
v = Cvar_VariableString ("nextserver");
|
|
if (!v[0])
|
|
Cbuf_AddText ("killserver\n");
|
|
else
|
|
{
|
|
Cbuf_AddText (v);
|
|
Cbuf_AddText ("\n");
|
|
}
|
|
Cvar_Set ("nextserver","");
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Nextserver_f
|
|
|
|
A cinematic has completed or been aborted by a client, so move
|
|
to the next server,
|
|
==================
|
|
*/
|
|
void SV_Nextserver_f (void)
|
|
{
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount ) {
|
|
Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
|
|
return; // leftover from last server
|
|
}
|
|
|
|
Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
|
|
|
|
SV_Nextserver ();
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
char *name;
|
|
void (*func) (void);
|
|
} ucmd_t;
|
|
|
|
ucmd_t ucmds[] =
|
|
{
|
|
// auto issued
|
|
{"new", SV_New_f},
|
|
{"configstrings", SV_Configstrings_f},
|
|
{"baselines", SV_Baselines_f},
|
|
{"begin", SV_Begin_f},
|
|
|
|
{"nextserver", SV_Nextserver_f},
|
|
|
|
{"disconnect", SV_Disconnect_f},
|
|
|
|
// issued by hand at client consoles
|
|
{"info", SV_ShowServerinfo_f},
|
|
|
|
{"download", SV_BeginDownload_f},
|
|
{"nextdl", SV_NextDownload_f},
|
|
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/*
|
|
==================
|
|
SV_ExecuteUserCommand
|
|
==================
|
|
*/
|
|
void SV_ExecuteUserCommand (char *s)
|
|
{
|
|
ucmd_t *u;
|
|
|
|
/* http://www.quakesrc.org/forum/topicDisplay.php?topicID=160
|
|
* the client can read the rcon_password variable, among others
|
|
* so don't do any macro expansion on the server side */
|
|
Cmd_TokenizeString(s, false);
|
|
sv_player = sv_client->edict;
|
|
|
|
// SV_BeginRedirect (RD_CLIENT);
|
|
|
|
for (u=ucmds ; u->name ; u++)
|
|
if (!strcmp (Cmd_Argv(0), u->name) )
|
|
{
|
|
u->func ();
|
|
break;
|
|
}
|
|
|
|
if (!u->name && sv.state == ss_game)
|
|
ge->ClientCommand (sv_player);
|
|
|
|
// SV_EndRedirect ();
|
|
}
|
|
|
|
/*
|
|
===========================================================================
|
|
|
|
USER CMD EXECUTION
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
|
|
|
|
void SV_ClientThink (client_t *cl, usercmd_t *cmd)
|
|
|
|
{
|
|
cl->commandMsec -= cmd->msec;
|
|
|
|
if (cl->commandMsec < 0 && sv_enforcetime->value )
|
|
{
|
|
Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
|
|
return;
|
|
}
|
|
|
|
ge->ClientThink (cl->edict, cmd);
|
|
}
|
|
|
|
|
|
|
|
#define MAX_STRINGCMDS 8
|
|
/*
|
|
===================
|
|
SV_ExecuteClientMessage
|
|
|
|
The current net_message is parsed for the given client
|
|
===================
|
|
*/
|
|
void SV_ExecuteClientMessage (client_t *cl)
|
|
{
|
|
int c;
|
|
char *s;
|
|
|
|
usercmd_t nullcmd;
|
|
usercmd_t oldest, oldcmd, newcmd;
|
|
int net_drop;
|
|
int stringCmdCount;
|
|
int checksum, calculatedChecksum;
|
|
int checksumIndex;
|
|
qboolean move_issued;
|
|
int lastframe;
|
|
|
|
sv_client = cl;
|
|
sv_player = sv_client->edict;
|
|
|
|
// only allow one move command
|
|
move_issued = false;
|
|
stringCmdCount = 0;
|
|
|
|
while (1)
|
|
{
|
|
if (net_message.readcount > net_message.cursize)
|
|
{
|
|
Com_Printf ("SV_ReadClientMessage: badread\n");
|
|
SV_DropClient (cl);
|
|
return;
|
|
}
|
|
|
|
c = MSG_ReadByte (&net_message);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c)
|
|
{
|
|
default:
|
|
Com_Printf ("SV_ReadClientMessage: unknown command char\n");
|
|
SV_DropClient (cl);
|
|
return;
|
|
|
|
case clc_nop:
|
|
break;
|
|
|
|
case clc_userinfo:
|
|
strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
|
|
SV_UserinfoChanged (cl);
|
|
break;
|
|
|
|
case clc_move:
|
|
if (move_issued)
|
|
return; // someone is trying to cheat...
|
|
|
|
move_issued = true;
|
|
checksumIndex = net_message.readcount;
|
|
checksum = MSG_ReadByte (&net_message);
|
|
lastframe = MSG_ReadLong (&net_message);
|
|
if (lastframe != cl->lastframe) {
|
|
cl->lastframe = lastframe;
|
|
if (cl->lastframe > 0) {
|
|
cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] =
|
|
svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
|
|
}
|
|
}
|
|
|
|
memset (&nullcmd, 0, sizeof(nullcmd));
|
|
MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
|
|
MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
|
|
MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
|
|
|
|
if ( cl->state != cs_spawned )
|
|
{
|
|
cl->lastframe = -1;
|
|
break;
|
|
}
|
|
|
|
// if the checksum fails, ignore the rest of the packet
|
|
calculatedChecksum = COM_BlockSequenceCRCByte (
|
|
net_message.data + checksumIndex + 1,
|
|
net_message.readcount - checksumIndex - 1,
|
|
cl->netchan.incoming_sequence);
|
|
|
|
if (calculatedChecksum != checksum)
|
|
{
|
|
Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n",
|
|
cl->name, calculatedChecksum, checksum,
|
|
cl->netchan.incoming_sequence);
|
|
return;
|
|
}
|
|
|
|
if (!sv_paused->value)
|
|
{
|
|
net_drop = cl->netchan.dropped;
|
|
if (net_drop < 20)
|
|
{
|
|
|
|
//if (net_drop > 2)
|
|
|
|
// Com_Printf ("drop %i\n", net_drop);
|
|
while (net_drop > 2)
|
|
{
|
|
SV_ClientThink (cl, &cl->lastcmd);
|
|
|
|
net_drop--;
|
|
}
|
|
if (net_drop > 1)
|
|
SV_ClientThink (cl, &oldest);
|
|
|
|
if (net_drop > 0)
|
|
SV_ClientThink (cl, &oldcmd);
|
|
|
|
}
|
|
SV_ClientThink (cl, &newcmd);
|
|
}
|
|
|
|
cl->lastcmd = newcmd;
|
|
break;
|
|
|
|
case clc_stringcmd:
|
|
s = MSG_ReadString (&net_message);
|
|
|
|
// malicious users may try using too many string commands
|
|
if (++stringCmdCount < MAX_STRINGCMDS)
|
|
SV_ExecuteUserCommand (s);
|
|
|
|
if (cl->state == cs_zombie)
|
|
return; // disconnect command
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|