🇩🇪 Deutsch
Dieses PHP-Skript durchsucht alle Unterordner, erkennt automatisch Web- oder E-Mail-Zertifikate und sendet die Daten an die CERT-API. Das Ergebnis wird als cert.pem im jeweiligen Ordner gespeichert.
Falls die Datei cert.pem gelöscht wird, wird beim nächsten Durchlauf automatisch ein neues Zertifikat erstellt.
🇬🇧 English
This PHP script scans all subfolders, automatically detects web or email certificates, and sends the data to the CERT API. The result is saved as cert.pem in the corresponding folder.
If the cert.pem file is deleted, a new certificate will be automatically created during the next run.
🇫🇷 Français
Ce script PHP analyse tous les sous-dossiers, détecte automatiquement les certificats Web ou Email, et envoie les données à l'API CERT. Le résultat est enregistré sous le nom cert.pem dans le dossier correspondant.
Si le fichier cert.pem est supprimé, un nouveau certificat sera automatiquement généré lors de la prochaine exécution.
<?php
// Basisverzeichnis – dort, wo das Skript liegt
$baseDir = __DIR__;
$apiUrl = 'https://www.erhardt-fbt.com/public/cert_api/';
$apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // zentraler API-Key
// Alle Unterordner im Basisverzeichnis finden
$entries = scandir($baseDir);
// Korrektur: Stellen Sie sicher, dass $entries kein 'false' (Fehler) ist
if ($entries === false) {
echo "❌ FEHLER: Konnte das Verzeichnis '$baseDir' nicht lesen. Überprüfen Sie die Dateiberechtigungen.\n";
exit(1);
}
// $folders ist nun definitiv ein Array, auch wenn es leer ist
$folders = array_filter($entries, function ($entry) use ($baseDir) {
return $entry !== '.' && $entry !== '..' && is_dir("$baseDir/$entry");
});
foreach ($folders as $folderName) {
$folderPath = "$baseDir/$folderName";
// Schlüsseldateien definieren
// ACHTUNG: Der Name der Schlüsseldatei (z.B. 'mein-schluessel.key') muss angepasst werden!
$keyFile = "$folderPath/[PRIVAT_KEY_DATEINAME].key"; // privater Schlüssel
$keyPassFile = "$folderPath/key.txt"; // Passwort für den Schlüssel
$dnsFile = "$folderPath/dns.txt"; // optional für Web
$ipFile = "$folderPath/ip.txt"; // optional für Web
// NEU: Dateinamen für das JSON-Bundle
$certFile = "$folderPath/cert.pem";
$chainFile = "$folderPath/chain.pem";
$fullchainFile = "$folderPath/fullchain.pem";
$newKeyFile = "$folderPath/private.key"; // Der entschlüsselte Schlüssel vom Server
// Prüfen, ob Schlüssel und Passwort vorhanden sind
if (!file_exists($keyFile) || !file_exists($keyPassFile)) {
echo "❌ Überspringe '$folderName' – fehlende Schlüsseldateien\n";
continue;
}
// Prüfen, ob Zertifikat bereits existiert und gültig aussieht
// HINWEIS: Prüft nur die Cert-Datei, die Existenz der anderen wird akzeptiert.
if (file_exists($certFile) && filesize($certFile) > 1000) {
echo "✅ Zertifikat für '$folderName' bereits vorhanden – überspringe.\n";
continue;
}
// Schlüssel und Passwort laden
$privateKey = file_get_contents($keyFile);
$keyPass = trim(file_get_contents($keyPassFile));
// Typ erkennen: E-Mail oder Web
$isEmail = strpos($folderName, '@') !== false;
$type = $isEmail ? 'emailProtection' : 'serverAuth';
// DNS/IP laden (nur für Web-Zertifikate)
$dnsList = file_exists($dnsFile) ? file($dnsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
$ipList = file_exists($ipFile) ? file($ipFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
// Daten für API vorbereiten
$data = [
'private_key' => $privateKey,
'keypass' => $keyPass,
'api_key' => $apiKey,
'type' => $type,
'dns' => [implode("\n", $dnsList)],
'ip' => [implode("\n", $ipList)]
];
// Anfrage an die API senden
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
// Wichtig: Wir akzeptieren nun sowohl JSON als auch PEM
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json, application/x-pem-file'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
// Header-Informationen erfassen
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
// Example for Postfix and Dovecot incl. Restart
// Ergebnis speichern oder Fehler melden
if ($httpCode === 200) {
// 1. Prüfen, ob JSON-Bundle zurückkam (bevorzugt)
if (strpos($contentType, 'application/json') !== false) {
$bundle = json_decode($response, true);
if ($bundle && isset($bundle['success']) && $bundle['success'] === true) {
// Standard-Dateien im jeweiligen Domain-Ordner speichern
file_put_contents($certFile, $bundle['cert_only']);
file_put_contents($newKeyFile, $bundle['private_key']);
chmod($newKeyFile, 0600);
if (!empty($bundle['sub_ca_only'])) {
file_put_contents($chainFile, $bundle['sub_ca_only']);
// ZUSÄTZLICH: Als explizite subca.pem für den Zertifikatsspeicher ablegen
file_put_contents("$folderPath/subca.pem", $bundle['sub_ca_only']);
}
// ZUSÄTZLICH: Das nackte Wurzelzertifikat (Root-CA) separat ablegen
if (!empty($bundle['root_ca_only'])) {
file_put_contents("$folderPath/root.pem", $bundle['root_ca_only']);
}
// 1.) Die Fullchain für den Webserver (inkl. Root für FritzBox & Co.)
if (!empty($bundle['chain_with_root'])) {
file_put_contents($fullchainFile, $bundle['chain_with_root']);
}
// 2.) Die reine Chain für Postfix (Cert + Sub) im gleichen Ordner ablegen
if (!empty($bundle['chain_standard'])) {
file_put_contents("$folderPath/postfix-chain.pem", $bundle['chain_standard']);
}
// 3.) Das Combined-File für Dovecot (Key + Cert + Sub) im gleichen Ordner ablegen
if (!empty($bundle['dovecot_combined'])) {
$dovecotFile = "$folderPath/dovecot-combined.pem";
file_put_contents($dovecotFile, $bundle['dovecot_combined']);
chmod($dovecotFile, 0600);
}
echo "✅ Alle Zertifikats-Formate für '$folderName' erfolgreich im Ordner abgelegt.\n";
// Dienste neu laden, da die Dateien jetzt überall bereitliegen
shell_exec("postmap -F /etc/postfix/vmail_ssl.map");
shell_exec("systemctl reload postfix 2>&1");
shell_exec("systemctl restart dovecot 2>&1");
echo "🔄 Mailserver-Dienste (Postfix & Dovecot) wurden aktualisiert.\n";
continue;
} else {
echo "⚠️ Fehler bei '$folderName': API meldete Erfolg = false oder JSON war ungültig.\n";
if (isset($bundle['error'])) {
echo "Details: " . $bundle['error'] . "\n";
}
}
} elseif (strpos($contentType, 'application/x-pem-file') !== false) {
// 2. Fallback: Nur das einfache Zertifikat kam zurück
file_put_contents($certFile, $response);
echo "✅ Zertifikat (reine PEM-Datei) für '$folderName' gespeichert.\n";
} else {
echo "⚠️ Fehler bei '$folderName': Unerwarteter Content-Type '$contentType'.\n";
}
} else {
// 3. Fehler melden (HTTP Code ist nicht 200)
echo "⚠️ Fehler bei '$folderName': HTTP $httpCode\n";
$error = json_decode($response, true);
if ($error && isset($error['error'])) {
echo "Fehlerdetail: " . $error['error'] . "\n";
}
}
}
?>