I've prepared a patch that fixes several critical vulnerabilities in
Quake2Forge I have found so far, as well as some other minor bugs. All
security issues described in [1], exept of #5 (Fake Clients DoS), were
fixed, plus some additional ones.
This doesn't mean however that client side is secure now. I haven't
carefully studied the menu code yet (which is rather poorly written and
may contain buffer overflow vulnerabilities), as well as renderer
libraries. Hosting a dedicated quake2 server should be (hopefully) safe now.
This patch also addresses the following non-critical bugs:
1. Compilation errors on systems with that have neither HAVE_XF86_DGA
nor HAVE_XF86_VIDMODE defined. GCC reports warnings about unused
variables, preventing executable from compiling with -Wall flag set.
2. Empty packets from server being written to demofile with invalid
'length' field. This makes client demos unplayable.
3. Problems with configuration files that do not terminate with newline.
4. Long level load time due to unnecessary 10 fps clamp being applied
during client connection process. Server was also tweaked to immediately
respond to requests from connecting clients. This improves gaming
experience a lot on fast machines with fast network connection, while
still preventing low bandwidth connections from overflowing due to
round-trip nature of initial connection sequence.
Reference:
[1] - http://secur1ty.net/advisories/001
--
Andrei Nazarov
diff -ur quake2-0.3-orig/src/cl_ents.c quake2-0.3/src/cl_ents.c
--- quake2-0.3-orig/src/cl_ents.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/cl_ents.c 2005-08-17 20:27:27.000000000 +0400
@@ -413,7 +413,7 @@
while (1)
{
newnum = CL_ParseEntityBits (&bits);
- if (newnum >= MAX_EDICTS)
+ if (newnum < 0 || newnum >= MAX_EDICTS)
Com_Error (ERR_DROP,"CL_ParsePacketEntities: bad number:%i", newnum);
if (net_message.readcount > net_message.cursize)
diff -ur quake2-0.3-orig/src/cl_main.c quake2-0.3/src/cl_main.c
--- quake2-0.3-orig/src/cl_main.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/cl_main.c 2005-08-18 17:41:33.000000000 +0400
@@ -117,9 +117,11 @@
// the first eight bytes are just packet sequencing stuff
len = net_message.cursize-8;
- swlen = LittleLong(len);
- fwrite (&swlen, 4, 1, cls.demofile);
- fwrite (net_message.data+8, len, 1, cls.demofile);
+ if( len > 0 ) {
+ swlen = LittleLong(len);
+ fwrite (&swlen, 4, 1, cls.demofile);
+ fwrite (net_message.data+8, len, 1, cls.demofile);
+ }
}
@@ -308,7 +310,7 @@
if ( argc > 2 )
{
- char buffer[1000];
+ char buffer[MAX_STRING_CHARS];
int i;
strcpy( buffer, Cmd_Argv(1) );
@@ -535,8 +537,7 @@
*/
void CL_Rcon_f (void)
{
- char message[1024];
- int i;
+ char message[MAX_STRING_CHARS];
netadr_t to;
if (!rcon_client_password->string)
@@ -548,24 +549,9 @@
memset(&to, 0, sizeof(to));
- message[0] = (char)255;
- message[1] = (char)255;
- message[2] = (char)255;
- message[3] = (char)255;
- message[4] = 0;
-
NET_Config (true); // allow remote
-
- strcat (message, "rcon ");
-
- strcat (message, rcon_client_password->string);
- strcat (message, " ");
-
- for (i=1 ; i<Cmd_Argc() ; i++)
- {
- strcat (message, Cmd_Argv(i));
- strcat (message, " ");
- }
+
+ Com_sprintf( message, sizeof( message ), "rcon %s %s ", rcon_client_password->string, Cmd_Args() );
if (cls.state >= ca_connected)
to = cls.netchan.remote_address;
@@ -584,7 +570,7 @@
to.port = BigShort (PORT_SERVER);
}
- NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);
+ Netchan_OutOfBand (NS_CLIENT, to, strlen(message)+1, message);
}
@@ -660,6 +646,7 @@
fclose(cls.download);
cls.download = NULL;
}
+ cls.downloadname[0] = 0;
cls.state = ca_disconnected;
}
@@ -679,6 +666,7 @@
Contents allows \n escape character
====================
*/
+/*
void CL_Packet_f (void)
{
char send[2048];
@@ -721,6 +709,7 @@
NET_SendPacket (NS_CLIENT, out-send, send, adr);
}
+*/
/*
=================
@@ -1062,7 +1051,7 @@
void CL_FixUpGender(void)
{
char *p;
- char sk[80];
+ char sk[MAX_QPATH];
if (gender_auto->value) {
@@ -1072,7 +1061,7 @@
return;
}
- strncpy(sk, skin->string, sizeof(sk) - 1);
+ Q_strncpyz(sk, skin->string, sizeof(sk));
if ((p = strchr(sk, '/')) != NULL)
*p = 0;
if (Q_stricmp(sk, "male") == 0 || Q_stricmp(sk, "cyborg") == 0)
@@ -1364,7 +1353,7 @@
while (precache_tex < numtexinfo) {
char fn[MAX_OSPATH];
- sprintf(fn, "textures/%s.wal", map_surfaces[precache_tex++].rname);
+ Com_sprintf(fn, sizeof( fn ), "textures/%s.wal", map_surfaces[precache_tex++].rname);
if (!CL_CheckOrDownloadFile(fn))
return; // started a download
}
@@ -1699,8 +1688,8 @@
if (!cl_timedemo->value)
{
- if (cls.state == ca_connected && extratime < 100)
- return; // don't flood packets out while connecting
+ //if (cls.state == ca_connected && extratime < 100)
+ // return; // don't flood packets out while connecting
if (extratime < 1000/cl_maxfps->value)
return; // framerate is too high
}
diff -ur quake2-0.3-orig/src/cl_parse.c quake2-0.3/src/cl_parse.c
--- quake2-0.3-orig/src/cl_parse.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/cl_parse.c 2005-08-18 17:29:30.000000000 +0400
@@ -88,7 +88,11 @@
// to the real name when done, so if interrupted
// a runt file wont be left
COM_StripExtension (cls.downloadname, cls.downloadtempname);
- strcat (cls.downloadtempname, ".tmp");
+ if( strlen( cls.downloadtempname ) >= sizeof( cls.downloadtempname ) - 5 ) {
+ strcpy (cls.downloadtempname + sizeof( cls.downloadtempname ) - 5, ".tmp");
+ } else {
+ strcat (cls.downloadtempname, ".tmp");
+ }
//ZOID
// check to see if we already have a tmp for this file, if so, try to resume
@@ -145,6 +149,8 @@
Com_Printf ("Refusing to download a path with ..\n");
return;
}
+
+
if (FS_LoadFile (filename, NULL) != -1)
{ // it exists, no need to download
@@ -159,7 +165,11 @@
// to the real name when done, so if interrupted
// a runt file wont be left
COM_StripExtension (cls.downloadname, cls.downloadtempname);
- strcat (cls.downloadtempname, ".tmp");
+ if( strlen( cls.downloadtempname ) >= sizeof( cls.downloadtempname ) - 5 ) {
+ strcpy (cls.downloadtempname + sizeof( cls.downloadtempname ) - 5, ".tmp");
+ } else {
+ strcat (cls.downloadtempname, ".tmp");
+ }
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message,
@@ -218,6 +228,10 @@
CL_RequestNextDownload ();
return;
}
+
+ if( !cls.downloadname[0] ) {
+ Com_Error( ERR_DROP, "Received unrequesed download packet from server" );
+ }
// open the file if not opened yet
if (!cls.download)
@@ -231,6 +245,7 @@
{
net_message.readcount += size;
Com_Printf ("Failed to open %s\n", cls.downloadtempname);
+ cls.downloadname[0] = 0;
CL_RequestNextDownload ();
return;
}
@@ -274,6 +289,7 @@
cls.download = NULL;
cls.downloadpercent = 0;
+ cls.downloadname[0] = 0;
// get another file if needed
@@ -366,6 +382,10 @@
memset (&nullstate, 0, sizeof(nullstate));
newnum = CL_ParseEntityBits (&bits);
+ /* range check entity index, note that dummy entity 0 should never be used */
+ if( newnum < 1 || newnum >= MAX_EDICTS ) {
+ Com_Error( ERR_DROP, "CL_ParseBaseline: entity index out of range" );
+ }
es = &cl_entities[newnum].baseline;
CL_ParseDelta (&nullstate, es, newnum, bits);
}
@@ -387,12 +407,10 @@
char skin_filename[MAX_QPATH];
char weapon_filename[MAX_QPATH];
- strncpy(ci->cinfo, s, sizeof(ci->cinfo));
- ci->cinfo[sizeof(ci->cinfo)-1] = 0;
+ Q_strncpyz(ci->cinfo, s, sizeof(ci->cinfo));
// isolate the player's name
- strncpy(ci->name, s, sizeof(ci->name));
- ci->name[sizeof(ci->name)-1] = 0;
+ Q_strncpyz(ci->name, s, sizeof(ci->name));
t = strstr (s, "\\");
if (t)
{
@@ -526,11 +544,26 @@
if (i < 0 || i >= MAX_CONFIGSTRINGS)
Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS");
s = MSG_ReadString(&net_message);
+
+ /* do not allow configstring to be written past bounds of cl.configstrings array.
+ note that it is *legal* for some configstrings to exceed MAX_QPATH and span
+ multiple slots, some mods do rely on this (see below) */
+ if( strlen( s ) + i * sizeof( cl.configstrings[0] ) >= sizeof( cl.configstrings ) ) {
+ Com_Error( ERR_DROP, "CL_ParseConfigString: oversize configstring" );
+ }
- strncpy (olds, cl.configstrings[i], sizeof(olds));
- olds[sizeof(olds) - 1] = 0;
-
- strcpy (cl.configstrings[i], s);
+ Q_strncpyz (olds, cl.configstrings[i], sizeof(olds));
+
+ if( i >= CS_GENERAL ) {
+ /* let mod-specific configstrings live on their own */
+ strcpy( cl.configstrings[i], s );
+ } else if( i >= CS_STATUSBAR && i < CS_AIRACCEL ) {
+ /* let statusbar code overflow, but make sure it doesn't overwrite nearby slots */
+ strncpy( cl.configstrings[i], s, sizeof( cl.configstrings[0] ) * ( CS_AIRACCEL - i ) - 1 );
+ } else {
+ /* other configstrings should strictly fit in MAX_QPATH */
+ Q_strncpyz( cl.configstrings[i], s, sizeof( cl.configstrings[0] ) );
+ }
// do something apropriate
@@ -718,6 +751,7 @@
fclose (cls.download);
cls.download = NULL;
}
+ cls.downloadname[0] = 0;
cls.state = ca_connecting;
cls.connect_time = -99999; // CL_CheckForResend() will fire immediately
break;
diff -ur quake2-0.3-orig/src/cmd.c quake2-0.3/src/cmd.c
--- quake2-0.3-orig/src/cmd.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/cmd.c 2005-08-18 17:36:12.000000000 +0400
@@ -129,6 +129,7 @@
// add the entire text of the file
Cbuf_AddText (text);
+ Cbuf_AddText ("\n");
// add the copied off data
if (templen)
@@ -195,7 +196,7 @@
{
int i;
char *text;
- char line[1024];
+ char line[MAX_STRING_CHARS];
int quotes;
alias_count = 0; // don't allow infinite alias loops
@@ -216,7 +217,9 @@
break;
}
-
+ if( i > sizeof( line ) - 1 ) {
+ i = sizeof( line ) - 1;
+ }
memcpy (line, text, i);
line[i] = 0;
@@ -425,7 +428,7 @@
void Cmd_Alias_f (void)
{
cmdalias_t *a;
- char cmd[1024];
+ char cmd[MAX_STRING_CHARS];
int i, c;
char *s;
@@ -539,9 +542,9 @@
Cmd_MacroExpandString
======================
*/
-char *Cmd_MacroExpandString (char *text)
+static char *Cmd_MacroExpandString (char *text, int len)
{
- int i, j, count, len;
+ int i, j, count;
qboolean inquote;
char *scan;
static char expanded[MAX_STRING_CHARS];
@@ -551,13 +554,6 @@
inquote = false;
scan = text;
- len = strlen (scan);
- if (len >= MAX_STRING_CHARS)
- {
- Com_Printf ("Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
- return NULL;
- }
-
count = 0;
for (i=0 ; i<len ; i++)
@@ -574,7 +570,10 @@
if (!start)
continue;
- token = Cvar_VariableString (token);
+ if( strncmp( token, "rcon_", 5 ) ) {
+ /* don't allow server steal rcon password from client */
+ token = Cvar_VariableString (token);
+ }
j = strlen(token);
len += j;
@@ -619,7 +618,7 @@
*/
void Cmd_TokenizeString (char *text, qboolean macroExpand)
{
- int i;
+ int i, len;
char *com_token;
// clear the args from the last string
@@ -629,11 +628,22 @@
cmd_argc = 0;
cmd_args[0] = 0;
- // macro expand the text
- if (macroExpand)
- text = Cmd_MacroExpandString (text);
if (!text)
return;
+
+ len = strlen( text );
+ if (len >= MAX_STRING_CHARS)
+ {
+ Com_Printf ("Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
+ return;
+ }
+
+ // macro expand the text
+ if (macroExpand) {
+ text = Cmd_MacroExpandString (text, len);
+ if (!text)
+ return;
+ }
while (1)
{
diff -ur quake2-0.3-orig/src/common.c quake2-0.3/src/common.c
--- quake2-0.3-orig/src/common.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/common.c 2005-08-18 17:55:27.000000000 +0400
@@ -805,7 +805,7 @@
l = 0;
do
{
- c = MSG_ReadChar (msg_read);
+ c = MSG_ReadByte (msg_read);
if (c == -1 || c == 0)
break;
string[l] = c;
@@ -825,7 +825,7 @@
l = 0;
do
{
- c = MSG_ReadChar (msg_read);
+ c = MSG_ReadByte (msg_read);
if (c == -1 || c == 0 || c == '\n')
break;
string[l] = c;
diff -ur quake2-0.3-orig/src/gl_glx.c quake2-0.3/src/gl_glx.c
--- quake2-0.3-orig/src/gl_glx.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/gl_glx.c 2005-08-17 21:59:10.000000000 +0400
@@ -214,9 +214,9 @@
#ifdef HAVE_XF86_VIDMODE
static XF86VidModeModeInfo **vidmodes;
+static int num_vidmodes;
#endif // HAVE_XF86_VIDMODE
//static int default_dotclock_vidmode;
-static int num_vidmodes;
static qboolean vidmode_active = false;
/* hardware gamma */
@@ -228,7 +228,9 @@
static qboolean mouse_active = false;
static qboolean dgamouse = false;
+#ifdef HAVE_XF86_VIDMODE
static qboolean vidmode_ext = false;
+#endif // HAVE_XF86_VIDMODE
/* stencilbuffer shadows */
qboolean have_stencil = false;
@@ -1008,7 +1010,8 @@
XWMHints *wmhints;
unsigned long mask;
int MajorVersion, MinorVersion;
- int actualWidth, actualHeight;
+ /* FIXME: these are no longer used
+ int actualWidth, actualHeight; */
int i;
r_fakeFullscreen = ri.Cvar_Get( "r_fakeFullscreen", "0", CVAR_ARCHIVE);
@@ -1131,8 +1134,9 @@
}
if (best_fit != -1) {
+ /* FIXME: these are no longer used
actualWidth = vidmodes[best_fit]->hdisplay;
- actualHeight = vidmodes[best_fit]->vdisplay;
+ actualHeight = vidmodes[best_fit]->vdisplay; */
// change to the mode
XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[best_fit]);
@@ -1191,7 +1195,6 @@
Pixmap icon_pixmap, icon_mask;
unsigned long fg, bg;
- int i;
fg = BlackPixel(dpy, visinfo->screen);
bg = WhitePixel(dpy, visinfo->screen);
diff -ur quake2-0.3-orig/src/keys.c quake2-0.3/src/keys.c
--- quake2-0.3-orig/src/keys.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/keys.c 2005-08-18 16:38:56.000000000 +0400
@@ -572,7 +572,7 @@
void Key_Bind_f (void)
{
int i, c, b;
- char cmd[1024];
+ char cmd[MAX_STRING_CHARS];
c = Cmd_Argc();
@@ -744,7 +744,7 @@
void Key_Event (int key, qboolean down, unsigned time)
{
char *kb;
- char cmd[1024];
+ char cmd[MAX_STRING_CHARS];
// hack for modal presses
if (key_waiting == -1)
diff -ur quake2-0.3-orig/src/q_shared.c quake2-0.3/src/q_shared.c
--- quake2-0.3-orig/src/q_shared.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/q_shared.c 2005-08-17 20:48:58.000000000 +0400
@@ -1056,10 +1056,10 @@
char *va(char *format, ...)
{
va_list argptr;
- static char string[1024];
+ static char string[MAX_STRING_CHARS];
va_start (argptr, format);
- vsnprintf (string, 1024, format, argptr);
+ vsnprintf (string, MAX_STRING_CHARS, format, argptr);
va_end (argptr);
return string;
@@ -1120,11 +1120,9 @@
c = *data++;
if (c=='\"' || !c)
{
- com_token[len] = 0;
- *data_p = data;
- return com_token;
+ goto finish;
}
- if (len < MAX_TOKEN_CHARS)
+ if (len < MAX_TOKEN_CHARS - 1)
{
com_token[len] = c;
len++;
@@ -1135,7 +1133,7 @@
// parse a regular word
do
{
- if (len < MAX_TOKEN_CHARS)
+ if (len < MAX_TOKEN_CHARS - 1)
{
com_token[len] = c;
len++;
@@ -1144,11 +1142,7 @@
c = *data;
} while (c>32);
- if (len == MAX_TOKEN_CHARS)
- {
-// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
- len = 0;
- }
+finish:
com_token[len] = 0;
*data_p = data;
diff -ur quake2-0.3-orig/src/q_shared.h quake2-0.3/src/q_shared.h
--- quake2-0.3-orig/src/q_shared.h 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/q_shared.h 2005-08-17 20:48:59.000000000 +0400
@@ -239,6 +239,8 @@
int Q_strcasecmp (char *s1, char *s2);
int Q_strncasecmp (char *s1, char *s2, int n);
+#define Q_strncpyz( dest, src, destsize ) ( strncpy( dest, src, destsize - 1 ), (dest)[destsize - 1] = 0 )
+
//=============================================
short BigShort(short l);
diff -ur quake2-0.3-orig/src/rw_x11.c quake2-0.3/src/rw_x11.c
--- quake2-0.3-orig/src/rw_x11.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/rw_x11.c 2005-08-17 21:48:56.000000000 +0400
@@ -550,9 +550,9 @@
CurrentTime);
if (in_dgamouse->value) {
- int MajorVersion, MinorVersion;
-
#ifdef HAVE_XF86_DGA
+ int MajorVersion, MinorVersion;
+
if (!XF86DGAQueryVersion(dpy, &MajorVersion, &MinorVersion)) {
// unable to query, probalby not supported
ri.Con_Printf( PRINT_ALL, "Failed to detect XF86DGA Mouse\n" );
diff -ur quake2-0.3-orig/src/snd_dma.c quake2-0.3/src/snd_dma.c
--- quake2-0.3-orig/src/snd_dma.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/snd_dma.c 2005-08-18 17:11:30.000000000 +0400
@@ -345,8 +345,7 @@
char *s;
int i;
- s = Z_Malloc (MAX_QPATH);
- strcpy (s, truename);
+ s = CopyString( truename );
// find a free sfx
for (i=0 ; i < num_sfx ; i++)
@@ -1264,19 +1263,17 @@
void S_Play(void)
{
int i;
- char name[256];
+ char name[MAX_QPATH];
sfx_t *sfx;
i = 1;
while (i<Cmd_Argc())
{
- if (!strrchr(Cmd_Argv(i), '.'))
- {
- strcpy(name, Cmd_Argv(i));
- strcat(name, ".wav");
+ Q_strncpyz( name, Cmd_Argv( i ), sizeof( name ) );
+ if( strlen( name ) < sizeof( name ) - 4 ) {
+ COM_DefaultExtension( name, ".wav" );
}
- else
- strcpy(name, Cmd_Argv(i));
+
sfx = S_RegisterSound(name);
S_StartSound(NULL, cl.playernum+1, 0, sfx, 1.0, 1.0, 0);
i++;
diff -ur quake2-0.3-orig/src/sv_main.c quake2-0.3/src/sv_main.c
--- quake2-0.3-orig/src/sv_main.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/sv_main.c 2005-09-21 17:51:47.000000000 +0400
@@ -298,8 +298,14 @@
challenge = atoi(Cmd_Argv(3));
- strncpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-1);
- userinfo[sizeof(userinfo) - 1] = 0;
+ Q_strncpyz (userinfo, Cmd_Argv(4), sizeof(userinfo));
+
+ /* make sure there is enough space for IPv4 key/address pair
+ FIXME: does game dll support IPv6 addresses? */
+ if( strlen( userinfo ) + 25 > sizeof( userinfo ) - 1 ) {
+ Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nOversize userinfo.\n");
+ return;
+ }
// force the IP key/value pair so the game can filter based on ip
Info_SetValueForKey (userinfo, "ip", NET_AdrToString(net_from));
@@ -309,7 +315,7 @@
{
if (!NET_IsLocalAddress (adr))
{
- Com_Printf ("Remote connect in attract loop. Ignored.\n");
+ Com_DPrintf ("Remote connect in attract loop. Ignored.\n");
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n");
return;
}
@@ -322,8 +328,10 @@
{
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
{
- if (challenge == svs.challenges[i].challenge)
+ if (challenge == svs.challenges[i].challenge) {
+ svs.challenges[i].challenge = 0;
break; // good
+ }
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nBad challenge.\n");
return;
}
@@ -347,6 +355,13 @@
&& ( cl->netchan.qport == qport
|| adr.port == cl->netchan.remote_address.port ) )
{
+ /* never overwrite connected clients, let them timeout and call ge->ClientDisconnect properly */
+ if( cl->state != cs_zombie ) {
+ Netchan_OutOfBandPrint( NS_SERVER, adr, "print\nConnected client from this IP is already present.\n" );
+ Com_DPrintf( " rejected a connection - slot already in use.\n" );
+ return;
+ }
+
if (!NET_IsLocalAddress (adr) && (svs.realtime - cl->lastconnect) < ((int)sv_reconnect_limit->value * 1000))
{
Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (adr));
@@ -437,8 +452,10 @@
*/
void SVC_RemoteCommand (void)
{
- int i;
- char remaining[1024];
+ int i, numargs;
+ char remaining[2048]; /* HACK: make this buffer as large as maximum
+ string length returned by MSG_ReadString,
+ so we hopefully don't overflow */
i = Rcon_Validate ();
@@ -457,10 +474,12 @@
{
remaining[0] = 0;
- for (i=2 ; i<Cmd_Argc() ; i++)
+ numargs = Cmd_Argc();
+ for (i=2 ; i<numargs ; i++)
{
strcat (remaining, Cmd_Argv(i) );
- strcat (remaining, " ");
+ if( i != numargs - 1 )
+ strcat (remaining, " ");
}
Cmd_ExecuteString (remaining);
@@ -912,9 +931,9 @@
ge->ClientUserinfoChanged (cl->edict, cl->userinfo);
// name for C code
- strncpy (cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name)-1);
+ Q_strncpyz (cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name));
// mask off high bit
- for (i=0 ; i<sizeof(cl->name) ; i++)
+ for (i=0 ; cl->name[i] ; i++)
cl->name[i] &= 127;
// rate command
diff -ur quake2-0.3-orig/src/sv_user.c quake2-0.3/src/sv_user.c
--- quake2-0.3-orig/src/sv_user.c 2005-09-21 17:43:06.000000000 +0400
+++ quake2-0.3/src/sv_user.c 2005-09-21 16:47:57.000000000 +0400
@@ -126,6 +126,8 @@
void SV_Configstrings_f (void)
{
int start;
+ char *string;
+ int length;
Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
@@ -144,17 +146,28 @@
}
start = atoi(Cmd_Argv(2));
+ if( start < 0 ) {
+ start = 0;
+ }
// write a packet full of data
while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
&& start < MAX_CONFIGSTRINGS)
{
- if (sv.configstrings[start][0])
+ string = sv.configstrings[start];
+ if (*string)
{
+ /* avoid resending chunks of the same large configstring multiple times
+ see CL_ParseConfigstring in cl_parse.c for more info */
+ length = strlen( string );
+ if( length > MAX_QPATH ) {
+ length = MAX_QPATH;
+ }
MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
MSG_WriteShort (&sv_client->netchan.message, start);
- MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
+ SZ_Write (&sv_client->netchan.message, string, length);
+ MSG_WriteByte (&sv_client->netchan.message, 0);
}
start++;
}
@@ -201,6 +214,9 @@
}
start = atoi(Cmd_Argv(2));
+ if( start < 0 ) {
+ start = 0;
+ }
memset (&nullstate, 0, sizeof(nullstate));
@@ -311,19 +327,28 @@
extern cvar_t *allow_download_maps;
extern int file_from_pak; // ZOID did file come from pak?
int offset = 0;
+ int length;
name = Cmd_Argv(1);
+
+ length = strlen( name );
- if (Cmd_Argc() > 2)
+ if (Cmd_Argc() > 2) {
offset = atoi(Cmd_Argv(2)); // downloaded offset
+ if( offset < 0 ) {
+ offset = 0;
+ }
+ }
// hacked by zoid to allow more conrol over download
// first off, no .. or global allow check
- if (strstr (name, "..") || !allow_download->value
+ if (!length || strstr (name, "..") || !allow_download->value
// leading dot is no good
|| *name == '.'
// leading slash bad as well, must be in subdir
|| *name == '/'
+ // check for win32 backslash too
+ || *name == '\\'
// next up, skin check
|| (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
// now models
@@ -333,7 +358,9 @@
// 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, "/") )
+ || !strchr (name, '/')
+ // NEVER allow 'downloading' directories on linux, like maps/ or maps/.
+ || name[length - 1] == '/' || name[length - 1] == '.' )
{ // don't allow anything with .. path
MSG_WriteByte (&sv_client->netchan.message, svc_download);
MSG_WriteShort (&sv_client->netchan.message, -1);
@@ -665,5 +692,9 @@
break;
}
}
+
+ /* send reply immediately to connecting clients */
+ if( cl->state == cs_connected && cl->netchan.message.cursize )
+ Netchan_Transmit (&cl->netchan, 0, NULL);
}