Compare commits
28 Commits
2.1.0-beta
...
2.1.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d52f33a50c | ||
|
|
287ad84b18 | ||
|
|
3f1b792f60 | ||
|
|
d94317421d | ||
|
|
7717a82d5c | ||
|
|
ace1651ceb | ||
|
|
1f74bf059c | ||
|
|
c98e912fc5 | ||
|
|
d04a8e7bbf | ||
|
|
d4a940b723 | ||
|
|
0dd20bc29a | ||
|
|
f71ee9f1f2 | ||
|
|
dd61302445 | ||
|
|
0bee1f03de | ||
|
|
a59aaa3dc9 | ||
|
|
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
|
||||
|
||||
@@ -35,6 +35,8 @@ Only reproducable issues on a default/clean setup from the latest stable release
|
||||
- Theoretical attacks without proof of exploitability
|
||||
- Attacks that are the result of a third party library should be reported to the library maintainers
|
||||
- Social engineering
|
||||
- Attacks that require disabling security features or reducing the security level of the environment
|
||||
- Exploits by an admin user itself (privileged user and implicitly trusted)
|
||||
- Reflected file download
|
||||
- Physical attacks
|
||||
- Weak SSL/TLS/SSH algorithms or protocols
|
||||
@@ -45,4 +47,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).
|
||||
|
||||
@@ -43,7 +43,8 @@ return [
|
||||
'settinggroup' => 'spf',
|
||||
'varname' => 'spf_entry',
|
||||
'type' => 'text',
|
||||
'default' => '"v=spf1 a mx -all"',
|
||||
'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
|
||||
'default' => 'v=spf1 a mx -all',
|
||||
'save_method' => 'storeSettingField'
|
||||
]
|
||||
]
|
||||
|
||||
@@ -327,11 +327,12 @@ if ($action == '2fa_entercode') {
|
||||
if ($userinfo['type_2fa'] == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
$secret = $tfa->createSecret();
|
||||
$code = $tfa->getCode($secret);
|
||||
// set code for user
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $code,
|
||||
"d2fa" => $secret,
|
||||
"uid" => $userinfo[$uid]
|
||||
]);
|
||||
// build up & send email
|
||||
|
||||
@@ -391,7 +391,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
|
||||
('admin', 'show_version_footer', '0'),
|
||||
('caa', 'caa_entry', ''),
|
||||
('spf', 'use_spf', '0'),
|
||||
('spf', 'spf_entry', '"v=spf1 a mx -all"'),
|
||||
('spf', 'spf_entry', 'v=spf1 a mx -all'),
|
||||
('dkim', 'dkim_algorithm', 'all'),
|
||||
('dkim', 'dkim_keylength', '1024'),
|
||||
('dkim', 'dkim_servicetype', '0'),
|
||||
@@ -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-rc2'),
|
||||
('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-rc2'),
|
||||
('panel', 'db_version', '202305240');
|
||||
|
||||
|
||||
|
||||
@@ -103,3 +103,23 @@ 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');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc1 to 2.1.0-rc2", false);
|
||||
|
||||
Update::showUpdateStep("Adjusting setting spf_entry");
|
||||
$spf_entry = Settings::Get('spf.spf_entry');
|
||||
if (!preg_match('/^v=spf[a-z0-9:~?\s.-]+$/i', $spf_entry)) {
|
||||
Settings::Set('spf.spf_entry', 'v=spf1 a mx -all');
|
||||
Update::lastStepStatus(1, 'corrected');
|
||||
} else {
|
||||
Update::lastStepStatus(0);
|
||||
}
|
||||
|
||||
Froxlor::updateToVersion('2.1.0-rc2');
|
||||
}
|
||||
|
||||
@@ -349,6 +349,8 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|
||||
if (substr($p_domain, 0, 4) == 'xn--') {
|
||||
Response::standardError('domain_nopunycode', '', true);
|
||||
} elseif (Validate::validate_ip2($p_domain, true, '', true, true)) {
|
||||
Response::standardError('domain_noipaddress', '', true);
|
||||
}
|
||||
|
||||
$idna_convert = new IdnaWrapper();
|
||||
@@ -1667,6 +1669,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|| $hsts_sub != $result['hsts_sub']
|
||||
|| $hsts_preload != $result['hsts_preload']
|
||||
|| $ocsp_stapling != $result['ocsp_stapling']
|
||||
|| $sslenabled != $result['ssl_enabled']
|
||||
) {
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
}
|
||||
@@ -1815,7 +1818,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$update_data['wwwserveralias'] = $wwwserveralias;
|
||||
$update_data['iswildcarddomain'] = $iswildcarddomain;
|
||||
$update_data['phpenabled'] = $phpenabled;
|
||||
$update_data['openbasedir'] = $openbasedir;;
|
||||
$update_data['openbasedir'] = $openbasedir;
|
||||
$update_data['openbasedir_path'] = $openbasedir_path;
|
||||
$update_data['speciallogfile'] = $speciallogfile;
|
||||
$update_data['phpsettingid'] = $phpsettingid;
|
||||
|
||||
@@ -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] = '';
|
||||
|
||||
@@ -187,7 +187,8 @@ class CurrentUser
|
||||
if (self::getField('type_2fa') == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
$secret = $tfa->createSecret();
|
||||
$code = $tfa->getCode($secret);
|
||||
// set code for user
|
||||
$table = TABLE_PANEL_CUSTOMERS;
|
||||
$uid = 'customerid';
|
||||
@@ -197,7 +198,7 @@ class CurrentUser
|
||||
}
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $code,
|
||||
"d2fa" => $secret,
|
||||
"uid" => self::getField($uid)
|
||||
]);
|
||||
// build up & send email
|
||||
|
||||
@@ -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-rc2';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202305240';
|
||||
|
||||
@@ -64,7 +64,7 @@ class IdnaWrapper
|
||||
*/
|
||||
public function encode(string $to_encode): string
|
||||
{
|
||||
$to_encode = $this->isUtf8($to_encode) ? $to_encode : utf8_encode($to_encode);
|
||||
$to_encode = $this->isUtf8($to_encode) ? $to_encode : mb_convert_encoding($to_encode, 'UTF-8');
|
||||
try {
|
||||
return $this->idna_converter->encode($to_encode);
|
||||
} catch (InvalidArgumentException $iae) {
|
||||
|
||||
@@ -304,7 +304,7 @@ class Install
|
||||
throw new Exception(lng('error.invalidip', [$serveripv4]));
|
||||
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || IPTools::is_ipv6($serveripv6) == false)) {
|
||||
throw new Exception(lng('error.invalidip', [$serveripv6]));
|
||||
} elseif (!Validate::validateDomain($servername) && !Validate::validateLocalHostname($servername)) {
|
||||
} elseif (!Validate::validateDomain($servername)) {
|
||||
throw new Exception(lng('install.errors.servernameneedstobevalid'));
|
||||
} elseif (posix_getpwnam($httpuser) === false) {
|
||||
throw new Exception(lng('install.errors.websrvuserdoesnotexist'));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -193,10 +193,14 @@ class Form
|
||||
if (!$do_show) {
|
||||
$fielddata['note'] = lng('serversettings.option_requires_otp');
|
||||
if (!$otp_enabled_system) {
|
||||
$fielddata['disabled'] = true;
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated');
|
||||
} elseif (!$otp_enabled_user) {
|
||||
$fielddata['disabled'] = true;
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user');
|
||||
}
|
||||
// show field in any case
|
||||
$do_show = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ class Validate
|
||||
* Check if the submitted string is a valid domainname
|
||||
*
|
||||
* @param string $domainname The domainname which should be checked.
|
||||
* @param bool $allow_underscore optional if true, allowes the underscore character in a domain label (DKIM etc.)
|
||||
* @param bool $allow_underscore optional if true, allows the underscore character in a domain label (DKIM etc.)
|
||||
*
|
||||
* @return string|boolean the domain-name if the domain is valid, false otherwise
|
||||
*/
|
||||
|
||||
@@ -9,11 +9,18 @@ return [
|
||||
* 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.)
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
'enable_webupdate' => false,
|
||||
|
||||
/**
|
||||
* @todo description
|
||||
* settings that have a major impact on the system or which values are used to be executed with high
|
||||
* privileges on the system require the admin-user to have set up and enabled OTP for the corresponding
|
||||
* account to change these values.
|
||||
* To disable this extra security validation, set the value of this to true
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
'disable_otp_security_check' => false,
|
||||
];
|
||||
|
||||
@@ -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'),
|
||||
|
||||
30
lib/init.php
30
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['SERVER_NAME'] != Settings::Get('system.hostname') &&
|
||||
!filter_var($_SERVER['SERVER_NAME'], FILTER_VALIDATE_IP) && (
|
||||
empty(Settings::Get('system.froxloraliases')) ||
|
||||
(!empty(Settings::Get('system.froxloraliases')) && !in_array($_SERVER['SERVER_NAME'], 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();
|
||||
}
|
||||
}
|
||||
@@ -327,6 +346,7 @@ if (CurrentUser::hasSession()) {
|
||||
if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) {
|
||||
$current_token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
|
||||
if ($current_token != CurrentUser::getField('csrf_token')) {
|
||||
http_response_code(403);
|
||||
Response::dynamicError('CSRF validation failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -920,6 +920,7 @@ return [
|
||||
'dns_duplicate_entry' => 'Eintrag existiert bereits',
|
||||
'dns_notfoundorallowed' => 'Domain nicht gefunden oder keine Berechtigung',
|
||||
'domain_nopunycode' => 'Die Eingabe von Punycode (IDNA) ist nicht notwendig. Die Domain wird automatisch konvertiert.',
|
||||
'domain_noipaddress' => 'Eine IP-Adresse kann nicht als Domain angelegt werden',
|
||||
'dns_record_toolong' => 'Records/Labels können maximal 63 Zeichen lang sein',
|
||||
'noipportgiven' => 'Keine IP/Port angegeben',
|
||||
'nosslippportgiven' => 'Wenn SSL aktiviert ist, muss eine SSL IP/Port angegeben werden',
|
||||
@@ -1308,6 +1309,7 @@ Vielen Dank, Ihr Administrator',
|
||||
'dnsentry_reallydelete' => 'Wollen Sie den DNS-Eintrag wirklich löschen?',
|
||||
'certificate_reallydelete' => 'Wollen Sie diese Zertifikat wirklich löschen?',
|
||||
'cache_reallydelete' => 'Wollen Sie den Cache wirklich leeren?',
|
||||
'please_enter_otp' => 'Bitte 2FA Code eingeben',
|
||||
],
|
||||
'serversettings' => [
|
||||
'session_timeout' => [
|
||||
|
||||
@@ -992,6 +992,7 @@ return [
|
||||
'dns_duplicate_entry' => 'Record already exists',
|
||||
'dns_notfoundorallowed' => 'Domain not found or no permission',
|
||||
'domain_nopunycode' => 'You must not specify punycode (IDNA). The domain will automatically be converted',
|
||||
'domain_noipaddress' => 'Cannot add an IP address as domain',
|
||||
'dns_record_toolong' => 'Records/labels can only be up to 63 characters',
|
||||
'noipportgiven' => 'No IP/port given',
|
||||
'nosslippportgiven' => 'When enabling SSL you need to select a SSL IP/port',
|
||||
@@ -1423,6 +1424,7 @@ Yours sincerely, your administrator',
|
||||
'dnsentry_reallydelete' => 'Do you really want to delete this zone entry?',
|
||||
'certificate_reallydelete' => 'Do you really want to delete this certificate?',
|
||||
'cache_reallydelete' => 'Do you really want to clear the cache?',
|
||||
'please_enter_otp' => 'Please enter 2FA code',
|
||||
],
|
||||
'redirect_desc' => [
|
||||
'rc_default' => 'default',
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"axios": "^1.1.2",
|
||||
"axios": "^1.6.0",
|
||||
"bootstrap": "^5.3.2",
|
||||
"chart.js": "^4.4.0",
|
||||
"jquery": "^3.6.1",
|
||||
@@ -21,6 +21,9 @@
|
||||
"sass": "^1.69.3",
|
||||
"vite": "^4.0.0",
|
||||
"vue": "^3.2.37"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
@@ -577,9 +580,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"axios": "^1.1.2",
|
||||
"axios": "^1.6.0",
|
||||
"bootstrap": "^5.3.2",
|
||||
"chart.js": "^4.4.0",
|
||||
"jquery": "^3.6.1",
|
||||
@@ -21,5 +21,8 @@
|
||||
"sass": "^1.69.3",
|
||||
"vite": "^4.0.0",
|
||||
"vue": "^3.2.37"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
||||
1
templates/Froxlor/assets/js/bootstrap.js
vendored
1
templates/Froxlor/assets/js/bootstrap.js
vendored
@@ -18,3 +18,4 @@ window.Chart = Chart;
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
|
||||
@@ -31,6 +31,9 @@ export default function () {
|
||||
planid: pid
|
||||
},
|
||||
dataType: "json",
|
||||
beforeSend: function(request) {
|
||||
request.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
|
||||
},
|
||||
success: function (json) {
|
||||
for (var i in json) {
|
||||
if (i == 'email_imap' || i == 'email_pop3' || i == 'perlenabled' || i == 'phpenabled' || i == 'dnsenabled' || i == 'logviewenabled') {
|
||||
|
||||
@@ -92,3 +92,10 @@ td.text-end {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Table
|
||||
@include color-mode(light) {
|
||||
.table {
|
||||
--bs-table-bg: white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
position: absolute;
|
||||
top: 2.75rem;
|
||||
z-index: 50;
|
||||
width: 70vh;
|
||||
width: 90vw;
|
||||
max-width: 750px;
|
||||
max-height: 50vh;
|
||||
|
||||
background: $search-bg;
|
||||
|
||||
@@ -159,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 %} {% 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 }}" 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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="card-body d-grid gap-2">
|
||||
<input type="hidden" name="action" value="2fa_verify"/>
|
||||
<input type="hidden" name="send" value="send"/>
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="2faverify">{{ lng('2fa.2fa_verify') }}</button>
|
||||
<button class="btn btn-primary" type="submit" name="2faverify">{{ lng('2fa.2fa_verify') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body d-grid gap-2">
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button>
|
||||
<button class="btn btn-primary" type="submit" name="doremind">{{ lng('login.remind') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body d-grid gap-2">
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="dologin">{{ lng('login.login') }}</button>
|
||||
<button class="btn btn-primary" type="submit" name="dologin">{{ lng('login.login') }}</button>
|
||||
</div>
|
||||
|
||||
{% if get_setting('panel.allow_preset') == '1' %}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body d-grid gap-2">
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button>
|
||||
<button class="btn btn-primary" type="submit" name="doremind">{{ lng('login.remind') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
|
||||
@@ -45,13 +45,13 @@
|
||||
<input type="hidden" name="page" value="{{ page }}"/>
|
||||
<input type="hidden" name="send" value="send"/>
|
||||
{% if userinfo.type_2fa == 0 %}
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="preadd">
|
||||
<button class="btn btn-primary" type="submit" name="preadd">
|
||||
{{ lng('2fa.2fa_add') }}</button>
|
||||
{% elseif userinfo['2fa_unsaved'] is defined and userinfo['2fa_unsaved'] %}
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="add">
|
||||
<button class="btn btn-primary" type="submit" name="add">
|
||||
{{ lng('2fa.2fa_add') }}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-warning rounded-top-0" type="submit" name="delete">
|
||||
<button class="btn btn-warning" type="submit" name="delete">
|
||||
{{ lng('2fa.2fa_delete') }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
|
||||
<input type="hidden" name="page" value="{{ page }}"/>
|
||||
<input type="hidden" name="send" value="changepassword"/>
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="dosave">
|
||||
<button class="btn btn-primary" type="submit" name="dosave">
|
||||
<i class="fa-regular fa-floppy-disk"></i>
|
||||
{{ lng('menue.main.changepassword') }}</button>
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
|
||||
<input type="hidden" name="page" value="{{ page }}"/>
|
||||
<input type="hidden" name="send" value="changetheme"/>
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="dosave">
|
||||
<button class="btn btn-primary" type="submit" name="dosave">
|
||||
<i class="fa-regular fa-floppy-disk"></i>
|
||||
{{ lng('menue.main.changetheme') }}
|
||||
</button>
|
||||
@@ -130,7 +130,7 @@
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
|
||||
<input type="hidden" name="page" value="{{ page }}"/>
|
||||
<input type="hidden" name="send" value="changelanguage"/>
|
||||
<button class="btn btn-primary rounded-top-0" type="submit" name="dosave">
|
||||
<button class="btn btn-primary" type="submit" name="dosave">
|
||||
<i class="fa-regular fa-floppy-disk"></i>
|
||||
{{ lng('menue.main.changelanguage') }}</button>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<a class="navbar-brand me-0 {% if block('heading') %}shadow-sm{% endif %}" href="{{ linker({'section': 'index'}) }}">
|
||||
<img src="{{ header_logo }}" alt="logo" class="header-logo d-inline-block align-text-top ms-md-3">
|
||||
</a>
|
||||
<div class="order-0 order-md-1 d-flex flex-grow-0 flex-md-grow-1" id="navbar">
|
||||
<div class="order-0 order-md-1 d-flex flex-grow-0 flex-md-grow-auto" id="navbar">
|
||||
<ul class="navbar-nav ms-md-auto me-3 me-lg-5">
|
||||
<a class="nav-link d-md-none" data-bs-toggle="collapse" href="#collapseSearch" role="button" aria-expanded="false" aria-controls="collapseSearch">
|
||||
<i class="fa-solid fa-search text-body-secondary"></i>
|
||||
@@ -77,10 +77,10 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="order-1 order-md-0 collapse navbar-collapse" id="collapseSearch">
|
||||
<form class="ms-3 mt-3 ms-lg-5 my-md-0" id="search" method="post">
|
||||
<div class="d-flex align-items-center">
|
||||
<form class="ms-3 mt-3 ms-lg-5 my-md-0 w-100" id="search" method="post">
|
||||
<div class="d-flex align-items-center w-100">
|
||||
<i class="fa-solid fa-search text-body-secondary"></i>
|
||||
<input tabindex="1" class="search-input" title="search" type="search" placeholder="{{ lng('panel.search') }}...">
|
||||
<input tabindex="1" class="search-input w-75" title="search" type="search" placeholder="{{ lng('panel.search') }}...">
|
||||
</div>
|
||||
<div class="search-results-box p-2 shadow" style="display:none;">
|
||||
<div class="search-results list-group-flush"></div>
|
||||
|
||||
@@ -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 - Domain not configured</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