2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================//
# include "utlstring.h"
# include "checksum_crc.h"
# include "userid.h"
# include "pure_server.h"
# include "common.h"
# include "tier1/KeyValues.h"
# include "convar.h"
# include "filesystem_engine.h"
# include "server.h"
# include "sv_filter.h"
# include "tier1/UtlSortVector.h"
// NOTE: This has to be the last file included!
# include "tier0/memdbgon.h"
extern ConVar sv_pure_consensus ;
extern ConVar sv_pure_retiretime ;
extern ConVar sv_pure_trace ;
CPureServerWhitelist : : CCommand : : ~ CCommand ( )
{
}
CPureServerWhitelist * CPureServerWhitelist : : Create ( IFileSystem * pFileSystem )
{
CPureServerWhitelist * pRet = new CPureServerWhitelist ;
pRet - > Init ( pFileSystem ) ;
return pRet ;
}
CPureServerWhitelist : : CPureServerWhitelist ( )
{
m_pFileSystem = NULL ;
m_LoadCounter = 0 ;
m_RefCount = 1 ;
}
CPureServerWhitelist : : ~ CPureServerWhitelist ( )
{
Term ( ) ;
}
void CPureServerWhitelist : : Init ( IFileSystem * pFileSystem )
{
Term ( ) ;
m_pFileSystem = pFileSystem ;
}
void CPureServerWhitelist : : Load ( int iPureMode )
{
Term ( ) ;
// Not pure at all?
if ( iPureMode < 0 )
return ;
// Load base trusted keys
{
KeyValues * kv = new KeyValues ( " " ) ;
bool bLoaded = kv - > LoadFromFile ( g_pFileSystem , " cfg/trusted_keys_base.txt " , " game " ) ;
if ( bLoaded )
bLoaded = LoadTrustedKeysFromKeyValues ( kv ) ;
else
Warning ( " Error loading cfg/trusted_keys_base.txt \n " ) ;
kv - > deleteThis ( ) ;
}
// sv_pure 0: minimal rules only
if ( iPureMode = = 0 )
{
KeyValues * kv = new KeyValues ( " " ) ;
bool bLoaded = kv - > LoadFromFile ( g_pFileSystem , " cfg/pure_server_minimal.txt " , " game " ) ;
if ( bLoaded )
bLoaded = LoadCommandsFromKeyValues ( kv ) ;
else
Warning ( " Error loading cfg/pure_server_minimal.txt \n " ) ;
kv - > deleteThis ( ) ;
return ;
}
// Load up full pure rules
{
KeyValues * kv = new KeyValues ( " " ) ;
bool bLoaded = kv - > LoadFromFile ( g_pFileSystem , " cfg/pure_server_full.txt " , " game " ) ;
if ( bLoaded )
bLoaded = LoadCommandsFromKeyValues ( kv ) ;
else
Warning ( " Error loading cfg/pure_server_full.txt \n " ) ;
kv - > deleteThis ( ) ;
}
// Now load user customizations
if ( iPureMode = = 1 )
{
// Load custom whitelist
KeyValues * kv = new KeyValues ( " " ) ;
bool bLoaded = kv - > LoadFromFile ( g_pFileSystem , " cfg/pure_server_whitelist.txt " , " game " ) ;
if ( ! bLoaded )
// Check the old location
bLoaded = kv - > LoadFromFile ( g_pFileSystem , " pure_server_whitelist.txt " , " game " ) ;
if ( bLoaded )
bLoaded = LoadCommandsFromKeyValues ( kv ) ;
else
Msg ( " pure_server_whitelist.txt not present; pure server using only base file rules \n " ) ;
kv - > deleteThis ( ) ;
// Load custom trusted keys
kv = new KeyValues ( " " ) ;
bLoaded = kv - > LoadFromFile ( g_pFileSystem , " cfg/trusted_keys.txt " , " game " ) ;
if ( bLoaded )
bLoaded = LoadTrustedKeysFromKeyValues ( kv ) ;
else
Msg ( " trusted_keys.txt not present; pure server using only base trusted key list \n " ) ;
kv - > deleteThis ( ) ;
}
// Hardcoded rules last
AddHardcodedFileCommands ( ) ;
}
bool operator = = ( const PureServerPublicKey_t & a , const PureServerPublicKey_t & b )
{
return a . Count ( ) = = b . Count ( ) & & V_memcmp ( a . Base ( ) , b . Base ( ) , a . Count ( ) ) = = 0 ;
}
bool CPureServerWhitelist : : CommandDictDifferent ( const CUtlDict < CCommand * , int > & a , const CUtlDict < CCommand * , int > & b )
{
FOR_EACH_DICT ( a , idxA )
{
if ( a [ idxA ] - > m_LoadOrder = = kLoadOrder_HardcodedOverride )
continue ;
int idxB = b . Find ( a . GetElementName ( idxA ) ) ;
if ( ! b . IsValidIndex ( idxB ) )
return true ;
if ( b [ idxB ] - > m_LoadOrder = = kLoadOrder_HardcodedOverride )
continue ;
if ( a [ idxA ] - > m_eFileClass ! = b [ idxB ] - > m_eFileClass
| | a [ idxA ] - > m_LoadOrder ! = b [ idxB ] - > m_LoadOrder )
return true ;
}
return false ;
}
bool CPureServerWhitelist : : operator = = ( const CPureServerWhitelist & x ) const
{
// Compare rule dictionaries
if ( CommandDictDifferent ( m_FileCommands , x . m_FileCommands )
| | CommandDictDifferent ( x . m_FileCommands , m_FileCommands )
| | CommandDictDifferent ( m_RecursiveDirCommands , x . m_RecursiveDirCommands )
| | CommandDictDifferent ( x . m_RecursiveDirCommands , m_RecursiveDirCommands )
| | CommandDictDifferent ( m_NonRecursiveDirCommands , x . m_NonRecursiveDirCommands )
| | CommandDictDifferent ( x . m_NonRecursiveDirCommands , m_NonRecursiveDirCommands ) )
return false ;
// Compare trusted key list
if ( m_vecTrustedKeys . Count ( ) ! = x . m_vecTrustedKeys . Count ( ) )
return false ;
for ( int i = 0 ; i < m_vecTrustedKeys . Count ( ) ; + + i )
if ( ! ( m_vecTrustedKeys [ i ] = = x . m_vecTrustedKeys [ i ] ) )
return false ;
// they are the same
return true ;
}
void CPureServerWhitelist : : Term ( )
{
m_FileCommands . PurgeAndDeleteElements ( ) ;
m_RecursiveDirCommands . PurgeAndDeleteElements ( ) ;
m_NonRecursiveDirCommands . PurgeAndDeleteElements ( ) ;
m_vecTrustedKeys . Purge ( ) ;
m_LoadCounter = 0 ;
}
bool CPureServerWhitelist : : LoadCommandsFromKeyValues ( KeyValues * kv )
{
for ( KeyValues * pCurItem = kv - > GetFirstValue ( ) ; pCurItem ; pCurItem = pCurItem - > GetNextValue ( ) )
{
char szPathName [ MAX_PATH ] ;
const char * pKeyValue = pCurItem - > GetName ( ) ;
const char * pModifiers = pCurItem - > GetString ( ) ;
if ( ! pKeyValue | | ! pModifiers )
continue ;
Q_strncpy ( szPathName , pKeyValue , sizeof ( szPathName ) ) ;
Q_FixSlashes ( szPathName ) ;
const char * pValue = szPathName ;
// Figure out the modifiers.
bool bFromTrustedSource = false , bAllowFromDisk = false , bCheckCRC = false , bAny = false ;
CUtlVector < char * > mods ;
V_SplitString ( pModifiers , " + " , mods ) ;
for ( int i = 0 ; i < mods . Count ( ) ; i + + )
{
if (
V_stricmp ( mods [ i ] , " from_steam " ) = = 0
| | V_stricmp ( mods [ i ] , " trusted_source " ) = = 0
)
bFromTrustedSource = true ;
else if ( V_stricmp ( mods [ i ] , " allow_from_disk " ) = = 0 )
bAllowFromDisk = true ;
else if (
V_stricmp ( mods [ i ] , " check_crc " ) = = 0
| | V_stricmp ( mods [ i ] , " check_hash " ) = = 0
)
bCheckCRC = true ;
else if ( V_stricmp ( mods [ i ] , " any " ) = = 0 )
bAny = true ;
else
Warning ( " Unknown modifier in whitelist file: %s. \n " , mods [ i ] ) ;
}
2023-10-07 10:04:46 +08:00
mods . PurgeAndDeleteElementsArray ( ) ;
2020-04-23 00:56:21 +08:00
if (
( bFromTrustedSource & & ( bAllowFromDisk | | bCheckCRC | | bAny ) )
| | ( bAny & & bCheckCRC ) )
{
Warning ( " Whitelist: incompatible flags used on %s. \n " , pValue ) ;
continue ;
}
EPureServerFileClass eFileClass ;
if ( bCheckCRC )
eFileClass = ePureServerFileClass_CheckHash ;
else if ( bFromTrustedSource )
eFileClass = ePureServerFileClass_AnyTrusted ;
else
eFileClass = ePureServerFileClass_Any ;
// Setup a new CCommand to hold this command.
AddFileCommand ( pValue , eFileClass , m_LoadCounter + + ) ;
}
return true ;
}
void CPureServerWhitelist : : AddHardcodedFileCommands ( )
{
AddFileCommand ( " materials/vgui/replay/thumbnails/... " , ePureServerFileClass_Any , kLoadOrder_HardcodedOverride ) ;
AddFileCommand ( " sound/ui/hitsound.wav " , ePureServerFileClass_Any , kLoadOrder_HardcodedOverride ) ;
AddFileCommand ( " sound/ui/killsound.wav " , ePureServerFileClass_Any , kLoadOrder_HardcodedOverride ) ;
AddFileCommand ( " materials/vgui/logos/... " , ePureServerFileClass_Any , kLoadOrder_HardcodedOverride ) ;
}
void CPureServerWhitelist : : AddFileCommand ( const char * pszFilePath , EPureServerFileClass eFileClass , unsigned short nLoadOrder )
{
CPureServerWhitelist : : CCommand * pCommand = new CPureServerWhitelist : : CCommand ;
pCommand - > m_LoadOrder = nLoadOrder ;
pCommand - > m_eFileClass = eFileClass ;
// Figure out if they're referencing a file, a recursive directory, or a nonrecursive directory.
CUtlDict < CCommand * , int > * pList ;
const char * pEndPart = V_UnqualifiedFileName ( pszFilePath ) ;
if ( Q_stricmp ( pEndPart , " ... " ) = = 0 )
pList = & m_RecursiveDirCommands ;
else if ( Q_stricmp ( pEndPart , " *.* " ) = = 0 )
pList = & m_NonRecursiveDirCommands ;
else
pList = & m_FileCommands ;
// If it's a directory command, get rid of the *.* or ...
char filePath [ MAX_PATH ] ;
if ( pList = = & m_RecursiveDirCommands | | pList = = & m_NonRecursiveDirCommands )
V_ExtractFilePath ( pszFilePath , filePath , sizeof ( filePath ) ) ;
else
V_strncpy ( filePath , pszFilePath , sizeof ( filePath ) ) ;
V_FixSlashes ( filePath ) ;
int idxExisting = pList - > Find ( filePath ) ;
if ( idxExisting ! = pList - > InvalidIndex ( ) )
{
delete pList - > Element ( idxExisting ) ;
pList - > RemoveAt ( idxExisting ) ;
}
pList - > Insert ( filePath , pCommand ) ;
}
bool CPureServerWhitelist : : LoadTrustedKeysFromKeyValues ( KeyValues * kv )
{
for ( KeyValues * pCurItem = kv - > GetFirstTrueSubKey ( ) ; pCurItem ; pCurItem = pCurItem - > GetNextTrueSubKey ( ) )
{
if ( V_stricmp ( pCurItem - > GetName ( ) , " public_key " ) ! = 0 )
{
Warning ( " Trusted key list has unexpected block '%s'; expected only 'public_key' blocks \n " , pCurItem - > GetName ( ) ) ;
continue ;
}
const char * pszType = pCurItem - > GetString ( " type " , " (none) " ) ;
if ( V_stricmp ( pszType , " rsa " ) ! = 0 )
{
Warning ( " Trusted key type '%s' not supported. \n " , pszType ) ;
continue ;
}
const char * pszKeyData = pCurItem - > GetString ( " rsa_public_key " , " " ) ;
if ( * pszKeyData = = ' \0 ' )
{
Warning ( " trusted key is missing 'rsa_public_key' data; ignored \n " ) ;
continue ;
}
PureServerPublicKey_t & key = m_vecTrustedKeys [ m_vecTrustedKeys . AddToTail ( ) ] ;
int nKeyDataLen = V_strlen ( pszKeyData ) ;
key . SetSize ( nKeyDataLen / 2 ) ;
// Aaaannnnnnnnddddd V_hextobinary has no return code.
// Because nobody could *ever* possible attempt to parse bad data. It could never possibly happen.
V_hextobinary ( pszKeyData , nKeyDataLen , key . Base ( ) , key . Count ( ) ) ;
}
return true ;
}
void CPureServerWhitelist : : UpdateCommandStats ( CUtlDict < CPureServerWhitelist : : CCommand * , int > & commands , int * pHighest , int * pLongestPathName )
{
for ( int i = commands . First ( ) ; i ! = commands . InvalidIndex ( ) ; i = commands . Next ( i ) )
{
* pHighest = max ( * pHighest , ( int ) commands [ i ] - > m_LoadOrder ) ;
int len = V_strlen ( commands . GetElementName ( i ) ) ;
* pLongestPathName = max ( * pLongestPathName , len ) ;
}
}
void CPureServerWhitelist : : PrintCommand ( const char * pFileSpec , const char * pExt , int maxPathnameLen , CPureServerWhitelist : : CCommand * pCommand )
{
// Get rid of the trailing slash if there is one.
char tempFileSpec [ MAX_PATH ] ;
V_strncpy ( tempFileSpec , pFileSpec , sizeof ( tempFileSpec ) ) ;
int len = V_strlen ( tempFileSpec ) ;
if ( len > 0 & & ( tempFileSpec [ len - 1 ] = = ' / ' | | tempFileSpec [ len - 1 ] = = ' \\ ' ) )
tempFileSpec [ len - 1 ] = 0 ;
CUtlString buf ;
if ( pExt )
buf . Format ( " %s%c%s " , tempFileSpec , CORRECT_PATH_SEPARATOR , pExt ) ;
else
buf . Format ( " %s " , tempFileSpec ) ;
len = V_strlen ( pFileSpec ) ;
for ( int i = len ; i < maxPathnameLen + 6 ; i + + )
{
buf + = " " ;
}
buf + = " \t " ;
switch ( pCommand - > m_eFileClass )
{
default :
buf + = va ( " (bogus value %d) " , ( int ) pCommand - > m_eFileClass ) ;
Assert ( false ) ;
break ;
case ePureServerFileClass_Any :
buf + = " any " ;
break ;
case ePureServerFileClass_AnyTrusted :
buf + = " trusted_source " ;
break ;
case ePureServerFileClass_CheckHash :
buf + = " check_crc " ;
break ;
}
Msg ( " %s \n " , buf . String ( ) ) ;
}
int CPureServerWhitelist : : FindCommandByLoadOrder ( CUtlDict < CPureServerWhitelist : : CCommand * , int > & commands , int iLoadOrder )
{
for ( int i = commands . First ( ) ; i ! = commands . InvalidIndex ( ) ; i = commands . Next ( i ) )
{
if ( commands [ i ] - > m_LoadOrder = = iLoadOrder )
return i ;
}
return - 1 ;
}
void CPureServerWhitelist : : PrintWhitelistContents ( )
{
int highestLoadOrder = 0 , longestPathName = 0 ;
UpdateCommandStats ( m_FileCommands , & highestLoadOrder , & longestPathName ) ;
UpdateCommandStats ( m_RecursiveDirCommands , & highestLoadOrder , & longestPathName ) ;
UpdateCommandStats ( m_NonRecursiveDirCommands , & highestLoadOrder , & longestPathName ) ;
for ( int iLoadOrder = 0 ; iLoadOrder < = highestLoadOrder ; iLoadOrder + + )
{
// Check regular file commands.
int iCommand = FindCommandByLoadOrder ( m_FileCommands , iLoadOrder ) ;
if ( iCommand ! = - 1 )
{
PrintCommand ( m_FileCommands . GetElementName ( iCommand ) , NULL , longestPathName , m_FileCommands [ iCommand ] ) ;
}
else
{
// Check recursive commands.
iCommand = FindCommandByLoadOrder ( m_RecursiveDirCommands , iLoadOrder ) ;
if ( iCommand ! = - 1 )
{
PrintCommand ( m_RecursiveDirCommands . GetElementName ( iCommand ) , " ... " , longestPathName , m_RecursiveDirCommands [ iCommand ] ) ;
}
else
{
// Check *.* commands.
iCommand = FindCommandByLoadOrder ( m_NonRecursiveDirCommands , iLoadOrder ) ;
if ( iCommand ! = - 1 )
{
PrintCommand ( m_NonRecursiveDirCommands . GetElementName ( iCommand ) , " *.* " , longestPathName , m_NonRecursiveDirCommands [ iCommand ] ) ;
}
}
}
}
}
void CPureServerWhitelist : : Encode ( CUtlBuffer & buf )
{
// Put dummy version number
buf . PutUnsignedInt ( 0xffff ) ;
// Encode rules
EncodeCommandList ( m_FileCommands , buf ) ;
EncodeCommandList ( m_RecursiveDirCommands , buf ) ;
EncodeCommandList ( m_NonRecursiveDirCommands , buf ) ;
// Encode trusted keys
buf . PutUnsignedInt ( m_vecTrustedKeys . Count ( ) ) ;
FOR_EACH_VEC ( m_vecTrustedKeys , i )
{
uint32 nKeySize = m_vecTrustedKeys [ i ] . Count ( ) ;
buf . PutUnsignedInt ( nKeySize ) ;
buf . Put ( m_vecTrustedKeys [ i ] . Base ( ) , nKeySize ) ;
}
}
void CPureServerWhitelist : : EncodeCommandList ( CUtlDict < CPureServerWhitelist : : CCommand * , int > & theList , CUtlBuffer & buf )
{
// Count how many we're really going to write
int nCount = 0 ;
for ( int i = theList . First ( ) ; i ! = theList . InvalidIndex ( ) ; i = theList . Next ( i ) )
{
if ( theList [ i ] - > m_LoadOrder ! = kLoadOrder_HardcodedOverride )
+ + nCount ;
}
buf . PutInt ( nCount ) ;
// Write them
for ( int i = theList . First ( ) ; i ! = theList . InvalidIndex ( ) ; i = theList . Next ( i ) )
{
CPureServerWhitelist : : CCommand * pCommand = theList [ i ] ;
if ( pCommand - > m_LoadOrder = = kLoadOrder_HardcodedOverride )
continue ;
unsigned char val = ( unsigned char ) pCommand - > m_eFileClass ;
buf . PutUnsignedChar ( val ) ;
buf . PutUnsignedShort ( pCommand - > m_LoadOrder ) ;
buf . PutString ( theList . GetElementName ( i ) ) ;
}
}
void CPureServerWhitelist : : Decode ( CUtlBuffer & buf )
{
Term ( ) ;
uint32 nVersionTag = * ( uint32 * ) buf . PeekGet ( ) ;
uint32 nFormatVersion = 0 ;
if ( nVersionTag = = 0xffff )
{
buf . GetUnsignedInt ( ) ;
nFormatVersion = 1 ;
}
else
{
// Talking to legacy server -- load up default rules,
// the rest of his list are supposed to be exceptions to
// the base
Load ( 2 ) ;
}
DecodeCommandList ( m_FileCommands , buf , nFormatVersion ) ;
DecodeCommandList ( m_RecursiveDirCommands , buf , nFormatVersion ) ;
DecodeCommandList ( m_NonRecursiveDirCommands , buf , nFormatVersion ) ;
// Hardcoded
AddHardcodedFileCommands ( ) ;
if ( nFormatVersion > = 1 )
{
uint32 nKeyCount = buf . GetUnsignedInt ( ) ;
m_vecTrustedKeys . SetCount ( nKeyCount ) ;
FOR_EACH_VEC ( m_vecTrustedKeys , i )
{
uint32 nKeySize = buf . GetUnsignedInt ( ) ;
m_vecTrustedKeys [ i ] . SetCount ( nKeySize ) ;
buf . Get ( m_vecTrustedKeys [ i ] . Base ( ) , nKeySize ) ;
}
}
}
void CPureServerWhitelist : : CacheFileCRCs ( )
{
InternalCacheFileCRCs ( m_FileCommands , k_eCacheCRCType_SingleFile ) ;
InternalCacheFileCRCs ( m_NonRecursiveDirCommands , k_eCacheCRCType_Directory ) ;
InternalCacheFileCRCs ( m_RecursiveDirCommands , k_eCacheCRCType_Directory_Recursive ) ;
}
// !SV_PURE FIXME! Do we need this?
void CPureServerWhitelist : : InternalCacheFileCRCs ( CUtlDict < CCommand * , int > & theList , ECacheCRCType eType )
{
// for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) )
// {
// CCommand *pCommand = theList[i];
// if ( pCommand->m_bCheckCRC )
// {
// const char *pPathname = theList.GetElementName( i );
// m_pFileSystem->CacheFileCRCs( pPathname, eType, &m_ForceMatchList );
// }
// }
}
void CPureServerWhitelist : : DecodeCommandList ( CUtlDict < CPureServerWhitelist : : CCommand * , int > & theList , CUtlBuffer & buf , uint32 nFormatVersion )
{
int nCommands = buf . GetInt ( ) ;
for ( int i = 0 ; i < nCommands ; i + + )
{
CPureServerWhitelist : : CCommand * pCommand = new CPureServerWhitelist : : CCommand ;
unsigned char val = buf . GetUnsignedChar ( ) ;
unsigned short nLoadOrder = buf . GetUnsignedShort ( ) ;
if ( nFormatVersion = = 0 )
{
pCommand - > m_eFileClass = ( val & 1 ) ? ePureServerFileClass_Any : ePureServerFileClass_AnyTrusted ;
pCommand - > m_LoadOrder = nLoadOrder + m_LoadCounter ;
}
else
{
pCommand - > m_eFileClass = ( EPureServerFileClass ) val ;
pCommand - > m_LoadOrder = nLoadOrder ;
}
char str [ MAX_PATH ] ;
buf . GetString ( str ) ;
V_FixSlashes ( str ) ;
theList . Insert ( str , pCommand ) ;
}
}
CPureServerWhitelist : : CCommand * CPureServerWhitelist : : GetBestEntry ( const char * pFilename )
{
// NOTE: Since this is a user-specified file, we don't have the added complexity of path IDs in here.
// So when the filesystem asks if a file is in the whitelist, we just ignore the path ID.
// Make sure we have a relative pathname with fixed slashes..
char relativeFilename [ MAX_PATH ] ;
V_strncpy ( relativeFilename , pFilename , sizeof ( relativeFilename ) ) ;
// Convert the path to relative if necessary.
if ( ! V_IsAbsolutePath ( relativeFilename ) | | m_pFileSystem - > FullPathToRelativePath ( pFilename , relativeFilename , sizeof ( relativeFilename ) ) )
{
V_FixSlashes ( relativeFilename ) ;
// Get the directory this thing is in.
char relativeDir [ MAX_PATH ] ;
if ( ! V_ExtractFilePath ( relativeFilename , relativeDir , sizeof ( relativeDir ) ) )
relativeDir [ 0 ] = 0 ;
// Check each of our dictionaries to see if there is an entry for this thing.
CCommand * pBestEntry = NULL ;
pBestEntry = CheckEntry ( m_FileCommands , relativeFilename , pBestEntry ) ;
if ( relativeDir [ 0 ] ! = 0 )
{
pBestEntry = CheckEntry ( m_NonRecursiveDirCommands , relativeDir , pBestEntry ) ;
while ( relativeDir [ 0 ] ! = 0 )
{
// Check for this directory.
pBestEntry = CheckEntry ( m_RecursiveDirCommands , relativeDir , pBestEntry ) ;
if ( ! V_StripLastDir ( relativeDir , sizeof ( relativeDir ) ) )
break ;
}
}
return pBestEntry ;
}
// Either we couldn't find an entry, or they specified an absolute path that we could not convert to a relative path.
return NULL ;
}
CPureServerWhitelist : : CCommand * CPureServerWhitelist : : CheckEntry (
CUtlDict < CPureServerWhitelist : : CCommand * , int > & dict ,
const char * pEntryName ,
CPureServerWhitelist : : CCommand * pBestEntry )
{
int i = dict . Find ( pEntryName ) ;
if ( i ! = dict . InvalidIndex ( ) & & ( ! pBestEntry | | dict [ i ] - > m_LoadOrder > pBestEntry - > m_LoadOrder ) )
pBestEntry = dict [ i ] ;
return pBestEntry ;
}
void CPureServerWhitelist : : AddRef ( )
{
ThreadInterlockedIncrement ( & m_RefCount ) ;
}
void CPureServerWhitelist : : Release ( )
{
if ( ThreadInterlockedDecrement ( & m_RefCount ) < = 0 )
delete this ;
}
int CPureServerWhitelist : : GetTrustedKeyCount ( ) const
{
return m_vecTrustedKeys . Count ( ) ;
}
const byte * CPureServerWhitelist : : GetTrustedKey ( int iKeyIndex , int * nKeySize ) const
{
Assert ( nKeySize ! = NULL ) ;
if ( ! m_vecTrustedKeys . IsValidIndex ( iKeyIndex ) )
{
* nKeySize = 0 ;
return NULL ;
}
* nKeySize = m_vecTrustedKeys [ iKeyIndex ] . Count ( ) ;
return m_vecTrustedKeys [ iKeyIndex ] . Base ( ) ;
}
EPureServerFileClass CPureServerWhitelist : : GetFileClass ( const char * pszFilename )
{
CCommand * pCommand = GetBestEntry ( pszFilename ) ;
if ( pCommand )
return pCommand - > m_eFileClass ;
// Default action is to be permissive. (The default whitelist protects certain directories and files at a root level.)
return ePureServerFileClass_Any ;
}
void CPureFileTracker : : AddUserReportedFileHash ( int idxFile , FileHash_t * pFileHash , USERID_t userID , bool bAddMasterRecord )
{
UserReportedFileHash_t userFileHash ;
userFileHash . m_idxFile = idxFile ;
userFileHash . m_userID = userID ;
userFileHash . m_FileHash = * pFileHash ;
int idxUserReported = m_treeUserReportedFileHash . Find ( userFileHash ) ;
if ( idxUserReported = = m_treeUserReportedFileHash . InvalidIndex ( ) )
{
idxUserReported = m_treeUserReportedFileHash . Insert ( userFileHash ) ;
if ( bAddMasterRecord )
{
// count the number of matches for this idxFile
// if it exceeds > 5 then make a master record
int idxFirst = idxUserReported ;
int idxLast = idxUserReported ;
int ctMatches = 1 ;
int ctTotalFiles = 1 ;
// first go forward
int idx = m_treeUserReportedFileHash . NextInorder ( idxUserReported ) ;
while ( idx ! = m_treeUserReportedFileHash . InvalidIndex ( ) & & m_treeUserReportedFileHash [ idx ] . m_idxFile = = m_treeUserReportedFileHash [ idxUserReported ] . m_idxFile )
{
if ( m_treeUserReportedFileHash [ idx ] . m_FileHash = = m_treeUserReportedFileHash [ idxUserReported ] . m_FileHash )
ctMatches + + ;
ctTotalFiles + + ;
idxLast = idx ;
idx = m_treeUserReportedFileHash . NextInorder ( idx ) ;
}
// then backwards
idx = m_treeUserReportedFileHash . PrevInorder ( idxUserReported ) ;
while ( idx ! = m_treeUserReportedFileHash . InvalidIndex ( ) & & m_treeUserReportedFileHash [ idx ] . m_idxFile = = m_treeUserReportedFileHash [ idxUserReported ] . m_idxFile )
{
if ( m_treeUserReportedFileHash [ idx ] . m_FileHash = = m_treeUserReportedFileHash [ idxUserReported ] . m_FileHash )
ctMatches + + ;
ctTotalFiles + + ;
idxFirst = idx ;
idx = m_treeUserReportedFileHash . PrevInorder ( idx ) ;
}
// if ctTotalFiles >> ctMatches then that means clients are reading different bits from the file.
// in order to get this right we need to ask them to read the entire thing
if ( ctMatches > = sv_pure_consensus . GetInt ( ) )
{
MasterFileHash_t masterFileHashNew ;
masterFileHashNew . m_idxFile = m_treeUserReportedFileHash [ idxUserReported ] . m_idxFile ;
masterFileHashNew . m_cMatches = ctMatches ;
masterFileHashNew . m_FileHash = m_treeUserReportedFileHash [ idxUserReported ] . m_FileHash ;
m_treeMasterFileHashes . Insert ( masterFileHashNew ) ;
// remove all the individual records that matched the new master, we don't need them anymore
int idxRemove = idxFirst ;
while ( idxRemove ! = m_treeUserReportedFileHash . InvalidIndex ( ) )
{
int idxNext = m_treeUserReportedFileHash . NextInorder ( idxRemove ) ;
if ( m_treeUserReportedFileHash [ idxRemove ] . m_FileHash = = m_treeUserReportedFileHash [ idxUserReported ] . m_FileHash )
m_treeUserReportedFileHash . RemoveAt ( idxRemove ) ;
if ( idxRemove = = idxLast )
break ;
idxRemove = idxNext ;
}
}
}
}
else
{
m_treeUserReportedFileHash [ idxUserReported ] . m_FileHash = * pFileHash ;
}
// we dont have enough data to decide if you match or not yet - so we call it a match
}
void FileRenderHelper ( USERID_t userID , const char * pchMessage , const char * pchPath , const char * pchFileName , FileHash_t * pFileHash , int nFileFraction , FileHash_t * pFileHashLocal )
{
char rgch [ 256 ] ;
char hex [ 34 ] ;
Q_memset ( hex , 0 , sizeof ( hex ) ) ;
Q_binarytohex ( ( const byte * ) & pFileHash - > m_md5contents . bits , sizeof ( pFileHash - > m_md5contents . bits ) , hex , sizeof ( hex ) ) ;
char hex2 [ 34 ] ;
Q_memset ( hex2 , 0 , sizeof ( hex2 ) ) ;
if ( pFileHashLocal )
Q_binarytohex ( ( const byte * ) & pFileHashLocal - > m_md5contents . bits , sizeof ( pFileHashLocal - > m_md5contents . bits ) , hex2 , sizeof ( hex2 ) ) ;
if ( pFileHash - > m_PackFileID )
{
Q_snprintf ( rgch , 256 , " Pure server: file: %s \\ %s ( %d %d %8.8x %6.6x ) %s : %s : %s \n " ,
pchPath , pchFileName ,
pFileHash - > m_PackFileID , pFileHash - > m_nPackFileNumber , nFileFraction , pFileHash - > m_cbFileLen ,
pchMessage ,
hex , hex2 ) ;
}
else
{
Q_snprintf ( rgch , 256 , " Pure server: file: %s \\ %s ( %d %d %x ) %s : %s : %s \n " ,
pchPath , pchFileName ,
pFileHash - > m_eFileHashType , pFileHash - > m_cbFileLen , pFileHash - > m_eFileHashType ? pFileHash - > m_crcIOSequence : 0 ,
pchMessage ,
hex , hex2 ) ;
}
if ( userID . idtype ! = 0 )
Msg ( " [%s] %s \n " , GetUserIDString ( userID ) , rgch ) ;
else
Msg ( " %s " , rgch ) ;
}
bool CPureFileTracker : : DoesFileMatch ( const char * pPathID , const char * pRelativeFilename , int nFileFraction , FileHash_t * pFileHash , USERID_t userID )
{
// // if the server has been idle for more than 15 minutes, discard all this data
// const float flRetireTime = sv_pure_retiretime.GetFloat();
// float flCurTime = Plat_FloatTime();
// if ( ( flCurTime - m_flLastFileReceivedTime ) > flRetireTime )
// {
// m_treeMasterFileHashes.RemoveAll();
// m_treeUserReportedFileHash.RemoveAll();
// m_treeMasterFileHashes.RemoveAll();
// }
// m_flLastFileReceivedTime = flCurTime;
//
// // The clients must send us all files. We decide if it is whitelisted or not
// // That way the clients can not hide modified files in a whitelisted directory
// if ( pFileHash->m_PackFileID == 0 &&
// sv.GetPureServerWhitelist()->GetFileClass( pRelativeFilename ) != ePureServerFileClass_CheckHash )
// {
//
// if ( sv_pure_trace.GetInt() == 4 )
// {
// char warningStr[1024] = {0};
// V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s ignored by whitelist.", pPathID, pRelativeFilename );
// Msg( "[%s] %s\n", GetUserIDString(userID), warningStr );
// }
//
// return true;
// }
//
// char rgchFilenameFixed[MAX_PATH];
// Q_strncpy( rgchFilenameFixed, pRelativeFilename, sizeof( rgchFilenameFixed ) );
// Q_FixSlashes( rgchFilenameFixed );
//
// // first look up the file and see if we have ever seen it before
// CRC32_t crcFilename;
// CRC32_Init( &crcFilename );
// CRC32_ProcessBuffer( &crcFilename, rgchFilenameFixed, Q_strlen( rgchFilenameFixed ) );
// CRC32_ProcessBuffer( &crcFilename, pPathID, Q_strlen( pPathID ) );
// CRC32_Final( &crcFilename );
// UserReportedFile_t ufile;
// ufile.m_crcIdentifier = crcFilename;
// ufile.m_filename = rgchFilenameFixed;
// ufile.m_path = pPathID;
// ufile.m_nFileFraction = nFileFraction;
// int idxFile = m_treeAllReportedFiles.Find( ufile );
// if ( idxFile == m_treeAllReportedFiles.InvalidIndex() )
// {
// idxFile = m_treeAllReportedFiles.Insert( ufile );
// }
// else
// {
// m_cMatchedFile++;
// }
// // then check if we have a master CRC for the file
// MasterFileHash_t masterFileHash;
// masterFileHash.m_idxFile = idxFile;
// int idxMaster = m_treeMasterFileHashes.Find( masterFileHash );
// // dont do anything with this yet
//
// // check to see if we have loaded the file locally and can match it
// FileHash_t filehashLocal;
// EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal );
// if ( eStatus == k_eFileCRCStatus_FileInVPK)
// {
// // you managed to load a file outside a VPK that the server has in the VPK
// // this is possible if the user explodes the VPKs into individual files and then deletes the VPKs
// FileRenderHelper( userID, "file should be in VPK", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
// return false;
// }
// // if the user sent us a full file hash, but we dont have one, hash it now
// if ( pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
// ( eStatus != k_eFileCRCStatus_GotCRC || filehashLocal.m_eFileHashType != FileHash_t::k_EFileHashTypeEntireFile ) )
// {
// // lets actually read the file so we get a complete file hash
// FileHandle_t f = g_pFileSystem->Open( rgchFilenameFixed, "rb", pPathID);
// // try to load the file and really compute the hash - should only have to do this once ever
// if ( f )
// {
// // load file into a null-terminated buffer
// int fileSize = g_pFileSystem->Size( f );
// unsigned bufSize = g_pFileSystem->GetOptimalReadSize( f, fileSize );
//
// char *buffer = (char*)g_pFileSystem->AllocOptimalReadBuffer( f, bufSize );
// Assert( buffer );
//
// // read into local buffer
// bool bRetOK = ( g_pFileSystem->ReadEx( buffer, bufSize, fileSize, f ) != 0 );
// bRetOK;
// g_pFileSystem->FreeOptimalReadBuffer( buffer );
//
// g_pFileSystem->Close( f ); // close file after reading
//
// eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal );
// }
// else
// {
// // what should we do if we couldn't open the file? should probably kick
// FileRenderHelper( userID, "could not open file to hash ( benign for now )", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
// }
// }
// if ( eStatus == k_eFileCRCStatus_GotCRC )
// {
// if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
// pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile )
// {
// if ( filehashLocal == *pFileHash )
// {
// m_cMatchedFileFullHash++;
// return true;
// }
// else
// {
// // don't need to check anything else
// // did not match - record so that we have a record of the file that did not match ( just for reporting )
// AddUserReportedFileHash( idxFile, pFileHash, userID, false );
// FileRenderHelper( userID, "file does not match", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, &filehashLocal );
// return false;
// }
// }
// }
//
// // if this is a VPK file, we have completely cataloged all the VPK files, so no suprises are allowed
// if ( pFileHash->m_PackFileID )
// {
// AddUserReportedFileHash( idxFile, pFileHash, userID, false );
// FileRenderHelper( userID, "unrecognized vpk file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
// return false;
// }
//
//
// // now lets see if we have a master file hash for this
// if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() )
// {
// m_cMatchedMasterFile++;
//
// FileHash_t *pFileHashLocal = &m_treeMasterFileHashes[idxMaster].m_FileHash;
// if ( *pFileHashLocal == *pFileHash )
// {
// m_cMatchedMasterFileHash++;
// return true;
// }
// else
// {
// // did not match - record so that we have a record of the file that did not match ( just for reporting )
// AddUserReportedFileHash( idxFile, pFileHash, userID, false );
// // and then return failure
// FileRenderHelper( userID, "file does not match server master file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, pFileHashLocal );
// return false;
// }
// }
//
// // no master record, accumulate individual record so we can get a consensus
// if ( sv_pure_trace.GetInt() == 3 )
// {
// FileRenderHelper( userID, "server does not have hash for this file. Waiting for consensus", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
// }
//
// AddUserReportedFileHash( idxFile, pFileHash, userID, true );
// we dont have enough data to decide if you match or not yet - so we call it a match
return true ;
}
struct FindFileIndex_t
{
int idxFindFile ;
} ;
class CStupidLess
{
public :
bool Less ( const FindFileIndex_t & src1 , const FindFileIndex_t & src2 , void * pCtx )
{
if ( src1 . idxFindFile < src2 . idxFindFile )
return true ;
return false ;
}
} ;
int CPureFileTracker : : ListUserFiles ( bool bListAll , const char * pchFilenameFind )
{
CUtlSortVector < FindFileIndex_t , CStupidLess > m_vecReportedFiles ;
int idxFindFile = m_treeAllReportedFiles . FirstInorder ( ) ;
while ( idxFindFile ! = m_treeAllReportedFiles . InvalidIndex ( ) )
{
UserReportedFile_t & ufile = m_treeAllReportedFiles [ idxFindFile ] ;
if ( pchFilenameFind & & Q_stristr ( ufile . m_filename , pchFilenameFind ) )
{
FileHash_t filehashLocal ;
EFileCRCStatus eStatus = g_pFileSystem - > CheckCachedFileHash ( ufile . m_path . String ( ) , ufile . m_filename . String ( ) , 0 , & filehashLocal ) ;
if ( eStatus = = k_eFileCRCStatus_GotCRC )
{
USERID_t useridFake ;
useridFake . idtype = IDTYPE_STEAM ;
FileRenderHelper ( useridFake , " Found: " , ufile . m_path . String ( ) , ufile . m_filename . String ( ) , & filehashLocal , 0 , NULL ) ;
FindFileIndex_t ffi ;
ffi . idxFindFile = idxFindFile ;
m_vecReportedFiles . Insert ( ffi ) ;
}
else
{
Msg ( " File not found %s %s %x \n " , ufile . m_filename . String ( ) , ufile . m_path . String ( ) , idxFindFile ) ;
}
}
idxFindFile = m_treeAllReportedFiles . NextInorder ( idxFindFile ) ;
}
int cTotalFiles = 0 ;
int cTotalMatches = 0 ;
int idx = m_treeUserReportedFileHash . FirstInorder ( ) ;
while ( idx ! = m_treeUserReportedFileHash . InvalidIndex ( ) )
{
UserReportedFileHash_t & file = m_treeUserReportedFileHash [ idx ] ;
int idxNext = m_treeUserReportedFileHash . NextInorder ( idx ) ;
int ctMatches = 1 ;
int ctFiles = 1 ;
// check this against all others for the same file
while ( idxNext ! = m_treeUserReportedFileHash . InvalidIndex ( ) & & m_treeUserReportedFileHash [ idx ] . m_idxFile = = m_treeUserReportedFileHash [ idxNext ] . m_idxFile )
{
if ( m_treeUserReportedFileHash [ idx ] . m_FileHash = = m_treeUserReportedFileHash [ idxNext ] . m_FileHash )
{
ctMatches + + ;
cTotalMatches + + ;
}
ctFiles + + ;
idxNext = m_treeUserReportedFileHash . NextInorder ( idxNext ) ;
}
idx = m_treeUserReportedFileHash . NextInorder ( idx ) ;
cTotalFiles + + ;
// do we have a master for this one?
MasterFileHash_t masterFileHashFind ;
masterFileHashFind . m_idxFile = file . m_idxFile ;
int idxMaster = m_treeMasterFileHashes . Find ( masterFileHashFind ) ;
UserReportedFile_t & ufile = m_treeAllReportedFiles [ file . m_idxFile ] ;
bool bOutput = false ;
if ( Q_stristr ( ufile . m_filename . String ( ) , " bin \\ pak01 " ) ! = NULL | | Q_stristr ( ufile . m_filename . String ( ) , " .vpk " ) ! = NULL )
bOutput = true ;
else
{
FileHash_t filehashLocal ;
EFileCRCStatus eStatus = g_pFileSystem - > CheckCachedFileHash ( ufile . m_path . String ( ) , ufile . m_filename . String ( ) , 0 , & filehashLocal ) ;
if ( eStatus = = k_eFileCRCStatus_GotCRC )
{
if ( filehashLocal . m_eFileHashType = = FileHash_t : : k_EFileHashTypeEntireFile & &
file . m_FileHash . m_eFileHashType = = FileHash_t : : k_EFileHashTypeEntireFile & &
filehashLocal ! = file . m_FileHash )
{
bOutput = true ;
}
}
}
FindFileIndex_t ffi ;
ffi . idxFindFile = file . m_idxFile ;
if ( ctMatches ! = ctFiles | | idxMaster ! = m_treeMasterFileHashes . InvalidIndex ( ) | | bListAll | | ( pchFilenameFind & & m_vecReportedFiles . Find ( ffi ) ! = - 1 ) | | bOutput )
{
char rgch [ 256 ] ;
Q_snprintf ( rgch , 256 , " reports=%d matches=%d Hash details: " , ctFiles , ctMatches ) ;
FileHash_t * pFileHashMaster = NULL ;
if ( idxMaster ! = m_treeMasterFileHashes . InvalidIndex ( ) )
pFileHashMaster = & m_treeMasterFileHashes [ idxMaster ] . m_FileHash ;
FileRenderHelper ( file . m_userID , rgch , ufile . m_path . String ( ) , ufile . m_filename . String ( ) , & file . m_FileHash , 0 , pFileHashMaster ) ;
}
}
Msg ( " Total user files %d %d %d \n " , m_treeUserReportedFileHash . Count ( ) , cTotalFiles , cTotalMatches ) ;
Msg ( " Total files %d, total with authoritative hashes %d \n " , m_treeAllReportedFiles . Count ( ) , m_treeMasterFileHashes . Count ( ) ) ;
Msg ( " Matching files %d %d %d \n " , m_cMatchedFile , m_cMatchedMasterFile , m_cMatchedMasterFileHash ) ;
return 0 ;
}
int CPureFileTracker : : ListAllTrackedFiles ( bool bListAll , const char * pchFilenameFind , int nFileFractionMin , int nFileFractionMax )
{
g_pFileSystem - > MarkAllCRCsUnverified ( ) ;
int cTotal = 0 ;
int cTotalMatch = 0 ;
int count = 0 ;
do
{
CUnverifiedFileHash rgUnverifiedFiles [ 1 ] ;
count = g_pFileSystem - > GetUnverifiedFileHashes ( rgUnverifiedFiles , ARRAYSIZE ( rgUnverifiedFiles ) ) ;
if ( count & & ( bListAll | | ( pchFilenameFind & & Q_stristr ( rgUnverifiedFiles [ 0 ] . m_Filename , pchFilenameFind ) & & rgUnverifiedFiles [ 0 ] . m_nFileFraction > = nFileFractionMin & & rgUnverifiedFiles [ 0 ] . m_nFileFraction < = nFileFractionMax ) ) )
{
USERID_t useridFake ;
useridFake . idtype = IDTYPE_STEAM ;
FileRenderHelper ( useridFake , " " , rgUnverifiedFiles [ 0 ] . m_PathID , rgUnverifiedFiles [ 0 ] . m_Filename , & rgUnverifiedFiles [ 0 ] . m_FileHash , rgUnverifiedFiles [ 0 ] . m_nFileFraction , NULL ) ;
if ( rgUnverifiedFiles [ 0 ] . m_FileHash . m_PackFileID )
{
g_pFileSystem - > CheckVPKFileHash ( rgUnverifiedFiles [ 0 ] . m_FileHash . m_PackFileID , rgUnverifiedFiles [ 0 ] . m_FileHash . m_nPackFileNumber , rgUnverifiedFiles [ 0 ] . m_nFileFraction , rgUnverifiedFiles [ 0 ] . m_FileHash . m_md5contents ) ;
}
cTotalMatch + + ;
}
if ( count )
cTotal + + ;
} while ( count ) ;
Msg ( " Total files %d Matching files %d \n " , cTotal , cTotalMatch ) ;
return 0 ;
}
CPureFileTracker g_PureFileTracker ;
//#define DEBUG_PURE_SERVER
# ifdef DEBUG_PURE_SERVER
void CC_ListPureServerFiles ( const CCommand & args )
{
if ( ! sv . IsDedicated ( ) )
return ;
g_PureFileTracker . ListUserFiles ( args . ArgC ( ) > 1 & & ( atoi ( args [ 1 ] ) > 0 ) , NULL ) ;
}
static ConCommand svpurelistuserfiles ( " sv_pure_listuserfiles " , CC_ListPureServerFiles , " ListPureServerFiles " ) ;
void CC_PureServerFindFile ( const CCommand & args )
{
if ( ! sv . IsDedicated ( ) )
return ;
g_PureFileTracker . ListUserFiles ( false , args [ 1 ] ) ;
}
static ConCommand svpurefinduserfiles ( " sv_pure_finduserfiles " , CC_PureServerFindFile , " ListPureServerFiles " ) ;
void CC_PureServerListTrackedFiles ( const CCommand & args )
{
// BUGBUG! Because this code is in engine instead of server, it exists in the client - ugh!
// Remove this command from client before shipping for realz.
//if ( !sv.IsDedicated() )
// return;
int nFileFractionMin = args . ArgC ( ) > = 3 ? Q_atoi ( args [ 2 ] ) : 0 ;
int nFileFractionMax = args . ArgC ( ) > = 4 ? Q_atoi ( args [ 3 ] ) : nFileFractionMin ;
if ( nFileFractionMax < 0 )
nFileFractionMax = 0x7FFFFFFF ;
g_PureFileTracker . ListAllTrackedFiles ( args . ArgC ( ) < = 1 , args . ArgC ( ) > = 2 ? args [ 1 ] : NULL , nFileFractionMin , nFileFractionMax ) ;
}
static ConCommand svpurelistfiles ( " sv_pure_listfiles " , CC_PureServerListTrackedFiles , " ListPureServerFiles " ) ;
void CC_PureServerCheckVPKFiles ( const CCommand & args )
{
if ( sv . IsDedicated ( ) )
Plat_BeginWatchdogTimer ( 5 * 60 ) ; // reset watchdog timer to allow 5 minutes for the VPK check
g_pFileSystem - > CacheAllVPKFileHashes ( false , true ) ;
if ( sv . IsDedicated ( ) )
Plat_EndWatchdogTimer ( ) ;
}
static ConCommand svpurecheckvpks ( " sv_pure_checkvpk " , CC_PureServerCheckVPKFiles , " CheckPureServerVPKFiles " ) ;
# endif