Fix: Mailbox Stats über Dovecot mit config/mailpool.php

main v1.0.90
boban 2025-10-28 22:53:41 +01:00
parent 2ef27b5e8f
commit 5e233ec16d
2 changed files with 253 additions and 69 deletions

View File

@ -1,10 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace App\Livewire\Ui\Security; namespace App\Livewire\Ui\Security;
use Livewire\Component;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class RblCard extends Component class RblCard extends Component
{ {
@ -15,6 +15,9 @@ class RblCard extends Component
public ?string $ipv4 = null; public ?string $ipv4 = null;
public ?string $ipv6 = null; public ?string $ipv6 = null;
// Schalte registrierungspflichtige Listen (Barracuda etc.) optional zu
private bool $includeRegistered = false; // env('RBL_INCLUDE_REGISTERED', false) wenn du willst
public function mount(): void public function mount(): void
{ {
$this->load(); $this->load();
@ -33,33 +36,21 @@ class RblCard extends Component
protected function load(bool $force = false): void protected function load(bool $force = false): void
{ {
// 1) IPv4/IPv6 bevorzugt aus /etc/mailwolt/installer.env
[$ip4, $ip6] = $this->resolvePublicIpsFromInstallerEnv(); [$ip4, $ip6] = $this->resolvePublicIpsFromInstallerEnv();
// 2) Fallback auf .env $this->ipv4 = $ip4 ?: trim((string)env('SERVER_PUBLIC_IPV4', '')) ?: '';
$this->ipv4 = $ip4 ?: trim((string) env('SERVER_PUBLIC_IPV4', '')) ?: ''; $this->ipv6 = $ip6 ?: trim((string)env('SERVER_PUBLIC_IPV6', '')) ?: '';
$this->ipv6 = $ip6 ?: trim((string) env('SERVER_PUBLIC_IPV6', '')) ?: '';
// 3) RBL-Ermittlung (cached) $data = Cache::remember('dash.rbl', $force ? 1 : 6 * 3600, function () {
$data = Cache::remember('dash.rbl', $force ? 1 : 21600, function () {
// bevorzugt eine valide IPv4 für den RBL-Check
$candidate = $this->validIPv4($this->ipv4 ?? '') ? $this->ipv4 : null; $candidate = $this->validIPv4($this->ipv4 ?? '') ? $this->ipv4 : null;
if (!$candidate) { if (!$candidate) {
$fromFile = @file_get_contents('/etc/mailwolt/public_ip') ?: ''; $fromFile = trim((string)@file_get_contents('/etc/mailwolt/public_ip'));
$fromFile = trim($fromFile); if ($this->validIPv4($fromFile)) $candidate = $fromFile;
if ($this->validIPv4($fromFile)) {
$candidate = $fromFile;
} }
}
if (!$candidate) { if (!$candidate) {
// letzter Fallback kann auf Hardened-Systemen geblockt sein $curl = trim((string)@shell_exec("curl -fsS --max-time 2 ifconfig.me 2>/dev/null"));
$curl = @shell_exec("curl -fsS --max-time 2 ifconfig.me 2>/dev/null") ?: ''; if ($this->validIPv4($curl)) $candidate = $curl;
$curl = trim($curl);
if ($this->validIPv4($curl)) {
$candidate = $curl;
}
} }
$ip = $candidate ?: '0.0.0.0'; $ip = $candidate ?: '0.0.0.0';
@ -68,82 +59,76 @@ class RblCard extends Component
return ['ip' => $ip, 'hits' => count($lists), 'lists' => $lists]; return ['ip' => $ip, 'hits' => count($lists), 'lists' => $lists];
}); });
// 4) Werte ins Component-State foreach ($data as $k => $v) $this->$k = $v;
foreach ($data as $k => $v) {
$this->$k = $v;
}
} }
/** Bevorzugt Installer-ENV; gibt [ipv4, ipv6] zurück oder [null, null]. */ /** bevorzugt Installer-ENV */
private function resolvePublicIpsFromInstallerEnv(): array private function resolvePublicIpsFromInstallerEnv(): array
{ {
$file = '/etc/mailwolt/installer.env'; $file = '/etc/mailwolt/installer.env';
if (!is_readable($file)) { if (!is_readable($file)) return [null, null];
return [null, null];
}
$ipv4 = null;
$ipv6 = null;
$ipv4 = $ipv6 = null;
$lines = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; $lines = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
foreach ($lines as $line) { foreach ($lines as $line) {
// Kommentare überspringen if (preg_match('/^\s*#/', $line) || !str_contains($line, '=')) continue;
if (preg_match('/^\s*#/', $line)) {
continue;
}
// KEY=VALUE (VALUE evtl. in "..." oder '...')
if (!str_contains($line, '=')) {
continue;
}
[$k, $v] = array_map('trim', explode('=', $line, 2)); [$k, $v] = array_map('trim', explode('=', $line, 2));
$v = trim($v, " \t\n\r\0\x0B\"'"); $v = trim($v, " \t\n\r\0\x0B\"'");
if ($k === 'SERVER_PUBLIC_IPV4' && $this->validIPv4($v)) $ipv4 = $v;
if ($k === 'SERVER_PUBLIC_IPV4' && $this->validIPv4($v)) { if ($k === 'SERVER_PUBLIC_IPV6' && $this->validIPv6($v)) $ipv6 = $v;
$ipv4 = $v;
} elseif ($k === 'SERVER_PUBLIC_IPV6' && $this->validIPv6($v)) {
$ipv6 = $v;
} }
}
return [$ipv4, $ipv6]; return [$ipv4, $ipv6];
} }
private function validIPv4(?string $ip): bool private function validIPv4(?string $ip): bool
{ {
return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); return (bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
} }
private function validIPv6(?string $ip): bool private function validIPv6(?string $ip): bool
{ {
return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); return (bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
} }
/** /**
* Prüft die IP gegen ein paar gängige RBLs. * Prüft die IP gegen gängige **öffentliche** RBLs.
* Nutzt PHP-DNS (checkdnsrr), keine externen Tools.
*
* @return array<string> gelistete RBL-Zonen * @return array<string> gelistete RBL-Zonen
*/ */
private function queryRblLists(string $ip): array private function queryRblLists(string $ip): array
{ {
// Nur IPv4 prüfen (die meisten Listen hier sind v4) if (!$this->validIPv4($ip)) return [];
if (!$this->validIPv4($ip)) {
return [];
}
$rev = implode('.', array_reverse(explode('.', $ip))); $rev = implode('.', array_reverse(explode('.', $ip)));
$sources = [
'zen.spamhaus.org', // Öffentliche/abfragbare Zonen (keine Registrierung nötig)
'bl.spamcop.net', $publicZones = [
'dnsbl.sorbs.net', 'zen.spamhaus.org', // seriös, rate-limited
'b.barracudacentral.org', 'psbl.surriel.com', // public
'dnsbl-1.uceprotect.net', // public (umstritten, aber abfragbar)
'all.s5h.net', // public
]; ];
// Registrierungspflichtige nur optional prüfen
$registeredZones = [
'b.barracudacentral.org', // benötigt Account
// weitere bei Bedarf
];
$zones = $publicZones;
if ($this->includeRegistered) {
$zones = array_merge($zones, $registeredZones);
}
// Vorab: Zone existiert? (NS-Record) sonst überspringen
$zones = array_values(array_filter($zones, function ($z) {
return @checkdnsrr($z . '.', 'NS');
}));
$listed = []; $listed = [];
foreach ($sources as $zone) { foreach ($zones as $zone) {
$qname = "{$rev}.{$zone}"; $qname = "{$rev}.{$zone}.";
// A-Record oder TXT deuten auf Listing hin // Wenn A oder TXT existiert → gelistet
if (@checkdnsrr($qname . '.', 'A') || @checkdnsrr($qname . '.', 'TXT')) { if (@checkdnsrr($qname, 'A') || @checkdnsrr($qname, 'TXT')) {
$listed[] = $zone; $listed[] = $zone;
} }
} }
@ -151,6 +136,158 @@ class RblCard extends Component
return $listed; return $listed;
} }
} }
//declare(strict_types=1);
//
//namespace App\Livewire\Ui\Security;
//
//use Livewire\Component;
//use Illuminate\Support\Facades\Cache;
//
//class RblCard extends Component
//{
// public string $ip = '';
// public int $hits = 0;
// public array $lists = [];
//
// public ?string $ipv4 = null;
// public ?string $ipv6 = null;
//
// public function mount(): void
// {
// $this->load();
// }
//
// public function render()
// {
// return view('livewire.ui.security.rbl-card');
// }
//
// public function refresh(): void
// {
// Cache::forget('dash.rbl');
// $this->load(true);
// }
//
// protected function load(bool $force = false): void
// {
// // 1) IPv4/IPv6 bevorzugt aus /etc/mailwolt/installer.env
// [$ip4, $ip6] = $this->resolvePublicIpsFromInstallerEnv();
//
// // 2) Fallback auf .env
// $this->ipv4 = $ip4 ?: trim((string) env('SERVER_PUBLIC_IPV4', '')) ?: '';
// $this->ipv6 = $ip6 ?: trim((string) env('SERVER_PUBLIC_IPV6', '')) ?: '';
//
// // 3) RBL-Ermittlung (cached)
// $data = Cache::remember('dash.rbl', $force ? 1 : 21600, function () {
// // bevorzugt eine valide IPv4 für den RBL-Check
// $candidate = $this->validIPv4($this->ipv4 ?? '') ? $this->ipv4 : null;
//
// if (!$candidate) {
// $fromFile = @file_get_contents('/etc/mailwolt/public_ip') ?: '';
// $fromFile = trim($fromFile);
// if ($this->validIPv4($fromFile)) {
// $candidate = $fromFile;
// }
// }
//
// if (!$candidate) {
// // letzter Fallback kann auf Hardened-Systemen geblockt sein
// $curl = @shell_exec("curl -fsS --max-time 2 ifconfig.me 2>/dev/null") ?: '';
// $curl = trim($curl);
// if ($this->validIPv4($curl)) {
// $candidate = $curl;
// }
// }
//
// $ip = $candidate ?: '0.0.0.0';
// $lists = $this->queryRblLists($ip);
//
// return ['ip' => $ip, 'hits' => count($lists), 'lists' => $lists];
// });
//
// // 4) Werte ins Component-State
// foreach ($data as $k => $v) {
// $this->$k = $v;
// }
// }
//
// /** Bevorzugt Installer-ENV; gibt [ipv4, ipv6] zurück oder [null, null]. */
// private function resolvePublicIpsFromInstallerEnv(): array
// {
// $file = '/etc/mailwolt/installer.env';
// if (!is_readable($file)) {
// return [null, null];
// }
//
// $ipv4 = null;
// $ipv6 = null;
//
// $lines = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
// foreach ($lines as $line) {
// // Kommentare überspringen
// if (preg_match('/^\s*#/', $line)) {
// continue;
// }
// // KEY=VALUE (VALUE evtl. in "..." oder '...')
// if (!str_contains($line, '=')) {
// continue;
// }
// [$k, $v] = array_map('trim', explode('=', $line, 2));
// $v = trim($v, " \t\n\r\0\x0B\"'");
//
// if ($k === 'SERVER_PUBLIC_IPV4' && $this->validIPv4($v)) {
// $ipv4 = $v;
// } elseif ($k === 'SERVER_PUBLIC_IPV6' && $this->validIPv6($v)) {
// $ipv6 = $v;
// }
// }
//
// return [$ipv4, $ipv6];
// }
//
// private function validIPv4(?string $ip): bool
// {
// return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
// }
//
// private function validIPv6(?string $ip): bool
// {
// return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
// }
//
// /**
// * Prüft die IP gegen ein paar gängige RBLs.
// * Nutzt PHP-DNS (checkdnsrr), keine externen Tools.
// *
// * @return array<string> gelistete RBL-Zonen
// */
// private function queryRblLists(string $ip): array
// {
// // Nur IPv4 prüfen (die meisten Listen hier sind v4)
// if (!$this->validIPv4($ip)) {
// return [];
// }
//
// $rev = implode('.', array_reverse(explode('.', $ip)));
// $sources = [
// 'zen.spamhaus.org',
// 'bl.spamcop.net',
// 'dnsbl.sorbs.net',
// 'b.barracudacentral.org',
// ];
//
// $listed = [];
// foreach ($sources as $zone) {
// $qname = "{$rev}.{$zone}";
// // A-Record oder TXT deuten auf Listing hin
// if (@checkdnsrr($qname . '.', 'A') || @checkdnsrr($qname . '.', 'TXT')) {
// $listed[] = $zone;
// }
// }
//
// return $listed;
// }
//}
// //
//namespace App\Livewire\Ui\Security; //namespace App\Livewire\Ui\Security;
// //

View File

@ -4,17 +4,64 @@
<i class="ph ph-shield-warning text-white/70 text-[13px]"></i> <i class="ph ph-shield-warning text-white/70 text-[13px]"></i>
<span class="text-[11px] uppercase text-white/70">Reputation / RBL</span> <span class="text-[11px] uppercase text-white/70">Reputation / RBL</span>
</div> </div>
<div class="text-xs text-white/70">IP: <span class="text-white/90">{{ $ip }}</span></div>
<div class="text-xs text-white/70">
IP: <span class="text-white/90">{{ $ip }}</span>
</div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
@if($hits === 0) @if($hits === 0)
<span class="px-2 py-0.5 rounded-full border text-emerald-300 border-emerald-400/30 bg-emerald-500/10">Keine Treffer</span> <div class="flex items-center gap-2 text-emerald-300">
<i class="ph ph-shield-check-thin text-lg"></i>
<span>Deine IP ist aktuell auf keiner der geprüften Blacklists.</span>
</div>
<div class="mt-2 text-[11px] text-white/45">
Geprüfte öffentliche RBLs: Spamhaus, PSBL, UCEPROTECT-1, s5h.
</div>
@else @else
<div class="text-sm text-white/70">{{ $hits }} Treffer:</div> <div class="flex items-center gap-2 text-rose-300">
<i class="ph ph-warning-circle text-lg"></i>
<span>{{ $hits }} Treffer auf Blacklists:</span>
</div>
<ul class="mt-2 space-y-1 text-sm text-rose-300"> <ul class="mt-2 space-y-1 text-sm text-rose-300">
@foreach($lists as $l)<li> {{ $l }}</li>@endforeach @foreach($lists as $l)
<li class="flex items-center gap-2">
<span class="inline-block w-1.5 h-1.5 rounded-full bg-rose-400"></span>
<span>{{ $l }}</span>
</li>
@endforeach
</ul> </ul>
@endif @endif
</div> </div>
<div class="mt-3">
<button wire:click="refresh"
class="inline-flex items-center gap-1.5 rounded-full text-[11px] px-3 py-1
text-white/80 bg-white/10 border border-white/15
hover:bg-white/15 hover:text-white">
<i class="ph ph-arrows-clockwise text-[12px]"></i>
Neu prüfen
</button>
</div>
</div> </div>
{{--<div class="glass-card p-4 rounded-2xl border border-white/10 bg-white/5">--}}
{{-- <div class="flex items-center justify-between">--}}
{{-- <div class="inline-flex items-center gap-2 bg-white/5 border border-white/10 px-2.5 py-1 rounded-full">--}}
{{-- <i class="ph ph-shield-warning text-white/70 text-[13px]"></i>--}}
{{-- <span class="text-[11px] uppercase text-white/70">Reputation / RBL</span>--}}
{{-- </div>--}}
{{-- <div class="text-xs text-white/70">IP: <span class="text-white/90">{{ $ip }}</span></div>--}}
{{-- </div>--}}
{{-- <div class="mt-3">--}}
{{-- @if($hits === 0)--}}
{{-- <span class="px-2 py-0.5 rounded-full border text-emerald-300 border-emerald-400/30 bg-emerald-500/10">Keine Treffer</span>--}}
{{-- @else--}}
{{-- <div class="text-sm text-white/70">{{ $hits }} Treffer:</div>--}}
{{-- <ul class="mt-2 space-y-1 text-sm text-rose-300">--}}
{{-- @foreach($lists as $l)<li> {{ $l }}</li>@endforeach--}}
{{-- </ul>--}}
{{-- @endif--}}
{{-- </div>--}}
{{--</div>--}}