Merge branch 'master' of github.com:Froxlor/Froxlor

This commit is contained in:
Michael Kaufmann (d00p)
2016-02-05 15:54:27 +01:00
19 changed files with 910 additions and 21 deletions

View File

@@ -58,3 +58,19 @@ http://files.froxlor.org/releases/froxlor-latest.tar.gz [MD5](http://files.froxl
[HowTo](http://redmine.froxlor.org/projects/froxlor/wiki/Installationgentoo)
http://files.froxlor.org/gentoo/repositories.xml
## Let's Encrypt support
This version of Froxlor contains a test implementation of support for [Let's Encrypt](https://letsencrypt.org). This is (as Let's Encrypt is in itself)
still a beta version and may break your system. The way it currently works is by creating a (sub-)domain with the default system - certificate,
after which the Let's Encrypt cronjob orders the certificate for this (sub-)domain and inserts the certificates in the database. With the next run
of the default cronjob, the certificates will be updated on the disk and the webserver reloaded.
This has 2 known side-effects at the moment:
* The basic ip/port combinations don't work with the Froxlor - integration of Let's Encrypt, since it needs a certificate for the very first creation
* After creating a domain, it will have the default certificate for a short time (by default 5 minutes until the cronjob runs the next time)
It may be possible to fix these issues, but they are not a priority at the moment
**By default the testing environment of Let's Encrypt is used**. This issues certificates which will not be signed by a known certificate authority.
To activate the production system, change the `$ca` in `lib/classes/ssl/class.lescript.php` to `https://acme-v01.api.letsencrypt.org`.

View File

@@ -79,7 +79,35 @@ return array(
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField',
)
),
'system_letsencryptca' => array(
'label' => $lng['serversettings']['letsencryptca'],
'settinggroup' => 'system',
'varname' => 'letsencryptca',
'type' => 'option',
'default' => 'testing',
'option_mode' => 'one',
'option_options' => array('testing' => 'https://acme-staging.api.letsencrypt.org (Test)', 'production' => 'https://acme-v01.api.letsencrypt.org (Live)'),
'save_method' => 'storeSettingField',
),
'system_letsencryptcountrycode' => array(
'label' => $lng['serversettings']['letsencryptcountrycode'],
'settinggroup' => 'system',
'varname' => 'letsencryptcountrycode',
'type' => 'string',
'string_emptyallowed' => false,
'default' => 'DE',
'save_method' => 'storeSettingField',
),
'system_letsencryptstate' => array(
'label' => $lng['serversettings']['letsencryptstate'],
'settinggroup' => 'system',
'varname' => 'letsencryptstate',
'type' => 'string',
'string_emptyallowed' => false,
'default' => 'Germany',
'save_method' => 'storeSettingField',
),
)
)
)

View File

