certCandidates($host) as $p) { if (is_file($p) && is_readable($p)) return $p; } return null; } public function computeHashFromCertPath(string $certPath): ?string { $cmd = "openssl x509 -in " . escapeshellarg($certPath) . " -noout -pubkey" . " | openssl pkey -pubin -outform DER" . " | openssl dgst -sha256"; $out = trim(shell_exec($cmd . ' 2>/dev/null') ?? ''); if ($out === '') return null; // Akzeptiere "SHA256(stdin)= x" und "SHA2-256(stdin)= x" $hash = preg_replace('/^SHA2?-?256\(stdin\)=\s*/i', '', $out); $hash = trim($hash); return $hash !== '' ? strtolower($hash) : null; } /** Erzeugt/aktualisiert TLSA (3 1 1) für den aktuellen MX-Host (is_server-Domain). */ public function refreshForMx(string $service = '_25._tcp'): ?TlsaRecord { $host = $this->resolveMxHost(); $serverDomain = Domain::where('is_server', true) ->where('domain', $host) // Absicherung ->first(); if (!$serverDomain) return null; $certPath = $this->firstReadableCert($host); if (!$certPath) return null; $hash = $this->computeHashFromCertPath($certPath); if (!$hash) return null; $rec = TlsaRecord::updateOrCreate( ['domain_id' => $serverDomain->id, 'host' => $host, 'service' => $service], [ 'usage' => 3, 'selector' => 1, 'matching' => 1, 'hash' => $hash, 'cert_path' => $certPath, ] ); @mkdir('/etc/mailwolt/dns', 0755, true); $line = sprintf('%s.%s IN TLSA %d %d %d %s', $service, $host, 3, 1, 1, $hash); @file_put_contents("/etc/mailwolt/dns/{$host}.tlsa.txt", $line . "\n"); return $rec; } /** 🔧 Alias für Seeder-Kompatibilität */ public function prefreshForServerDomain(?Domain $unused = null): ?TlsaRecord { return $this->refreshForMx('_25._tcp'); } } //namespace App\Services; // //use App\Models\Domain; //use App\Models\TlsaRecord; // //class TlsaService //{ // public function refreshForServerDomain(Domain $serverDomain): ?TlsaRecord // { // // Falls du bereits eine Methode hast, die den Server-TLSA erzeugt: // if (method_exists($this, 'refreshForServer')) { // return $this->refreshForServer(); // } // // // Minimal-Implementierung (TLSA 3 1 1 aus aktuellem Mail-Zertifikat) // $host = env('MTA_HOST', $serverDomain->mx_host ?? ("mx.".$serverDomain->domain)); // $service = '_25._tcp'; // $cert = env('MAIL_CERT', "/etc/ssl/mail/fullchain.pem"); // $pubkeyDer = trim(shell_exec( // "openssl x509 -in ".escapeshellarg($cert)." -pubkey -noout | ". // "openssl pkey -pubin -outform DER 2>/dev/null | ". // "openssl dgst -sha256 -r | awk '{print \$1}'" // ) ?? ''); // // if ($pubkeyDer === '') { // return null; // } // // return TlsaRecord::updateOrCreate( // ['service' => $service, 'host' => $host], // ['usage' => 3, 'selector' => 1, 'matching' => 1, 'hash' => strtolower($pubkeyDer)] // ); // } // // public function resolveMxHost(): string // { // $base = env('BASE_DOMAIN', 'example.com'); // $sub = env('MTA_SUB', 'mx') ?: 'mx'; // return "{$sub}.{$base}"; // } // // private function certCandidates(string $host): array // { // return [ // '/etc/ssl/mail/fullchain.pem', // vom Deploy-Wrapper kopiert (lesbar) // "/etc/letsencrypt/live/{$host}/fullchain.pem", // Fallback (meist nicht lesbar) // ]; // } // // private function firstReadableCert(string $host): ?string // { // foreach ($this->certCandidates($host) as $p) { // if (is_file($p) && is_readable($p)) return $p; // } // return null; // } // // public function computeHashFromCertPath(string $certPath): ?string // { // $cmd = "openssl x509 -in " . escapeshellarg($certPath) . " -noout -pubkey" // . " | openssl pkey -pubin -outform DER" // . " | openssl dgst -sha256"; // $out = shell_exec($cmd . ' 2>/dev/null') ?? ''; // $out = trim($out); // if ($out === '') return null; // // // Variationen entfernen: "SHA256(stdin)= x", "SHA2-256(stdin)= x" // $hash = preg_replace('/^SHA2?56\(stdin\)=\s*/i', '', $out); // $hash = preg_replace('/^SHA2?-256\(stdin\)=\s*/i', '', $hash); // falls "SHA2-256" // $hash = trim($hash); // // return $hash !== '' ? $hash : null; // } // // /** Holt is_server-Domain und schreibt/aktualisiert TLSA (3 1 1) */ // public function refreshForMx(string $service = '_25._tcp'): ?TlsaRecord // { // $host = $this->resolveMxHost(); // // // 1) Server-Domain ermitteln (es darf genau eine mit is_server=true geben) // $serverDomain = Domain::where('is_server', true) // ->where('domain', $host) // doppelt absichern: passt zur ENV // ->first(); // // if (!$serverDomain) { // // Kein is_server Eintrag -> nichts zu tun // return null; // } // // // 2) Zertifikat finden/lesen // $certPath = $this->firstReadableCert($host); // if (!$certPath) return null; // // // 3) Hash berechnen // $hash = $this->computeHashFromCertPath($certPath); // if (!$hash) return null; // // // 4) DB schreiben (idempotent) // $rec = TlsaRecord::updateOrCreate( // ['domain_id' => $serverDomain->id, 'host' => $host, 'service' => $service], // [ // 'usage' => 3, // 'selector' => 1, // 'matching' => 1, // 'hash' => $hash, // 'cert_path' => $certPath, // ] // ); // // // 5) optionale Export-Datei // @mkdir('/etc/mailwolt/dns', 0755, true); // $line = sprintf('%s.%s IN TLSA %d %d %d %s', $service, $host, 3, 1, 1, $hash); // @file_put_contents("/etc/mailwolt/dns/{$host}.tlsa.txt", $line . "\n"); // // return $rec; // } //} // // // ////namespace App\Services; //// ////use App\Models\Domain; ////use App\Models\TlsaRecord; //// ////class TlsaService ////{ //// public function resolveMxHost(): string //// { //// $base = env('BASE_DOMAIN', 'example.com'); //// $sub = env('MTA_SUB', 'mx') ?: 'mx'; //// return "{$sub}.{$base}"; //// } //// //// private function certCandidates(string $host): array //// { //// // bevorzugt die vom Deploy-Hook gesetzten, lesbaren Symlinks //// $candidates = [ //// '/etc/ssl/mail/fullchain.pem', // MX //// "/etc/letsencrypt/live/{$host}/fullchain.pem", // Fallback //// ]; //// return $candidates; //// } //// //// public function refreshForMx(string $service = '_25._tcp'): ?\App\Models\TlsaRecord //// { //// $host = $this->resolveMxHost(); //// //// // bevorzugt die als Server markierte Domain //// $serverDomain = \App\Models\Domain::where('is_server', true)->first(); //// //// // Fallback: Domain über FQDN (mx.) finden //// if (!$serverDomain) { //// $serverDomain = \App\Models\Domain::where('domain', $host)->first(); //// } //// //// if (!$serverDomain) { //// // Keine passende Domain in der DB → nichts tun //// return null; //// } //// //// return $this->refreshForServerDomain($serverDomain, $service); //// } //// //// private function firstReadableCert(string $host): ?string //// { //// foreach ($this->certCandidates($host) as $path) { //// if (is_file($path) && is_readable($path)) { //// return $path; //// } //// } //// return null; //// } //// //// public function computeHashFromCert(string $host): ?string //// { //// $certPath = $this->firstReadableCert($host); //// if (!$certPath) return null; //// //// $cmd = "openssl x509 -in ".escapeshellarg($certPath)." -noout -pubkey" //// . " | openssl pkey -pubin -outform DER" //// . " | openssl dgst -sha256"; //// $out = shell_exec($cmd.' 2>/dev/null') ?? ''; //// $hash = preg_replace('/^SHA256\(stdin\)=\s*/', '', trim($out)); //// return $hash !== '' ? $hash : null; //// } //// //// public function refreshForServerDomain(Domain $serverDomain, string $service = '_25._tcp'): ?TlsaRecord //// { //// $host = $this->resolveMxHost(); //// $hash = $this->computeHashFromCert($host); //// if (!$hash) return null; //// //// $certPath = $this->firstReadableCert($host) ?? ''; //// //// $rec = TlsaRecord::updateOrCreate( //// ['domain_id' => $serverDomain->id, 'host' => $host, 'service' => $service], //// [ //// 'usage' => 3, //// 'selector' => 1, //// 'matching' => 1, //// 'hash' => $hash, //// 'cert_path' => $certPath, //// ] //// ); //// //// @mkdir('/etc/mailwolt/dns', 0755, true); //// $line = sprintf('%s.%s IN TLSA %d %d %d %s', $service, $host, 3, 1, 1, $hash); //// @file_put_contents("/etc/mailwolt/dns/{$host}.tlsa.txt", $line."\n"); //// //// return $rec; //// } // //// public function computeHashFromCert(string $host): ?string //// { //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// if (!is_file($certPath)) { //// // optional: Log für Debug //// logger()->warning("TLSA: Zertifikat nicht gefunden", ['path' => $certPath]); //// return null; //// } //// //// $cmd = "openssl x509 -in ".escapeshellarg($certPath)." -noout -pubkey" //// . " | openssl pkey -pubin -outform DER" //// . " | openssl dgst -sha256"; //// $out = shell_exec($cmd.' 2>/dev/null') ?? ''; //// $hash = preg_replace('/^SHA256\(stdin\)=\s*/', '', trim($out)); //// return $hash !== '' ? $hash : null; //// } //// //// /** //// * TLSA (3 1 1) für den MX-Host der übergebenen Domain erzeugen/aktualisieren. //// * Host kommt DIREKT aus $serverDomain->domain, nicht aus ENV. //// */ //// public function refreshForServerDomain(Domain $serverDomain, string $service = '_25._tcp'): ?TlsaRecord //// { //// $host = $serverDomain->domain; // <— wichtig: Domain-Objekt statt ENV //// $hash = $this->computeHashFromCert($host); //// if (!$hash) { //// return null; // Zert (noch) nicht vorhanden //// } //// //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// //// $rec = TlsaRecord::updateOrCreate( //// ['domain_id' => $serverDomain->id, 'host' => $host, 'service' => $service], //// [ //// 'usage' => 3, // DANE-EE //// 'selector' => 1, // SPKI //// 'matching' => 1, // SHA-256 //// 'hash' => $hash, //// 'cert_path' => $certPath, //// ] //// ); //// //// // optional: Datei für Export/Debug //// @mkdir('/etc/mailwolt/dns', 0755, true); //// @file_put_contents("/etc/mailwolt/dns/{$host}.tlsa.txt", sprintf('%s.%s IN TLSA 3 1 1 %s', $service, $host, $hash)."\n"); //// //// return $rec; //// } // //// public function resolveMxHost(): string //// { //// $base = env('BASE_DOMAIN', 'example.com'); //// $sub = env('MTA_SUB', 'mx') ?: 'mx'; //// return "{$sub}.{$base}"; //// } //// //// public function computeHashFromCert(string $host): ?string //// { //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// if (!is_file($certPath)) return null; //// //// $cmd = "openssl x509 -in ".escapeshellarg($certPath)." -noout -pubkey" //// . " | openssl pkey -pubin -outform DER" //// . " | openssl dgst -sha256"; //// $out = shell_exec($cmd.' 2>/dev/null') ?? ''; //// $hash = preg_replace('/^SHA256\(stdin\)=\s*/', '', trim($out)); //// return $hash !== '' ? $hash : null; //// } //// //// /** //// * Schreibt/aktualisiert TLSA (3 1 1) für den MX-Host in DB //// * und legt zusätzlich /etc/mailwolt/dns/.tlsa.txt ab. //// */ //// public function refreshForServerDomain(Domain $serverDomain, string $service = '_25._tcp'): ?TlsaRecord //// { //// $host = $this->resolveMxHost(); //// $hash = $this->computeHashFromCert($host); //// if (!$hash) { //// return null; // Zert (noch) nicht vorhanden //// } //// //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// //// $rec = TlsaRecord::updateOrCreate( //// ['domain_id' => $serverDomain->id, 'host' => $host, 'service' => $service], //// [ //// 'usage' => 3, // DANE-EE //// 'selector' => 1, // SPKI //// 'matching' => 1, // SHA-256 //// 'hash' => $hash, //// 'cert_path' => $certPath, //// ] //// ); //// //// // Optional: TXT-Datei für Export/Debug //// @mkdir('/etc/mailwolt/dns', 0755, true); //// $line = sprintf('%s.%s IN TLSA %d %d %d %s', $service, $host, 3, 1, 1, $hash); //// @file_put_contents("/etc/mailwolt/dns/{$host}.tlsa.txt", $line."\n"); //// //// return $rec; //// } ////} // // ////class TlsaService ////{ //// public function resolveMtaHost(): string //// { //// $base = env('BASE_DOMAIN', 'example.com'); //// $sub = env('MTA_SUB', 'mx') ?: 'mx'; //// return "{$sub}.{$base}"; //// } //// //// public function computeHashFromCert(string $host): ?string //// { //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// if (!is_file($certPath)) return null; //// //// $cmd = "openssl x509 -in ".escapeshellarg($certPath)." -noout -pubkey" //// . " | openssl pkey -pubin -outform DER" //// . " | openssl dgst -sha256"; //// $out = shell_exec($cmd.' 2>/dev/null') ?? ''; //// $hash = preg_replace('/^SHA256\(stdin\)=\s*/', '', trim($out)); //// return $hash !== '' ? $hash : null; //// } //// //// public function refreshForMx(string $service = '_25._tcp'): ?TlsaRecord //// { //// $host = $this->resolveMtaHost(); //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// $hash = $this->computeHashFromCert($host); //// if (!$hash) return null; //// //// // DB upsert (global, domain_id = null) //// $rec = TlsaRecord::updateOrCreate( //// ['domain_id' => null, 'host' => $host, 'service' => $service], //// [ //// 'usage' => 3, // DANE-EE //// 'selector' => 1, // SPKI //// 'matching' => 1, // SHA-256 //// 'hash' => $hash, //// 'cert_path' => $certPath, //// ] //// ); //// //// // Datei – nur aktualisieren, wenn sich der Hash ändert //// @mkdir('/etc/mailwolt/dns', 0755, true); //// $file = "/etc/mailwolt/dns/{$host}.tlsa.txt"; //// $newLine = sprintf('%s.%s IN TLSA %d %d %d %s', $service, $host, 3, 1, 1, $hash); //// //// $needWrite = true; //// if (is_file($file)) { //// $current = trim((string)file_get_contents($file)); //// if ($current === $newLine) { //// $needWrite = false; //// } //// } //// if ($needWrite) { //// file_put_contents($file, $newLine."\n"); //// } //// //// return $rec; //// } ////} //// ////---- // // //// ////namespace App\Services; //// ////use App\Models\TlsaRecord; //// ////class TlsaService ////{ //// public function resolveMtaHost(): string //// { //// $base = env('BASE_DOMAIN', 'example.com'); //// $sub = env('MTA_SUB', 'mx') ?: 'mx'; //// return "{$sub}.{$base}"; //// } //// //// public function computeHashFromCert(string $host): ?string //// { //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// if (!is_file($certPath)) return null; //// //// $cmd = "openssl x509 -in ".escapeshellarg($certPath)." -noout -pubkey" //// . " | openssl pkey -pubin -outform DER" //// . " | openssl dgst -sha256"; //// $out = shell_exec($cmd.' 2>/dev/null') ?? ''; //// $hash = preg_replace('/^SHA256\(stdin\)=\s*/', '', trim($out)); //// return $hash !== '' ? $hash : null; //// } //// //// /** //// * Schreibt/aktualisiert TLSA in DB (global) + Datei unter /etc/mailwolt/dns/. //// * Wir speichern ohne domain_id (global, nur pro Host/Service). //// */ //// public function refreshForMx(string $service = '_25._tcp'): ?TlsaRecord //// { //// $host = $this->resolveMtaHost(); //// $certPath = "/etc/letsencrypt/live/{$host}/fullchain.pem"; //// $hash = $this->computeHashFromCert($host); //// if (!$hash) return null; //// //// // DB upsert (domain_id = null → globaler Eintrag) //// $rec = TlsaRecord::updateOrCreate( //// ['domain_id' => null, 'host' => $host, 'service' => $service], //// [ //// 'usage' => 3, // DANE-EE //// 'selector' => 1, // SPKI //// 'matching' => 1, // SHA-256 //// 'hash' => $hash, //// 'cert_path' => $certPath, //// ] //// ); //// //// // Datei schreiben (für externen DNS-Export etc.) //// @mkdir('/etc/mailwolt/dns', 0755, true); //// $line = sprintf('%s.%s IN TLSA %d %d %d %s', //// $service, $host, 3, 1, 1, $hash); //// file_put_contents("/etc/mailwolt/dns/{$host}.tlsa.txt", $line."\n"); //// //// return $rec; //// } ////}