Compare commits
13 Commits
2.1.0-beta
...
2.1.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1debe9d939 | ||
|
|
3d2e81b457 | ||
|
|
ac759cd9a4 | ||
|
|
05c77929e4 | ||
|
|
cefd9226bd | ||
|
|
762f295d3d | ||
|
|
d3e6063027 | ||
|
|
f18c14e119 | ||
|
|
77bcd10729 | ||
|
|
6ee990af0a | ||
|
|
a3fe37b69b | ||
|
|
56388ede54 | ||
|
|
b98035bf3a |
10
README.md
10
README.md
@@ -34,19 +34,13 @@ You may find help in the following places:
|
||||
|
||||
The froxlor community discord server can be found here: https://discord.froxlor.org
|
||||
|
||||
### IRC
|
||||
|
||||
froxlor may be found on libera.chat, channel #froxlor:
|
||||
irc://irc.libera.chat/froxlor
|
||||
|
||||
### Forum
|
||||
|
||||
The community is located on https://forum.froxlor.org/
|
||||
|
||||
### Wiki
|
||||
### Documentation
|
||||
|
||||
More documentation may be found in the froxlor - documentation:
|
||||
https://docs.froxlor.org/
|
||||
The documentation may be found at https://docs.froxlor.org/
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ With that, good luck hacking us ;)
|
||||
|
||||
### Vulnerabilities we accept
|
||||
|
||||
Only reproducable issues on a default/clean setup from the latest stable release of a supported version will be accepted.
|
||||
Only reproducible issues on a default/clean setup from the latest stable release of a supported version will be accepted.
|
||||
|
||||
## Non-Qualifying Vulnerabilities
|
||||
|
||||
@@ -45,4 +45,4 @@ Only reproducable issues on a default/clean setup from the latest stable release
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you have found a vulnerability in froxlor, please head over to [https://huntr.dev/repos/froxlor/froxlor](https://huntr.dev/repos/froxlor/froxlor) and use the reporting possibilities there as we are funding the prize-pot for froxlor on this platform. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can send us an email to [team@froxlor.org](team@froxlor.org).
|
||||
If you think you have found a vulnerability in froxlor, please head over to [https://github.com/Froxlor/Froxlor/security/advisories](https://github.com/Froxlor/Froxlor/security/advisories/new) and use the reporting possibilities there. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can email us to [team@froxlor.org](team@froxlor.org).
|
||||
|
||||
@@ -679,7 +679,7 @@ opcache.validate_timestamps'),
|
||||
('system', 'distribution', ''),
|
||||
('system', 'update_channel', 'stable'),
|
||||
('system', 'updatecheck_data', ''),
|
||||
('system', 'update_notify_last', '2.1.0-beta2'),
|
||||
('system', 'update_notify_last', '2.1.0-rc1'),
|
||||
('system', 'traffictool', 'goaccess'),
|
||||
('system', 'req_limit_per_interval', 60),
|
||||
('system', 'req_limit_interval', 60),
|
||||
@@ -727,7 +727,7 @@ opcache.validate_timestamps'),
|
||||
('panel', 'logo_overridecustom', '0'),
|
||||
('panel', 'settings_mode', '0'),
|
||||
('panel', 'menu_collapsed', '1'),
|
||||
('panel', 'version', '2.1.0-beta2'),
|
||||
('panel', 'version', '2.1.0-rc1'),
|
||||
('panel', 'db_version', '202305240');
|
||||
|
||||
|
||||
|
||||
@@ -103,3 +103,8 @@ if (Froxlor::isFroxlorVersion('2.1.0-beta1')) {
|
||||
|
||||
Froxlor::updateToVersion('2.1.0-beta2');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-beta2')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-beta2 to 2.1.0-rc1", false);
|
||||
Froxlor::updateToVersion('2.1.0-rc1');
|
||||
}
|
||||
|
||||
@@ -176,8 +176,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ((int)Settings::Get('system.use_ssl') == 1) {
|
||||
$ssl = (bool)$this->getBoolParam('ssl', true, 0);
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, ''), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, ''), 'ssl_key_file', '', '', [], true);
|
||||
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, ''), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, ''), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_file', '', '', [], true);
|
||||
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
|
||||
$sslss = $this->getParam('ssl_specialsettings', true, '');
|
||||
@@ -415,8 +416,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ((int)Settings::Get('system.use_ssl') == 1) {
|
||||
$ssl = (bool)$this->getBoolParam('ssl', true, $result['ssl']);
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
|
||||
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, $result['ssl_ca_file']), 'ssl_ca_file', '', '', [], true);
|
||||
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', [], true);
|
||||
$sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);
|
||||
|
||||
@@ -56,7 +56,7 @@ final class UpdateCommand extends CliCommand
|
||||
|
||||
// database update only
|
||||
if ($input->getOption('database')) {
|
||||
$result = $this->validateRequirements($input, $output, true);
|
||||
$result = $this->validateRequirements($output, true);
|
||||
if ($result == self::SUCCESS) {
|
||||
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
|
||||
$output->writeln('<info>' . lng('updates.dbupdate_required') . '</>');
|
||||
@@ -77,7 +77,7 @@ final class UpdateCommand extends CliCommand
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
if ($result != self::SUCCESS) {
|
||||
// requirements failed, exit
|
||||
|
||||
@@ -515,13 +515,7 @@ class Apache extends HttpConfigBase
|
||||
*/
|
||||
private function createStandardDirectoryEntry()
|
||||
{
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_dirfix_nofcgid.conf');
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_dirfix_nofcgid.conf');
|
||||
|
||||
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
|
||||
$this->virtualhosts_data[$vhosts_filename] = '';
|
||||
@@ -545,7 +539,7 @@ class Apache extends HttpConfigBase
|
||||
}
|
||||
$this->virtualhosts_data[$vhosts_filename] .= ' </Directory>' . "\n";
|
||||
|
||||
$ocsp_cache_filename = FileDir::makeCorrectFile($vhosts_folder . '/03_froxlor_ocsp_cache.conf');
|
||||
$ocsp_cache_filename = $this->getCustomVhostFilename('03_froxlor_ocsp_cache.conf');
|
||||
if (Settings::Get('system.use_ssl') == '1' && Settings::Get('system.apache24') == 1) {
|
||||
$this->virtualhosts_data[$ocsp_cache_filename] = 'SSLStaplingCache ' . Settings::Get('system.apache24_ocsp_cache_path') . "\n";
|
||||
} else {
|
||||
@@ -562,14 +556,7 @@ class Apache extends HttpConfigBase
|
||||
private function createStandardErrorHandler()
|
||||
{
|
||||
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
|
||||
|
||||
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
|
||||
$this->virtualhosts_data[$vhosts_filename] = '';
|
||||
|
||||
@@ -202,4 +202,13 @@ class HttpConfigBase
|
||||
}
|
||||
return FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $filename);
|
||||
}
|
||||
|
||||
protected function getCustomVhostFilename(string $name)
|
||||
{
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
}
|
||||
return FileDir::makeCorrectFile($vhosts_folder . '/' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1161,14 +1161,7 @@ class Nginx extends HttpConfigBase
|
||||
private function createStandardErrorHandler()
|
||||
{
|
||||
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
|
||||
|
||||
if (!isset($this->nginx_data[$vhosts_filename])) {
|
||||
$this->nginx_data[$vhosts_filename] = '';
|
||||
|
||||
@@ -320,12 +320,15 @@ class Domain
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function triggerLetsEncryptCSRForAliasDestinationDomain(
|
||||
int $aliasDestinationDomainID,
|
||||
int $aliasDestinationDomainID,
|
||||
FroxlorLogger $log
|
||||
) {
|
||||
if ($aliasDestinationDomainID > 0) {
|
||||
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO,
|
||||
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID);
|
||||
$log->logAction(
|
||||
FroxlorLogger::ADM_ACTION,
|
||||
LOG_INFO,
|
||||
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID
|
||||
);
|
||||
$upd_stmt = Database::prepare("UPDATE
|
||||
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
|
||||
SET
|
||||
@@ -349,15 +352,20 @@ class Domain
|
||||
$acmesh = AcmeSh::getAcmeSh();
|
||||
if (file_exists($acmesh)) {
|
||||
$certificate_folder = AcmeSh::getWorkingDirFromEnv($domainname);
|
||||
if (file_exists($certificate_folder)) {
|
||||
$certificate_ecc_folder = AcmeSh::getWorkingDirFromEnv($domainname, true);
|
||||
if (file_exists($certificate_folder) || file_exists($certificate_ecc_folder)) {
|
||||
$params = " --remove -d " . $domainname;
|
||||
if (Settings::Get('system.leecc') > 0) {
|
||||
if (file_exists($certificate_ecc_folder)) {
|
||||
$params .= " --ecc";
|
||||
}
|
||||
// run remove command
|
||||
FileDir::safe_exec($acmesh . $params);
|
||||
// remove certificates directory
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_folder);
|
||||
if (file_exists($certificate_folder)) {
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_folder);
|
||||
} elseif (file_exists($certificate_ecc_folder)) {
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_ecc_folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -31,7 +31,7 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.1.0-beta2';
|
||||
const VERSION = '2.1.0-rc1';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202305240';
|
||||
|
||||
@@ -85,7 +85,7 @@ class Update
|
||||
self::$update_tasks[self::$task_counter]['result'] = 1;
|
||||
break;
|
||||
default:
|
||||
self::$update_tasks[self::$task_counter]['result'] = -1;
|
||||
self::$update_tasks[self::$task_counter]['result'] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class SImExporter
|
||||
public static function export()
|
||||
{
|
||||
$settings_definitions = [];
|
||||
foreach (PhpHelper::loadConfigArrayDir('./actions/admin/settings/')['groups'] as $group) {
|
||||
foreach (PhpHelper::loadConfigArrayDir(Froxlor::getInstallDir() . '/actions/admin/settings/')['groups'] as $group) {
|
||||
foreach ($group['fields'] as $field) {
|
||||
$settings_definitions[$field['settinggroup']][$field['varname']] = $field;
|
||||
}
|
||||
|
||||
@@ -142,8 +142,6 @@ class UI
|
||||
header("X-Content-Security-Policy: " . $csp_content);
|
||||
header("X-WebKit-CSP: " . $csp_content);
|
||||
|
||||
header("X-XSS-Protection: 1; mode=block");
|
||||
|
||||
// Don't allow to load Froxlor in an iframe to prevent i.e. clickjacking
|
||||
header("X-Frame-Options: DENY");
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<install><![CDATA[apt-get install lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
|
||||
@@ -92,7 +92,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<install><![CDATA[apt-get install lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
|
||||
@@ -91,7 +91,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<install><![CDATA[apt-get install lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
|
||||
@@ -100,7 +100,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<install><![CDATA[emerge www-servers/lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
|
||||
@@ -91,7 +91,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<install><![CDATA[apt-get install lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
|
||||
@@ -55,13 +55,16 @@ return [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'mandatory' => true
|
||||
],
|
||||
'directory_password_suggestion' => [
|
||||
'label' => lng('customer.generated_pwd'),
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword()
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'directory_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword(),
|
||||
'readonly' => true
|
||||
]
|
||||
]
|
||||
],
|
||||
'directory_authname' => [
|
||||
'label' => lng('extras.htpasswdauthname'),
|
||||
|
||||
@@ -49,13 +49,16 @@ return [
|
||||
'directory_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off'
|
||||
],
|
||||
'directory_password_suggestion' => [
|
||||
'label' => lng('customer.generated_pwd'),
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword()
|
||||
'autocomplete' => 'off',
|
||||
'next_to' => [
|
||||
'directory_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword(),
|
||||
'readonly' => true
|
||||
]
|
||||
]
|
||||
],
|
||||
'directory_authname' => [
|
||||
'label' => lng('extras.htpasswdauthname'),
|
||||
|
||||
@@ -46,14 +46,16 @@ return [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'mandatory' => true
|
||||
],
|
||||
'mysql_password_suggestion' => [
|
||||
'label' => lng('customer.generated_pwd'),
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword(),
|
||||
'readonly' => true
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'mysql_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword(),
|
||||
'readonly' => true
|
||||
]
|
||||
]
|
||||
],
|
||||
'sendinfomail' => [
|
||||
'label' => lng('customer.sendinfomail'),
|
||||
|
||||
@@ -52,14 +52,16 @@ return [
|
||||
'mysql_password' => [
|
||||
'label' => lng('changepassword.new_password_ifnotempty'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off'
|
||||
],
|
||||
'mysql_password_suggestion' => [
|
||||
'label' => lng('customer.generated_pwd'),
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword(),
|
||||
'readonly' => true
|
||||
'autocomplete' => 'off',
|
||||
'next_to' => [
|
||||
'mysql_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
'type' => 'text',
|
||||
'visible' => (Settings::Get('panel.password_regex') == ''),
|
||||
'value' => Crypt::generatePassword(),
|
||||
'readonly' => true
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -44,7 +44,8 @@ return [
|
||||
'cols' => 100,
|
||||
'rows' => 15,
|
||||
'value' => $result['ssl_cert_file'],
|
||||
'placeholder' => lng('domain.ssl_certificate_placeholder')
|
||||
'placeholder' => lng('domain.ssl_certificate_placeholder'),
|
||||
'mandatory' => true
|
||||
],
|
||||
'ssl_key_file' => [
|
||||
'label' => lng('admin.ipsandports.ssl_key_file_content'),
|
||||
@@ -53,7 +54,8 @@ return [
|
||||
'cols' => 100,
|
||||
'rows' => 15,
|
||||
'value' => $result['ssl_key_file'],
|
||||
'placeholder' => lng('domain.ssl_key_placeholder')
|
||||
'placeholder' => lng('domain.ssl_key_placeholder'),
|
||||
'mandatory' => true
|
||||
],
|
||||
'ssl_cert_chainfile' => [
|
||||
'label' => lng('admin.ipsandports.ssl_cert_chainfile_content'),
|
||||
|
||||
29
lib/init.php
29
lib/init.php
@@ -50,10 +50,12 @@ if (!file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Http\RateLimiter;
|
||||
use Froxlor\Idna\IdnaWrapper;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Language;
|
||||
use Froxlor\PhpHelper;
|
||||
use Froxlor\Settings;
|
||||
@@ -63,7 +65,6 @@ use Froxlor\UI\Linker;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\Install\Update;
|
||||
|
||||
// include MySQL-tabledefinitions
|
||||
require Froxlor::getInstallDir() . '/lib/tables.inc.php';
|
||||
@@ -110,6 +111,24 @@ if (!isset($sql) || !is_array($sql)) {
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show nice note if requested domain is "unknown" to froxlor and thus is being lead to its vhost
|
||||
*/
|
||||
if ($_SERVER['HTTP_HOST'] != Settings::Get('system.hostname') &&
|
||||
!filter_var($_SERVER['HTTP_HOST'], FILTER_VALIDATE_IP) && (
|
||||
empty(Settings::Get('system.froxloraliases')) ||
|
||||
(!empty(Settings::Get('system.froxloraliases')) && !in_array($_SERVER['HTTP_HOST'], array_map('trim', explode(',', Settings::Get('system.froxloraliases')))))
|
||||
)) {
|
||||
// not the froxlor system-hostname, show info page for domains not configured in froxlor
|
||||
$unconfiguredPath = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/templates/misc/unconfigured/index.html');
|
||||
if (file_exists($unconfiguredPath)) {
|
||||
echo file_get_contents($unconfiguredPath);
|
||||
} else {
|
||||
echo "This domain requires configuration via the froxlor server management panel, as it is currently not assigned to any customer.";
|
||||
}
|
||||
die();
|
||||
}
|
||||
|
||||
// set error-handler
|
||||
@set_error_handler([
|
||||
'\\Froxlor\\PhpHelper',
|
||||
@@ -182,9 +201,9 @@ if (@file_exists('templates/' . $theme . '/config.json')) {
|
||||
|
||||
// check for existence of variant in theme
|
||||
if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) || !array_key_exists(
|
||||
$themevariant,
|
||||
$_themeoptions['variants']
|
||||
))) {
|
||||
$themevariant,
|
||||
$_themeoptions['variants']
|
||||
))) {
|
||||
$themevariant = "default";
|
||||
}
|
||||
|
||||
@@ -242,7 +261,7 @@ if (CurrentUser::hasSession()) {
|
||||
$log = FroxlorLogger::getInstanceOf($userinfo);
|
||||
if ((CurrentUser::isAdmin() && AREA != 'admin') || (!CurrentUser::isAdmin() && AREA != 'customer')) {
|
||||
// user tries to access an area not meant for him -> redirect to corresponding index
|
||||
Response::redirectTo((CurrentUser::isAdmin() ? 'admin' : 'customer') . '_index.php', $params);
|
||||
Response::redirectTo((CurrentUser::isAdmin() ? 'admin' : 'customer') . '_index.php');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,3 +92,10 @@ td.text-end {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Table
|
||||
@include color-mode(light) {
|
||||
.table {
|
||||
--bs-table-bg: white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro plain(id, field) %}
|
||||
<input type="text" readonly class="form-control-plaintext" id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}">
|
||||
<input type="text" readonly class="form-control-plaintext" id="{{ id }}" name="{{ id }}" value="{{ field.value|raw|e }}">
|
||||
{% if field.next_to is defined %}
|
||||
{% for nid, nfield in field.next_to %}
|
||||
{% if nfield.next_to_prefix is defined %}
|
||||
@@ -159,9 +159,9 @@
|
||||
{% 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 %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% 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|e }}" 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 }}">
|
||||
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw|e }}">
|
||||
{% endif %}
|
||||
{% if field.next_to is defined %}
|
||||
{% for nid, nfield in field.next_to %}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<td class="w-75" scope="row">{{ check.title }}</td>
|
||||
<td class="col-auto text-end{% if check.result == 0 %} text-success{% endif %}">
|
||||
<span class="d-none d-md-inline">{{ check.result_txt }}</span>
|
||||
{% if check.result == 0 %} <i class="fa-solid fa-check-circle" {% elseif check.result == 2 %}<span class="d-md-none"> ???</span>{% elseif check.result == 1 %}<span class="d-md-none"> !!!</span>
|
||||
{% if check.result == 0 %} <i class="fa-solid fa-check-circle"></i>{% elseif check.result == 2 %}<span class="d-md-none"> ???</span>{% elseif check.result == 1 %}<span class="d-md-none"> !!!</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
|
||||
</svg>
|
||||
<span>Please ask your provider/hoster if you think this is not correct</span>
|
||||
<span>Please ask your provider/hoster if you have any questions.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
55
templates/misc/unconfigured/index.html
Normal file
55
templates/misc/unconfigured/index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>froxlor - Deactivated page</title>
|
||||
<style>
|
||||
:root{--primary:#1872a2;--fonts:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}
|
||||
body{display:flex;flex-direction:column;background:#f8f9fa;color:#4b5563;align-items:center;justify-content:center;font-family:var(--fonts)}
|
||||
main{background:#fff;margin:10% auto 12px;max-width:540px;padding:2rem;box-shadow:4px 8px 16px 0 rgba(0,0,0,.07);border-radius:.375rem}
|
||||
main h2{margin:0}
|
||||
main p{margin-bottom:1.5rem}
|
||||
main p:last-child{margin-bottom:0}
|
||||
main ul{list-style:none;margin-left:-40px}
|
||||
main li{display:flex;align-items:center;margin-bottom:1rem}
|
||||
main .icon{min-width:24px;width:24px;stroke:var(--primary);margin-right:.75rem}
|
||||
code{background:#eee;padding:.1rem .25rem;border-radius:4px;color:rgba(0,0,0,.75)}
|
||||
hr{margin:2rem 0;border:none;border-bottom:solid 1px rgba(0,0,0,.1)}
|
||||
a,a:active,a:visited{color:var(--primary);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
footer{display:flex;align-items:center;margin-top:.5rem}
|
||||
footer .logo{margin-right:.35rem}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root{--primary:#29a2d6}
|
||||
body{background:#212529;color:#f8f9fa}
|
||||
main{background:#343a40}
|
||||
hr{border-color:rgba(0,0,0,.2)}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h2>Domain not configured</h2>
|
||||
<p>
|
||||
This domain requires configuration via the froxlor server management panel, as it is currently not assigned to any customer.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
|
||||
</svg>
|
||||
<span>Please ask your provider/hoster if you have any questions.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer>
|
||||
<img class="logo" src="data:image/png;base64,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" alt="froxlor"/>
|
||||
<small>© 2009-<span id="year"></span> by <a href="https://froxlor.org" rel="external">the froxlor team</a></small>
|
||||
</footer>
|
||||
<script>
|
||||
document.getElementById("year").innerHTML = new Date().getFullYear();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user