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);
 }
 

Reply via email to