Compare commits

..

41 Commits
2.1.2 ... 2.1.7

Author SHA1 Message Date
Michael Kaufmann
809e8ef45b set version to 2.1.7 for maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-17 08:30:38 +01:00
Michael Kaufmann
0a091a99e8 wrap SetHandler to php-fpm in file-exists check, as we do for customer-domains already
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-17 08:24:56 +01:00
dependabot[bot]
e299fbe665 Bump follow-redirects from 1.15.4 to 1.15.6 (#1244)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-17 08:15:09 +01:00
Michael Kaufmann
67e8b622d8 correctly save pass_authorizationheader flag for php-configs if FCGID is used; correctly add 'FcgidPassHeader' for froxlor-vhost itself if set
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-17 08:13:24 +01:00
Michael Kaufmann
ce509273d4 correctly validate if a symlink is within the customers home-directory if it's not an absolute path; fixes #1242
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-08 09:23:58 +01:00
Michael Kaufmann
bcf588a2e4 correctly disabled ssl-related settings when domain update sets ssl-enbled flag to false; fixes #1241
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-04 16:49:20 +01:00
Michael Kaufmann
f08d540e66 dont escape panel_password_special_char field
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 10:37:42 +01:00
Michael Kaufmann
e06db3d8c5 re-trigger vhost regeneration on tmp. ssl-redirect
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 10:36:41 +01:00
Michael Kaufmann
c5c04ebe9c fix adding/editing domains as customer when php is not enabled for the domain; don't add custom-vhost-content to deactivated domain-vhosts
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-05 22:10:33 +01:00
Michael Kaufmann
c9faa38f6c fix regression bug in 'incorrect top-5 customers' sorting in traffic-overview which leads to incorrect customer-links due to wrong indexing in the array; fixes #1236
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-04 20:34:48 +01:00
Michael Kaufmann
c188f047dc backport UI/Callback fixes from 2.2-dev (main); fixes #1235
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-04 18:53:12 +01:00
Michael Kaufmann
775d50306c set version to 2.1.6 for bugfix/regression release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-03 14:22:33 +01:00
Michael Kaufmann
3821144c3b also fix unittests accordingly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-03 14:08:23 +01:00
Michael Kaufmann
a1da70c221 fix password crypt hash being always evaluated to argon2i as the case always returns true if PASSWORD_ARGON2I is defined but the froxlor setting might be set to another hash leading to a useless password
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-03 13:49:43 +01:00
Michael Kaufmann
bb2db0fed0 set version to 2.1.5 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-02 11:18:48 +01:00
Michael Kaufmann
9680f24640 fix check for allowed_phpconfigs if using mod_php when adding/editing a customer
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-30 19:32:10 +01:00
Michael Kaufmann
c732fbd81b set correct channel for update-check if switching from apt-installed stable/testing to nightly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-26 13:57:51 +01:00
Michael Kaufmann
7980b8d14d create empty dns-server config if no (dns-enabled) domain is determined; fixes #1230
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-24 08:39:16 +01:00
Michael Kaufmann
13e88f5b47 fix incorrect top-5 customers in traffic overview for admins; show manual update command if webupdate is disabled
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-19 09:22:12 +01:00
sro0
031596301b Check for argon2 support before using constant PASSWORD_ARGON2X (#1228) 2024-01-16 21:40:03 +01:00
Michael Kaufmann
b34ab45746 disable pam auth in dovecot for debian bookworm (like the other distros do it)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-07 09:06:15 +01:00
Michael Kaufmann
dbf83c6f24 build nightly only from main branch #2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 15:01:52 +01:00
Michael Kaufmann
4cb974839c build nightly only from main branch
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 14:54:23 +01:00
Michael Kaufmann
1fa714ef2c add v2.1 branch to security md as currently supported as well as update main-branch version; add field.disabled attribute to formfield-input-template
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 14:48:41 +01:00
Michael Kaufmann
63bbcd4e00 add missing language string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 14:48:28 +01:00
Michael Kaufmann
49d67d7c27 set version to 2.1.4 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 14:33:45 +01:00
Michael Kaufmann
7cc4c9fedb possibility to specify sender address for froxlor as the admin-email address, custom or empty for system-default; fixes #1217
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-04 08:29:19 +01:00
Michael Kaufmann
afd110a6ed use correct regex for dnscheck-resolver; fixes #1220
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-03 07:35:28 +01:00
Michael Kaufmann
7cdf6c8d64 don't output ipv6 in brackets for system.ipaddress setting as the brackets will be added to the value resulting in an invalid mysql-access-host; fixes #1215
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-01 17:04:02 +01:00
Michael Kaufmann
60621da243 dont use deprecated 'mysql_native_password' for mysql8; fixes #1214
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-01 12:24:45 +01:00
Michael Kaufmann
96ccdda304 use different language string for password-placeholder when adding a new customer; fixes #1216
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-31 18:01:39 +01:00
Michael Kaufmann
4073984fd7 traffic-cron: check for standardsubdomain to be in the domainlist array to avoid undefined index if e.g. an alias was set to the standardsubdomain
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-31 16:16:01 +01:00
Michael Kaufmann
ea31c8a64d fix font-color in apcu info; clear updatecheck-cache for nightly users
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-30 23:30:52 +01:00
Michael Kaufmann
832ee07e0e Don't show stats-icon for domains with redirect; hide goaccess output in traffic cron and keepalive database connection for long-running log-analysis; use same certificate-file if child-domain inherits the parentdomains certificate data (avoid possible http 421 Misdirected Request)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-29 10:15:45 +01:00
Michael Kaufmann
b542b140c6 set version to 2.1.3 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-22 14:33:11 +01:00
Michael Kaufmann
ac89fc7120 adjust order of css files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-22 14:30:23 +01:00
Michael Kaufmann
150858485d include custom.css from config.json if preset correctly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-21 10:59:15 +01:00
Michael Kaufmann
e7810e2066 correctly merge fielddetails with prefetched-formfielddata in form-processing
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-20 09:39:01 +01:00
Michael Kaufmann
4879446567 domains in php-configs are not sortable
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-19 17:17:51 +01:00
Michael Kaufmann
43eff78088 use panel.password_min_length setting for Froxlor.generatePassword() default length parameter; allow '::1' as valid mysql localhost value; wrapper to clean output for cli installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-16 20:20:58 +01:00
Gamerboy59
55a2ae3801 Add manual_config install var to cli (#1208)
Make the manual_config var, which is available to the web installer, usuable for the cli installer too. If manual_config is set to true skip else (not set or false) proceed with auto config.
2023-12-16 20:13:58 +01:00
58 changed files with 300 additions and 189 deletions

View File

@@ -53,7 +53,7 @@ jobs:
name: Create nightly/testing tarball
runs-on: ubuntu-latest
needs: froxlor
if: ${{ github.event_name == 'push' }}
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- name: Checkout

View File

@@ -10,7 +10,8 @@ With that, good luck hacking us ;)
## Supported versions
- ️✅ **2.1.x** (`main` git-branch)
- ️✅ **2.2.x** (`main` git-branch)
- ️✅ **2.1.x** (`v2.1` git-branch)
- ❌ 2.0.x (`2.0.x`-tags)
- ❌ 0.10.x (`0.10.x`-tags)
- ❌ other git-branches

View File

@@ -248,7 +248,7 @@ return [
'settinggroup' => 'system',
'varname' => 'le_domain_dnscheck_resolver',
'type' => 'text',
'string_regexp' => '/^(([0-9]+ [a-z0-9\-\._]+, ?)*[0-9]+ [a-z0-9\-\._]+)?$/i',
'string_type' => 'validate_ip',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField'

View File

@@ -57,7 +57,7 @@
"pear/net_dns2": "^1.5",
"amnuts/opcache-gui": "^3.4",
"league/commonmark": "^2.4"
},
},
"require-dev": {
"phpunit/phpunit": "^9",
"ext-pcntl": "*",

View File

@@ -686,7 +686,7 @@ opcache.validate_timestamps'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
('panel', 'adminmail', 'ADMIN_MAIL'),
('panel', 'phpmyadmin_url', ''),
('panel', 'webmail_url', ''),
('panel', 'webftp_url', ''),
@@ -726,7 +726,7 @@ opcache.validate_timestamps'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.1.2'),
('panel', 'version', '2.1.7'),
('panel', 'db_version', '202312120');

View File

@@ -269,3 +269,28 @@ if (Froxlor::isFroxlorVersion('2.1.1')) {
Update::showUpdateStep("Updating from 2.1.1 to 2.1.2", false);
Froxlor::updateToVersion('2.1.2');
}
if (Froxlor::isFroxlorVersion('2.1.2')) {
Update::showUpdateStep("Updating from 2.1.2 to 2.1.3", false);
Froxlor::updateToVersion('2.1.3');
}
if (Froxlor::isFroxlorVersion('2.1.3')) {
Update::showUpdateStep("Updating from 2.1.3 to 2.1.4", false);
Froxlor::updateToVersion('2.1.4');
}
if (Froxlor::isFroxlorVersion('2.1.4')) {
Update::showUpdateStep("Updating from 2.1.4 to 2.1.5", false);
Froxlor::updateToVersion('2.1.5');
}
if (Froxlor::isFroxlorVersion('2.1.5')) {
Update::showUpdateStep("Updating from 2.1.5 to 2.1.6", false);
Froxlor::updateToVersion('2.1.6');
}
if (Froxlor::isFroxlorVersion('2.1.6')) {
Update::showUpdateStep("Updating from 2.1.6 to 2.1.7", false);
Froxlor::updateToVersion('2.1.7');
}

View File

@@ -400,7 +400,10 @@ class Customers extends ApiCommand implements ResourceEntity
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
// only required if not using mod_php
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
}
$allowed_mysqlserver = array();
@@ -1114,7 +1117,10 @@ class Customers extends ApiCommand implements ResourceEntity
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
// only required if not using mod_php
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
}
// add permission for allowed mysql usage if customer was not allowed to use mysql prior

