1177 lines
42 KiB
PHP
1177 lines
42 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Ui\Security\Modal;
|
|
|
|
use LivewireUI\Modal\ModalComponent;
|
|
|
|
class Fail2BanJailModal extends ModalComponent
|
|
{
|
|
public string $jail = '';
|
|
public array $rows = [];
|
|
|
|
public static function modalMaxWidth(): string { return '4xl'; }
|
|
|
|
public function mount(string $jail): void { $this->jail = $jail; $this->load(); }
|
|
public function refresh(): void { $this->load(true); }
|
|
public function render() { return view('livewire.ui.security.modal.fail2-ban-jail-modal'); }
|
|
|
|
/* ---------------- intern ---------------- */
|
|
|
|
// protected function load(bool $force = false): void
|
|
// {
|
|
// $jail = $this->jail;
|
|
//
|
|
// [, $s] = $this->f2b('status '.escapeshellarg($jail));
|
|
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
|
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
|
//
|
|
// $defaultBantime = $this->getBantime($jail);
|
|
//
|
|
// $rows = [];
|
|
// foreach ($ips as $ip) {
|
|
// $banAt = null; $until = null; $remaining = null;
|
|
//
|
|
// // 1) Primärquelle: DB
|
|
// if ($info = $this->banInfoFromDb($jail, $ip)) {
|
|
// $banAt = $info['banned_at'];
|
|
// if ((int)$info['expire'] === -1) {
|
|
// $remaining = -1; // permanent
|
|
// } elseif ((int)$info['expire'] > 0) {
|
|
// $until = (int)$info['expire'];
|
|
// $remaining = max(0, $until - time());
|
|
// }
|
|
// }
|
|
//
|
|
// // 2) Fallback: Log + Jail-Bantime
|
|
// if ($remaining === null) {
|
|
// $banAt = $banAt ?? $this->lastBanTimestamp($jail, $ip);
|
|
// if ($banAt !== null) {
|
|
// $remaining = max(0, $defaultBantime - (time() - $banAt));
|
|
// $until = $remaining > 0 ? $banAt + $defaultBantime : null;
|
|
// } else {
|
|
// $remaining = -2; // ~approx
|
|
// }
|
|
// }
|
|
//
|
|
// // 3) Wenn 0 Sekunden, aber Fail2Ban hält die IP noch → „verlängert/unbekannt“
|
|
// if ($remaining === 0 && $this->isStillBanned($jail, $ip)) {
|
|
// $remaining = -2; // markiere als „~ unbekannt/verlängert“
|
|
// }
|
|
//
|
|
// [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until);
|
|
//
|
|
// $rows[] = [
|
|
// 'ip' => $ip,
|
|
// 'bantime' => $defaultBantime,
|
|
// 'banned_at' => $banAt,
|
|
// 'remaining' => $remaining,
|
|
// 'until' => $until,
|
|
// 'time_text' => $timeText,
|
|
// 'meta_text' => $metaText,
|
|
// 'box_class' => $boxClass,
|
|
// ];
|
|
// }
|
|
//
|
|
// $this->rows = $rows;
|
|
// logger()->debug('rows='.json_encode($rows, JSON_UNESCAPED_SLASHES));
|
|
// }
|
|
|
|
protected function load(bool $force = false): void
|
|
{
|
|
$jail = $this->jail;
|
|
|
|
// 1) Primär: DB
|
|
$rows = $this->activeBansFromDb($jail);
|
|
|
|
// 2) Fallback: status parsen, wenn DB leer (z. B. sehr alte F2B-Setups)
|
|
if (empty($rows)) {
|
|
[, $s] = $this->f2b('status '.escapeshellarg($jail));
|
|
$ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
|
$ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
|
$defaultBantime = $this->getBantime($jail);
|
|
|
|
foreach ($ips as $ip) {
|
|
$banAt = $this->lastBanTimestamp($jail, $ip);
|
|
$remaining = $banAt !== null ? max(0, $defaultBantime - (time() - $banAt)) : -2;
|
|
$until = ($remaining > 0 && $banAt) ? $banAt + $defaultBantime : null;
|
|
$rows[] = [
|
|
'ip' => $ip,
|
|
'bantime' => $defaultBantime,
|
|
'banned_at' => $banAt,
|
|
'remaining' => $remaining,
|
|
'until' => $until,
|
|
];
|
|
}
|
|
}
|
|
|
|
// Präsentationswerte bauen (einheitlich)
|
|
$presented = [];
|
|
foreach ($rows as $r) {
|
|
[$timeText, $metaText, $boxClass] = $this->present($r['remaining'], $r['banned_at'], $r['until']);
|
|
$presented[] = $r + [
|
|
'time_text' => $timeText,
|
|
'meta_text' => $metaText,
|
|
'box_class' => $boxClass,
|
|
];
|
|
}
|
|
|
|
$this->rows = $presented;
|
|
logger()->debug('rows='.json_encode($presented, JSON_UNESCAPED_SLASHES));
|
|
}
|
|
/** ROBUST: findet Binaries automatisch */
|
|
private function bin(string $name): string
|
|
{
|
|
$p = trim((string)@shell_exec("command -v ".escapeshellarg($name)." 2>/dev/null"));
|
|
return $p !== '' ? $p : $name;
|
|
}
|
|
|
|
|
|
private function activeBansFromDb(string $jail): array
|
|
{
|
|
$sudo = $this->bin('sudo');
|
|
$sqlite = $this->bin('sqlite3');
|
|
$db = $this->getDbFile();
|
|
|
|
$sql = <<<SQL
|
|
WITH last AS (
|
|
SELECT ip, MAX(timeofban) AS t
|
|
FROM bans
|
|
WHERE jail=:JAIL:
|
|
GROUP BY ip
|
|
),
|
|
curr AS (
|
|
SELECT b.ip, b.timeofban, b.bantime
|
|
FROM bans b
|
|
JOIN last l ON l.ip=b.ip AND l.t=b.timeofban
|
|
)
|
|
SELECT
|
|
ip,
|
|
timeofban,
|
|
bantime,
|
|
CASE WHEN bantime < 0 THEN -1
|
|
ELSE (timeofban + bantime) - CAST(strftime('%s','now') AS INTEGER)
|
|
END AS remaining,
|
|
CASE WHEN bantime < 0 THEN NULL
|
|
ELSE (timeofban + bantime)
|
|
END AS expire
|
|
FROM curr
|
|
WHERE bantime < 0
|
|
OR (timeofban + bantime) > CAST(strftime('%s','now') AS INTEGER)
|
|
ORDER BY timeofban DESC;
|
|
SQL;
|
|
|
|
$qSql = str_replace(':JAIL:', $this->sql($jail), $sql);
|
|
$cmd = "$sudo -n $sqlite -readonly ".escapeshellarg($db).' '.escapeshellarg($qSql).' 2>&1';
|
|
$out = trim((string)@shell_exec($cmd));
|
|
if ($out === '') return [];
|
|
|
|
$rows = [];
|
|
foreach (explode("\n", $out) as $line) {
|
|
$parts = explode('|', $line);
|
|
if (count($parts) < 5) continue;
|
|
[$ip, $banAt, $bantime, $remaining, $expire] = [ $parts[0], (int)$parts[1], (int)$parts[2], (int)$parts[3], ($parts[4] !== '' ? (int)$parts[4] : null) ];
|
|
$rows[] = [
|
|
'ip' => trim($ip),
|
|
'bantime' => $bantime,
|
|
'banned_at' => $banAt ?: null,
|
|
'remaining' => $remaining,
|
|
'until' => $expire,
|
|
];
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
/** letzte "Ban <IP>"-Zeile → Unix-Timestamp (mit Fallbacks) */
|
|
private function lastBanTimestamp(string $jail, string $ip): ?int
|
|
{
|
|
$sudo = $this->bin('sudo');
|
|
$zgrep = $this->bin('zgrep');
|
|
$grep = $this->bin('grep');
|
|
$tail = $this->bin('tail');
|
|
$journal = $this->bin('journalctl');
|
|
|
|
// 1) exakter Treffer: "[jail] Ban IP"
|
|
$needleExact = sprintf('[%s] Ban %s', $jail, $ip);
|
|
$qExact = escapeshellarg($needleExact);
|
|
$cmd1 = "$sudo -n $zgrep -h -F $qExact /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
$line = trim((string)@shell_exec($cmd1));
|
|
|
|
// 2) Fallback: nur "Ban IP"
|
|
if ($line === '') {
|
|
$qLoose = escapeshellarg("Ban $ip");
|
|
$cmd2 = "$sudo -n $zgrep -h -F $qLoose /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
$line = trim((string)@shell_exec($cmd2));
|
|
}
|
|
|
|
// 3) Fallback: unkomprimiertes Log
|
|
if ($line === '') {
|
|
$cmd3 = "$sudo -n $grep -h -F $qExact /var/log/fail2ban.log 2>/dev/null | $tail -n 1";
|
|
$line = trim((string)@shell_exec($cmd3));
|
|
if ($line === '') {
|
|
$cmd3b = "$sudo -n $grep -h -F ".escapeshellarg("Ban $ip")." /var/log/fail2ban.log 2>/dev/null | $tail -n 1";
|
|
$line = trim((string)@shell_exec($cmd3b));
|
|
}
|
|
}
|
|
|
|
// 4) Fallback: journald
|
|
if ($line === '') {
|
|
$cmd4 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager | $grep -F $qExact | $tail -n 1";
|
|
$line = trim((string)@shell_exec($cmd4));
|
|
if ($line === '') {
|
|
$cmd4b = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager | $grep -F ".escapeshellarg("Ban $ip")." | $tail -n 1";
|
|
$line = trim((string)@shell_exec($cmd4b));
|
|
}
|
|
}
|
|
|
|
if ($line === '') return null;
|
|
|
|
// "2025-10-25 11:22:05,958 ..." → Millisekunden ignorieren
|
|
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;
|
|
}
|
|
|
|
private function getDbFile(): string
|
|
{
|
|
[, $out] = $this->f2b('get dbfile');
|
|
// nimm die letzte nicht-leere Zeile
|
|
$lines = array_values(array_filter(array_map('trim', preg_split('/\r?\n/', $out))));
|
|
$path = end($lines) ?: '';
|
|
// fail2ban zeigt manchmal "`- /pfad" → führende Zeichen abschneiden
|
|
$path = preg_replace('/^`?-?\s*/', '', $path);
|
|
return $path !== '' ? $path : '/var/lib/fail2ban/fail2ban.sqlite3';
|
|
}
|
|
|
|
private function sql(string $s): string
|
|
{
|
|
return "'".str_replace("'", "''", $s)."'";
|
|
}
|
|
|
|
/** Darstellung: permanent / Restzeit / abgelaufen / ~approx / unbekannt */
|
|
private function present(?int $remaining, ?int $banAt, ?int $until): array
|
|
{
|
|
$amber = 'border-amber-400/30 bg-amber-500/10';
|
|
$rose = 'border-rose-400/30 bg-rose-500/10';
|
|
$muted = 'border-white/10 bg-white/5';
|
|
|
|
if ($remaining === -1) {
|
|
return ['permanent', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $rose];
|
|
}
|
|
if ($remaining === -2) {
|
|
return ['~ '.$this->fmtSecs($this->getBantime($this->jail)), '—', $amber];
|
|
}
|
|
if (is_int($remaining)) {
|
|
if ($remaining > 0) {
|
|
$meta = [];
|
|
if ($banAt) $meta[] = 'seit '.$this->fmtTs($banAt);
|
|
if ($until) $meta[] = 'bis '.$this->fmtTs($until);
|
|
return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
|
}
|
|
return ['abgelaufen', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $muted];
|
|
}
|
|
return ['—', '—', $muted];
|
|
}
|
|
|
|
private function getApproxBantime(): int { return $this->getBantime($this->jail); }
|
|
private function f2b(string $args): array
|
|
{
|
|
$sudo = $this->bin('sudo');
|
|
$f2b = $this->bin('fail2ban-client');
|
|
$out = (string)@shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
|
$ok = stripos($out, 'Status') !== false
|
|
|| stripos($out, 'Jail list') !== false
|
|
|| stripos($out, 'pong') !== false;
|
|
return [$ok, $out];
|
|
}
|
|
|
|
private function banInfoFromDb(string $jail, string $ip): ?array
|
|
{
|
|
$sudo = $this->bin('sudo');
|
|
$sqlite = $this->bin('sqlite3');
|
|
$db = $this->getDbFile();
|
|
|
|
// 1) Spalten ermitteln
|
|
$cmdCols = "$sudo -n $sqlite -readonly ".escapeshellarg($db)." ".escapeshellarg("PRAGMA table_info(bans);");
|
|
$colsRaw = trim((string)@shell_exec($cmdCols));
|
|
if ($colsRaw === '') return null;
|
|
|
|
$hasExpire = strpos($colsRaw, "|expiretime|") !== false;
|
|
$hasBantime= strpos($colsRaw, "|bantime|") !== false;
|
|
|
|
// 2) Query bauen nach Schema
|
|
if ($hasExpire) {
|
|
$q = sprintf(
|
|
"SELECT timeofban, expiretime FROM bans
|
|
WHERE jail=%s AND ip=%s
|
|
ORDER BY timeofban DESC LIMIT 1",
|
|
$this->sql($jail), $this->sql($ip)
|
|
);
|
|
$cmd = "$sudo -n $sqlite -readonly ".escapeshellarg($db).' '.escapeshellarg($q).' 2>&1';
|
|
$out = trim((string)@shell_exec($cmd));
|
|
if ($out === '') return null;
|
|
[$timeofban, $expire] = array_map('intval', explode('|', $out)) + [null, null];
|
|
return ['banned_at' => $timeofban ?: null, 'expire' => $expire ?? null];
|
|
}
|
|
|
|
if ($hasBantime) {
|
|
$q = sprintf(
|
|
"SELECT timeofban, bantime FROM bans
|
|
WHERE jail=%s AND ip=%s
|
|
ORDER BY timeofban DESC LIMIT 1",
|
|
$this->sql($jail), $this->sql($ip)
|
|
);
|
|
$cmd = "$sudo -n $sqlite -readonly ".escapeshellarg($db).' '.escapeshellarg($q).' 2>&1';
|
|
$out = trim((string)@shell_exec($cmd));
|
|
if ($out === '') return null;
|
|
[$timeofban, $bantime] = array_map('intval', explode('|', $out)) + [null, null];
|
|
$expire = ($timeofban && $bantime) ? ($timeofban + $bantime) : null;
|
|
return ['banned_at' => $timeofban ?: null, 'expire' => $expire];
|
|
}
|
|
|
|
// Fallback: nur timeofban vorhanden → aktuelles Jail-Bantime verwenden
|
|
$q = sprintf(
|
|
"SELECT timeofban FROM bans
|
|
WHERE jail=%s AND ip=%s
|
|
ORDER BY timeofban DESC LIMIT 1",
|
|
$this->sql($jail), $this->sql($ip)
|
|
);
|
|
$cmd = "$sudo -n $sqlite -readonly ".escapeshellarg($db).' '.escapeshellarg($q).' 2>&1';
|
|
$out = trim((string)@shell_exec($cmd));
|
|
if ($out === '') return null;
|
|
|
|
$timeofban = (int)$out;
|
|
$bantime = $this->getBantime($jail); // aktuell konfiguriert (z. B. 3600)
|
|
$expire = $timeofban ? ($timeofban + max(0, $bantime)) : null;
|
|
|
|
return ['banned_at' => $timeofban ?: null, 'expire' => $expire];
|
|
}
|
|
|
|
private function isStillBanned(string $jail, string $ip): bool
|
|
{
|
|
[, $out1] = $this->f2b('get '.escapeshellarg($jail).' banip '.escapeshellarg($ip));
|
|
if (preg_match('/\b1\b/', $out1)) return true;
|
|
|
|
[, $out3] = $this->f2b('status '.escapeshellarg($jail));
|
|
$ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $out3) ?: '';
|
|
return $ipList !== '' && preg_match('/(^|\s)'.preg_quote($ip,'/').'(\s|$)/', $ipList) === 1;
|
|
}
|
|
|
|
private function getBantime(string $jail): int
|
|
{
|
|
[, $out] = $this->f2b('get '.escapeshellarg($jail).' bantime');
|
|
$val = trim($out);
|
|
return preg_match('/-?\d+/', $val, $m) ? (int)$m[0] : 600;
|
|
}
|
|
|
|
private function firstMatch(string $pattern, string $haystack): ?string
|
|
{
|
|
return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
|
}
|
|
|
|
private function fmtSecs(int $s): string
|
|
{
|
|
$h = intdiv($s, 3600); $m = intdiv($s % 3600, 60); $r = $s % 60;
|
|
if ($h > 0) return sprintf('%dh %02dm %02ds', $h, $m, $r);
|
|
if ($m > 0) return sprintf('%02dm %02ds', $m, $r);
|
|
return sprintf('%ds', $r);
|
|
}
|
|
|
|
private function fmtTs(int $ts): string { return date('d.m. H:i', $ts); }
|
|
}
|
|
|
|
//namespace App\Livewire\Ui\Security\Modal;
|
|
//
|
|
//use LivewireUI\Modal\ModalComponent;
|
|
//
|
|
//class Fail2BanJailModal extends ModalComponent
|
|
//{
|
|
// public string $jail = '';
|
|
// public array $rows = [];
|
|
//
|
|
// public static function modalMaxWidth(): string
|
|
// {
|
|
// return '4xl';
|
|
// }
|
|
//
|
|
// public function mount(string $jail): void
|
|
// {
|
|
// $this->jail = $jail;
|
|
// $this->load();
|
|
// }
|
|
//
|
|
// public function refresh(): void
|
|
// {
|
|
// $this->load(true);
|
|
// }
|
|
//
|
|
// public function render()
|
|
// {
|
|
// return view('livewire.ui.security.modal.fail2-ban-jail-modal');
|
|
// }
|
|
//
|
|
// /* ---------------- intern ---------------- */
|
|
//
|
|
// protected function load(bool $force = false): void
|
|
// {
|
|
// $jail = $this->jail;
|
|
//
|
|
// // Aktuell gebannte IPs + Bantime
|
|
// [, $s] = $this->f2b('status ' . escapeshellarg($jail));
|
|
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
|
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
|
// $bantime = $this->getBantime($jail); // Sek., -1 = permanent
|
|
//
|
|
// $rows = [];
|
|
// foreach ($ips as $ip) {
|
|
// $banAt = $this->lastBanTimestamp($jail, $ip); // Unix-TS oder null
|
|
//
|
|
// $remaining = null;
|
|
// $until = null;
|
|
// if ($bantime === -1) {
|
|
// $remaining = -1;
|
|
// } elseif ($banAt !== null) {
|
|
// $remaining = max(0, $bantime - (time() - $banAt));
|
|
// $until = $remaining > 0 ? ($banAt + $bantime) : null;
|
|
// } else {
|
|
// // kein exakter Timestamp auffindbar → als Approximation ausweisen
|
|
// $remaining = -2; // Kennzeichen für "~ Bantime"
|
|
// }
|
|
//
|
|
// [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until, $bantime);
|
|
//
|
|
// $rows[] = [
|
|
// 'ip' => $ip,
|
|
// 'bantime' => $bantime,
|
|
// 'banned_at' => $banAt,
|
|
// 'remaining' => $remaining, // -1=permanent, -2≈approx, 0..Sek, null=unbekannt
|
|
// 'until' => $until,
|
|
// 'time_text' => $timeText,
|
|
// 'meta_text' => $metaText,
|
|
// 'box_class' => $boxClass,
|
|
// ];
|
|
// }
|
|
//
|
|
// $this->rows = $rows;
|
|
// }
|
|
//
|
|
// /** robust: findet Pfade automatisch, mehrere Fallbacks */
|
|
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
|
// {
|
|
// $sudo = $this->bin('sudo');
|
|
// $zgrep = $this->bin('zgrep');
|
|
// $grep = $this->bin('grep');
|
|
// $tail = $this->bin('tail');
|
|
// $journal = $this->bin('journalctl');
|
|
//
|
|
// // 1) exakte Suche: "[jail] Ban IP"
|
|
// $needleExact = sprintf('[%s] Ban %s', $jail, $ip);
|
|
// $qExact = escapeshellarg($needleExact);
|
|
// $cmd1 = "$sudo -n $zgrep -h -F $qExact /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd1));
|
|
//
|
|
// // 2) locker: nur "Ban IP" (falls der Jail-Tag im Log anders ist)
|
|
// if ($line === '') {
|
|
// $qLoose = escapeshellarg("Ban $ip");
|
|
// $cmd2 = "$sudo -n $zgrep -h -F $qLoose /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd2));
|
|
// }
|
|
//
|
|
// // 3) unkomprimiertes Log als Fallback
|
|
// if ($line === '') {
|
|
// $cmd3 = "$sudo -n $grep -h -F $qExact /var/log/fail2ban.log 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd3));
|
|
// if ($line === '') {
|
|
// $cmd3b = "$sudo -n $grep -h -F " . escapeshellarg("Ban $ip") . " /var/log/fail2ban.log 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd3b));
|
|
// }
|
|
// }
|
|
//
|
|
// // 4) journald
|
|
// if ($line === '') {
|
|
// $cmd4 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager | $grep -F $qExact | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd4));
|
|
// if ($line === '') {
|
|
// $cmd4b = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager | $grep -F " . escapeshellarg("Ban $ip") . " | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd4b));
|
|
// }
|
|
// }
|
|
//
|
|
// if ($line === '') return null;
|
|
//
|
|
// // 2025-10-30 22:34:59,491 ... -> Datum/Zeit extrahieren
|
|
// 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;
|
|
// }
|
|
//
|
|
// /** Anzeige-Logik */
|
|
// private function present(?int $remaining, ?int $banAt, ?int $until, int $bantime): array
|
|
// {
|
|
// $amber = 'border-amber-400/30 bg-amber-500/10';
|
|
// $rose = 'border-rose-400/30 bg-rose-500/10';
|
|
// $muted = 'border-white/10 bg-white/5';
|
|
//
|
|
// if ($remaining === -1) {
|
|
// return ['permanent', $banAt ? ('seit ' . $this->fmtTs($banAt)) : '—', $rose];
|
|
// }
|
|
// if ($remaining === -2) {
|
|
// // nur ungefähre Bantime verfügbar
|
|
// return ['~ ' . $this->fmtSecs($bantime), '—', $amber];
|
|
// }
|
|
// if (is_int($remaining)) {
|
|
// if ($remaining > 0) {
|
|
// $meta = [];
|
|
// if ($banAt) $meta[] = 'seit ' . $this->fmtTs($banAt);
|
|
// if ($until) $meta[] = 'bis ' . $this->fmtTs($until);
|
|
// return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
|
// }
|
|
// return ['abgelaufen', $banAt ? ('seit ' . $this->fmtTs($banAt)) : '—', $muted];
|
|
// }
|
|
// return ['—', '—', $muted];
|
|
// }
|
|
//
|
|
// /** sudo + fail2ban-client */
|
|
// private function f2b(string $args): array
|
|
// {
|
|
// $sudo = $this->bin('sudo');
|
|
// $f2b = $this->bin('fail2ban-client');
|
|
// $out = (string)@shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
|
// $ok = stripos($out, 'Status') !== false
|
|
// || stripos($out, 'Jail list') !== false
|
|
// || stripos($out, 'pong') !== false;
|
|
// return [$ok, $out];
|
|
// }
|
|
//
|
|
// private function getBantime(string $jail): int
|
|
// {
|
|
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
|
// $val = trim($out);
|
|
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
|
// return 600;
|
|
// }
|
|
//
|
|
// private function firstMatch(string $pattern, string $haystack): ?string
|
|
// {
|
|
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
|
// }
|
|
//
|
|
// /** findet den Pfad zu einem Binary robust */
|
|
// private function bin(string $name): string
|
|
// {
|
|
// $p = trim((string)@shell_exec('command -v ' . escapeshellarg($name) . ' 2>/dev/null'));
|
|
// return $p !== '' ? $p : $name;
|
|
// }
|
|
//
|
|
// private function fmtSecs(int $s): string
|
|
// {
|
|
// $h = intdiv($s, 3600);
|
|
// $m = intdiv($s % 3600, 60);
|
|
// $r = $s % 60;
|
|
// if ($h > 0) return sprintf('%dh %02dm %02ds', $h, $m, $r);
|
|
// if ($m > 0) return sprintf('%02dm %02ds', $m, $r);
|
|
// return sprintf('%ds', $r);
|
|
// }
|
|
//
|
|
// private function fmtTs(int $ts): string
|
|
// {
|
|
// return date('d.m. H:i', $ts);
|
|
// }
|
|
//}
|
|
|
|
//namespace App\Livewire\Ui\Security\Modal;
|
|
//
|
|
//use LivewireUI\Modal\ModalComponent;
|
|
//
|
|
//class Fail2BanJailModal extends ModalComponent
|
|
//{
|
|
// public string $jail = '';
|
|
// public array $rows = [];
|
|
//
|
|
// public static function modalMaxWidth(): string
|
|
// {
|
|
// return '4xl';
|
|
// }
|
|
//
|
|
// public function mount(string $jail): void
|
|
// {
|
|
// $this->jail = $jail;
|
|
// $this->load();
|
|
// }
|
|
//
|
|
// public function render()
|
|
// {
|
|
// return view('livewire.ui.security.modal.fail2-ban-jail-modal');
|
|
// }
|
|
//
|
|
// public function refresh(): void
|
|
// {
|
|
// $this->load(true);
|
|
// }
|
|
//
|
|
// /* ---------------- intern ---------------- */
|
|
//
|
|
// protected function load(bool $force = false): void
|
|
// {
|
|
// $jail = $this->jail;
|
|
//
|
|
// [, $s] = $this->f2b('status ' . escapeshellarg($jail));
|
|
// $ipLine = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
|
// $ips = $ipLine !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipLine)))) : [];
|
|
//
|
|
// $bantime = $this->getBantime($jail);
|
|
//
|
|
// $rows = [];
|
|
// foreach ($ips as $ip) {
|
|
// $banAt = $this->lastBanTimestamp($jail, $ip); // null wenn nicht gefunden
|
|
//
|
|
// $remaining = null;
|
|
// $until = null;
|
|
// if ($bantime === -1) {
|
|
// $remaining = -1;
|
|
// } elseif ($banAt !== null) {
|
|
// $remaining = max(0, $bantime - (time() - $banAt));
|
|
// $until = $remaining > 0 ? ($banAt + $bantime) : null;
|
|
// }
|
|
//
|
|
// [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until);
|
|
//
|
|
// $rows[] = [
|
|
// 'ip' => $ip,
|
|
// 'bantime' => $bantime,
|
|
// 'banned_at' => $banAt,
|
|
// 'remaining' => $remaining,
|
|
// 'until' => $until,
|
|
// 'time_text' => $timeText,
|
|
// 'meta_text' => $metaText,
|
|
// 'box_class' => $boxClass,
|
|
// ];
|
|
// }
|
|
// $this->rows = $rows;
|
|
// }
|
|
//
|
|
// /** ---- helpers ---- */
|
|
//
|
|
// private function f2b(string $args): array
|
|
// {
|
|
// $sudo = $this->bin('sudo');
|
|
// $f2b = $this->bin('fail2ban-client');
|
|
// $out = (string)@shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
|
// $ok = stripos($out, 'Status') !== false
|
|
// || stripos($out, 'Jail list') !== false
|
|
// || stripos($out, 'pong') !== false;
|
|
// return [$ok, $out];
|
|
// }
|
|
//
|
|
// private function getBantime(string $jail): int
|
|
// {
|
|
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
|
// if (preg_match('/-?\d+/', trim($out), $m)) return (int)$m[0];
|
|
// return 600;
|
|
// }
|
|
//
|
|
// /** findet die letzte "Ban <IP>"-Zeile in log / rotierte .gz / journald */
|
|
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
|
// {
|
|
// $sudo = $this->bin('sudo');
|
|
// $zgrep = $this->bin('zgrep'); // findet /bin/zgrep ODER /usr/bin/zgrep
|
|
// $grep = $this->bin('grep');
|
|
// $tail = $this->bin('tail');
|
|
// $journal = $this->bin('journalctl');
|
|
//
|
|
// // 1) exakter Treffer: "[jail] Ban IP"
|
|
// $needleExact = sprintf('[%s] Ban %s', $jail, $ip);
|
|
// $qExact = escapeshellarg($needleExact);
|
|
//
|
|
// $cmd1 = "$sudo -n $zgrep -h -F $qExact /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string) @shell_exec($cmd1));
|
|
//
|
|
// // 2) Fallback: nur "Ban IP" (falls Jail-Name im Log abweicht)
|
|
// if ($line === '') {
|
|
// $needleLoose = sprintf('Ban %s', $ip);
|
|
// $qLoose = escapeshellarg($needleLoose);
|
|
// $cmd2 = "$sudo -n $zgrep -h -F $qLoose /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string) @shell_exec($cmd2));
|
|
// }
|
|
//
|
|
// // 3) Fallback: unkomprimiertes Log mit grep
|
|
// if ($line === '') {
|
|
// $cmd3 = "$sudo -n $grep -h -F $qExact /var/log/fail2ban.log 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string) @shell_exec($cmd3));
|
|
// if ($line === '') {
|
|
// $cmd3b = "$sudo -n $grep -h -F ".escapeshellarg("Ban $ip")." /var/log/fail2ban.log 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string) @shell_exec($cmd3b));
|
|
// }
|
|
// }
|
|
//
|
|
// // 4) Fallback: journald
|
|
// if ($line === '') {
|
|
// $cmd4 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager | $grep -F $qExact | $tail -n 1";
|
|
// $line = trim((string) @shell_exec($cmd4));
|
|
// if ($line === '') {
|
|
// $cmd4b = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager | $grep -F ".escapeshellarg("Ban $ip")." | $tail -n 1";
|
|
// $line = trim((string) @shell_exec($cmd4b));
|
|
// }
|
|
// }
|
|
//
|
|
// if ($line === '') return null;
|
|
//
|
|
// // Zeitstempel extrahieren (YYYY-MM-DD HH:MM:SS[,ms] egal)
|
|
// 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;
|
|
// }
|
|
//
|
|
// private function firstMatch(string $pattern, string $haystack): ?string
|
|
// {
|
|
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
|
// }
|
|
//
|
|
// private function present(?int $remaining, ?int $banAt, ?int $until): array
|
|
// {
|
|
// $amber = 'border-amber-400/30 bg-amber-500/10';
|
|
// $rose = 'border-rose-400/30 bg-rose-500/10';
|
|
// $muted = 'border-white/10 bg-white/5';
|
|
//
|
|
// if ($remaining === -1) {
|
|
// return ['permanent', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $rose];
|
|
// }
|
|
// if ($remaining === -2) {
|
|
// // zeigt ungefähre Bantime, falls Timestamp nicht gefunden wurde
|
|
// return ['~ '.$this->fmtSecs($this->getApproxBantime()), '—', $amber];
|
|
// }
|
|
// if (is_int($remaining)) {
|
|
// if ($remaining > 0) {
|
|
// $meta = [];
|
|
// if ($banAt) $meta[] = 'seit '.$this->fmtTs($banAt);
|
|
// if ($until) $meta[] = 'bis '.$this->fmtTs($until);
|
|
// return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
|
// }
|
|
// return ['abgelaufen', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $muted];
|
|
// }
|
|
// return ['—', '—', $muted];
|
|
// }
|
|
//
|
|
// private function getApproxBantime(): int { return 600; }
|
|
//
|
|
// private function fmtSecs(int $s): string
|
|
// {
|
|
// $h = intdiv($s, 3600);
|
|
// $m = intdiv($s % 3600, 60);
|
|
// $r = $s % 60;
|
|
// if ($h > 0) return sprintf('%dh %02dm %02ds', $h, $m, $r);
|
|
// if ($m > 0) return sprintf('%02dm %02ds', $m, $r);
|
|
// return sprintf('%ds', $r);
|
|
// }
|
|
//
|
|
// private function fmtTs(int $ts): string
|
|
// {
|
|
// return date('d.m. H:i', $ts);
|
|
// }
|
|
//
|
|
// private function bin(string $name): string
|
|
// {
|
|
// $p = trim((string)@shell_exec("command -v ".escapeshellarg($name)." 2>/dev/null"));
|
|
// return $p !== '' ? $p : $name;
|
|
// }
|
|
//}
|
|
|
|
//namespace App\Livewire\Ui\Security\Modal;
|
|
//
|
|
//use LivewireUI\Modal\ModalComponent;
|
|
//
|
|
//class Fail2BanJailModal extends ModalComponent
|
|
//{
|
|
// public string $jail = '';
|
|
// public array $rows = []; // [{ip,bantime,banned_at,remaining,until,time_text,meta_text,box_class}]
|
|
//
|
|
// public static function modalMaxWidth(): string
|
|
// {
|
|
// return '4xl';
|
|
// }
|
|
//
|
|
// public function mount(string $jail): void
|
|
// {
|
|
// $this->jail = $jail;
|
|
// $this->load();
|
|
// }
|
|
//
|
|
// public function render()
|
|
// {
|
|
// return view('livewire.ui.security.modal.fail2-ban-jail-modal');
|
|
// }
|
|
//
|
|
// public function refresh(): void
|
|
// {
|
|
// $this->load(true);
|
|
// }
|
|
//
|
|
// /* ---------------- intern ---------------- */
|
|
//
|
|
// protected function load(bool $force = false): void
|
|
// {
|
|
// $jail = $this->jail;
|
|
//
|
|
// // Aktuell gebannte IPs
|
|
// [, $s] = $this->f2b('status ' . escapeshellarg($jail));
|
|
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
|
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
|
//
|
|
// $bantime = $this->getBantime($jail);
|
|
//
|
|
// $rows = [];
|
|
// foreach ($ips as $ip) {
|
|
// $banAt = $this->lastBanTimestamp($jail, $ip); // Unix-Timestamp oder null
|
|
//
|
|
// $remaining = null;
|
|
// $until = null;
|
|
//
|
|
// if ($bantime === -1) {
|
|
// $remaining = -1;
|
|
// } elseif ($banAt !== null) {
|
|
// $remaining = max(0, $bantime - (time() - $banAt));
|
|
// $until = $remaining > 0 ? ($banAt + $bantime) : null;
|
|
// }
|
|
//
|
|
// [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until);
|
|
//
|
|
// $rows[] = [
|
|
// 'ip' => $ip,
|
|
// 'bantime' => $bantime,
|
|
// 'banned_at' => $banAt,
|
|
// 'remaining' => $remaining,
|
|
// 'until' => $until,
|
|
// 'time_text' => $timeText,
|
|
// 'meta_text' => $metaText,
|
|
// 'box_class' => $boxClass,
|
|
// ];
|
|
// }
|
|
//
|
|
// $this->rows = $rows;
|
|
// }
|
|
//
|
|
// /** sudo + fail2ban-client */
|
|
// private function f2b(string $args): array
|
|
// {
|
|
// $sudo = '/usr/bin/sudo';
|
|
// $f2b = '/usr/bin/fail2ban-client';
|
|
// $out = (string)@shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
|
// $ok = stripos($out, 'Status') !== false
|
|
// || stripos($out, 'Jail list') !== false
|
|
// || stripos($out, 'pong') !== false;
|
|
// return [$ok, $out];
|
|
// }
|
|
//
|
|
// private function getBantime(string $jail): int
|
|
// {
|
|
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
|
// $val = trim($out);
|
|
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
|
// return 600;
|
|
// }
|
|
//
|
|
// /** robust: zgrep + journalctl via sudo */
|
|
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
|
// {
|
|
// $sudo = '/usr/bin/sudo';
|
|
// $zgrep = '/usr/bin/zgrep';
|
|
// $journal = '/usr/bin/journalctl';
|
|
// $tail = '/usr/bin/tail';
|
|
//
|
|
// $needle = sprintf('[%s] Ban %s', $jail, $ip);
|
|
// $q = escapeshellarg($needle);
|
|
//
|
|
// // Logs inkl. Rotation (gz)
|
|
// $cmd1 = "$sudo -n $zgrep -h $q /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd1));
|
|
//
|
|
// if ($line === '') { // Fallback: journald
|
|
// $cmd2 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager -g $q 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd2));
|
|
// }
|
|
// if ($line === '') return null;
|
|
//
|
|
// 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;
|
|
// }
|
|
//
|
|
// private function firstMatch(string $pattern, string $haystack): ?string
|
|
// {
|
|
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
|
// }
|
|
//
|
|
// private function present(?int $remaining, ?int $banAt, ?int $until): array
|
|
// {
|
|
// $amber = 'border-amber-400/30 bg-amber-500/10';
|
|
// $rose = 'border-rose-400/30 bg-rose-500/10';
|
|
// $muted = 'border-white/10 bg-white/5';
|
|
//
|
|
// if ($remaining === -1) {
|
|
// return ['permanent', $banAt ? ('seit ' . $this->fmtTs($banAt)) : '—', $rose];
|
|
// }
|
|
// if (is_int($remaining)) {
|
|
// if ($remaining > 0) {
|
|
// $meta = [];
|
|
// if ($banAt) $meta[] = 'seit ' . $this->fmtTs($banAt);
|
|
// if ($until) $meta[] = 'bis ' . $this->fmtTs($until);
|
|
// return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
|
// }
|
|
// return ['abgelaufen', $banAt ? ('seit ' . $this->fmtTs($banAt)) : '—', $muted];
|
|
// }
|
|
// return ['—', '—', $muted];
|
|
// }
|
|
//
|
|
// private function fmtSecs(int $s): string
|
|
// {
|
|
// $h = intdiv($s, 3600);
|
|
// $m = intdiv($s % 3600, 60);
|
|
// $r = $s % 60;
|
|
// if ($h > 0) return sprintf('%dh %02dm %02ds', $h, $m, $r);
|
|
// if ($m > 0) return sprintf('%02dm %02ds', $m, $r);
|
|
// return sprintf('%ds', $r);
|
|
// }
|
|
//
|
|
// private function fmtTs(int $ts): string
|
|
// {
|
|
// return date('d.m. H:i', $ts);
|
|
// }
|
|
//}
|
|
|
|
//
|
|
//namespace App\Livewire\Ui\Security\Modal;
|
|
//
|
|
//use LivewireUI\Modal\ModalComponent;
|
|
//
|
|
//class Fail2BanJailModal extends ModalComponent
|
|
//{
|
|
// public string $jail = '';
|
|
// /** @var array<int,array{
|
|
// * ip:string,
|
|
// * bantime:int, // Sek.; -1 = permanent
|
|
// * banned_at:?int, // Unix-Timestamp
|
|
// * remaining:?int, // -1=permanent, 0..Sekunden, null=unbekannt
|
|
// * until:?int, // Unix-Timestamp oder null
|
|
// * time_text:string, // "HHh MMm SSs", "permanent", "—"
|
|
// * meta_text:string, // "seit … • bis …" oder "seit …" oder "—"
|
|
// * box_class:string // Tailwind-Klassen für BG/Border
|
|
// * }]
|
|
// */
|
|
// public array $rows = [];
|
|
//
|
|
// public static function modalMaxWidth(): string
|
|
// {
|
|
// return '4xl';
|
|
// }
|
|
//
|
|
// public function mount(string $jail): void
|
|
// {
|
|
// $this->jail = $jail;
|
|
// $this->load();
|
|
// }
|
|
//
|
|
// public function refresh(): void
|
|
// {
|
|
// $this->load(true);
|
|
// }
|
|
//
|
|
// public function render()
|
|
// {
|
|
// return view('livewire.ui.security.modal.fail2-ban-jail-modal');
|
|
// }
|
|
//
|
|
// /* ---------------- intern ---------------- */
|
|
//
|
|
// protected function load(bool $force = false): void
|
|
// {
|
|
// $jail = $this->jail;
|
|
//
|
|
// // Aktuell gebannte IPs
|
|
// [, $s] = $this->f2b('status '.escapeshellarg($jail));
|
|
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
|
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
|
//
|
|
// // bantime für Jail
|
|
// $bantime = $this->getBantime($jail);
|
|
//
|
|
// $rows = [];
|
|
// foreach ($ips as $ip) {
|
|
// $banAt = $this->lastBanTimestamp($jail, $ip); // Unix-Timestamp oder null
|
|
//
|
|
// $remaining = null;
|
|
// $until = null;
|
|
// if ($banAt !== null) {
|
|
// if ($bantime === -1) {
|
|
// $remaining = -1;
|
|
// } else {
|
|
// $remaining = max(0, $bantime - (time() - $banAt));
|
|
// $until = $remaining > 0 ? $banAt + $bantime : null;
|
|
// }
|
|
// }
|
|
//
|
|
// // Darstellung
|
|
// [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until);
|
|
//
|
|
// $rows[] = [
|
|
// 'ip' => $ip,
|
|
// 'bantime' => $bantime,
|
|
// 'banned_at' => $banAt,
|
|
// 'remaining' => $remaining,
|
|
// 'until' => $until,
|
|
// 'time_text' => $timeText,
|
|
// 'meta_text' => $metaText,
|
|
// 'box_class' => $boxClass,
|
|
// ];
|
|
// }
|
|
//
|
|
// $this->rows = $rows;
|
|
// }
|
|
//
|
|
// /** letzte "Ban <IP>"-Zeile -> Unix-Timestamp (robust über zgrep/journal/jail-tail) */
|
|
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
|
// {
|
|
// $sudo = '/usr/bin/sudo';
|
|
// $zgrep = '/usr/bin/zgrep';
|
|
// $journal = '/usr/bin/journalctl';
|
|
// $tail = '/usr/bin/tail';
|
|
//
|
|
// $needle = sprintf('[%s] Ban %s', $jail, $ip);
|
|
// $q = escapeshellarg($needle);
|
|
//
|
|
// // 1) zgrep über alle fail2ban-Logs (inkl. Rotation .N, .gz)
|
|
// $cmd1 = "$sudo -n $zgrep -h $q /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd1));
|
|
//
|
|
// // 2) Fallback: journald (ohne grep-Pipe, mit -g)
|
|
// if ($line === '') {
|
|
// $cmd2 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager -g $q 2>/dev/null | $tail -n 1";
|
|
// $line = trim((string)@shell_exec($cmd2));
|
|
// }
|
|
//
|
|
// if ($line === '') return null;
|
|
//
|
|
// // Zeitstempel extrahieren (YYYY-MM-DD HH:MM:SS)
|
|
// 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
|
|
// {
|
|
// $green = 'border-emerald-400/30 bg-emerald-500/10';
|
|
// $amber = 'border-amber-400/30 bg-amber-500/10';
|
|
// $rose = 'border-rose-400/30 bg-rose-500/10';
|
|
// $muted = 'border-white/10 bg-white/5';
|
|
//
|
|
// if ($remaining === -1) {
|
|
// return ['permanent', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $rose];
|
|
// }
|
|
// if ($remaining === -2) {
|
|
// // Approximation: Bannzeit ohne Startzeitpunkt
|
|
// return ['~ '.$this->fmtSecs((int) $this->getApproxBantime()), '—', $amber];
|
|
// }
|
|
// if (is_int($remaining)) {
|
|
// if ($remaining > 0) {
|
|
// $meta = [];
|
|
// if ($banAt) $meta[] = 'seit '.$this->fmtTs($banAt);
|
|
// if ($until) $meta[] = 'bis '.$this->fmtTs($until);
|
|
// return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
|
// }
|
|
// return ['abgelaufen', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $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 */
|
|
// private function f2b(string $args): array
|
|
// {
|
|
// $sudo = '/usr/bin/sudo';
|
|
// $f2b = '/usr/bin/fail2ban-client';
|
|
// $out = (string) @shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
|
// $ok = stripos($out, 'Status') !== false
|
|
// || stripos($out, 'Jail list') !== false
|
|
// || stripos($out, 'pong') !== false;
|
|
// return [$ok, $out];
|
|
// }
|
|
//
|
|
// private function getBantime(string $jail): int
|
|
// {
|
|
// [, $out] = $this->f2b('get '.escapeshellarg($jail).' bantime');
|
|
// $val = trim($out);
|
|
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
|
// return 600;
|
|
// }
|
|
//
|
|
// /** letzte "Ban <IP>"-Zeile parsen -> Unix-Timestamp */
|
|
// /** letzte "Ban <IP>"-Zeile -> Unix-Timestamp aus /var/log/fail2ban.log */
|
|
//
|
|
// private function firstMatch(string $pattern, string $haystack): ?string
|
|
// {
|
|
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
|
// }
|
|
//
|
|
//
|
|
// private function fmtSecs(int $s): string
|
|
// {
|
|
// // kompakt: 1h 23m 45s / 05m 10s / 12s
|
|
// $h = intdiv($s, 3600);
|
|
// $m = intdiv($s % 3600, 60);
|
|
// $r = $s % 60;
|
|
// if ($h > 0) return sprintf('%dh %02dm %02ds', $h, $m, $r);
|
|
// if ($m > 0) return sprintf('%02dm %02ds', $m, $r);
|
|
// return sprintf('%ds', $r);
|
|
// }
|
|
//
|
|
// private function fmtTs(int $ts): string
|
|
// {
|
|
// // 29.10. 18:07
|
|
// return date('d.m. H:i', $ts);
|
|
// }
|
|
//}
|