Compare commits

...

10 Commits
2.0.8 ... 2.0.9

Author SHA1 Message Date
Michael Kaufmann
42b3f1e59d set version to 2.0.9
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-20 18:01:28 +01:00
Michael Kaufmann
1b77632fa8 correctly display config-services command in updater if manual commands are needed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-19 20:19:43 +01:00
Maurice Preuß (envoyr)
867b7b1390 fix domain variable for gethostbynamel6 function
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-18 14:47:25 +01:00
Maurice Preuß (envoyr)
4c6ebde58c adding new dns resolver setting for let's encrypt
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 13:57:47 +01:00
Michael Kaufmann
1e013d9e9a enhance information on updater regarding acme-challenge (if lets encrypt is enabled and applicable)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 11:47:55 +01:00
Michael Kaufmann
c56bc651b9 allow hiding documentation menu for customers via customers-hide-option; use --staging for acme.sh for every test-CA
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 08:59:59 +01:00
aPollO2k
6cbdf45a7c Typo fixed in update_2.x.inc.php (#1082)
PHO_EOL => PHP_EOL
2023-01-16 21:32:56 +01:00
Michael Kaufmann
715667e227 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-15 23:49:09 +01:00
Michael Kaufmann
41de161555 show exact froxlor:config-services parameter for updater; better checks for changed acme-challenge paths; fix typo in PHP_EOL statement; remove crsf token from config-apply-parameter generation from within the ui
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-15 23:48:37 +01:00
Maurice Preuß (envoyr)
1f1ea370c0 add version to mix-manifest.json and add mix function
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-14 21:14:55 +01:00
26 changed files with 284 additions and 74 deletions

View File

@@ -269,7 +269,8 @@ return [
'traffic' => lng('menue.traffic.traffic'),
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP",
'traffic.mail' => lng('menue.traffic.traffic') . " / Mail"
'traffic.mail' => lng('menue.traffic.traffic') . " / Mail",
'misc.documentation' => lng('admin.documentation'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true

View File

@@ -241,6 +241,16 @@ return [
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField'
],
'system_le_domain_dnscheck_resolver' => [
'label' => lng('serversettings.le_domain_dnscheck_resolver'),
'settinggroup' => 'system',
'varname' => 'le_domain_dnscheck_resolver',
'type' => 'text',
'string_regexp' => '/^(([0-9]+ [a-z0-9\-\._]+, ?)*[0-9]+ [a-z0-9\-\._]+)?$/i',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField'
]
]
]

View File

@@ -92,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
if ($distribution != "" && isset($_POST['finish'])) {
unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST;
$params['distro'] = $distribution;
$params['system'] = [];
@@ -121,8 +122,6 @@ if ($userinfo['change_serversettings'] == '1') {
'distribution' => $distribution
]);
} else {
// @fixme check set distribution from settings
$cfg_formfield = [
'config' => [
'title' => lng('admin.configfiles.serverconfiguration'),

View File

@@ -52,7 +52,8 @@
"voku/anti-xss": "^4.1",
"twig/twig": "^3.3",
"erusev/parsedown": "^1.7",
"symfony/console": "^5.4"
"symfony/console": "^5.4",
"pear/net_dns2": "^1.5"
},
"require-dev": {
"phpunit/phpunit": "^9",

53
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f8370edea3c85bcb7b681926a1fff04e",
"content-hash": "41e7a3bc0e13b47c4f245334b113c3be",
"packages": [
{
"name": "erusev/parsedown",
@@ -198,6 +198,57 @@
],
"time": "2022-06-09T08:53:42+00:00"
},
{
"name": "pear/net_dns2",
"version": "v1.5.3",
"source": {
"type": "git",
"url": "https://github.com/mikepultz/netdns2.git",
"reference": "dc8053772132a855b8bb6193422a959995f3a773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikepultz/netdns2/zipball/dc8053772132a855b8bb6193422a959995f3a773",
"reference": "dc8053772132a855b8bb6193422a959995f3a773",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"require-dev": {
"phpunit/phpunit": "^9"
},
"type": "library",
"autoload": {
"psr-0": {
"Net_DNS2": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Mike Pultz",
"email": "mike@mikepultz.com",
"homepage": "https://mikepultz.com/",
"role": "lead"
}
],
"description": "Native PHP DNS Resolver and Updater Library",
"homepage": "https://netdns2.com/",
"keywords": [
"PEAR",
"dns",
"network"
],
"support": {
"issues": "https://github.com/mikepultz/netdns2/issues",
"source": "https://github.com/mikepultz/netdns2"
},
"time": "2022-11-28T19:16:31+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.6.3",

View File

@@ -670,6 +670,7 @@ opcache.validate_timestamps'),
('system', 'leaccount', ''),
('system', 'nssextrausers', '1'),
('system', 'le_domain_dnscheck', '1'),
('system', 'le_domain_dnscheck_resolver', '1.1.1.1'),
('system', 'ssl_protocols', 'TLSv1.2'),
('system', 'tlsv13_cipher_list', ''),
('system', 'honorcipherorder', '0'),
@@ -696,7 +697,7 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.8'),
('system', 'update_notify_last', '2.0.9'),
('system', 'traffictool', 'goaccess'),
('api', 'enabled', '0'),
('2fa', 'enabled', '1'),
@@ -740,8 +741,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.8'),
('panel', 'db_version', '202301120');
('panel', 'version', '2.0.9'),
('panel', 'db_version', '202301180');
DROP TABLE IF EXISTS `panel_tasks`;

View File

@@ -222,7 +222,7 @@ EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHO_EOL;
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
$cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:<br><pre>' . $cron_run_cmd . '</pre>');
}
@@ -281,7 +281,7 @@ EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHO_EOL;
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
$cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:<br><pre>' . $cron_run_cmd . '</pre>');
}
@@ -323,7 +323,26 @@ if (Froxlor::isDatabaseVersion('202212060')) {
$system_letsencryptchallengepath_upd = isset($_POST['system_letsencryptchallengepath_upd']) ? $_POST['system_letsencryptchallengepath_upd'] : $acmesh_challenge_dir;
if ($acmesh_challenge_dir != $system_letsencryptchallengepath_upd) {
Settings::Set('system.letsencryptchallengepath', $system_letsencryptchallengepath_upd);
Update::lastStepStatus(1, 'manual commands needed', 'Please reconfigure webserver service using <pre>bin/froxlor-cli froxlor:config-services</pre> or adjust the path manually in <pre>' . Settings::Get('system.letsencryptacmeconf') . '</pre>');
if ((int) Settings::Get('system.leenabled') == 1) {
// create JSON string for --apply
$dist = Settings::Get('system.distribution');
$webserver = Settings::Get('system.webserver');
if ($webserver == 'apache2') {
$webserver = 'apache22';
if (Settings::Get('system.apache24')) {
$webserver = 'apache24';
}
}
$apply_json = '{"http":"' . $webserver . '","dns":"x","smtp":"x","mail":"x","ftp":"x","distro":"' . $dist . '","system":[]}';
Update::lastStepStatus(1, 'manual commands needed',
"Please reconfigure webserver service using <pre>bin/froxlor-cli froxlor:config-services --apply='" . $apply_json . "'</pre>" .
'<br>or adjust the path manually in <pre>' . Settings::Get('system.letsencryptacmeconf') . '</pre>' .
'<br><br>In case you already have certificates issued, run the following command to validate and correct the webroot used for renewal:<br>' .
'<pre>bin/froxlor-cli froxlor:validate-acme-webroot</pre><br>'
);
} else {
Update::lastStepStatus(0);
}
} else {
Update::lastStepStatus(0);
}
@@ -344,3 +363,17 @@ if (Froxlor::isFroxlorVersion('2.0.7')) {
Froxlor::updateToVersion('2.0.8');
}
if (Froxlor::isDatabaseVersion('202301120')) {
Update::showUpdateStep("Adding new setting for DNS resolver when using Let's Encrypt");
$system_le_domain_dnscheck_resolver = isset($_POST['system_le_domain_dnscheck_resolver']) ? $_POST['system_le_domain_dnscheck_resolver'] : '1.1.1.1';
Settings::AddNew("system.le_domain_dnscheck_resolver", $system_le_domain_dnscheck_resolver);
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202301180');
}
if (Froxlor::isFroxlorVersion('2.0.8')) {
Update::showUpdateStep("Updating from 2.0.8 to 2.0.9", false);
Froxlor::updateToVersion('2.0.9');
}

View File

@@ -74,17 +74,35 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
}
if (Update::versionInUpdate($current_db_version, '202301120')) {
$acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
if ($acmesh_challenge_dir != Froxlor::getInstallDir()) {
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
if ((int) Settings::Get('system.leenabled') == 1 && $acmesh_challenge_dir != $recommended) {
$has_preconfig = true;
$description = 'ACME challenge docroot from settings differs from the current installation directory.';
$question = '<strong>Validate Let\'s Encrypt challenge path&nbsp;';
$question = '<strong>Validate Let\'s Encrypt challenge path (recommended value: ' . $recommended . ')</strong>';
$return['system_letsencryptchallengepath_upd'] = [
'type' => 'text',
'value' => $acmesh_challenge_dir,
'placeholder' => Froxlor::getInstallDir(),
'value' => $recommended,
'placeholder' => $acmesh_challenge_dir,
'label' => $question,
'prior_infotext' => $description
'prior_infotext' => $description,
'mandatory' => true,
];
}
}
if (Update::versionInUpdate($current_db_version, '202301180')) {
if ((int) Settings::Get('system.leenabled') == 1) {
$has_preconfig = true;
$description = 'Froxlor now supports to set an external DNS resolver for the Let\'s Encrypt pre-check.';
$question = '<strong>Specify a DNS resolver IP (recommended value: 1.1.1.1 or similar)</strong>';
$return['system_le_domain_dnscheck_resolver'] = [
'type' => 'text',
'pattern' => '^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$|^\s*$',
'value' => '1.1.1.1',
'placeholder' => '1.1.1.1',
'label' => $question,
'prior_infotext' => $description,
];
}
}

View File

@@ -559,7 +559,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$domain_ips = PhpHelper::gethostbynamel6($domain);
$domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -1523,7 +1523,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$domain_ips = PhpHelper::gethostbynamel6($result['domain']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);

View File

@@ -262,7 +262,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($domain_check['id']);
$domain_ips = PhpHelper::gethostbynamel6($completedomain);
$domain_ips = PhpHelper::gethostbynamel6($completedomain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
@@ -738,7 +738,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($result['letsencrypt'] != $letsencrypt && $letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($result['parentdomainid']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}

View File

@@ -44,7 +44,7 @@ final class ValidateAcmeWebroot extends CliCommand
protected function configure()
{
$this->setName('froxlor:validate-acme-webroot');
$this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\s Encrypt certificate.');
$this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\'s Encrypt certificate.');
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
@@ -71,19 +71,20 @@ final class ValidateAcmeWebroot extends CliCommand
$domains = $sel_stmt->fetchAll(PDO::FETCH_ASSOC);
$upd_stmt = Database::prepare("UPDATE domain_ssl_settings SET expirationdate=NULL WHERE `domainid` = :did");
$acmesh_dir = dirname(Settings::Get('system.acmeshpath'));
$acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
if ($acmesh_challenge_dir != Froxlor::getInstallDir()) {
if ($acmesh_challenge_dir != $recommended) {
$io->warning([
"ACME challenge docroot from settings differs from the current installation directory.",
"Settings: '" . $acmesh_challenge_dir . "'",
"Default/recommended value: '" . Froxlor::getInstallDir() . "'",
"Default/recommended value: '" . $recommended . "'",
]);
$question = new ConfirmationQuestion('Fix ACME challenge docroot setting? [yes] ', true, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
Settings::Set('system.letsencryptchallengepath', Froxlor::getInstallDir());
Settings::Set('system.letsencryptchallengepath', $recommended);
$former_value = $acmesh_challenge_dir;
$acmesh_challenge_dir = Froxlor::getInstallDir();
$acmesh_challenge_dir = $recommended;
// need to update the corresponding acme-alias config-file
$acme_alias_file = Settings::Get('system.letsencryptacmeconf');
$sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";

View File

@@ -521,7 +521,7 @@ EOC;
foreach ($loop_domains as $idx => $domain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
// ips according to NS
$domain_ips = PhpHelper::gethostbynamel6($domain);
$domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || 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");
@@ -557,7 +557,7 @@ EOC;
if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key";
}
if (Settings::Get('system.letsencryptca') == 'letsencrypt_test') {
if (substr(Settings::Get('system.letsencryptca'), -5) == '_test') {
$acmesh_cmd .= " --staging";
}
if ($force) {

View File

@@ -31,10 +31,10 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.0.8';
const VERSION = '2.0.9';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202301120';
const DBVERSION = '202301180';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';

View File

@@ -101,7 +101,7 @@ class Preconfig
$agree = [
'title' => 'Check',
'fields' => [
'update_changesagreed' => ['type' => 'checkbox', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
]
];

View File

@@ -27,6 +27,8 @@ namespace Froxlor;
use Exception;
use Froxlor\UI\Panel\UI;
use Net_DNS2_Exception;
use Net_DNS2_Resolver;
use Throwable;
use voku\helper\AntiXSS;
@@ -244,45 +246,60 @@ class PhpHelper
* ipv6 aware gethostbynamel function
*
* @param string $host
* @param boolean $try_a
* default true
* @param boolean $try_a default true
* @param string|null $nameserver set additional resolver nameserver to use (e.g. 1.1.1.1)
* @return boolean|array
*/
public static function gethostbynamel6($host, $try_a = true)
public static function gethostbynamel6(string $host, bool $try_a = true, string $nameserver = null)
{
$dns6 = @dns_get_record($host, DNS_AAAA);
if (!is_array($dns6)) {
// no record or failed to check
$dns6 = [];
}
if ($try_a == true) {
$dns4 = @dns_get_record($host, DNS_A);
if (!is_array($dns4)) {
// no record or failed to check
$dns4 = [];
}
$dns = array_merge($dns4, $dns6);
} else {
$dns = $dns6;
}
$ips = [];
foreach ($dns as $record) {
if ($record["type"] == "A") {
// always use compressed ipv6 format
$ip = inet_ntop(inet_pton($record["ip"]));
$ips[] = $ip;
try {
// set the default nameservers to use, use the system default if none are provided
$resolver = new Net_DNS2_Resolver($nameserver ? ['nameservers' => [$nameserver]] : []);
// get all ip addresses from the A record
if ($try_a) {
try {
$answer = $resolver->query($host, 'A')->answer;
foreach ($answer as $rr) {
$ips[] = $rr->address;
}
} catch (Net_DNS2_Exception $e) {
// we can't do anything here, just continue
}
}
if ($record["type"] == "AAAA") {
// always use compressed ipv6 format
$ip = inet_ntop(inet_pton($record["ipv6"]));
$ips[] = $ip;
// get all ip addresses from the AAAA record
try {
$answer = $resolver->query($host, 'AAAA')->answer;
foreach ($answer as $rr) {
$ips[] = $rr->address;
}
} catch (Net_DNS2_Exception $e) {
// we can't do anything here, just continue
}
} catch (Net_DNS2_Exception $e) {
// fallback to php's dns_get_record if Net_DNS2 has no resolver available, but this may cause
// problems if the system's dns is not configured correctly; for example, the acme pre-check
// will fail because some providers put a local ip in /etc/hosts
// get all ip addresses from the A record
if ($try_a) {
$answer = @dns_get_record($host, DNS_A);
foreach ($answer as $rr) {
$ips[] = $rr['ip'];
}
}
// get all ip addresses from the AAAA record
$answer = @dns_get_record($host, DNS_AAAA);
foreach ($answer as $rr) {
$ips[] = $rr['ipv6'];
}
}
if (count($ips) < 1) {
return false;
} else {
return $ips;
}
return count($ips) > 0 ? $ips : false;
}
/**

View File

@@ -87,6 +87,10 @@ class FroxlorTwig extends AbstractExtension
new TwigFunction('linker', [
$this,
'getLink'
]),
new TwigFunction('mix', [
$this,
'getMix'
])
];
}
@@ -158,4 +162,9 @@ class FroxlorTwig extends AbstractExtension
{
return 'froxlortwig';
}
public function getMix($mix = '')
{
return mix($mix);
}
}

View File

@@ -4,5 +4,11 @@
* change the options below to either true or false
*/
return [
'enable_webupdate' => false
/**
* enable/disable the possibility to update froxlor from within the web-interface,
* recommended value for debian/ubuntu package users is false to rely on apt and not have version mixup.
* This is also useful for providers that manage the servers but give admin access to froxlor to handle
* updates the way the providers does it (e.g. automation, etc.)
*/
'enable_webupdate' => false,
];

View File

@@ -26,6 +26,14 @@
use Froxlor\Language;
use Froxlor\UI\Request;
/**
* Render a template with the given data.
* Mostly used if we have no template-engine (twig).
*
* @param $template
* @param $attributes
* @return array|false|string|string[]
*/
function view($template, $attributes)
{
$view = file_get_contents(dirname(__DIR__) . '/templates/' . $template);
@@ -33,11 +41,26 @@ function view($template, $attributes)
return str_replace(array_keys($attributes), array_values($attributes), $view);
}
/**
* Get the current translation for a given string.
*
* @param string $identifier
* @param array $arguments
* @return array|string
*/
function lng(string $identifier, array $arguments = [])
{
return Language::getTranslation($identifier, $arguments);
}
/**
* Get the value of a request variable.
*
* @param string $identifier
* @param string|null $default
* @param string|null $session
* @return mixed|string|null
*/
function old(string $identifier, string $default = null, string $session = null)
{
if ($session && isset($_SESSION[$session])) {
@@ -45,3 +68,26 @@ function old(string $identifier, string $default = null, string $session = null)
}
return Request::any($identifier, $default);
}
/**
* Loading the mix manifest file from given theme.
* This file contains the hashed filenames of the assets.
* It must be always placed in the theme assets folder.
*
* @param $filename
* @return mixed|string
*/
function mix($filename)
{
if (preg_match('/templates\/(.+)\/assets\/(.+)\/(.+)/', $filename, $matches)) {
$mixManifest = dirname(__DIR__) . '/templates/' . $matches[1] . '/assets/mix-manifest.json';
if (file_exists($mixManifest)) {
$manifest = json_decode(file_get_contents($mixManifest), true);
$key = '/' . $matches[2] . '/' . $matches[3];
if ($manifest && !empty($manifest[$key])) {
$filename = 'templates/' . $matches[1] . '/assets' . $manifest[$key];
}
}
}
return $filename;
}

View File

@@ -277,14 +277,14 @@ if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'
if (is_array($_themeoptions['variants'][$themevariant]['js'])) {
foreach ($_themeoptions['variants'][$themevariant]['js'] as $jsfile) {
if (file_exists('templates/' . $theme . '/assets/js/' . $jsfile)) {
$js .= '<script type="text/javascript" src="templates/' . $theme . '/assets/js/' . $jsfile . '"></script>' . "\n";
$js .= '<script type="text/javascript" src="' . mix('templates/' . $theme . '/assets/js/' . $jsfile) . '"></script>' . "\n";
}
}
}
if (is_array($_themeoptions['variants'][$themevariant]['css'])) {
foreach ($_themeoptions['variants'][$themevariant]['css'] as $cssfile) {
if (file_exists('templates/' . $theme . '/assets/css/' . $cssfile)) {
$css .= '<link href="templates/' . $theme . '/assets/css/' . $cssfile . '" rel="stylesheet" type="text/css" />' . "\n";
$css .= '<link href="' . mix('templates/' . $theme . '/assets/css/' . $cssfile) . '" rel="stylesheet" type="text/css" />' . "\n";
}
}
}

View File

@@ -158,6 +158,7 @@ return [
'docs' => [
'label' => lng('admin.documentation'),
'icon' => 'fa-solid fa-circle-info',
'show_element' => (!Settings::IsInList('panel.customer_hide_options', 'misc.documentation')),
'elements' => [
[
'url' => 'https://docs.froxlor.org/v2/user-guide/',

View File

@@ -1965,6 +1965,10 @@ Vielen Dank, Ihr Administrator',
'title' => 'Validiere DNS der Domains wenn Let\'s Encrypt genutzt wird',
'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.',
],
'le_domain_dnscheck_resolver' => [
'title' => 'DNS Resolver für die DNS Überprüfung',
'description' => 'IP Adresse des DNS Servers, welcher für die DNS Überprüfung genutzt werden soll. Wenn leer, wird der Standard DNS Resolver des Systems genutzt.',
],
'phpsettingsforsubdomains' => [
'description' => 'Wenn ja, wird die gewählte PHP-Config für alle Subdomains übernommen',
],

View File

@@ -2084,6 +2084,10 @@ Yours sincerely, your administrator',
'title' => 'Validate DNS of domains when using Let\'s Encrypt',
'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.',
],
'le_domain_dnscheck_resolver' => [
'title' => 'Use a external nameserver for DNS validation',
'description' => 'If set, froxlor will use this DNS to validate the DNS of domains when using Let\'s Encrypt. If empty, the system\'s default DNS resolver will be used.',
],
'phpsettingsforsubdomains' => [
'description' => 'If yes the chosen php-config will be updated to all subdomains',
],

View File

@@ -11,7 +11,7 @@
<!-- CSS -->
{% if theme_css is empty %}
<link href="{{ basehref|default('') }}templates/Froxlor/assets/css/main.css" rel="stylesheet" type="text/css" />
<link href="{{ basehref|default('') }}{{ mix('templates/Froxlor/assets/css/main.css') }}" rel="stylesheet" type="text/css" />
{% else %}
{{ theme_css|raw }}
{% endif %}
@@ -19,7 +19,7 @@
<!-- Scripts -->
{% if theme_js is empty %}
<script type="text/javascript" src="{{ basehref|default('') }}templates/Froxlor/assets/js/main.js"></script>
<script type="text/javascript" src="{{ basehref|default('') }}{{ mix('templates/Froxlor/assets/js/main.js') }}"></script>
{% else %}
{{ theme_js|raw }}
{% endif %}

View File

@@ -54,6 +54,8 @@
{{ _self.input_ul(id, field) }}
{% elseif field.type == 'checkbox' %}
{{ _self.bool(id, field) }}
{% elseif field.type == 'checkrequired' %}
{{ _self.chk_required(id, field) }}
{% elseif field.type == 'select' %}
{{ _self.select(id, field) }}
{% elseif field.type == 'textarea' %}
@@ -119,6 +121,12 @@
{% endif %}
{% endmacro %}
{% macro chk_required(id, field) %}
<div class="form-check form-switch">
<input type="checkbox" value="{{ field.value }}" id="{{ id }}" name="{{ id }}" class="form-check-input" {% if field.mandatory is defined and field.mandatory == 1 %} required {% endif %} />
</div>
{% endmacro %}
{% macro infotext(id, field) %}
{% if field.next_to is defined %}
<div class="input-group">
@@ -151,7 +159,7 @@
{% if field.next_to is defined %}
<div class="input-group">
{% endif %}
<input type="{{ field.type }}" {% if field.visible is defined and field.visible == false %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %}/>
<input type="{{ field.type }}" {% if field.visible is defined and field.visible == false %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
{% if field.type == 'hidden' and field.display is defined %}
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
{% endif %}

View File

@@ -45,7 +45,7 @@
{% if stype == 'system' %}
<div class="form-check">
{% set recommended = false %}
{% if
{% if
(dtype == get_setting('system.traffictool')) or
(dtype == 'libnssextrausers' and (get_setting('system.mod_fcgid') == '1' or get_setting('phpfpm.enabled') == '1' or get_setting('system.apacheitksupport') == '1')) or
(dtype == 'logrotate') or
@@ -72,7 +72,7 @@
<div class="form-check">
{% set recommended = false %}
{% if
(dtype == 'apache22' and get_setting('system.webserver') == 'apache2' and get_setting('system.apache24') == '0') or
(dtype == 'apache22' and get_setting('system.webserver') == 'apache2' and get_setting('system.apache24') == '0') or
(dtype == 'apache24' and get_setting('system.webserver') == 'apache2' and get_setting('system.apache24') == '1') or
(dtype == 'lighttpd' and get_setting('system.webserver') == 'lighttpd') or
(dtype == 'nginx' and get_setting('system.webserver') == 'nginx') or
@@ -112,7 +112,6 @@
{{ lng('admin.configfiles.recommendednote') }}
</div>
<div class="col-12 col-md-6 text-end">
<input type="hidden" name="dist" value="{{ distribution }}"/>
<button type="button" class="btn btn-outline-secondary" id="selectRecommendedConfig">{{ lng('admin.configfiles.selectrecommended') }}</button>
<button type="button" class="btn btn-outline-secondary" id="downloadSelectionAsJson">
<i class="fa-solid fa-download"></i>

View File

@@ -10,4 +10,5 @@ mix
.copyDirectory('node_modules/@fortawesome/fontawesome-free/webfonts', 'templates/Froxlor/assets/webfonts')
.js('templates/Froxlor/src/js/main.js', 'js')
.sass('templates/Froxlor/src/scss/main.scss', 'css')
.sass('templates/Froxlor/src/scss/dark.scss', 'css');
.sass('templates/Froxlor/src/scss/dark.scss', 'css')
.version();