//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "server_pch.h" #include "vfilter.h" // Renamed to avoid conflict with Microsoft's filter.h #include "sv_filter.h" #include "sv_steamauth.h" #include "GameEventManager.h" #include "proto_oob.h" #include "tier1/CommandBuffer.h" #ifndef DEDICATED #include "cl_steamauth.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static ConVar sv_filterban( "sv_filterban", "1", 0, "Set packet filtering by IP mode" ); CUtlVector< ipfilter_t > g_IPFilters; CUtlVector< userfilter_t > g_UserFilters; #define BANNED_IP_FILENAME "banned_ip.cfg" #define BANNED_USER_FILENAME "banned_user.cfg" #define CONFIG_DIR "cfg/" #define STEAM_PREFIX "STEAM_" //----------------------------------------------------------------------------- // Purpose: Sends a message to prospective clients letting them know they're banned // Input : *adr - //----------------------------------------------------------------------------- void Filter_SendBan( const netadr_t& adr ) { NET_OutOfBandPrintf( NS_SERVER, adr, "%cBanned by server\n", A2A_PRINT ); } //----------------------------------------------------------------------------- // Purpose: Checks an IP address to see if it is banned // Input : *adr - // Output : bool //----------------------------------------------------------------------------- bool Filter_ShouldDiscard( const netadr_t& adr ) { if ( sv_filterban.GetInt() == 0 ) { return false; } bool bNegativeFilter = sv_filterban.GetInt() == 1; unsigned in = *(unsigned *)&adr.ip[0]; // Handle timeouts for ( int i = g_IPFilters.Count() - 1 ; i >= 0 ; i--) { if ( ( g_IPFilters[i].compare != 0xffffffff) && ( g_IPFilters[i].banEndTime != 0.0f ) && ( g_IPFilters[i].banEndTime <= realtime ) ) { g_IPFilters.Remove(i); continue; } // Only get here if ban is still in effect. if ( (in & g_IPFilters[i].mask) == g_IPFilters[i].compare) { return bNegativeFilter; } } return !bNegativeFilter; } //----------------------------------------------------------------------------- // Purpose: Takes an IP address string and fills in an ipfilter_t mask and compare (raw address) // Input : *s - // *f - // Output : bool Filter_ConvertString //----------------------------------------------------------------------------- bool Filter_ConvertString( const char *s, ipfilter_t *f ) { char num[128]; int i, j; byte b[4]; byte m[4]; for (i=0 ; i<4 ; i++) { b[i] = 0; m[i] = 0; } for (i=0 ; i<4 ; i++) { if (*s < '0' || *s > '9') { ConMsg("Bad filter address: %s\n", s); return false; } j = 0; while (*s >= '0' && *s <= '9') { num[j++] = *s++; } num[j] = 0; b[i] = atoi(num); if (b[i] != 0) m[i] = 255; if (!*s) break; s++; } f->mask = *(unsigned int *)m; f->compare = *(unsigned int *)b; return true; } //----------------------------------------------------------------------------- // Purpose: Adds an IP ban //----------------------------------------------------------------------------- static void Filter_Add_f( const CCommand& args ) { int i = 0; float banTime; bool bKick = true; bool bFound = false; char szDuration[256]; CGameClient *client = NULL; if ( !Q_stricmp( args[0], "banip" ) ) { ConMsg( "Note: should use \"addip\" instead of \"banip\".\n" ); } if ( args.ArgC() != 3 ) { ConMsg( "Usage: addip < minutes > < ipaddress >\nUse 0 minutes for permanent\n" ); return; } ipfilter_t f; if ( !Filter_ConvertString( args[2], &f ) ) return; for (i=0 ; iIsActive() || !client->IsConnected() || !client->IsSpawned() ) continue; if ( client->IsFakeClient() ) continue; if ( !Filter_ShouldDiscard( client->GetNetChannel()->GetRemoteAddress() ) ) continue; bFound = true; break; } } // Build a duration string for the ban if ( banTime == 0.0 ) { Q_snprintf( szDuration, sizeof( szDuration ), "permanently" ); } else { Q_snprintf( szDuration, sizeof( szDuration ), "for %.2f minutes", banTime ); } // fire the event IGameEvent *event = g_GameEventManager.CreateEvent( "server_addban" ); if ( event ) { if ( bFound && client ) { event->SetString( "name", client->m_Name ); event->SetInt( "userid", client->GetUserID() ); event->SetString( "networkid", client->GetNetworkIDString() ); } else { event->SetString( "name", "" ); event->SetInt( "userid", 0 ); event->SetString( "networkid", "" ); } event->SetString( "ip", args[2] ); event->SetString( "duration", szDuration ); event->SetString( "by", ( cmd_source == src_command ) ? "Console" : host_client->m_Name ); event->SetBool( "kicked", bKick && bFound && client ); g_GameEventManager.FireEvent( event ); } if ( bKick && bFound && client ) { client->ClientPrintf ( "The server operator has added you to the banned list.\n" ); client->Disconnect( "Added to banned list" ); } } // IP Address filtering ConCommands static ConCommand addip( "addip", Filter_Add_f, "Add an IP address to the ban list." ); static ConCommand banip( "banip", Filter_Add_f, "Add an IP address to the ban list." ); //----------------------------------------------------------------------------- // Purpose: Removes an IP ban //----------------------------------------------------------------------------- CON_COMMAND( removeip, "Remove an IP address from the ban list." ) { ipfilter_t f; int i; if ( args.ArgC() < 1 ) { ConMsg( "Usage: removeip < slot | ipaddress >\n" ); return; } // if no "." in the string we'll assume it's a slot number if ( !Q_strstr( args[1], "." ) ) { int slot = Q_atoi( args[1] ); if ( slot > 0 && slot <= g_IPFilters.Count() ) { byte b[4]; char szIP[32]; // array access is zero based slot--; *(unsigned *)b = g_IPFilters[slot].compare; Q_snprintf( szIP, sizeof( szIP ), "%3i.%3i.%3i.%3i", b[0], b[1], b[2], b[3] ); g_IPFilters.Remove( slot ); // Tell server operator ConMsg( "removeip: filter removed for %s, IP %s\n", args[1], szIP ); // send an event IGameEvent *event = g_GameEventManager.CreateEvent( "server_removeban" ); if ( event ) { event->SetString( "networkid", "" ); event->SetString( "ip", szIP ); event->SetString( "by", ( cmd_source == src_command ) ? "Console" : host_client->m_Name ); g_GameEventManager.FireEvent( event ); } } else { ConMsg( "removeip: invalid slot %i\n", slot ); } return; } if ( !Filter_ConvertString( args[1], &f ) ) return; for ( i = 0 ; i < g_IPFilters.Count() ; i++ ) { if ( ( g_IPFilters[i].mask == f.mask ) && ( g_IPFilters[i].compare == f.compare ) ) { g_IPFilters.Remove(i); ConMsg( "removeip: filter removed for %s\n", args[1] ); // send an event IGameEvent *event = g_GameEventManager.CreateEvent( "server_removeban" ); if ( event ) { event->SetString( "networkid", "" ); event->SetString( "ip", args[1] ); event->SetString( "by", ( cmd_source == src_command ) ? "Console" : host_client->m_Name ); g_GameEventManager.FireEvent( event ); } return; } } ConMsg( "removeip: couldn't find %s\n", args[1] ); } //----------------------------------------------------------------------------- // Purpose: Lists IP bans //----------------------------------------------------------------------------- CON_COMMAND( listip, "List IP addresses on the ban list." ) { int i; byte b[4]; int count = g_IPFilters.Count(); if ( !count ) { ConMsg( "IP filter list: empty\n" ); return; } else { if ( count == 1 ) { ConMsg( "IP filter list: %i entry\n", count ); } else { ConMsg( "IP filter list: %i entries\n", count ); } } for ( i = 0 ; i < count ; i++ ) { *(unsigned *)b = g_IPFilters[i].compare; if ( g_IPFilters[i].banTime != 0.0f ) { ConMsg( "%i %3i.%3i.%3i.%3i : %.3f min\n", i+1, b[0], b[1], b[2], b[3], g_IPFilters[i].banTime ); } else { ConMsg( "%i %3i.%3i.%3i.%3i : permanent\n", i+1, b[0], b[1], b[2], b[3] ); } } } //----------------------------------------------------------------------------- // Purpose: Saves IP bans to a file //----------------------------------------------------------------------------- CON_COMMAND( writeip, "Save the ban list to " BANNED_IP_FILENAME "." ) { FileHandle_t f; char name[MAX_OSPATH]; byte b[4]; int i; float banTime; Q_strncpy( name, CONFIG_DIR BANNED_IP_FILENAME, sizeof( name ) ); ConMsg( "Writing %s.\n", name ); f = g_pFileSystem->Open ( name, "wb" ); if ( !f ) { ConMsg( "Couldn't open %s\n", name ); return; } for ( i = 0 ; i < g_IPFilters.Count() ; i++ ) { *(unsigned *)b = g_IPFilters[i].compare; // Only store out the permanent bad guys from this server. banTime = g_IPFilters[i].banTime; if ( banTime != 0.0f ) { continue; } g_pFileSystem->FPrintf( f, "addip 0 %i.%i.%i.%i\r\n", b[0], b[1], b[2], b[3] ); } g_pFileSystem->Close( f ); } //----------------------------------------------------------------------------- // Purpose: Checks a USERID_t to see if the Steam ID has been banned //----------------------------------------------------------------------------- bool Filter_IsUserBanned( const USERID_t& userid ) { #ifndef _XBOX if ( sv_filterban.GetInt() == 0 ) return false; bool bNegativeFilter = sv_filterban.GetInt() == 1; // Handle timeouts for ( int i =g_UserFilters.Count() - 1 ; i >= 0 ; i-- ) { // Time out old filters if ( ( g_UserFilters[i].banEndTime != 0.0f ) && ( g_UserFilters[i].banEndTime <= realtime ) ) { g_UserFilters.Remove( i ); continue; } // Only get here if ban is still in effect. if ( Steam3Server().CompareUserID( userid, g_UserFilters[i].userid ) ) { return bNegativeFilter; } } return !bNegativeFilter; #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: Converts a "STEAM_X:Y:Z" string into a USERID_t //----------------------------------------------------------------------------- USERID_t *Filter_StringToUserID( const char *str ) { static USERID_t id; Q_memset( &id, 0, sizeof( id ) ); if ( str && str[ 0 ] ) { char szTemp[128]; if ( !Q_strnicmp( str, STEAM_PREFIX, strlen( STEAM_PREFIX ) ) ) { Q_strncpy( szTemp, str + Q_strlen( STEAM_PREFIX ), sizeof( szTemp ) - 1 ); id.idtype = IDTYPE_STEAM; szTemp[ sizeof( szTemp ) - 1 ] = '\0'; CCommand args; args.Tokenize( szTemp ); if ( args.ArgC() >= 5 ) { // allow settings from old style steam2 format TSteamGlobalUserID steam2ID; steam2ID.m_SteamInstanceID = (SteamInstanceID_t)atoi( args[ 0 ] ); steam2ID.m_SteamLocalUserID.Split.High32bits = (int)atoi( args[ 2 ] ); steam2ID.m_SteamLocalUserID.Split.Low32bits = (int)atoi( args[ 4 ] ); EUniverse eUniverse = k_EUniversePublic; if ( Steam3Server().GetGSSteamID().IsValid() ) eUniverse = Steam3Server().GetGSSteamID().GetEUniverse(); #ifndef DEDICATED else if ( Steam3Client().SteamUser() ) eUniverse = Steam3Client().SteamUser()->GetSteamID().GetEUniverse(); #endif id.steamid.SetFromSteam2( &steam2ID, eUniverse ); } } else { id.steamid.SetFromString( str, k_EUniversePublic ); if ( id.steamid.IsValid() ) id.idtype = IDTYPE_STEAM; } } return &id; } //----------------------------------------------------------------------------- // Purpose: Saves Steam ID bans to a file //----------------------------------------------------------------------------- CON_COMMAND( writeid, "Writes a list of permanently-banned user IDs to " BANNED_USER_FILENAME "." ) { FileHandle_t f; char name[MAX_OSPATH]; int i; float banTime; Q_strncpy( name, CONFIG_DIR BANNED_USER_FILENAME, sizeof( name ) ); ConMsg( "Writing %s.\n", name ); f = g_pFileSystem->Open ( name, "wb" ); if ( !f ) { ConMsg( "Couldn't open %s\n", name ); return; } for ( i = 0 ; i < g_UserFilters.Count() ; i++ ) { banTime = g_UserFilters[i].banTime; if ( banTime != 0.0f ) { continue; } g_pFileSystem->FPrintf( f, "banid 0 %s\r\n", GetUserIDString( g_UserFilters[i].userid ) ); } g_pFileSystem->Close( f ); } //----------------------------------------------------------------------------- // Purpose: Removes a Steam ID ban //----------------------------------------------------------------------------- CON_COMMAND( removeid, "Remove a user ID from the ban list." ) { int i = 0; const char *pszArg1 = NULL; char szSearchString[64]; if ( args.ArgC() != 2 && args.ArgC() != 6 ) { ConMsg( "Usage: removeid < slot | uniqueid >\n" ); return; } // get the first argument pszArg1 = args[1]; // don't need the # if they're using it if ( !Q_strncmp( pszArg1, "#", 1 ) ) { ConMsg( "Usage: removeid < userid | uniqueid >\n" ); ConMsg( "No # necessary\n" ); return; } // if the first letter is a character then // we're searching for a uniqueid ( e.g. STEAM_ ) if ( *pszArg1 < '0' || *pszArg1 > '9' || V_atoi64( pszArg1 ) > ( (uint32)~0 ) ) { bool bValid = false; // SteamID ( need to reassemble it ) if ( !Q_strnicmp( pszArg1, STEAM_PREFIX, Q_strlen( STEAM_PREFIX ) ) && Q_strstr( args[2], ":" ) ) { Q_snprintf( szSearchString, sizeof( szSearchString ), "%s:%s:%s", pszArg1, args[3], args[5] ); USERID_t *id = Filter_StringToUserID( szSearchString ); if ( id ) { bValid = id->steamid.IsValid(); if ( bValid ) V_sprintf_safe( szSearchString, "%s", id->steamid.Render() ); } } else { CSteamID cSteamIDCheck; const char *pchUUID = args.ArgS(); if ( pchUUID ) { cSteamIDCheck.SetFromString( pchUUID, k_EUniversePublic ); bValid = cSteamIDCheck.IsValid(); if ( bValid ) V_sprintf_safe( szSearchString, "%s", cSteamIDCheck.Render() ); } } // some other ID (e.g. "UNKNOWN", "STEAM_ID_PENDING", "STEAM_ID_LAN") // NOTE: assumed to be one argument if ( !bValid ) { ConMsg( "removeid: invalid ban ID \"%s\"\n", pszArg1 ); return; } for ( i = 0 ; i < g_UserFilters.Count() ; i++ ) { if ( Q_stricmp( GetUserIDString( g_UserFilters[i].userid ), szSearchString ) ) continue; g_UserFilters.Remove( i ); ConMsg( "removeid: filter removed for %s\n", szSearchString ); // send an event IGameEvent *event = g_GameEventManager.CreateEvent( "server_removeban" ); if ( event ) { event->SetString( "networkid", szSearchString ); event->SetString( "ip", "" ); event->SetString( "by", ( cmd_source == src_command ) ? "Console" : host_client->m_Name ); g_GameEventManager.FireEvent( event ); } return; } ConMsg( "removeid: couldn't find %s\n", szSearchString ); } // this is a userid else { int slot = Q_atoi( pszArg1 ); if ( slot > 0 && slot <= g_UserFilters.Count() ) { USERID_t id; // array access is zero based slot--; // Copy off slot id = g_UserFilters[slot].userid; g_UserFilters.Remove( slot ); // Tell server operator ConMsg( "removeid: filter removed for %s, ID %s\n", pszArg1, GetUserIDString( id ) ); // send an event IGameEvent *event = g_GameEventManager.CreateEvent( "server_removeban" ); if ( event ) { event->SetString( "networkid", GetUserIDString( id ) ); event->SetString( "ip", "" ); event->SetString( "by", ( cmd_source == src_command ) ? "Console" : host_client->m_Name ); g_GameEventManager.FireEvent( event ); } } else { ConMsg( "removeid: invalid slot %i\n", slot ); } } } //----------------------------------------------------------------------------- // Purpose: Prints Steam ID bans to the console //----------------------------------------------------------------------------- CON_COMMAND( listid, "Lists banned users." ) { int i; int count = g_UserFilters.Count(); if ( !count ) { ConMsg( "ID filter list: empty\n" ); return; } else { if ( count == 1 ) { ConMsg( "ID filter list: %i entry\n", count ); } else { ConMsg( "ID filter list: %i entries\n", count ); } } for ( i = 0 ; i < count ; i++ ) { if ( g_UserFilters[i].banTime != 0.0 ) { ConMsg( "%i %s : %.3f min\n", i+1, GetUserIDString( g_UserFilters[i].userid ), g_UserFilters[i].banTime ); } else { ConMsg( "%i %s : permanent\n", i+1, GetUserIDString( g_UserFilters[i].userid ) ); } } } //----------------------------------------------------------------------------- // Purpose: Bans a Steam ID //----------------------------------------------------------------------------- CON_COMMAND( banid, "Add a user ID to the ban list." ) { #ifndef _XBOX int i; float banTime; USERID_t localId; USERID_t * id = NULL; int iSearchIndex = -1; char szDuration[256]; char szSearchString[64]; bool bKick = false; bool bPlaying = false; const char *pszArg2 = NULL; CGameClient *client = NULL; if ( Steam3Server().BLanOnly() ) { ConMsg( "Can't ban users on a LAN\n" ); return; } if ( args.ArgC() < 3 || args.ArgC() > 8 ) { ConMsg( "Usage: banid < minutes > < userid | uniqueid > { kick }\n" ); ConMsg( "Use 0 minutes for permanent\n"); return; } banTime = Q_atof( args[1] ); if ( banTime < 0.01 ) { banTime = 0.0; } // get the first argument pszArg2 = args[2]; // don't need the # if they're using it if ( !Q_strncmp( pszArg2, "#", 1 ) ) { ConMsg( "Usage: banid < minutes > < userid | uniqueid > { kick }\n" ); ConMsg( "No # necessary\n"); return; } bKick = ( args.ArgC() >= 3 && Q_strcasecmp( args[ args.ArgC() - 1 ], "kick" ) == 0 ); // if the first letter is a character then // we're searching for a uniqueid ( e.g. STEAM_ ) if ( *pszArg2 < '0' || *pszArg2 > '9' || V_atoi64( pszArg2 ) > ((uint32)~0) ) { bool bValid = false; iSearchIndex = -1; // SteamID (need to reassemble it) if ( !Q_strnicmp( pszArg2, STEAM_PREFIX, strlen( STEAM_PREFIX ) ) && Q_strstr( args[3], ":" ) ) { Q_snprintf( szSearchString, sizeof( szSearchString ), "%s:%s:%s", pszArg2, args[4], args[6] ); bValid = true; } else { CSteamID cSteamIDCheck; const char *pchArgs = args.ArgS(); const char *pchUUID = strchr( pchArgs, ' ' ); if ( pchUUID ) { cSteamIDCheck.SetFromString( pchUUID + 1, k_EUniversePublic ); bValid = cSteamIDCheck.IsValid(); if ( bValid ) V_sprintf_safe( szSearchString, "%s", cSteamIDCheck.Render() ); } } // some other ID (e.g. "UNKNOWN", "STEAM_ID_PENDING", "STEAM_ID_LAN") // NOTE: assumed to be one argument if ( !bValid ) { ConMsg( "Can't ban users with ID \"%s\"\n", pszArg2 ); return; } } else { // see if it is a userid iSearchIndex = Q_atoi( pszArg2 ); } // find this client (if they're currently in the server) for ( i = 0; i < sv.GetClientCount(); i++ ) { client = sv.Client(i); if ( !client || !client->IsActive() || !client->IsConnected() || !client->IsSpawned() ) { continue; } if ( client->IsFakeClient() ) { continue; } // searching by UserID if ( iSearchIndex != -1 ) { if ( client->GetUserID() == iSearchIndex ) { // found! localId = client->GetNetworkID(); id = &localId; bPlaying = true; break; } } // searching by UniqueID else { if ( Q_stricmp( client->GetNetworkIDString(), szSearchString ) == 0 ) { // found! localId = client->GetNetworkID(); id = &localId; bPlaying = true; break; } } } // if we were searching by userid and we didn't find the person, we're done if ( iSearchIndex != -1 && !id ) { ConMsg( "banid: couldn't find userid %d\n", iSearchIndex ); return; } if ( !id ) { // we're searching by SteamID and we haven't found them actively playing id = Filter_StringToUserID( szSearchString ); if ( !id ) { ConMsg( "banid: Couldn't resolve uniqueid \"%s\".\n", szSearchString ); ConMsg( "Usage: banid < minutes > < userid | uniqueid > { kick }\n" ); ConMsg( "Use 0 minutes for permanent\n"); return; } } if ( !id ) { // Should never occur!!! ConMsg( "SV_BanId_f: id == NULL\n" ); return; } // See if it's in the list already for ( i = 0 ; i < g_UserFilters.Count() ; i++ ) { // We're just updating an existing id if ( Steam3Server().CompareUserID( g_UserFilters[i].userid, *id ) ) break; } // // Adding a new one if ( i >= g_UserFilters.Count() ) { // See if we have space for it if ( g_UserFilters.Count() >= MAX_USERFILTERS ) { ConMsg( "banid: user filter list is full\n" ); return; } userfilter_t nullUser; memset( &nullUser, 0, sizeof(nullUser) ); i = g_UserFilters.AddToTail( nullUser ); } g_UserFilters[i].banTime = banTime; g_UserFilters[i].banEndTime = ( banTime != 0.0 ) ? ( realtime + 60.0 * banTime ) : 0.0; g_UserFilters[i].userid = *id; // Build a duration string for the ban if ( banTime == 0.0 ) { Q_snprintf( szDuration, sizeof( szDuration ), "permanently" ); } else { Q_snprintf( szDuration, sizeof( szDuration ), "for %.2f minutes", banTime ); } // fire the event IGameEvent *event = g_GameEventManager.CreateEvent( "server_addban" ); if ( event ) { if ( bPlaying ) { event->SetString( "name", client->m_Name ); event->SetInt( "userid", client->GetUserID() ); event->SetString( "networkid", client->GetNetworkIDString() ); } else { event->SetString( "name", "" ); event->SetInt( "userid", 0 ); event->SetString( "networkid", GetUserIDString( *id ) ); } event->SetString( "ip", "" ); event->SetString( "duration", szDuration ); event->SetString( "by", ( cmd_source == src_command ) ? "Console" : host_client->m_Name ); event->SetInt( "kicked", ( bKick && bPlaying && client ) ? 1 : 0 ); g_GameEventManager.FireEvent( event ); } if ( bKick && bPlaying && client ) { client->ClientPrintf ( "You have been kicked and banned %s by the server.\n", szDuration ); client->Disconnect( "Kicked and banned" ); } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void Filter_Init( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void Filter_Shutdown( void ) { }