Compare commits

..

28 Commits

Author SHA1 Message Date
Michael Kaufmann
50f2047da3 set version to 0.10.17 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-15 20:35:25 +02:00
Michael Kaufmann
ecb9470b65 fix including of language-strings in reports-cron, fixes #836
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-15 15:16:24 +02:00
Michael Kaufmann
6d90b5ba80 remove leftover GROUP BY from testing
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-15 09:56:24 +02:00
Michael Kaufmann
eb3590dc34 add unique-key domainid to domain_ssl_settings table
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-15 09:49:53 +02:00
Michael Kaufmann
bddf9b496c enable internal api-call to bypass customer_hide_options check in certain situations where it is needed, fixes #803
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-15 09:35:20 +02:00
Michael Kaufmann
edc702dafa check for required min version of php every time (frontend and cron), fixes #833
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-08 10:03:01 +02:00
Michael Kaufmann
85dfc1030a Merge pull request #832 from RipClaw2971/bugfix
Certificate file cannot be read to database
2020-05-04 16:11:42 +02:00
Andreas Grundler
c0dd432916 Certificate file cannot be read to database if the domain contains capital letters. 2020-05-04 12:08:20 +02:00
Michael Kaufmann
b3db4dd887 set version to 0.10.16 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-05-01 16:27:23 +02:00
Michael Kaufmann
14413a3e8d try to fix travis irc notifications again
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-30 20:10:52 +02:00
Michael Kaufmann
a02a081c6b try to fix travis irc notifications
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-30 20:03:54 +02:00
Michael Kaufmann
43070e4808 remove possible trailing slash of apache DocumentRoot diretives as sugggested by the httpd-docs, thx to jonbert
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-30 19:49:20 +02:00
Michael Kaufmann
98c636c282 let send-to-alternative-email be optional if no address is given instead of displaying error that the email address is invalid; fixes #829
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-30 13:03:19 +02:00
Michael Kaufmann
8dace6eca5 remove special characters from name when generating extrausers file
add froxlor hostname to 2fa-qrcode; refs #814

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-28 07:59:37 +02:00
Michael Kaufmann
78fc4f84b2 add optional dns validation for let's encrypt activated domains; fixes #817
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-14 10:28:33 +02:00
Michael Kaufmann
9018404faa Double check whether installation of acme.sh worked when not installed yet and do not continue if not; fixes #823
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-14 08:10:36 +02:00
Michael Kaufmann
8bdd843bd9 fix renew of froxlors own letsencrypt certificate; fix only variables should be passed by reference in BackupCron; fix possible notice or double inclusion of language file in ReportsCron
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-08 09:07:48 +02:00
Michael Kaufmann
0d35f5cb29 restructure acmesh implementation and let acme.sh take care of renewing the certificates itself; fixes #792, fixes #816
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-04-04 18:04:39 +02:00
Michael Kaufmann
6815c1c20b Merge pull request #821 from chrschn/master
Fix renewal of ECC/ECDSA certificates.
2020-03-29 23:52:34 +02:00
Christian Schneider
048e6c13ae Fix renewal of ECC/ECDSA certificates.
The ACME v2 implementation uses separate directoies for ECC and on-ECC
certificates. The renew command for a domain checks if an ECC directory
exists (having a "_ecc" suffix) and refuses the command unless the
"--ecc" flag was specified.

Confusingly, this flag is only required to *renew* an ECC certificate,
but not to issue it.

This fixes https://github.com/Froxlor/Froxlor/issues/820.
2020-03-29 22:36:26 +02:00
Michael Kaufmann
aedb829a74 Merge pull request #819 from HBerni/hberni-p0002
fixed parsing due to changes in dovecots default mail_log_prefix
2020-03-21 17:36:00 +01:00
HBerni
4745581720 fixed parsing due to changes in dovecots default mail_log_prefix
regex supports old and new default format now
(see https://wiki2.dovecot.org/Upgrading/2.3)
2020-03-21 16:39:01 +01:00
Michael Kaufmann
489ccbe07a fix removing ip address if ip is set as system-ipaddress but there are other entries of that ip with a different port
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-03-12 07:01:26 +01:00
Michael Kaufmann
a46e7a3bc4 set correct umask to create user config in installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-03-10 07:48:35 +01:00
Michael Kaufmann
a4431e25d3 remove ssl-certificates connected to domains that are being deleted when deleting a customer; fixes #818
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-03-10 07:36:22 +01:00
Michael Kaufmann
1fe9f1e9d6 fix language strings for cron cmdline setting and fallback to php binary if no setting is found
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-03-08 10:30:33 +01:00
Michael Kaufmann
13767df562 set version to 0.10.15 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-03-08 07:39:39 +01:00
Michael Kaufmann
02c5f80854 correct chmod value for userdata.inc.php file to be written on installation; fixes #815
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2020-03-08 07:35:19 +01:00
31 changed files with 769 additions and 369 deletions

View File

@@ -58,7 +58,7 @@ after_success:
- bash <(curl -s https://codecov.io/bash) -f "build/logs/clover.xml" - bash <(curl -s https://codecov.io/bash) -f "build/logs/clover.xml"
notifications: notifications:
irc: "irc.freenode.org#froxlor" irc: "chat.freenode.net#froxlor"
webhooks: webhooks:
urls: urls:
- https://webhooks.gitter.im/e/bdf91d1c3f745e51f796 - https://webhooks.gitter.im/e/bdf91d1c3f745e51f796

View File

@@ -38,7 +38,7 @@ if (AREA == 'admin') {
} }
$success_message = ""; $success_message = "";
$tfa = new \Froxlor\FroxlorTwoFactorAuth('Froxlor'); $tfa = new \Froxlor\FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
// do the delete and then just show a success-message // do the delete and then just show a success-message
if ($action == 'delete') { if ($action == 'delete') {

View File

@@ -214,11 +214,11 @@ return array(
'save_method' => 'storeSettingField' 'save_method' => 'storeSettingField'
), ),
'system_disable_le_selfcheck' => array( 'system_disable_le_selfcheck' => array(
'label' => $lng['serversettings']['disable_le_selfcheck'], 'label' => $lng['serversettings']['le_domain_dnscheck'],
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'disable_le_selfcheck', 'varname' => 'le_domain_dnscheck',
'type' => 'bool', 'type' => 'bool',
'default' => false, 'default' => true,
'save_method' => 'storeSettingField' 'save_method' => 'storeSettingField'
) )
) )

View File

@@ -19,7 +19,6 @@
define('AREA', 'customer'); define('AREA', 'customer');
require './lib/init.php'; require './lib/init.php';
use Froxlor\Api\Commands\SubDomains;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\Api\Commands\Emails as Emails; use Froxlor\Api\Commands\Emails as Emails;

View File

@@ -654,7 +654,7 @@ opcache.interned_strings_buffer'),
('system', 'leregistered', '0'), ('system', 'leregistered', '0'),
('system', 'leaccount', ''), ('system', 'leaccount', ''),
('system', 'nssextrausers', '0'), ('system', 'nssextrausers', '0'),
('system', 'disable_le_selfcheck', '0'), ('system', 'le_domain_dnscheck', '1'),
('system', 'ssl_protocols', 'TLSv1,TLSv1.2'), ('system', 'ssl_protocols', 'TLSv1,TLSv1.2'),
('system', 'tlsv13_cipher_list', ''), ('system', 'tlsv13_cipher_list', ''),
('system', 'honorcipherorder', '0'), ('system', 'honorcipherorder', '0'),
@@ -704,8 +704,8 @@ opcache.interned_strings_buffer'),
('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'password_special_char', '!?<>§$%+#=@'),
('panel', 'customer_hide_options', ''), ('panel', 'customer_hide_options', ''),
('panel', 'is_configured', '0'), ('panel', 'is_configured', '0'),
('panel', 'version', '0.10.14'), ('panel', 'version', '0.10.17'),
('panel', 'db_version', '202002290'); ('panel', 'db_version', '202005150');
DROP TABLE IF EXISTS `panel_tasks`; DROP TABLE IF EXISTS `panel_tasks`;
@@ -997,7 +997,8 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` (
`ssl_csr_file` mediumtext, `ssl_csr_file` mediumtext,
`ssl_fullchain_file` mediumtext, `ssl_fullchain_file` mediumtext,
`expirationdate` datetime DEFAULT NULL, `expirationdate` datetime DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`),
UNIQUE KEY (`domainid`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;

View File

@@ -332,17 +332,18 @@ class FroxlorInstall
$userdata .= "?>"; $userdata .= "?>";
// test if we can store the userdata.inc.php in ../lib // test if we can store the userdata.inc.php in ../lib
$umask = @umask(077);
$userdata_file = dirname(dirname(dirname(__FILE__))) . '/lib/userdata.inc.php'; $userdata_file = dirname(dirname(dirname(__FILE__))) . '/lib/userdata.inc.php';
if (@touch($userdata_file) && @chmod($userdata_file, 0400) && @is_writable($userdata_file)) { if (@touch($userdata_file) && @is_writable($userdata_file)) {
$fp = @fopen($userdata_file, 'w'); $fp = @fopen($userdata_file, 'w');
@fputs($fp, $userdata, strlen($userdata)); @fputs($fp, $userdata, strlen($userdata));
@fclose($fp); @fclose($fp);
$content .= $this->_status_message('green', 'OK'); $content .= $this->_status_message('green', 'OK');
} else { } else {
@unlink($userdata_file);
// try creating it in a temporary file // try creating it in a temporary file
$temp_file = @tempnam(sys_get_temp_dir(), 'fx'); $temp_file = @tempnam(sys_get_temp_dir(), 'fx');
if ($temp_file) { if ($temp_file) {
chmod($temp_file, 0400);
$fp = @fopen($temp_file, 'w'); $fp = @fopen($temp_file, 'w');
@fputs($fp, $userdata, strlen($userdata)); @fputs($fp, $userdata, strlen($userdata));
@fclose($fp); @fclose($fp);
@@ -353,6 +354,7 @@ class FroxlorInstall
eval("\$content .= \"" . $this->_getTemplate("textarea") . "\";"); eval("\$content .= \"" . $this->_getTemplate("textarea") . "\";");
} }
} }
@umask($umask);
return $content; return $content;
} }

View File

@@ -597,3 +597,44 @@ if (\Froxlor\Froxlor::isFroxlorVersion('0.10.13')) {
showUpdateStep("Updating from 0.10.13 to 0.10.14", false); showUpdateStep("Updating from 0.10.13 to 0.10.14", false);
\Froxlor\Froxlor::updateToVersion('0.10.14'); \Froxlor\Froxlor::updateToVersion('0.10.14');
} }
if (\Froxlor\Froxlor::isFroxlorVersion('0.10.14')) {
showUpdateStep("Updating from 0.10.14 to 0.10.15", false);
\Froxlor\Froxlor::updateToVersion('0.10.15');
}
if (\Froxlor\Froxlor::isDatabaseVersion('202002290')) {
showUpdateStep("Adding new setting to validate DNS when using Let's Encrypt");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'system' AND `varname` = 'disable_le_selfcheck'");
$le_domain_dnscheck = isset($_POST['system_le_domain_dnscheck']) ? (int) $_POST['system_le_domain_dnscheck'] : '1';
Settings::AddNew("system.le_domain_dnscheck", $le_domain_dnscheck);
lastStepStatus(0);
\Froxlor\Froxlor::updateToDbVersion('202004140');
}
if (\Froxlor\Froxlor::isFroxlorVersion('0.10.15')) {
showUpdateStep("Updating from 0.10.15 to 0.10.16", false);
\Froxlor\Froxlor::updateToVersion('0.10.16');
}
if (\Froxlor\Froxlor::isDatabaseVersion('202004140')) {
showUpdateStep("Adding unique key on domainid field in domain ssl table");
// check for duplicate entries prior to set a unique key to avoid errors on update
Database::query("
DELETE a.* FROM domain_ssl_settings AS a
LEFT JOIN domain_ssl_settings AS b ON UNIX_TIMESTAMP(b.`expirationdate`) > UNIX_TIMESTAMP(a.`expirationdate`)
AND (b.`domainid`=a.`domainid` OR (UNIX_TIMESTAMP(b.`expirationdate`) = UNIX_TIMESTAMP(a.`expirationdate`) AND b.`id`>a.`id`))
WHERE b.`id` IS NOT NULL
");
Database::query("ALTER TABLE `domain_ssl_settings` ADD UNIQUE(`domainid`)");
lastStepStatus(0);
\Froxlor\Froxlor::updateToDbVersion('202005150');
}
if (\Froxlor\Froxlor::isFroxlorVersion('0.10.16')) {
showUpdateStep("Updating from 0.10.16 to 0.10.17", false);
\Froxlor\Froxlor::updateToVersion('0.10.17');
}

View File

@@ -34,6 +34,9 @@ function getPreConfig($current_version, $current_db_version)
include_once \Froxlor\FileDir::makeCorrectFile(dirname(__FILE__) . '/preconfig/0.9/preconfig_0.9.inc.php'); include_once \Froxlor\FileDir::makeCorrectFile(dirname(__FILE__) . '/preconfig/0.9/preconfig_0.9.inc.php');
parseAndOutputPreconfig($has_preconfig, $return, $current_version, $current_db_version); parseAndOutputPreconfig($has_preconfig, $return, $current_version, $current_db_version);
include_once \Froxlor\FileDir::makeCorrectFile(dirname(__FILE__) . '/preconfig/0.10/preconfig_0.10.inc.php');
parseAndOutputPreconfig2($has_preconfig, $return, $current_version, $current_db_version);
$return .= '<br /><br />' . \Froxlor\UI\HTML::makecheckbox('update_changesagreed', '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>', '1', true, '0', true); $return .= '<br /><br />' . \Froxlor\UI\HTML::makecheckbox('update_changesagreed', '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>', '1', true, '0', true);
$return .= '</div>'; $return .= '</div>';
$return .= '<input type="hidden" name="update_preconfig" value="1" />'; $return .= '<input type="hidden" name="update_preconfig" value="1" />';

View File

@@ -0,0 +1,42 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 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 Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Updater
*
*/
/**
* checks if the new-version has some updating to do
*
* @param boolean $has_preconfig
* pointer to check if any preconfig has to be output
* @param string $return
* pointer to output string
* @param string $current_version
* current froxlor version
*
* @return null
*/
function parseAndOutputPreconfig2(&$has_preconfig, &$return, $current_version, $current_db_version)
{
global $lng;
if (versionInUpdate($current_db_version, '202004140')) {
$has_preconfig = true;
$description = 'Froxlor can now optionally validate the dns entries of domains that request Lets Encrypt certificates to reduce dns-related problems (e.g. freshly registered domain or updated a-record).<br />';
$question = '<strong>Validate DNS of domains when using Lets Encrypt&nbsp;';
$question .= \Froxlor\UI\HTML::makeyesno('system_le_domain_dnscheck', '1', '0', '1');
eval("\$return.=\"" . \Froxlor\UI\Template::getTemplate("update/preconfigitem") . "\";");
}
}

View File

@@ -54,6 +54,13 @@ abstract class ApiCommand extends ApiParameter
*/ */
private $mail = null; private $mail = null;
/**
* whether the call is an internal one or not
*
* @var boolean
*/
private $internal_call = false;
/** /**
* language strings array * language strings array
* *
@@ -90,10 +97,12 @@ abstract class ApiCommand extends ApiParameter
* optional, array of parameters (var=>value) for the command * optional, array of parameters (var=>value) for the command
* @param array $userinfo * @param array $userinfo
* optional, passed via WebInterface (instead of $header) * optional, passed via WebInterface (instead of $header)
* @param boolean $internal
* optional whether called internally, default false
* *
* @throws \Exception * @throws \Exception
*/ */
public function __construct($header = null, $params = null, $userinfo = null) public function __construct($header = null, $params = null, $userinfo = null, $internal = false)
{ {
parent::__construct($params); parent::__construct($params);
@@ -127,6 +136,9 @@ abstract class ApiCommand extends ApiParameter
if ($this->debug) { if ($this->debug) {
$this->logger()->logAction(\Froxlor\FroxlorLogger::LOG_ERROR, LOG_DEBUG, "[API] " . get_called_class() . ": " . json_encode($params, JSON_UNESCAPED_SLASHES)); $this->logger()->logAction(\Froxlor\FroxlorLogger::LOG_ERROR, LOG_DEBUG, "[API] " . get_called_class() . ": " . json_encode($params, JSON_UNESCAPED_SLASHES));
} }
// set internal call flag
$this->internal_call = $internal;
} }
/** /**
@@ -191,13 +203,15 @@ abstract class ApiCommand extends ApiParameter
* array of user-data * array of user-data
* @param array $params * @param array $params
* array of parameters for the command * array of parameters for the command
* @param boolean $internal
* optional whether called internally, default false
* *
* @return ApiCommand * @return ApiCommand
* @throws \Exception * @throws \Exception
*/ */
public static function getLocal($userinfo = null, $params = null) public static function getLocal($userinfo = null, $params = null, $internal = false)
{ {
return new static(null, $params, $userinfo); return new static(null, $params, $userinfo, $internal);
} }
/** /**
@@ -210,6 +224,16 @@ abstract class ApiCommand extends ApiParameter
return $this->is_admin; return $this->is_admin;
} }
/**
* internal call flag
*
* @return boolean
*/
protected function isInternal()
{
return $this->internal_call;
}
/** /**
* return field from user-table * return field from user-table
* *
@@ -241,7 +265,7 @@ abstract class ApiCommand extends ApiParameter
* optional array of placeholders mapped to the actual value which is used in the API commands when executing the statement [internal] * optional array of placeholders mapped to the actual value which is used in the API commands when executing the statement [internal]
* @param boolean $append * @param boolean $append
* optional append to WHERE clause rather then create new one, default false [internal] * optional append to WHERE clause rather then create new one, default false [internal]
* *
* @return string * @return string
*/ */
protected function getSearchWhere(&$query_fields = array(), $append = false) protected function getSearchWhere(&$query_fields = array(), $append = false)
@@ -304,7 +328,7 @@ abstract class ApiCommand extends ApiParameter
* optional, limit resultset, default 0 * optional, limit resultset, default 0
* @param int $sql_offset * @param int $sql_offset
* optional, offset for limitation, default 0 * optional, offset for limitation, default 0
* *
* @return string * @return string
*/ */
protected function getLimit() protected function getLimit()
@@ -333,7 +357,7 @@ abstract class ApiCommand extends ApiParameter
* optional array with index = fieldname and value = ASC|DESC * optional array with index = fieldname and value = ASC|DESC
* @param boolean $append * @param boolean $append
* optional append to ORDER BY clause rather then create new one, default false [internal] * optional append to ORDER BY clause rather then create new one, default false [internal]
* *
* @return string * @return string
*/ */
protected function getOrderBy($append = false) protected function getOrderBy($append = false)
@@ -417,15 +441,18 @@ abstract class ApiCommand extends ApiParameter
* *
* @param string $command * @param string $command
* @param array|null $params * @param array|null $params
* * @param boolean $internal
* optional whether called internally, default false
*
*
* @return array * @return array
*/ */
protected function apiCall($command = null, $params = null) protected function apiCall($command = null, $params = null, $internal = false)
{ {
$_command = explode(".", $command); $_command = explode(".", $command);
$module = __NAMESPACE__ . "\Commands\\" . $_command[0]; $module = __NAMESPACE__ . "\Commands\\" . $_command[0];
$function = $_command[1]; $function = $_command[1];
$json_result = $module::getLocal($this->getUserData(), $params)->{$function}(); $json_result = $module::getLocal($this->getUserData(), $params, $internal)->{$function}();
return json_decode($json_result, true)['data']; return json_decode($json_result, true)['data'];
} }
@@ -491,7 +518,7 @@ abstract class ApiCommand extends ApiParameter
$customer_ids[] = $customer['customerid']; $customer_ids[] = $customer['customerid'];
} }
} else { } else {
if (! empty($customer_hide_option) && \Froxlor\Settings::IsInList('panel.customer_hide_options', $customer_hide_option)) { if (!$this->isInternal() && ! empty($customer_hide_option) && \Froxlor\Settings::IsInList('panel.customer_hide_options', $customer_hide_option)) {
throw new \Exception("You cannot access this resource", 405); throw new \Exception("You cannot access this resource", 405);
} }
$customer_ids = array( $customer_ids = array(

View File

@@ -1410,7 +1410,7 @@ class Customers extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resource
'id' => $id 'id' => $id
), true, true); ), true, true);
// first gather all domain-id's to clean up panel_domaintoip and dns-entries accordingly // first gather all domain-id's to clean up panel_domaintoip, dns-entries and certificates accordingly
$did_stmt = Database::prepare("SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id"); $did_stmt = Database::prepare("SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id");
Database::pexecute($did_stmt, array( Database::pexecute($did_stmt, array(
'id' => $id 'id' => $id
@@ -1426,6 +1426,11 @@ class Customers extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resource
Database::pexecute($stmt, array( Database::pexecute($stmt, array(
'did' => $row['id'] 'did' => $row['id']
), true, true); ), true, true);
// remove domain->certificates entries
$stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :did");
Database::pexecute($stmt, array(
'did' => $row['id']
), true, true);
} }
// remove customer domains // remove customer domains
$stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id"); $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id");

View File

@@ -100,8 +100,8 @@ class EmailAccounts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Reso
// alternative email address to send info to // alternative email address to send info to
if (Settings::Get('panel.sendalternativemail') == 1) { if (Settings::Get('panel.sendalternativemail') == 1) {
$alternative_email = $idna_convert->encode(\Froxlor\Validate\Validate::validate($alternative_email, 'alternative_email', '', '', array(), true)); $alternative_email = $idna_convert->encode(\Froxlor\Validate\Validate::validate($alternative_email, 'alternative_email', '', '', array(), true));
if (! \Froxlor\Validate\Validate::validateEmail($alternative_email)) { if (!empty($alternative_email) && ! \Froxlor\Validate\Validate::validateEmail($alternative_email)) {
\Froxlor\UI\Response::standard_error('emailiswrong', $alternative_email, true); \Froxlor\UI\Response::standard_error('alternativeemailiswrong', $alternative_email, true);
} }
} else { } else {
$alternative_email = ''; $alternative_email = '';
@@ -236,7 +236,7 @@ class EmailAccounts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Reso
$this->mailer()->clearAddresses(); $this->mailer()->clearAddresses();
// customer wants to send the e-mail to an alternative email address too // customer wants to send the e-mail to an alternative email address too
if (Settings::Get('panel.sendalternativemail') == 1) { if (Settings::Get('panel.sendalternativemail') == 1 && !empty($alternative_email)) {
// get template for mail subject // get template for mail subject
$mail_subject = $this->getMailTemplate($customer, 'mails', 'pop_success_alternative_subject', $replace_arr, $this->lng['mails']['pop_success_alternative']['subject']); $mail_subject = $this->getMailTemplate($customer, 'mails', 'pop_success_alternative_subject', $replace_arr, $this->lng['mails']['pop_success_alternative']['subject']);
// get template for mail body // get template for mail body

View File

@@ -62,9 +62,10 @@ class Emails extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\ResourceEnt
} }
// check domain and whether it's an email-enabled domain // check domain and whether it's an email-enabled domain
// use internal call because the customer might have 'domains' in customer_hide_options
$domain_check = $this->apiCall('SubDomains.get', array( $domain_check = $this->apiCall('SubDomains.get', array(
'domainname' => $domain 'domainname' => $domain
)); ), true);
if ($domain_check['isemaildomain'] == 0) { if ($domain_check['isemaildomain'] == 0) {
\Froxlor\UI\Response::standard_error('maindomainnonexist', $domain, true); \Froxlor\UI\Response::standard_error('maindomainnonexist', $domain, true);
} }

View File

@@ -560,7 +560,7 @@ class IpsAndPorts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
'ip' => $result['ip'] 'ip' => $result['ip']
)); ));
if (($result['ip'] != Settings::Get('system.ipaddress')) || ($result['ip'] == Settings::Get('system.ipaddress') && $result_sameipotherport == false)) { if (($result['ip'] != Settings::Get('system.ipaddress')) || ($result['ip'] == Settings::Get('system.ipaddress') && $result_sameipotherport != false)) {
$del_stmt = Database::prepare(" $del_stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "`

View File

@@ -409,7 +409,7 @@ class SubDomains extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resourc
); );
} }
} else { } else {
if (Settings::IsInList('panel.customer_hide_options', 'domains')) { if (! $this->isInternal() && Settings::IsInList('panel.customer_hide_options', 'domains')) {
throw new \Exception("You cannot access this resource", 405); throw new \Exception("You cannot access this resource", 405);
} }
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("

View File

@@ -99,7 +99,7 @@ class CronConfig
$binpath = Settings::Get("system.croncmdline"); $binpath = Settings::Get("system.croncmdline");
// fallback as it is important // fallback as it is important
if ($binpath === null) { if ($binpath === null) {
$binpath = "/usr/bin/nice -n 5 /usr/bin/php5 -q"; $binpath = "/usr/bin/nice -n 5 /usr/bin/php -q";
} }
$cronfile .= "root " . $binpath . " " . \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . "/scripts/froxlor_master_cronjob.php") . " --" . $row_cronentry['cronfile'] . " 1> /dev/null\n"; $cronfile .= "root " . $binpath . " " . \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . "/scripts/froxlor_master_cronjob.php") . " --" . $row_cronentry['cronfile'] . " 1> /dev/null\n";

View File

@@ -172,7 +172,7 @@ class Apache extends HttpConfigBase
$mypath = $this->getMyPath($row_ipsandports); $mypath = $this->getMyPath($row_ipsandports);
$this->virtualhosts_data[$vhosts_filename] .= 'DocumentRoot "' . $mypath . '"' . "\n"; $this->virtualhosts_data[$vhosts_filename] .= 'DocumentRoot "' . rtrim($mypath, "/") . '"' . "\n";
if ($row_ipsandports['vhostcontainer_servername_statement'] == '1') { if ($row_ipsandports['vhostcontainer_servername_statement'] == '1') {
$this->virtualhosts_data[$vhosts_filename] .= ' ServerName ' . Settings::Get('system.hostname') . "\n"; $this->virtualhosts_data[$vhosts_filename] .= ' ServerName ' . Settings::Get('system.hostname') . "\n";
@@ -661,7 +661,7 @@ class Apache extends HttpConfigBase
if ($domain['deactivated'] == '1' && Settings::Get('system.deactivateddocroot') != '') { if ($domain['deactivated'] == '1' && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= ' # Using docroot for deactivated users...' . "\n"; $webroot_text .= ' # Using docroot for deactivated users...' . "\n";
$webroot_text .= ' DocumentRoot "' . \Froxlor\FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . "\"\n"; $webroot_text .= ' DocumentRoot "' . rtrim(\Froxlor\FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')), "/") . "\"\n";
$webroot_text .= ' <Directory "' . \Froxlor\FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . '">' . "\n"; $webroot_text .= ' <Directory "' . \Froxlor\FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . '">' . "\n";
// >=apache-2.4 enabled? // >=apache-2.4 enabled?
if (Settings::Get('system.apache24') == '1') { if (Settings::Get('system.apache24') == '1') {
@@ -674,7 +674,7 @@ class Apache extends HttpConfigBase
$webroot_text .= ' </Directory>' . "\n"; $webroot_text .= ' </Directory>' . "\n";
$this->deactivated = true; $this->deactivated = true;
} else { } else {
$webroot_text .= ' DocumentRoot "' . $domain['documentroot'] . "\"\n"; $webroot_text .= ' DocumentRoot "' . rtrim($domain['documentroot'], "/") . "\"\n";
$this->deactivated = false; $this->deactivated = false;
} }

View File

@@ -4,6 +4,8 @@ namespace Froxlor\Cron\Http\LetsEncrypt;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\PhpHelper;
use Froxlor\Domain\Domain;
/** /**
* This file is part of the Froxlor project. * This file is part of the Froxlor project.
@@ -18,9 +20,9 @@ use Froxlor\Database\Database;
* @author Froxlor team <team@froxlor.org> (2016-) * @author Froxlor team <team@froxlor.org> (2016-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron * @package Cron
* *
* @since 0.9.35 * @since 0.9.35
* *
*/ */
class AcmeSh extends \Froxlor\Cron\FroxlorCron class AcmeSh extends \Froxlor\Cron\FroxlorCron
{ {
@@ -45,7 +47,406 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
public static $no_inserttask = false; public static $no_inserttask = false;
private static function needRenew() /**
* run the task
*
* @param boolean $internal
* @return number
*/
public static function run($internal = false)
{
// usually, this is action is called from within the tasks-jobs
if (! defined('CRON_IS_FORCED') && ! defined('CRON_DEBUG_FLAG') && $internal == false) {
// 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.
// check whether we MIGHT need to run although there is no task to regenerate config-files
$needRenew = self::issueDomains();
if ($needRenew || self::issueFroxlorVhost()) {
// insert task to generate certificates and vhost-configs
\Froxlor\System\Cronjob::inserttask(1);
}
return 0;
}
// set server according to settings
self::$apiserver = 'https://acme-' . (Settings::Get('system.letsencryptca') == 'testing' ? 'staging-' : '') . 'v0' . \Froxlor\Settings::Get('system.leapiversion') . '.api.letsencrypt.org/directory';
// validate acme.sh installation
if (! self::checkInstall()) {
return - 1;
}
// flag for re-generation of vhost files
$changedetected = 0;
// prepare update sql
self::$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,
`ssl_fullchain_file` = :fullchain,
`expirationdate` = :expirationdate
");
// prepare domain update sql
self::$upddom_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `ssl_redirect` = '1' WHERE `id` = :domainid");
// check whether there are certificates to issue
$issue_froxlor = self::issueFroxlorVhost();
$issue_domains = self::issueDomains();
// first - generate LE for system-vhost if enabled
if ($issue_froxlor) {
// build row
$certrow = array(
'loginname' => 'froxlor.panel',
'domain' => Settings::Get('system.hostname'),
'domainid' => 0,
'documentroot' => \Froxlor\Froxlor::getInstallDir(),
'leprivatekey' => Settings::Get('system.leprivatekey'),
'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => null,
'ssl_cert_file' => null,
'ssl_key_file' => null,
'ssl_ca_file' => null,
'ssl_csr_file' => null,
'id' => null
);
// add to queue
$issue_domains[] = $certrow;
}
if (count($issue_domains)) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Requesting " . count($issue_domains) . " new Let's Encrypt certificates");
self::runIssueFor($issue_domains);
$changedetected = 1;
}
// compare file-system certificates with the ones in our database
// and update if needed
$renew_froxlor = self::renewFroxlorVhost();
$renew_domains = self::renewDomains();
if ($renew_froxlor) {
// build row
$certrow = array(
'loginname' => 'froxlor.panel',
'domain' => Settings::Get('system.hostname'),
'domainid' => 0,
'documentroot' => \Froxlor\Froxlor::getInstallDir(),
'leprivatekey' => Settings::Get('system.leprivatekey'),
'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'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_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_csr_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_csr_file'] : null,
'id' => is_array($renew_froxlor) ? $renew_froxlor['id'] : null
);
$renew_domains[] = $certrow;
}
foreach ($renew_domains as $domain) {
$cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $domain['loginname'],
'adminsession' => 0
));
if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['expirationdate'])) {
self::certToDb($domain, $cronlog, array());
$changedetected = 1;
}
}
// 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) {
if (self::$no_inserttask == false) {
\Froxlor\System\Cronjob::inserttask(1);
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
} else {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No new certificates or certificate updates found");
}
}
/**
* issue certificates for a list of domains
*/
private static function runIssueFor($certrows = array())
{
// prepare aliasdomain-check
$aliasdomains_stmt = Database::prepare("
SELECT
dom.`id` as domainid,
dom.`domain`,
dom.`wwwserveralias`
FROM `" . TABLE_PANEL_DOMAINS . "` AS dom
WHERE
dom.`aliasdomain` = :id
AND dom.`letsencrypt` = 1
AND dom.`iswildcarddomain` = 0
");
// iterate through all domains
foreach ($certrows as $certrow) {
// set logger to corresponding loginname for the log to appear in the users system-log
$cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $certrow['loginname'],
'adminsession' => 0
));
// Only issue let's encrypt certificate if no broken ssl_redirect is enabled
if ($certrow['ssl_redirect'] != 2) {
$do_force = false;
if (! empty($certrow['ssl_cert_file']) && empty($certrow['expirationdate'])) {
// domain changed (SAN or similar)
$do_force = true;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']);
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Creating certificate for " . $certrow['domain']);
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding common-name: " . $certrow['domain']);
$domains = array(
strtolower($certrow['domain'])
);
// add www.<domain> to SAN list
if ($certrow['wwwserveralias'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $certrow['domain']);
$domains[] = strtolower('www.' . $certrow['domain']);
}
if ($certrow['domainid'] == 0) {
$froxlor_aliases = Settings::Get('system.froxloraliases');
if (! empty($froxlor_aliases)) {
$froxlor_aliases = explode(",", $froxlor_aliases);
foreach ($froxlor_aliases as $falias) {
if (\Froxlor\Validate\Validate::validateDomain(trim($falias))) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . strtolower(trim($falias)));
$domains[] = strtolower(trim($falias));
}
}
}
} else {
// add alias domains (and possibly www.<aliasdomain>) to SAN list
Database::pexecute($aliasdomains_stmt, array(
'id' => $certrow['domainid']
));
$aliasdomains = $aliasdomains_stmt->fetchAll(\PDO::FETCH_ASSOC);
foreach ($aliasdomains as $aliasdomain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . $aliasdomain['domain']);
$domains[] = strtolower($aliasdomain['domain']);
if ($aliasdomain['wwwserveralias'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $aliasdomain['domain']);
$domains[] = strtolower('www.' . $aliasdomain['domain']);
}
}
}
self::validateDns($domains, $certrow['domainid'], $cronlog);
self::runAcmeSh($certrow, $domains, $cronlog, $do_force);
} else {
$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
*
* @param array $domains
* @param int $domain_id
* @param FroxlorLogger $cronlog
*/
private static function validateDns(&$domains = array(), $domain_id, &$cronlog)
{
if (Settings::Get('system.le_domain_dnscheck') == '1' && ! empty($domains)) {
$loop_domains = $domains;
// ips according to our system
$our_ips = Domain::getIpsOfDomain($domain_id);
foreach ($loop_domains as $idx => $domain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
// ips accordint to NS
$domain_ips = PhpHelper::gethostbynamel6($domain);
if (count(array_intersect($our_ips, $domain_ips)) <= 0) {
// 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");
unset($domains[$idx]);
}
}
}
}
private static function runAcmeSh($certrow = array(), $domains = array(), &$cronlog = null, $force = false)
{
if (! empty($domains)) {
if (self::$do_update) {
self::checkUpgrade();
self::$do_update = false;
}
$acmesh_cmd = self::$acmesh . " --auto-upgrade 0 --server " . self::$apiserver . " --issue -d " . implode(" -d ", $domains);
// challenge path
$acmesh_cmd .= " -w " . Settings::Get('system.letsencryptchallengepath');
if (Settings::Get('system.leecc') > 0) {
// ecc certificate
$acmesh_cmd .= " --keylength ec-" . Settings::Get('system.leecc');
} else {
$acmesh_cmd .= " --keylength " . Settings::Get('system.letsencryptkeysize');
}
if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key";
}
if (Settings::Get('system.letsencryptca') == 'testing') {
$acmesh_cmd .= " --staging";
}
if ($force) {
$acmesh_cmd .= " --force";
}
if (defined('CRON_DEBUG_FLAG')) {
$acmesh_cmd .= " --debug";
}
$acme_result = \Froxlor\FileDir::safe_exec($acmesh_cmd);
// debug output of acme.sh run
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result));
self::certToDb($certrow, $cronlog, $acme_result);
}
}
private static function certToDb($certrow, &$cronlog, $acme_result)
{
$return = array();
self::readCertificateToVar(strtolower($certrow['domain']), $return, $cronlog);
if (! empty($return['crt'])) {
$newcert = openssl_x509_parse($return['crt']);
if ($newcert) {
// Store the new data
Database::pexecute(self::$updcert_stmt, array(
'id' => $certrow['id'],
'domainid' => $certrow['domainid'],
'crt' => $return['crt'],
'key' => $return['key'],
'ca' => $return['chain'],
'chain' => $return['chain'],
'csr' => $return['csr'],
'fullchain' => $return['fullchain'],
'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t'])
));
if ($certrow['ssl_redirect'] == 3) {
Database::pexecute(self::$upddom_stmt, array(
'domainid' => $certrow['domainid']
));
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
}
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
}
}
/**
* check whether we need to issue a new certificate for froxlor itself
*
* @return boolean
*/
private static function issueFroxlorVhost()
{
if (Settings::Get('system.le_froxlor_enabled') == '1') {
// let's encrypt is enabled, now check whether we have a certificate
$froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0'
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate
if (! $froxlor_ssl && ! self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s'))) {
return true;
}
}
return false;
}
/**
* check whether we need to renew-check the certificate for froxlor itself
*
* @return boolean
*/
private static function renewFroxlorVhost()
{
if (Settings::Get('system.le_froxlor_enabled') == '1') {
// let's encrypt is enabled, now check whether we have a certificate
$froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0'
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate
if ($froxlor_ssl || (! $froxlor_ssl && ! self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s', 0)))) {
return ($froxlor_ssl ? $froxlor_ssl : true);
}
}
return false;
}
/**
* get a list of domains that have a lets encrypt certificate (possible renew)
*/
private static function renewDomains()
{
$certificates_stmt = Database::query("
SELECT
domssl.`id`,
domssl.`domainid`,
domssl.`expirationdate`,
domssl.`ssl_cert_file`,
domssl.`ssl_key_file`,
dom.`domain`,
dom.`id` AS 'domainid',
dom.`ssl_redirect`,
cust.`loginname`
FROM
`" . TABLE_PANEL_CUSTOMERS . "` AS cust,
`" . TABLE_PANEL_DOMAINS . "` AS dom
LEFT JOIN
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` AS domssl ON
dom.`id` = domssl.`domainid`
WHERE
dom.`customerid` = cust.`customerid`
AND cust.deactivated = 0
AND dom.`letsencrypt` = 1
AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0
");
$renew_certs = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC);
if ($renew_certs) {
return $renew_certs;
}
return array();
}
/**
* get a list of domains that require a new certificate (issue)
*/
private static function issueDomains()
{ {
$certificates_stmt = Database::query(" $certificates_stmt = Database::query("
SELECT SELECT
@@ -78,322 +479,51 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
AND dom.`letsencrypt` = 1 AND dom.`letsencrypt` = 1
AND dom.`aliasdomain` IS NULL AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
AND ( AND domssl.`expirationdate` IS NULL
domssl.`expirationdate` < DATE_ADD(NOW(), INTERVAL 30 DAY)
OR 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) {
$customer_ssl = array(); return $customer_ssl;
} }
return array();
$froxlor_ssl = array();
if (Settings::Get('system.le_froxlor_enabled') == '1') {
$froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' AND
(`expirationdate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `expirationdate` IS NULL)
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
if (! $froxlor_ssl) {
$froxlor_ssl = array();
}
}
if (count($customer_ssl) > 0 || count($froxlor_ssl) > 0) {
return array(
'customer_ssl' => $customer_ssl,
'froxlor_ssl' => $froxlor_ssl
);
}
return false;
} }
public static function run($internal = false) private static function checkFsFilesAreNewer($domain, $cert_date = 0)
{
if (! defined('CRON_IS_FORCED') && ! defined('CRON_DEBUG_FLAG') && $internal == false) {
// 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.
// check whether we MIGHT need to run although there is no task to regenerate config-files
$needRenew = self::needRenew();
if ($needRenew) {
// insert task to generate certificates and vhost-configs
\Froxlor\System\Cronjob::inserttask(1);
}
return 0;
}
self::checkInstall();
self::$apiserver = 'https://acme-'.(Settings::Get('system.letsencryptca') == 'testing' ? 'staging-' : '').'v0' . \Froxlor\Settings::Get('system.leapiversion') . '.api.letsencrypt.org/directory';
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Requesting/renewing Let's Encrypt certificates");
$aliasdomains_stmt = Database::prepare("
SELECT
dom.`id` as domainid,
dom.`domain`,
dom.`wwwserveralias`
FROM `" . TABLE_PANEL_DOMAINS . "` AS dom
WHERE
dom.`aliasdomain` = :id
AND dom.`letsencrypt` = 1
AND dom.`iswildcarddomain` = 0
");
self::$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,
`ssl_fullchain_file` = :fullchain,
`expirationdate` = :expirationdate
");
self::$upddom_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `ssl_redirect` = '1' WHERE `id` = :domainid");
// flag for re-generation of vhost files
$changedetected = 0;
$needRenew = self::needRenew();
// first - generate LE for system-vhost if enabled
if (Settings::Get('system.le_froxlor_enabled') == '1') {
$certrow = array(
'loginname' => 'froxlor.panel',
'domain' => Settings::Get('system.hostname'),
'domainid' => 0,
'documentroot' => \Froxlor\Froxlor::getInstallDir(),
'leprivatekey' => Settings::Get('system.leprivatekey'),
'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => null,
'ssl_cert_file' => null,
'ssl_key_file' => null,
'ssl_ca_file' => null,
'ssl_csr_file' => null,
'id' => null
);
$froxlor_ssl = $needRenew ? $needRenew['froxlor_ssl'] : array();
$cert_mode = 'issue';
if (count($froxlor_ssl) > 0) {
$cert_mode = 'renew';
$certrow['id'] = $froxlor_ssl['id'];
$certrow['expirationdate'] = $froxlor_ssl['expirationdate'];
$certrow['ssl_cert_file'] = $froxlor_ssl['ssl_cert_file'];
$certrow['ssl_key_file'] = $froxlor_ssl['ssl_key_file'];
$certrow['ssl_ca_file'] = $froxlor_ssl['ssl_ca_file'];
$certrow['ssl_csr_file'] = $froxlor_ssl['ssl_csr_file'];
} else {
// check whether we have an entry with valid certificates which just does not need
// updating yet, so we need to skip this here
$froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = '0'
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
if ($froxlor_ssl && ! empty($froxlor_ssl['ssl_cert_file'])) {
$cert_mode = false;
}
}
if ($cert_mode) {
$domains = array(
strtolower($certrow['domain'])
);
$froxlor_aliases = Settings::Get('system.froxloraliases');
if (! empty($froxlor_aliases)) {
$froxlor_aliases = explode(",", $froxlor_aliases);
foreach ($froxlor_aliases as $falias) {
if (\Froxlor\Validate\Validate::validateDomain(trim($falias))) {
$domains[] = strtolower(trim($falias));
}
}
}
// Only renew let's encrypt certificate if no broken ssl_redirect is enabled
// - this temp. deactivation of the ssl-redirect is handled by the webserver-cronjob
$do_force = false;
if ($cert_mode == 'renew') {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updating certificate for " . $certrow['domain']);
} else {
$do_force = true;
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Creating certificate for " . $certrow['domain']);
}
$cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $certrow['loginname'],
'adminsession' => 0
));
self::runAcmeSh($certrow, $domains, $cert_mode, $cronlog, $changedetected, $do_force);
}
}
// customer domains
$certrows = $needRenew ? $needRenew['customer_ssl'] : array();
foreach ($certrows as $certrow) {
// initialize mode to 'issue'
$cert_mode = 'issue';
// set logger to corresponding loginname for the log to appear in the users system-log
$cronlog = FroxlorLogger::getInstanceOf(array(
'loginname' => $certrow['loginname'],
'adminsession' => 0
));
// Only renew let's encrypt certificate if no broken ssl_redirect is enabled
if ($certrow['ssl_redirect'] != 2) {
$do_force = false;
if (! empty($certrow['ssl_cert_file']) && ! empty($certrow['expirationdate'])) {
$cert_mode = 'renew';
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updating certificate for " . $certrow['domain']);
} else if (! empty($certrow['ssl_cert_file']) && empty($certrow['expirationdate'])) {
// domain changed (SAN or similar)
$do_force = true;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']);
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Creating certificate for " . $certrow['domain']);
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . $certrow['domain']);
$domains = array(
strtolower($certrow['domain'])
);
// add www.<domain> to SAN list
if ($certrow['wwwserveralias'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $certrow['domain']);
$domains[] = strtolower('www.' . $certrow['domain']);
}
// add alias domains (and possibly www.<aliasdomain>) to SAN list
Database::pexecute($aliasdomains_stmt, array(
'id' => $certrow['domainid']
));
$aliasdomains = $aliasdomains_stmt->fetchAll(\PDO::FETCH_ASSOC);
foreach ($aliasdomains as $aliasdomain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: " . $aliasdomain['domain']);
$domains[] = strtolower($aliasdomain['domain']);
if ($aliasdomain['wwwserveralias'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Adding SAN entry: www." . $aliasdomain['domain']);
$domains[] = strtolower('www.' . $aliasdomain['domain']);
}
}
self::runAcmeSh($certrow, $domains, $cert_mode, $cronlog, $changedetected, $do_force);
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect");
}
}
// 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) {
if (self::$no_inserttask == false) {
\Froxlor\System\Cronjob::inserttask(1);
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
} else {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No new certificates or certificates due for renewal found");
}
}
private static function runAcmeSh($certrow = array(), $domains = array(), $cert_mode = 'issue', &$cronlog = null, &$changedetected = 0, $force = false)
{
if (! empty($domains)) {
if (self::$do_update) {
self::checkUpgrade();
self::$do_update = false;
}
$acmesh_cmd = self::$acmesh . " --auto-upgrade 0 --server " . self::$apiserver . " --" . $cert_mode . " -d " . implode(" -d ", $domains);
if ($cert_mode == 'issue') {
$acmesh_cmd .= " -w " . Settings::Get('system.letsencryptchallengepath');
}
if (Settings::Get('system.leecc') > 0) {
$acmesh_cmd .= " --keylength ec-" . Settings::Get('system.leecc');
} else {
$acmesh_cmd .= " --keylength " . Settings::Get('system.letsencryptkeysize');
}
if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key";
}
if (Settings::Get('system.letsencryptca') == 'testing') {
$acmesh_cmd .= " --staging";
}
if ($force) {
$acmesh_cmd .= " --force";
}
if (defined('CRON_DEBUG_FLAG')) {
$acmesh_cmd .= " --debug";
}
$acme_result = \Froxlor\FileDir::safe_exec($acmesh_cmd);
// debug output of acme.sh run
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result));
$return = array();
self::readCertificateToVar($certrow['domain'], $return);
if (! empty($return['crt'])) {
$newcert = openssl_x509_parse($return['crt']);
if ($newcert) {
// Store the new data
Database::pexecute(self::$updcert_stmt, array(
'id' => $certrow['id'],
'domainid' => $certrow['domainid'],
'crt' => $return['crt'],
'key' => $return['key'],
'ca' => $return['chain'],
'chain' => $return['chain'],
'csr' => $return['csr'],
'fullchain' => $return['fullchain'],
'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t'])
));
if ($certrow['ssl_redirect'] == 3) {
Database::pexecute(self::$upddom_stmt, array(
'domainid' => $certrow['domainid']
));
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
$changedetected = 1;
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
}
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
}
}
}
private static function readCertificateToVar($domain, &$return)
{ {
$certificate_folder = dirname(self::$acmesh) . "/" . $domain; $certificate_folder = dirname(self::$acmesh) . "/" . $domain;
if (Settings::Get('system.leecc') > 0) { if (Settings::Get('system.leecc') > 0) {
$certificate_folder .= "_ecc"; $certificate_folder .= "_ecc";
} }
$certificate_folder = \Froxlor\FileDir::makeCorrectDir($certificate_folder); $certificate_folder = \Froxlor\FileDir::makeCorrectDir($certificate_folder);
$ssl_file = \Froxlor\FileDir::makeCorrectFile($certificate_folder . '/' . $domain . '.cer');
if (is_dir($certificate_folder)) { if (is_dir($certificate_folder) && file_exists($ssl_file) && is_readable($ssl_file)) {
$cert_data = openssl_x509_parse(file_get_contents($ssl_file));
if (strtotime($cert_data['validTo_time_t']) > strtotime($cert_date)) {
return true;
}
}
return false;
}
/**
* get certificate files from filesystem and store in $return array
*
* @param string $domain
* @param array $return
* @param object $cronlog
*/
private static function readCertificateToVar($domain, &$return, &$cronlog)
{
$certificate_folder = dirname(self::$acmesh) . "/" . $domain;
$certificate_folder_noecc = null;
if (Settings::Get('system.leecc') > 0) {
$certificate_folder_noecc = \Froxlor\FileDir::makeCorrectDir($certificate_folder);
$certificate_folder .= "_ecc";
}
$certificate_folder = \Froxlor\FileDir::makeCorrectDir($certificate_folder);
if (is_dir($certificate_folder) || is_dir($certificate_folder_noecc)) {
foreach ([ foreach ([
'crt' => $domain . '.cer', 'crt' => $domain . '.cer',
'key' => $domain . '.key', 'key' => $domain . '.key',
@@ -405,28 +535,52 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
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)) {
$ssl_file_fb = \Froxlor\FileDir::makeCorrectFile($certificate_folder_noecc . '/' . $sslfile);
if (file_exists($ssl_file_fb)) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "ECC certificates activated but found only non-ecc file");
$return[$index] = file_get_contents($ssl_file_fb);
continue;
}
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find file '" . $sslfile . "' in '" . $certificate_folder . "'");
$return[$index] = null; $return[$index] = null;
} }
} }
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find certificate-folder '" . $certificate_folder . "'");
} }
} }
private static function checkInstall() /**
* install acme.sh if not found yet
*/
private static function checkInstall($tries = 0)
{ {
if (! file_exists(self::$acmesh)) { 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 . "'");
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;
} 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
return self::checkInstall(++ $tries);
} }
return true;
} }
/**
* run upgrade
*/
private static function checkUpgrade() private static function checkUpgrade()
{ {
$acmesh_result = \Froxlor\FileDir::safe_exec(self::$acmesh . " --upgrade"); $acmesh_result = \Froxlor\FileDir::safe_exec(self::$acmesh . " --upgrade");
// check for activated cron (which is installed automatically) but we don't need it // check for activated cron
$acmesh_result2 = \Froxlor\FileDir::safe_exec(self::$acmesh . " --uninstall-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));
} }
} }

View File

@@ -77,6 +77,7 @@ class BackupCron extends \Froxlor\Cron\FroxlorCron
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id"); $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id");
$cronlog = FroxlorLogger::getInstanceOf();
$all_jobs = $result_tasks_stmt->fetchAll(); $all_jobs = $result_tasks_stmt->fetchAll();
foreach ($all_jobs as $row) { foreach ($all_jobs as $row) {
@@ -96,7 +97,7 @@ class BackupCron extends \Froxlor\Cron\FroxlorCron
\Froxlor\FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir'])); \Froxlor\FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir']));
} }
self::createCustomerBackup($row['data'], $customerdocroot, FroxlorLogger::getInstanceOf()); self::createCustomerBackup($row['data'], $customerdocroot, $cronlog);
} }
} }

View File

@@ -67,7 +67,7 @@ class Extrausers
'name' => \Froxlor\Customer\Customer::getCustomerDetail($u['customerid'], 'name'), 'name' => \Froxlor\Customer\Customer::getCustomerDetail($u['customerid'], 'name'),
'company' => \Froxlor\Customer\Customer::getCustomerDetail($u['customerid'], 'company') 'company' => \Froxlor\Customer\Customer::getCustomerDetail($u['customerid'], 'company')
); );
$u['comment'] = \Froxlor\User::getCorrectUserSalutation($salutation_array); $u['comment'] = self::cleanString(\Froxlor\User::getCorrectUserSalutation($salutation_array));
if ($u['login_enabled'] != 'Y') { if ($u['login_enabled'] != 'Y') {
$u['password'] = '*'; $u['password'] = '*';
$u['shell'] = '/bin/false'; $u['shell'] = '/bin/false';
@@ -90,4 +90,10 @@ class Extrausers
$cronlog->logAction(\Froxlor\FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Error when writing ' . $type . ' file entries'); $cronlog->logAction(\Froxlor\FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Error when writing ' . $type . ' file entries');
} }
} }
private static function cleanString($string = null)
{
$allowed = "/[^a-z0-9\\.\\-\\_\\ ]/i";
return preg_replace($allowed, "", $string);
}
} }

View File

@@ -55,7 +55,6 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
Database::pexecute($result_stmt, $result_data); Database::pexecute($result_stmt, $result_data);
while ($row = $result_stmt->fetch(\PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(\PDO::FETCH_ASSOC)) {
if (isset($row['traffic']) && $row['traffic'] > 0 && $row['traffic_used'] != null && (($row['traffic_used'] * 100) / $row['traffic']) >= (int) Settings::Get('system.report_trafficmax')) { if (isset($row['traffic']) && $row['traffic'] > 0 && $row['traffic_used'] != null && (($row['traffic_used'] * 100) / $row['traffic']) >= (int) Settings::Get('system.report_trafficmax')) {
$rep_userinfo = array( $rep_userinfo = array(
'name' => $row['name'], 'name' => $row['name'],
@@ -93,9 +92,11 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
} }
// include english language file (fallback) // include english language file (fallback)
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php'); include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php');
// include admin/customer language file // include admin/customer language file
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile); if ($lngfile != 'lng/english.lng.php') {
include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile);
}
// Get mail templates from database; the ones from 'admin' are fetched for fallback // Get mail templates from database; the ones from 'admin' are fetched for fallback
$result2_stmt = Database::prepare(" $result2_stmt = Database::prepare("
@@ -146,6 +147,8 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
Database::pexecute($upd_stmt, array( Database::pexecute($upd_stmt, array(
'customerid' => $row['customerid'] 'customerid' => $row['customerid']
)); ));
unset($lng);
} }
} }
@@ -195,9 +198,11 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
} }
// include english language file (fallback) // include english language file (fallback)
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php'); include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php');
// include admin/customer language file // include admin/customer language file
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile); if ($lngfile != 'lng/english.lng.php') {
include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile);
}
// Get mail templates from database; the ones from 'admin' are fetched for fallback // Get mail templates from database; the ones from 'admin' are fetched for fallback
$result2_stmt = Database::prepare(" $result2_stmt = Database::prepare("
@@ -326,6 +331,8 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
} }
$mail->ClearAddresses(); $mail->ClearAddresses();
unset($lng);
} }
} }
} // trafficmax > 0 } // trafficmax > 0
@@ -394,13 +401,15 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
$lngfile = Database::pexecute_first($lngfile_stmt, array( $lngfile = Database::pexecute_first($lngfile_stmt, array(
'deflang' => Settings::Get('panel.standardlanguage') 'deflang' => Settings::Get('panel.standardlanguage')
)); ));
$langfile = $lngfile['file']; $langfile = $lngfile['file'] ?? 'lng/english.lng.php';
} }
// include english language file (fallback) // include english language file (fallback)
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php'); include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php');
// include admin/customer language file // include admin/customer language file
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile); if ($lngfile != 'lng/english.lng.php') {
include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile);
}
// Get mail templates from database; the ones from 'admin' are fetched for fallback // Get mail templates from database; the ones from 'admin' are fetched for fallback
$result2_stmt = Database::prepare(" $result2_stmt = Database::prepare("
@@ -451,6 +460,8 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
Database::pexecute($upd_stmt, array( Database::pexecute($upd_stmt, array(
'customerid' => $row['customerid'] 'customerid' => $row['customerid']
)); ));
unset($lng);
} }
} }
@@ -491,9 +502,11 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
} }
// include english language file (fallback) // include english language file (fallback)
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php'); include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/lng/english.lng.php');
// include admin/customer language file // include admin/customer language file
include_once \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile); if ($lngfile != 'lng/english.lng.php') {
include \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . '/' . $langfile);
}
// Get mail templates from database; the ones from 'admin' are fetched for fallback // Get mail templates from database; the ones from 'admin' are fetched for fallback
$result2_stmt = Database::prepare(" $result2_stmt = Database::prepare("
@@ -544,6 +557,8 @@ class ReportsCron extends \Froxlor\Cron\FroxlorCron
Database::pexecute($upd_stmt, array( Database::pexecute($upd_stmt, array(
'adminid' => $row['adminid'] 'adminid' => $row['adminid']
)); ));
unset($lng);
} }
} }
} // webmax > 0 } // webmax > 0

View File

@@ -6,6 +6,41 @@ use Froxlor\Database\Database;
class Domain class Domain
{ {
/**
* return all ip addresses associated with given domain,
* returns all ips if domain-id = 0 (froxlor.vhost)
*
* @param int $domain_id
* @return array
*/
public static function getIpsOfDomain($domain_id)
{
if ($domain_id > 0) {
$sel_stmt = Database::prepare("
SELECT i.ip FROM `" . TABLE_PANEL_IPSANDPORTS . "` `i`
LEFT JOIN `" . TABLE_DOMAINTOIP . "` `dip` ON dip.id_ipandports = i.id
AND dip.id_domain = :domainid
GROUP BY i.ip
");
$sel_param = array(
'domainid' => $domain_id
);
} else {
// assuming froxlor.vhost (id = 0)
$sel_stmt = Database::prepare("
SELECT ip FROM `" . TABLE_PANEL_IPSANDPORTS . "`
GROUP BY ip
");
$sel_param = array();
}
Database::pexecute($sel_stmt, $sel_param);
$result = array();
while ($ip = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$result[] = $ip['ip'];
}
return $result;
}
/** /**
* return an array of all enabled redirect-codes * return an array of all enabled redirect-codes
* *

View File

@@ -7,10 +7,10 @@ final class Froxlor
{ {
// Main version variable // Main version variable
const VERSION = '0.10.14'; const VERSION = '0.10.17';
// Database version (YYYYMMDDC where C is a daily counter) // Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202002290'; const DBVERSION = '202005150';
// Distribution branding-tag (used for Debian etc.) // Distribution branding-tag (used for Debian etc.)
const BRANDING = ''; const BRANDING = '';

View File

@@ -209,12 +209,12 @@ class MailLogParser
$timestamp = $this->getLogTimestamp($line); $timestamp = $this->getLogTimestamp($line);
if ($this->startTime < $timestamp) { if ($this->startTime < $timestamp) {
if (preg_match("/dovecot.*(?::|\]) imap\(.*@([a-z0-9\.\-]+)\):.*(?:in=(\d+) out=(\d+)|bytes=(\d+)\/(\d+))/i", $line, $matches)) { if (preg_match("/dovecot.*(?::|\]) imap\(.*@([a-z0-9\.\-]+)\)(<\d+><[a-z0-9+\/=]+>)?:.*(?:in=(\d+) out=(\d+)|bytes=(\d+)\/(\d+))/i", $line, $matches)) {
// Dovecot IMAP // Dovecot IMAP
$this->addDomainTraffic($matches[1], (int) $matches[2] + (int) $matches[3], $timestamp); $this->addDomainTraffic($matches[1], (int) $matches[3] + (int) $matches[4], $timestamp);
} elseif (preg_match("/dovecot.*(?::|\]) pop3\(.*@([a-z0-9\.\-]+)\):.*in=(\d+).*out=(\d+)/i", $line, $matches)) { } elseif (preg_match("/dovecot.*(?::|\]) pop3\(.*@([a-z0-9\.\-]+)\)(<\d+><[a-z0-9+\/=]+>)?:.*in=(\d+).*out=(\d+)/i", $line, $matches)) {
// Dovecot POP3 // Dovecot POP3
$this->addDomainTraffic($matches[1], (int) $matches[2] + (int) $matches[3], $timestamp); $this->addDomainTraffic($matches[1], (int) $matches[3] + (int) $matches[4], $timestamp);
} }
} }
} }

View File

@@ -20,6 +20,17 @@
// define default theme for configurehint, etc. // define default theme for configurehint, etc.
$_deftheme = 'Sparkle'; $_deftheme = 'Sparkle';
// validate correct php version
if (version_compare("7.0.0", PHP_VERSION, ">=")) {
// get hint-template
$vendor_hint = file_get_contents(dirname(__DIR__) . '/templates/' . $_deftheme . '/misc/phprequirementfailed.tpl');
// replace values
$vendor_hint = str_replace("<FROXLOR_PHPMIN>", "7.0.0", $vendor_hint);
$vendor_hint = str_replace("<CURRENT_VERSION>", PHP_VERSION, $vendor_hint);
$vendor_hint = str_replace("<CURRENT_YEAR>", date('Y', time()), $vendor_hint);
die($vendor_hint);
}
if (! file_exists(dirname(__DIR__) . '/vendor/autoload.php')) { if (! file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
// get hint-template // get hint-template
$vendor_hint = file_get_contents(dirname(__DIR__) . '/templates/' . $_deftheme . '/misc/vendormissinghint.tpl'); $vendor_hint = file_get_contents(dirname(__DIR__) . '/templates/' . $_deftheme . '/misc/vendormissinghint.tpl');

View File

@@ -204,6 +204,7 @@ $lng['error']['mydomain'] = '\'Domain\'';
$lng['error']['mydocumentroot'] = '\'Documentroot\''; $lng['error']['mydocumentroot'] = '\'Documentroot\'';
$lng['error']['loginnameexists'] = 'Loginname %s already exists'; $lng['error']['loginnameexists'] = 'Loginname %s already exists';
$lng['error']['emailiswrong'] = 'Email-address %s contains invalid characters or is incomplete'; $lng['error']['emailiswrong'] = 'Email-address %s contains invalid characters or is incomplete';
$lng['error']['alternativeemailiswrong'] = 'The given alternative email address %s to send the credentials to seems to be invalid';
$lng['error']['loginnameiswrong'] = 'Loginname "%s" contains illegal characters.'; $lng['error']['loginnameiswrong'] = 'Loginname "%s" contains illegal characters.';
$lng['error']['loginnameiswrong2'] = 'Loginname contains too many characters. Only %s characters are allowed.'; $lng['error']['loginnameiswrong2'] = 'Loginname contains too many characters. Only %s characters are allowed.';
$lng['error']['userpathcombinationdupe'] = 'Combination of username and path already exists'; $lng['error']['userpathcombinationdupe'] = 'Combination of username and path already exists';
@@ -1697,7 +1698,7 @@ $lng['admin']['integrityresult'] = 'Result';
$lng['admin']['integrityfix'] = 'Fix problems automatically'; $lng['admin']['integrityfix'] = 'Fix problems automatically';
$lng['question']['admin_integritycheck_reallyfix'] = 'Do you really want to try fixing all database integrity problems automatically?'; $lng['question']['admin_integritycheck_reallyfix'] = 'Do you really want to try fixing all database integrity problems automatically?';
$lng['serversettings']['system_croncmdline']['title'] = 'Cron execution command (php-binary)'; $lng['serversettings']['system_croncmdline']['title'] = 'Cron execution command (php-binary)';
$lng['serversettings']['system_croncmdline']['description'] = 'Command to execute our cronjobs. Change this only if you know what you are doing (default: "/usr/bin/nice -n 5 /usr/bin/php5 -q")!'; $lng['serversettings']['system_croncmdline']['description'] = 'Command to execute our cronjobs. Change this only if you know what you are doing (default: "/usr/bin/nice -n 5 /usr/bin/php -q")!';
$lng['error']['cannotdeletehostnamephpconfig'] = 'This PHP-configuration is used by the Froxlor-vhost and cannot be deleted.'; $lng['error']['cannotdeletehostnamephpconfig'] = 'This PHP-configuration is used by the Froxlor-vhost and cannot be deleted.';
$lng['error']['cannotdeletedefaultphpconfig'] = 'This PHP-configuration is set as default and cannot be deleted.'; $lng['error']['cannotdeletedefaultphpconfig'] = 'This PHP-configuration is set as default and cannot be deleted.';
$lng['serversettings']['system_cron_allowautoupdate']['title'] = 'Allow automatic database updates'; $lng['serversettings']['system_cron_allowautoupdate']['title'] = 'Allow automatic database updates';
@@ -1982,8 +1983,8 @@ $lng['admin']['domain_http2']['title'] = 'HTTP2 support';
$lng['admin']['domain_http2']['description'] = 'See <a target="_blank" href="https://en.wikipedia.org/wiki/HTTP/2">Wikipedia</a> for a detailed explanation of HTTP2'; $lng['admin']['domain_http2']['description'] = 'See <a target="_blank" href="https://en.wikipedia.org/wiki/HTTP/2">Wikipedia</a> for a detailed explanation of HTTP2';
$lng['admin']['testmail'] = 'SMTP test'; $lng['admin']['testmail'] = 'SMTP test';
$lng['success']['testmailsent'] = 'Test mail sent successfully'; $lng['success']['testmailsent'] = 'Test mail sent successfully';
$lng['serversettings']['disable_le_selfcheck']['title'] = "Disable Let's Encrypt local self-check"; $lng['serversettings']['le_domain_dnscheck']['title'] = "Validate DNS of domains when using Let's Encrypt";
$lng['serversettings']['disable_le_selfcheck']['description'] = "If activated, froxlor will <strong>not</strong> perform its self-check for token accessibility. Needed for NATed IP's or similar."; $lng['serversettings']['le_domain_dnscheck']['description'] = "If activated, froxlor will validate whether the domain which requests a Let's Encrypt certificate resolves to at least one of the system ip addresses.";
$lng['menue']['phpsettings']['fpmdaemons'] = 'PHP-FPM versions'; $lng['menue']['phpsettings']['fpmdaemons'] = 'PHP-FPM versions';
$lng['admin']['phpsettings']['activephpconfigs'] = 'In use for php-config(s)'; $lng['admin']['phpsettings']['activephpconfigs'] = 'In use for php-config(s)';
$lng['admin']['phpsettingsforsubdomains'] = 'Apply php-config to all subdomains:'; $lng['admin']['phpsettingsforsubdomains'] = 'Apply php-config to all subdomains:';

View File

@@ -202,6 +202,7 @@ $lng['error']['mydomain'] = '\'Domain\'';
$lng['error']['mydocumentroot'] = '\'Documentroot\''; $lng['error']['mydocumentroot'] = '\'Documentroot\'';
$lng['error']['loginnameexists'] = 'Der Login-Name "%s" existiert bereits.'; $lng['error']['loginnameexists'] = 'Der Login-Name "%s" existiert bereits.';
$lng['error']['emailiswrong'] = 'Die E-Mail-Adresse "%s" enthält ungültige Zeichen oder ist nicht vollständig.'; $lng['error']['emailiswrong'] = 'Die E-Mail-Adresse "%s" enthält ungültige Zeichen oder ist nicht vollständig.';
$lng['error']['alternativeemailiswrong'] = 'Die angegebene alternative E-Mail Adresse "%s", an welche die Zugangsdaten geschickt werden soll, scheint ungültig zu sein.';
$lng['error']['loginnameiswrong'] = 'Der Login-Name "%s" enthält ungültige Zeichen.'; $lng['error']['loginnameiswrong'] = 'Der Login-Name "%s" enthält ungültige Zeichen.';
$lng['error']['loginnameiswrong2'] = 'Der Login-Name enthält zu viele Zeichen, es sind maximal %s Zeichen erlaubt.'; $lng['error']['loginnameiswrong2'] = 'Der Login-Name enthält zu viele Zeichen, es sind maximal %s Zeichen erlaubt.';
$lng['error']['userpathcombinationdupe'] = 'Die Kombination aus Benutzername und Pfad existiert bereits.'; $lng['error']['userpathcombinationdupe'] = 'Die Kombination aus Benutzername und Pfad existiert bereits.';
@@ -1421,7 +1422,7 @@ $lng['admin']['integrityresult'] = 'Ergebnis';
$lng['admin']['integrityfix'] = 'Probleme automatisch beheben'; $lng['admin']['integrityfix'] = 'Probleme automatisch beheben';
$lng['question']['admin_integritycheck_reallyfix'] = 'M&ouml;chten Sie wirklich versuchen s&auml;mtliche Datenbank-Integrit&auml;tsprobleme automatisch zu beheben?'; $lng['question']['admin_integritycheck_reallyfix'] = 'M&ouml;chten Sie wirklich versuchen s&auml;mtliche Datenbank-Integrit&auml;tsprobleme automatisch zu beheben?';
$lng['serversettings']['system_croncmdline']['title'] = 'Cron Startbefehl (php Programm)'; $lng['serversettings']['system_croncmdline']['title'] = 'Cron Startbefehl (php Programm)';
$lng['serversettings']['system_croncmdline']['description'] = 'Befehl zum Ausführen des Cronjobs. Ändern dieser Einstellung nur wenn nötig (Standard: "/usr/bin/nice -n 5 /usr/bin/php5 -q")!'; $lng['serversettings']['system_croncmdline']['description'] = 'Befehl zum Ausführen des Cronjobs. Ändern dieser Einstellung nur wenn nötig (Standard: "/usr/bin/nice -n 5 /usr/bin/php -q")!';
$lng['error']['cannotdeletehostnamephpconfig'] = 'Diese PHP-Konfiguration ist dem Froxlor-Vhost zugewiesen und kann daher nicht gelöscht werden.'; $lng['error']['cannotdeletehostnamephpconfig'] = 'Diese PHP-Konfiguration ist dem Froxlor-Vhost zugewiesen und kann daher nicht gelöscht werden.';
$lng['error']['cannotdeletedefaultphpconfig'] = 'Diese PHP-Konfiguration ist als Standard hinterlegt und kann daher nicht gelöscht werden.'; $lng['error']['cannotdeletedefaultphpconfig'] = 'Diese PHP-Konfiguration ist als Standard hinterlegt und kann daher nicht gelöscht werden.';
$lng['serversettings']['system_cron_allowautoupdate']['title'] = 'Erlaube automatische Datenbank-Aktualisierungen'; $lng['serversettings']['system_cron_allowautoupdate']['title'] = 'Erlaube automatische Datenbank-Aktualisierungen';
@@ -1632,8 +1633,8 @@ $lng['admin']['domain_http2']['title'] = 'HTTP2 Unterstützung';
$lng['admin']['domain_http2']['description'] = 'Siehe <a target="_blank" href="https://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP.2F2">Wikipedia</a> für eine ausführliche Beschreibung von HTTP2'; $lng['admin']['domain_http2']['description'] = 'Siehe <a target="_blank" href="https://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP.2F2">Wikipedia</a> für eine ausführliche Beschreibung von HTTP2';
$lng['admin']['testmail'] = 'SMTP Test'; $lng['admin']['testmail'] = 'SMTP Test';
$lng['success']['testmailsent'] = 'Test E-Mail erfolgreich gesendet'; $lng['success']['testmailsent'] = 'Test E-Mail erfolgreich gesendet';
$lng['serversettings']['disable_le_selfcheck']['title'] = "Deaktiviere Let's Encrypt lokale Selbstprüfung"; $lng['serversettings']['le_domain_dnscheck']['title'] = "Validiere DNS der Domains wenn Let's Encrypt genutzt wird";
$lng['serversettings']['disable_le_selfcheck']['description'] = "Wenn aktiviert wird Froxlor <strong>keine</strong> Erreichbarkeitsprüfung des Tokens vornehmen. Nötig bei ge-NAT-eten IP-Adressen oder Ähnlichem"; $lng['serversettings']['le_domain_dnscheck']['description'] = "Wenn aktiviert wird froxlor überprüfen ob die DNS Einträge der Domains, welche ein Let's Encrypt Zertifikat beantragt, mindestens auf eine der System IP Adressen auflöst.";
$lng['menue']['phpsettings']['fpmdaemons'] = 'PHP-FPM Versionen'; $lng['menue']['phpsettings']['fpmdaemons'] = 'PHP-FPM Versionen';
$lng['admin']['phpsettings']['activephpconfigs'] = 'In Verwendung für PHP-Konfiguration(en)'; $lng['admin']['phpsettings']['activephpconfigs'] = 'In Verwendung für PHP-Konfiguration(en)';
$lng['admin']['phpsettingsforsubdomains'] = 'PHP-Config für alle Subdomains übernehmen:'; $lng['admin']['phpsettingsforsubdomains'] = 'PHP-Config für alle Subdomains übernehmen:';

View File

@@ -1635,7 +1635,7 @@ $lng['admin']['integrityresult'] = 'Risultato';
$lng['admin']['integrityfix'] = 'Risolvi problemi automaticamente'; $lng['admin']['integrityfix'] = 'Risolvi problemi automaticamente';
$lng['question']['admin_integritycheck_reallyfix'] = 'Vuoi veramente provare a risolvere i problemi di integrità del database automaticamente?'; $lng['question']['admin_integritycheck_reallyfix'] = 'Vuoi veramente provare a risolvere i problemi di integrità del database automaticamente?';
$lng['serversettings']['system_croncmdline']['title'] = 'Commando di esecuzione Cron (binario php)'; $lng['serversettings']['system_croncmdline']['title'] = 'Commando di esecuzione Cron (binario php)';
$lng['serversettings']['system_croncmdline']['description'] = 'Commando per eseguire i nostri cronjob. Modificalo soltanto se sai cosa stai facendo (predefinito: "/usr/bin/nice -n 5 /usr/bin/php5 -q")!'; $lng['serversettings']['system_croncmdline']['description'] = 'Commando per eseguire i nostri cronjob. Modificalo soltanto se sai cosa stai facendo (predefinito: "/usr/bin/nice -n 5 /usr/bin/php -q")!';
$lng['error']['cannotdeletehostnamephpconfig'] = 'Questa configurazione PHP è utilizzata dal vhost Froxlor e non può essere eliminata.'; $lng['error']['cannotdeletehostnamephpconfig'] = 'Questa configurazione PHP è utilizzata dal vhost Froxlor e non può essere eliminata.';
$lng['error']['cannotdeletedefaultphpconfig'] = 'Questa configurazione PHP è impostata come predefinita e non può essere eliminata.'; $lng['error']['cannotdeletedefaultphpconfig'] = 'Questa configurazione PHP è impostata come predefinita e non può essere eliminata.';
$lng['serversettings']['system_cron_allowautoupdate']['title'] = 'Permetti aggiornamenti automatici del database'; $lng['serversettings']['system_cron_allowautoupdate']['title'] = 'Permetti aggiornamenti automatici del database';

View File

@@ -14,6 +14,12 @@
* @package Cron * @package Cron
* *
*/ */
// validate correct php version
if (version_compare("7.0.0", PHP_VERSION, ">=")) {
die('Froxlor requires at least php-7.0. Please validate that your php-cli version and the cron execution command are correct.');
}
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
\Froxlor\Cron\MasterCron::setArguments($argv); \Froxlor\Cron\MasterCron::setArguments($argv);

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="Default-Style" content="text/css" />
<link rel="stylesheet" href="templates/Sparkle/assets/css/main.css" />
<!--[if IE]><link rel="stylesheet" href="templates/Sparkle/assets/css/main_ie.css" /><![endif]-->
<!--[if lt IE 9]><script src="js/html5shiv.min.js"></script><![endif]-->
<link href="templates/Sparkle/assets/img/favicon.ico" rel="icon" type="image/x-icon" />
<title>Froxlor Server Management Panel - Requirements not met</title>
</head>
<body>
<div class="loginpage">
<article class="errorbox bradius">
<header class="dark">
<img src="templates/Sparkle/assets/img/logo.png" alt="Froxlor Server Management Panel" />
</header>
<section class="errorsec">
<div class="errorcontainer bradius">
<div class="errortitle">Whoops!</div>
<div class="error">
<p>It seems you are using an older version of PHP</p>
<p>&nbsp;</p>
<p>Froxlor requires at least PHP version <FROXLOR_PHPMIN><br />The installed version is: <CURRENT_VERSION></p>
</div>
</div>
<aside class="right">
<a href="index.php" title="Click to refresh">Refresh</a>
</aside>
</section>
</article>
</div>
<footer>
<span>
Froxlor &copy; 2009-<CURRENT_YEAR> by <a href="https://www.froxlor.org/" rel="external">the Froxlor Team</a>
</span>
</footer>
</body>
</html>

View File

@@ -25,6 +25,9 @@ class MailsTest extends TestCase
{ {
global $admin_userdata; global $admin_userdata;
// set domains as hidden to test whether the internal flag works
Settings::Set('panel.customer_hide_options', 'domains', true);
// get customer // get customer
$json_result = Customers::getLocal($admin_userdata, array( $json_result = Customers::getLocal($admin_userdata, array(
'loginname' => 'test1' 'loginname' => 'test1'
@@ -39,6 +42,9 @@ class MailsTest extends TestCase
$result = json_decode($json_result, true)['data']; $result = json_decode($json_result, true)['data'];
$this->assertEquals("info@test2.local", $result['email_full']); $this->assertEquals("info@test2.local", $result['email_full']);
$this->assertEquals(0, $result['iscatchall']); $this->assertEquals(0, $result['iscatchall']);
// reset setting
Settings::Set('panel.customer_hide_options', '', true);
} }
public function testAdminEmailsAdd() public function testAdminEmailsAdd()