563 lines
17 KiB
PHP
563 lines
17 KiB
PHP
<?php
|
|
|
|
/** @noinspection PhpMissingFieldTypeInspection */
|
|
|
|
namespace khm;
|
|
|
|
use acm2\acm2;
|
|
use acm2\Exceptions\ConfigurationNotDefinedException;
|
|
use acm2\Objects\Schema;
|
|
use Exception;
|
|
use khm\Abstracts\GeoLookupSource;
|
|
use khm\Abstracts\HostFlags;
|
|
use khm\Abstracts\SearchMethods\DeviceSearchMethod;
|
|
use khm\Classes\DeviceDetection;
|
|
use khm\Classes\Utilities;
|
|
use khm\Exceptions\AbuseRecordNotFoundException;
|
|
use khm\Exceptions\BadGeoSourceException;
|
|
use khm\Exceptions\GeoRecordNotFoundException;
|
|
use khm\Exceptions\NoUserAgentProvidedException;
|
|
use khm\Managers\AbuseManager;
|
|
use khm\Managers\DevicesManager;
|
|
use khm\Managers\GeoManager;
|
|
use khm\Managers\KnownDevicesManager;
|
|
use khm\Managers\KnownHostsManager;
|
|
use khm\Managers\OnionManager;
|
|
use khm\Objects\AbuseCheck;
|
|
use khm\Objects\Client;
|
|
use khm\Objects\Device;
|
|
use khm\Objects\GeoLookup;
|
|
use khm\Objects\KnownDevice;
|
|
use khm\Objects\KnownHost;
|
|
use khm\Objects\OnionRelay;
|
|
use khm\ThirdParty\AbuseIPDB;
|
|
use khm\ThirdParty\IpAPI;
|
|
use khm\ThirdParty\IpAPIco;
|
|
use khm\ThirdParty\TorProject;
|
|
use mysqli;
|
|
|
|
class khm
|
|
{
|
|
/**
|
|
* @var acm2
|
|
*/
|
|
private $acm;
|
|
|
|
/**
|
|
* @var mixed
|
|
*/
|
|
private $DatabaseConfiguration;
|
|
|
|
/**
|
|
* @var mysqli|null
|
|
*/
|
|
private $database;
|
|
|
|
/**
|
|
* @var int
|
|
*/
|
|
private $LastConnectedDatabaseTimestamp;
|
|
|
|
/**
|
|
* @var AbuseManager
|
|
*/
|
|
private $AbuseManager;
|
|
|
|
/**
|
|
* @var mixed
|
|
*/
|
|
private $AbuseIpDbConfiguration;
|
|
|
|
/**
|
|
* @var GeoManager
|
|
*/
|
|
private $GeoManager;
|
|
|
|
/**
|
|
* @var OnionManager
|
|
*/
|
|
private $OnionManager;
|
|
|
|
/**
|
|
* @var DevicesManager
|
|
*/
|
|
private $DevicesManager;
|
|
|
|
/**
|
|
* @var KnownHostsManager
|
|
*/
|
|
private $KnownHostsManager;
|
|
|
|
/**
|
|
* @var KnownDevicesManager
|
|
*/
|
|
private $KnownDevicesManager;
|
|
|
|
/**
|
|
* @throws ConfigurationNotDefinedException
|
|
*/
|
|
public function __construct()
|
|
{
|
|
// Advanced Configuration Manager
|
|
$this->acm = new acm2('khm');
|
|
|
|
// Database Schema Configuration
|
|
$DatabaseSchema = new Schema();
|
|
$DatabaseSchema->setName('Database');
|
|
$DatabaseSchema->setDefinition('Host', '127.0.0.1');
|
|
$DatabaseSchema->setDefinition('Port', '3306');
|
|
$DatabaseSchema->setDefinition('Username', 'root');
|
|
$DatabaseSchema->setDefinition('Password', 'root');
|
|
$DatabaseSchema->setDefinition('Name', 'khm');
|
|
$this->acm->defineSchema($DatabaseSchema);
|
|
|
|
// Database Schema Configuration
|
|
$AbuseIpDB = new Schema();
|
|
$AbuseIpDB->setName('AbuseIpDB');
|
|
$AbuseIpDB->setDefinition('ApiKeys', []);
|
|
$this->acm->defineSchema($AbuseIpDB);
|
|
|
|
// Save any changes
|
|
$this->acm->updateConfiguration();
|
|
$this->acm->reloadConfiguration();
|
|
|
|
// Get the configuration
|
|
$this->DatabaseConfiguration = $this->acm->getConfiguration('Database');
|
|
$this->AbuseIpDbConfiguration = $this->acm->getConfiguration('AbuseIpDB');
|
|
|
|
$this->AbuseManager = new AbuseManager($this);
|
|
$this->GeoManager = new GeoManager($this);
|
|
$this->OnionManager = new OnionManager($this);
|
|
$this->DevicesManager = new DevicesManager($this);
|
|
$this->KnownHostsManager = new KnownHostsManager($this);
|
|
$this->KnownDevicesManager = new KnownDevicesManager($this);
|
|
}
|
|
|
|
/**
|
|
* @return mysqli|null
|
|
*/
|
|
public function getDatabase(): ?mysqli
|
|
{
|
|
if($this->database == null)
|
|
{
|
|
$this->connectDatabase();
|
|
}
|
|
|
|
if( (time() - $this->LastConnectedDatabaseTimestamp) > 1800)
|
|
$this->connectDatabase();
|
|
|
|
return $this->database;
|
|
}
|
|
|
|
|
|
/**
|
|
* Closes the current database connection
|
|
*/
|
|
public function disconnectDatabase()
|
|
{
|
|
$this->database->close();
|
|
$this->database = null;
|
|
$this->LastConnectedDatabaseTimestamp = null;
|
|
}
|
|
|
|
/**
|
|
* Creates a new database connection
|
|
*/
|
|
public function connectDatabase()
|
|
{
|
|
if($this->database !== null)
|
|
{
|
|
$this->disconnectDatabase();
|
|
}
|
|
|
|
$this->database = new mysqli(
|
|
$this->DatabaseConfiguration['Host'],
|
|
$this->DatabaseConfiguration['Username'],
|
|
$this->DatabaseConfiguration['Password'],
|
|
$this->DatabaseConfiguration['Name'],
|
|
$this->DatabaseConfiguration['Port']
|
|
);
|
|
$this->LastConnectedDatabaseTimestamp = time();
|
|
}
|
|
|
|
/**
|
|
* Preforms an IP Abuse lookup, uses local database if the information is not out of date.
|
|
*
|
|
* @param string $ip_address
|
|
* @return AbuseCheck
|
|
* @throws Exceptions\DatabaseException
|
|
*/
|
|
public function abuseLookup(string $ip_address): AbuseCheck
|
|
{
|
|
$selected_key = $this->AbuseIpDbConfiguration['ApiKeys'][array_rand($this->AbuseIpDbConfiguration['ApiKeys'])];
|
|
|
|
try
|
|
{
|
|
$AbuseCheck = $this->AbuseManager->getRecord($ip_address);
|
|
}
|
|
catch(AbuseRecordNotFoundException $e)
|
|
{
|
|
$AbuseCheck = $this->AbuseManager->registerRecord(AbuseIPDB::check($selected_key, $ip_address));
|
|
}
|
|
|
|
if((time() - $AbuseCheck->LastUpdatedTimestamp) > 43200)
|
|
{
|
|
return $this->AbuseManager->updateRecord(AbuseIPDB::check($selected_key, $ip_address));
|
|
}
|
|
|
|
return $AbuseCheck;
|
|
}
|
|
|
|
/**
|
|
* Preforms a Geo lookup query, or searches against the database if the record is not out of date
|
|
*
|
|
* @param string $ip_address
|
|
* @param string $source
|
|
* @return GeoLookup
|
|
* @throws BadGeoSourceException
|
|
* @throws Exceptions\DatabaseException
|
|
* @throws Exception
|
|
*/
|
|
public function geoLookup(string $ip_address, string $source=GeoLookupSource::ipApiCo): GeoLookup
|
|
{
|
|
try
|
|
{
|
|
$GeoLookup = $this->GeoManager->getRecord($ip_address);
|
|
}
|
|
catch(GeoRecordNotFoundException $e)
|
|
{
|
|
$GeoLookup = null;
|
|
}
|
|
|
|
if($GeoLookup == null || (time() - $GeoLookup->LastUpdatedTimestamp) >= 1209600)
|
|
{
|
|
switch($source)
|
|
{
|
|
case GeoLookupSource::ipApi:
|
|
$api = new IpAPI();
|
|
$api->setLanguage('en');
|
|
$api->setFields([
|
|
'status', 'message', 'continent', 'continentCode', 'country', 'countryCode', 'region',
|
|
'regionName', 'city', 'district', 'zip', 'lat', 'lon', 'timezone', 'offset', 'currency',
|
|
'isp', 'org', 'as', 'asname', 'reverse', 'mobile', 'proxy', 'hosting', 'query'
|
|
]);
|
|
|
|
if($GeoLookup == null)
|
|
{
|
|
$GeoLookup = $this->GeoManager->registerRecord($api->get($ip_address));
|
|
}
|
|
else
|
|
{
|
|
$GeoLookup = $this->GeoManager->updateRecord($api->get($ip_address));
|
|
}
|
|
break;
|
|
|
|
case GeoLookupSource::ipApiCo:
|
|
$api = new IpAPIco();
|
|
|
|
if($GeoLookup == null)
|
|
{
|
|
$GeoLookup = $this->GeoManager->registerRecord($api->get($ip_address));
|
|
}
|
|
else
|
|
{
|
|
$GeoLookup = $this->GeoManager->updateRecord($api->get($ip_address));
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
throw new BadGeoSourceException('The geo lookup source \'' . $source . '\' is not supported by the engine');
|
|
}
|
|
}
|
|
|
|
return $GeoLookup;
|
|
}
|
|
|
|
/**
|
|
* Preforms a batch query while comparing results from the database
|
|
*
|
|
* @param array $query
|
|
* @return array
|
|
* @throws Exceptions\DatabaseException
|
|
* @throws Exception
|
|
*/
|
|
public function multipleGeoLookup(array $query): array
|
|
{
|
|
$api = new IpAPI();
|
|
$api->setLanguage('en');
|
|
$api->setFields([
|
|
'status', 'message', 'continent', 'continentCode', 'country', 'countryCode', 'region',
|
|
'regionName', 'city', 'district', 'zip', 'lat', 'lon', 'timezone', 'offset', 'currency',
|
|
'isp', 'org', 'as', 'asname', 'reverse', 'mobile', 'proxy', 'hosting', 'query'
|
|
]);
|
|
|
|
$new_queries = [];
|
|
$update_queries = [];
|
|
$results = [];
|
|
|
|
foreach($query as $item)
|
|
{
|
|
try
|
|
{
|
|
$GeoLookup = $this->GeoManager->getRecord($item);
|
|
if((time() - $GeoLookup->LastUpdatedTimestamp) >= 1209600)
|
|
{
|
|
$update_queries[] = $item;
|
|
}
|
|
else
|
|
{
|
|
$results[] = $GeoLookup;
|
|
}
|
|
}
|
|
catch(GeoRecordNotFoundException $e)
|
|
{
|
|
$new_queries[] = $item;
|
|
}
|
|
}
|
|
|
|
foreach($api->getBatch(array_merge($new_queries, $update_queries)) as $lookup)
|
|
{
|
|
if(in_array($lookup->IPAddress, $update_queries))
|
|
{
|
|
$results[] = $this->GeoManager->updateRecord($lookup);
|
|
}
|
|
|
|
if(in_array($lookup->IPAddress, $new_queries))
|
|
{
|
|
$results[] = $this->GeoManager->registerRecord($lookup);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Detects the client's device
|
|
*
|
|
* @return Device
|
|
* @throws Exceptions\DatabaseException
|
|
* @throws Exceptions\NoUserAgentProvidedException
|
|
*/
|
|
public function detectDevice(): Device
|
|
{
|
|
$device = DeviceDetection::detectDevice();
|
|
|
|
try
|
|
{
|
|
$device = $this->DevicesManager->getRecord(DeviceSearchMethod::ByFingerprint, $device->Fingerprint);
|
|
return $this->DevicesManager->updateLastSeen($device);
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
return $this->DevicesManager->registerRecord($device);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Known Host record
|
|
*
|
|
* @return KnownHost
|
|
* @throws Exceptions\DatabaseException
|
|
*/
|
|
public function getKnownHost(): KnownHost
|
|
{
|
|
$client_ip = DeviceDetection::getClientIP();
|
|
|
|
try
|
|
{
|
|
$knownHost = $this->KnownHostsManager->getRecord($client_ip);
|
|
}
|
|
catch (Exceptions\KnownHostRecordNotFoundException $e)
|
|
{
|
|
return $this->KnownHostsManager->registerRecord($client_ip);
|
|
}
|
|
|
|
return $this->getKnownHostsManager()->updateLastSeen($knownHost);
|
|
}
|
|
|
|
/**
|
|
* Returns a known device record
|
|
*
|
|
* @param Device $device
|
|
* @param KnownHost $knownHost
|
|
* @return KnownDevice
|
|
* @throws Exceptions\DatabaseException
|
|
*/
|
|
public function getKnownDevice(Device $device, KnownHost $knownHost): KnownDevice
|
|
{
|
|
$id = Utilities::generateKnownDeviceId($knownHost, $device);
|
|
|
|
try
|
|
{
|
|
$knownDevice = $this->KnownDevicesManager->getRecord($id);
|
|
}
|
|
catch (Exceptions\KnownDeviceNotFoundException $e)
|
|
{
|
|
return $this->KnownDevicesManager->registerRecord($device, $knownHost);
|
|
}
|
|
|
|
return $this->getKnownDevicesManager()->updateLastSeen($knownDevice);
|
|
}
|
|
|
|
/**
|
|
* Preforms a tor IP lookup against the database if the record exists
|
|
*
|
|
* @param string $ip_address
|
|
* @return OnionRelay
|
|
* @throws Exceptions\DatabaseException
|
|
* @throws Exceptions\OnionRecordNotFoundException
|
|
*/
|
|
public function torLookup(string $ip_address): OnionRelay
|
|
{
|
|
return $this->OnionManager->getRecord($ip_address);
|
|
}
|
|
|
|
/**
|
|
* Syncs the current onion relays into the database
|
|
*
|
|
* @return void
|
|
* @throws Exceptions\DatabaseException
|
|
*/
|
|
public function syncOnionRelays()
|
|
{
|
|
$onion_relays = TorProject::getRelays();
|
|
|
|
foreach($onion_relays as $relay)
|
|
{
|
|
try
|
|
{
|
|
$this->OnionManager->getRecord($relay->IPAddress);
|
|
$this->OnionManager->updateRecord($relay);
|
|
}
|
|
catch (Exceptions\OnionRecordNotFoundException $e)
|
|
{
|
|
$this->OnionManager->registerRecord($relay);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return DevicesManager
|
|
*/
|
|
public function getDevicesManager(): DevicesManager
|
|
{
|
|
return $this->DevicesManager;
|
|
}
|
|
|
|
/**
|
|
* @return KnownHostsManager
|
|
*/
|
|
public function getKnownHostsManager(): KnownHostsManager
|
|
{
|
|
return $this->KnownHostsManager;
|
|
}
|
|
|
|
/**
|
|
* @return KnownDevicesManager
|
|
*/
|
|
public function getKnownDevicesManager(): KnownDevicesManager
|
|
{
|
|
return $this->KnownDevicesManager;
|
|
}
|
|
|
|
/**
|
|
* Identifies the current client and returns the results
|
|
*
|
|
* @return Client
|
|
* @throws Exceptions\DatabaseException
|
|
*/
|
|
public function identify(): Client
|
|
{
|
|
$client_object = new Client();
|
|
|
|
// Get the IP address
|
|
$client_object->IPAddress = DeviceDetection::getClientIP();
|
|
|
|
// Detect the version of the IP address
|
|
if(filter_var($client_object->IPAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
|
{
|
|
$client_object->IPVersion = 4;
|
|
}
|
|
if(filter_var($client_object->IPAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
|
{
|
|
$client_object->IPVersion = 6;
|
|
}
|
|
|
|
// Detect the device
|
|
try
|
|
{
|
|
$client_object->Device = $this->detectDevice();
|
|
}
|
|
catch(NoUserAgentProvidedException $exception)
|
|
{
|
|
unset($exception);
|
|
}
|
|
|
|
// Detect the known host
|
|
$known_host = $this->getKnownHost();
|
|
|
|
// Detect the known device
|
|
if($client_object->Device !== null)
|
|
{
|
|
$known_device = $this->getKnownDevice($client_object->Device, $known_host);
|
|
$client_object->KnownDeviceID = $known_device->ID;
|
|
}
|
|
|
|
// Detect the abuse status
|
|
try
|
|
{
|
|
$client_object->Abuse = Client\Abuse::fromAbuseCheck($this->abuseLookup($client_object->IPAddress));
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
unset($e);
|
|
}
|
|
|
|
// Detect the onion status
|
|
try
|
|
{
|
|
$client_object->Onion = Client\OnionRelay::fromOnionRelayObject($this->torLookup($client_object->IPAddress));
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
unset($e);
|
|
}
|
|
|
|
// Detect the geo data
|
|
try
|
|
{
|
|
$client_object->Geo = Client\Geo::fromGeoLookup($this->geoLookup($client_object->IPAddress));
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
unset($e);
|
|
}
|
|
|
|
$client_object->Flags = [];
|
|
if($client_object->Onion !== null)
|
|
{
|
|
$client_object->Flags[] = HostFlags::TorRelay;
|
|
|
|
if($client_object->Onion->Exit)
|
|
$client_object->Flags[] = HostFlags::TorExit;
|
|
}
|
|
|
|
if($client_object->Abuse !== null)
|
|
{
|
|
if($client_object->Abuse->AbuseConfidenceScore >= 70)
|
|
$client_object->Flags[] = HostFlags::BadUser;
|
|
}
|
|
|
|
if($client_object->Device !== null)
|
|
{
|
|
if($client_object->Device->MobileDevice)
|
|
$client_object->Flags[] = HostFlags::MobileDevice;
|
|
|
|
if($client_object->Device->MobileBrowser)
|
|
$client_object->Flags[] = HostFlags::MobileBrowser;
|
|
}
|
|
|
|
return $client_object;
|
|
}
|
|
} |