65535) return null; return $p; } /** * Very basic CIDR/subnet validation. * Accepts forms like: * 10.0.0.0/24 * 192.168.1.5 */ function sanitize_subnet($subnet) { $subnet = trim($subnet); if ($subnet === '') return null; // allow plain IP if (filter_var($subnet, FILTER_VALIDATE_IP)) { return $subnet; } // allow IP/CIDR $parts = explode('/', $subnet); if (count($parts) === 2) { [$ip, $mask] = $parts; $ip = trim($ip); $mask = trim($mask); if (!filter_var($ip, FILTER_VALIDATE_IP)) { return null; } if (!ctype_digit($mask)) { return null; } $m = (int)$mask; if ($m < 0 || $m > 32) { return null; } return $ip . '/' . $m; } return null; } /** * Run a UFW command and capture output. */ function run_ufw_command($cmd, $dryRun = false) { $cmdline = UFW_BIN . ' ' . $cmd; if ($dryRun) { return [ 'cmd' => $cmdline, 'output' => ['[DRY RUN] Command not executed'], 'exit_code' => 0, ]; } $output = []; $ret = 0; exec($cmdline . ' 2>&1', $output, $ret); return [ 'cmd' => $cmdline, 'output' => $output, 'exit_code' => $ret, ]; } // Handle POST if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; if ($action === 'apply_firewall') { $globalPorts = $_POST['global_ports'] ?? []; $restrictedPorts = $_POST['restricted_port'] ?? []; $restrictedSubnets = $_POST['restricted_subnet'] ?? []; $dryRunChecked = isset($_POST['dry_run']) && $_POST['dry_run'] === '1'; $didAnything = false; // 1) Open ports for all IP (allow) foreach ($globalPorts as $rawPort) { $rawPort = trim($rawPort); if ($rawPort === '') { continue; } $port = sanitize_port($rawPort); if ($port === null) { $errors[] = "Invalid open port: " . htmlspecialchars($rawPort); continue; } $didAnything = true; $res = run_ufw_command('allow ' . (int)$port, $dryRunChecked || $DRY_RUN); if ($res['exit_code'] !== 0) { $errors[] = "Failed to allow port {$port}: " . implode(" ", $res['output']); } else { $messages[] = "Allowed port {$port} for all IPs."; } } // 2) Restricted ports with subnets // Model: Deny port globally, then allow from specified subnets. $count = max(count($restrictedPorts), count($restrictedSubnets)); for ($i = 0; $i < $count; $i++) { $rawPort = $restrictedPorts[$i] ?? ''; $rawSubnet = $restrictedSubnets[$i] ?? ''; $rawPort = trim($rawPort); $rawSubnet = trim($rawSubnet); if ($rawPort === '' || $rawSubnet === '') { continue; } $port = sanitize_port($rawPort); $subnet = sanitize_subnet($rawSubnet); if ($port === null) { $errors[] = "Invalid restricted port: " . htmlspecialchars($rawPort); continue; } if ($subnet === null) { $errors[] = "Invalid subnet/CIDR for port {$port}: " . htmlspecialchars($rawSubnet); continue; } $didAnything = true; // Deny port from everywhere $denyRes = run_ufw_command('deny ' . (int)$port, $dryRunChecked || $DRY_RUN); if ($denyRes['exit_code'] !== 0) { $errors[] = "Failed to deny port {$port}: " . implode(" ", $denyRes['output']); } else { $messages[] = "Denied port {$port} for all IPs."; } // Allow from subnet $allowCmd = 'allow from ' . escapeshellarg($subnet) . ' to any port ' . (int)$port; // We used escapeshellarg() here, but run_ufw_command expects only the ufw arguments, // so we need to build carefully: // Rebuild without full path: $allowCmdForRun = 'allow from ' . $subnet . ' to any port ' . (int)$port; $allowRes = run_ufw_command($allowCmdForRun, $dryRunChecked || $DRY_RUN); if ($allowRes['exit_code'] !== 0) { $errors[] = "Failed to allow port {$port} from {$subnet}: " . implode(" ", $allowRes['output']); } else { $messages[] = "Allowed port {$port} only from {$subnet}."; } } if (!$didAnything) { $errors[] = "No valid firewall rules submitted."; } // Optional: reload or enable ufw here // $reload = run_ufw_command('reload', $dryRunChecked || $DRY_RUN); } } // Get current UFW status $currentStatus = []; $statusExit = 0; if (file_exists(UFW_BIN)) { exec(UFW_BIN . ' status numbered 2>&1', $currentStatus, $statusExit); } else { $currentStatus[] = "UFW binary not found at " . UFW_BIN; $statusExit = 1; } ?>
Output of ufw status numbered: