Esempio n. 1
0
/**
 * @brief Responds with all the info that the server browser can see
 * @sa SV_StatusString
 */
static void SVC_Status (struct net_stream* s)
{
    if (SVC_RateLimitAddress(*s)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
        return;
    }

    /* Allow getstatus to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
    if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit exceeded, dropping request\n");
        return;
    }

    dbuffer msg;
    NET_WriteByte(&msg, svc_oob);
    NET_WriteRawString(&msg, SV_CMD_PRINT "\n");
    char info[MAX_INFO_STRING];
    NET_WriteRawString(&msg, Cvar_Serverinfo(info, sizeof(info)));
    NET_WriteRawString(&msg, "\n");

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr) {
        if (cl->state <= cs_free)
            continue;

        char player[1024];
        Com_sprintf(player, sizeof(player), "%i \"%s\"\n", svs.ge->ClientGetTeamNum(*cl->player), cl->name);
        NET_WriteRawString(&msg, player);
    }

    NET_WriteMsg(s, msg);
}
Esempio n. 2
0
/*
===============
SV_ClientCommand
===============
*/
static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) {
	int		seq;
	const char	*s;
	qboolean clientOk = qtrue;

	seq = MSG_ReadLong( msg );
	s = MSG_ReadString( msg );

	// see if we have already executed it
	if ( cl->lastClientCommand >= seq ) {
		return qtrue;
	}

	Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s );

	// drop the connection if we have somehow lost commands
	if ( seq > cl->lastClientCommand + 1 ) {
		Com_Printf( "Client %s lost %i clientCommands\n", cl->name,
			seq - cl->lastClientCommand + 1 );
		SV_DropClient( cl, "Lost reliable commands" );
		return qfalse;
	}

	// malicious users may try using too many string commands
	// to lag other players.  If we decide that we want to stall
	// the command, we will stop processing the rest of the packet,
	// including the usercmd.  This causes flooders to lag themselves
	// but not other people
	// We don't do this when the client hasn't been active yet since its
	// normal to spam a lot of commands when downloading

	// Applying floodprotect only to "CS_ACTIVE" clients leaves too much room for abuse. Extending floodprotect to clients pre CS_ACTIVE shouldn't cause any issues, as the download-commands are handled within the engine and floodprotect only filters calls to the VM.
	if ( !com_cl_running->integer && /* cl->state >= CS_ACTIVE && */ sv_floodProtect->integer )
	{
		if ( sv_floodProtect->integer == 1 && svs.time < cl->nextReliableTime )
		{
			clientOk = qfalse;
		}
		else if ( SVC_RateLimit(&cl->cmdBucket, sv_floodProtect->integer, 1000, svs.time) )
		{
			clientOk = qfalse;
		}
	}

	// don't allow another command for one second
	cl->nextReliableTime = svs.time + 1000;

	SV_ExecuteClientCommand( cl, s, clientOk );

	cl->lastClientCommand = seq;
	Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s);

	return qtrue;		// continue procesing
}
Esempio n. 3
0
/**
 * @brief A client issued an rcon command. Shift down the remaining args. Redirect all printfs
 */
static void SVC_RemoteCommand (struct net_stream* stream)
{
    char buf[64];
    const char* peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false);

    /* Prevent using rcon as an amplifier and make dictionary attacks impractical */
    if (SVC_RateLimitAddress(*stream)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_RemoteCommand: rate limit from %s exceeded, dropping request\n", peername);
        return;
    }

    const bool valid = Rcon_Validate(Cmd_Argv(1));
    if (!valid) {
        static leakyBucket_t bucket;
        /* Make DoS via rcon impractical */
        if (SVC_RateLimit(&bucket, 10, 1000)) {
            Com_DPrintf(DEBUG_SERVER, "SVC_RemoteCommand: rate limit exceeded, dropping request\n");
            return;
        }

        Com_Printf("Bad rcon from %s with password: '******'\n", peername, Cmd_Argv(1));
    } else {
        Com_Printf("Rcon from %s\n", peername);
    }

    static char sv_outputbuf[1024];
    Com_BeginRedirect(stream, sv_outputbuf, sizeof(sv_outputbuf));

    if (!valid) {
        /* inform the client */
        Com_Printf(BAD_RCON_PASSWORD);
    } else {
        char remaining[1024] = "";
        int i;

        /* execute the rcon commands */
        for (i = 2; i < Cmd_Argc(); i++) {
            Q_strcat(remaining, sizeof(remaining), "%s ", Cmd_Argv(i));
        }

        /* execute the string */
        Cmd_ExecuteString("%s", remaining);
    }

    Com_EndRedirect();
}
Esempio n. 4
0
/**
 * @brief Responds with short info for broadcast scans
 * @note The second parameter should be the current protocol version number.
 * @note Only a short server description - the user can determine whether he is
 * interested in a full status
 * @sa CL_ParseStatusMessage
 * @sa CL_ProcessPingReply
 */
