correct heredoc indentation in AcmeSh for php-7.1; fixes #957

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann
2021-07-20 08:11:32 +02:00
parent ce9d8dad7f
commit 5608f0407f

View File

@@ -28,69 +28,69 @@ use Froxlor\FileDir;
class AcmeSh extends \Froxlor\Cron\FroxlorCron class AcmeSh extends \Froxlor\Cron\FroxlorCron
{ {
const ACME_PROVIDER = [ const ACME_PROVIDER = [
'letsencrypt' => "https://acme-v02.api.letsencrypt.org/directory", 'letsencrypt' => "https://acme-v02.api.letsencrypt.org/directory",
'letsencrypt_test' => "https://acme-staging-v02.api.letsencrypt.org/directory", 'letsencrypt_test' => "https://acme-staging-v02.api.letsencrypt.org/directory",
'zerossl' => "https://acme.zerossl.com/v2/DV90" 'zerossl' => "https://acme.zerossl.com/v2/DV90"
]; ];
private static $apiserver = ""; private static $apiserver = "";
private static $acmesh = "/root/.acme.sh/acme.sh"; private static $acmesh = "/root/.acme.sh/acme.sh";
/** /**
* *
* @var \PDOStatement * @var \PDOStatement
*/ */
private static $updcert_stmt = null; private static $updcert_stmt = null;
/** /**
* *
* @var \PDOStatement * @var \PDOStatement
*/ */
private static $upddom_stmt = null; private static $upddom_stmt = null;
public static $no_inserttask = false; public static $no_inserttask = false;
/** /**
* run the task * run the task
* *
* @param boolean $internal * @param boolean $internal
* @return number * @return number
*/ */
public static function run($internal = false) public static function run($internal = false)
{ {
// usually, this is action is called from within the tasks-jobs // usually, this is action is called from within the tasks-jobs
if (! defined('CRON_IS_FORCED') && ! defined('CRON_DEBUG_FLAG') && $internal == false) { if (! defined('CRON_IS_FORCED') && ! defined('CRON_DEBUG_FLAG') && $internal == false) {
// Let's Encrypt cronjob is combined with regeneration of webserver configuration files. // Let's Encrypt cronjob is combined with regeneration of webserver configuration files.
// For debugging purposes you can use the --debug switch and the --force switch to run the cron manually. // For debugging purposes you can use the --debug switch and the --force switch to run the cron manually.
// check whether we MIGHT need to run although there is no task to regenerate config-files // check whether we MIGHT need to run although there is no task to regenerate config-files
$issue_froxlor = self::issueFroxlorVhost(); $issue_froxlor = self::issueFroxlorVhost();
$issue_domains = self::issueDomains(); $issue_domains = self::issueDomains();
$renew_froxlor = self::renewFroxlorVhost(); $renew_froxlor = self::renewFroxlorVhost();
$renew_domains = self::renewDomains(true); $renew_domains = self::renewDomains(true);
if ($issue_froxlor || ! empty($issue_domains) || ! empty($renew_froxlor) || $renew_domains) { if ($issue_froxlor || ! empty($issue_domains) || ! empty($renew_froxlor) || $renew_domains) {
// insert task to generate certificates and vhost-configs // insert task to generate certificates and vhost-configs
\Froxlor\System\Cronjob::inserttask(1); \Froxlor\System\Cronjob::inserttask(1);
} }
return 0; return 0;
} }
// set server according to settings // set server according to settings
self::$apiserver = self::ACME_PROVIDER[Settings::Get('system.letsencryptca')]; self::$apiserver = self::ACME_PROVIDER[Settings::Get('system.letsencryptca')];
// validate acme.sh installation // validate acme.sh installation
if (! self::checkInstall()) { if (! self::checkInstall()) {
return - 1; return - 1;
} }
self::checkUpgrade(); self::checkUpgrade();
// flag for re-generation of vhost files // flag for re-generation of vhost files
$changedetected = 0; $changedetected = 0;
// prepare update sql // prepare update sql
self::$updcert_stmt = Database::prepare(" self::$updcert_stmt = Database::prepare("
REPLACE INTO REPLACE INTO
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
SET SET
@@ -105,99 +105,99 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
`expirationdate` = :expirationdate `expirationdate` = :expirationdate
"); ");
// prepare domain update sql // prepare domain update sql
self::$upddom_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `ssl_redirect` = '1' WHERE `id` = :domainid"); self::$upddom_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `ssl_redirect` = '1' WHERE `id` = :domainid");
// check whether there are certificates to issue // check whether there are certificates to issue
$issue_froxlor = self::issueFroxlorVhost(); $issue_froxlor = self::issueFroxlorVhost();
$issue_domains = self::issueDomains(); $issue_domains = self::issueDomains();
// first - generate LE for system-vhost if enabled // first - generate LE for system-vhost if enabled
if ($issue_froxlor) { if ($issue_froxlor) {
// build row // build row
$certrow = array( $certrow = array(
'loginname' => 'froxlor.panel', 'loginname' => 'froxlor.panel',
'domain' => Settings::Get('system.hostname'), 'domain' => Settings::Get('system.hostname'),
'domainid' => 0, 'domainid' => 0,
'documentroot' => \Froxlor\Froxlor::getInstallDir(), 'documentroot' => \Froxlor\Froxlor::getInstallDir(),
'leprivatekey' => Settings::Get('system.leprivatekey'), 'leprivatekey' => Settings::Get('system.leprivatekey'),
'lepublickey' => Settings::Get('system.lepublickey'), 'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'), 'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => null, 'expirationdate' => null,
'ssl_cert_file' => null, 'ssl_cert_file' => null,
'ssl_key_file' => null, 'ssl_key_file' => null,
'ssl_ca_file' => null, 'ssl_ca_file' => null,
'ssl_csr_file' => null, 'ssl_csr_file' => null,
'id' => null 'id' => null
); );
// add to queue // add to queue
$issue_domains[] = $certrow; $issue_domains[] = $certrow;
} }
if (count($issue_domains)) { if (count($issue_domains)) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Requesting " . count($issue_domains) . " new Let's Encrypt certificates"); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Requesting " . count($issue_domains) . " new Let's Encrypt certificates");
self::runIssueFor($issue_domains); self::runIssueFor($issue_domains);
$changedetected = 1; $changedetected = 1;
} }
// compare file-system certificates with the ones in our database // compare file-system certificates with the ones in our database
// and update if needed // and update if needed
$renew_froxlor = self::renewFroxlorVhost(); $renew_froxlor = self::renewFroxlorVhost();
$renew_domains = self::renewDomains(); $renew_domains = self::renewDomains();
if ($renew_froxlor) { if ($renew_froxlor) {
// build row // build row
$certrow = array( $certrow = array(
'loginname' => 'froxlor.panel', 'loginname' => 'froxlor.panel',
'domain' => Settings::Get('system.hostname'), 'domain' => Settings::Get('system.hostname'),
'domainid' => 0, 'domainid' => 0,
'documentroot' => \Froxlor\Froxlor::getInstallDir(), 'documentroot' => \Froxlor\Froxlor::getInstallDir(),
'leprivatekey' => Settings::Get('system.leprivatekey'), 'leprivatekey' => Settings::Get('system.leprivatekey'),
'lepublickey' => Settings::Get('system.lepublickey'), 'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'), 'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => is_array($renew_froxlor) ? $renew_froxlor['expirationdate'] : date('Y-m-d H:i:s', 0), 'expirationdate' => is_array($renew_froxlor) ? $renew_froxlor['expirationdate'] : date('Y-m-d H:i:s', 0),
'ssl_cert_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_cert_file'] : null, 'ssl_cert_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_cert_file'] : null,
'ssl_key_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_key_file'] : null, 'ssl_key_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_key_file'] : null,
'ssl_ca_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_ca_file'] : null, 'ssl_ca_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_ca_file'] : null,
'ssl_csr_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_csr_file'] : null, 'ssl_csr_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_csr_file'] : null,
'id' => is_array($renew_froxlor) ? $renew_froxlor['id'] : null 'id' => is_array($renew_froxlor) ? $renew_froxlor['id'] : null
); );
$renew_domains[] = $certrow; $renew_domains[] = $certrow;
} }
foreach ($renew_domains as $domain) { foreach ($renew_domains as $domain) {
$cronlog = FroxlorLogger::getInstanceOf(array( $cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $domain['loginname'], 'loginname' => $domain['loginname'],
'adminsession' => 0 'adminsession' => 0
)); ));
if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['expirationdate'])) { if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['expirationdate'])) {
self::certToDb($domain, $cronlog, array()); self::certToDb($domain, $cronlog, array());
$changedetected = 1; $changedetected = 1;
} }
} }
// If we have a change in a certificate, we need to update the webserver - configs // If we have a change in a certificate, we need to update the webserver - configs
// This is easiest done by just creating a new task ;) // This is easiest done by just creating a new task ;)
if ($changedetected) { if ($changedetected) {
if (self::$no_inserttask == false) { if (self::$no_inserttask == false) {
\Froxlor\System\Cronjob::inserttask(1); \Froxlor\System\Cronjob::inserttask(1);
} }
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated"); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
} else { } else {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No new certificates or certificate updates found"); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No new certificates or certificate updates found");
} }
} }
/** /**
* issue certificates for a list of domains * issue certificates for a list of domains
*/ */
private static function runIssueFor($certrows = array()) private static function runIssueFor($certrows = array())
{ {
// prepare aliasdomain-check // prepare aliasdomain-check
$aliasdomains_stmt = Database::prepare(" $aliasdomains_stmt = Database::prepare("
SELECT SELECT
dom.`id` as domainid, dom.`id` as domainid,
dom.`domain`, dom.`domain`,
@@ -208,216 +208,216 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
AND dom.`letsencrypt` = 1 AND dom.`letsencrypt` = 1
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
"); ");
// iterate through all domains // iterate through all domains
foreach ($certrows as $certrow) { foreach ($certrows as $certrow) {
// set logger to corresponding loginname for the log to appear in the users system-log // set logger to corresponding loginname for the log to appear in the users system-log
$cronlog = FroxlorLogger::getInstanceOf(array( $cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $certrow['loginname'], 'loginname' => $certrow['loginname'],
'adminsession' => 0 'adminsession' => 0
)); ));
// Only issue let's encrypt certificate if no broken ssl_redirect is enabled // Only issue let's encrypt certificate if no broken ssl_redirect is enabled
if ($certrow['ssl_redirect'] != 2) { if ($certrow['ssl_redirect'] != 2) {
$do_force = false; $do_force = false;
if (! empty($certrow['ssl_cert_file']) && empty($certrow['expirationdate'])) { if (! empty($certrow['ssl_cert_file']) && empty($certrow['expirationdate'])) {
// domain changed (SAN or similar) // domain changed (SAN or similar)
$do_force = true; $do_force = true;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']);
} else { } else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Creating certificate for " . $certrow['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Creating certificate for " . $certrow['domain']);
} }
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding common-name: " . $certrow['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding common-name: " . $certrow['domain']);
$domains = array( $domains = array(
strtolower($certrow['domain']) strtolower($certrow['domain'])
); );
// add www.<domain> to SAN list // add www.<domain> to SAN list
if ($certrow['wwwserveralias'] == 1) { if ($certrow['wwwserveralias'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $certrow['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $certrow['domain']);
$domains[] = strtolower('www.' . $certrow['domain']); $domains[] = strtolower('www.' . $certrow['domain']);
} }
if ($certrow['domainid'] == 0) { if ($certrow['domainid'] == 0) {
$froxlor_aliases = Settings::Get('system.froxloraliases'); $froxlor_aliases = Settings::Get('system.froxloraliases');
if (! empty($froxlor_aliases)) { if (! empty($froxlor_aliases)) {
$froxlor_aliases = explode(",", $froxlor_aliases); $froxlor_aliases = explode(",", $froxlor_aliases);
foreach ($froxlor_aliases as $falias) { foreach ($froxlor_aliases as $falias) {
if (\Froxlor\Validate\Validate::validateDomain(trim($falias))) { if (\Froxlor\Validate\Validate::validateDomain(trim($falias))) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . strtolower(trim($falias))); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . strtolower(trim($falias)));
$domains[] = strtolower(trim($falias)); $domains[] = strtolower(trim($falias));
} }
} }
} }
} else { } else {
// add alias domains (and possibly www.<aliasdomain>) to SAN list // add alias domains (and possibly www.<aliasdomain>) to SAN list
Database::pexecute($aliasdomains_stmt, array( Database::pexecute($aliasdomains_stmt, array(
'id' => $certrow['domainid'] 'id' => $certrow['domainid']
)); ));
$aliasdomains = $aliasdomains_stmt->fetchAll(\PDO::FETCH_ASSOC); $aliasdomains = $aliasdomains_stmt->fetchAll(\PDO::FETCH_ASSOC);
foreach ($aliasdomains as $aliasdomain) { foreach ($aliasdomains as $aliasdomain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . $aliasdomain['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . $aliasdomain['domain']);
$domains[] = strtolower($aliasdomain['domain']); $domains[] = strtolower($aliasdomain['domain']);
if ($aliasdomain['wwwserveralias'] == 1) { if ($aliasdomain['wwwserveralias'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $aliasdomain['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $aliasdomain['domain']);
$domains[] = strtolower('www.' . $aliasdomain['domain']); $domains[] = strtolower('www.' . $aliasdomain['domain']);
} }
} }
} }
self::validateDns($domains, $certrow['domainid'], $cronlog); self::validateDns($domains, $certrow['domainid'], $cronlog);
self::runAcmeSh($certrow, $domains, $cronlog, $do_force); self::runAcmeSh($certrow, $domains, $cronlog, $do_force);
} else { } else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect");
} }
} }
} }
/** /**
* validate dns (A / AAAA record) of domain against known system ips * validate dns (A / AAAA record) of domain against known system ips
* *
* @param array $domains * @param array $domains
* @param int $domain_id * @param int $domain_id
* @param FroxlorLogger $cronlog * @param FroxlorLogger $cronlog
*/ */
private static function validateDns(array &$domains, $domain_id, &$cronlog) private static function validateDns(array &$domains, $domain_id, &$cronlog)
{ {
if (Settings::Get('system.le_domain_dnscheck') == '1' && ! empty($domains)) { if (Settings::Get('system.le_domain_dnscheck') == '1' && ! empty($domains)) {
$loop_domains = $domains; $loop_domains = $domains;
// ips according to our system // ips according to our system
$our_ips = Domain::getIpsOfDomain($domain_id); $our_ips = Domain::getIpsOfDomain($domain_id);
foreach ($loop_domains as $idx => $domain) { foreach ($loop_domains as $idx => $domain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
// ips accordint to NS // ips accordint to NS
$domain_ips = PhpHelper::gethostbynamel6($domain); $domain_ips = PhpHelper::gethostbynamel6($domain);
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) { if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
// no common ips... // no common ips...
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $domain . " due to no system known IP address via DNS check"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $domain . " due to no system known IP address via DNS check");
unset($domains[$idx]); unset($domains[$idx]);
} }
} }
} }
} }
private static function runAcmeSh(array $certrow, array $domains, &$cronlog = null, $force = false) private static function runAcmeSh(array $certrow, array $domains, &$cronlog = null, $force = false)
{ {
if (! empty($domains)) { if (! empty($domains)) {
$acmesh_cmd = self::$acmesh . " --server " . self::$apiserver . " --issue -d " . implode(" -d ", $domains); $acmesh_cmd = self::$acmesh . " --server " . self::$apiserver . " --issue -d " . implode(" -d ", $domains);
// challenge path // challenge path
$acmesh_cmd .= " -w " . Settings::Get('system.letsencryptchallengepath'); $acmesh_cmd .= " -w " . Settings::Get('system.letsencryptchallengepath');
if (Settings::Get('system.leecc') > 0) { if (Settings::Get('system.leecc') > 0) {
// ecc certificate // ecc certificate
$acmesh_cmd .= " --keylength ec-" . Settings::Get('system.leecc'); $acmesh_cmd .= " --keylength ec-" . Settings::Get('system.leecc');
} else { } else {
$acmesh_cmd .= " --keylength " . Settings::Get('system.letsencryptkeysize'); $acmesh_cmd .= " --keylength " . Settings::Get('system.letsencryptkeysize');
} }
if (Settings::Get('system.letsencryptreuseold') != '1') { if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key"; $acmesh_cmd .= " --always-force-new-domain-key";
} }
if (Settings::Get('system.letsencryptca') == 'letsencrypt_test') { if (Settings::Get('system.letsencryptca') == 'letsencrypt_test') {
$acmesh_cmd .= " --staging"; $acmesh_cmd .= " --staging";
} }
if ($force) { if ($force) {
$acmesh_cmd .= " --force"; $acmesh_cmd .= " --force";
} }
if (defined('CRON_DEBUG_FLAG')) { if (defined('CRON_DEBUG_FLAG')) {
$acmesh_cmd .= " --debug"; $acmesh_cmd .= " --debug";
} }
$acme_result = \Froxlor\FileDir::safe_exec($acmesh_cmd); $acme_result = \Froxlor\FileDir::safe_exec($acmesh_cmd);
// debug output of acme.sh run // debug output of acme.sh run
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result)); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result));
self::certToDb($certrow, $cronlog, $acme_result); self::certToDb($certrow, $cronlog, $acme_result);
} }
} }
private static function certToDb($certrow, &$cronlog, $acme_result) private static function certToDb($certrow, &$cronlog, $acme_result)
{ {
$return = array(); $return = array();
self::readCertificateToVar(strtolower($certrow['domain']), $return, $cronlog); self::readCertificateToVar(strtolower($certrow['domain']), $return, $cronlog);
if (! empty($return['crt'])) { if (! empty($return['crt'])) {
$newcert = openssl_x509_parse($return['crt']); $newcert = openssl_x509_parse($return['crt']);
if ($newcert) { if ($newcert) {
// Store the new data // Store the new data
Database::pexecute(self::$updcert_stmt, array( Database::pexecute(self::$updcert_stmt, array(
'id' => $certrow['id'], 'id' => $certrow['id'],
'domainid' => $certrow['domainid'], 'domainid' => $certrow['domainid'],
'crt' => $return['crt'], 'crt' => $return['crt'],
'key' => $return['key'], 'key' => $return['key'],
'ca' => $return['chain'], 'ca' => $return['chain'],
'chain' => $return['chain'], 'chain' => $return['chain'],
'csr' => $return['csr'], 'csr' => $return['csr'],
'fullchain' => $return['fullchain'], 'fullchain' => $return['fullchain'],
'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']) 'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t'])
)); ));
if ($certrow['ssl_redirect'] == 3) { if ($certrow['ssl_redirect'] == 3) {
Database::pexecute(self::$upddom_stmt, array( Database::pexecute(self::$upddom_stmt, array(
'domainid' => $certrow['domainid'] 'domainid' => $certrow['domainid']
)); ));
} }
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
} else { } else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result)); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
} }
} else { } else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result)); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
} }
} }
/** /**
* check whether we need to issue a new certificate for froxlor itself * check whether we need to issue a new certificate for froxlor itself
* *
* @return boolean * @return boolean
*/ */
private static function issueFroxlorVhost() private static function issueFroxlorVhost()
{ {
if (Settings::Get('system.le_froxlor_enabled') == '1') { if (Settings::Get('system.le_froxlor_enabled') == '1') {
// let's encrypt is enabled, now check whether we have a certificate // let's encrypt is enabled, now check whether we have a certificate
$froxlor_ssl_settings_stmt = Database::prepare(" $froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' WHERE `domainid` = '0'
"); ");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate // also check for possible existing certificate
if (! $froxlor_ssl && ! self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s'))) { if (! $froxlor_ssl && ! self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s'))) {
return true; return true;
} }
} }
return false; return false;
} }
/** /**
* check whether we need to renew-check the certificate for froxlor itself * check whether we need to renew-check the certificate for froxlor itself
* *
* @return boolean * @return boolean
*/ */
private static function renewFroxlorVhost() private static function renewFroxlorVhost()
{ {
if (Settings::Get('system.le_froxlor_enabled') == '1') { if (Settings::Get('system.le_froxlor_enabled') == '1') {
// let's encrypt is enabled, now check whether we have a certificate // let's encrypt is enabled, now check whether we have a certificate
$froxlor_ssl_settings_stmt = Database::prepare(" $froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' WHERE `domainid` = '0'
"); ");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate // also check for possible existing certificate
if ($froxlor_ssl && self::checkFsFilesAreNewer(Settings::Get('system.hostname'), $froxlor_ssl['expirationdate'])) { if ($froxlor_ssl && self::checkFsFilesAreNewer(Settings::Get('system.hostname'), $froxlor_ssl['expirationdate'])) {
return $froxlor_ssl; return $froxlor_ssl;
} }
} }
return false; return false;
} }
/** /**
* get a list of domains that have a lets encrypt certificate (possible renew) * get a list of domains that have a lets encrypt certificate (possible renew)
*/ */
private static function renewDomains($check = false) private static function renewDomains($check = false)
{ {
$certificates_stmt = Database::query(" $certificates_stmt = Database::query("
SELECT SELECT
domssl.`id`, domssl.`id`,
domssl.`domainid`, domssl.`domainid`,
@@ -441,27 +441,27 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
AND dom.`aliasdomain` IS NULL AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
"); ");
$renew_certs = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC); $renew_certs = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC);
if ($renew_certs) { if ($renew_certs) {
if ($check) { if ($check) {
foreach ($renew_certs as $cert) { foreach ($renew_certs as $cert) {
if (self::checkFsFilesAreNewer($cert['domain'], $cert['expirationdate'])) { if (self::checkFsFilesAreNewer($cert['domain'], $cert['expirationdate'])) {
return true; return true;
} }
} }
return false; return false;
} }
return $renew_certs; return $renew_certs;
} }
return array(); return array();
} }
/** /**
* get a list of domains that require a new certificate (issue) * get a list of domains that require a new certificate (issue)
*/ */
private static function issueDomains() private static function issueDomains()
{ {
$certificates_stmt = Database::query(" $certificates_stmt = Database::query("
SELECT SELECT
domssl.`id`, domssl.`id`,
domssl.`domainid`, domssl.`domainid`,
@@ -494,125 +494,125 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
AND domssl.`expirationdate` IS NULL AND domssl.`expirationdate` IS NULL
"); ");
$customer_ssl = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC); $customer_ssl = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC);
if ($customer_ssl) { if ($customer_ssl) {
return $customer_ssl; return $customer_ssl;
} }
return array(); return array();
} }
private static function checkFsFilesAreNewer($domain, $cert_date = 0) private static function checkFsFilesAreNewer($domain, $cert_date = 0)
{ {
$certificate_folder = self::getWorkingDirFromEnv(strtolower($domain)); $certificate_folder = self::getWorkingDirFromEnv(strtolower($domain));
$ssl_file = \Froxlor\FileDir::makeCorrectFile($certificate_folder . '/' . strtolower($domain) . '.cer'); $ssl_file = \Froxlor\FileDir::makeCorrectFile($certificate_folder . '/' . strtolower($domain) . '.cer');
if (is_dir($certificate_folder) && file_exists($ssl_file) && is_readable($ssl_file)) { if (is_dir($certificate_folder) && file_exists($ssl_file) && is_readable($ssl_file)) {
$cert_data = openssl_x509_parse(file_get_contents($ssl_file)); $cert_data = openssl_x509_parse(file_get_contents($ssl_file));
if ($cert_data && $cert_data['validTo_time_t'] > strtotime($cert_date)) { if ($cert_data && $cert_data['validTo_time_t'] > strtotime($cert_date)) {
return true; return true;
} }
} }
return false; return false;
} }
public static function getWorkingDirFromEnv($domain = "", $forced_noecc = false) public static function getWorkingDirFromEnv($domain = "", $forced_noecc = false)
{ {
if (Settings::Get('system.leecc') > 0 && ! $forced_noecc) { if (Settings::Get('system.leecc') > 0 && ! $forced_noecc) {
$domain .= "_ecc"; $domain .= "_ecc";
} }
$env_file = FileDir::makeCorrectFile(dirname(self::$acmesh) . '/acme.sh.env'); $env_file = FileDir::makeCorrectFile(dirname(self::$acmesh) . '/acme.sh.env');
if (file_exists($env_file)) { if (file_exists($env_file)) {
$output = []; $output = [];
$cut = <<<EOC $cut = <<<EOC
cut -d'"' -f2 cut -d'"' -f2
EOC; EOC;
exec('grep "LE_WORKING_DIR" ' . escapeshellarg($env_file) . ' | ' . $cut, $output); exec('grep "LE_WORKING_DIR" ' . escapeshellarg($env_file) . ' | ' . $cut, $output);
if (is_array($output) && ! empty($output) && isset($output[0]) && ! empty($output[0])) { if (is_array($output) && ! empty($output) && isset($output[0]) && ! empty($output[0])) {
return FileDir::makeCorrectDir($output[0] . "/" . $domain); return FileDir::makeCorrectDir($output[0] . "/" . $domain);
} }
} }
return FileDir::makeCorrectDir(dirname(self::$acmesh) . "/" . $domain); return FileDir::makeCorrectDir(dirname(self::$acmesh) . "/" . $domain);
} }
public static function getAcmeSh() public static function getAcmeSh()
{ {
return self::$acmesh; return self::$acmesh;
} }
/** /**
* get certificate files from filesystem and store in $return array * get certificate files from filesystem and store in $return array
* *
* @param string $domain * @param string $domain
* @param array $return * @param array $return
* @param object $cronlog * @param object $cronlog
*/ */
private static function readCertificateToVar($domain, &$return, &$cronlog) private static function readCertificateToVar($domain, &$return, &$cronlog)
{ {
$certificate_folder = self::getWorkingDirFromEnv($domain); $certificate_folder = self::getWorkingDirFromEnv($domain);
$certificate_folder_noecc = null; $certificate_folder_noecc = null;
if (Settings::Get('system.leecc') > 0) { if (Settings::Get('system.leecc') > 0) {
$certificate_folder_noecc = self::getWorkingDirFromEnv($domain, true); $certificate_folder_noecc = self::getWorkingDirFromEnv($domain, true);
} }
$certificate_folder = \Froxlor\FileDir::makeCorrectDir($certificate_folder); $certificate_folder = \Froxlor\FileDir::makeCorrectDir($certificate_folder);
if (is_dir($certificate_folder) || is_dir($certificate_folder_noecc)) { if (is_dir($certificate_folder) || is_dir($certificate_folder_noecc)) {
foreach ([ foreach ([
'crt' => $domain . '.cer', 'crt' => $domain . '.cer',
'key' => $domain . '.key', 'key' => $domain . '.key',
'chain' => 'ca.cer', 'chain' => 'ca.cer',
'fullchain' => 'fullchain.cer', 'fullchain' => 'fullchain.cer',
'csr' => $domain . '.csr' 'csr' => $domain . '.csr'
] as $index => $sslfile) { ] as $index => $sslfile) {
$ssl_file = \Froxlor\FileDir::makeCorrectFile($certificate_folder . '/' . $sslfile); $ssl_file = \Froxlor\FileDir::makeCorrectFile($certificate_folder . '/' . $sslfile);
if (file_exists($ssl_file)) { if (file_exists($ssl_file)) {
$return[$index] = file_get_contents($ssl_file); $return[$index] = file_get_contents($ssl_file);
} else { } else {
if (! empty($certificate_folder_noecc)) { if (! empty($certificate_folder_noecc)) {
$ssl_file_fb = \Froxlor\FileDir::makeCorrectFile($certificate_folder_noecc . '/' . $sslfile); $ssl_file_fb = \Froxlor\FileDir::makeCorrectFile($certificate_folder_noecc . '/' . $sslfile);
if (file_exists($ssl_file_fb)) { if (file_exists($ssl_file_fb)) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "ECC certificates activated but found only non-ecc file"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "ECC certificates activated but found only non-ecc file");
$return[$index] = file_get_contents($ssl_file_fb); $return[$index] = file_get_contents($ssl_file_fb);
continue; continue;
} }
} }
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find file '" . $sslfile . "' in '" . $certificate_folder . "'"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find file '" . $sslfile . "' in '" . $certificate_folder . "'");
$return[$index] = null; $return[$index] = null;
} }
} }
} else { } else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find certificate-folder '" . $certificate_folder . "'"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find certificate-folder '" . $certificate_folder . "'");
} }
} }
/** /**
* install acme.sh if not found yet * install acme.sh if not found yet
*/ */
private static function checkInstall($tries = 0) private static function checkInstall($tries = 0)
{ {
if (! file_exists(self::$acmesh) && $tries > 0) { if (! file_exists(self::$acmesh) && $tries > 0) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Download/installation of acme.sh seems to have failed. Re-run cronjob to try again or install manually to '" . self::$acmesh . "'"); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Download/installation of acme.sh seems to have failed. Re-run cronjob to try again or install manually to '" . self::$acmesh . "'");
echo PHP_EOL . "Download/installation of acme.sh seems to have failed. Re-run cronjob to try again or install manually to '" . self::$acmesh . "'" . PHP_EOL; echo PHP_EOL . "Download/installation of acme.sh seems to have failed. Re-run cronjob to try again or install manually to '" . self::$acmesh . "'" . PHP_EOL;
return false; return false;
} else if (! file_exists(self::$acmesh)) { } else if (! file_exists(self::$acmesh)) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Could not find acme.sh - installing it to /root/.acme.sh/"); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Could not find acme.sh - installing it to /root/.acme.sh/");
$return = false; $return = false;
\Froxlor\FileDir::safe_exec("wget -O - https://get.acme.sh | sh", $return, array( \Froxlor\FileDir::safe_exec("wget -O - https://get.acme.sh | sh", $return, array(
'|' '|'
)); ));
// check whether the installation worked // check whether the installation worked
return self::checkInstall(++ $tries); return self::checkInstall(++ $tries);
} }
return true; return true;
} }
/** /**
* run upgrade * run upgrade
*/ */
private static function checkUpgrade() private static function checkUpgrade()
{ {
$acmesh_result = \Froxlor\FileDir::safe_exec(self::$acmesh . " --upgrade --auto-upgrade 0"); $acmesh_result = \Froxlor\FileDir::safe_exec(self::$acmesh . " --upgrade --auto-upgrade 0");
// check for activated cron // check for activated cron
$acmesh_result2 = \Froxlor\FileDir::safe_exec(self::$acmesh . " --install-cronjob"); $acmesh_result2 = \Froxlor\FileDir::safe_exec(self::$acmesh . " --install-cronjob");
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Checking for LetsEncrypt client upgrades before renewing certificates:\n" . implode("\n", $acmesh_result) . "\n" . implode("\n", $acmesh_result2)); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Checking for LetsEncrypt client upgrades before renewing certificates:\n" . implode("\n", $acmesh_result) . "\n" . implode("\n", $acmesh_result2));
} }
} }