diff --git a/actions/admin/settings/131.ssl.php b/actions/admin/settings/131.ssl.php index a77ec589..05f5dca7 100644 --- a/actions/admin/settings/131.ssl.php +++ b/actions/admin/settings/131.ssl.php @@ -251,8 +251,37 @@ return [ 'string_type' => 'validate_ip', 'string_emptyallowed' => true, 'default' => '', - 'save_method' => 'storeSettingField' - ] + 'save_method' => 'storeSettingField', + 'advanced_mode' => true + ], + 'system_le_renew_services' => [ + 'label' => lng('serversettings.le_renew_services'), + 'settinggroup' => 'system', + 'varname' => 'le_renew_services', + 'type' => 'select', + 'default' => '', + 'select_mode' => 'multiple', + 'option_emptyallowed' => true, + 'select_var' => [ + '' => lng('panel.none_value'), + 'postfix' => 'postfix (smtp)', + 'dovecot' => 'dovecot (imap/pop3)', + 'proftpd' => 'proftpd (ftp)', + ], + 'save_method' => 'storeSettingField', + 'advanced_mode' => true + ], + 'system_le_renew_hook' => [ + 'label' => lng('serversettings.le_renew_hook'), + 'settinggroup' => 'system', + 'varname' => 'le_renew_hook', + 'type' => 'text', + 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', + 'default' => 'systemctl restart postfix dovecot proftpd', + 'save_method' => 'storeSettingField', + 'advanced_mode' => true, + 'required_otp' => true + ], ] ] ] diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index c58a59f7..84e40bb5 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -638,6 +638,8 @@ opcache.validate_timestamps'), ('system', 'available_shells', ''), ('system', 'le_froxlor_enabled', '0'), ('system', 'le_froxlor_redirect', '0'), + ('system', 'le_renew_hook', 'systemctl restart postfix dovecot proftpd'), + ('system', 'le_renew_services', ''), ('system', 'letsencryptacmeconf', '/etc/apache2/conf-enabled/acme.conf'), ('system', 'mail_use_smtp', '0'), ('system', 'mail_smtp_host', 'localhost'), @@ -729,7 +731,7 @@ opcache.validate_timestamps'), ('panel', 'settings_mode', '0'), ('panel', 'menu_collapsed', '1'), ('panel', 'version', '2.2.0-dev1'), - ('panel', 'db_version', '202312230'); + ('panel', 'db_version', '202401090'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/update_2.2.inc.php b/install/updates/froxlor/update_2.2.inc.php index 4b1a6309..f76c9dcf 100644 --- a/install/updates/froxlor/update_2.2.inc.php +++ b/install/updates/froxlor/update_2.2.inc.php @@ -91,3 +91,13 @@ if (Froxlor::isFroxlorVersion('2.1.4')) { Froxlor::updateToDbVersion('202312230'); Froxlor::updateToVersion('2.2.0-dev1'); } + +if (Froxlor::isDatabaseVersion('202312230')) { + + Update::showUpdateStep("Adding new settings"); + Settings::AddNew("system.le_renew_services", ""); + Settings::AddNew("system.le_renew_hook", "systemctl restart postfix dovecot proftpd"); + Update::lastStepStatus(0); + + Froxlor::updateToDbVersion('202401090'); +} diff --git a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php index 125c1dea..e50746f4 100644 --- a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php +++ b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php @@ -518,7 +518,7 @@ EOC; self::validateDns($domains, $certrow['domainid'], $cronlog); - self::runAcmeSh($certrow, $domains, $cronlog, $do_force); + self::runAcmeSh($certrow, $domains, $cronlog, $do_force, $certrow['domainid'] == 0); } else { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect"); } @@ -566,7 +566,7 @@ EOC; } } - private static function runAcmeSh(array $certrow, array $domains, &$cronlog = null, $force = false) + private static function runAcmeSh(array $certrow, array $domains, &$cronlog = null, bool $force = false, bool $renew_hook = false) { if (!empty($domains)) { $acmesh_cmd = self::getAcmeSh() . " --server " . self::$apiserver . " --issue -d " . implode(" -d ", $domains); @@ -587,6 +587,12 @@ EOC; if ($force) { $acmesh_cmd .= " --force"; } + if ($renew_hook + && !empty(trim(Settings::Get('system.le_renew_services') ?? "")) + && !empty(trim(Settings::Get('system.le_renew_hook') ?? "")) + ) { + $acmesh_cmd .= " --renew-hook '" . Settings::Get('system.le_renew_hook') . "'"; + } if (defined('CRON_DEBUG_FLAG')) { $acmesh_cmd .= " --debug"; } @@ -603,12 +609,48 @@ EOC; } } else { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate"); - self::certToDb($certrow, $cronlog, $acme_result); + $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"); + + $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'); + + 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_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']); + return true; } else { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result)); } } else { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result)); } + return false; } /** diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index 5ba45c9c..dcebc533 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -34,7 +34,7 @@ final class Froxlor const VERSION = '2.2.0-dev1'; // Database version (YYYYMMDDC where C is a daily counter) - const DBVERSION = '202312230'; + const DBVERSION = '202401090'; // Distribution branding-tag (used for Debian etc.) const BRANDING = ''; diff --git a/lib/Froxlor/Install/Install/Core.php b/lib/Froxlor/Install/Install/Core.php index 07670f2f..39e6ddb6 100644 --- a/lib/Froxlor/Install/Install/Core.php +++ b/lib/Froxlor/Install/Install/Core.php @@ -115,7 +115,7 @@ class Core // create entries $this->doDataEntries($pdo); // create JSON array for config-services - $this->createJsonArray(); + $this->createJsonArray($pdo); if ($create_ud_str) { $this->createUserdataParamStr(); } @@ -660,9 +660,16 @@ class Core @umask($umask); } - private function createJsonArray() + private function createJsonArray(&$db_user) { - $system_params = ["cron", "libnssextrausers", "logrotate", "goaccess"]; + // use traffic analyzer from settings as we could define defaults in the lib/configfiles/*.xml templates + // which can be useful for third-party package-maintainer (e.g. other distros) to have more control + // over the installation defaults (less hardcoded values) + $traffic_analyzer = $db_user->query(" + SELECT `value` FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'system' AND `varname` = 'traffictool' + "); + $ta_result = $traffic_analyzer->fetch(\PDO::FETCH_ASSOC); + $system_params = ["cron", "libnssextrausers", "logrotate", $ta_result['value']]; if ($this->validatedData['webserver_backend'] == 'php-fpm') { $system_params[] = 'php-fpm'; } elseif ($this->validatedData['webserver_backend'] == 'fcgid') { diff --git a/lib/Froxlor/UI/Form.php b/lib/Froxlor/UI/Form.php index a824bd8a..b831cdc1 100644 --- a/lib/Froxlor/UI/Form.php +++ b/lib/Froxlor/UI/Form.php @@ -273,6 +273,10 @@ class Form if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) { // Check fields for plausibility foreach ($groupdetails['fields'] as $fieldname => $fielddetails) { + if (!isset($submitted_fields[$fieldname])) { + // skip unset fields due to unavailability for this system/settings-set + continue; + } if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) { if (($plausibility_check = self::checkPlausibilityFormField($fieldname, $fielddetails, $submitted_fields[$fieldname], $submitted_fields)) !== false) { if (is_array($plausibility_check) && isset($plausibility_check[0])) {