mirror of
https://github.com/SubleXBle/Fail2Ban-Report.git
synced 2026-02-12 03:38:45 -06:00
Delete includes directory
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
// includes/actions/action_ban-ip.php
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/../block-ip.php';
|
||||
|
||||
// Check if IP(s) were provided
|
||||
if (!isset($_POST['ip'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Missing IP address(es)',
|
||||
'type' => 'error'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Normalize input
|
||||
$ips = $_POST['ip'];
|
||||
if (!is_array($ips)) {
|
||||
$ips = [$ips]; // Single IP fallback
|
||||
}
|
||||
|
||||
$jail = $_POST['jail'] ?? 'unknown';
|
||||
$source = $_POST['source'] ?? 'action_ban-ip';
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($ips as $ip) {
|
||||
$ip = trim($ip);
|
||||
$result = blockIp($ip, $jail, $source);
|
||||
|
||||
// Ensure each result has a type
|
||||
$type = $result['type'] ?? ($result['success'] ? 'success' : 'error');
|
||||
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => $result['success'],
|
||||
'message' => $result['message'],
|
||||
'type' => $type
|
||||
];
|
||||
}
|
||||
|
||||
// HTTP-Status abhängig vom Ergebnis
|
||||
$hasError = array_filter($results, fn($r) => $r['type'] === 'error');
|
||||
http_response_code(count($hasError) > 0 ? 207 : 200);
|
||||
|
||||
// Dominantesten Typ ermitteln (error > info > success)
|
||||
$priority = ['error' => 3, 'info' => 2, 'success' => 1];
|
||||
$finalType = 'success';
|
||||
foreach ($results as $r) {
|
||||
if (isset($r['type']) && $priority[$r['type']] > $priority[$finalType]) {
|
||||
$finalType = $r['type'];
|
||||
}
|
||||
}
|
||||
|
||||
// Nachrichten zusammenbauen
|
||||
$messages = array_map(fn($r) => "[{$r['ip']}] {$r['message']}", $results);
|
||||
$combinedMessage = implode(" | ", $messages);
|
||||
|
||||
// Finales JSON
|
||||
echo json_encode([
|
||||
'results' => $results,
|
||||
'message' => $combinedMessage,
|
||||
'type' => $finalType
|
||||
], JSON_PRETTY_PRINT);
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
// includes/actions/action_report-ip.php
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$config = parse_ini_file('/opt/Fail2Ban-Report/fail2ban-report.config');
|
||||
|
||||
$ips = $_POST['ip'] ?? null;
|
||||
if (!$config['report'] || !$config['report_types'] || !$ips) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Reporting not enabled or invalid IP(s).',
|
||||
'type' => 'info',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!is_array($ips)) {
|
||||
$ips = [$ips]; // Convert single IP to array
|
||||
}
|
||||
|
||||
$services = array_map('trim', explode(',', $config['report_types']));
|
||||
$results = [];
|
||||
$allMessages = [];
|
||||
$overallSuccess = true;
|
||||
|
||||
foreach ($ips as $ip) {
|
||||
$ip = trim($ip);
|
||||
$reportResults = [];
|
||||
$messages = [];
|
||||
$ipSuccess = true;
|
||||
|
||||
foreach ($services as $service) {
|
||||
$script = __DIR__ . "/reports/$service.php";
|
||||
|
||||
// Check API keys for services that require them
|
||||
if ($service === 'abuseipdb' && empty($config['abuseipdb_key'])) {
|
||||
$ipSuccess = false;
|
||||
$reportResults[$service] = [
|
||||
'success' => false,
|
||||
'message' => 'AbuseIPDB API key not set.',
|
||||
'type' => 'error'
|
||||
];
|
||||
$messages[] = "[$service] API key missing";
|
||||
continue;
|
||||
}
|
||||
if ($service === 'ipinfo' && empty($config['ipinfo_key'])) {
|
||||
$ipSuccess = false;
|
||||
$reportResults[$service] = [
|
||||
'success' => false,
|
||||
'message' => 'IPInfo API key not set.',
|
||||
'type' => 'error'
|
||||
];
|
||||
$messages[] = "[$service] API key missing";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists($script)) {
|
||||
$_POST['ip'] = $ip;
|
||||
|
||||
ob_start();
|
||||
include $script;
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Pause 500ms to avoid hitting API rate limits
|
||||
usleep(500000);
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$ipSuccess = false;
|
||||
$reportResults[$service] = [
|
||||
'success' => false,
|
||||
'message' => "Invalid JSON from $service: " . json_last_error_msg(),
|
||||
'raw_response' => $response
|
||||
];
|
||||
$messages[] = "[$service] JSON error";
|
||||
} else {
|
||||
if (empty($decoded['success'])) {
|
||||
$ipSuccess = false;
|
||||
}
|
||||
$reportResults[$service] = $decoded;
|
||||
$messages[] = "[$service] " . ($decoded['message'] ?? 'Reported.');
|
||||
}
|
||||
} else {
|
||||
$ipSuccess = false;
|
||||
$reportResults[$service] = [
|
||||
'success' => false,
|
||||
'message' => "$service report script not available",
|
||||
];
|
||||
$messages[] = "[$service] not available";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ipSuccess) {
|
||||
$overallSuccess = false;
|
||||
}
|
||||
|
||||
$ipMessage = implode(' | ', $messages);
|
||||
$allMessages[] = "$ip → $ipMessage";
|
||||
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => $ipSuccess,
|
||||
'message' => $ipMessage,
|
||||
'data' => $reportResults,
|
||||
];
|
||||
}
|
||||
|
||||
// Final overall message type forced to 'info' for better UI
|
||||
$finalType = 'info';
|
||||
$finalMessage = implode(' || ', $allMessages);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $overallSuccess,
|
||||
'message' => $finalMessage,
|
||||
'type' => $finalType,
|
||||
'results' => $results
|
||||
], JSON_PRETTY_PRINT);
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
// includes/actions/action_unban-ip.php
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../unblock-ip.php';
|
||||
|
||||
// Validate input
|
||||
if (!isset($_POST['ip']) || !isset($_POST['jail'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Missing IP address or jail']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ip = trim($_POST['ip']);
|
||||
$jail = trim($_POST['jail']);
|
||||
|
||||
// Call the unblock function with jail context
|
||||
$result = unblockIp($ip, $jail);
|
||||
|
||||
if ($result['success']) {
|
||||
echo json_encode(['success' => true, 'message' => $result['message']]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $result['message']]);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
// abuseipdb.php
|
||||
|
||||
$config = parse_ini_file('/opt/Fail2Ban-Report/fail2ban-report.config');
|
||||
$apiKey = trim($config['abuseipdb_key'] ?? '');
|
||||
|
||||
if (!$apiKey) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'AbuseIPDB API key not set.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$ipToCheck = $ip ?? null;
|
||||
|
||||
if (!$ipToCheck) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'No IP specified for AbuseIPDB check.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => "https://api.abuseipdb.com/api/v2/check?ipAddress=$ipToCheck",
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Key: $apiKey",
|
||||
"Accept: application/json"
|
||||
],
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($response) {
|
||||
$json = json_decode($response, true);
|
||||
$count = $json['data']['totalReports'] ?? null;
|
||||
|
||||
if ($count === null) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'AbuseIPDB: Unexpected API response.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$msg = "AbuseIPDB: $ipToCheck was reported $count time(s).";
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $msg,
|
||||
'type' => ($count >= 10) ? 'error' : (($count > 0) ? 'info' : 'success')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'AbuseIPDB request failed.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
// ipinfo.php
|
||||
|
||||
$config = parse_ini_file('/opt/Fail2Ban-Report/fail2ban-report.config');
|
||||
$apiKey = trim($config['ipinfo_key'] ?? '');
|
||||
|
||||
if (!$apiKey) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'IPInfo API key not set.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$ipToCheck = $ip ?? null;
|
||||
|
||||
if (!$ipToCheck) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'No IP specified for IPInfo check.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => "https://ipinfo.io/{$ipToCheck}/json?token={$apiKey}",
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Accept: application/json"
|
||||
],
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$curlError = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($response) {
|
||||
$json = json_decode($response, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'IPInfo: Invalid JSON response.',
|
||||
'type' => 'error'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Example fields from IPInfo
|
||||
$ip = $json['ip'] ?? 'unknown';
|
||||
$hostname = $json['hostname'] ?? 'N/A';
|
||||
$city = $json['city'] ?? 'N/A';
|
||||
$region = $json['region'] ?? 'N/A';
|
||||
$country = $json['country'] ?? 'N/A';
|
||||
$org = $json['org'] ?? 'N/A';
|
||||
$loc = $json['loc'] ?? 'N/A';
|
||||
$postal = $json['postal'] ?? 'N/A';
|
||||
|
||||
$msg = "IPInfo: $ip - Hostname: $hostname, Location: $city, $region, $country, Org: $org";
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $msg,
|
||||
'data' => $json,
|
||||
'type' => 'info'
|
||||
]);
|
||||
} else {
|
||||
$errorMsg = $curlError ?: 'IPInfo request failed.';
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $errorMsg,
|
||||
'type' => 'error'
|
||||
]);
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
<?php
|
||||
// includes/block-ip.php
|
||||
|
||||
/**
|
||||
* Blocks one or multiple IP addresses by adding them to their jail-specific blocklist JSON files.
|
||||
*
|
||||
* @param string|array $ips IP address or array of IP addresses to block.
|
||||
* @param string $jail Fail2Ban jail/context name (optional).
|
||||
* @param string $source Who triggered the block (e.g. 'manual', 'report', etc.)
|
||||
* @return array Result array or array of results with 'success', 'message' and 'type'.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
|
||||
function blockIp($ips, $jail = 'unknown', $source = 'manual') {
|
||||
$results = [];
|
||||
|
||||
if (!is_array($ips)) {
|
||||
$ips = [$ips];
|
||||
}
|
||||
|
||||
foreach ($ips as $ip) {
|
||||
$ip = trim($ip);
|
||||
|
||||
// Validate IP address format
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "Invalid IP address: $ip",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanitize jail name
|
||||
$safeJail = strtolower(preg_replace('/[^a-z0-9_-]/', '', $jail));
|
||||
if ($safeJail === '') {
|
||||
$safeJail = 'unknown';
|
||||
}
|
||||
|
||||
$jsonFile = $GLOBALS["PATHS"]["blocklists"] . $safeJail . ".blocklist.json";
|
||||
$lockFile = "/tmp/{$safeJail}.blocklist.lock";
|
||||
|
||||
// Open lock file
|
||||
$lockHandle = fopen($lockFile, 'c');
|
||||
if (!$lockHandle) {
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[LOCK] Unable to open lock file for {$safeJail}.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flock($lockHandle, LOCK_EX)) {
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[LOCK] Could not acquire lock for {$safeJail}.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load existing JSON
|
||||
$data = [];
|
||||
if (file_exists($jsonFile)) {
|
||||
$existing = file_get_contents($jsonFile);
|
||||
$data = json_decode($existing, true);
|
||||
if (!is_array($data)) {
|
||||
$data = []; // fallback if file is corrupted
|
||||
}
|
||||
}
|
||||
|
||||
// Check if IP already exists
|
||||
$found = false;
|
||||
foreach ($data as &$item) {
|
||||
if ($item['ip'] === $ip) {
|
||||
$found = true;
|
||||
if (!isset($item['active']) || $item['active'] === false) {
|
||||
$item['active'] = true;
|
||||
$item['lastModified'] = date('c');
|
||||
if (file_put_contents($jsonFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) === false) {
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[WRITE] Failed to write to {$safeJail}.blocklist.json.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue 2;
|
||||
}
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => true,
|
||||
'message' => "IP $ip was reactivated in {$safeJail}.blocklist.json.",
|
||||
'type' => 'success'
|
||||
];
|
||||
continue 2;
|
||||
} else {
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => true,
|
||||
'message' => "IP $ip is already active in {$safeJail}.blocklist.json.",
|
||||
'type' => 'info'
|
||||
];
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
|
||||
// Add new entry if not found
|
||||
if (!$found) {
|
||||
$entry = [
|
||||
'ip' => $ip,
|
||||
'jail' => $safeJail,
|
||||
'source' => $source,
|
||||
'timestamp' => date('c'),
|
||||
'expires' => null,
|
||||
'reason' => '',
|
||||
'active' => true,
|
||||
'lastModified' => date('c'),
|
||||
'tags' => [],
|
||||
'pending' => true
|
||||
];
|
||||
$data[] = $entry;
|
||||
|
||||
if (file_put_contents($jsonFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) === false) {
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[WRITE] Failed to write to {$safeJail}.blocklist.json.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => true,
|
||||
'message' => "IP $ip was successfully added to {$safeJail}.blocklist.json.",
|
||||
'type' => 'success'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten result if only one entry
|
||||
if (count($results) === 1) {
|
||||
return $results[0];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => count($results) . ' IP(s) processed.',
|
||||
'details' => $results,
|
||||
'type' => 'success'
|
||||
];
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
// Set correct path to your blocklist directory
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
|
||||
$blocklistDir = $PATHS["blocklists"];
|
||||
|
||||
$stats = [];
|
||||
|
||||
foreach (glob($blocklistDir . '*.blocklist.json') as $filepath) {
|
||||
$filename = basename($filepath);
|
||||
|
||||
// Extract jail name (remove .blocklist.json)
|
||||
$jail = preg_replace('/\.blocklist\.json$/', '', $filename);
|
||||
if (!$jail) continue;
|
||||
|
||||
// Read JSON
|
||||
$json = file_get_contents($filepath);
|
||||
if (!$json) continue;
|
||||
|
||||
$entries = json_decode($json, true);
|
||||
if (!is_array($entries)) continue;
|
||||
|
||||
// Initialize counters
|
||||
$active = 0;
|
||||
$pending = 0;
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
// Count pending entries (pending === true)
|
||||
if (isset($entry['pending']) && $entry['pending'] === true) {
|
||||
$pending++;
|
||||
}
|
||||
|
||||
// Count active entries with new rule:
|
||||
// 1) active === true and pending missing or false
|
||||
// 2) active === false and pending === true
|
||||
if (
|
||||
(isset($entry['active']) && $entry['active'] === true &&
|
||||
(!isset($entry['pending']) || $entry['pending'] === false))
|
||||
||
|
||||
(isset($entry['active']) && $entry['active'] === false &&
|
||||
isset($entry['pending']) && $entry['pending'] === true)
|
||||
) {
|
||||
$active++;
|
||||
}
|
||||
}
|
||||
|
||||
// Store result
|
||||
$stats[$jail] = [
|
||||
'active' => $active,
|
||||
'pending' => $pending
|
||||
];
|
||||
}
|
||||
|
||||
// Output JSON
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($stats);
|
||||
@@ -1,114 +0,0 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
$archiveDirectory = $blocklistDir = $PATHS["fail2ban"];
|
||||
|
||||
$files = array_filter(scandir($archiveDirectory), function($file) {
|
||||
return preg_match('/^fail2ban-events-\d{8}\.json$/', $file);
|
||||
});
|
||||
|
||||
if (!$files) {
|
||||
echo json_encode([
|
||||
'ban_count' => 0,
|
||||
'ban_unique_ips' => 0,
|
||||
'unban_count' => 0,
|
||||
'unban_unique_ips' => 0,
|
||||
'total_events' => 0,
|
||||
'total_unique_ips' => 0,
|
||||
'aggregated' => [],
|
||||
'error' => 'No log files found.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
rsort($files); // newest first
|
||||
|
||||
// Heute = neueste Datei
|
||||
$todayFile = $files[0];
|
||||
|
||||
// Aggregationsziel: [‘yesterday’ => 1, ‘last_7_days’ => 7, ‘last_30_days’ => 30]
|
||||
$aggregationRanges = [
|
||||
'yesterday' => 1,
|
||||
'last_7_days' => 7,
|
||||
'last_30_days' => 30,
|
||||
];
|
||||
|
||||
// Funktion zur Verarbeitung von Einträgen
|
||||
function processEntries($entries): array {
|
||||
$banTotal = 0;
|
||||
$unbanTotal = 0;
|
||||
$banIPs = [];
|
||||
$unbanIPs = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!isset($entry['action'], $entry['ip'])) continue;
|
||||
|
||||
if ($entry['action'] === 'Ban') {
|
||||
$banTotal++;
|
||||
$banIPs[$entry['ip']] = true;
|
||||
} elseif ($entry['action'] === 'Unban') {
|
||||
$unbanTotal++;
|
||||
$unbanIPs[$entry['ip']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'ban_count' => $banTotal,
|
||||
'ban_unique_ips' => count($banIPs),
|
||||
'unban_count' => $unbanTotal,
|
||||
'unban_unique_ips' => count($unbanIPs),
|
||||
'total_events' => $banTotal + $unbanTotal,
|
||||
'total_unique_ips' => count(array_unique(array_merge(array_keys($banIPs), array_keys($unbanIPs))))
|
||||
];
|
||||
}
|
||||
|
||||
// Zähle Bans pro Jail in einem Eintrags-Array
|
||||
function countBansPerJail(array $entries): array {
|
||||
$bansPerJail = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!isset($entry['action'], $entry['jail'])) continue;
|
||||
|
||||
if ($entry['action'] === 'Ban') {
|
||||
$jail = $entry['jail'];
|
||||
if (!isset($bansPerJail[$jail])) {
|
||||
$bansPerJail[$jail] = 0;
|
||||
}
|
||||
$bansPerJail[$jail]++;
|
||||
}
|
||||
}
|
||||
|
||||
return $bansPerJail;
|
||||
}
|
||||
|
||||
// Zuerst: heutige Datei verarbeiten
|
||||
$todayPath = $archiveDirectory . '/' . $todayFile;
|
||||
$todayEntries = json_decode(file_get_contents($todayPath), true);
|
||||
$todayStats = processEntries($todayEntries);
|
||||
|
||||
// Zusätzliche Statistik: Bans pro Jail (nur heute)
|
||||
$banCountPerJail = countBansPerJail($todayEntries);
|
||||
|
||||
// Dann aggregierte Werte berechnen
|
||||
$aggregatedStats = [];
|
||||
foreach ($aggregationRanges as $label => $count) {
|
||||
$aggregatedEntries = [];
|
||||
|
||||
// n Dateien überspringen wenn nicht genug vorhanden
|
||||
for ($i = 1; $i <= $count && isset($files[$i]); $i++) {
|
||||
$filePath = $archiveDirectory . '/' . $files[$i];
|
||||
$content = json_decode(file_get_contents($filePath), true);
|
||||
if (is_array($content)) {
|
||||
$aggregatedEntries = array_merge($aggregatedEntries, $content);
|
||||
}
|
||||
}
|
||||
|
||||
$aggregatedStats[$label] = processEntries($aggregatedEntries);
|
||||
}
|
||||
|
||||
// Finales JSON-Resultat mit zusätzlichem Feld ban_count_per_jail
|
||||
echo json_encode(array_merge($todayStats, [
|
||||
'aggregated' => $aggregatedStats,
|
||||
'ban_count_per_jail' => $banCountPerJail,
|
||||
]));
|
||||
@@ -1,5 +0,0 @@
|
||||
<footer class="site-footer">
|
||||
<div class="footer-content">
|
||||
<span title="Made with sweat, tears and love by SubleXBle">Made with 🥵, 😿 and ❤️ </span>by <a href="https://github.com/SubleXBle" title="SubleXBle on Github" target="_blank" class="yellowtext">SubleXBle</a> © <?php echo date('Y'); ?> All rights reserved
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
// includes/get-blocklist.php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
|
||||
$archiveDir = $blocklistDir = $PATHS["blocklists"];
|
||||
if (!$archiveDir) {
|
||||
http_response_code(500);
|
||||
die('Archive directory not found.');
|
||||
}
|
||||
$archiveDir .= '/';
|
||||
|
||||
|
||||
// Get all files ending with ".blocklist.json"
|
||||
$blocklistFiles = glob($archiveDir . '*.blocklist.json');
|
||||
|
||||
if (!$blocklistFiles) {
|
||||
http_response_code(404);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'No blocklist files found.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$allEntries = [];
|
||||
|
||||
foreach ($blocklistFiles as $file) {
|
||||
$content = file_get_contents($file);
|
||||
if ($content === false) {
|
||||
// Skip file if reading fails, optionally log the error
|
||||
continue;
|
||||
}
|
||||
$data = json_decode($content, true);
|
||||
if (!is_array($data)) {
|
||||
// Skip invalid JSON files
|
||||
continue;
|
||||
}
|
||||
// Append all entries from this file
|
||||
$allEntries = array_merge($allEntries, $data);
|
||||
}
|
||||
|
||||
// Output the aggregated blocklist entries as JSON
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'entries' => $allEntries
|
||||
]);
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
// includes/get-json.php
|
||||
require_once __DIR__ . '/paths.php';
|
||||
|
||||
$filename = basename($_GET['file'] ?? '');
|
||||
$filepath = $PATHS["fail2ban"] . '/' . $filename;
|
||||
|
||||
// secure: it can only read json from archive
|
||||
if (
|
||||
!$filename ||
|
||||
!preg_match('/^fail2ban-events-\d{8}\.json$/', $filename) ||
|
||||
strpos(realpath($filepath), realpath($PATHS["fail2ban"])) !== 0 ||
|
||||
!file_exists($filepath)
|
||||
) {
|
||||
http_response_code(404);
|
||||
exit('Not found');
|
||||
}
|
||||
|
||||
// deliver header and json
|
||||
header('Content-Type: application/json');
|
||||
readfile($filepath);
|
||||
@@ -1,98 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Fail2Ban Report</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<link rel="stylesheet" href="assets/css/style.css" />
|
||||
<link rel="icon" href="assets/css/favicon-32x32.png" type="image/png">
|
||||
<script>
|
||||
const availableFiles = <?php echo $filesJson; ?>;
|
||||
</script>
|
||||
<script>
|
||||
const statsFile = 'fail2ban-events-<?php echo date("Ymd"); ?>.json';
|
||||
</script>
|
||||
<script src="assets/js/jsonreader.js" defer></script>
|
||||
<script src="assets/js/notifications.js"></script>
|
||||
<script src="assets/js/action-collector.js" defer></script>
|
||||
<script src="assets/js/action.js" defer></script>
|
||||
<script src="assets/js/blocklist-overlay.js" defer></script>
|
||||
<script src="assets/js/fail2ban-logstats.js" defer></script>
|
||||
<script src="assets/js/blocklist-stats.js"></script>
|
||||
<script src="assets/js/warnings.js"></script>
|
||||
<script src="assets/js/table-export.js"></script>
|
||||
<script src="assets/js/ufw-report.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- ################################## -->
|
||||
|
||||
<form method="post" style="margin-bottom: 1em;">
|
||||
<label for="server">Server: </label>
|
||||
<select name="server" id="server" onchange="this.form.submit()">
|
||||
<?php
|
||||
foreach ($SERVERS as $key => $name) {
|
||||
$selected = ($key === $activeServer) ? "selected" : "";
|
||||
echo "<option value='$key' $selected>$name</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<!-- ################################## -->
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="inline-headlines">
|
||||
<div>
|
||||
<h1>Fail2Ban-Report</h1>
|
||||
<h2>Let's catch the bad guys!</h2>
|
||||
<div><span title="Beta 5.0"><small>Version : 0.5.0</small></span></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="fail2ban-alerts-container">
|
||||
<div class="headhead"><span title="Shows Jail | Events | Unique IPs">DoS/Scan/BF:</span></div>
|
||||
<div id="fail2ban-warning-status" class="fail2ban-status">
|
||||
<span class="status-dot yellow" id="warning-dot" title="No warnings">🟡</span>
|
||||
<span class="status-label" id="warning-label">none</span>
|
||||
</div>
|
||||
<div id="fail2ban-critical-status" class="fail2ban-status">
|
||||
<span class="status-dot red" id="critical-dot" title="No criticals">🔴</span>
|
||||
<span class="status-label" id="critical-label">none</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fail2ban-alerts-container">
|
||||
<div class="headhead">Top 3 Bans/Jails:</div>
|
||||
<div id="fail2ban-top3-jails" class="toplist"></div>
|
||||
</div>
|
||||
|
||||
<div id="fail2ban-stats">
|
||||
<div class="headhead">Fail2Ban Today:</div>
|
||||
<div>🚫 Bans: <span id="fail2ban-bans">--</span></div>
|
||||
<div>🟢 Unbans: <span id="fail2ban-unbans">--</span></div>
|
||||
<div>📊 Total: <span id="fail2ban-total">--</span></div>
|
||||
</div>
|
||||
|
||||
<div class="history-stats">
|
||||
<div class="headhead">Fail2Ban History:</div>
|
||||
<div class="headstat">🕓 Yesterd: <span id="fail2ban-yesterday">--</span></div>
|
||||
<div class="headstat">📅 7 Days : <span id="fail2ban-last7">--</span></div>
|
||||
<div class="headstat">📆 30 Days: <span id="fail2ban-last30">--</span></div>
|
||||
</div>
|
||||
|
||||
<div id="blocklist-stats">
|
||||
<div class="headhead">Fail2Ban-Report Blocklists:</div>
|
||||
<div id="blocklist-stats-container">
|
||||
<!-- JS render Jails here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Path to the config file
|
||||
$configPath = '/opt/Fail2Ban-Report/fail2ban-report.config';
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
|
||||
// Read config file and parse the [Fail2Ban-Daily-List-Settings] section
|
||||
$config = [];
|
||||
if (file_exists($configPath)) {
|
||||
$config = parse_ini_file($configPath, true);
|
||||
}
|
||||
|
||||
// Default max days to show if not set in config
|
||||
$maxDays = 7;
|
||||
if (isset($config['Fail2Ban-Daily-List-Settings']['max_display_days'])) {
|
||||
$maxDays = (int)$config['Fail2Ban-Daily-List-Settings']['max_display_days'];
|
||||
}
|
||||
|
||||
$jsonDir = $PATHS["fail2ban"];
|
||||
//$jsonDir = dirname(__DIR__) . '/archive/swsrv/fail2ban/';
|
||||
|
||||
// Collect all matching JSON files with their dates extracted from filenames
|
||||
$matchedFiles = [];
|
||||
foreach (scandir($jsonDir) as $filename) {
|
||||
// Match files like fail2ban-events-YYYYMMDD.json
|
||||
if (preg_match('/^fail2ban-events-(\d{8})\.json$/', $filename, $matches)) {
|
||||
$matchedFiles[] = [
|
||||
'filename' => $filename,
|
||||
'date' => $matches[1], // Extracted date as string YYYYMMDD
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort files by date descending (newest first)
|
||||
usort($matchedFiles, function($a, $b) {
|
||||
return strcmp($b['date'], $a['date']); // descending order by date string
|
||||
});
|
||||
|
||||
// Take only the latest $maxDays files
|
||||
$latestFiles = array_slice($matchedFiles, 0, $maxDays);
|
||||
|
||||
// Extract only the filenames for JavaScript consumption
|
||||
$files = array_column($latestFiles, 'filename');
|
||||
|
||||
// Encode the list of files as JSON for frontend use
|
||||
$filesJson = json_encode(array_values($files));
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Basepath
|
||||
$ARCHIVE_ROOT = __DIR__ . "/../archive/";
|
||||
|
||||
// List of available Servers (has to be edited by hand for now)
|
||||
$SERVERS = [
|
||||
"swsrv" => "Webserver",
|
||||
"tests" => "Testing"
|
||||
];
|
||||
|
||||
// Standardserver
|
||||
$DEFAULT_SERVER = "swsrv";
|
||||
|
||||
// if selected item in serverdropdown → dont forget it
|
||||
if (isset($_POST['server']) && array_key_exists($_POST['server'], $SERVERS)) {
|
||||
$_SESSION['active_server'] = $_POST['server'];
|
||||
}
|
||||
|
||||
// set active Server (Session → Default)
|
||||
$activeServer = $_SESSION['active_server'] ?? $DEFAULT_SERVER;
|
||||
|
||||
/**
|
||||
* Path for active selected Server
|
||||
*/
|
||||
function getPaths($server) {
|
||||
global $ARCHIVE_ROOT;
|
||||
$base = $ARCHIVE_ROOT . $server . "/";
|
||||
return [
|
||||
"fail2ban" => $base . "fail2ban/",
|
||||
"blocklists" => $base . "blocklists/",
|
||||
"ufw" => $base . "ufw/",
|
||||
];
|
||||
}
|
||||
|
||||
// set Global PATHS-Variable
|
||||
$PATHS = getPaths($activeServer);
|
||||
@@ -1,140 +0,0 @@
|
||||
<?php
|
||||
// includes/unblock-ip.php
|
||||
|
||||
/**
|
||||
* Unblocks one or multiple IP addresses by marking them inactive
|
||||
* in their jail-specific blocklist JSON files.
|
||||
*
|
||||
* @param string|array $ips IP address or array of IP addresses to unblock.
|
||||
* @param string $jail Fail2Ban jail/context name (optional).
|
||||
* @return array Result array or array of results with 'success', 'message' and 'type'.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
|
||||
function unblockIp($ips, $jail = 'unknown') {
|
||||
$results = [];
|
||||
|
||||
if (!is_array($ips)) {
|
||||
$ips = [$ips];
|
||||
}
|
||||
|
||||
foreach ($ips as $ip) {
|
||||
$ip = trim($ip);
|
||||
|
||||
// Validate IP address format
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "Invalid IP address: $ip",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanitize jail name
|
||||
$safeJail = strtolower(preg_replace('/[^a-z0-9_-]/', '', $jail));
|
||||
if ($safeJail === '') {
|
||||
$safeJail = 'unknown';
|
||||
}
|
||||
|
||||
$jsonFile = $GLOBALS["PATHS"]["blocklists"] . $safeJail . ".blocklist.json";
|
||||
$lockFile = "/tmp/{$safeJail}.blocklist.lock";
|
||||
|
||||
if (!file_exists($jsonFile)) {
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[NOTFOUND] Blocklist file {$safeJail}.blocklist.json not found.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Open lock file
|
||||
$lockHandle = fopen($lockFile, 'c');
|
||||
if (!$lockHandle) {
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[LOCK] Unable to open lock file for {$safeJail}.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flock($lockHandle, LOCK_EX)) {
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[LOCK] Could not acquire lock for {$safeJail}.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load existing JSON
|
||||
$data = json_decode(file_get_contents($jsonFile), true);
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$found = false;
|
||||
foreach ($data as &$item) {
|
||||
if ($item['ip'] === $ip && (!isset($item['active']) || $item['active'] === true)) {
|
||||
$item['active'] = false;
|
||||
$item['lastModified'] = date('c');
|
||||
$found = true;
|
||||
|
||||
if (file_put_contents($jsonFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) === false) {
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[WRITE] Failed to update {$safeJail}.blocklist.json.",
|
||||
'type' => 'error'
|
||||
];
|
||||
continue 2;
|
||||
}
|
||||
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => true,
|
||||
'message' => "IP $ip was successfully unblocked in {$safeJail}.blocklist.json.",
|
||||
'type' => 'success'
|
||||
];
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
|
||||
flock($lockHandle, LOCK_UN);
|
||||
fclose($lockHandle);
|
||||
|
||||
if (!$found) {
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'success' => false,
|
||||
'message' => "[NOTFOUND] IP $ip not active in {$safeJail}.blocklist.json.",
|
||||
'type' => 'error'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten result if only one entry
|
||||
if (count($results) === 1) {
|
||||
return $results[0];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => count($results) . ' IP(s) processed.',
|
||||
'details' => $results,
|
||||
'type' => 'success'
|
||||
];
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . "/paths.php";
|
||||
|
||||
// read Config
|
||||
$configPath = '/opt/Fail2Ban-Report/fail2ban-report.config';
|
||||
if (!file_exists($configPath)) {
|
||||
echo json_encode(['status' => 'disabled', 'reason' => 'No config found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$config = parse_ini_file($configPath, true);
|
||||
|
||||
// Warnings only active, when in Config enabled = true steht (bool valid Check)
|
||||
if (empty($config['Warnings']['enabled']) || !filter_var($config['Warnings']['enabled'], FILTER_VALIDATE_BOOLEAN)) {
|
||||
echo json_encode(['status' => 'disabled', 'reason' => 'Warnings not enabled']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Fallback 20:50 for warn and crit - warnings in header
|
||||
$thresholdRaw = $config['Warnings']['threshold'] ?? '20:50';
|
||||
[$warnThreshold, $criticalThreshold] = array_map('intval', explode(':', $thresholdRaw));
|
||||
|
||||
// find newest log in archive
|
||||
$archiveDir = $PATHS["fail2ban"];
|
||||
$files = array_filter(scandir($archiveDir), fn($f) => preg_match('/^fail2ban-events-\d{8}\.json$/', $f));
|
||||
rsort($files); // neueste zuerst
|
||||
$todayFile = $files[0] ?? null;
|
||||
|
||||
if (!$todayFile || !file_exists($archiveDir . $todayFile)) {
|
||||
echo json_encode(['status' => 'error', 'reason' => 'No log data found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// load JSON Data
|
||||
$entries = json_decode(file_get_contents($archiveDir . $todayFile), true);
|
||||
if (!is_array($entries)) {
|
||||
echo json_encode(['status' => 'error', 'reason' => 'Invalid JSON log']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Group Events - Jail and Minute (Minute als 'Y-m-d H:i')
|
||||
$jailMinuteEvents = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!isset($entry['action'], $entry['ip'], $entry['jail'], $entry['timestamp'])) continue;
|
||||
if ($entry['action'] !== 'Ban') continue;
|
||||
|
||||
$jail = $entry['jail'];
|
||||
$ip = $entry['ip'];
|
||||
$minute = date('Y-m-d H:i', strtotime(str_replace(',', '.', $entry['timestamp'])));
|
||||
|
||||
$jailMinuteEvents[$jail][$minute][] = $ip;
|
||||
}
|
||||
|
||||
// Analyze: count Events and unique IPs per Jail per Minute, classify as Warn or Crit
|
||||
$warnings = [];
|
||||
$criticals = [];
|
||||
|
||||
foreach ($jailMinuteEvents as $jail => $minutes) {
|
||||
foreach ($minutes as $minute => $ips) {
|
||||
$eventCount = count($ips);
|
||||
$uniqueIPCount = count(array_unique($ips));
|
||||
|
||||
if ($eventCount >= $criticalThreshold) {
|
||||
// Critical
|
||||
if (!isset($criticals[$jail])) {
|
||||
$criticals[$jail] = ['events' => 0, 'unique_ips' => 0];
|
||||
}
|
||||
$criticals[$jail]['events'] += $eventCount;
|
||||
$criticals[$jail]['unique_ips'] += $uniqueIPCount;
|
||||
} elseif ($eventCount >= $warnThreshold) {
|
||||
// Warning (only when not crit)
|
||||
if (!isset($warnings[$jail])) {
|
||||
$warnings[$jail] = ['events' => 0, 'unique_ips' => 0];
|
||||
}
|
||||
$warnings[$jail]['events'] += $eventCount;
|
||||
$warnings[$jail]['unique_ips'] += $uniqueIPCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// forge response
|
||||
$response = [
|
||||
'status' => 'ok',
|
||||
'warning' => [
|
||||
'total_events' => array_sum(array_column($warnings, 'events')),
|
||||
'total_unique_ips' => array_sum(array_column($warnings, 'unique_ips')),
|
||||
'total_jails' => count($warnings),
|
||||
'jails' => $warnings,
|
||||
'jail_names' => array_keys($warnings)
|
||||
],
|
||||
'critical' => [
|
||||
'total_events' => array_sum(array_column($criticals, 'events')),
|
||||
'total_unique_ips' => array_sum(array_column($criticals, 'unique_ips')),
|
||||
'total_jails' => count($criticals),
|
||||
'jails' => $criticals,
|
||||
'jail_names' => array_keys($criticals)
|
||||
],
|
||||
'enabled' => true
|
||||
];
|
||||
|
||||
|
||||
echo json_encode($response);
|
||||
Reference in New Issue
Block a user