diff --git a/actions/admin/settings/131.ssl.php b/actions/admin/settings/131.ssl.php index 05f5dca7..cfed6178 100644 --- a/actions/admin/settings/131.ssl.php +++ b/actions/admin/settings/131.ssl.php @@ -268,7 +268,7 @@ return [ 'dovecot' => 'dovecot (imap/pop3)', 'proftpd' => 'proftpd (ftp)', ], - 'save_method' => 'storeSettingField', + 'save_method' => 'storeSettingFieldInsertUpdateServicesTask', 'advanced_mode' => true ], 'system_le_renew_hook' => [ @@ -278,7 +278,7 @@ return [ 'type' => 'text', 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', 'default' => 'systemctl restart postfix dovecot proftpd', - 'save_method' => 'storeSettingField', + 'save_method' => 'storeSettingFieldInsertUpdateServicesTask', 'advanced_mode' => true, 'required_otp' => true ], diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php index 7045de8e..a8e93f0f 100644 --- a/lib/Froxlor/Cli/MasterCron.php +++ b/lib/Froxlor/Cli/MasterCron.php @@ -80,6 +80,7 @@ final class MasterCron extends CliCommand Cronjob::inserttask(TaskId::REBUILD_RSPAMD); Cronjob::inserttask(TaskId::CREATE_QUOTA); Cronjob::inserttask(TaskId::REBUILD_CRON); + Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES); $jobs[] = 'tasks'; } define('CRON_IS_FORCED', 1); diff --git a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php index 11ee96a3..49bc699f 100644 --- a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php +++ b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php @@ -69,7 +69,8 @@ class AcmeSh extends FroxlorCron * run the task * * @param bool $internal - * @return number + * @return int + * @throws \Exception */ public static function run(bool $internal = false) { @@ -85,6 +86,9 @@ class AcmeSh extends FroxlorCron if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) { // insert task to generate certificates and vhost-configs Cronjob::inserttask(TaskId::REBUILD_VHOST); + if ($renew_froxlor) { + Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES); + } } return 0; } @@ -217,6 +221,7 @@ class AcmeSh extends FroxlorCron * check whether we need to issue a new certificate for froxlor itself * * @return boolean + * @throws \Exception */ private static function issueFroxlorVhost() { @@ -340,6 +345,7 @@ EOC; * check whether we need to renew-check the certificate for froxlor itself * * @return boolean + * @throws \Exception */ private static function renewFroxlorVhost() { @@ -539,6 +545,7 @@ EOC; * @param array $domains * @param int $domain_id * @param FroxlorLogger $cronlog + * @throws \Exception */ private static function validateDns(array &$domains, $domain_id, &$cronlog) { @@ -619,27 +626,47 @@ EOC; $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate"); $cert_stored = self::certToDb($certrow, $cronlog, $acme_result); - if ($cert_stored - && $renew_hook - && !empty(trim(Settings::Get('system.le_renew_services') ?? "")) - && !empty(trim(Settings::Get('system.le_renew_hook') ?? "")) - ) { - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations"); + if ($cert_stored && $renew_hook) { + self::renewHookConfigs($cronlog); + } + } + } + } - $certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname'))); - $fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer'); - $keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key'); - $ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer'); + public static function renewHookConfigs($cronlog) + { + if (!empty(trim(Settings::Get('system.le_renew_services') ?? "")) + && !empty(trim(Settings::Get('system.le_renew_hook') ?? "")) + ) { - if (Settings::IsInList('system.le_renew_services', 'postfix')) { - // "postconf -e" for postfix - FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain)); - FileDir::safe_exec('postconf -e smtpd_tls_key_file=' . escapeshellarg($keyfile)); - } - if (Settings::IsInList('system.le_renew_services', 'dovecot')) { - // custom config for dovecot - $dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting? - $ssl_content = <<logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations"); + + $certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname'))); + + if (empty($certificate_folder)) { + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No certificate folder for '" . Settings::Get('system.hostname') . "' found"); + return; + } + + $fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer'); + $keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key'); + $ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer'); + + if (!file_exists($fullchain) || !file_exists($keyfile) || !file_exists($ca_file)) { + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "At least one of the required certificate files for '" . Settings::Get('system.hostname') . "' could not be found"); + return; + } + + $dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting? + + if (Settings::IsInList('system.le_renew_services', 'postfix')) { + // "postconf -e" for postfix + FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain)); + FileDir::safe_exec('postconf -e smtpd_tls_key_file=' . escapeshellarg($keyfile)); + } + if (Settings::IsInList('system.le_renew_services', 'dovecot')) { + // custom config for dovecot + $ssl_content = <<logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Removing Let's Encrypt entries for domain " . $row['data']['domain']); Domain::doLetsEncryptCleanUp($row['data']['domain']); + } elseif ($row['type'] == TaskId::UPDATE_LE_SERVICES) { + /** + * TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate + */ + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Updating Let's Encrypt configuration for selected services"); + AcmeSh::renewHookConfigs(FroxlorLogger::getInstanceOf()); } } diff --git a/lib/Froxlor/Cron/TaskId.php b/lib/Froxlor/Cron/TaskId.php index f905eba4..92d6b629 100644 --- a/lib/Froxlor/Cron/TaskId.php +++ b/lib/Froxlor/Cron/TaskId.php @@ -87,6 +87,11 @@ final class TaskId */ const DELETE_DOMAIN_SSL = 12; + /** + * TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate + */ + const UPDATE_LE_SERVICES = 13; + /** * TYPE=20 CUSTUMER DATA DUMP */ diff --git a/lib/Froxlor/Settings/Store.php b/lib/Froxlor/Settings/Store.php index be5f2cb9..bbc6b210 100644 --- a/lib/Froxlor/Settings/Store.php +++ b/lib/Froxlor/Settings/Store.php @@ -237,6 +237,17 @@ class Store return $returnvalue; } + public static function storeSettingFieldInsertUpdateServicesTask($fieldname, $fielddata, $newfieldvalue) + { + // first save the setting itself + $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue); + + if ($returnvalue !== false) { + Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES); + } + return $returnvalue; + } + public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue) { $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue); diff --git a/lng/de.lng.php b/lng/de.lng.php index d10d72aa..a876e3d3 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -2192,6 +2192,7 @@ Vielen Dank, Ihr Administrator', 'CREATE_CUSTOMER_DATADUMP' => 'Daten-Export für Kunde %s', 'DELETE_DOMAIN_PDNS' => 'Lösche Domain %s von PowerDNS Datenbank', 'DELETE_DOMAIN_SSL' => 'Lösche SSL Dateien von Domain %s', + 'UPDATE_LE_SERVICES' => 'Aktualisiere Systemdienste für Let\'s Encrypt', ], 'terms' => 'AGB', 'traffic' => [ diff --git a/lng/en.lng.php b/lng/en.lng.php index 4ae337f4..2de83ad3 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -2326,6 +2326,7 @@ Yours sincerely, your administrator', 'CREATE_CUSTOMER_DATADUMP' => 'Data export job for customer %s', 'DELETE_DOMAIN_PDNS' => 'Delete domain %s from PowerDNS database', 'DELETE_DOMAIN_SSL' => 'Delete ssl files of domain %s', + 'UPDATE_LE_SERVICES' => 'Updating system services for Let\'s Encrypt', ], 'terms' => 'Terms of use', 'traffic' => [