static void SVC_Info (struct net_stream* s)
{
    if (SVC_RateLimitAddress(*s)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
        return;
    }

    /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
    if (SVC_RateLimit(&outboundLeakyBucket)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit exceeded, dropping request\n");
        return;
    }

    if (sv_maxclients->integer == 1) {
        Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n");
        return;	/* ignore in single player */
    }

    const int version = atoi(Cmd_Argv(1));
    if (version != PROTOCOL_VERSION) {
        char string[MAX_VAR];
        Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION);
        NET_OOB_Printf(s, SV_CMD_PRINT "\n%s", string);
        return;
    }

    int count = 0;

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr)
        if (cl->state >= cs_spawning)
            count++;

    char infostring[MAX_INFO_STRING];
    infostring[0] = '\0';
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION));
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name);
    Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION);
    NET_OOB_Printf(s, SV_CMD_INFO "\n%s", infostring);
}
Esempio n. 5
0
/**
 * @brief Responds with teaminfo such as free team num
 * @sa CL_ParseTeamInfoMessage
 */
static void SVC_TeamInfo (struct net_stream* s)
{
    if (SVC_RateLimitAddress(*s)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
        return;
    }

    /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
    if (SVC_RateLimit(&outboundLeakyBucket)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit exceeded, dropping request\n");
        return;
    }

    char infoGlobal[MAX_INFO_STRING] = "";
    Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay"));
    Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams"));
    Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam"));

    dbuffer msg;
    NET_WriteByte(&msg, svc_oob);
    NET_WriteRawString(&msg, "teaminfo\n");
    NET_WriteString(&msg, infoGlobal);

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr) {
        if (cl->state < cs_connected)
            continue;
        char infoPlayer[MAX_INFO_STRING] = "";
        /* show players that already have a team with their teamnum */
        int teamId = svs.ge->ClientGetTeamNum(*cl->player);
        if (!teamId)
            teamId = TEAM_NO_ACTIVE;
        Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId);
        Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player));
        Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name);
        NET_WriteString(&msg, infoPlayer);
    }

    NET_WriteByte(&msg, 0);

    NET_WriteMsg(s, msg);
}
Esempio n. 6
0
/*
=================
SV_GetChallenge

A "getchallenge" OOB command has been received
Returns a challenge number that can be used
in a subsequent connectResponse command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs.  With a
challenge, they must give a valid IP address.

If we are authorizing, a challenge request will cause a packet
to be sent to the authorize server.

When an authorizeip is returned, a challenge response will be
sent to that ip.

ioquake3/openjk: we added a possibility for clients to add a challenge
to their packets, to make it more difficult for malicious servers
to hi-jack client connections.
=================
*/
void SV_GetChallenge( netadr_t from ) {
	int		i;
	int		oldest;
	int		oldestTime;
	int		clientChallenge;
	challenge_t	*challenge;

	// ignore if we are in single player
	/*
	if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
		return;
	}
	*/
	if (Cvar_VariableValue("ui_singlePlayerActive"))
	{
		return;
	}

	// Prevent using getchallenge as an amplifier
	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
		Com_DPrintf( "SV_GetChallenge: rate limit from %s exceeded, dropping request\n",
			NET_AdrToString( from ) );
		return;
	}

	// Allow getchallenge to be DoSed relatively easily, but prevent
	// excess outbound bandwidth usage when being flooded inbound
	if ( SVC_RateLimit( &outboundLeakyBucket, 10, 100 ) ) {
		Com_DPrintf( "SV_GetChallenge: rate limit exceeded, dropping request\n" );
		return;
	}

	oldest = 0;
	oldestTime = 0x7fffffff;

	// see if we already have a challenge for this ip
	challenge = &svs.challenges[0];
	clientChallenge = atoi(Cmd_Argv(1));

	for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++)
	{
		if(!challenge->connected && NET_CompareAdr(from, challenge->adr))
		{
			break;
		}
		if ( challenge->time < oldestTime )
		{
			oldestTime = challenge->time;
			oldest = i;
		}
	}

	if (i == MAX_CHALLENGES) {
		// this is the first time this client has asked for a challenge
		challenge = &svs.challenges[oldest];

		challenge->adr = from;
		challenge->firstTime = svs.time;
		challenge->time = svs.time;
		challenge->connected = qfalse;
	}

	// always generate a new challenge number, so the client cannot circumvent sv_maxping
	challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time;
	challenge->wasrefused = qfalse;

	challenge->pingTime = svs.time;
	NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %i %i", challenge->challenge, clientChallenge );
}
Esempio n. 7
0
/**
 * @brief Rate limit for a particular address
 */
static bool SVC_RateLimitAddress (struct net_stream& from, int burst = 10, int period = 1000)
{
    leakyBucket_t* bucket = SVC_BucketForAddress(from, burst, period);
    return SVC_RateLimit(bucket, burst, period);
}
Esempio n. 8
0
/*
=================
SV_GetChallenge

A "getchallenge" OOB command has been received
Returns a challenge number that can be used
in a subsequent connectResponse command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs.  With a
challenge, they must give a valid IP address.

If we are authorizing, a challenge request will cause a packet
to be sent to the authorize server.

When an authorizeip is returned, a challenge response will be
sent to that ip.

ioquake3: we added a possibility for clients to add a challenge
to their packets, to make it more difficult for malicious servers
to hi-jack client connections.
Also, the auth stuff is completely disabled for com_standalone games
as well as IPv6 connections, since there is no way to use the
v4-only auth server for these new types of connections.
=================
*/
void SV_GetChallenge(netadr_t from)
{
	int		i;
	int		oldest;
	int		oldestTime;
	int		oldestClientTime;
	int		clientChallenge;
	challenge_t	*challenge;
	qboolean wasfound = qfalse;
	char *gameName;
	qboolean gameMismatch;

	// Prevent using getchallenge as an amplifier
	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
		Com_DPrintf( "SV_GetChallenge: rate limit from %s exceeded, dropping request\n",
			NET_AdrToString( from ) );
		return;
	}

	// Allow getchallenge to be DoSed relatively easily, but prevent
	// excess outbound bandwidth usage when being flooded inbound
	if ( SVC_RateLimit( &outboundLeakyBucket, 10, 100 ) ) {
		Com_DPrintf( "SV_GetChallenge: rate limit exceeded, dropping request\n" );
		return;
	}

	gameName = Cmd_Argv(2);

	gameMismatch = !*gameName || strcmp(gameName, com_gamename->string) != 0;

	// reject client if the gamename string sent by the client doesn't match ours
	if (gameMismatch)
	{
		NET_OutOfBandPrint(NS_SERVER, from, "print\nGame mismatch: This is a %s server\n",
			com_gamename->string);
		return;
	}

	oldest = 0;
	oldestClientTime = oldestTime = 0x7fffffff;

	// see if we already have a challenge for this ip
	challenge = &svs.challenges[0];
	clientChallenge = atoi(Cmd_Argv(1));

	for(i = 0 ; i < MAX_CHALLENGES ; i++, challenge++)
	{
		if(!challenge->connected && NET_CompareAdr(from, challenge->adr))
		{
			wasfound = qtrue;
			
			if(challenge->time < oldestClientTime)
				oldestClientTime = challenge->time;
		}
		
		if(wasfound && i >= MAX_CHALLENGES_MULTI)
		{
			i = MAX_CHALLENGES;
			break;
		}
		
		if(challenge->time < oldestTime)
		{
			oldestTime = challenge->time;
			oldest = i;
		}
	}

	if (i == MAX_CHALLENGES)
	{
		// this is the first time this client has asked for a challenge
		challenge = &svs.challenges[oldest];
		challenge->clientChallenge = clientChallenge;
		challenge->adr = from;
		challenge->firstTime = svs.time;
		challenge->connected = qfalse;
	}

	// always generate a new challenge number, so the client cannot circumvent sv_maxping
	challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time;
	challenge->wasrefused = qfalse;
	challenge->time = svs.time;
	challenge->pingTime = svs.time;
	NET_OutOfBandPrint(NS_SERVER, challenge->adr, "challengeResponse %d %d %d",
			   challenge->challenge, clientChallenge, PROTOCOL_VERSION);
}
Esempio n. 9
0
/*
=================
SV_GetChallenge

A "getchallenge" OOB command has been received
Returns a challenge number that can be used
in a subsequent connectResponse command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs.  With a
challenge, they must give a valid IP address.

If we are authorizing, a challenge request will cause a packet
to be sent to the authorize server.

When an authorizeip is returned, a challenge response will be
sent to that ip.

ioquake3: we added a possibility for clients to add a challenge
to their packets, to make it more difficult for malicious servers
to hi-jack client connections.
Also, the auth stuff is completely disabled for com_standalone games
as well as IPv6 connections, since there is no way to use the
v4-only auth server for these new types of connections.
=================
*/
void SV_GetChallenge( netadr_t from ) {
	static leakyBucket_t outboundLeakyBucket;
	int		i;
	int		oldest;
	int		oldestTime;
	int		oldestClientTime;
	int		clientChallenge;
	challenge_t	*challenge;
	qboolean wasfound = qfalse;

	// ignore if we are in single player
	if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
		return;
	}

		// Prevent using getchallenge as an amplifier
	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
		Com_DPrintf( "SV_GetChallenge: rate limit from %s exceeded, dropping request\n",
			NET_AdrToString( from ) );
		return;
	}

	// Allow getchallenge to be DoSed relatively easily, but prevent
	// excess outbound bandwidth usage when being flooded inbound
	if ( SVC_RateLimit( &outboundLeakyBucket, 10, 100 ) ) {
		Com_DPrintf( "SV_GetChallenge: rate limit exceeded, dropping request\n" );
		return;
	}

	oldest = 0;
 	oldestClientTime = oldestTime = 0x7fffffff;

	// see if we already have a challenge for this ip
	challenge = &svs.challenges[0];
 	clientChallenge = atoi( Cmd_Argv( 1 ) );

 	for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++ )
 	{
 		if ( !challenge->connected && NET_CompareAdr( from, challenge->adr ) )
 		{
 			wasfound = qtrue;

 			if ( challenge->time < oldestClientTime )
 				oldestClientTime = challenge->time;
 		}

 		if ( wasfound && i >= MAX_CHALLENGES_MULTI )
 		{
 			i = MAX_CHALLENGES;
  			break;
  		}

 		if ( challenge->time < oldestTime )
 		{
  			oldestTime = challenge->time;
  			oldest = i;
  		}
	}

	if (i == MAX_CHALLENGES)
	{
		// this is the first time this client has asked for a challenge
		challenge = &svs.challenges[oldest];
		challenge->clientChallenge = clientChallenge;
		challenge->adr = from;
		challenge->firstTime = svs.time;
		challenge->connected = qfalse;
	}

	// always generate a new challenge number, so the client cannot circumvent sv_maxping
	challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time;
	challenge->wasrefused = qfalse;
	challenge->time = svs.time;

