parent
aaeea2bb86
commit
fc0ede4840
|
|
@ -4,6 +4,7 @@ namespace App\Livewire\Ui\Security;
|
|||
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
use SQLite3;
|
||||
|
||||
class Fail2banBanlist extends Component
|
||||
{
|
||||
|
|
@ -15,22 +16,9 @@ class Fail2banBanlist extends Component
|
|||
public ?string $jail = null;
|
||||
|
||||
/**
|
||||
* Struktur für Blade (reine Ausgabe, keine Logik im Blade):
|
||||
* [
|
||||
* [
|
||||
* 'ip' => '1.2.3.4',
|
||||
* 'jail' => 'recidive',
|
||||
* 'permanent' => false,
|
||||
* 'label' => 'Temporär', // oder 'Permanent'
|
||||
* 'box' => 'border-amber-400/20 bg-white/3', // Kartenstil
|
||||
* 'badge' => 'border-amber-400/30 bg-amber-500/10 text-amber-200',
|
||||
* 'btn' => 'border-rose-400/30 bg-rose-500/10 text-rose-200 hover:border-rose-400/50',
|
||||
* ],
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* @var array<int,array{
|
||||
* ip:string,jail:string,permanent:bool,label:string,box:string,badge:string,btn:string
|
||||
* ip:string,jail:string,service:string,permanent:bool,label:string,
|
||||
* remaining:string,box:string,badge:string,dot:string,btn:string
|
||||
* }>
|
||||
*/
|
||||
public array $rows = [];
|
||||
|
|
@ -75,7 +63,9 @@ class Fail2banBanlist extends Component
|
|||
continue;
|
||||
}
|
||||
|
||||
$permanent = $this->isPermanent($j, $ip);
|
||||
$banInfo = $this->getBanInfo($j, $ip);
|
||||
$permanent = $banInfo['permanent'];
|
||||
$remaining = $banInfo['remaining'];
|
||||
|
||||
if ($permanent) {
|
||||
$box = 'border-rose-400/30 bg-rose-500/5';
|
||||
|
|
@ -94,9 +84,11 @@ class Fail2banBanlist extends Component
|
|||
$rows[] = [
|
||||
'ip' => $ip,
|
||||
'jail' => $j,
|
||||
'service' => $this->serviceLabel($j),
|
||||
'permanent' => $permanent,
|
||||
'style' => $style,
|
||||
'label' => $label,
|
||||
'remaining' => $remaining,
|
||||
'box' => $box,
|
||||
'badge' => $badge,
|
||||
'dot' => $dot,
|
||||
|
|
@ -140,35 +132,67 @@ class Fail2banBanlist extends Component
|
|||
|
||||
/* ================= helpers ================= */
|
||||
|
||||
/** Prüft via SQLite, ob der **letzte** Ban für (jail, ip) permanent ist (bantime < 0). */
|
||||
private function isPermanent(string $jail, string $ip): bool
|
||||
/** Gibt permanent-Flag und verbleibende Zeit für (jail, ip) zurück. */
|
||||
private function getBanInfo(string $jail, string $ip): array
|
||||
{
|
||||
$db = $this->getDbFile();
|
||||
$fallbackPermanent = ($jail === 'mailwolt-blacklist');
|
||||
|
||||
if ($db === '' || !is_readable($db)) {
|
||||
// Fallback: Blacklist-Jail ist per Design permanent
|
||||
return $jail === 'mailwolt-blacklist';
|
||||
return ['permanent' => $fallbackPermanent, 'remaining' => ''];
|
||||
}
|
||||
|
||||
$q = <<<SQL
|
||||
WITH last AS (
|
||||
SELECT MAX(timeofban) AS t
|
||||
FROM bans
|
||||
WHERE jail = '$jail' AND ip = '$ip'
|
||||
)
|
||||
SELECT bantime
|
||||
FROM bans, last
|
||||
WHERE jail = '$jail' AND ip = '$ip' AND timeofban = last.t
|
||||
LIMIT 1;
|
||||
SQL;
|
||||
|
||||
$cmd = sprintf(
|
||||
'sudo -n /usr/bin/sqlite3 -readonly %s %s 2>&1',
|
||||
escapeshellarg($db),
|
||||
escapeshellarg($q)
|
||||
$q = sprintf(
|
||||
"WITH last AS (SELECT MAX(timeofban) AS t FROM bans WHERE jail='%s' AND ip='%s') ".
|
||||
"SELECT timeofban, bantime FROM bans, last ".
|
||||
"WHERE jail='%s' AND ip='%s' AND timeofban=last.t LIMIT 1;",
|
||||
SQLite3::escapeString($jail), SQLite3::escapeString($ip),
|
||||
SQLite3::escapeString($jail), SQLite3::escapeString($ip)
|
||||
);
|
||||
|
||||
$cmd = sprintf('sudo -n /usr/bin/sqlite3 -readonly %s %s 2>&1',
|
||||
escapeshellarg($db), escapeshellarg($q));
|
||||
$out = trim((string)@shell_exec($cmd));
|
||||
if ($out === '') return ($jail === 'mailwolt-blacklist'); // Fallback
|
||||
return ((int)$out) < 0;
|
||||
|
||||
if ($out === '') {
|
||||
return ['permanent' => $fallbackPermanent, 'remaining' => ''];
|
||||
}
|
||||
|
||||
[$timeofban, $bantime] = array_pad(explode('|', $out), 2, '0');
|
||||
$timeofban = (int)$timeofban;
|
||||
$bantime = (int)$bantime;
|
||||
|
||||
if ($bantime < 0) {
|
||||
return ['permanent' => true, 'remaining' => ''];
|
||||
}
|
||||
|
||||
$remaining = max(0, ($timeofban + $bantime) - time());
|
||||
return [
|
||||
'permanent' => false,
|
||||
'remaining' => $this->formatRemaining($remaining),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatRemaining(int $seconds): string
|
||||
{
|
||||
if ($seconds <= 0) return 'läuft ab';
|
||||
if ($seconds < 60) return "noch {$seconds} Sek.";
|
||||
if ($seconds < 3600) return 'noch ' . (int)ceil($seconds / 60) . ' Min.';
|
||||
if ($seconds < 86400) return 'noch ' . round($seconds / 3600, 1) . ' Std.';
|
||||
return 'noch ' . (int)ceil($seconds / 86400) . ' Tage';
|
||||
}
|
||||
|
||||
private function serviceLabel(string $jail): string
|
||||
{
|
||||
return match(true) {
|
||||
$jail === 'sshd' => 'SSH',
|
||||
$jail === 'dovecot' => 'IMAP/POP3',
|
||||
str_starts_with($jail, 'postfix') => 'SMTP',
|
||||
str_starts_with($jail, 'nginx') => 'Web',
|
||||
$jail === 'recidive' => 'Recidive',
|
||||
str_contains($jail, 'blacklist') => 'Blacklist',
|
||||
default => $jail,
|
||||
};
|
||||
}
|
||||
|
||||
/** Liste aller Jails */
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@
|
|||
<div style="padding:10px 12px">
|
||||
@foreach($rows as $r)
|
||||
<div class="f2b-row {{ $r['style'] }}">
|
||||
<div style="display:flex;align-items:center;gap:10px;min-width:0">
|
||||
<div style="display:flex;align-items:center;gap:10px;min-width:0;flex:1">
|
||||
<div class="f2b-dot {{ $r['style'] }}"></div>
|
||||
<code style="font-size:12.5px;color:var(--mw-t1);letter-spacing:.02em;flex-shrink:0">{{ $r['ip'] }}</code>
|
||||
<span class="f2b-jail-badge">{{ $r['jail'] }}</span>
|
||||
<span class="f2b-jail-badge" title="{{ $r['jail'] }}">{{ $r['service'] }}</span>
|
||||
<span style="font-size:10.5px;color:{{ $r['style'] === 'permanent' ? 'rgba(252,165,165,.8)' : 'rgba(252,211,77,.8)' }}">{{ $r['label'] }}</span>
|
||||
@if($r['remaining'] !== '')
|
||||
<span style="font-size:10.5px;color:var(--mw-t4)">· {{ $r['remaining'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<button wire:click="unban('{{ $r['ip'] }}','{{ $r['jail'] }}')"
|
||||
class="mbx-act-btn mbx-act-danger" title="Entbannen"
|
||||
|
|
|
|||
Loading…
Reference in New Issue