parent
e833033074
commit
beb2f863a3
|
|
@ -88,41 +88,110 @@ class Fail2BanJailModal extends ModalComponent
|
||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Darstellung ableiten (Farbcode + Texte) */
|
/** letzte "Ban <IP>"-Zeile -> Unix-Timestamp (robust über zgrep/journal/jail-tail) */
|
||||||
|
private function lastBanTimestamp(string $jail, string $ip): ?int
|
||||||
|
{
|
||||||
|
// 1) zgrep über alle Logfiles (inkl. Rotation .N und .gz)
|
||||||
|
$j = escapeshellarg($jail);
|
||||||
|
$p = escapeshellarg($ip);
|
||||||
|
$cmd = "zgrep -h \"[${jail}] Ban ${ip}\" /var/log/fail2ban.log* 2>/dev/null | tail -n 1";
|
||||||
|
$line = trim((string) @shell_exec($cmd));
|
||||||
|
|
||||||
|
// 2) Fallback: journald der letzten 14 Tage
|
||||||
|
if ($line === '') {
|
||||||
|
$cmdJ = "journalctl -u fail2ban --since '14 days ago' 2>/dev/null | grep -F \"[{$jail}] Ban {$ip}\" | tail -n 1";
|
||||||
|
$line = trim((string) @shell_exec($cmdJ));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Fallback: PHP-Tail nur aktuelles file
|
||||||
|
if ($line === '') {
|
||||||
|
$file = '/var/log/fail2ban.log';
|
||||||
|
if (!is_readable($file)) return null;
|
||||||
|
$tailBytes = 400000;
|
||||||
|
$size = @filesize($file) ?: 0;
|
||||||
|
$seek = max(0, $size - $tailBytes);
|
||||||
|
$fh = @fopen($file, 'rb'); if (!$fh) return null;
|
||||||
|
if ($seek > 0) fseek($fh, $seek);
|
||||||
|
$data = stream_get_contents($fh) ?: '';
|
||||||
|
fclose($fh);
|
||||||
|
if (preg_match('/^.*\['.preg_quote($jail,'/').'\]\s+Ban\s+'.preg_quote($ip,'/').'.*$/m', $data, $m)) {
|
||||||
|
$line = trim($m[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($line === '') return null;
|
||||||
|
|
||||||
|
// Zeitstempel irgendwo in der Zeile (ISO-ähnlich) nehmen
|
||||||
|
if (preg_match('/(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})/', $line, $m)) {
|
||||||
|
$ts = strtotime($m[1].' '.$m[2]);
|
||||||
|
return $ts ?: null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** baut Details inkl. Restzeit; wenn kein Timestamp gefunden: ~bantime als Approximation */
|
||||||
|
private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
$out = [];
|
||||||
|
|
||||||
|
foreach ($ips as $ip) {
|
||||||
|
$banAt = $this->lastBanTimestamp($jail, $ip);
|
||||||
|
$remaining = null; $until = null;
|
||||||
|
|
||||||
|
if ($bantime === -1) {
|
||||||
|
$remaining = -1;
|
||||||
|
} elseif ($banAt !== null) {
|
||||||
|
$remaining = max(0, $bantime - ($now - $banAt));
|
||||||
|
$until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||||||
|
} else {
|
||||||
|
// kein Ban-Timestamp gefunden → Approximation anzeigen
|
||||||
|
$remaining = -2; // Kennzeichen für "~bantime"
|
||||||
|
}
|
||||||
|
|
||||||
|
$out[] = [
|
||||||
|
'ip' => $ip,
|
||||||
|
'banned_at' => $banAt,
|
||||||
|
'remaining' => $remaining, // -1=permanent, -2≈approx, 0..Sek, null=unbekannt
|
||||||
|
'until' => $until,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Darstellung (permanent / Restzeit / abgelaufen / approx / unbekannt) */
|
||||||
private function present(?int $remaining, ?int $banAt, ?int $until): array
|
private function present(?int $remaining, ?int $banAt, ?int $until): array
|
||||||
{
|
{
|
||||||
// Farben an dein Schema angelehnt
|
|
||||||
$green = 'border-emerald-400/30 bg-emerald-500/10';
|
$green = 'border-emerald-400/30 bg-emerald-500/10';
|
||||||
$amber = 'border-amber-400/30 bg-amber-500/10';
|
$amber = 'border-amber-400/30 bg-amber-500/10';
|
||||||
$rose = 'border-rose-400/30 bg-rose-500/10';
|
$rose = 'border-rose-400/30 bg-rose-500/10';
|
||||||
$muted = 'border-white/10 bg-white/5';
|
$muted = 'border-white/10 bg-white/5';
|
||||||
|
|
||||||
if ($remaining === -1) {
|
if ($remaining === -1) {
|
||||||
$time = 'permanent';
|
return ['permanent', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $rose];
|
||||||
$meta = $banAt ? ('seit '.$this->fmtTs($banAt)) : '—';
|
}
|
||||||
return [$time, $meta, $rose];
|
if ($remaining === -2) {
|
||||||
|
// Approximation: Bannzeit ohne Startzeitpunkt
|
||||||
|
return ['~ '.$this->fmtSecs((int) $this->getApproxBantime()), '—', $amber];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_int($remaining)) {
|
if (is_int($remaining)) {
|
||||||
if ($remaining > 0) {
|
if ($remaining > 0) {
|
||||||
$time = $this->fmtSecs($remaining);
|
$meta = [];
|
||||||
$parts = [];
|
if ($banAt) $meta[] = 'seit '.$this->fmtTs($banAt);
|
||||||
if ($banAt) $parts[] = 'seit '.$this->fmtTs($banAt);
|
if ($until) $meta[] = 'bis '.$this->fmtTs($until);
|
||||||
if ($until) $parts[] = 'bis '.$this->fmtTs($until);
|
return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
||||||
$meta = $parts ? implode(' • ', $parts) : '—';
|
|
||||||
return [$time, $meta, $amber];
|
|
||||||
}
|
}
|
||||||
|
return ['abgelaufen', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $muted];
|
||||||
// 0 Sekunden -> theoretisch abgelaufen
|
|
||||||
$time = 'abgelaufen';
|
|
||||||
$meta = $banAt ? ('seit '.$this->fmtTs($banAt)) : '—';
|
|
||||||
return [$time, $meta, $muted];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// keine Info
|
|
||||||
return ['—', '—', $muted];
|
return ['—', '—', $muted];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bantime als Approximation (du kannst das einfach aus dem aufrufenden Kontext übergeben;
|
||||||
|
// hier fallback 600)
|
||||||
|
private function getApproxBantime(): int { return 600; }
|
||||||
|
|
||||||
|
/** Darstellung ableiten (Farbcode + Texte) */
|
||||||
|
|
||||||
/** sudo + fail2ban-client */
|
/** sudo + fail2ban-client */
|
||||||
private function f2b(string $args): array
|
private function f2b(string $args): array
|
||||||
{
|
{
|
||||||
|
|
@ -145,66 +214,12 @@ class Fail2BanJailModal extends ModalComponent
|
||||||
|
|
||||||
/** letzte "Ban <IP>"-Zeile parsen -> Unix-Timestamp */
|
/** letzte "Ban <IP>"-Zeile parsen -> Unix-Timestamp */
|
||||||
/** letzte "Ban <IP>"-Zeile -> Unix-Timestamp aus /var/log/fail2ban.log */
|
/** letzte "Ban <IP>"-Zeile -> Unix-Timestamp aus /var/log/fail2ban.log */
|
||||||
private function lastBanTimestamp(string $jail, string $ip): ?int
|
|
||||||
{
|
|
||||||
$file = '/var/log/fail2ban.log';
|
|
||||||
if (!is_readable($file)) return null;
|
|
||||||
|
|
||||||
// nur das Dateiende lesen (performant, auch bei großen Logs)
|
|
||||||
$tailBytes = 400000; // 400 KB
|
|
||||||
$size = @filesize($file) ?: 0;
|
|
||||||
$seek = max(0, $size - $tailBytes);
|
|
||||||
|
|
||||||
$fh = @fopen($file, 'rb');
|
|
||||||
if (!$fh) return null;
|
|
||||||
if ($seek > 0) fseek($fh, $seek);
|
|
||||||
$data = stream_get_contents($fh) ?: '';
|
|
||||||
fclose($fh);
|
|
||||||
|
|
||||||
// Beispiel: 2025-10-30 22:34:20,797 ... [sshd] Ban 193.46.255.244
|
|
||||||
$j = preg_quote($jail, '/');
|
|
||||||
$p = preg_quote($ip, '/');
|
|
||||||
$pattern = '/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}),\d+.*\['.$j.'\]\s+Ban\s+'.$p.'\s*$/m';
|
|
||||||
|
|
||||||
if (preg_match_all($pattern, $data, $m) && !empty($m[1])) {
|
|
||||||
$date = end($m[1]);
|
|
||||||
$time = end($m[2]);
|
|
||||||
$dt = \DateTime::createFromFormat('Y-m-d H:i:s', "$date $time",
|
|
||||||
new \DateTimeZone(date_default_timezone_get()));
|
|
||||||
return $dt ? $dt->getTimestamp() : null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function firstMatch(string $pattern, string $haystack): ?string
|
private function firstMatch(string $pattern, string $haystack): ?string
|
||||||
{
|
{
|
||||||
return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
|
||||||
{
|
|
||||||
$out = [];
|
|
||||||
$now = time();
|
|
||||||
|
|
||||||
foreach ($ips as $ip) {
|
|
||||||
$banAt = $this->lastBanTimestamp($jail, $ip);
|
|
||||||
$remaining = null; $until = null;
|
|
||||||
|
|
||||||
if ($bantime === -1) {
|
|
||||||
$remaining = -1; // permanent
|
|
||||||
} elseif ($banAt !== null) {
|
|
||||||
$remaining = max(0, $bantime - ($now - $banAt));
|
|
||||||
$until = $remaining > 0 ? ($banAt + $bantime) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$out[] = [
|
|
||||||
'ip' => $ip,
|
|
||||||
'remaining' => $remaining, // -1=permanent, null=unbekannt, 0..N Sekunden
|
|
||||||
'until' => $until,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fmtSecs(int $s): string
|
private function fmtSecs(int $s): string
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue