/* ================= 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 challenge; int clientChallenge; // 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 ) ) { if ( com_developer->integer ) { Com_Printf( "SV_GetChallenge: rate limit from %s exceeded, dropping request\n", NET_AdrToString( from ) ); } return; } // Create a unique challenge for this client without storing state on the server challenge = SV_CreateChallenge(from); // Grab the client's challenge to echo back (if given) clientChallenge = atoi(Cmd_Argv(1)); NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i %i", challenge, clientChallenge ); }
/** * @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); }
/** * @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(); }
/** * @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); }
/** * @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); }
/* ================= 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 ); }
/* ================= 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); }
/* ================= 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); }