2
0
mirror of https://github.com/FWGS/xash3d-fwgs synced 2024-11-29 05:20:23 +01:00
xash3d-fwgs/engine/common/identification.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

713 lines
14 KiB
C

/*
identification.c - unique id generation
Copyright (C) 2017 mittorn
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "common.h"
#include <fcntl.h>
#if !XASH_WIN32
#include <dirent.h>
#endif
static char id_md5[33];
static char id_customid[MAX_STRING];
/*
==========================================================
simple 64-bit one-hash-func bloom filter
should be enough to determine if device exist in identifier
==========================================================
*/
typedef uint64_t bloomfilter_t;
static bloomfilter_t id;
#define bf64_mask ((1U<<6)-1)
bloomfilter_t BloomFilter_Process( const char *buffer, int size )
{
dword crc32;
bloomfilter_t value = 0;
if( size <= 0 || size > 512 )
return 0;
CRC32_Init( &crc32 );
CRC32_ProcessBuffer( &crc32, buffer, size );
while( crc32 )
{
value |= ((uint64_t)1) << ( crc32 & bf64_mask );
crc32 = crc32 >> 6;
}
return value;
}
bloomfilter_t BloomFilter_ProcessStr( const char *buffer )
{
return BloomFilter_Process( buffer, Q_strlen( buffer ) );
}
uint BloomFilter_Weight( bloomfilter_t value )
{
int weight = 0;
while( value )
{
if( value & 1 )
weight++;
value = value >> 1;
#if _MSC_VER == 1200
value &= 0x7FFFFFFFFFFFFFFF;
#endif
}
return weight;
}
qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str )
{
bloomfilter_t value = BloomFilter_ProcessStr( str );
return (filter & value) == value;
}
/*
=============================================
IDENTIFICATION
=============================================
*/
#define MAXBITS_GEN 30
#define MAXBITS_CHECK MAXBITS_GEN + 6
qboolean ID_ProcessFile( bloomfilter_t *value, const char *path );
void ID_BloomFilter_f( void )
{
bloomfilter_t value = 0;
int i;
for( i = 1; i < Cmd_Argc(); i++ )
value |= BloomFilter_ProcessStr( Cmd_Argv( i ) );
Msg( "%d %016llX\n", BloomFilter_Weight( value ), value );
// test
// for( i = 1; i < Cmd_Argc(); i++ )
// Msg( "%s: %d\n", Cmd_Argv( i ), BloomFilter_ContainsString( value, Cmd_Argv( i ) ) );
}
qboolean ID_VerifyHEX( const char *hex )
{
uint chars = 0;
char prev = 0;
qboolean monotonic = true; // detect 11:22...
int weight = 0;
while( *hex++ )
{
char ch = Q_tolower( *hex );
if( ( ch >= 'a' && ch <= 'f') || ( ch >= '0' && ch <= '9' ) )
{
if( prev && ( ch - prev < -1 || ch - prev > 1 ) )
monotonic = false;
if( ch >= 'a' )
chars |= 1 << (ch - 'a' + 10);
else
chars |= 1 << (ch - '0');
prev = ch;
}
}
if( monotonic )
return false;
while( chars )
{
if( chars & 1 )
weight++;
chars = chars >> 1;
if( weight > 2 )
return true;
}
return false;
}
void ID_VerifyHEX_f( void )
{
if( ID_VerifyHEX( Cmd_Argv( 1 ) ) )
Msg( "Good\n" );
else
Msg( "Bad\n" );
}
#if XASH_LINUX
qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
{
int cpuinfofd = open( "/proc/cpuinfo", O_RDONLY );
char buffer[1024], *pbuf, *pbuf2;
int ret;
if( cpuinfofd < 0 )
return false;
if( (ret = read( cpuinfofd, buffer, 1023 ) ) < 0 )
return false;
close( cpuinfofd );
buffer[ret] = 0;
if( !ret )
return false;
pbuf = Q_strstr( buffer, "Serial" );
if( !pbuf )
return false;
pbuf += 6;
if( ( pbuf2 = Q_strchr( pbuf, '\n' ) ) )
*pbuf2 = 0;
else
pbuf2 = pbuf + Q_strlen( pbuf );
if( !ID_VerifyHEX( pbuf ) )
return false;
*value |= BloomFilter_Process( pbuf, pbuf2 - pbuf );
return true;
}
qboolean ID_ValidateNetDevice( const char *dev )
{
const char *prefix = "/sys/class/net";
byte *pfile;
int assignType;
// These devices are fake, their mac address is generated each boot, while assign_type is 0
if( !Q_strnicmp( dev, "ccmni", sizeof( "ccmni" ) ) ||
!Q_strnicmp( dev, "ifb", sizeof( "ifb" ) ) )
return false;
pfile = FS_LoadDirectFile( va( "%s/%s/addr_assign_type", prefix, dev ), NULL );
// if NULL, it may be old kernel
if( pfile )
{
assignType = Q_atoi( (char*)pfile );
Mem_Free( pfile );
// check is MAC address is constant
if( assignType != 0 )
return false;
}
return true;
}
int ID_ProcessNetDevices( bloomfilter_t *value )
{
const char *prefix = "/sys/class/net";
DIR *dir;
struct dirent *entry;
int count = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) && BloomFilter_Weight( *value ) < MAXBITS_GEN )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( !ID_ValidateNetDevice( entry->d_name ) )
continue;
count += ID_ProcessFile( value, va( "%s/%s/address", prefix, entry->d_name ) );
}
closedir( dir );
return count;
}
int ID_CheckNetDevices( bloomfilter_t value )
{
const char *prefix = "/sys/class/net";
DIR *dir;
struct dirent *entry;
int count = 0;
bloomfilter_t filter = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( !ID_ValidateNetDevice( entry->d_name ) )
continue;
if( ID_ProcessFile( &filter, va( "%s/%s/address", prefix, entry->d_name ) ) )
count += ( value & filter ) == filter, filter = 0;
}
closedir( dir );
return count;
}
void ID_TestCPUInfo_f( void )
{
bloomfilter_t value = 0;
if( ID_ProcessCPUInfo( &value ) )
Msg( "Got %016llX\n", value );
else
Msg( "Could not get serial\n" );
}
#endif
qboolean ID_ProcessFile( bloomfilter_t *value, const char *path )
{
int fd = open( path, O_RDONLY );
char buffer[256];
int ret;
if( fd < 0 )
return false;
if( (ret = read( fd, buffer, 255 ) ) < 0 )
return false;
close( fd );
if( !ret )
return false;
buffer[ret] = 0;
if( !ID_VerifyHEX( buffer ) )
return false;
*value |= BloomFilter_Process( buffer, ret );
return true;
}
#if !XASH_WIN32
int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix )
{
DIR *dir;
struct dirent *entry;
int count = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) && BloomFilter_Weight( *value ) < MAXBITS_GEN )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
count += ID_ProcessFile( value, va( "%s/%s/%s", prefix, entry->d_name, postfix ) );
}
closedir( dir );
return count;
}
int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix )
{
DIR *dir;
struct dirent *entry;
int count = 0;
bloomfilter_t filter = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( ID_ProcessFile( &filter, va( "%s/%s/%s", prefix, entry->d_name, postfix ) ) )
count += ( value & filter ) == filter, filter = 0;
}
closedir( dir );
return count;
}
#else
int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData )
{
HKEY hKey;
if( RegOpenKeyEx( hRootKey, subKey, 0, KEY_QUERY_VALUE, &hKey ) != ERROR_SUCCESS )
return 0;
if( RegQueryValueEx( hKey, value, NULL, NULL, data, &cbData ) != ERROR_SUCCESS )
{
RegCloseKey( hKey );
return 0;
}
RegCloseKey( hKey );
return 1;
}
int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYTE data, DWORD cbData)
{
HKEY hKey;
if( RegCreateKey( hRootKey, subKey, &hKey ) != ERROR_SUCCESS )
return 0;
if( RegSetValueEx( hKey, value, 0, dwType, data, cbData ) != ERROR_SUCCESS )
{
RegCloseKey( hKey );
return 0;
}
RegCloseKey( hKey );
return 1;
}
#define BUFSIZE 4096
int ID_RunWMIC(char *buffer, const char *cmdline)
{
HANDLE g_IN_Rd = NULL;
HANDLE g_IN_Wr = NULL;
HANDLE g_OUT_Rd = NULL;
HANDLE g_OUT_Wr = NULL;
DWORD dwRead;
BOOL bSuccess = FALSE;
SECURITY_ATTRIBUTES saAttr;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
CreatePipe( &g_IN_Rd, &g_IN_Wr, &saAttr, 0 );
CreatePipe( &g_OUT_Rd, &g_OUT_Wr, &saAttr, 0 );
SetHandleInformation( g_IN_Wr, HANDLE_FLAG_INHERIT, 0 );
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = g_IN_Rd;
si.hStdOutput = g_OUT_Wr;
si.hStdError = g_OUT_Wr;
si.wShowWindow = SW_HIDE;
si.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess( NULL, (char*)cmdline, NULL, NULL, true, CREATE_NO_WINDOW , NULL, NULL, &si, &pi );
CloseHandle( g_OUT_Wr );
CloseHandle( g_IN_Wr );
WaitForSingleObject( pi.hProcess, 500 );
bSuccess = ReadFile( g_OUT_Rd, buffer, BUFSIZE, &dwRead, NULL );
buffer[BUFSIZE-1] = 0;
CloseHandle( g_IN_Rd );
CloseHandle( g_OUT_Rd );
return bSuccess;
}
int ID_ProcessWMIC( bloomfilter_t *value, const char *cmdline )
{
char buffer[BUFSIZE], token[BUFSIZE], *pbuf;
int count = 0;
if( !ID_RunWMIC( buffer, cmdline ) )
return 0;
pbuf = COM_ParseFile( buffer, token ); // Header
while( pbuf = COM_ParseFile( pbuf, token ) )
{
if( !ID_VerifyHEX( token ) )
continue;
*value |= BloomFilter_ProcessStr( token );
count ++;
}
return count;
}
int ID_CheckWMIC( bloomfilter_t value, const char *cmdline )
{
char buffer[BUFSIZE], token[BUFSIZE], *pbuf;
int count = 0;
if( !ID_RunWMIC( buffer, cmdline ) )
return 0;
pbuf = COM_ParseFile( buffer, token ); // Header
while( pbuf = COM_ParseFile( pbuf, token ) )
{
bloomfilter_t filter;
if( !ID_VerifyHEX( token ) )
continue;
filter = BloomFilter_ProcessStr( token );
count += ( filter & value ) == filter;
}
return count;
}
#endif
#if XASH_IOS
char *IOS_GetUDID( void );
#endif
bloomfilter_t ID_GenerateRawId( void )
{
bloomfilter_t value = 0;
int count = 0;
#if XASH_LINUX
#if XASH_ANDROID && !XASH_DEDICATED
{
const char *androidid = Android_GetAndroidID();
if( androidid && ID_VerifyHEX( androidid ) )
{
value |= BloomFilter_ProcessStr( androidid );
count ++;
}
}
#endif
count += ID_ProcessCPUInfo( &value );
count += ID_ProcessFiles( &value, "/sys/block", "device/cid" );
count += ID_ProcessNetDevices( &value );
#endif
#if XASH_WIN32
count += ID_ProcessWMIC( &value, "wmic path win32_physicalmedia get SerialNumber " );
count += ID_ProcessWMIC( &value, "wmic bios get serialnumber " );
#endif
#if XASH_IOS
{
value |= BloomFilter_ProcessStr(IOS_GetUDID());
count ++;
}
#endif
return value;
}
uint ID_CheckRawId( bloomfilter_t filter )
{
bloomfilter_t value = 0;
int count = 0;
#if XASH_LINUX
#if XASH_ANDROID && !XASH_DEDICATED
{
const char *androidid = Android_GetAndroidID();
if( androidid && ID_VerifyHEX( androidid ) )
{
value = BloomFilter_ProcessStr( androidid );
count += (filter & value) == value;
value = 0;
}
}
#endif
count += ID_CheckNetDevices( filter );
count += ID_CheckFiles( filter, "/sys/block", "device/cid" );
if( ID_ProcessCPUInfo( &value ) )
count += (filter & value) == value;
#endif
#if XASH_WIN32
count += ID_CheckWMIC( filter, "wmic path win32_physicalmedia get SerialNumber" );
count += ID_CheckWMIC( filter, "wmic bios get serialnumber" );
#endif
#if XASH_IOS
{
value = BloomFilter_ProcessStr(IOS_GetUDID());
count += (filter & value) == value;
value = 0;
}
#endif
#if 0
Msg( "ID_CheckRawId: %d\n", count );
#endif
return count;
}
#define SYSTEM_XOR_MASK 0x10331c2dce4c91db
#define GAME_XOR_MASK 0x7ffc48fbac1711f1
void ID_Check( void )
{
uint weight = BloomFilter_Weight( id );
uint mincount = weight >> 2;
if( mincount < 1 )
mincount = 1;
if( weight > MAXBITS_CHECK )
{
id = 0;
#if 0
Msg( "ID_Check(): fail %d\n", weight );
#endif
return;
}
if( ID_CheckRawId( id ) < mincount )
id = 0;
#if 0
Msg( "ID_Check(): success %d\n", weight );
#endif
}
const char *ID_GetMD5( void )
{
if( id_customid[0] )
return id_customid;
return id_md5;
}
/*
===============
ID_SetCustomClientID
===============
*/
void GAME_EXPORT ID_SetCustomClientID( const char *id )
{
if( !id )
return;
Q_strncpy( id_customid, id, sizeof( id_customid ) );
}
void ID_Init( void )
{
MD5Context_t hash = {0};
byte md5[16];
int i;
Cmd_AddCommand( "bloomfilter", ID_BloomFilter_f, "print bloomfilter raw value of arguments set");
Cmd_AddCommand( "verifyhex", ID_VerifyHEX_f, "check if id source seems to be fake" );
#if XASH_LINUX
Cmd_AddCommand( "testcpuinfo", ID_TestCPUInfo_f, "try read cpu serial" );
#endif
#if XASH_ANDROID && !XASH_DEDICATED
sscanf( Android_LoadID(), "%016llX", &id );
if( id )
{
id ^= SYSTEM_XOR_MASK;
ID_Check();
}
#elif XASH_WIN32
{
CHAR szBuf[MAX_PATH];
ID_GetKeyData( HKEY_CURRENT_USER, "Software\\Xash3D\\", "xash_id", szBuf, MAX_PATH );
sscanf(szBuf, "%016llX", &id);
id ^= SYSTEM_XOR_MASK;
ID_Check();
}
#else
{
const char *home = getenv( "HOME" );
if( COM_CheckString( home ) )
{
FILE *cfg = fopen( va( "%s/.config/.xash_id", home ), "r" );
if( !cfg )
cfg = fopen( va( "%s/.local/.xash_id", home ), "r" );
if( !cfg )
cfg = fopen( va( "%s/.xash_id", home ), "r" );
if( cfg )
{
if( fscanf( cfg, "%016llX", &id ) > 0 )
{
id ^= SYSTEM_XOR_MASK;
ID_Check();
}
fclose( cfg );
}
}
}
#endif
if( !id )
{
const char *buf = (const char*) FS_LoadFile( ".xash_id", NULL, false );
if( buf )
{
sscanf( buf, "%016llX", &id );
id ^= GAME_XOR_MASK;
ID_Check();
}
}
if( !id )
id = ID_GenerateRawId();
MD5Init( &hash );
MD5Update( &hash, (byte *)&id, sizeof( id ) );
MD5Final( (byte*)md5, &hash );
for( i = 0; i < 16; i++ )
Q_sprintf( &id_md5[i*2], "%02hhx", md5[i] );
#if XASH_ANDROID && !XASH_DEDICATED
Android_SaveID( va("%016llX", id^SYSTEM_XOR_MASK ) );
#elif XASH_WIN32
{
CHAR Buf[MAX_PATH];
sprintf( Buf, "%016llX", id^SYSTEM_XOR_MASK );
ID_SetKeyData( HKEY_CURRENT_USER, "Software\\Xash3D\\", REG_SZ, "xash_id", Buf, Q_strlen(Buf) );
}
#else
{
const char *home = getenv( "HOME" );
if( COM_CheckString( home ) )
{
FILE *cfg = fopen( va( "%s/.config/.xash_id", home ), "w" );
if( !cfg )
cfg = fopen( va( "%s/.local/.xash_id", home ), "w" );
if( !cfg )
cfg = fopen( va( "%s/.xash_id", home ), "w" );
if( cfg )
{
fprintf( cfg, "%016llX", id^SYSTEM_XOR_MASK );
fclose( cfg );
}
}
}
#endif
FS_WriteFile( ".xash_id", va("%016llX", id^GAME_XOR_MASK), 16 );
#if 0
Msg("MD5 id: %s\nRAW id:%016llX\n", id_md5, id );
#endif
}