View File

@@ -519,7 +519,8 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = '-1';
}
} else {
$phpenabled = '1';
// set default to whether the customer has php enabled or not
$phpenabled = $customer['phpenabled'];
$openbasedir = '1';
if ((int)Settings::Get('phpfpm.enabled') == 1) {
@@ -1527,13 +1528,12 @@ class Domains extends ApiCommand implements ResourceEntity
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports) || !$sslenabled) {
$ssl_redirect = 0;
$letsencrypt = 0;
$http2 = 0;
// we need this for the json_encode
// if ssl is disabled or no ssl-ip/port exists
$ssl_ipandports[] = -1;
// act like $remove_ssl_ipandport
$ssl_ipandports = [];
// HSTS
$hsts_maxage = 0;

View File

@@ -157,10 +157,10 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
// prefix hash-algo
switch (Settings::Get('system.passwordcryptfunc')) {
case PASSWORD_ARGON2I:
case 'argon2i':
$cpPrefix = '{ARGON2I}';
break;
case PASSWORD_ARGON2ID:
case 'argon2id':
$cpPrefix = '{ARGON2ID}';
break;
default:
@@ -404,10 +404,10 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$password = Crypt::validatePassword($password, true);
// prefix hash-algo
switch (Settings::Get('system.passwordcryptfunc')) {
case PASSWORD_ARGON2I:
case 'argon2i':
$cpPrefix = '{ARGON2I}';
break;
case PASSWORD_ARGON2ID:
case 'argon2id':
$cpPrefix = '{ARGON2ID}';
break;
default:

View File

@@ -259,14 +259,15 @@ class Froxlor extends ApiCommand
* returns a random password based on froxlor settings for min-length, included characters, etc.
*
* @param int $length
* optional length of password, defaults to 10
* optional length of password, defaults to 0 (panel.password_min_length)
*
* @access admin, customer
* @return string
* @throws Exception
*/
public function generatePassword()
public function generatePassword(): string
{
$length = $this->getParam('length', true, 10);
$length = $this->getParam('length', true, 0);
return $this->response(Crypt::generatePassword($length));
}

View File

@@ -222,8 +222,8 @@ class PhpSettings extends ApiCommand implements ResourceEntity
* optional request terminate timeout if FPM is used, default is '60s'
* @param string $phpfpm_reqslowtimeout
* optional request slowlog timeout if FPM is used, default is '5s'
* @param bool $phpfpm_pass_authorizationheader
* optional whether to pass authorization header to webserver if FPM is used, default is 0 (false)
* @param bool $pass_authorizationheader
* optional whether to pass authorization header to webserver if FPM/FCGID is used, default is 0 (false)
* @param bool $override_fpmconfig
* optional whether to override fpm-daemon-config value for the following settings if FPM is used,
* default is 0 (false)
@@ -276,7 +276,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = $this->getBoolParam('phpfpm_enable_slowlog', true, 0);
$fpm_reqtermtimeout = $this->getParam('phpfpm_reqtermtimeout', true, "60s");
$fpm_reqslowtimeout = $this->getParam('phpfpm_reqslowtimeout', true, "5s");
$fpm_pass_authorizationheader = $this->getBoolParam('phpfpm_pass_authorizationheader', true, 0);
$pass_authorizationheader = $this->getBoolParam('pass_authorizationheader', true, 0);
$override_fpmconfig = $this->getBoolParam('override_fpmconfig', true, 0);
$def_fpmconfig = $this->apiCall('FpmDaemons.get', [
@@ -312,7 +312,6 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = 0;
$fpm_reqtermtimeout = 0;
$fpm_reqslowtimeout = 0;
$fpm_pass_authorizationheader = 0;
$override_fpmconfig = 0;
} elseif (Settings::Get('phpfpm.enabled') == 1) {
$fpm_reqtermtimeout = Validate::validate($fpm_reqtermtimeout, 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', [], true);
@@ -377,7 +376,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
'fpmreqslow' => $fpm_reqslowtimeout,
'phpsettings' => $phpsettings,
'fpmsettingid' => $fpm_config_id,
'fpmpassauth' => $fpm_pass_authorizationheader,
'fpmpassauth' => $pass_authorizationheader,
'ofc' => $override_fpmconfig,
'pm' => $pmanager,
'max_children' => $max_children,
@@ -464,7 +463,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
* optional request terminate timeout if FPM is used, default is '60s'
* @param string $phpfpm_reqslowtimeout
* optional request slowlog timeout if FPM is used, default is '5s'
* @param bool $phpfpm_pass_authorizationheader
* @param bool $pass_authorizationheader
* optional whether to pass authorization header to webserver if FPM is used, default is 0 (false)
* @param bool $override_fpmconfig
* optional whether to override fpm-daemon-config value for the following settings if FPM is used,
@@ -516,7 +515,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = $this->getBoolParam('phpfpm_enable_slowlog', true, $result['fpm_slowlog']);
$fpm_reqtermtimeout = $this->getParam('phpfpm_reqtermtimeout', true, $result['fpm_reqterm']);
$fpm_reqslowtimeout = $this->getParam('phpfpm_reqslowtimeout', true, $result['fpm_reqslow']);
$fpm_pass_authorizationheader = $this->getBoolParam('phpfpm_pass_authorizationheader', true, $result['pass_authorizationheader']);
$pass_authorizationheader = $this->getBoolParam('pass_authorizationheader', true, $result['pass_authorizationheader']);
$override_fpmconfig = $this->getBoolParam('override_fpmconfig', true, $result['override_fpmconfig']);
$pmanager = $this->getParam('pm', true, $result['pm']);
$max_children = $this->getParam('max_children', true, $result['max_children']);
@@ -548,7 +547,6 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = 0;
$fpm_reqtermtimeout = 0;
$fpm_reqslowtimeout = 0;
$fpm_pass_authorizationheader = 0;
$override_fpmconfig = 0;
} elseif (Settings::Get('phpfpm.enabled') == 1) {
$fpm_reqtermtimeout = Validate::validate($fpm_reqtermtimeout, 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', [], true);
@@ -614,7 +612,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
'fpmreqslow' => $fpm_reqslowtimeout,
'phpsettings' => $phpsettings,
'fpmsettingid' => $fpm_config_id,
'fpmpassauth' => $fpm_pass_authorizationheader,
'fpmpassauth' => $pass_authorizationheader,
'ofc' => $override_fpmconfig,
'pm' => $pmanager,
'max_children' => $max_children,

View File

@@ -296,21 +296,24 @@ class SubDomains extends ApiCommand implements ResourceEntity
// assign default config
$phpsid_result['phpsettingid'] = 1;
}
// check whether the customer has chosen its own php-config
if ($phpsettingid > 0 && $phpsettingid != $phpsid_result['phpsettingid']) {
$phpsid_result['phpsettingid'] = intval($phpsettingid);
}
$allowed_phpconfigs = $customer['allowed_phpconfigs'];
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = json_decode($allowed_phpconfigs, true);
} else {
$allowed_phpconfigs = [];
}
// only with fcgid/fpm enabled will it be possible to select a php-setting
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
if (!in_array($phpsid_result['phpsettingid'], $allowed_phpconfigs)) {
Response::standardError('notallowedphpconfigused', '', true);
if ($domain_check['phpenabled'] == 1) {
// check whether the customer has chosen its own php-config
if ($phpsettingid > 0 && $phpsettingid != $phpsid_result['phpsettingid']) {
$phpsid_result['phpsettingid'] = intval($phpsettingid);
}
$allowed_phpconfigs = $customer['allowed_phpconfigs'];
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = json_decode($allowed_phpconfigs, true);
} else {
$allowed_phpconfigs = [];
}
// only with fcgid/fpm enabled will it be possible to select a php-setting
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
if (!in_array($phpsid_result['phpsettingid'], $allowed_phpconfigs)) {
Response::standardError('notallowedphpconfigused', '', true);
}
}
}
@@ -797,7 +800,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$allowed_phpconfigs = [];
}
// only with fcgid/fpm enabled will it be possible to select a php-setting
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
if ((int)$result['phpenabled'] == 1 && ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1)) {
if (!in_array($phpsettingid, $allowed_phpconfigs)) {
Response::standardError('notallowedphpconfigused', '', true);
}

View File

@@ -402,7 +402,7 @@ final class ConfigServices extends CliCommand
case "file":
if (array_key_exists('content', $action)) {
$output->writeln('<comment>Creating file "' . $action['name'] . '"</>');
file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr)));
file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr)) . PHP_EOL);
} elseif (array_key_exists('subcommands', $action)) {
foreach ($action['subcommands'] as $fileaction) {
if (array_key_exists('execute', $fileaction) && $fileaction['execute'] == "pre") {
@@ -411,7 +411,7 @@ final class ConfigServices extends CliCommand
exec(strtr($fileaction['content'], $replace_arr));
} elseif ($fileaction['type'] == 'file') {
$output->writeln('<comment>Creating file "' . $fileaction['name'] . '"</>');
file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr)));
file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr)) . PHP_EOL);
}
}
}
@@ -514,6 +514,7 @@ final class ConfigServices extends CliCommand
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup'),
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
'<ADMIN_MAIL>' => Settings::Get('panel.adminmail'),
];
}
}

View File

@@ -211,7 +211,7 @@ final class InstallCommand extends Command
$ask_field = false;
}
$fielddata['value'] = $this->formfielddata[$fieldname] ?? ($fielddata['value'] ?? null);
$fielddata['label'] = strip_tags(str_replace("<br>", " ", $fielddata['label']));
$fielddata['label'] = $this->cliTextFormat($fielddata['label'], " ");
if ($ask_field) {
if ($fielddata['type'] == 'password') {
$this->formfielddata[$fieldname] = $this->io->askHidden($fielddata['label'], function ($value) use ($fielddata) {
@@ -267,14 +267,16 @@ final class InstallCommand extends Command
case 4:
$section = $inst->formfield['install']['sections']['step' . $step] ?? [];
$this->io->section($section['title']);
$this->io->note($section['description']);
$this->io->note($this->cliTextFormat($section['description']));
$cmdfield = $section['fields']['system'];
$this->io->success([
$cmdfield['label'],
$cmdfield['value']
]);
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
passthru($cmdfield['value']);
if (!isset($decoded_input['manual_config']) || (bool)$decoded_input['manual_config'] === false) {
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
passthru($cmdfield['value']);
}
}
break;
}
@@ -305,7 +307,7 @@ final class InstallCommand extends Command
$json_output = [];
foreach ($fields['install']['sections'] as $section => $section_fields) {
foreach ($section_fields['fields'] as $name => $field) {
if ($name == 'system' || $name == 'manual_config' || $name == 'target_servername') {
if ($name == 'system' || $name == 'target_servername') {
continue;
}
if ($field['type'] == 'text' || $field['type'] == 'email') {
@@ -318,7 +320,7 @@ final class InstallCommand extends Command
$fieldval = '******';
} elseif ($field['type'] == 'select') {
$fieldval = implode("|", array_keys($field['select_var']));
} else if ($field['type'] == 'checkbox') {
} elseif ($field['type'] == 'checkbox') {
$fieldval = "1|0";
} else {
$fieldval = "?";
@@ -346,4 +348,10 @@ final class InstallCommand extends Command
curl_close($ch);
fclose($fp);
}
private function cliTextFormat(string $text, string $nl_char = "\n"): string
{
$text = str_replace(['<br>', '<br/>', '<br />'], [$nl_char, $nl_char, $nl_char], $text);
return strip_tags($text);
}
}

View File

@@ -263,6 +263,8 @@ final class MasterCron extends CliCommand
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
// reload crond as shell users might use crontab and the user is only known to crond if reloaded
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.crondreload')));
return;
}
@@ -275,6 +277,8 @@ final class MasterCron extends CliCommand
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
]);
// reload crond as shell users might use crontab and the user is only known to crond if reloaded
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.crondreload')));
}
}
}

View File

@@ -110,7 +110,7 @@ final class UpdateCommand extends CliCommand
$newversionavail = true;
$output->writeln('<comment>' . $text . '</>');
$result = self::SUCCESS;
} else if ($aucheck < 0 || $aucheck > 1) {
} elseif ($aucheck < 0 || $aucheck > 1) {
if ($input->getOption('integer-return')) {
$output->write(-1);
return self::INVALID;

View File

@@ -117,7 +117,7 @@ class ConfigDisplay
'<SQL_UNPRIVILEGED_PASSWORD>' => 'FROXLOR_MYSQL_PASSWORD',
'<SQL_DB>' => $sql['db'],
'<SQL_HOST>' => $sql['host'],
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
'<SQL_SOCKET>' => $sql['socket'] ?? null,
'<SERVERNAME>' => Settings::Get('system.hostname'),
'<SERVERIP>' => Settings::Get('system.ipaddress'),
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
@@ -127,12 +127,15 @@ class ConfigDisplay
'<VIRTUAL_GID_MAPS>' => Settings::Get('system.vmail_gid'),
'<SSLPROTOCOLS>' => (Settings::Get('system.use_ssl') == '1') ? 'imaps pop3s' : '',
'<CUSTOMER_TMP>' => FileDir::makeCorrectDir($customer_tmpdir),
'<BASE_PATH>' => FileDir::makeCorrectDir(Froxlor::getInstallDir()),
'<BASE_PATH>' => Froxlor::getInstallDir(),
'<BIND_CONFIG_PATH>' => FileDir::makeCorrectDir(Settings::Get('system.bindconf_directory')),
'<WEBSERVER_RELOAD_CMD>' => Settings::Get('system.apachereload_command'),
'<CUSTOMER_LOGS>' => FileDir::makeCorrectDir(Settings::Get('system.logfiles_directory')),
'<FPM_IPCDIR>' => FileDir::makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir')),
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup')
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup'),
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
'<ADMIN_MAIL>' => Settings::Get('panel.adminmail'),
];
$commands_pre = "";

View File

@@ -55,18 +55,17 @@ class Bind extends DnsBase
$domains = $this->getDomainList();
if (empty($domains)) {
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
return;
}
$this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
foreach ($domains as $domain) {
if ($domain['is_child']) {
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
continue;
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, not creating any zones...');
$this->bindconf_file = '';
} else {
$this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
foreach ($domains as $domain) {
if ($domain['is_child']) {
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
continue;
}
$this->walkDomainList($domain, $domains);
}
$this->walkDomainList($domain, $domains);
}
$bindconf_file_handler = fopen(FileDir::makeCorrectFile(Settings::Get('system.bindconf_directory') . '/froxlor_bind.conf'), 'w');

View File

@@ -45,18 +45,16 @@ class PowerDNS extends DnsBase
$this->clearZoneTables($domains);
if (empty($domains)) {
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
return;
}
foreach ($domains as $domain) {
if ($domain['is_child']) {
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
continue;
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, not creating any zones...');
} else {
foreach ($domains as $domain) {
if ($domain['is_child']) {
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
continue;
}
$this->walkDomainList($domain, $domains);
}
$this->walkDomainList($domain, $domains);
}
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'PowerDNS database updated');
$this->reloadDaemon();
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Task4 finished');

View File

@@ -25,19 +25,21 @@
namespace Froxlor\Cron\Http;
use Froxlor\Froxlor;
use Froxlor\Cron\Http\Php\PhpInterface;
use Froxlor\Cron\TaskId;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Http\Directory;
use Froxlor\Http\Statistics;
use Froxlor\PhpHelper;
use Froxlor\Settings;
use Froxlor\Validate\Validate;
use Froxlor\System\Cronjob;
use Froxlor\System\Crypt;
use Froxlor\Validate\Validate;
use PDO;
class Apache extends HttpConfigBase
@@ -133,6 +135,7 @@ class Apache extends HttpConfigBase
if (Settings::Get('system.le_froxlor_enabled') && ($this->froxlorVhostHasLetsEncryptCert() == false || $this->froxlorVhostLetsEncryptNeedsRenew())) {
$this->virtualhosts_data[$vhosts_filename] .= '# temp. disabled ssl-redirect due to Let\'s Encrypt certificate generation.' . PHP_EOL;
$is_redirect = false;
Cronjob::inserttask(TaskId::REBUILD_VHOST);
} else {
$_sslport = $this->checkAlternativeSslPort();
@@ -159,7 +162,7 @@ class Apache extends HttpConfigBase
if (Settings::Get('system.froxlordirectlyviahostname')) {
$relpath = "/";
} else {
$relpath = "/".basename(Froxlor::getInstallDir());
$relpath = "/" . basename(Froxlor::getInstallDir());
}
// protect lib/userdata.inc.php
$this->virtualhosts_data[$vhosts_filename] .= ' <Directory "' . rtrim($relpath, "/") . '/lib/">' . "\n";
@@ -205,7 +208,9 @@ class Apache extends HttpConfigBase
];
$php = new PhpInterface($domain);
$phpconfig = $php->getPhpConfig(Settings::Get('system.mod_fcgid_defaultini_ownvhost'));
if ($phpconfig['pass_authorizationheader'] == '1') {
$this->virtualhosts_data[$vhosts_filename] .= ' FcgidPassHeader Authorization' . "\n";
}
$starter_filename = FileDir::makeCorrectFile($configdir . '/php-fcgi-starter');
$this->virtualhosts_data[$vhosts_filename] .= ' SuexecUserGroup "' . Settings::Get('system.mod_fcgid_httpuser') . '" "' . Settings::Get('system.mod_fcgid_httpgroup') . '"' . "\n";
$this->virtualhosts_data[$vhosts_filename] .= ' <Directory "' . $mypath . '">' . "\n";
@@ -276,7 +281,9 @@ class Apache extends HttpConfigBase
// start block, cut off last pipe and close block
$filesmatch = '(' . str_replace(".", "\.", substr($filesmatch, 0, -1)) . ')';
$this->virtualhosts_data[$vhosts_filename] .= ' <FilesMatch \.' . $filesmatch . '$>' . "\n";
$this->virtualhosts_data[$vhosts_filename] .= ' SetHandler proxy:unix:' . $php->getInterface()->getSocketFile() . '|fcgi://localhost' . "\n";
$this->virtualhosts_data[$vhosts_filename] .= ' <If "-f %{SCRIPT_FILENAME}">' . "\n";
$this->virtualhosts_data[$vhosts_filename] .= ' SetHandler proxy:unix:' . $php->getInterface()->getSocketFile() . '|fcgi://localhost' . "\n";
$this->virtualhosts_data[$vhosts_filename] .= ' </If>' . "\n";
$this->virtualhosts_data[$vhosts_filename] .= ' </FilesMatch>' . "\n";
if ($phpconfig['pass_authorizationheader'] == '1') {
$this->virtualhosts_data[$vhosts_filename] .= ' <Directory "' . $mypath . '">' . "\n";
@@ -842,24 +849,26 @@ class Apache extends HttpConfigBase
}
$vhost_content .= $this->getLogfiles($domain);
if ($domain['specialsettings'] != '' && ($ssl_vhost == false || ($ssl_vhost == true && $domain['include_specialsettings'] == 1))) {
$vhost_content .= $this->processSpecialConfigTemplate($domain['specialsettings'], $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
if ($this->deactivated == false) {
if ($domain['specialsettings'] != '' && ($ssl_vhost == false || ($ssl_vhost == true && $domain['include_specialsettings'] == 1))) {
$vhost_content .= $this->processSpecialConfigTemplate($domain['specialsettings'], $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
if ($domain['ssl_specialsettings'] != '' && $ssl_vhost == true) {
$vhost_content .= $this->processSpecialConfigTemplate($domain['ssl_specialsettings'], $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
if ($domain['ssl_specialsettings'] != '' && $ssl_vhost == true) {
$vhost_content .= $this->processSpecialConfigTemplate($domain['ssl_specialsettings'], $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
if ($_vhost_content != '') {
$vhost_content .= $_vhost_content;
}
if ($_vhost_content != '') {
$vhost_content .= $_vhost_content;
}
if (Settings::Get('system.default_vhostconf') != '' && ($ssl_vhost == false || ($ssl_vhost == true && Settings::Get('system.include_default_vhostconf') == 1))) {
$vhost_content .= $this->processSpecialConfigTemplate(Settings::Get('system.default_vhostconf'), $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
if (Settings::Get('system.default_vhostconf') != '' && ($ssl_vhost == false || ($ssl_vhost == true && Settings::Get('system.include_default_vhostconf') == 1))) {
$vhost_content .= $this->processSpecialConfigTemplate(Settings::Get('system.default_vhostconf'), $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
if (Settings::Get('system.default_sslvhostconf') != '' && $ssl_vhost == true) {
$vhost_content .= $this->processSpecialConfigTemplate(Settings::Get('system.default_sslvhostconf'), $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
if (Settings::Get('system.default_sslvhostconf') != '' && $ssl_vhost == true) {
$vhost_content .= $this->processSpecialConfigTemplate(Settings::Get('system.default_sslvhostconf'), $domain, $domain['ip'], $domain['port'], $ssl_vhost) . "\n";
}
}
}
@@ -964,8 +973,8 @@ class Apache extends HttpConfigBase
if ($domain['openbasedir'] == '1') {
if ($domain['openbasedir_path'] == '1' || strstr($domain['documentroot'], ":") !== false) {
$_phpappendopenbasedir = Domain::appendOpenBasedirPath($domain['customerroot'], true);
} else if ($domain['openbasedir_path'] == '2' && strpos(dirname($domain['documentroot']).'/', $domain['customerroot']) !== false) {
$_phpappendopenbasedir = Domain::appendOpenBasedirPath(dirname($domain['documentroot']).'/', true);
} else if ($domain['openbasedir_path'] == '2' && strpos(dirname($domain['documentroot']) . '/', $domain['customerroot']) !== false) {
$_phpappendopenbasedir = Domain::appendOpenBasedirPath(dirname($domain['documentroot']) . '/', true);
} else {
$_phpappendopenbasedir = Domain::appendOpenBasedirPath($domain['documentroot'], true);
}
@@ -1013,10 +1022,10 @@ class Apache extends HttpConfigBase
}
$statDocroot = FileDir::makeCorrectFile($domain['customerroot'] . '/' . $statTool . $statDomain);
$stats_text .= ' Alias /'.$statTool.' "' . $statDocroot . '"' . "\n";
$stats_text .= ' Alias /' . $statTool . ' "' . $statDocroot . '"' . "\n";
// awstats special requirement for icons
if ($statTool == 'awstats') {
$stats_text .= ' Alias /awstats-icon "' . FileDir::makeCorrectDir(Settings::Get('system.awstats_icons')) . '"' . "\n";
$stats_text .= ' Alias /awstats-icon "' . FileDir::makeCorrectDir(Settings::Get('system.awstats_icons')) . '"' . "\n";
}
return $stats_text;

View File

@@ -43,23 +43,29 @@ class DomainSSL
* domain-array as reference so we can set the corresponding array-indices
*
* @return null
* @throws \Exception
*/
public function setDomainSSLFilesArray(array &$domain = null)
{
// check if the domain itself has a certificate defined
$dom_certs_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :domid
SELECT s.*, d.domain
FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON d.id = s.domainid
WHERE s.`domainid` = :domid
");
$dom_certs = Database::pexecute_first($dom_certs_stmt, [
'domid' => $domain['id']
]);
$parent_certificate = false;
if (!is_array($dom_certs) || !isset($dom_certs['ssl_cert_file']) || $dom_certs['ssl_cert_file'] == '') {
// maybe its parent?
if (isset($domain['parentdomainid']) && $domain['parentdomainid'] != 0) {
$dom_certs = Database::pexecute_first($dom_certs_stmt, [
'domid' => $domain['parentdomainid']
]);
$parent_certificate = true;
}
}
@@ -73,8 +79,8 @@ class DomainSSL
}
// make correct files for the certificates
$ssl_files = [
'ssl_cert_file' => FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '.crt'),
'ssl_key_file' => FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '.key')
'ssl_cert_file' => FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '.crt'),
'ssl_key_file' => FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '.key')
];
if (!$this->validateCertificate($dom_certs)) {
@@ -93,19 +99,19 @@ class DomainSSL
$ssl_files['ssl_cert_chainfile'] = '';
// set them if they are != empty
if ($dom_certs['ssl_ca_file'] != '') {
$ssl_files['ssl_ca_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '_CA.pem');
$ssl_files['ssl_ca_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '_CA.pem');
}
if ($dom_certs['ssl_cert_chainfile'] != '') {
if (Settings::Get('system.webserver') == 'nginx') {
// put ca.crt in my.crt, as nginx does not support a separate chain file.
$dom_certs['ssl_cert_file'] = trim($dom_certs['ssl_cert_file']) . "\n" . trim($dom_certs['ssl_cert_chainfile']) . "\n";
} else {
$ssl_files['ssl_cert_chainfile'] = FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '_chain.pem');
$ssl_files['ssl_cert_chainfile'] = FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '_chain.pem');
}
}
// will only be generated to be used externally, froxlor does not need this
if ($dom_certs['ssl_fullchain_file'] != '') {
$ssl_files['ssl_fullchain_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '_fullchain.pem');
$ssl_files['ssl_fullchain_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '_fullchain.pem');
}
// create them on the filesystem
foreach ($ssl_files as $type => $filename) {
@@ -131,7 +137,7 @@ class DomainSSL
return;
}
private function validateCertificate($dom_certs = [])
private function validateCertificate($dom_certs = []): bool
{
return openssl_x509_check_private_key($dom_certs['ssl_cert_file'], $dom_certs['ssl_key_file']);
}

View File

@@ -521,6 +521,8 @@ EOC;
self::runAcmeSh($certrow, $domains, $cronlog, $do_force);
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect");
// we need another reconfigure in order to get the certificate
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
}
}

View File

@@ -47,7 +47,7 @@ class TrafficCron extends FroxlorCron
public static function run()
{
self::runFork([self::class, 'handle']);
self::runFork([self::class, 'handle'], [true]);
}
public static function handle()
@@ -163,14 +163,14 @@ class TrafficCron extends FroxlorCron
if (isset($domainlist[$row['customerid']]) && is_array($domainlist[$row['customerid']]) && count($domainlist[$row['customerid']]) != 0) {
// Examining which caption to use for default webalizer stats...
if ($row['standardsubdomain'] != '0') {
if ($row['standardsubdomain'] != '0' && isset($domainlist[$row['customerid']][$row['standardsubdomain']])) {
// ... of course we'd prefer to use the standardsubdomain ...
$caption = $domainlist[$row['customerid']][$row['standardsubdomain']];
} else {
// ... but if there is no standardsubdomain, we have to use the loginname ...
$caption = $row['loginname'];
// ... which results in non-usable links to files in the stats, so lets have a look if we find a domain which is not speciallogfiledomain
// ... which results in non-usable links to files in the stats, so let's have a look if we find a domain which is not speciallogfiledomain
foreach ($domainlist[$row['customerid']] as $domainid => $domain) {
if (!isset($speciallogfile_domainlist[$row['customerid']]) || !isset($speciallogfile_domainlist[$row['customerid']][$domainid])) {
$caption = $domain;
@@ -193,6 +193,8 @@ class TrafficCron extends FroxlorCron
} else {
$httptraffic += floatval(self::callWebalizerGetTraffic($row['loginname'] . '-' . $domain, $row['documentroot'] . '/webalizer/' . $domain . '/', $domain, $domainlist[$row['customerid']]));
}
// kind of a keep-alive-call as this unsets the link which leads to a new connection to the database
Database::needRoot();
}
}
}
@@ -210,6 +212,8 @@ class TrafficCron extends FroxlorCron
} else {
$httptraffic += floatval(self::callWebalizerGetTraffic($row['loginname'], $row['documentroot'] . '/webalizer/', $caption, $domainlist[$row['customerid']]));
}
// kind of a keep-alive-call as this unsets the link which leads to a new connection to the database
Database::needRoot();
// make the stuff readable for the customer, #258
Statistics::makeChownWithNewStats($row);
@@ -618,7 +622,7 @@ class TrafficCron extends FroxlorCron
$format = Settings::Get('system.logfiles_type') == '2' ? 'VCOMBINED' : 'COMBINED';
$monthyear = $monthyear_arr['month'] . '/' . $monthyear_arr['year'];
$return_value = false;
FileDir::safe_exec("grep '" . $monthyear . "' " . escapeshellarg($logfile) . " | goaccess " . $keep_params . " --db-path=" . escapeshellarg($outputdir) . " -o " . escapeshellarg($outputdir . '.tmp.json') . " -o " . escapeshellarg($outputdir . 'index.html') . " --html-report-title=" . escapeshellarg($caption) . " --log-format=" . $format . " - ", $return_value, ['|']);
FileDir::safe_exec("grep '" . $monthyear . "' " . escapeshellarg($logfile) . " | goaccess " . $keep_params . " --db-path=" . escapeshellarg($outputdir) . " -o " . escapeshellarg($outputdir . '.tmp.json') . " -o " . escapeshellarg($outputdir . 'index.html') . " --html-report-title=" . escapeshellarg($caption) . " --log-format=" . $format . " --no-parsing-spinner --no-progress - ", $return_value, ['|']);
if (file_exists($outputdir . '.tmp.json')) {
// need jq here because of potentially LARGE json files
@@ -787,6 +791,8 @@ class TrafficCron extends FroxlorCron
// 'real' domains and no subdomains which are aliases in the
// model-config-file.
$returnval += self::awstatsDoSingleDomain($singledomain, $outputdir, $current_stamp);
// kind of a keep-alive-call as this unsets the link which leads to a new connection to the database
Database::needRoot();
}
/**

View File

@@ -43,9 +43,6 @@ class IpAddr
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if (!isset($system_ipaddress_array[$row['ip']]) && !in_array($row['ip'], $system_ipaddress_array)) {
if (filter_var($row['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$row['ip'] = '[' . $row['ip'] . ']';
}
$system_ipaddress_array[$row['ip']] = $row['ip'];
}
}

View File

@@ -140,6 +140,12 @@ class FileDir
if (is_link($check_dir)) {
$original_target = $check_dir;
$check_dir = readlink($check_dir);
$link_dir = dirname($original_target);
// check whether the link is relative or absolute
if (substr($check_dir, 0, 1) != '/') {
// relative directory, prepend link_dir
$check_dir = $link_dir . '/' . $check_dir;
}
if (substr($check_dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
throw new Exception("Found symlink pointing outside of customer home directory: " . substr($original_target, strlen($fixed_homedir)));
}
@@ -287,7 +293,7 @@ class FileDir
$tpl_content = lng('admin.templates.unconfigured_content_fallback');
}
}
$redirect_file = FileDir::makeCorrectFile(Froxlor::getInstallDir().'/notice.'.$tpl_ext);
$redirect_file = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/notice.' . $tpl_ext);
file_put_contents($redirect_file, $tpl_content);
return basename($redirect_file);
}

View File

@@ -31,7 +31,7 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.1.2';
const VERSION = '2.1.7';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202312120';

View File

@@ -69,7 +69,7 @@ class AutoUpdate
if (Settings::Get('system.update_channel') == 'testing') {
$channel = '/testing';
} elseif (Settings::Get('system.update_channel') == 'nightly') {
if (empty(Froxlor::BRANDING)) {
if (empty(Froxlor::BRANDING) || substr(Froxlor::BRANDING, 0, 1) == '-') {
$channel = '/nightly.0000000';
} else {
$channel = '/' . substr(Froxlor::BRANDING, 1);
@@ -151,6 +151,8 @@ class AutoUpdate
$zip->close();
// success - remove unused archive
@unlink($localArchive);
// reset cached version check
Settings::Set('system.updatecheck_data', '');
// wait a bit before we redirect to be sure
sleep(3);
return 0;

View File

@@ -99,7 +99,7 @@ class Install
}
// check for url manipulation or wrong step
if ((isset($_SESSION['installation']['stepCompleted']) && ($this->currentStep + 1) > $_SESSION['installation']['stepCompleted'])
if ((isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > $_SESSION['installation']['stepCompleted'])
|| (!isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > 0)
) {
$this->currentStep = isset($_SESSION['installation']['stepCompleted']) ? $_SESSION['installation']['stepCompleted'] + 1 : 1;
@@ -322,6 +322,8 @@ class Install
$email = $validatedData['admin_email'] ?? '';
$password = $validatedData['admin_pass'] ?? '';
$password_confirm = $validatedData['admin_pass_confirm'] ?? '';
$useadminmailassender = $validatedData['use_admin_email_as_sender'] ?? '1';
$senderemail = $validatedData['sender_email'] ?? '';
if (!preg_match('/^[^\r\n\t\f\0]*$/D', $name)) {
throw new Exception(lng('error.stringformaterror', ['admin_name']));
@@ -329,6 +331,8 @@ class Install
throw new Exception(lng('error.loginnameiswrong', [$loginname]));
} elseif (empty(trim($email)) || !Validate::validateEmail($email)) {
throw new Exception(lng('error.emailiswrong', [$email]));
} elseif ((int)$useadminmailassender == 0 && !empty(trim($senderemail)) && !Validate::validateEmail($senderemail)) {
throw new Exception(lng('error.emailiswrong', [$senderemail]));
} elseif (empty($password) || $password != $password_confirm) {
throw new Exception(lng('error.newpasswordconfirmerror'));
} elseif ($password == $loginname) {

View File

@@ -301,8 +301,8 @@ class Core
/* continue */
}
}
if (version_compare($db_root->getAttribute(PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
// mariadb compatibility
if (version_compare($db_root->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '>=')) {
// mariadb & mysql8
// create user
$stmt = $db_root->prepare("CREATE USER '" . $username . "'@'" . $access_host . "' IDENTIFIED BY :password");
$stmt->execute([
@@ -314,19 +314,6 @@ class Core
"username" => $username,
"host" => $access_host
]);
} elseif (version_compare($db_root->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '>=')) {
// mysql8 compatibility
// create user
$stmt = $db_root->prepare("CREATE USER '" . $username . "'@'" . $access_host . "' IDENTIFIED WITH mysql_native_password BY :password");
$stmt->execute([
"password" => $password
]);
// grant privileges
$stmt = $db_root->prepare("GRANT ALL ON `" . $database . "`.* TO :username@:host");
$stmt->execute([
"username" => $username,
"host" => $access_host
]);
} else {
// grant privileges
$stmt = $db_root->prepare("GRANT ALL PRIVILEGES ON `" . $database . "`.* TO :username@:host IDENTIFIED BY :password");
@@ -378,7 +365,14 @@ class Core
$mainip = !empty($this->validatedData['serveripv6']) ? $this->validatedData['serveripv6'] : $this->validatedData['serveripv4'];
$this->updateSetting($upd_stmt, 'admin@' . $this->validatedData['servername'], 'panel', 'adminmail');
if ($this->validatedData['use_admin_email_as_sender'] == '1') {
$adminmail_value = $this->validatedData['admin_email'];
} elseif ($this->validatedData['use_admin_email_as_sender'] == '0' && !empty($this->validatedData['sender_email'])) {
$adminmail_value = $this->validatedData['sender_email'];
} else {
$adminmail_value = 'admin@' . $this->validatedData['servername'];
}
$this->updateSetting($upd_stmt, $adminmail_value, 'panel', 'adminmail');
$this->updateSetting($upd_stmt, $mainip, 'system', 'ipaddress');
if ($this->validatedData['use_ssl']) {
$this->updateSetting($upd_stmt, 1, 'system', 'use_ssl');
@@ -576,7 +570,7 @@ class Core
'password' => password_hash($this->validatedData['admin_pass'], PASSWORD_DEFAULT),
'adminname' => $this->validatedData['admin_name'],
'email' => $this->validatedData['admin_email'],
'deflang' => 'en' // TODO: set lanuage
'deflang' => 'en' // TODO: set language
];
$ins_stmt = $db_user->prepare("
INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET

View File

@@ -465,6 +465,7 @@ class PhpHelper
'mysql_unprivileged_pass',
'admin_pass',
'admin_pass_confirm',
'panel_password_special_char',
];
if (!empty($global)) {
$tmp = $global;

View File

@@ -102,6 +102,14 @@ class Traffic
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
}
// sort users by total traffic
uasort($users, function ($user_a, $user_b) {
if ($user_a['total'] == $user_b['total']) {
return 0;
}
return ($user_a['total'] < $user_b['total']) ? 1 : -1;
});
return [
'metrics' => $metrics,
'users' => $users,

View File

@@ -25,6 +25,7 @@
namespace Froxlor\UI\Callbacks;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain as DDomain;
use Froxlor\FileDir;
@@ -81,7 +82,7 @@ class Domain
return lng('domains.aliasdomain') . ' ' . $attributes['fields']['aliasdomain'];
}
public static function domainExternalLinkInfo(array $attributes)
public static function domainExternalLinkInfo(array $attributes): string
{
$result = '';
if ($attributes['fields']['parentdomainid'] != 0) {
@@ -89,7 +90,11 @@ class Domain
}
$result .= '<a href="http://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
// check for statistics if parentdomainid==0 to show stats-link for customers
if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0 && $attributes['fields']['deactivated'] == 0) {
if ((int)UI::getCurrentUser()['adminsession'] == 0
&& $attributes['fields']['parentdomainid'] == 0
&& $attributes['fields']['deactivated'] == 0
&& preg_match('/^https?:\/\/(.*)/i', $attributes['fields']['documentroot']) == false
) {
$statsapp = Settings::Get('system.traffictool');
$result .= ' <a href="http://' . $attributes['data'] . '/' . $statsapp . '" rel="external" target="_blank" title="' . lng('domains.statstics') . '"><i class="fa-solid fa-chart-line text-secondary"></i></a>';
}
@@ -109,7 +114,7 @@ class Domain
public static function canViewLogs(array $attributes): bool
{
if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) {
if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) {
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
return true;
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
@@ -151,7 +156,7 @@ class Domain
public static function hasLetsEncryptActivated(array $attributes): bool
{
return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0);
return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)));
}
public static function canEditSSL(array $attributes): bool
@@ -161,7 +166,7 @@ class Domain
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
&& (int)$attributes['fields']['caneditdomain'] == 1
&& (int)$attributes['fields']['letsencrypt'] == 0
&& (int)$attributes['fields']['email_only'] == 0
&& (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))
&& !$attributes['fields']['deactivated']
) {
return true;

View File

@@ -25,6 +25,7 @@
namespace Froxlor\UI\Callbacks;
use Froxlor\CurrentUser;
use Froxlor\Settings;
class Style
@@ -68,7 +69,7 @@ class Style
$termination_css = 'table-danger';
}
}
$deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated'];
$deactivated = $attributes['fields']['deactivated'] || (CurrentUser::isAdmin() && $attributes['fields']['customer_deactivated']);
return $deactivated ? 'table-info' : $termination_css;
}

View File

@@ -90,9 +90,10 @@ class Text
public static function customerNoteDetailModal(array $attributes): array
{
$note = $attributes['fields']['custom_notes'] ?? '';
$key = $attributes['fields']['customerid'] ?? $attributes['fields']['adminid'];
return [
'entry' => $attributes['fields']['id'],
'id' => 'cnModal' . $attributes['fields']['id'],
'entry' => $key,
'id' => 'cnModal' . $key,
'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
'body' => nl2br(Markdown::cleanCustomNotes($note))
];

View File

@@ -217,7 +217,8 @@ class Form
{
$returnvalue = [];
if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] == 'select') {
if ((!isset($fielddata['select_var']) || !is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) {
if ((empty($fielddata['select_var']) || !is_array($fielddata['select_var'])) && (isset($fielddata['option_options_method']))
) {
$returnvalue['select_var'] = call_user_func($fielddata['option_options_method']);
}
}
@@ -236,8 +237,8 @@ class Form
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
// Prefetch form fields
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
$groupdetails['fields'][$fieldname] = self::arrayMergePrefix($fielddetails, $fielddetails['type'], self::prefetchFormFieldData($fieldname, $fielddetails));
if (!$only_enabledisable || isset($fielddetails['overview_option'])) {
$groupdetails['fields'][$fieldname] = array_merge($fielddetails, self::prefetchFormFieldData($fieldname, $fielddetails));
$form['groups'][$groupname]['fields'][$fieldname] = $groupdetails['fields'][$fieldname];
}
}
@@ -347,7 +348,7 @@ class Form
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
// Save fields
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
if (!$only_enabledisable || (isset($fielddetails['overview_option']))) {
if (isset($changed_fields[$fieldname])) {
if (($saved_field = self::saveFormField($fieldname, $fielddetails, self::manipulateFormFieldData($fieldname, $fielddetails, $changed_fields[$fieldname]))) !== false) {
$saved_fields = array_merge($saved_fields, $saved_field);
@@ -364,24 +365,7 @@ class Form
// Save form
return self::saveForm($form, $saved_fields);
}
}
private static function arrayMergePrefix($array1, $key_prefix, $array2)
{
if (is_array($array1) && is_array($array2)) {
if ($key_prefix != '') {
foreach ($array2 as $key => $value) {
$array1[$key_prefix . '_' . $key] = $value;
unset($array2[$key]);
}
unset($array2);
return $array1;
} else {
return array_merge($array1, $array2);
}
} else {
return $array1;
}
return false;
}
public static function getFormFieldData($fieldname, $fielddata, &$input)

View File

@@ -176,7 +176,7 @@ class Validate
}
// special case where localhost ip is allowed (mysql-access-hosts for example)
if ($allow_localhost && $ip == '127.0.0.1') {
if ($allow_localhost && ($ip == '127.0.0.1' || $ip == '::1')) {
return $ip . $cidr;
}

View File

@@ -1556,7 +1556,7 @@ noc: root
security: root
# change this to a valid e-mail address you can access
root: root@<SERVERNAME>
root: <ADMIN_MAIL>
]]>
</content>
</file>
@@ -2547,6 +2547,7 @@ plugin {
</file>
</files>
<commands index="1">
<command><![CDATA[sed -i.bak 's/^!include auth-system.conf.ext/#!include auth-system.conf.ext/' /etc/dovecot/conf.d/10-auth.conf]]></command>
<command><![CDATA[service dovecot restart]]></command>
</commands>
</general>

View File

@@ -1556,7 +1556,7 @@ noc: root
security: root
# change this to a valid e-mail address you can access
root: root@<SERVERNAME>
root: <ADMIN_MAIL>
]]>
</content>
</file>

View File

@@ -1585,7 +1585,7 @@ noc: root
security: root
# change this to a valid e-mail address you can access
root: root@<SERVERNAME>
root: <ADMIN_MAIL>
]]>
</content>
</file>

View File

@@ -1541,7 +1541,7 @@ noc: root
security: root
# change this to a valid e-mail address you can access
root: root@<SERVERNAME>
root: <ADMIN_MAIL>
]]>
</content>
</file>

View File

@@ -1585,7 +1585,7 @@ noc: root
security: root
# change this to a valid e-mail address you can access
root: root@<SERVERNAME>
root: <ADMIN_MAIL>
]]>
</content>
</file>

View File

@@ -59,7 +59,7 @@ return [
'label' => lng('login.password'),
'type' => 'password',
'autocomplete' => 'off',
'placeholder' => lng('admin.username_default_msg'),
'placeholder' => lng('admin.password_default_msg'),
'next_to' => [
'new_customer_password_suggestion' => [
'next_to_prefix' => lng('customer.generated_pwd') . ':',

View File

@@ -103,7 +103,7 @@ return [
'maxlength' => 10,
'value' => '5s'
],
'phpfpm_pass_authorizationheader' => [
'pass_authorizationheader' => [
'visible' => Settings::Get('system.webserver') == "apache2",
'label' => lng('admin.phpsettings.pass_authorizationheader'),
'type' => 'checkbox',

View File

@@ -106,7 +106,7 @@ return [
'maxlength' => 10,
'value' => $result['fpm_reqslow']
],
'phpfpm_pass_authorizationheader' => [
'pass_authorizationheader' => [
'visible' => Settings::Get('system.webserver') == "apache2",
'label' => lng('admin.phpsettings.pass_authorizationheader'),
'type' => 'checkbox',

View File

@@ -143,6 +143,18 @@ return [
'mandatory' => true,
'value' => old('admin_email', null, 'installation'),
],
'use_admin_email_as_sender' => [
'label' => lng('install.admin.use_admin_email_as_sender'),
'type' => 'checkbox',
'value' => '1',
'checked' => old('use_admin_email_as_sender', '1', 'installation'),
],
'sender_email' => [
'label' => lng('serversettings.adminmail.title'),
'placeholder' => lng('install.admin.use_autogenerated_email_as_sender'),
'type' => 'email',
'value' => old('sender_email', null, 'installation'),
],
]
],
'step3' => [

View File

@@ -112,9 +112,14 @@ function vite($basehref, array $filenames): string
$assetDirectory = '/templates/' . $matches[1] . '/build/';
$viteManifest = dirname(__DIR__) . $assetDirectory . '/manifest.json';
$manifest = json_decode(file_get_contents($viteManifest), true);
$links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file'];
if (!empty($manifest[$filename]['file'])) {
$links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file'];
} else {
// additional asset from config.json that was not prebuilt on release (e.g. custom.css)
$links[] = $filename;
}
} else {
$links = $filenames;
$links[] = $filename;
}
}

View File

@@ -123,7 +123,7 @@ if ($req_host != Settings::Get('system.hostname') &&
(!empty(Settings::Get('system.froxloraliases')) && !in_array($req_host, array_map('trim', explode(',', Settings::Get('system.froxloraliases')))))
)) {
// not the froxlor system-hostname, show info page for domains not configured in froxlor
$redirect_file = FileDir::getUnknownDomainTemplate($req_host);
$redirect_file = FileDir::getUnknownDomainTemplate($req_host ?? "non-detectable http-host");
header('Location: '.$redirect_file);
die();
}

View File

@@ -49,6 +49,7 @@ return [
'field' => 'domains',
'callback' => [PHPConf::class, 'domainList'],
'searchable' => false,
'sortable' => false,
],
'fpmdesc' => [
'label' => lng('admin.phpsettings.fpmdesc'),

View File

@@ -67,6 +67,7 @@ return [
'customer_add' => 'Kunden anlegen',
'customer_edit' => 'Kunden bearbeiten',
'username_default_msg' => 'Leer lassen für automatische Benutzername-Vergabe',
'password_default_msg' => 'Leer lassen für Passwortgenerierung',
'domains' => 'Domains',
'domain_add' => 'Domain anlegen',
'domain_edit' => 'Domain bearbeiten',
@@ -1315,6 +1316,7 @@ Vielen Dank, Ihr Administrator',
'certificate_reallydelete' => 'Wollen Sie diese Zertifikat wirklich löschen?',
'cache_reallydelete' => 'Wollen Sie den Cache wirklich leeren?',
'please_enter_otp' => 'Bitte 2FA Code eingeben',
'admin_mysqlserver_reallydelete' => 'Wollen Sie wirklich diesen MySQL-Server löschen?',
],
'serversettings' => [
'session_timeout' => [
@@ -2252,6 +2254,8 @@ Vielen Dank, Ihr Administrator',
'top' => 'Admin Konto',
'title' => 'Erstellen des Haupt-Administrators.',
'description' => 'Dieser Benutzer erhält alle Berechtigungen zur Anpassungen von Einstellungen und Erstellen/Bearbeiten/Löschen von Resourcen wie Kunden, Domains, etc.',
'use_admin_email_as_sender' => 'Verwende die oben angegebene E-Mail-Adresse als Absenderadresse. Wenn die Option deaktiviert ist, geben Sie unten bitte eine Absenderadresse an.',
'use_autogenerated_email_as_sender' => 'Leer lassen für Standard: admin@servername',
],
'system' => [
'top' => 'System Setup',
@@ -2266,7 +2270,7 @@ Vielen Dank, Ihr Administrator',
'install' => [
'top' => 'Abschluss',
'title' => 'Ein letzter Schritt...',
'description' => 'Der untenstehende Befehl lädt, installiert und konfiguriert die benötigten Dienste auf dem System aufgrund der Angaben die während des Installationsprozessen gesammelt wurden.<br><br><span class="text-danger">Führe die gezeigten Befehle als <b>root</b> in der Shell/Konsole des Servers aus.</span>',
'description' => 'Der untenstehende Befehl lädt, installiert und konfiguriert die benötigten Dienste auf dem System aufgrund der Angaben die während des Installationsprozessen gesammelt wurden.<br><br><span class="text-danger">Führe die gezeigten Befehle als <b>root</b> in der Shell/Konsole des Servers aus. <b>Beachte bitte</b> das dieser Befehl vorhandene Konfigurationen <b>überschreibt</b> (Sicherungsdateien werden erstellt)!<br>Sollte dies nicht gewünscht sein, wähle <i>Ich werden die Dienste manuell konfigurieren</i> am Ende dieser Seite.</span>',
'runcmd' => 'Folgende Befehle ausführen, um die Installation abzuschließen:',
'manual_config' => 'Ich werden die Dienste manuell konfigurieren, direkt zum Login umleiten',
'waitforconfig' => 'Warte auf Abschluss der Dienstkonfiguration...',

View File

@@ -68,6 +68,7 @@ return [
'customer_add' => 'Create customer',
'customer_edit' => 'Edit customer',
'username_default_msg' => 'Leave empty for autogenerated value',
'password_default_msg' => 'Autogenerated if empty',
'domains' => 'Domains',
'domain_add' => 'Create domain',
'domain_edit' => 'Edit domain',
@@ -1430,6 +1431,7 @@ Yours sincerely, your administrator',
'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',
'admin_mysqlserver_reallydelete' => 'Do you really want to delete this MySQL-server?',
],
'redirect_desc' => [
'rc_default' => 'default',
@@ -2388,6 +2390,8 @@ Yours sincerely, your administrator',
'top' => 'Admin user',
'title' => 'Let\'s create the main administrator user.',
'description' => 'This user will be granted all privileges to adjust settings and add/update/delete resources like customers, domains, etc.',
'use_admin_email_as_sender' => 'Use the email address above as sender address. If unchecked, please specify a sender address below.',
'use_autogenerated_email_as_sender' => 'Leave empty for default: admin@servername',
],
'system' => [
'top' => 'System setup',
@@ -2402,7 +2406,7 @@ Yours sincerely, your administrator',
'install' => [
'top' => 'Finish setup',
'title' => 'One last step...',
'description' => 'The command below will download, install and configure required services on your system according to the data you have given in this installation process.<br><br><span class="text-danger">Be sure to run the following command as <b>root</b> on the server\'s shell/terminal.</span>',
'description' => 'The command below will download, install and configure required services on your system according to the data you have given in this installation process.<br><br><span class="text-danger">Be sure to run the following command as <b>root</b> on the server\'s shell/terminal and <b>be aware</b> that this command will <b>overwrite</b> any existing configuration for the used services (backups will be created)!.<br>If you do not want to overwrite any configurations, select <i>I will manually configure the services</i> at the bottom of this page!</span>',
'runcmd' => 'Run the following command to finish the installation:',
'manual_config' => 'I will manually configure the services, just take me to the login',
'waitforconfig' => 'Waiting for services to be configured...',

6
package-lock.json generated
View File

@@ -776,9 +776,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{

View File

@@ -6,6 +6,14 @@
background-color: $body-tertiary-bg;
}
.form-floating > .form-control::placeholder {
color: revert;
}
.form-floating > .form-control:not(:focus)::placeholder {
color: transparent;
}
@include color-mode(dark) {
.formfield {
border-bottom: $border-color-dark solid 1px;

View File

@@ -1,8 +1,8 @@
{
"global": {
"css": [
"assets/css/custom.css",
"assets/scss/app.scss"
"assets/scss/app.scss",
"assets/css/custom.css"
],
"js": [
"assets/js/app.js",

View File

@@ -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) or (field.disabled is defined and field.disabled == true) %} 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

@@ -21,6 +21,9 @@
<p>{{ message }}</p>
{% if get_config('enable_webupdate') %}
<a class='btn d-block btn-outline-warning' href='admin_autoupdate.php?page=overview'>Open updater</a>
{% else %}
<p>Run the following command in your shell to update:</p>
<code>{{ call_static('\\Froxlor\\Froxlor', 'getInstallDir') }}bin/froxlor-cli froxlor:update</code>
{% endif %}
{% endif %}
{% endmacro %}

View File

@@ -24,7 +24,7 @@
aria-valuemin="0" aria-valuemax="{{ apcuinfo.mem_avail }}">
<div class="progress-bar bg-success" style="width: {{ apcuinfo.mem_used_percentage }}%"></div>
<small
class="justify-content-center d-flex position-absolute w-100 text-dark">{{ apcuinfo.mem_used_percentage }}
class="justify-content-center d-flex position-absolute w-100">{{ apcuinfo.mem_used_percentage }}
%</small>
</div>
</div>

View File

@@ -152,7 +152,7 @@
{% if userinfo.adminsession == 1 %}
const labelsC = [];
const dataValues = [];
{% for user in users|sort((a, b) => a.total <=> b.total)|slice(0, 5) %}
{% for user in users|slice(0, 5) %}
labelsC.push('{{ user.loginname }}');
dataValues.push({value: '{{ user.total|default(0) }}', formatted: '{{ user.total|formatBytes }}'});
{% endfor %}

View File

@@ -426,10 +426,10 @@ class MailsTest extends TestCase
$this->assertEquals(1, $result['popaccountid']);
switch (Settings::Get('system.passwordcryptfunc')) {
case PASSWORD_ARGON2I:
case 'argon2i':
$cpPrefix = '{ARGON2I}';
break;
case PASSWORD_ARGON2ID:
case 'argon2id':
$cpPrefix = '{ARGON2ID}';
break;
default: