From b107e9a5808aa7e088453ea0fbede467f513f135 Mon Sep 17 00:00:00 2001 From: boban Date: Sun, 26 Apr 2026 19:21:37 +0200 Subject: [PATCH] Feature: ClamAV-Verwaltung in Sicherheit-Sidebar - Neue Seite /security/clamav: Status, Aktivieren/Deaktivieren, Signatur-Update, RAM-Hinweis, Info-Box - Optionale Dienste (ClamAV) im Dashboard nur sichtbar wenn aktiv - mailwolt-clamav sbin-Wrapper + sudoers-Regel in ensure_system Co-Authored-By: Claude Sonnet 4.6 --- app/Livewire/Ui/Nx/Dashboard.php | 2 + app/Livewire/Ui/Security/ClamavManager.php | 142 ++++++++++++++ config/ui-menu.php | 1 + config/woltguard.php | 1 + .../ui/security/clamav-manager.blade.php | 178 ++++++++++++++++++ routes/web.php | 1 + scripts/mailwolt-clamav | 31 +++ scripts/update.sh | 2 + 8 files changed, 358 insertions(+) create mode 100644 app/Livewire/Ui/Security/ClamavManager.php create mode 100644 resources/views/livewire/ui/security/clamav-manager.blade.php create mode 100755 scripts/mailwolt-clamav diff --git a/app/Livewire/Ui/Nx/Dashboard.php b/app/Livewire/Ui/Nx/Dashboard.php index c94c0f7..aab2e1f 100644 --- a/app/Livewire/Ui/Nx/Dashboard.php +++ b/app/Livewire/Ui/Nx/Dashboard.php @@ -90,6 +90,8 @@ class Dashboard extends Component foreach ($card['sources'] as $src) { if ($this->probeSource($src)) { $isOk = true; break; } } + // Optionale Dienste (z.B. ClamAV) nur anzeigen wenn aktiv + if (!$isOk && ($card['optional'] ?? false)) continue; $rows[] = ['label' => $card['label'], 'hint' => $card['hint'], 'ok' => $isOk]; } } diff --git a/app/Livewire/Ui/Security/ClamavManager.php b/app/Livewire/Ui/Security/ClamavManager.php new file mode 100644 index 0000000..bb7fedb --- /dev/null +++ b/app/Livewire/Ui/Security/ClamavManager.php @@ -0,0 +1,142 @@ +refresh(); + } + + public function refresh(): void + { + $this->lastError = ''; + $this->installed = $this->isInstalled(); + $this->running = $this->installed && $this->serviceActive(); + $this->enabled = $this->installed && $this->serviceEnabled(); + $this->ramMb = $this->running ? $this->readRamMb() : null; + [$this->dbDate, $this->dbVersion] = $this->readDbInfo(); + } + + public function enable(): void + { + if (!$this->installed) { + $this->lastError = 'ClamAV ist nicht installiert.'; + return; + } + $out = []; + @exec('sudo -n /usr/local/sbin/mailwolt-clamav enable 2>&1', $out, $rc); + if ($rc !== 0) { + $this->lastError = implode(' ', $out) ?: 'Fehler beim Aktivieren.'; + } else { + $this->dispatch('toast', type: 'success', badge: 'ClamAV', + title: 'Aktiviert', text: 'ClamAV läuft jetzt und startet automatisch beim Booten.'); + } + $this->refresh(); + } + + public function disable(): void + { + $out = []; + @exec('sudo -n /usr/local/sbin/mailwolt-clamav disable 2>&1', $out, $rc); + if ($rc !== 0) { + $this->lastError = implode(' ', $out) ?: 'Fehler beim Deaktivieren.'; + } else { + $this->dispatch('toast', type: 'success', badge: 'ClamAV', + title: 'Deaktiviert', text: 'ClamAV wurde gestoppt und aus dem Autostart entfernt.'); + } + $this->refresh(); + } + + public function updateDb(): void + { + $out = []; + @exec('sudo -n /usr/local/sbin/mailwolt-clamav freshclam 2>&1', $out, $rc); + if ($rc !== 0) { + $this->lastError = implode(' ', $out) ?: 'Fehler beim Aktualisieren.'; + } else { + $this->dispatch('toast', type: 'success', badge: 'ClamAV', + title: 'Datenbank aktualisiert', text: 'Virensignaturen wurden aktualisiert.'); + } + $this->refresh(); + } + + private function isInstalled(): bool + { + return is_executable('/usr/bin/clamscan') || is_executable('/usr/sbin/clamd') + || @file_exists('/usr/sbin/clamd') || @file_exists('/usr/bin/clamdscan'); + } + + private function serviceActive(): bool + { + $exit = null; + @exec('systemctl is-active --quiet clamav-daemon 2>/dev/null', $_, $exit); + return $exit === 0; + } + + private function serviceEnabled(): bool + { + $exit = null; + @exec('systemctl is-enabled --quiet clamav-daemon 2>/dev/null', $_, $exit); + return $exit === 0; + } + + private function readRamMb(): ?int + { + $out = []; + @exec("ps -C clamd -o rss= 2>/dev/null", $out); + $kb = array_sum(array_map('intval', array_filter($out))); + return $kb > 0 ? (int) round($kb / 1024) : null; + } + + private function readDbInfo(): array + { + $paths = [ + '/var/lib/clamav/main.cvd', + '/var/lib/clamav/main.cld', + '/var/lib/clamav/daily.cvd', + '/var/lib/clamav/daily.cld', + ]; + $latest = 0; + foreach ($paths as $p) { + if (@file_exists($p)) { + $mtime = @filemtime($p); + if ($mtime > $latest) $latest = $mtime; + } + } + if ($latest === 0) return ['—', '—']; + $date = date('d.m.Y H:i', $latest); + + $ver = '—'; + $out = []; + @exec('sigtool --info /var/lib/clamav/daily.cld 2>/dev/null | grep "^Version:" | head -1', $out); + if (empty($out)) { + @exec('sigtool --info /var/lib/clamav/daily.cvd 2>/dev/null | grep "^Version:" | head -1', $out); + } + if (!empty($out[0])) { + $ver = trim(str_replace('Version:', '', $out[0])); + } + + return [$date, $ver]; + } + + public function render() + { + return view('livewire.ui.security.clamav-manager'); + } +} diff --git a/config/ui-menu.php b/config/ui-menu.php index 2f9a5fc..d32dac8 100644 --- a/config/ui-menu.php +++ b/config/ui-menu.php @@ -40,6 +40,7 @@ return [ 'label' => 'Sicherheit', 'icon' => 'ph-shield', 'items' => [ ['label' => 'TLS & Ciphers', 'route' => 'ui.security.tls'], ['label' => 'Ratelimits', 'route' => 'ui.security.abuse'], + ['label' => 'Virenschutz', 'route' => 'ui.security.clamav'], ['label' => 'Audit-Logs', 'route' => 'ui.security.audit'], ], ], diff --git a/config/woltguard.php b/config/woltguard.php index 552397d..88a45b5 100644 --- a/config/woltguard.php +++ b/config/woltguard.php @@ -23,6 +23,7 @@ return [ ], 'clamav' => [ 'label' => 'ClamAV', 'hint' => 'Virenscanner', + 'optional' => true, 'sources' => [ 'systemd:clamav-daemon', 'systemd:clamav-daemon@scan', 'systemd:clamd', 'socket:/run/clamav/clamd.ctl', 'pid:/run/clamav/clamd.pid', 'tcp:127.0.0.1:3310', diff --git a/resources/views/livewire/ui/security/clamav-manager.blade.php b/resources/views/livewire/ui/security/clamav-manager.blade.php new file mode 100644 index 0000000..8c70d98 --- /dev/null +++ b/resources/views/livewire/ui/security/clamav-manager.blade.php @@ -0,0 +1,178 @@ +Sicherheit +Virenschutz + +
+ +
+
+ + + + + Virenschutz (ClamAV) +
+
+ +
+
+ + {{-- Fehler-Banner --}} + @if($lastError) +
+ + {{ $lastError }} +
+ @endif + +
+
+ + {{-- RAM-Hinweis --}} +
+ +
+
Hoher RAM-Verbrauch
+
+ ClamAV lädt die komplette Viren-Datenbank (~500 MB) in den Arbeitsspeicher. + Auf Servern mit weniger als 2 GB RAM kann das die Performance beeinträchtigen. + Empfohlen für Produktiv-Mailserver mit ausreichend RAM. +
+
+
+ + {{-- Status-Karte --}} +
+
+
+ Status +
+
+
+ + @if(!$installed) + {{-- Nicht installiert --}} +
+
+
+
+
ClamAV nicht installiert
+
Das Paket clamav clamav-daemon ist nicht verfügbar.
+
+
+
+
Installation
+ apt install clamav clamav-daemon -y +
Nach der Installation diese Seite neu laden.
+
+
+ + @else + {{-- Installiert --}} +
+ + {{-- Status-Zeile --}} +
+
+ @if($running) +
+
+
ClamAV läuft
+
+ Aktiv{{ $enabled ? ', Autostart ein' : '' }} + @if($ramMb) · {{ $ramMb }} MB RAM @endif +
+
+ @else +
+
+
ClamAV inaktiv
+
Installiert, aber nicht aktiv{{ $enabled ? '' : ' · Autostart aus' }}
+
+ @endif +
+ +
+ @if($running) + + @else + + @endif +
+
+ + {{-- Signaturen --}} +
+
+
Signatur-Datenbank
+
{{ $dbDate }}
+ @if($dbVersion !== '—')
Version {{ $dbVersion }}
@endif +
+
+
+
Signaturen aktualisieren
+
Läuft automatisch täglich
+
+ +
+
+ +
+ @endif + +
+
+ + {{-- Info-Box --}} +
+
+
+ Info +
+
+
+
+ ClamAV ist ein Open-Source-Virenscanner der in Kombination mit Rspamd eingehende E-Mails auf Schadsoftware prüft. + Infizierte Anhänge werden automatisch abgewiesen oder in Quarantäne verschoben. +
+
+
+
Empfohlen bei
+
≥ 2 GB RAM
+
+
+
RAM-Verbrauch
+
~500–700 MB
+
+
+
Integration
+
Rspamd → Milter
+
+
+
Signaturen
+
Täglich automatisch
+
+
+
+
+ +
+
+
diff --git a/routes/web.php b/routes/web.php index 85cf22b..74b8c09 100644 --- a/routes/web.php +++ b/routes/web.php @@ -60,6 +60,7 @@ Route::middleware(['auth.user', 'require2fa'])->name('ui.')->group(function () { Route::get('/rspamd', \App\Livewire\Ui\Security\RspamdForm::class)->name('rspamd'); Route::get('/tls-ciphers', \App\Livewire\Ui\Security\TlsCiphersForm::class)->name('tls'); Route::get('/audit-logs', \App\Livewire\Ui\Security\AuditLogsTable::class)->name('audit'); + Route::get('/clamav', \App\Livewire\Ui\Security\ClamavManager::class)->name('clamav'); }); #DOMAIN ROUTES diff --git a/scripts/mailwolt-clamav b/scripts/mailwolt-clamav new file mode 100755 index 0000000..ea09923 --- /dev/null +++ b/scripts/mailwolt-clamav @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Wrapper für ClamAV-Verwaltung via MailWolt UI (läuft als root via sudo) +set -euo pipefail + +CMD="${1:-status}" + +case "$CMD" in + enable) + systemctl enable clamav-daemon + systemctl start clamav-daemon + echo "ClamAV aktiviert und gestartet." + ;; + disable) + systemctl stop clamav-daemon || true + systemctl disable clamav-daemon + echo "ClamAV gestoppt und deaktiviert." + ;; + freshclam) + systemctl stop clamav-freshclam 2>/dev/null || true + freshclam --quiet + systemctl start clamav-freshclam 2>/dev/null || true + echo "Signaturen aktualisiert." + ;; + status) + systemctl is-active clamav-daemon 2>/dev/null && echo "active" || echo "inactive" + ;; + *) + echo "Usage: mailwolt-clamav {enable|disable|freshclam|status}" >&2 + exit 1 + ;; +esac diff --git a/scripts/update.sh b/scripts/update.sh index f66bce8..d13ac66 100644 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -183,10 +183,12 @@ ensure_system(){ _sbin_install "${APP_DIR}/scripts/mailwolt-apply-domains" /usr/local/sbin/mailwolt-apply-domains _sbin_install "${APP_DIR}/scripts/mailwolt-fetch-tags" /usr/local/sbin/mailwolt-fetch-tags _sbin_install "${APP_DIR}/scripts/update.sh" /usr/local/sbin/mailwolt-update + _sbin_install "${APP_DIR}/scripts/mailwolt-clamav" /usr/local/sbin/mailwolt-clamav # Sudoers-Regeln nachrüsten for f in /etc/sudoers.d/mailwolt-certbot /etc/sudoers.d/mailwolt-dkim; do _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-fetch-tags' _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update --check-only' + _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-clamav' _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/*/fullchain.pem' done # Laravel Scheduler cron (schedule:run muss jede Minute laufen)