#if !defined(STANDALONE) && defined (USE_AUTHORIZE)
	// Drop the authorize stuff if this client is coming in via v6 as the auth server does not support ipv6.
	// Drop also for addresses coming in on local LAN and for stand-alone games independent from id's assets.
	if(challenge->adr.type == NA_IP && !Cvar_VariableIntegerValue("com_standalone") && !Sys_IsLANAddress(from))
	{
		// look up the authorize server's IP
		if (svs.authorizeAddress.type == NA_BAD)
		{
			Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
			
			if (NET_StringToAdr(AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP))
			{
				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 ) );
			}
		}

		// we couldn't contact the auth server, let them in.
		if(svs.authorizeAddress.type == NA_BAD)
			Com_Printf("Couldn't resolve auth server address\n");

		// if they have been challenging for a long time and we
		// haven't heard anything from the authorize server, go ahead and
		// let them in, assuming the id server is down
		else if(svs.time - oldestClientTime > AUTHORIZE_TIMEOUT)
			Com_DPrintf( "authorize server timed out\n" );
		else
		{
			// otherwise send their ip to the authorize server
			cvar_t	*fs;
			char	game[1024];

			Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from ));
		
			strcpy(game, IDBASEGAME);
			fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
			if (fs && fs->string[0] != 0) {
				strcpy(game, fs->string);
			}
			
			// the 0 is for backwards compatibility with obsolete sv_allowanonymous flags
			// getIpAuthorize <challenge> <IP> <game> 0 <auth-flag>
			NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
				"getIpAuthorize %i %i.%i.%i.%i %s 0 %s",  challenge->challenge,
				from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, sv_strictAuth->string );
			
			return;
		}
	}
#endif

	challenge->pingTime = svs.time;
	NET_OutOfBandPrint(NS_SERVER, challenge->adr, "challengeResponse %d %d",
			   challenge->challenge, clientChallenge);
}