1327 lines
55 KiB
PHP
1327 lines
55 KiB
PHP
<?php
|
|
|
|
namespace Paymenter\Extensions\Others\AntiVPN;
|
|
|
|
use App\Classes\Extension\Extension;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Http\Request;
|
|
use Closure;
|
|
|
|
class AntiVPN extends Extension
|
|
{
|
|
public function getName(): string
|
|
{
|
|
return 'AntiVPN Protection';
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'Blocks detected VPN/Proxy/Suspicious IPs using IPHunter.info, IPGeolocation.io, IsProxyIP.com, or GetIPIntel.net API with multi-provider support. Note: GetIPIntel requires a valid email address for contact information.';
|
|
}
|
|
|
|
public function getVersion(): string
|
|
{
|
|
return '2.1.0';
|
|
}
|
|
|
|
public function getAuthor(): string
|
|
{
|
|
return 'QKingsoftware';
|
|
}
|
|
|
|
public function getConfig($values = []): array
|
|
{
|
|
return [
|
|
[
|
|
'name' => 'operation_mode',
|
|
'type' => 'select',
|
|
'label' => 'Operation Mode',
|
|
'description' => 'Choose how the extension operates with multiple API providers.',
|
|
'required' => true,
|
|
'options' => [
|
|
'multi' => 'Multi-Mode (Use all available providers)',
|
|
'check' => 'Check Mode (IPv4→IPHunter, IPv6→Others)',
|
|
'single' => 'Single Provider Mode (Legacy)',
|
|
],
|
|
'value' => $values['operation_mode'] ?? 'multi',
|
|
],
|
|
[
|
|
'name' => 'api_provider',
|
|
'type' => 'select',
|
|
'label' => 'Primary API Provider (Single Mode)',
|
|
'description' => 'Choose your primary VPN detection API provider for single mode.',
|
|
'required' => false,
|
|
'options' => [
|
|
'iphunter' => 'IPHunter.info',
|
|
'ipgeolocation' => 'IPGeolocation.io (Security API - Paid)',
|
|
'isproxyip' => 'IsProxyIP.com (Free 1K/day, IPv4+IPv6)',
|
|
'getipintel' => 'GetIPIntel.net (Free 500/day, IPv4+IPv6)',
|
|
],
|
|
'value' => $values['api_provider'] ?? 'iphunter',
|
|
],
|
|
[
|
|
'name' => 'iphunter_api_key',
|
|
'type' => 'password',
|
|
'label' => 'IPHunter.info API Key',
|
|
'description' => 'Enter your IPHunter.info API key (IPv4 only).',
|
|
'required' => false,
|
|
'value' => $values['iphunter_api_key'] ?? '',
|
|
],
|
|
[
|
|
'name' => 'ipgeolocation_api_key',
|
|
'type' => 'password',
|
|
'label' => 'IPGeolocation.io API Key',
|
|
'description' => 'Enter your IPGeolocation.io Security API key (IPv4+IPv6).',
|
|
'required' => false,
|
|
'value' => $values['ipgeolocation_api_key'] ?? '',
|
|
],
|
|
[
|
|
'name' => 'isproxyip_api_key',
|
|
'type' => 'password',
|
|
'label' => 'IsProxyIP.com API Key',
|
|
'description' => 'Enter your IsProxyIP.com API key (IPv4+IPv6, Free 1K/day).',
|
|
'required' => false,
|
|
'value' => $values['isproxyip_api_key'] ?? '',
|
|
],
|
|
[
|
|
'name' => 'getipintel_api_key',
|
|
'type' => 'password',
|
|
'label' => 'GetIPIntel.net Contact Email',
|
|
'description' => 'Enter your valid email address for GetIPIntel.net (Free 500/day, IPv4+IPv6). Must be a real email that can receive messages.',
|
|
'required' => false,
|
|
'value' => $values['getipintel_api_key'] ?? '',
|
|
],
|
|
[
|
|
'name' => 'provider_api_key',
|
|
'type' => 'password',
|
|
'label' => 'Legacy API Key (Single Mode)',
|
|
'description' => 'Legacy field for single mode operation. Use individual API keys above for multi-mode.',
|
|
'required' => false,
|
|
'value' => $values['provider_api_key'] ?? '',
|
|
],
|
|
[
|
|
'name' => 'block_on_detect',
|
|
'type' => 'checkbox',
|
|
'label' => 'Block VPN/Proxy',
|
|
'description' => 'Block when VPN/Proxy is detected by any provider.',
|
|
'required' => false,
|
|
'value' => $values['block_on_detect'] ?? true,
|
|
],
|
|
[
|
|
'name' => 'block_on_suspicious',
|
|
'type' => 'checkbox',
|
|
'label' => 'Block Suspicious IPs',
|
|
'description' => 'Block when IP is flagged as suspicious by any provider.',
|
|
'required' => false,
|
|
'value' => $values['block_on_suspicious'] ?? true,
|
|
],
|
|
[
|
|
'name' => 'multi_mode_consensus',
|
|
'type' => 'select',
|
|
'label' => 'Multi-Mode Consensus',
|
|
'description' => 'How to handle results when multiple providers are used.',
|
|
'required' => false,
|
|
'options' => [
|
|
'any' => 'Block if ANY provider detects threat',
|
|
'majority' => 'Block if MAJORITY of providers detect threat',
|
|
'all' => 'Block only if ALL providers detect threat',
|
|
],
|
|
'value' => $values['multi_mode_consensus'] ?? 'any',
|
|
],
|
|
[
|
|
'name' => 'ipv6_handling',
|
|
'type' => 'select',
|
|
'label' => 'IPv6 Handling (Check Mode)',
|
|
'description' => 'How to handle IPv6 addresses in check mode. Note: IPHunter only supports IPv4.',
|
|
'required' => false,
|
|
'options' => [
|
|
'convert' => 'Try to convert to IPv4 first (recommended for IPHunter)',
|
|
'allow' => 'Allow all IPv6',
|
|
'block' => 'Block all IPv6',
|
|
'native' => 'Use native IPv6 support (IPGeolocation/IsProxyIP/GetIPIntel only)',
|
|
],
|
|
'value' => $values['ipv6_handling'] ?? 'convert',
|
|
],
|
|
[
|
|
'name' => 'whitelist',
|
|
'type' => 'textarea',
|
|
'label' => 'Whitelisted IPs',
|
|
'description' => 'IPs to never block (one per line). Supports both IPv4 and IPv6.',
|
|
'required' => false,
|
|
'value' => $values['whitelist'] ?? '',
|
|
],
|
|
[
|
|
'name' => 'test_mode',
|
|
'type' => 'checkbox',
|
|
'label' => 'Test Mode (Force Block)',
|
|
'description' => 'Block ALL IPs for testing (DANGER - can lock you out).',
|
|
'required' => false,
|
|
'value' => $values['test_mode'] ?? false,
|
|
],
|
|
[
|
|
'name' => 'enable_caching',
|
|
'type' => 'checkbox',
|
|
'label' => 'Enable IP Caching',
|
|
'description' => 'Cache API results to reduce API calls and improve performance.',
|
|
'required' => false,
|
|
'value' => $values['enable_caching'] ?? true,
|
|
],
|
|
[
|
|
'name' => 'cache_ttl',
|
|
'type' => 'number',
|
|
'label' => 'Cache TTL (minutes)',
|
|
'description' => 'How long to cache IP results in minutes (1-1440).',
|
|
'required' => false,
|
|
'value' => $values['cache_ttl'] ?? 60,
|
|
],
|
|
[
|
|
'name' => 'graceful_fallback',
|
|
'type' => 'checkbox',
|
|
'label' => 'Graceful Fallback',
|
|
'description' => 'Allow requests through when API returns unexpected responses.',
|
|
'required' => false,
|
|
'value' => $values['graceful_fallback'] ?? true,
|
|
],
|
|
[
|
|
'name' => 'debug_mode',
|
|
'type' => 'checkbox',
|
|
'label' => 'Debug Mode',
|
|
'description' => 'Enable detailed logging for troubleshooting. Disable in production for better performance.',
|
|
'required' => false,
|
|
'value' => $values['debug_mode'] ?? false,
|
|
],
|
|
];
|
|
}
|
|
|
|
public function boot(): void
|
|
{
|
|
if (app()->runningInConsole()) {
|
|
return;
|
|
}
|
|
|
|
// Only log boot once to avoid spam
|
|
static $booted = false;
|
|
if (!$booted) {
|
|
Log::info('AntiVPN Extension: Starting boot');
|
|
$booted = true;
|
|
}
|
|
|
|
$router = app('router');
|
|
$router->pushMiddlewareToGroup('web', \Paymenter\Extensions\Others\AntiVPN\AntiVPNMiddleware::class);
|
|
|
|
// Only log registration once to avoid spam
|
|
static $registered = false;
|
|
if (!$registered) {
|
|
Log::info('AntiVPN Extension: Middleware registered');
|
|
$registered = true;
|
|
}
|
|
}
|
|
|
|
public static function getInstance(): self
|
|
{
|
|
return app(self::class);
|
|
}
|
|
}
|
|
|
|
class AntiVPNMiddleware
|
|
{
|
|
private $debugMode = false;
|
|
|
|
public function handle(Request $request, Closure $next)
|
|
{
|
|
$this->debugLog('=== AntiVPN Middleware START ===');
|
|
$this->debugLog('Request URL: ' . $request->fullUrl());
|
|
$this->debugLog('Request Method: ' . $request->method());
|
|
$this->debugLog('Request Path: ' . $request->path());
|
|
|
|
// Skip admin routes
|
|
if (str_starts_with($request->path(), 'admin')) {
|
|
$this->debugLog('Skipping admin route');
|
|
return $next($request);
|
|
}
|
|
|
|
// Only check GET requests
|
|
if (!$request->isMethod('GET')) {
|
|
$this->debugLog('Skipping non-GET request');
|
|
return $next($request);
|
|
}
|
|
|
|
try {
|
|
$extension = AntiVPN::getInstance();
|
|
$this->debugLog('Extension instance loaded');
|
|
|
|
// Get config
|
|
$operationMode = $extension->config('operation_mode', 'multi') ?? 'multi';
|
|
$apiProvider = $extension->config('api_provider', 'iphunter') ?? 'iphunter';
|
|
$iphunterKey = $extension->config('iphunter_api_key', '') ?? '';
|
|
$ipgeolocationKey = $extension->config('ipgeolocation_api_key', '') ?? '';
|
|
$isproxyipKey = $extension->config('isproxyip_api_key', '') ?? '';
|
|
$getipintelKey = $extension->config('getipintel_api_key', '') ?? '';
|
|
$legacyKey = $extension->config('provider_api_key', '') ?? '';
|
|
$blockOnDetect = $extension->config('block_on_detect', true) ?? true;
|
|
$blockOnSuspicious = $extension->config('block_on_suspicious', true) ?? true;
|
|
$multiModeConsensus = $extension->config('multi_mode_consensus', 'any') ?? 'any';
|
|
$ipv6Handling = $extension->config('ipv6_handling', 'allow') ?? 'allow';
|
|
$testMode = $extension->config('test_mode', false) ?? false;
|
|
$enableCaching = $extension->config('enable_caching', true) ?? true;
|
|
$cacheTtl = max(1, min(1440, $extension->config('cache_ttl', 60) ?? 60)); // Clamp between 1-1440
|
|
$gracefulFallback = $extension->config('graceful_fallback', true) ?? true;
|
|
$debugMode = $extension->config('debug_mode', false) ?? false;
|
|
$this->debugMode = $debugMode;
|
|
|
|
$this->debugLog('Config loaded - Operation Mode: ' . $operationMode);
|
|
$this->debugLog('Config loaded - IPHunter Key: ' . (!empty($iphunterKey) ? 'SET' : 'EMPTY'));
|
|
$this->debugLog('Config loaded - IPGeolocation Key: ' . (!empty($ipgeolocationKey) ? 'SET' : 'EMPTY'));
|
|
$this->debugLog('Config loaded - IsProxyIP Key: ' . (!empty($isproxyipKey) ? 'SET' : 'EMPTY'));
|
|
$this->debugLog('Config loaded - GetIPIntel Key: ' . (!empty($getipintelKey) ? 'SET' : 'EMPTY'));
|
|
$this->debugLog('Config loaded - Legacy Key: ' . (!empty($legacyKey) ? 'SET' : 'EMPTY'));
|
|
|
|
// Debug: Show actual values (truncated for security)
|
|
$this->debugLog('IsProxyIP Key (first 10 chars): ' . substr($isproxyipKey, 0, 10));
|
|
$this->debugLog('GetIPIntel Key (first 10 chars): ' . substr($getipintelKey, 0, 10));
|
|
|
|
// Check if we have any valid API keys
|
|
$hasValidKeys = !empty($iphunterKey) || !empty($ipgeolocationKey) || !empty($isproxyipKey) || !empty($getipintelKey) || !empty($legacyKey);
|
|
|
|
if (!$hasValidKeys && !$testMode) {
|
|
$this->debugLog('No API keys and not test mode - allowing');
|
|
return $next($request);
|
|
}
|
|
|
|
$ip = $this->getRealIp($request);
|
|
$this->debugLog('Real IP detected: ' . $ip);
|
|
|
|
// Test mode - block everyone (except admin)
|
|
if ($testMode) {
|
|
Log::warning('AntiVPN: TEST MODE - BLOCKING IP: ' . $ip);
|
|
return $this->getBlockResponse($request, $ip, 'TEST');
|
|
}
|
|
|
|
// Check whitelist
|
|
if ($this->isWhitelisted($ip, $extension)) {
|
|
$this->debugLog('IP is whitelisted - allowing');
|
|
return $next($request);
|
|
}
|
|
|
|
// Store original IP for logging
|
|
$originalIp = $ip;
|
|
|
|
// Determine which providers to use based on operation mode
|
|
$providers = [];
|
|
|
|
if ($operationMode === 'multi') {
|
|
// Multi-mode: Use all available providers
|
|
if (!empty($iphunterKey)) {
|
|
$providers['iphunter'] = $iphunterKey;
|
|
}
|
|
if (!empty($ipgeolocationKey)) {
|
|
$providers['ipgeolocation'] = $ipgeolocationKey;
|
|
}
|
|
if (!empty($isproxyipKey)) {
|
|
$providers['isproxyip'] = $isproxyipKey;
|
|
}
|
|
if (!empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) {
|
|
$this->debugLog('Multi mode - Using GetIPIntel key (valid email)');
|
|
$providers['getipintel'] = $getipintelKey;
|
|
} elseif (!empty($getipintelKey)) {
|
|
Log::warning('AntiVPN: GetIPIntel key appears to be encrypted/invalid, skipping GetIPIntel provider');
|
|
$this->debugLog('Multi mode - GetIPIntel key value: ' . substr($getipintelKey, 0, 20) . '...');
|
|
}
|
|
} elseif ($operationMode === 'check') {
|
|
// Check mode: IPv4 → IPHunter, IPv6 → Others
|
|
if ($this->isIPv6($ip)) {
|
|
$this->debugLog('Check mode - IPv6 detected, using IPv6-capable providers');
|
|
if (!empty($ipgeolocationKey)) {
|
|
$providers['ipgeolocation'] = $ipgeolocationKey;
|
|
}
|
|
if (!empty($isproxyipKey)) {
|
|
$providers['isproxyip'] = $isproxyipKey;
|
|
}
|
|
if (!empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) {
|
|
$this->debugLog('Check mode - Using GetIPIntel key (valid email)');
|
|
$providers['getipintel'] = $getipintelKey;
|
|
} elseif (!empty($getipintelKey)) {
|
|
Log::warning('AntiVPN: GetIPIntel key appears to be encrypted/invalid, skipping GetIPIntel provider');
|
|
$this->debugLog('Check mode - GetIPIntel key value: ' . substr($getipintelKey, 0, 20) . '...');
|
|
}
|
|
|
|
// Handle IPv6 conversion if needed
|
|
if (empty($providers) && $ipv6Handling === 'convert') {
|
|
$convertedIp = $this->convertIPv6ToIPv4($ip);
|
|
if ($convertedIp !== null && !empty($iphunterKey)) {
|
|
$this->debugLog('Check mode - IPv6 converted to IPv4, using IPHunter');
|
|
$providers['iphunter'] = $iphunterKey;
|
|
// Update IP for API call
|
|
$ip = $convertedIp;
|
|
}
|
|
}
|
|
} else {
|
|
$this->debugLog('Check mode - IPv4 detected, using IPHunter');
|
|
if (!empty($iphunterKey)) {
|
|
$providers['iphunter'] = $iphunterKey;
|
|
} elseif (!empty($ipgeolocationKey)) {
|
|
$providers['ipgeolocation'] = $ipgeolocationKey;
|
|
} elseif (!empty($isproxyipKey)) {
|
|
$providers['isproxyip'] = $isproxyipKey;
|
|
} elseif (!empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) {
|
|
$providers['getipintel'] = $getipintelKey;
|
|
} elseif (!empty($getipintelKey)) {
|
|
Log::warning('AntiVPN: GetIPIntel key appears to be encrypted/invalid, skipping GetIPIntel provider');
|
|
}
|
|
}
|
|
} else {
|
|
// Single mode: Prioritize individual keys over legacy key
|
|
$this->debugLog('Single mode - Selected provider: ' . $apiProvider);
|
|
|
|
// Check if we have individual keys for the selected provider first
|
|
$useIndividualKey = false;
|
|
if ($apiProvider === 'iphunter' && !empty($iphunterKey)) {
|
|
$this->debugLog('Single mode - Using IPHunter individual key');
|
|
$providers['iphunter'] = $iphunterKey;
|
|
$useIndividualKey = true;
|
|
} elseif ($apiProvider === 'ipgeolocation' && !empty($ipgeolocationKey)) {
|
|
$this->debugLog('Single mode - Using IPGeolocation individual key');
|
|
$providers['ipgeolocation'] = $ipgeolocationKey;
|
|
$useIndividualKey = true;
|
|
} elseif ($apiProvider === 'isproxyip' && !empty($isproxyipKey)) {
|
|
$this->debugLog('Single mode - Using IsProxyIP individual key');
|
|
$providers['isproxyip'] = $isproxyipKey;
|
|
$useIndividualKey = true;
|
|
} elseif ($apiProvider === 'getipintel' && !empty($getipintelKey) && filter_var($getipintelKey, FILTER_VALIDATE_EMAIL)) {
|
|
$this->debugLog('Single mode - Using GetIPIntel individual key (valid email)');
|
|
$this->debugLog('Single mode - GetIPIntel key value: ' . substr($getipintelKey, 0, 20) . '...');
|
|
$providers['getipintel'] = $getipintelKey;
|
|
$useIndividualKey = true;
|
|
} elseif ($apiProvider === 'getipintel' && !empty($getipintelKey)) {
|
|
Log::warning('AntiVPN: GetIPIntel selected but email is invalid, falling back to other providers');
|
|
// Try other providers as fallback
|
|
if (!empty($iphunterKey)) {
|
|
$this->debugLog('Single mode - Fallback to IPHunter');
|
|
$providers['iphunter'] = $iphunterKey;
|
|
$useIndividualKey = true;
|
|
} elseif (!empty($isproxyipKey)) {
|
|
$this->debugLog('Single mode - Fallback to IsProxyIP');
|
|
$providers['isproxyip'] = $isproxyipKey;
|
|
$useIndividualKey = true;
|
|
} elseif (!empty($ipgeolocationKey)) {
|
|
$this->debugLog('Single mode - Fallback to IPGeolocation');
|
|
$providers['ipgeolocation'] = $ipgeolocationKey;
|
|
$useIndividualKey = true;
|
|
}
|
|
}
|
|
|
|
// Only use legacy key if no individual key was found
|
|
if (!$useIndividualKey && !empty($legacyKey)) {
|
|
$this->debugLog('Single mode - No individual key found, using legacy key for provider: ' . $apiProvider);
|
|
$this->debugLog('Single mode - Legacy key value: ' . substr($legacyKey, 0, 20) . '...');
|
|
$providers[$apiProvider] = $legacyKey;
|
|
} elseif (!$useIndividualKey) {
|
|
Log::warning('AntiVPN: Single mode - No valid key found for selected provider: ' . $apiProvider);
|
|
}
|
|
}
|
|
|
|
if (empty($providers)) {
|
|
$this->debugLog('No valid providers available - allowing');
|
|
return $next($request);
|
|
}
|
|
|
|
$this->debugLog('Using providers: ' . implode(', ', array_keys($providers)));
|
|
$this->debugLog('Selected provider in single mode: ' . $apiProvider);
|
|
|
|
// Skip private IPs (IPv4 only, or if we converted from IPv6)
|
|
if (!$this->isIPv6($ip) && !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
$this->debugLog('Private IPv4 - allowing');
|
|
return $next($request);
|
|
}
|
|
|
|
// Check cache first
|
|
$cacheKey = 'antivpn_' . md5($ip . '_' . implode('_', array_keys($providers)));
|
|
$results = null;
|
|
|
|
if ($enableCaching) {
|
|
$cachedResult = Cache::get($cacheKey);
|
|
if ($cachedResult !== null) {
|
|
$this->debugLog('Using cached result for IP: ' . $ip);
|
|
$results = $cachedResult;
|
|
}
|
|
}
|
|
|
|
// Check APIs if not cached
|
|
if ($results === null) {
|
|
$results = $this->checkMultipleApis($ip, $providers, $gracefulFallback);
|
|
$this->debugLog('API results: ' . json_encode($results));
|
|
|
|
// Cache the results
|
|
if ($enableCaching && !empty($results)) {
|
|
$cacheMinutes = max(1, min(1440, $cacheTtl)); // Clamp between 1-1440 minutes
|
|
Cache::put($cacheKey, $results, now()->addMinutes($cacheMinutes));
|
|
$this->debugLog('Cached result for IP: ' . $ip . ' (TTL: ' . $cacheMinutes . ' minutes)');
|
|
}
|
|
}
|
|
|
|
// Decision logic based on operation mode
|
|
$shouldBlock = false;
|
|
$blockReason = null;
|
|
$blockProvider = null;
|
|
|
|
if ($operationMode === 'multi') {
|
|
$decision = $this->evaluateMultiModeResults($results, $blockOnDetect, $blockOnSuspicious, $multiModeConsensus);
|
|
$shouldBlock = $decision['should_block'];
|
|
$blockReason = $decision['reason'];
|
|
$blockProvider = $decision['provider'] ?? null;
|
|
} else {
|
|
// Single mode or check mode - use first available result
|
|
$result = !empty($results) ? reset($results) : null;
|
|
if ($result !== null) {
|
|
if ($result['is_vpn'] && $blockOnDetect) {
|
|
Log::warning('AntiVPN: BLOCKING - VPN detected and block_on_detect enabled');
|
|
$shouldBlock = true;
|
|
$blockReason = 'VPN';
|
|
$blockProvider = $result['provider'] ?? null;
|
|
} elseif ($result['is_suspicious'] && $blockOnSuspicious) {
|
|
Log::warning('AntiVPN: BLOCKING - Suspicious IP detected and block_on_suspicious enabled');
|
|
$shouldBlock = true;
|
|
$blockReason = 'Suspicious';
|
|
$blockProvider = $result['provider'] ?? null;
|
|
} else {
|
|
$this->debugLog('NOT BLOCKING - VPN: ' . ($result['is_vpn']?'Y':'N') . ', Suspicious: ' . ($result['is_suspicious']?'Y':'N') . ', Block VPN: ' . ($blockOnDetect?'Y':'N') . ', Block Suspicious: ' . ($blockOnSuspicious?'Y':'N'));
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($shouldBlock) {
|
|
return $this->getBlockResponse($request, $originalIp, $blockReason, $blockProvider);
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: Exception - ' . $e->getMessage());
|
|
Log::error('AntiVPN: Stack trace - ' . $e->getTraceAsString());
|
|
}
|
|
|
|
$this->debugLog('Allowing request');
|
|
$this->debugLog('=== AntiVPN Middleware END ===');
|
|
return $next($request);
|
|
}
|
|
|
|
private function checkMultipleApis(string $ip, array $providers, bool $gracefulFallback = true): array
|
|
{
|
|
$results = [];
|
|
|
|
foreach ($providers as $provider => $apiKey) {
|
|
try {
|
|
$this->debugLog("Checking {$provider} for IP: {$ip}");
|
|
$result = $this->checkApi($ip, $apiKey, $provider, $gracefulFallback);
|
|
|
|
if ($result !== null) {
|
|
$results[$provider] = $result;
|
|
$this->debugLog("{$provider} result - VPN: " . ($result['is_vpn'] ? 'Y' : 'N') . ", Suspicious: " . ($result['is_suspicious'] ? 'Y' : 'N'));
|
|
} else {
|
|
Log::warning("AntiVPN: {$provider} returned null result");
|
|
if ($gracefulFallback) {
|
|
Log::info("AntiVPN: Graceful fallback enabled - allowing request due to {$provider} failure");
|
|
return []; // Return empty results to allow request
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::error("AntiVPN: {$provider} API exception - " . $e->getMessage());
|
|
if ($gracefulFallback) {
|
|
Log::info("AntiVPN: Graceful fallback enabled - allowing request due to {$provider} exception");
|
|
return []; // Return empty results to allow request
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
private function evaluateMultiModeResults(array $results, bool $blockOnDetect, bool $blockOnSuspicious, string $consensus): array
|
|
{
|
|
if (empty($results)) {
|
|
return ['should_block' => false, 'reason' => null, 'provider' => null];
|
|
}
|
|
|
|
// Filter out empty/null results and fallback results
|
|
$validResults = array_filter($results, function($result) {
|
|
return $result !== null && !isset($result['fallback']);
|
|
});
|
|
|
|
if (empty($validResults)) {
|
|
Log::info('AntiVPN: Multi-mode - No valid results available, allowing request');
|
|
return ['should_block' => false, 'reason' => 'no_valid_results', 'provider' => null];
|
|
}
|
|
|
|
$vpnDetections = 0;
|
|
$suspiciousDetections = 0;
|
|
$totalProviders = count($validResults);
|
|
$detectingProviders = [];
|
|
|
|
foreach ($validResults as $provider => $result) {
|
|
if ($result['is_vpn']) {
|
|
$vpnDetections++;
|
|
$detectingProviders[] = $provider;
|
|
}
|
|
if ($result['is_suspicious']) {
|
|
$suspiciousDetections++;
|
|
$detectingProviders[] = $provider;
|
|
}
|
|
}
|
|
|
|
$this->debugLog("Multi-mode evaluation - Valid providers: {$totalProviders}, VPN detections: {$vpnDetections}, Suspicious detections: {$suspiciousDetections}, Consensus: {$consensus}");
|
|
|
|
// If we have fewer valid results than expected, log it
|
|
$originalCount = count($results);
|
|
if ($totalProviders < $originalCount) {
|
|
$skippedCount = $originalCount - $totalProviders;
|
|
$this->debugLog("Multi-mode - Skipped {$skippedCount} invalid/fallback results");
|
|
}
|
|
|
|
$shouldBlock = false;
|
|
$reason = null;
|
|
$provider = null;
|
|
|
|
// Ensure consensus is valid, default to 'any' if not
|
|
if (!in_array($consensus, ['any', 'majority', 'all'])) {
|
|
Log::warning("AntiVPN: Invalid consensus value '{$consensus}', defaulting to 'any'");
|
|
$consensus = 'any';
|
|
}
|
|
|
|
if ($consensus === 'any') {
|
|
if ($vpnDetections > 0 && $blockOnDetect) {
|
|
$shouldBlock = true;
|
|
$reason = 'VPN (any)';
|
|
$provider = implode(', ', array_unique($detectingProviders));
|
|
} elseif ($suspiciousDetections > 0 && $blockOnSuspicious) {
|
|
$shouldBlock = true;
|
|
$reason = 'Suspicious (any)';
|
|
$provider = implode(', ', array_unique($detectingProviders));
|
|
}
|
|
} elseif ($consensus === 'majority') {
|
|
$majorityThreshold = ceil($totalProviders / 2);
|
|
if ($vpnDetections >= $majorityThreshold && $blockOnDetect) {
|
|
$shouldBlock = true;
|
|
$reason = 'VPN (majority)';
|
|
$provider = implode(', ', array_unique($detectingProviders));
|
|
} elseif ($suspiciousDetections >= $majorityThreshold && $blockOnSuspicious) {
|
|
$shouldBlock = true;
|
|
$reason = 'Suspicious (majority)';
|
|
$provider = implode(', ', array_unique($detectingProviders));
|
|
}
|
|
} elseif ($consensus === 'all') {
|
|
if ($vpnDetections === $totalProviders && $blockOnDetect) {
|
|
$shouldBlock = true;
|
|
$reason = 'VPN (all)';
|
|
$provider = implode(', ', array_unique($detectingProviders));
|
|
} elseif ($suspiciousDetections === $totalProviders && $blockOnSuspicious) {
|
|
$shouldBlock = true;
|
|
$reason = 'Suspicious (all)';
|
|
$provider = implode(', ', array_unique($detectingProviders));
|
|
}
|
|
}
|
|
|
|
if (!$shouldBlock) {
|
|
$this->debugLog("Multi-mode - No threats detected by any provider");
|
|
}
|
|
|
|
return ['should_block' => $shouldBlock, 'reason' => $reason, 'provider' => $provider];
|
|
}
|
|
|
|
private function getRealIp(Request $request): string
|
|
{
|
|
// Try Cloudflare first
|
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
|
return trim($_SERVER['HTTP_CF_CONNECTING_IP']);
|
|
}
|
|
|
|
// Try X-Forwarded-For
|
|
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
|
return trim($ips[0]);
|
|
}
|
|
|
|
return $request->ip();
|
|
}
|
|
|
|
private function isIPv6(string $ip): bool
|
|
{
|
|
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
|
|
}
|
|
|
|
private function convertIPv6ToIPv4(string $ipv6): ?string
|
|
{
|
|
try {
|
|
// Remove brackets if present
|
|
$ipv6 = trim($ipv6, '[]');
|
|
|
|
// 1. Check for IPv4-mapped IPv6 (::ffff:192.0.2.1)
|
|
if (preg_match('/^::ffff:([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/i', $ipv6, $matches)) {
|
|
$this->debugLog('Found IPv4-mapped IPv6');
|
|
return $matches[1];
|
|
}
|
|
|
|
// 2. Check for IPv4-compatible IPv6 (::192.0.2.1)
|
|
if (preg_match('/^::([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/', $ipv6, $matches)) {
|
|
$this->debugLog('Found IPv4-compatible IPv6');
|
|
return $matches[1];
|
|
}
|
|
|
|
// 3. Check for embedded IPv4 in last 32 bits (2001:db8::192.0.2.1)
|
|
if (preg_match('/^([0-9a-f:]+)::([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/i', $ipv6, $matches)) {
|
|
$this->debugLog('Found embedded IPv4 in IPv6');
|
|
return $matches[2];
|
|
}
|
|
|
|
// 4. Try to extract IPv4 from Teredo tunneling (2001:0000::/32)
|
|
if (preg_match('/^2001:0000:([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4}):([0-9a-f]{4})$/i', $ipv6, $matches)) {
|
|
$this->debugLog('Attempting Teredo IPv4 extraction');
|
|
|
|
// In Teredo, the client IPv4 is in the last 32 bits (XORed with 0xFFFFFFFF)
|
|
$clientIPv4Hex = $matches[5] . $matches[6];
|
|
$clientIPv4Int = hexdec($clientIPv4Hex) ^ 0xFFFFFFFF;
|
|
|
|
$ipv4 = long2ip($clientIPv4Int);
|
|
if (filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
return $ipv4;
|
|
}
|
|
}
|
|
|
|
// 5. Check for 6to4 tunneling (2002::/16)
|
|
if (preg_match('/^2002:([0-9a-f]{4}):([0-9a-f]{4}):/i', $ipv6, $matches)) {
|
|
$this->debugLog('Attempting 6to4 IPv4 extraction');
|
|
|
|
// In 6to4, the IPv4 address is encoded in the first 32 bits after 2002:
|
|
$ipv4Hex = $matches[1] . $matches[2];
|
|
$ipv4Int = hexdec($ipv4Hex);
|
|
|
|
$ipv4 = long2ip($ipv4Int);
|
|
if (filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
return $ipv4;
|
|
}
|
|
}
|
|
|
|
$this->debugLog('No IPv4 conversion method applicable for: ' . $ipv6);
|
|
return null;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: IPv6 to IPv4 conversion failed: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function isWhitelisted(string $ip, $extension): bool
|
|
{
|
|
$whitelist = trim($extension->config('whitelist', ''));
|
|
if (empty($whitelist)) {
|
|
return false;
|
|
}
|
|
|
|
$whitelistIps = array_filter(array_map('trim', explode("\n", $whitelist)));
|
|
return in_array($ip, $whitelistIps, true);
|
|
}
|
|
|
|
private function checkApi(string $ip, string $apiKey, string $provider, bool $gracefulFallback = true): ?array
|
|
{
|
|
try {
|
|
$this->debugLog("Making API call to {$provider} for IP: {$ip}");
|
|
|
|
if ($provider === 'iphunter') {
|
|
return $this->checkIPHunterApi($ip, $apiKey, $gracefulFallback);
|
|
} elseif ($provider === 'ipgeolocation') {
|
|
return $this->checkIPGeolocationApi($ip, $apiKey, $gracefulFallback);
|
|
} elseif ($provider === 'isproxyip') {
|
|
return $this->checkIsProxyIPApi($ip, $apiKey, $gracefulFallback);
|
|
} elseif ($provider === 'getipintel') {
|
|
return $this->checkGetIPIntelApi($ip, $apiKey, $gracefulFallback);
|
|
}
|
|
|
|
Log::error('AntiVPN: Unknown API provider: ' . $provider);
|
|
return null;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: API exception - ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function checkIPHunterApi(string $ip, string $apiKey, bool $gracefulFallback = true): ?array
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->withHeaders([
|
|
'X-Key' => $apiKey,
|
|
'Accept' => 'application/json',
|
|
'User-Agent' => 'AntiVPN-Extension/2.1.0',
|
|
])
|
|
->get("https://www.iphunter.info:8082/v1/ip/{$ip}");
|
|
|
|
$this->debugLog('IPHunter response status: ' . $response->status());
|
|
$this->debugLog('IPHunter response body: ' . $response->body());
|
|
|
|
if (!$response->successful()) {
|
|
Log::error('AntiVPN: IPHunter API request failed with status: ' . $response->status());
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to IPHunter API failure');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'iphunter', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
$data = $response->json();
|
|
$this->debugLog('IPHunter parsed data: ' . json_encode($data));
|
|
|
|
if (isset($data['status']) && $data['status'] === 'success' && isset($data['data']['block'])) {
|
|
$blockLevel = (int) $data['data']['block'];
|
|
return [
|
|
'is_vpn' => $blockLevel === 1,
|
|
'is_suspicious' => $blockLevel === 2,
|
|
'provider' => 'iphunter',
|
|
'raw_response' => $data
|
|
];
|
|
}
|
|
|
|
Log::warning('AntiVPN: Unexpected IPHunter API response structure');
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to unexpected IPHunter response');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'iphunter', 'fallback' => true];
|
|
}
|
|
return null;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: IPHunter API exception - ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function checkIPGeolocationApi(string $ip, string $apiKey, bool $gracefulFallback = true): ?array
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->withHeaders([
|
|
'Accept' => 'application/json',
|
|
'User-Agent' => 'AntiVPN-Extension/2.1.0',
|
|
])
|
|
->get("https://api.ipgeolocation.io/v2/security", [
|
|
'apiKey' => $apiKey,
|
|
'ip' => $ip
|
|
]);
|
|
|
|
$this->debugLog('IPGeolocation response status: ' . $response->status());
|
|
$this->debugLog('IPGeolocation response body: ' . $response->body());
|
|
|
|
if (!$response->successful()) {
|
|
Log::error('AntiVPN: IPGeolocation API request failed with status: ' . $response->status());
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to IPGeolocation API failure');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'ipgeolocation', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
$data = $response->json();
|
|
$this->debugLog('IPGeolocation parsed data: ' . json_encode($data));
|
|
|
|
// Check for error in response
|
|
if (isset($data['message'])) {
|
|
Log::error('AntiVPN: IPGeolocation API error: ' . $data['message']);
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to IPGeolocation API error');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'ipgeolocation', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// IPGeolocation.io Security API structure
|
|
$isProxy = false;
|
|
$isThreat = false;
|
|
|
|
if (isset($data['security'])) {
|
|
$security = $data['security'];
|
|
|
|
// Check for proxy/VPN
|
|
if (isset($security['is_proxy']) && $security['is_proxy']) {
|
|
$isProxy = true;
|
|
}
|
|
|
|
// Check for Tor
|
|
if (isset($security['is_tor']) && $security['is_tor']) {
|
|
$isProxy = true; // Treat Tor as proxy/VPN
|
|
}
|
|
|
|
// Check for various threat indicators
|
|
if (isset($security['is_anonymous']) && $security['is_anonymous']) {
|
|
$isThreat = true;
|
|
}
|
|
|
|
if (isset($security['is_known_attacker']) && $security['is_known_attacker']) {
|
|
$isThreat = true;
|
|
}
|
|
|
|
if (isset($security['is_spam']) && $security['is_spam']) {
|
|
$isThreat = true;
|
|
}
|
|
|
|
if (isset($security['is_bot']) && $security['is_bot']) {
|
|
$isThreat = true;
|
|
}
|
|
|
|
// High threat score
|
|
if (isset($security['threat_score']) && $security['threat_score'] > 50) {
|
|
$isThreat = true;
|
|
}
|
|
|
|
$this->debugLog('IPGeolocation security analysis - Proxy: ' . ($isProxy ? 'Y' : 'N') . ', Threat: ' . ($isThreat ? 'Y' : 'N'));
|
|
$this->debugLog('IPGeolocation threat details - Threat Score: ' . ($security['threat_score'] ?? 'N/A') . ', Proxy Type: ' . ($security['proxy_type'] ?? 'N/A') . ', Provider: ' . ($security['proxy_provider'] ?? 'N/A'));
|
|
}
|
|
|
|
return [
|
|
'is_vpn' => $isProxy,
|
|
'is_suspicious' => $isThreat,
|
|
'provider' => 'ipgeolocation',
|
|
'raw_response' => $data
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: IPGeolocation API exception - ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function checkIsProxyIPApi(string $ip, string $apiKey, bool $gracefulFallback = true): ?array
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->withHeaders([
|
|
'Accept' => 'application/json',
|
|
'User-Agent' => 'AntiVPN-Extension/2.1.0',
|
|
])
|
|
->get("https://api.isproxyip.com/v1/check.php", [
|
|
'key' => $apiKey,
|
|
'ip' => $ip,
|
|
'format' => 'json'
|
|
]);
|
|
|
|
$this->debugLog('IsProxyIP response status: ' . $response->status());
|
|
$this->debugLog('IsProxyIP response body: ' . $response->body());
|
|
|
|
if (!$response->successful()) {
|
|
Log::error('AntiVPN: IsProxyIP API request failed with status: ' . $response->status());
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to IsProxyIP API failure');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'isproxyip', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
$data = $response->json();
|
|
$this->debugLog('IsProxyIP parsed data: ' . json_encode($data));
|
|
|
|
// Check for error in response
|
|
if (isset($data['status']) && $data['status'] === 'error') {
|
|
Log::error('AntiVPN: IsProxyIP API error: ' . ($data['message'] ?? 'Unknown error'));
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to IsProxyIP API error');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'isproxyip', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// IsProxyIP API structure - simple proxy detection
|
|
$isProxy = false;
|
|
|
|
if (isset($data['status']) && $data['status'] === 'success' && isset($data['proxy'])) {
|
|
$isProxy = (bool) $data['proxy']; // 1 = proxy, 0 = not proxy
|
|
$this->debugLog('IsProxyIP proxy detection: ' . ($isProxy ? 'PROXY' : 'CLEAN'));
|
|
}
|
|
|
|
return [
|
|
'is_vpn' => $isProxy,
|
|
'is_suspicious' => false, // IsProxyIP only detects proxy/VPN, not suspicious behavior
|
|
'provider' => 'isproxyip',
|
|
'raw_response' => $data
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: IsProxyIP API exception - ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function getBlockResponse(Request $request, string $ip, $blockLevel, $provider = null)
|
|
{
|
|
if ($request->expectsJson()) {
|
|
return response()->json([
|
|
'error' => 'Access blocked by AntiVPN',
|
|
'ip' => $ip,
|
|
'level' => $blockLevel,
|
|
'provider' => $provider,
|
|
], 403);
|
|
}
|
|
$dateandtime = date('Y-m-d H:i:s');
|
|
$providerText = $provider ? "Detected by: <strong>{$provider}{$ip}</strong>" : '';
|
|
$html = "<!DOCTYPE html>
|
|
<html lang='en'>
|
|
<head>
|
|
<meta charset='UTF-8' />
|
|
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
|
<meta name='description' content='Access via VPN or proxy networks has been restricted on Virtnet.bond due to security policies.' />
|
|
<title>Access Restricted | Virtnet.bond</title>
|
|
<link rel='preconnect' href='https://fonts.googleapis.com' />
|
|
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin />
|
|
<link href='https://fonts.googleapis.com/css?family=Montserrat:400,500,700&display=swap' rel='stylesheet' />
|
|
<style>
|
|
/* ------------------------------
|
|
Global Styles
|
|
------------------------------ */
|
|
*, *::before, *::after {
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
margin: 0;
|
|
height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background-color: #eceff1;
|
|
color: #263238;
|
|
}
|
|
|
|
main.container {
|
|
background: #fff;
|
|
width: 90%;
|
|
max-width: 520px;
|
|
padding: 2rem 1.5rem;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.15);
|
|
text-align: center;
|
|
animation: fadeIn 0.8s ease-out forwards;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
margin: 1rem 0 0.5rem 0;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.125rem;
|
|
font-weight: 500;
|
|
color: #455a64;
|
|
margin: 0.25rem 0 1rem;
|
|
}
|
|
|
|
p {
|
|
color: #37474f;
|
|
line-height: 1.6;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
a {
|
|
color: #1976d2;
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
code {
|
|
background: #f5f5f5;
|
|
padding: 0.1rem 0.3rem;
|
|
border-radius: 0.2rem;
|
|
font-size: 0.95em;
|
|
}
|
|
|
|
small {
|
|
display: block;
|
|
margin-top: 1.5rem;
|
|
color: #607d8b;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* ORIGINAL forbidden-sign block (unchanged) */
|
|
.forbidden-sign {
|
|
margin: auto;
|
|
width: 4.6666666667rem;
|
|
height: 4.6666666667rem;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: #ef5350;
|
|
-webkit-animation: grow 1s forwards;
|
|
animation: grow 1s forwards;
|
|
}
|
|
|
|
.forbidden-sign::before {
|
|
position: absolute;
|
|
background-color: white;
|
|
border-radius: 50%;
|
|
content: '';
|
|
width: 4rem;
|
|
height: 4rem;
|
|
transform: scale(0);
|
|
animation: grow2 0.5s forwards 0.5s;
|
|
}
|
|
|
|
.forbidden-sign::after {
|
|
content: '';
|
|
z-index: 2;
|
|
position: absolute;
|
|
width: 4rem;
|
|
height: 0.3333333333rem;
|
|
transform: scaley(0) rotateZ(0deg);
|
|
background: #ef5350;
|
|
animation: grow3 0.5s forwards 1s;
|
|
}
|
|
|
|
@keyframes grow {
|
|
from { transform: scale(0); opacity: 0; }
|
|
to { transform: scale(1); opacity: 1; }
|
|
}
|
|
|
|
@keyframes grow2 {
|
|
from { transform: scale(0); }
|
|
to { transform: scale(1); }
|
|
}
|
|
|
|
@keyframes grow3 {
|
|
from { transform: scaley(0) rotateZ(0deg); }
|
|
to { transform: scaley(1) rotateZ(-45deg); }
|
|
}
|
|
|
|
.actions {
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.btn {
|
|
display: inline-block;
|
|
background: #1976d2;
|
|
color: white;
|
|
padding: 0.6rem 1.2rem;
|
|
border-radius: 0.4rem;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
transition: background 0.2s ease;
|
|
margin: 0.3rem;
|
|
}
|
|
.btn:hover {
|
|
background: #1565c0;
|
|
}
|
|
.btn-secondary {
|
|
background: #90a4ae;
|
|
}
|
|
.btn-secondary:hover {
|
|
background: #78909c;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(1rem);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
main.container {
|
|
padding: 1.5rem;
|
|
}
|
|
h1 {
|
|
font-size: 1.25rem;
|
|
}
|
|
}
|
|
|
|
/* ------------------------------
|
|
Dark Mode
|
|
------------------------------ */
|
|
@media (prefers-color-scheme: dark) {
|
|
body {
|
|
background-color: #121212;
|
|
color: #e0e0e0;
|
|
}
|
|
main.container {
|
|
background-color: #1e1e1e;
|
|
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.6);
|
|
}
|
|
h1, h2, p, small {
|
|
color: #e0e0e0;
|
|
}
|
|
a {
|
|
color: #64b5f6;
|
|
}
|
|
code {
|
|
background: #2c2c2c;
|
|
color: #fafafa;
|
|
}
|
|
.btn {
|
|
background: #2196f3;
|
|
}
|
|
.btn:hover {
|
|
background: #1976d2;
|
|
}
|
|
.btn-secondary {
|
|
background: #607d8b;
|
|
}
|
|
.btn-secondary:hover {
|
|
background: #546e7a;
|
|
}
|
|
.forbidden-sign {
|
|
background-color: #ef5350;
|
|
}
|
|
.forbidden-sign::before {
|
|
background-color: #1e1e1e;
|
|
}
|
|
.forbidden-sign::after {
|
|
background: #ef5350;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<main class='container' role='alert'>
|
|
<div class='forbidden-sign' aria-label='Access Restricted'></div>
|
|
|
|
<h1>Access Restricted</h1>
|
|
<h2>VPN or Proxy Connection Detected</h2>
|
|
|
|
<p>Access from virtual private networks (VPNs) or proxies is strictly prohibited. You must access this page using a standard, non-VPN/proxy internet connection. To maintain network integrity and prevent abuse, Virtnet.bond restricts access from these services.</p>
|
|
|
|
<p>If you believe this is an error, you may request a review by contacting <a href='mailto:abuse@virtnet.bond'>abuse@virtnet.bond</a> or submitting a support ticket.</p>
|
|
|
|
<p><strong>Logged IP:</strong> {$ip}</p>
|
|
<p><strong>Date/Time:</strong> <time datetime='{$dateandtime}'>{$dateandtime}</time></p>
|
|
|
|
<p>Your connection details are logged in accordance with our security and data protection policies. For more information, please review our <a href='https://virtnet.bond/tos' target='_blank'>Privacy Policy</a>.</p>
|
|
|
|
<div class='actions'>
|
|
<a href='mailto:abuse@virtnet.bond' class='btn'>Contact Support</a>
|
|
<a href='javascript:history.back()' class='btn btn-secondary'>Previous Page</a>
|
|
</div>
|
|
|
|
<small>© 2025 Virtnet.bond. All rights reserved.</small>
|
|
</main>
|
|
</body>
|
|
</html>
|
|
";
|
|
|
|
return response($html, 403)->header('Content-Type', 'text/html');
|
|
}
|
|
|
|
private function checkGetIPIntelApi(string $ip, string $contactEmail, bool $gracefulFallback = true): ?array
|
|
{
|
|
try {
|
|
$this->debugLog('GetIPIntel API called with contact email: ' . substr($contactEmail, 0, 20) . '...');
|
|
|
|
// Validate email format
|
|
if (!filter_var($contactEmail, FILTER_VALIDATE_EMAIL)) {
|
|
Log::error('AntiVPN: GetIPIntel contact email is not valid: ' . $contactEmail);
|
|
Log::error('AntiVPN: GetIPIntel contact email appears to be encrypted. Please check your configuration.');
|
|
return null;
|
|
}
|
|
|
|
$response = Http::timeout(10)
|
|
->withHeaders([
|
|
'Accept' => 'application/json',
|
|
'User-Agent' => 'AntiVPN-Extension/2.1.0',
|
|
])
|
|
->get("http://check.getipintel.net/check.php", [
|
|
'ip' => $ip,
|
|
'contact' => $contactEmail,
|
|
'format' => 'json',
|
|
'flags' => 'm' // Use dynamic ban list only for fastest response
|
|
]);
|
|
|
|
$this->debugLog('GetIPIntel response status: ' . $response->status());
|
|
$this->debugLog('GetIPIntel response body: ' . $response->body());
|
|
|
|
if (!$response->successful()) {
|
|
Log::error('AntiVPN: GetIPIntel API request failed with status: ' . $response->status());
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to GetIPIntel API failure');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'getipintel', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
$data = $response->json();
|
|
$this->debugLog('GetIPIntel parsed data: ' . json_encode($data));
|
|
|
|
// Check for error in response
|
|
if (isset($data['status']) && $data['status'] === 'error') {
|
|
$errorCode = $data['result'] ?? 'unknown';
|
|
$errorMessage = $data['message'] ?? 'Unknown error';
|
|
|
|
// Handle specific error codes
|
|
switch ($errorCode) {
|
|
case '-6':
|
|
Log::error('AntiVPN: GetIPIntel API error -6: Invalid contact email. Please provide a valid email address.');
|
|
break;
|
|
case '-5':
|
|
Log::error('AntiVPN: GetIPIntel API error -5: IP banned or rate limit exceeded.');
|
|
break;
|
|
case '-4':
|
|
Log::error('AntiVPN: GetIPIntel API error -4: Database unavailable.');
|
|
break;
|
|
case '-3':
|
|
Log::error('AntiVPN: GetIPIntel API error -3: Private/unroutable IP address.');
|
|
break;
|
|
case '-2':
|
|
Log::error('AntiVPN: GetIPIntel API error -2: Invalid IP address format.');
|
|
break;
|
|
case '-1':
|
|
Log::error('AntiVPN: GetIPIntel API error -1: No input provided.');
|
|
break;
|
|
default:
|
|
Log::error('AntiVPN: GetIPIntel API error ' . $errorCode . ': ' . $errorMessage);
|
|
}
|
|
|
|
if ($gracefulFallback) {
|
|
Log::info('AntiVPN: Graceful fallback - allowing request due to GetIPIntel API error');
|
|
return ['is_vpn' => false, 'is_suspicious' => false, 'provider' => 'getipintel', 'fallback' => true];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// GetIPIntel returns a score between 0-1
|
|
$score = isset($data['result']) ? (float) $data['result'] : 0.0;
|
|
|
|
// According to GetIPIntel docs:
|
|
// Values > 0.95 should be looked at
|
|
// Values > 0.99 are most likely proxies
|
|
// Values below 0.90 are considered "low risk"
|
|
$isProxy = $score >= 0.95;
|
|
$isSuspicious = $score >= 0.90 && $score < 0.95;
|
|
|
|
$this->debugLog('GetIPIntel score: ' . $score . ' - Proxy: ' . ($isProxy ? 'Y' : 'N') . ', Suspicious: ' . ($isSuspicious ? 'Y' : 'N'));
|
|
|
|
return [
|
|
'is_vpn' => $isProxy,
|
|
'is_suspicious' => $isSuspicious,
|
|
'provider' => 'getipintel',
|
|
'raw_response' => $data,
|
|
'score' => $score
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('AntiVPN: GetIPIntel API exception - ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debug logging helper - only logs when debug mode is enabled
|
|
*/
|
|
private function debugLog(string $message): void
|
|
{
|
|
if ($this->debugMode) {
|
|
Log::info('AntiVPN DEBUG: ' . $message);
|
|
}
|
|
}
|
|
}
|