Skip to content

Commit 8ca4b16

Browse files
committed
Fixes for acme.sh support #5226 #6563
1 parent 2fc38b9 commit 8ca4b16

File tree

1 file changed

+56
-51
lines changed

1 file changed

+56
-51
lines changed

server/lib/classes/letsencrypt.inc.php

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ public function get_acme_command($domains, $key_file, $bundle_file, $cert_file,
8787
'R=0 ; C=0',
8888
$acme_sh . ' --issue ' . $domain_args . ' -w /usr/local/ispconfig/interface/acme --always-force-new-domain-key ' . $conf_selection_arg . $certificate_type_arg,
8989
'R=$?',
90-
'if [ $R -eq 0 -o $R -eq 2 ]',
91-
' then ' . $acme_sh . ' --install-cert ' . $domain_args . $conf_selection_arg . $files_to_install . ' --reloadcmd ' . escapeshellarg($this->get_reload_command($server_type)),
90+
'if [ $R -eq 0 ] || [ $R -eq 2 ]; then :',
91+
' ' . $acme_sh . ' --install-cert ' . $domain_args . $conf_selection_arg . $files_to_install . ' --reloadcmd ' . escapeshellarg($this->get_reload_command($server_type)),
9292
' C=$?',
9393
'fi',
9494
'if [ $C -eq 0 ]',
@@ -521,41 +521,71 @@ public function get_certificate_list() {
521521

522522
$candidates = [];
523523
if($use_acme) {
524-
$info = $app->system->system_safe($shell_script . ' --info 2>/dev/null');
525-
// try to auto-upgrade acme.sh when --info command is not there
526-
if($app->system->last_exec_retcode() != 0) {
527-
$app->system->system_safe($shell_script . ' --upgrade 2>&1');
528-
$info = $app->system->system_safe($shell_script . ' --info 2>/dev/null');
529-
}
530-
if($app->system->last_exec_retcode() != 0) {
531-
$app->log('get_certificate_list: acme.sh --info failed', LOGLEVEL_ERROR);
532-
return [];
533-
}
534-
$info = $this->parse_env_file($info);
535-
$cert_dir = !empty($info['CERT_HOME']) ? $info['CERT_HOME'] : $info['LE_CONFIG_HOME'];
536-
if(empty($cert_dir) || !is_dir($cert_dir)) {
537-
$app->log('get_certificate_list: could not find certificate home ' . $cert_dir, LOGLEVEL_ERROR);
524+
// Use an inline shell script to get the configured acme.sh certificate home.
525+
// We use a shell script because acme.sh config file is a shell script itself - to support even dynamic configs, we will evaluate the config file.
526+
// The used --info command was not always there, so we try to auto-upgrade acme.sh when the command fails
527+
$home_extract_cmd = join(' ; ', [
528+
'_info() { :',
529+
' _info_stdout=$(' . escapeshellarg($shell_script) . ' --info 2>/dev/null)',
530+
' _info_ret=$?',
531+
'}',
532+
'_echo_home() { :',
533+
' eval "$_info_stdout"',
534+
' _info_ret=$?',
535+
' if [ $_info_ret -eq 0 ]; then :',
536+
' if [ -z "$CERT_HOME" ]',
537+
' then echo "$LE_CONFIG_HOME"',
538+
' else echo "$CERT_HOME"',
539+
' fi',
540+
' else :',
541+
' echo "Error eval-ing --info output (exit code $_info_ret). stdout was: $_info_stdout"',
542+
' exit 1',
543+
' fi',
544+
'}',
545+
'_info',
546+
'if [ $_info_ret -eq 0 ]; then :',
547+
' _echo_home',
548+
'else :',
549+
' if ' . escapeshellarg($shell_script) . ' --upgrade 2>&1; then :',
550+
' _info',
551+
' if [ $_info_ret -eq 0 ]; then :',
552+
' _echo_home',
553+
' else :',
554+
' echo "--info failed (exit code $_info_ret). stdout was: $_info_stdout"',
555+
' exit 1',
556+
' fi',
557+
' else :',
558+
' echo "--info failed (exit code $_info_ret) and auto-upgrade failed, too. Initial info stdout was: $_info_stdout"',
559+
' exit 1',
560+
' fi',
561+
'fi',
562+
]);
563+
$ret = 0;
564+
$cert_home = [];
565+
exec($home_extract_cmd, $cert_home, $ret);
566+
$cert_home = trim(implode("\n", $cert_home));
567+
if($ret != 0 || empty($cert_home) || !is_dir($cert_home)) {
568+
$app->log('get_certificate_list: could not find certificate home. Error: ' . $cert_home . '. Command used: ' . $home_extract_cmd, LOGLEVEL_ERROR);
538569
return [];
539570
}
540-
$dir = opendir($cert_dir);
571+
$app->log('get_certificate_list: discovered cert home as ' . $cert_home . '. Command used: ' . $home_extract_cmd, LOGLEVEL_DEBUG);
572+
$dir = opendir($cert_home);
541573
if(!$dir) {
542-
$app->log('get_certificate_list: could not open certificate home ' . $cert_dir, LOGLEVEL_ERROR);
574+
$app->log('get_certificate_list: could not open certificate home ' . $cert_home, LOGLEVEL_ERROR);
543575
return [];
544576
}
545577
while($path = readdir($dir)) {
578+
$full_path = $cert_home . '/' . $path;
546579
// valid conf dirs have a . in them
547-
if($path === '.' || $path === '..' || strpos($path, '.') === false) {
548-
continue;
549-
}
550-
$full_path = $cert_dir . '/' . $path;
551-
if(!is_dir($full_path)) {
580+
if($path === '.' || $path === '..' || strpos($path, '.') === false || !is_dir($full_path)) {
552581
continue;
553582
}
554583
$domain = $path;
555584
if(preg_match('/_ecc$/', $path)) {
556585
$domain = substr($path, 0, -4);
557586
}
558-
if(!$this->is_readable_link_or_file("$full_path/$domain.conf")) {
587+
if(!$this->is_readable_link_or_file($full_path . '/' . $domain . '.conf')) {
588+
$app->log('get_certificate_list: skip ' . $full_path . '/' . $domain . '.conf because it is not readable', LOGLEVEL_DEBUG);
559589
continue;
560590
}
561591
$candidates[] = [
@@ -666,7 +696,7 @@ public function remove_certificate($certificate) {
666696
}
667697
} else {
668698
if(is_dir($certificate['conf'])) {
669-
if(!$app->system->rmdir($certificate['conf'], false)) {
699+
if(!$app->system->rmdir($certificate['conf'], true)) {
670700
$app->log('remove_certificate: could not delete config folder ' . $certificate['conf'], LOGLEVEL_WARN);
671701
return false;
672702
}
@@ -738,7 +768,7 @@ public function extract_x509($cert_file, $chain_file = null) {
738768
$signature_type = 'ECDSA';
739769
}
740770
return [
741-
'serial_number' => $info['serialNumber'],
771+
'serial_number' => $info['serialNumberHex'] ?: $info['serialNumber'],
742772
'signature_type' => $signature_type,
743773
'subject' => $info['subject'],
744774
'issuer' => $info['issuer'],
@@ -762,29 +792,4 @@ private function is_domain_name_or_wildcard($input) {
762792
private function is_readable_link_or_file($path) {
763793
return $path && (@is_link($path) || @is_file($path)) && @is_readable($path);
764794
}
765-
766-
private function parse_env_file($lines) {
767-
$variables = [];
768-
foreach($lines as $line) {
769-
$line = trim($line);
770-
// does only handle comment-only lines.
771-
// lines like `KEY=Value # inline-comment` are not supported (and normally not used by acme.sh)
772-
if(!$line || substr($line, 0, 1) == '#') {
773-
continue;
774-
}
775-
$parts = explode('=', $line, 2);
776-
if(count($parts) < 2) {
777-
continue;
778-
}
779-
$key = trim($parts[0]);
780-
$value = trim($parts[1]);
781-
if(preg_match('/^"(.*)"$/', $value, $matches)) {
782-
$value = $matches[1];
783-
} elseif(preg_match("/^'(.*)'$/", $value, $matches)) {
784-
$value = $matches[1];
785-
}
786-
$variables[$key] = $value;
787-
}
788-
return $variables;
789-
}
790795
}

0 commit comments

Comments
 (0)