check for php-curl installed when cron_letsencrypt runs; format source

Signed-off-by: Michael Kaufmann (d00p) <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann (d00p)
2016-04-11 08:02:18 +02:00
parent 8565dbce8b
commit 84f1d94ad6
2 changed files with 442 additions and 416 deletions

View File

@@ -28,10 +28,13 @@
// and modified to work without files and integrate in Froxlor // and modified to work without files and integrate in Froxlor
class lescript class lescript
{ {
public $license = 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf'; public $license = 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf';
private $logger; private $logger;
private $client; private $client;
private $accountKey; private $accountKey;
public function __construct($logger) public function __construct($logger)
@@ -50,7 +53,7 @@ class lescript
{ {
// Let's see if we have the private accountkey // Let's see if we have the private accountkey
$this->accountKey = $certrow['leprivatekey']; $this->accountKey = $certrow['leprivatekey'];
if (!$this->accountKey || $this->accountKey == 'unset' || Settings::Get('system.letsencryptca') != 'production') { if (! $this->accountKey || $this->accountKey == 'unset' || Settings::Get('system.letsencryptca') != 'production') {
// generate and save new private key for account // generate and save new private key for account
// --------------------------------------------- // ---------------------------------------------
@@ -60,25 +63,26 @@ class lescript
// Only store the accountkey in production, in staging always generate a new key // Only store the accountkey in production, in staging always generate a new key
if (Settings::Get('system.letsencryptca') == 'production') { if (Settings::Get('system.letsencryptca') == 'production') {
$upd_stmt = Database::prepare(" $upd_stmt = Database::prepare("
UPDATE `".TABLE_PANEL_CUSTOMERS."` SET `lepublickey` = :public, `leprivatekey` = :private WHERE `customerid` = :customerid; UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private WHERE `customerid` = :customerid;
"); ");
Database::pexecute($upd_stmt, array('public' => $keys['public'], 'private' => $keys['private'], 'customerid' => $certrow['customerid'])); Database::pexecute($upd_stmt, array(
'public' => $keys['public'],
'private' => $keys['private'],
'customerid' => $certrow['customerid']
));
} }
$this->accountKey = $keys['private']; $this->accountKey = $keys['private'];
$this->postNewReg(); $this->postNewReg();
$this->log('New account certificate registered'); $this->log('New account certificate registered');
} else { } else {
$this->log('Account already registered. Continuing.'); $this->log('Account already registered. Continuing.');
} }
} }
public function signDomains(array $domains, $domainkey = null, $csr = null) public function signDomains(array $domains, $domainkey = null, $csr = null)
{ {
if (! $this->accountKey) {
if (!$this->accountKey) {
throw new \RuntimeException("Account not initiated"); throw new \RuntimeException("Account not initiated");
} }
@@ -90,44 +94,49 @@ class lescript
// start domains authentication // start domains authentication
// ---------------------------- // ----------------------------
foreach($domains as $domain) { foreach ($domains as $domain) {
// 1. getting available authentication options // 1. getting available authentication options
// ------------------------------------------- // -------------------------------------------
$this->log("Requesting challenge for $domain"); $this->log("Requesting challenge for $domain");
$response = $this->signedRequest( $response = $this->signedRequest("/acme/new-authz", array(
"/acme/new-authz", "resource" => "new-authz",
array("resource" => "new-authz", "identifier" => array("type" => "dns", "value" => $domain)) "identifier" => array(
); "type" => "dns",
"value" => $domain
)
));
// if response is not an array but a string, it's most likely a server-error, e.g. // if response is not an array but a string, it's most likely a server-error, e.g.
// <HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY>An error occurred while processing your request. // <HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY>An error occurred while processing your request.
// <p>Reference&#32;&#35;179&#46;d8be1402&#46;1458059103&#46;3613c4db</BODY></HTML> // <p>Reference&#32;&#35;179&#46;d8be1402&#46;1458059103&#46;3613c4db</BODY></HTML>
if (!is_array($response)) { if (! is_array($response)) {
throw new RuntimeException("Invalid response from LE for domain $domain. Whole response: ".$response); throw new RuntimeException("Invalid response from LE for domain $domain. Whole response: " . $response);
} }
if (!array_key_exists('challenges', $response)) { if (! array_key_exists('challenges', $response)) {
throw new RuntimeException("No challenges received for $domain. Whole response: ".json_encode($response)); throw new RuntimeException("No challenges received for $domain. Whole response: " . json_encode($response));
} }
// choose http-01 challenge only // choose http-01 challenge only
$challenge = array_reduce($response['challenges'], function($v, $w) { return $v ? $v : ($w['type'] == 'http-01' ? $w : false); }); $challenge = array_reduce($response['challenges'], function ($v, $w) {
if(!$challenge) throw new RuntimeException("HTTP Challenge for $domain is not available. Whole response: ".json_encode($response)); return $v ? $v : ($w['type'] == 'http-01' ? $w : false);
});
if (! $challenge)
throw new RuntimeException("HTTP Challenge for $domain is not available. Whole response: " . json_encode($response));
$this->log("Got challenge token for $domain"); $this->log("Got challenge token for $domain");
$location = $this->client->getLastLocation(); $location = $this->client->getLastLocation();
// 2. saving authentication token for web verification // 2. saving authentication token for web verification
// --------------------------------------------------- // ---------------------------------------------------
$directory = Settings::Get('system.letsencryptchallengepath').'/.well-known/acme-challenge'; $directory = Settings::Get('system.letsencryptchallengepath') . '/.well-known/acme-challenge';
$tokenPath = $directory.'/'.$challenge['token']; $tokenPath = $directory . '/' . $challenge['token'];
if(!file_exists($directory) && !@mkdir($directory, 0755, true)) { if (! file_exists($directory) && ! @mkdir($directory, 0755, true)) {
throw new \RuntimeException("Couldn't create directory to expose challenge: ${tokenPath}"); throw new \RuntimeException("Couldn't create directory to expose challenge: ${tokenPath}");
} }
@@ -136,8 +145,8 @@ class lescript
"e" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["e"]), "e" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["e"]),
"kty" => "RSA", "kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["n"]) "n" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["n"])
)
); ;
$payload = $challenge['token'] . '.' . Base64UrlSafeEncoder::encode(hash('sha256', json_encode($header), true)); $payload = $challenge['token'] . '.' . Base64UrlSafeEncoder::encode(hash('sha256', json_encode($header), true));
file_put_contents($tokenPath, $payload); file_put_contents($tokenPath, $payload);
@@ -151,7 +160,7 @@ class lescript
$this->log("Token for $domain saved at $tokenPath and should be available at $uri"); $this->log("Token for $domain saved at $tokenPath and should be available at $uri");
// simple self check // simple self check
if($payload !== trim(@file_get_contents($uri))) { if ($payload !== trim(@file_get_contents($uri))) {
$errmsg = json_encode(error_get_last()); $errmsg = json_encode(error_get_last());
if ($errmsg != "null") { if ($errmsg != "null") {
$errmsg = "; PHP error: " . $errmsg; $errmsg = "; PHP error: " . $errmsg;
@@ -165,35 +174,31 @@ class lescript
$this->log("Sending request to challenge"); $this->log("Sending request to challenge");
// send request to challenge // send request to challenge
$result = $this->signedRequest( $result = $this->signedRequest($challenge['uri'], array(
$challenge['uri'],
array(
"resource" => "challenge", "resource" => "challenge",
"type" => "http-01", "type" => "http-01",
"keyAuthorization" => $payload, "keyAuthorization" => $payload,
"token" => $challenge['token'] "token" => $challenge['token']
) ));
);
// waiting loop // waiting loop
// we wait for a maximum of 30 seconds to avoid endless loops // we wait for a maximum of 30 seconds to avoid endless loops
$count = 0; $count = 0;
do { do {
if(empty($result['status']) || $result['status'] == "invalid") { if (empty($result['status']) || $result['status'] == "invalid") {
@unlink($tokenPath); @unlink($tokenPath);
throw new \RuntimeException("Verification ended with error: ".json_encode($result)); throw new \RuntimeException("Verification ended with error: " . json_encode($result));
} }
$ended = !($result['status'] === "pending"); $ended = ! ($result['status'] === "pending");
if(!$ended) { if (! $ended) {
$this->log("Verification pending, sleeping 1s"); $this->log("Verification pending, sleeping 1s");
sleep(1); sleep(1);
$count++; $count ++;
} }
$result = $this->client->get($location); $result = $this->client->get($location);
} while (! $ended && $count < 30);
} while (!$ended && $count < 30);
$this->log("Verification ended with status: ${result['status']}"); $this->log("Verification ended with status: ${result['status']}");
@unlink($tokenPath); @unlink($tokenPath);
@@ -203,7 +208,7 @@ class lescript
// ---------------------- // ----------------------
// generate private key for domain if not exist // generate private key for domain if not exist
if(empty($domainkey) || Settings::Get('system.letsencryptreuseold') == 0) { if (empty($domainkey) || Settings::Get('system.letsencryptreuseold') == 0) {
$keys = $this->generateKey(); $keys = $this->generateKey();
$domainkey = $keys['private']; $domainkey = $keys['private'];
} }
@@ -218,34 +223,33 @@ class lescript
} }
// request certificates creation // request certificates creation
$result = $this->signedRequest( $result = $this->signedRequest("/acme/new-cert", array(
"/acme/new-cert", 'resource' => 'new-cert',
array('resource' => 'new-cert', 'csr' => $csr) 'csr' => $csr
); ));
if ($this->client->getLastCode() !== 201) { if ($this->client->getLastCode() !== 201) {
throw new \RuntimeException("Invalid response code: ".$this->client->getLastCode().", ".json_encode($result)); throw new \RuntimeException("Invalid response code: " . $this->client->getLastCode() . ", " . json_encode($result));
} }
$location = $this->client->getLastLocation(); $location = $this->client->getLastLocation();
// waiting loop // waiting loop
$certificates = array(); $certificates = array();
while(1) { while (1) {
$this->client->getLastLinks(); $this->client->getLastLinks();
$result = $this->client->get($location); $result = $this->client->get($location);
if($this->client->getLastCode() == 202) { if ($this->client->getLastCode() == 202) {
$this->log("Certificate generation pending, sleeping 1s"); $this->log("Certificate generation pending, sleeping 1s");
sleep(1); sleep(1);
} else
} else if ($this->client->getLastCode() == 200) { if ($this->client->getLastCode() == 200) {
$this->log("Got certificate! YAY!"); $this->log("Got certificate! YAY!");
$certificates[] = $this->parsePemFromBody($result); $certificates[] = $this->parsePemFromBody($result);
foreach ($this->client->getLastLinks() as $link) {
foreach($this->client->getLastLinks() as $link) {
$this->log("Requesting chained cert at $link"); $this->log("Requesting chained cert at $link");
$result = $this->client->get($link); $result = $this->client->get($link);
$certificates[] = $this->parsePemFromBody($result); $certificates[] = $this->parsePemFromBody($result);
@@ -254,19 +258,25 @@ class lescript
break; break;
} else { } else {
throw new \RuntimeException("Can't get certificate: HTTP code ".$this->client->getLastCode()); throw new \RuntimeException("Can't get certificate: HTTP code " . $this->client->getLastCode());
} }
} }
if(empty($certificates)) throw new \RuntimeException('No certificates generated'); if (empty($certificates))
throw new \RuntimeException('No certificates generated');
$fullchain = implode("\n", $certificates); $fullchain = implode("\n", $certificates);
$crt = array_shift($certificates); $crt = array_shift($certificates);
$chain = implode("\n", $certificates); $chain = implode("\n", $certificates);
$this->log("Done, returning new certificates and key"); $this->log("Done, returning new certificates and key");
return array('fullchain' => $fullchain, 'crt' => $crt, 'chain' => $chain, 'key' => $domainkey, 'csr' => $csr); return array(
'fullchain' => $fullchain,
'crt' => $crt,
'chain' => $chain,
'key' => $domainkey,
'csr' => $csr
);
} }
private function parsePemFromBody($body) private function parsePemFromBody($body)
@@ -279,23 +289,24 @@ class lescript
{ {
$this->log('Sending registration to letsencrypt server'); $this->log('Sending registration to letsencrypt server');
return $this->signedRequest( return $this->signedRequest('/acme/new-reg', array(
'/acme/new-reg', 'resource' => 'new-reg',
array('resource' => 'new-reg', 'agreement' => $this->license) 'agreement' => $this->license
); ));
} }
private function generateCSR($privateKey, array $domains) private function generateCSR($privateKey, array $domains)
{ {
$domain = reset($domains); $domain = reset($domains);
$san = implode(",", array_map(function ($dns) { return "DNS:" . $dns; }, $domains)); $san = implode(",", array_map(function ($dns) {
return "DNS:" . $dns;
}, $domains));
$tmpConf = tmpfile(); $tmpConf = tmpfile();
$tmpConfMeta = stream_get_meta_data($tmpConf); $tmpConfMeta = stream_get_meta_data($tmpConf);
$tmpConfPath = $tmpConfMeta["uri"]; $tmpConfPath = $tmpConfMeta["uri"];
// workaround to get SAN working // workaround to get SAN working
fwrite($tmpConf, fwrite($tmpConf, 'HOME = .
'HOME = .
RANDFILE = $ENV::HOME/.rnd RANDFILE = $ENV::HOME/.rnd
[ req ] [ req ]
default_bits = ' . Settings::Get('system.letsencryptkeysize') . ' default_bits = ' . Settings::Get('system.letsencryptkeysize') . '
@@ -306,24 +317,21 @@ req_extensions = v3_req
countryName = Country Name (2 letter code) countryName = Country Name (2 letter code)
[ v3_req ] [ v3_req ]
basicConstraints = CA:FALSE basicConstraints = CA:FALSE
subjectAltName = '.$san.' subjectAltName = ' . $san . '
keyUsage = nonRepudiation, digitalSignature, keyEncipherment'); keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
$csr = openssl_csr_new( $csr = openssl_csr_new(array(
array(
"CN" => $domain, "CN" => $domain,
"ST" => Settings::Get('system.letsencryptstate'), "ST" => Settings::Get('system.letsencryptstate'),
"C" => Settings::Get('system.letsencryptcountrycode'), "C" => Settings::Get('system.letsencryptcountrycode'),
"O" => "Unknown", "O" => "Unknown"
), ), $privateKey, array(
$privateKey,
array(
"config" => $tmpConfPath, "config" => $tmpConfPath,
"digest_alg" => "sha256" "digest_alg" => "sha256"
) ));
);
if (!$csr) throw new \RuntimeException("CSR couldn't be generated! ".openssl_error_string()); if (! $csr)
throw new \RuntimeException("CSR couldn't be generated! " . openssl_error_string());
openssl_csr_export($csr, $csr); openssl_csr_export($csr, $csr);
fclose($tmpConf); fclose($tmpConf);
@@ -337,16 +345,19 @@ keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
{ {
$res = openssl_pkey_new(array( $res = openssl_pkey_new(array(
"private_key_type" => OPENSSL_KEYTYPE_RSA, "private_key_type" => OPENSSL_KEYTYPE_RSA,
"private_key_bits" => (int)Settings::Get('system.letsencryptkeysize'), "private_key_bits" => (int) Settings::Get('system.letsencryptkeysize')
)); ));
if(!openssl_pkey_export($res, $privateKey)) { if (! openssl_pkey_export($res, $privateKey)) {
throw new \RuntimeException("Key export failed!"); throw new \RuntimeException("Key export failed!");
} }
$details = openssl_pkey_get_details($res); $details = openssl_pkey_get_details($res);
return array('private' => $privateKey, 'public' => $details['key']); return array(
'private' => $privateKey,
'public' => $details['key']
);
} }
private function signedRequest($uri, array $payload) private function signedRequest($uri, array $payload)
@@ -359,18 +370,17 @@ keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
"jwk" => array( "jwk" => array(
"kty" => "RSA", "kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($details["rsa"]["n"]), "n" => Base64UrlSafeEncoder::encode($details["rsa"]["n"]),
"e" => Base64UrlSafeEncoder::encode($details["rsa"]["e"]), "e" => Base64UrlSafeEncoder::encode($details["rsa"]["e"])
) )
); );
$protected = $header; $protected = $header;
$protected["nonce"] = $this->client->getLastNonce(); $protected["nonce"] = $this->client->getLastNonce();
$payload64 = Base64UrlSafeEncoder::encode(str_replace('\\/', '/', json_encode($payload))); $payload64 = Base64UrlSafeEncoder::encode(str_replace('\\/', '/', json_encode($payload)));
$protected64 = Base64UrlSafeEncoder::encode(json_encode($protected)); $protected64 = Base64UrlSafeEncoder::encode(json_encode($protected));
openssl_sign($protected64.'.'.$payload64, $signed, $privateKey, "SHA256"); openssl_sign($protected64 . '.' . $payload64, $signed, $privateKey, "SHA256");
$signed64 = Base64UrlSafeEncoder::encode($signed); $signed64 = Base64UrlSafeEncoder::encode($signed);
@@ -394,7 +404,9 @@ keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
class Client class Client
{ {
private $lastCode; private $lastCode;
private $lastHeader; private $lastHeader;
private $base; private $base;
@@ -406,9 +418,12 @@ class Client
private function curl($method, $url, $data = null) private function curl($method, $url, $data = null)
{ {
$headers = array('Accept: application/json', 'Content-Type: application/json'); $headers = array(
'Accept: application/json',
'Content-Type: application/json'
);
$handle = curl_init(); $handle = curl_init();
curl_setopt($handle, CURLOPT_URL, preg_match('~^http~', $url) ? $url : $this->base.$url); curl_setopt($handle, CURLOPT_URL, preg_match('~^http~', $url) ? $url : $this->base . $url);
curl_setopt($handle, CURLOPT_HTTPHEADER, $headers); curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HEADER, true); curl_setopt($handle, CURLOPT_HEADER, true);
@@ -427,8 +442,8 @@ class Client
} }
$response = curl_exec($handle); $response = curl_exec($handle);
if(curl_errno($handle)) { if (curl_errno($handle)) {
throw new \RuntimeException('Curl: '.curl_error($handle)); throw new \RuntimeException('Curl: ' . curl_error($handle));
} }
$header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE); $header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
@@ -455,7 +470,7 @@ class Client
public function getLastNonce() public function getLastNonce()
{ {
if(preg_match('~Replay\-Nonce: (.+)~i', $this->lastHeader, $matches)) { if (preg_match('~Replay\-Nonce: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]); return trim($matches[1]);
} }
@@ -465,7 +480,7 @@ class Client
public function getLastLocation() public function getLastLocation()
{ {
if(preg_match('~Location: (.+)~i', $this->lastHeader, $matches)) { if (preg_match('~Location: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]); return trim($matches[1]);
} }
return null; return null;
@@ -485,6 +500,7 @@ class Client
class Base64UrlSafeEncoder class Base64UrlSafeEncoder
{ {
public static function encode($input) public static function encode($input)
{ {
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));

View File

@@ -1,4 +1,7 @@
<?php if (!defined('MASTER_CRONJOB')) die('You cannot access this file directly!'); <?php
if (! defined('MASTER_CRONJOB'))
die('You cannot access this file directly!');
/** /**
* This file is part of the Froxlor project. * This file is part of the Froxlor project.
@@ -20,31 +23,37 @@
$cronlog->logAction(CRON_ACTION, LOG_INFO, "Updating Let's Encrypt certificates"); $cronlog->logAction(CRON_ACTION, LOG_INFO, "Updating Let's Encrypt certificates");
if (! extension_loaded('curl')) {
$cronlog->logAction(CRON_ACTION, LOG_ERR, "Let's Encrypt requires the php cURL extension to be installed.");
exit;
}
$certificates_stmt = Database::query(" $certificates_stmt = Database::query("
SELECT domssl.`id`, domssl.`domainid`, domssl.expirationdate, domssl.`ssl_cert_file`, domssl.`ssl_key_file`, domssl.`ssl_ca_file`, domssl.`ssl_csr_file`, dom.`domain`, dom.`iswildcarddomain`, dom.`wwwserveralias`, SELECT domssl.`id`, domssl.`domainid`, domssl.expirationdate, domssl.`ssl_cert_file`, domssl.`ssl_key_file`, domssl.`ssl_ca_file`, domssl.`ssl_csr_file`, dom.`domain`, dom.`iswildcarddomain`, dom.`wwwserveralias`,
dom.`documentroot`, dom.`id` as 'domainid', dom.`ssl_redirect`, cust.`leprivatekey`, cust.`lepublickey`, cust.customerid, cust.loginname dom.`documentroot`, dom.`id` as 'domainid', dom.`ssl_redirect`, cust.`leprivatekey`, cust.`lepublickey`, cust.customerid, cust.loginname
FROM `".TABLE_PANEL_CUSTOMERS."` as cust, `".TABLE_PANEL_DOMAINS."` dom LEFT JOIN `".TABLE_PANEL_DOMAIN_SSL_SETTINGS."` domssl ON (dom.id = domssl.domainid) FROM `" . TABLE_PANEL_CUSTOMERS . "` as cust, `" . TABLE_PANEL_DOMAINS . "` dom LEFT JOIN `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` domssl ON (dom.id = domssl.domainid)
WHERE dom.customerid = cust.customerid AND dom.letsencrypt = 1 AND (domssl.expirationdate < DATE_ADD(NOW(), INTERVAL 30 DAY) OR domssl.expirationdate IS NULL) WHERE dom.customerid = cust.customerid AND dom.letsencrypt = 1 AND (domssl.expirationdate < DATE_ADD(NOW(), INTERVAL 30 DAY) OR domssl.expirationdate IS NULL)
"); ");
$updcert_stmt = Database::prepare(" $updcert_stmt = Database::prepare("
REPLACE INTO `".TABLE_PANEL_DOMAIN_SSL_SETTINGS."` SET `id` = :id, `domainid` = :domainid, `ssl_cert_file` = :crt, `ssl_key_file` = :key, `ssl_ca_file` = :ca, `ssl_cert_chainfile` = :chain, `ssl_csr_file` = :csr, expirationdate = :expirationdate REPLACE INTO `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET `id` = :id, `domainid` = :domainid, `ssl_cert_file` = :crt, `ssl_key_file` = :key, `ssl_ca_file` = :ca, `ssl_cert_chainfile` = :chain, `ssl_csr_file` = :csr, expirationdate = :expirationdate
"); ");
$upddom_stmt = Database::prepare(" $upddom_stmt = Database::prepare("
UPDATE `".TABLE_PANEL_DOMAINS."` SET `ssl_redirect` = '1' WHERE `id` = :domainid UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `ssl_redirect` = '1' WHERE `id` = :domainid
"); ");
$changedetected = 0; $changedetected = 0;
$certrows = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC); $certrows = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
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('loginname' => $certrow['loginname'])); $cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $certrow['loginname']
));
// Only renew let's encrypt certificate if no broken ssl_redirect is enabled // Only renew let's encrypt certificate if no broken ssl_redirect is enabled
if ($certrow['ssl_redirect'] != 2) if ($certrow['ssl_redirect'] != 2) {
{
$cronlog->logAction(CRON_ACTION, LOG_DEBUG, "Updating " . $certrow['domain']); $cronlog->logAction(CRON_ACTION, LOG_DEBUG, "Updating " . $certrow['domain']);
if ($certrow['ssl_cert_file']) { if ($certrow['ssl_cert_file']) {
@@ -55,12 +64,14 @@ foreach($certrows AS $certrow) {
// We are interessted in the old SAN - data // We are interessted in the old SAN - data
$san = explode(', ', $x509data['extensions']['subjectAltName']); $san = explode(', ', $x509data['extensions']['subjectAltName']);
$domains = array(); $domains = array();
foreach($san as $dnsname) { foreach ($san as $dnsname) {
$domains[] = substr($dnsname, 4); $domains[] = substr($dnsname, 4);
} }
} else { } else {
$cronlog->logAction(CRON_ACTION, LOG_DEBUG, "letsencrypt generating new key / SAN for " . $certrow['domain']); $cronlog->logAction(CRON_ACTION, LOG_DEBUG, "letsencrypt generating new key / SAN for " . $certrow['domain']);
$domains = array($certrow['domain']); $domains = array(
$certrow['domain']
);
// Add www.<domain> for SAN // Add www.<domain> for SAN
if ($certrow['wwwserveralias'] == 1) { if ($certrow['wwwserveralias'] == 1) {
$domains[] = 'www.' . $certrow['domain']; $domains[] = 'www.' . $certrow['domain'];
@@ -90,20 +101,17 @@ foreach($certrows AS $certrow) {
'chain' => $return['chain'], 'chain' => $return['chain'],
'csr' => $return['csr'], 'csr' => $return['csr'],
'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($upddom_stmt, array( Database::pexecute($upddom_stmt, array(
'domainid' => $certrow['domainid'] 'domainid' => $certrow['domainid']
) ));
);
} }
$cronlog->logAction(CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']); $cronlog->logAction(CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
$changedetected = 1; $changedetected = 1;
} catch (Exception $e) { } catch (Exception $e) {
$cronlog->logAction(CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ": " . $e->getMessage()); $cronlog->logAction(CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ": " . $e->getMessage());
} }
@@ -119,5 +127,7 @@ if ($changedetected) {
} }
// reset logger // reset logger
$cronlog = FroxlorLogger::getInstanceOf(array('loginname' => 'cronjob')); $cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => 'cronjob'
));
$cronlog->logAction(CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated"); $cronlog->logAction(CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");