@@ -516,6 +516,11 @@ if ($page == 'domains'
$ssl_redirect = (int)$_POST['ssl_redirect'];
}
$letsencrypt = 0;
if (isset($_POST['letsencrypt'])) {
$letsencrypt = (int)$_POST['letsencrypt'];
}
$ssl_ipandports = array();
if (isset($_POST['ssl_ipandport']) && !is_array($_POST['ssl_ipandport'])) {
$_POST['ssl_ipandport'] = unserialize($_POST['ssl_ipandport']);
@@ -547,17 +552,24 @@ if ($page == 'domains'
}
} else {
$ssl_redirect = 0;
$letsencrypt = 0;
// we need this for the serialize
// if ssl is disabled or no ssl-ip/port exists
$ssl_ipandports[] = -1;
}
} else {
$ssl_redirect = 0;
$letsencrypt = 0;
// we need this for the serialize
// if ssl is disabled or no ssl-ip/port exists
$ssl_ipandports[] = -1;
}
// We can't enable let's encrypt for wildcard - domains
if ($serveraliasoption == '0') {
$letsencrypt = 0;
}
if (!preg_match('/^https?\:\/\//', $documentroot)) {
if (strstr($documentroot, ":") !== false) {
standard_error('pathmaynotcontaincolon');
@@ -702,7 +714,8 @@ if ($page == 'domains'
'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests,
'specialsettings' => $specialsettings,
'registration_date' => $registration_date,
'issubof' => $issubof
'issubof' => $issubof,
'letsencrypt' => $letsencrypt
);
$security_questions = array(
@@ -751,7 +764,8 @@ if ($page == 'domains'
'phpsettingid' => $phpsettingid,
'mod_fcgid_starter' => $mod_fcgid_starter,
'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests,
'ismainbutsubto' => $issubof
'ismainbutsubto' => $issubof,
'letsencrypt' => $letsencrypt
);
$ins_stmt = Database::prepare("
@@ -782,7 +796,8 @@ if ($page == 'domains'
`phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`ismainbutsubto` = :ismainbutsubto
`ismainbutsubto` = :ismainbutsubto,
`letsencrypt` = :letsencrypt
");
Database::pexecute($ins_stmt, $ins_data);
$domainid = Database::lastInsertId();
@@ -1288,6 +1303,11 @@ if ($page == 'domains'
$ssl_redirect = (int)$_POST['ssl_redirect'];
}
$letsencrypt = 0;
if (isset($_POST['letsencrypt'])) {
$letsencrypt = (int)$_POST['letsencrypt'];
}
$ssl_ipandports = array();
if (isset($_POST['ssl_ipandport']) && !is_array($_POST['ssl_ipandport'])) {
$_POST['ssl_ipandport'] = unserialize($_POST['ssl_ipandport']);
@@ -1314,17 +1334,24 @@ if ($page == 'domains'
}
} else {
$ssl_redirect = 0;
$letsencrypt = 0;
// we need this for the serialize
// if ssl is disabled or no ssl-ip/port exists
$ssl_ipandports[] = -1;
}
} else {
$ssl_redirect = 0;
$letsencrypt = 0;
// we need this for the serialize
// if ssl is disabled or no ssl-ip/port exists
$ssl_ipandports[] = -1;
}
// We can't enable let's encrypt for wildcard domains
if ($serveraliasoption == '0') {
$letsencrypt = '0';
}
if (!preg_match('/^https?\:\/\//', $documentroot)) {
$documentroot = makeCorrectDir($documentroot);
}
@@ -1443,7 +1470,8 @@ if ($page == 'domains'
'speciallogfile' => $speciallogfile,
'speciallogverified' => $speciallogverified,
'ipandport' => serialize($ipandports),
'ssl_ipandport' => serialize($ssl_ipandports)
'ssl_ipandport' => serialize($ssl_ipandports),
'letsencrypt' => $letsencrypt
);
$security_questions = array(
@@ -1478,6 +1506,7 @@ if ($page == 'domains'
|| $issubof != $result['ismainbutsubto']
|| $email_only != $result['email_only']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|| $letsencrypt != $result['letsencrypt']
) {
inserttask('1');
}
@@ -1613,6 +1642,7 @@ if ($page == 'domains'
$update_data['specialsettings'] = $specialsettings;
$update_data['registration_date'] = $registration_date;
$update_data['ismainbutsubto'] = $issubof;
$update_data['letsencrypt'] = $letsencrypt;
$update_data['id'] = $id;
$update_stmt = Database::prepare("
@@ -1638,7 +1668,8 @@ if ($page == 'domains'
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`specialsettings` = :specialsettings,
`registration_date` = :registration_date,
`ismainbutsubto` = :ismainbutsubto
`ismainbutsubto` = :ismainbutsubto,
`letsencrypt` = :letsencrypt
WHERE `id` = :id
");
Database::pexecute($update_stmt, $update_data);
@@ -1653,9 +1684,10 @@ if ($page == 'domains'
// if we have no more ssl-ip's for this domain,
// all its subdomains must have "ssl-redirect = 0"
// and disable let's encrypt
$update_sslredirect = '';
if (count($ssl_ipandports) == 1 && $ssl_ipandports[0] == -1) {
$update_sslredirect = ", `ssl_redirect` = '0' ";
$update_sslredirect = ", `ssl_redirect` = '0', `letsencrypt` = '0' ";
}
$_update_stmt = Database::prepare("
@@ -1867,6 +1899,7 @@ if ($page == 'domains'
$_value = '2';
if ($result['iswildcarddomain'] == '1') {
$_value = '0';
$letsencrypt = 0;
} elseif ($result['wwwserveralias'] == '1') {
$_value = '1';
}

View File

@@ -36,7 +36,7 @@ if ($page == 'overview') {
'd.domain' => $lng['domains']['domainname']
);
$paging = new paging($userinfo, TABLE_PANEL_DOMAINS, $fields);
$domains_stmt = Database::prepare("SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isemaildomain`, `d`.`caneditdomain`, `d`.`iswildcarddomain`, `d`.`parentdomainid`, `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`, `da`.`id` AS `domainaliasid`, `da`.`domain` AS `domainalias` FROM `" . TABLE_PANEL_DOMAINS . "` `d`
$domains_stmt = Database::prepare("SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isemaildomain`, `d`.`caneditdomain`, `d`.`iswildcarddomain`, `d`.`parentdomainid`, `d`.`letsencrypt`, `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`, `da`.`id` AS `domainaliasid`, `da`.`domain` AS `domainalias` FROM `" . TABLE_PANEL_DOMAINS . "` `d`
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `ad` ON `d`.`aliasdomain`=`ad`.`id`
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id`
WHERE `d`.`customerid`= :customerid
@@ -146,7 +146,7 @@ if ($page == 'overview') {
// get ssl-ips if activated
$show_ssledit = false;
if (Settings::Get('system.use_ssl') == '1' && domainHasSslIpPort($row['id']) && $row['caneditdomain'] == '1') {
if (Settings::Get('system.use_ssl') == '1' && domainHasSslIpPort($row['id']) && $row['caneditdomain'] == '1' && $row['letsencrypt'] == 0) {
$show_ssledit = true;
}
$row = htmlentities_array($row);
@@ -303,7 +303,7 @@ if ($page == 'overview') {
$ssl_redirect = '0';
if (isset($_POST['ssl_redirect']) && $_POST['ssl_redirect'] == '1') {
// a ssl-redirect only works of there actually is a
// a ssl-redirect only works if there actually is a
// ssl ip/port assigned to the domain
if (domainHasSslIpPort($domain_check['id']) == true) {
$ssl_redirect = '1';
@@ -313,6 +313,17 @@ if ($page == 'overview') {
}
}
$letsencrypt = '0';
if (isset($_POST['letsencrypt']) && $_POST['letsencrypt'] == '1') {
// let's encrypt only works if there actually is a
// ssl ip/port assigned to the domain
if (domainHasSslIpPort($domain_check['id']) == true) {
$letsencrypt = '1';
} else {
standard_error('letsencryptonlypossiblewithsslipport');
}
}
if ($path == '') {
standard_error('patherror');
} elseif ($subdomain == '') {
@@ -354,7 +365,8 @@ if ($page == 'overview') {
`speciallogfile` = :speciallogfile,
`specialsettings` = :specialsettings,
`ssl_redirect` = :ssl_redirect,
`phpsettingid` = :phpsettingid"
`phpsettingid` = :phpsettingid,
`letsencrypt` = :letsencrypt"
);
$params = array(
"customerid" => $userinfo['customerid'],
@@ -370,7 +382,8 @@ if ($page == 'overview') {
"speciallogfile" => $domain_check['speciallogfile'],
"specialsettings" => $domain_check['specialsettings'],
"ssl_redirect" => $ssl_redirect,
"phpsettingid" => $phpsid_result['phpsettingid']
"phpsettingid" => $phpsid_result['phpsettingid'],
"letsencrypt" => $letsencrypt
);
Database::pexecute($stmt, $params);
@@ -403,7 +416,7 @@ if ($page == 'overview') {
redirectTo($filename, array('page' => $page, 's' => $s));
}
} else {
$stmt = Database::prepare("SELECT `id`, `domain`, `documentroot`, `ssl_redirect`,`isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "`
$stmt = Database::prepare("SELECT `id`, `domain`, `documentroot`, `ssl_redirect`,`isemaildomain`,`letsencrypt` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `customerid` = :customerid
AND `parentdomainid` = '0'
AND `email_only` = '0'
@@ -465,7 +478,7 @@ if ($page == 'overview') {
} elseif ($action == 'edit' && $id != 0) {
$stmt = Database::prepare("SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isemaildomain`, `d`.`wwwserveralias`, `d`.`iswildcarddomain`,
`d`.`parentdomainid`, `d`.`ssl_redirect`, `d`.`aliasdomain`, `d`.`openbasedir`, `d`.`openbasedir_path`, `pd`.`subcanemaildomain`
`d`.`parentdomainid`, `d`.`ssl_redirect`, `d`.`aliasdomain`, `d`.`openbasedir`, `d`.`openbasedir_path`, `d`.`letsencrypt`, `pd`.`subcanemaildomain`
FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_DOMAINS . "` `pd`
WHERE `d`.`customerid` = :customerid
AND `d`.`id` = :id
@@ -545,7 +558,7 @@ if ($page == 'overview') {
}
if (isset($_POST['ssl_redirect']) && $_POST['ssl_redirect'] == '1') {
// a ssl-redirect only works of there actually is a
// a ssl-redirect only works if there actually is a
// ssl ip/port assigned to the domain
if (domainHasSslIpPort($id) == true) {
$ssl_redirect = '1';
@@ -557,6 +570,18 @@ if ($page == 'overview') {
$ssl_redirect = '0';
}
if (isset($_POST['letsencrypt']) && $_POST['letsencrypt'] == '1') {
// let's encrypt only works if there actually is a
// ssl ip/port assigned to the domain
if (domainHasSslIpPort($id) == true) {
$letsencrypt = '1';
} else {
standard_error('letsencryptonlypossiblewithsslipport');
}
} else {
$letsencrypt = '0';
}
if ($path == '') {
standard_error('patherror');
} else {
@@ -580,7 +605,8 @@ if ($page == 'overview') {
|| $iswildcarddomain != $result['iswildcarddomain']
|| $aliasdomain != $result['aliasdomain']
|| $openbasedir_path != $result['openbasedir_path']
|| $ssl_redirect != $result['ssl_redirect']) {
|| $ssl_redirect != $result['ssl_redirect']
|| $letsencrypt != $result['letsencrypt']) {
$log->logAction(USR_ACTION, LOG_INFO, "edited domain '" . $idna_convert->decode($result['domain']) . "'");
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
@@ -590,7 +616,8 @@ if ($page == 'overview') {
`iswildcarddomain`= :iswildcarddomain,
`aliasdomain`= :aliasdomain,
`openbasedir_path`= :openbasedir_path,
`ssl_redirect`= :ssl_redirect
`ssl_redirect`= :ssl_redirect,
`letsencrypt`= :letsencrypt
WHERE `customerid`= :customerid
AND `id`= :id"
);
@@ -602,6 +629,7 @@ if ($page == 'overview') {
"aliasdomain" => ($aliasdomain != 0 && $alias_check == 0) ? $aliasdomain : null,
"openbasedir_path" => $openbasedir_path,
"ssl_redirect" => $ssl_redirect,
"letsencrypt" => $letsencrypt,
"customerid" => $userinfo['customerid'],
"id" => $id
);

View File

@@ -194,6 +194,8 @@ CREATE TABLE `panel_customers` (
`theme` varchar(255) NOT NULL default 'Sparkle',
`custom_notes` text,
`custom_notes_show` tinyint(1) NOT NULL default '0',
`lepublickey` mediumtext DEFAULT NULL,
`leprivatekey` mediumtext DEFAULT NULL,
PRIMARY KEY (`customerid`),
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci;
@@ -247,6 +249,7 @@ CREATE TABLE `panel_domains` (
`mod_fcgid_starter` int(4) default '-1',
`mod_fcgid_maxrequests` int(4) default '-1',
`ismainbutsubto` int(11) unsigned NOT NULL default '0',
`letsencrypt` tinyint(1) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `customerid` (`customerid`),
KEY `parentdomain` (`parentdomainid`),
@@ -509,6 +512,11 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('system', 'dns_createhostnameentry', '0'),
('system', 'send_cron_errors', '0'),
('system', 'apacheitksupport', '0'),
('system', 'leprivatekey', 'unset'),
('system', 'lepublickey', 'unset'),
('system', 'letsencryptca', 'testing'),
('system', 'letsencryptcountrycode', 'DE'),
('system', 'letsencryptstate', 'Germany'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
('panel', 'phpmyadmin_url', ''),
@@ -539,7 +547,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('panel', 'password_numeric', '0'),
('panel', 'password_special_char_required', '0'),
('panel', 'password_special_char', '!?<>§$%+#=@'),
('panel', 'version', '0.9.34.2');
('panel', 'version', '0.9.35-dev2');
DROP TABLE IF EXISTS `panel_tasks`;
@@ -747,7 +755,8 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `interval`, `isactive`,
(3, 'froxlor/ticket', 'used_tickets_reset', '1 DAY', '1', 'cron_ticketsreset'),
(4, 'froxlor/ticket', 'ticketarchive', '1 MONTH', '1', 'cron_ticketarchive'),
(5, 'froxlor/reports', 'usage_report', '1 DAY', '1', 'cron_usage_report'),
(6, 'froxlor/core', 'mailboxsize', '6 HOUR', '1', 'cron_mailboxsize');
(6, 'froxlor/core', 'mailboxsize', '6 HOUR', '1', 'cron_mailboxsize'),
(7, 'froxlor/letsencrypt', 'letsencrypt', '5 MINUTE', '1', 'cron_letsencrypt');
@@ -822,6 +831,7 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` (
`ssl_key_file` mediumtext NOT NULL,
`ssl_ca_file` mediumtext,
`ssl_cert_chainfile` mediumtext,
`expirationdate` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci;

View File

@@ -3020,3 +3020,44 @@ if (isFroxlorVersion('0.9.34.1')) {
updateToVersion('0.9.34.2');
}
if (isFroxlorVersion('0.9.34.2')) {
showUpdateStep("Updating from 0.9.34.2 to 0.9.35-dev1");
lastStepStatus(0);
showUpdateStep("Adding Let's Encrypt - certificate fields");
Database::query("ALTER TABLE `".TABLE_PANEL_DOMAIN_SSL_SETTINGS."` ADD `expirationdate` DATETIME NULL AFTER `ssl_cert_chainfile`;");
Database::query("ALTER TABLE `".TABLE_PANEL_CUSTOMERS."` ADD `lepublickey` MEDIUMTEXT DEFAULT NULL AFTER `custom_notes_show`");
Database::query("ALTER TABLE `".TABLE_PANEL_CUSTOMERS."` ADD `leprivatekey` MEDIUMTEXT DEFAULT NULL AFTER `lepublickey`;");
Database::query("ALTER TABLE `".TABLE_PANEL_DOMAINS."` ADD `letsencrypt` TINYINT(1) NOT NULL DEFAULT '0' AFTER `ismainbutsubto`;");
Settings::AddNew("system.leprivatekey", 'unset');
Settings::AddNew("system.lepublickey", 'unset');
showUpdateStep("Adding new cron-module for Let's encrypt");
$stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_CRONRUNS . "` SET
`module` = 'froxlor/letsencrypt',
`cronfile` = 'letsencrypt',
`interval` = '5 MINUTE',
`desc_lng_key` = 'cron_letsencrypt',
`lastrun` = NOW(),
`isactive` = 1"
);
Database::pexecute($stmt);
lastStepStatus(0);
updateToVersion('0.9.35-dev1');
}
if (isFroxlorVersion('0.9.35-dev1')) {
showUpdateStep("Updating from 0.9.35-dev1 to 0.9.35-dev2");
lastStepStatus(0);
showUpdateStep("Adding Let's Encrypt - settings");
Settings::AddNew("system.letsencryptca", 'testing');
Settings::AddNew("system.letsencryptcountrycode", 'DE');
Settings::AddNew("system.letsencryptstate", 'Germany');
lastStepStatus(0);
updateToVersion('0.9.35-dev2');
}

View File

@@ -267,6 +267,76 @@ class IntegrityCheck {
}
}
/**
* Check if all subdomain have letsencrypt = 0 if domain has no ssl-port
* @param $fix Fix everything found directly
*/
public function SubdomainLetsencrypt($fix = false) {
$ips = array();
$parentdomains = array();
$subdomains = array();
if ($fix) {
// Prepare update statement for the fixes
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "`
SET `letsencrypt` = 0 WHERE `parentdomainid` = :domainid"
);
}
// Cache all ssl ip/port - combinations
$result_stmt = Database::prepare("SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl` = 1 ORDER BY `id` ASC");
Database::pexecute($result_stmt);
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$ips[$row['id']] = $row['ip'] . ':' . $row['port'];
}
// Cache all configured domains
$result_stmt = Database::prepare("SELECT `id`, `parentdomainid`, `letsencrypt` FROM `" . TABLE_PANEL_DOMAINS . "` ORDER BY `id` ASC");
$ip_stmt = Database::prepare("SELECT `id_domain`, `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :domainid");
Database::pexecute($result_stmt);
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($row['parentdomainid'] == 0) {
// All parentdomains by default have no ssl - ip/port
$parentdomains[$row['id']] = false;
Database::pexecute($ip_stmt, array('domainid' => $row['id']));
while ($iprow = $ip_stmt->fetch(PDO::FETCH_ASSOC)) {
// If the parentdomain has an ip/port assigned which we know is SSL enabled, set the parentdomain to "true"
if (array_key_exists($iprow['id_ipandports'], $ips)) { $parentdomains[$row['id']] = true; }
}
} elseif ($row['letsencrypt'] == 1) {
// All subdomains with enabled letsencrypt enabled are stored
if (!isset($subdomains[$row['parentdomainid']])) { $subdomains[$row['parentdomainid']] = array(); }
$subdomains[$row['parentdomainid']][] = $row['id'];
}
}
// Check if every parentdomain with enabled letsencrypt as SSL enabled
foreach ($parentdomains as $id => $sslavailable) {
// This parentdomain has no subdomains
if (!isset($subdomains[$id])) { continue; }
// This parentdomain has SSL enabled, doesn't matter what status the subdomains have
if ($sslavailable) { continue; }
// At this point only parentdomains reside which have letsencrypt enabled subdomains
if ($fix) {
// We make a blanket update to all subdomains of this parentdomain, doesn't matter which one is wrong, all have to be disabled
Database::pexecute($upd_stmt, array('domainid' => $id));
$this->_log->logAction(ADM_ACTION, LOG_WARNING, "found a subdomain with letsencrypt=1 but parent-domain has ssl=0, integrity check fixed this");
} else {
// It's just the check, let the function fail
$this->_log->logAction(ADM_ACTION, LOG_NOTICE, "found a subdomain with letsencrypt=1 but parent-domain has ssl=0, integrity check can fix this");
return false;
}
}
if ($fix) {
return $this->SubdomainLetsencrypt();
} else {
return true;
}
}
/**
* check whether the webserveruser is in
* the customers groups when fcgid / php-fpm is used

View File

@@ -0,0 +1,482 @@
<?php
// Copyright (c) 2015, Stanislav Humplik <sh@analogic.cz>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This file is copied from https://github.com/analogic/lescript
// and modified to work without files and integrate in Froxlor
class lescript
{
public $license = 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf';
private $webRootDir;
private $debugHandler;
private $client;
private $accountKey;
public function __construct($webRootDir, $debugHandler)
{
$this->webRootDir = $webRootDir;
$this->debugHandler = $debugHandler;
if (Settings::Get('system.letsencryptca') == 'production') {
$ca = 'https://acme-v01.api.letsencrypt.org';
} else {
$ca = 'https://acme-staging.api.letsencrypt.org';
}
$this->client = new Client($ca);
}
public function initAccount($certrow)
{
// Let's see if we have the private accountkey
$this->accountKey = $certrow['leprivatekey'];
if (!$this->accountKey || $this->accountKey == 'unset') {
// generate and save new private key for account
// ---------------------------------------------
$this->log('Starting new account registration');
$keys = $this->generateKey();
$upd_stmt = Database::prepare("
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']));
$this->accountKey = $keys['private'];
$this->postNewReg();
$this->log('New account certificate registered');
} else {
$this->log('Account already registered. Continuing.');
}
}
public function signDomains(array $domains, $domainkey = null)
{
if (!$this->accountKey) {
throw new \RuntimeException("Account not initiated");
}
$this->log('Starting certificate generation process for domains');
$privateAccountKey = openssl_pkey_get_private($this->accountKey);
$accountKeyDetails = openssl_pkey_get_details($privateAccountKey);
// start domains authentication
// ----------------------------
foreach($domains as $domain) {
// 1. getting available authentication options
// -------------------------------------------
$this->log("Requesting challenge for $domain");
$response = $this->signedRequest(
"/acme/new-authz",
array("resource" => "new-authz", "identifier" => array("type" => "dns", "value" => $domain))
);
if (!array_key_exists('challenges', $response)) {
throw new RuntimeException("No challenges received for $domain. Whole response: ".json_encode($response));
}
// choose http-01 challange only
$challenge = array_reduce($response['challenges'], function($v, $w) { 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");
$location = $this->client->getLastLocation();
// 2. saving authentication token for web verification
// ---------------------------------------------------
$directory = $this->webRootDir.'/.well-known/acme-challenge';
$tokenPath = $directory.'/'.$challenge['token'];
if(!file_exists($directory) && !@mkdir($directory, 0755, true)) {
throw new \RuntimeException("Couldn't create directory to expose challenge: ${tokenPath}");
}
$header = array(
// need to be in precise order!
"e" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["e"]),
"kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["n"])
);
$payload = $challenge['token'] . '.' . Base64UrlSafeEncoder::encode(hash('sha256', json_encode($header), true));
file_put_contents($tokenPath, $payload);
chmod($tokenPath, 0644);
// 3. verification process itself
// -------------------------------
$uri = "http://${domain}/.well-known/acme-challenge/${challenge['token']}";
$this->log("Token for $domain saved at $tokenPath and should be available at $uri");
// simple self check
if($payload !== trim(@file_get_contents($uri))) {
throw new \RuntimeException("Please check $uri - token not available");
}
$this->log("Sending request to challenge");
// send request to challenge
$result = $this->signedRequest(
$challenge['uri'],
array(
"resource" => "challenge",
"type" => "http-01",
"keyAuthorization" => $payload,
"token" => $challenge['token']
)
);
// waiting loop
// we wait for a maximum of 30 seconds to avoid endless loops
$count = 0;
do {
if(empty($result['status']) || $result['status'] == "invalid") {
throw new \RuntimeException("Verification ended with error: ".json_encode($result));
}
$ended = !($result['status'] === "pending");
if(!$ended) {
$this->log("Verification pending, sleeping 1s");
sleep(1);
$count++;
}
$result = $this->client->get($location);
} while (!$ended && $count < 30);
$this->log("Verification ended with status: ${result['status']}");
@unlink($tokenPath);
}
// requesting certificate
// ----------------------
// generate private key for domain if not exist
if(empty($domainkey)) {
$keys = $this->generateKey();
$domainkey = $keys['private'];
}
// load domain key
$privateDomainKey = openssl_pkey_get_private($domainkey);
$this->client->getLastLinks();
// request certificates creation
$result = $this->signedRequest(
"/acme/new-cert",
array('resource' => 'new-cert', 'csr' => $this->generateCSR($privateDomainKey, $domains))
);
if ($this->client->getLastCode() !== 201) {
throw new \RuntimeException("Invalid response code: ".$this->client->getLastCode().", ".json_encode($result));
}
$location = $this->client->getLastLocation();
// waiting loop
$certificates = array();
while(1) {
$this->client->getLastLinks();
$result = $this->client->get($location);
if($this->client->getLastCode() == 202) {
$this->log("Certificate generation pending, sleeping 1s");
sleep(1);
} else if ($this->client->getLastCode() == 200) {
$this->log("Got certificate! YAY!");
$certificates[] = $this->parsePemFromBody($result);
foreach($this->client->getLastLinks() as $link) {
$this->log("Requesting chained cert at $link");
$result = $this->client->get($link);
$certificates[] = $this->parsePemFromBody($result);
}
break;
} else {
throw new \RuntimeException("Can't get certificate: HTTP code ".$this->client->getLastCode());
}
}
if(empty($certificates)) throw new \RuntimeException('No certificates generated');
$fullchain = implode("\n", $certificates);
$crt = array_shift($certificates);
$chain = implode("\n", $certificates);
$this->log("Done, returning new certificates and key");
return array('fullchain' => $fullchain, 'crt' => $crt, 'chain' => $chain, 'key' => $domainkey);
}
private function parsePemFromBody($body)
{
$pem = chunk_split(base64_encode($body), 64, "\n");
return "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
}
private function postNewReg()
{
$this->log('Sending registration to letsencrypt server');
return $this->signedRequest(
'/acme/new-reg',
array('resource' => 'new-reg', 'agreement' => $this->license)
);
}
private function generateCSR($privateKey, array $domains)
{
$domain = reset($domains);
$san = implode(",", array_map(function ($dns) { return "DNS:" . $dns; }, $domains));
$tmpConf = tmpfile();
$tmpConfMeta = stream_get_meta_data($tmpConf);
$tmpConfPath = $tmpConfMeta["uri"];
// workaround to get SAN working
fwrite($tmpConf,
'HOME = .
RANDFILE = $ENV::HOME/.rnd
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
[ v3_req ]
basicConstraints = CA:FALSE
subjectAltName = '.$san.'
keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
$csr = openssl_csr_new(
array(
"CN" => $domain,
"ST" => Settings::Get('system.letsencryptstate'),
"C" => Settings::Get('system.letsencryptcountrycode'),
"O" => "Unknown",
),
$privateKey,
array(
"config" => $tmpConfPath,
"digest_alg" => "sha256"
)
);
if (!$csr) throw new \RuntimeException("CSR couldn't be generated! ".openssl_error_string());
openssl_csr_export($csr, $csr);
fclose($tmpConf);
preg_match('~REQUEST-----(.*)-----END~s', $csr, $matches);
return trim(Base64UrlSafeEncoder::encode(base64_decode($matches[1])));
}
private function generateKey()
{
$res = openssl_pkey_new(array(
"private_key_type" => OPENSSL_KEYTYPE_RSA,
"private_key_bits" => 4096,
));
if(!openssl_pkey_export($res, $privateKey)) {
throw new \RuntimeException("Key export failed!");
}
$details = openssl_pkey_get_details($res);
return array('private' => $privateKey, 'public' => $details['key']);
}
private function signedRequest($uri, array $payload)
{
$privateKey = openssl_pkey_get_private($this->accountKey);
$details = openssl_pkey_get_details($privateKey);
$header = array(
"alg" => "RS256",
"jwk" => array(
"kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($details["rsa"]["n"]),
"e" => Base64UrlSafeEncoder::encode($details["rsa"]["e"]),
)
);
$protected = $header;
$protected["nonce"] = $this->client->getLastNonce();
$payload64 = Base64UrlSafeEncoder::encode(str_replace('\\/', '/', json_encode($payload)));
$protected64 = Base64UrlSafeEncoder::encode(json_encode($protected));
openssl_sign($protected64.'.'.$payload64, $signed, $privateKey, "SHA256");
$signed64 = Base64UrlSafeEncoder::encode($signed);
$data = array(
'header' => $header,
'protected' => $protected64,
'payload' => $payload64,
'signature' => $signed64
);
$this->log("Sending signed request to $uri");
return $this->client->post($uri, json_encode($data));
}
protected function log($message)
{
fwrite($this->debugHandler, 'letsencrypt ' . $message . "\n");
}
}
class Client
{
private $lastCode;
private $lastHeader;
private $base;
public function __construct($base)
{
$this->base = $base;
}
private function curl($method, $url, $data = null)
{
$headers = array('Accept: application/json', 'Content-Type: application/json');
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL, preg_match('~^http~', $url) ? $url : $this->base.$url);
curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HEADER, true);
// DO NOT DO THAT!
// curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, false);
// curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
switch ($method) {
case 'GET':
break;
case 'POST':
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
break;
}
$response = curl_exec($handle);
if(curl_errno($handle)) {
throw new \RuntimeException('Curl: '.curl_error($handle));
}
$header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$this->lastHeader = $header;
$this->lastCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
$data = json_decode($body, true);
return $data === null ? $body : $data;
}
public function post($url, $data)
{
return $this->curl('POST', $url, $data);
}
public function get($url)
{
return $this->curl('GET', $url);
}
public function getLastNonce()
{
if(preg_match('~Replay\-Nonce: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]);
}
$this->curl('GET', '/directory');
return $this->getLastNonce();
}
public function getLastLocation()
{
if(preg_match('~Location: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]);
}
return null;
}
public function getLastCode()
{
return $this->lastCode;
}
public function getLastLinks()
{
preg_match_all('~Link: <(.+)>;rel="up"~', $this->lastHeader, $matches);
return $matches[1];
}
}
class Base64UrlSafeEncoder
{
public static function encode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
public static function decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
}

View File

@@ -113,6 +113,16 @@ return array(
),
'value' => array()
),
'letsencrypt' => array(
'visible' => (Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports != '' ? true : false) : false),
'label' => $lng['admin']['letsencrypt']['title'],
'desc' => $lng['admin']['letsencrypt']['description'],
'type' => 'checkbox',
'values' => array(
array ('label' => $lng['panel']['yes'], 'value' => '1')
),
'value' => array()
),
'no_ssl_available_info' => array(
'visible' => (Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports == '' ? true : false) : false),
'label' => 'SSL',

View File

@@ -124,6 +124,16 @@ return array(
),
'value' => array($result['ssl_redirect'])
),
'letsencrypt' => array(
'visible' => (Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports != '' ? true : false) : false),
'label' => $lng['admin']['letsencrypt']['title'],
'desc' => $lng['admin']['letsencrypt']['description'],
'type' => 'checkbox',
'values' => array(
array ('label' => $lng['panel']['yes'], 'value' => '1')
),
'value' => array($result['letsencrypt'])
),
'no_ssl_available_info' => array(
'visible' => (Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports == '' ? true : false) : false),
'label' => 'SSL',

View File

@@ -70,6 +70,16 @@ return array(
),
'value' => array()
),
'letsencrypt' => array(
'visible' => (Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports != '' ? true : false) : false),
'label' => $lng['customer']['letsencrypt']['title'],
'desc' => $lng['customer']['letsencrypt']['description'],
'type' => 'checkbox',
'values' => array(
array ('label' => $lng['panel']['yes'], 'value' => '1')
),
'value' => array()
),
'openbasedir_path' => array(
'label' => $lng['domain']['openbasedirpath'],
'type' => 'select',

View File

@@ -86,6 +86,16 @@ return array(
),
'value' => array($result['ssl_redirect'])
),
'letsencrypt' => array(
'visible' => (Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports != '' ? (domainHasSslIpPort($result['id']) ? true : false) : false) : false),
'label' => $lng['customer']['letsencrypt']['title'],
'desc' => $lng['customer']['letsencrypt']['description'],
'type' => 'checkbox',
'values' => array(
array ('label' => $lng['panel']['yes'], 'value' => '1')
),
'value' => array($result['letsencrypt'])
),
'openbasedir_path' => array(
'visible' => ($result['openbasedir'] == '1') ? true : false,
'label' => $lng['domain']['openbasedirpath'],

View File

@@ -16,7 +16,7 @@
*/
// Main version variable
$version = '0.9.34.2';
$version = '0.9.35-dev2';
// Database version (unused, old stuff from SysCP)
$dbversion = '2';

View File

@@ -1924,3 +1924,19 @@ $lng['opcacheinfo']['blacklist'] = 'Blacklist';
$lng['opcacheinfo']['novalue'] = '<i>no value</i>';
$lng['opcacheinfo']['true'] = '<i>true</i>';
$lng['opcacheinfo']['false'] = '<i>false</i>';
// Added for let's encrypt
$lng['admin']['letsencrypt']['title'] = 'Use Let\'s Encrypt';
$lng['admin']['letsencrypt']['description'] = 'Get a free certificate from <a href="https://letsencrypt.org">Let\'s Encrypt</a>. The certificate will be created and renewed automatically.<br><strong class="red">ATTENTION:</strong>If wildcards are enabled, this option will automatically be disabled. This feature is still in beta.';
$lng['customer']['letsencrypt']['title'] = 'Use Let\'s Encrypt';
$lng['customer']['letsencrypt']['description'] = 'Get a free certificate from <a href="https://letsencrypt.org">Let\'s Encrypt</a>. The certificate will be created and renewed automatically.<br><string class="red">ATTENTION:"</strong>This feature is still in beta.';
$lng['error']['sslredirectonlypossiblewithsslipport'] = 'Using Let\'s Encrypt is only possible when the domain has at least one ssl-enabled IP/port combination assigned.';
$lng['panel']['letsencrypt'] = 'Using Let\'s encrypt';
$lng['crondesc']['cron_letsencrypt'] = 'updating Let\'s Encrypt certificates';
$lng['serversettings']['letsencryptca']['title'] = "Let's Encrypt environment";
$lng['serversettings']['letsencryptca']['description'] = "Environment to be used for Let's Encrypt certificates.<br><strong class=\"red\">ATTENTION:</strong>Let's Encrypt is still in beta</strong>";
$lng['serversettings']['letsencryptcountrycode']['title'] = "Let's Encrypt country code";
$lng['serversettings']['letsencryptcountrycode']['description'] = "2 letter country code used to generate Let's Encrypt certificates.<br><strong class=\"red\">ATTENTION:</strong>Let's Encrypt is still in beta</strong>";
$lng['serversettings']['letsencryptstate']['title'] = "Let's Encrypt state";
$lng['serversettings']['letsencryptstate']['description'] = "State used to generate Let's Encrypt certificates.<br><strong class=\"red\">ATTENTION:</strong>Let's Encrypt is still in beta</strong>";

View File

@@ -1579,3 +1579,19 @@ $lng['admin']['specialsettings_replacements'] = "Die folgenden Variablen können
$lng['serversettings']['default_vhostconf']['description'] = 'Der Inhalt dieses Feldes wird direkt in den IP/Port-vHost-Container übernommen. '.$lng['admin']['specialsettings_replacements'].'<br /><strong>ACHTUNG:</strong> Der Code wird nicht auf Fehler geprüft. Etwaige Fehler werden also auch übernommen. Der Webserver könnte nicht mehr starten!';
$lng['serversettings']['default_vhostconf_domain']['description'] = 'Der Inhalt dieses Feldes wird direkt in jeden Domain-vHost-Container übernommen. '. $lng['admin']['specialsettings_replacements'].'<strong>ACHTUNG:</strong> Der Code wird nicht auf Fehler geprüft. Etwaige Fehler werden also auch übernommen. Der Webserver könnte nicht mehr starten!';
$lng['admin']['mod_fcgid_umask']['title'] = 'Umask (Standard: 022)';
// Added for let's encrypt
$lng['admin']['letsencrypt']['title'] = 'Benutze Let\'s Encrypt';
$lng['admin']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von <a href="https://letsencrypt.org">Let\'s Encrypt</a>. Das Zertifikat wird automatisch erstellt und verl&auml;nger.<br><strong class="red">ACHTUNG:</strong>Wenn Wildcards aktiviert sind, wird diese Option automatisch deaktiviert. Dieses Feature befindet sich noch im Test.';
$lng['customer']['letsencrypt']['title'] = 'Benutze Let\'s Encrypt';
$lng['customer']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von <a href="https://letsencrypt.org">Let\'s Encrypt</a>. Das Zertifikat wird automatisch erstellt und verl&auml;ngert.<br><string class="red">ACHTUNG:</strong>Dieses Feature befindet sich noch im Test.';
$lng['error']['sslredirectonlypossiblewithsslipport'] = 'Die Nutzung von Let\'s Encrypt ist nur m&ouml;glich, wenn die Domain mindestens eine IP/Port - Kombination mit aktiviertem SSL zugewiesen hat.';
$lng['panel']['letsencrypt'] = 'Benutzt Let\'s encrypt';
$lng['crondesc']['cron_letsencrypt'] = 'aktualisiert Let\'s Encrypt Zertifikate';
$lng['serversettings']['letsencryptca']['title'] = "Let's Encrypt Umgebung";
$lng['serversettings']['letsencryptca']['description'] = "Let's Encrypt - Umgebung, welche genutzt wird um Zertifikate zu bestellen.<br><strong class=\"red\">ATTENTION:</strong>Let's Encrypt befindet sich noch im Test</strong>";
$lng['serversettings']['letsencryptcountrycode']['title'] = "Let's Encrypt L&auml;ndercode";
$lng['serversettings']['letsencryptcountrycode']['description'] = "2 - stelliger L&auml;ndercode, welcher benutzt wird um Let's Encrypt - Zertifikate zu bestellen.<br><strong class=\"red\">ATTENTION:</strong>Let's Encrypt befindet sich noch im Test</strong>";
$lng['serversettings']['letsencryptstate']['title'] = "Let's Encrypt Bundesland";
$lng['serversettings']['letsencryptstate']['description'] = "Bundesland, welches benutzt wird um Let's Encrypt - Zertifikate zu bestellen.<br><strong class=\"red\">ATTENTION:</strong>Let's Encrypt befindet sich noch im Test</strong>";

View File

@@ -0,0 +1,106 @@
<?php if (!defined('MASTER_CRONJOB')) die('You cannot access this file directly!');
/**
* This file is part of the Froxlor project.
* Copyright (c) 2016 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Florian Aders <kontakt-froxlor@neteraser.de>
* @author Froxlor team <team@froxlor.org> (2016-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @since 0.9.35
*
*/
fwrite($debugHandler, "updating let's encrypt certificates\n");
$certificates_stmt = Database::query("
SELECT domssl.`id`, domssl.`domainid`, domssl.expirationdate, domssl.`ssl_cert_file`, domssl.`ssl_key_file`, domssl.`ssl_ca_file`, dom.`domain`, dom.`iswildcarddomain`, dom.`wwwserveralias`,
dom.`documentroot`, dom.`id` as 'domainid', cust.`leprivatekey`, cust.`lepublickey`, cust.customerid
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)
");
$upd_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` = :fullchain, expirationdate = :expirationdate
");
$changedetected = 0;
while ($certrow = $certificates_stmt->fetch(PDO::FETCH_ASSOC)) {
// Only renew let's encrypt certificate for domains where a documentroot
// already exists
if (file_exists($certrow['documentroot'])
&& is_dir($certrow['documentroot'])
) {
fwrite($debugHandler, "updating " . $certrow['domain'] . "\n");
if ($certrow['ssl_cert_file']) {
fwrite($debugHandler, "letsencrypt using old key / SAN for " . $certrow['domain'] . "\n");
// Parse the old certificate
$x509data = openssl_x509_parse($certrow['ssl_cert_file']);
// We are interessted in the old SAN - data
$san = explode(', ', $x509data['extensions']['subjectAltName']);
$domains = array();
foreach($san as $dnsname) {
$domains[] = substr($dnsname, 4);
}
} else {
fwrite($debugHandler, "letsencrypt generating new key / SAN for " . $certrow['domain'] . "\n");
$domains = array($certrow['domain']);
// Add www.<domain> for SAN
if ($certrow['wwwserveralias'] == 1) {
$domains[] = 'www.' . $certrow['domain'];
}
}
try {
// Initialize Lescript with documentroot
$le = new lescript($certrow['documentroot'], $debugHandler);
// Initialize Lescript
$le->initAccount($certrow);
// Request the new certificate (old key may be used)
$return = $le->signDomains($domains, $certrow['ssl_key_file']);
// We are interessted in the expirationdate
$newcert = openssl_x509_parse($return['crt']);
// Store the new data
Database::pexecute($upd_stmt, array(
'id' => $certrow['id'],
'domainid' => $certrow['domainid'],
'crt' => $return['crt'],
'key' => $return['key'],
'ca' => $return['chain'],
'fullchain' => $return['fullchain'],
'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t'])
)
);
$cronlog->logAction(CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
$changedetected = 1;
} catch (Exception $e) {
$cronlog->logAction(CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ": " . $e->getMessage());
fwrite($debugHandler, 'letsencrypt exception: ' . $e->getMessage() . "\n");
}
} else {
fwrite($debugHandler, 'letsencrypt skipped because documentroot ' . $certrow['documentroot'] . ' does not exist' . "\n");
}
}
// 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 ;)
if ($changedetected) {
inserttask(1);
}

View File

@@ -99,7 +99,7 @@ while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) {
) {
// webserver has no access, add it
if (isFreeBSD()) {
safe_exec('pw user mod '.escapeshellarg(Settings::Get('system.httpuser')).' -G '.escapeshellarg(Settings::Get('phpfpm.vhost_httpgroup')));
safe_exec('pw usermod '.escapeshellarg(Settings::Get('system.httpuser')).' -G '.escapeshellarg(Settings::Get('phpfpm.vhost_httpgroup')));
} else {
safe_exec('usermod -a -G ' . escapeshellarg(Settings::Get('phpfpm.vhost_httpgroup')).' '.escapeshellarg(Settings::Get('system.httpuser')));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -20,6 +20,9 @@
<img src="templates/{$theme}/assets/img/icons/ssl_<if $row['domain_hascert'] == 1>customer</if><if $row['domain_hascert'] == 2>shared</if><if $row['domain_hascert'] == 0>global</if>.png" alt="{$lng['panel']['ssleditor']}" title="{$lng['panel']['ssleditor']}" />
</a>&nbsp;
</if>
<if $row['letsencrypt'] == '1'>
<img src="templates/{$theme}/assets/img/icons/ssl_letsencrypt.png" alt="{$lng['panel']['letsencrypt']}" title="{$lng['panel']['letsencrypt']}" />
</if>
<if $row['parentdomainid'] == '0' && !(isset($row['domainaliasid']) && $row['domainaliasid'] != 0)>
({$lng['domains']['isassigneddomain']})&nbsp;
</if>