Compare commits

...

62 Commits

Author SHA1 Message Date
Michael Kaufmann
10555bff76 set version to 2.0.23 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-03 20:16:18 +02:00
Michael Kaufmann
37aa7af4da check for existing userinfo if settings are being imported via cli
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:11:06 +02:00
Michael Kaufmann
4b75369597 only check non-admin resources if user is not an admin in navigation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 15:53:15 +02:00
Michael Kaufmann
9d0e463906 set version to 2.0.22 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-01 09:58:33 +02:00
Daniel
a7198f58ce Fix"Add" shortcut link in email address navigation (#1169)
Seems to have changed when adding the domain-filter overview for email addresses, but not updated in the navigation.
2023-08-13 08:19:32 +02:00
Michael Kaufmann
47be4b2847 remove shortcode for --diff-params in configdiff command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:04:58 +02:00
Daniel
b0fae4bd14 Add config-diff CLI Command (#1168)
---------

Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:03:16 +02:00
Michael Kaufmann
4711a41436 correct validation of hostingplan name and description
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 13:57:21 +02:00
Michael Kaufmann
faa71ceaef forgot to save one file for the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:13:33 +02:00
Michael Kaufmann
2d30394150 correctly redirect to last-page if session is timed out and remove passing script/qrystr url parameters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:09:23 +02:00
Michael Kaufmann
99c1182af8 adjustments in installation for debian 12 and fcgid / disabling mod_php; thx to Konstantin
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 11:25:03 +02:00
Michael Kaufmann
d9abe58dd2 adjust proftpd config for debian 12 bookworm
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-22 13:00:11 +02:00
Michael Kaufmann
23034b8ad2 rework path to certificates non-ecc/ecc, regardless of current setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-21 08:54:29 +02:00
Michael Kaufmann
1cae5638d3 fix optional-flag for IpsAndPorts.add() and IpsAndPorts.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 17:09:45 +02:00
Michael Kaufmann
ce9a5f97a3 validate non-empy admin-name in Admins.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 16:44:46 +02:00
Michael Kaufmann
c38b90deef Merge branch 'main' of github.com:Froxlor/Froxlor 2023-07-07 09:52:37 +02:00
Michael Kaufmann
13daa7d6fa set version to 2.0.21 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:50:50 +02:00
Michael Kaufmann
b0e43d332d validate generated config-json parameter string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:47:17 +02:00
jabertwo
75c8754fb4 Fix typo in pathDescriptionSubdomain (#1156) 2023-06-26 11:03:48 +02:00
Michael Kaufmann
e0fa64f897 fix update-check unit-tests now that the current testing version is at 2.1.0-dev1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:33:48 +02:00
Michael Kaufmann
ed72fd1766 exclude password fields from being filtered/escaped by AntiXSS, fixes #1150
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:14:13 +02:00
Michael Kaufmann
826ae36647 adjust log-levels in API methods
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 12:13:38 +02:00
Michael Kaufmann
9ddf24539e remove hidden fields from login/passwd-reset; refs #1102
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 12:10:39 +02:00
Grigory Morozov
3940c1429d Correcting Nginx location match, fixes #1153 2023-06-05 08:06:44 +02:00
Michael Kaufmann
c236d9eaab set version to 2.0.20 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-02 20:13:36 +02:00
Michael Kaufmann
688994e40c idna encode umlaut-emailaddresses when adding email-forwarder
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-29 20:52:57 +02:00
Michael Kaufmann
9facaee809 re-enable fcgid/php-fpm activation-validate-check
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-28 15:49:06 +02:00
Michael Kaufmann
a7dd5f4685 show 0 value of resource-fields if value is empty, fixes #1149
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-28 10:46:28 +02:00
Michael Kaufmann
da810ea953 secure filename of local-archive in webupdate
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 09:51:55 +02:00
Michael Kaufmann
51b6e067e8 idna encode umlaut-emailaddresses when adding/editing email-account; use correct password-suggestion-layout in change-email-account formfield
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 08:26:39 +02:00
Michael Kaufmann
34cf6698bc remove superfluous try_files in nginx config if php-backend (non-fastcgi) is used
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-15 20:14:26 +02:00
Michael Kaufmann
4642160724 add same loginfail restrictions for entering 2fa code as for user/pwd login
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-12 10:36:27 +02:00
Nicolas Thumann
78a259ef3b Fix IPv6 address in cookie domain (#1137)
* Implement getCookieHost to extract cookie host from HTTP_HOST
2023-05-10 08:26:08 +02:00
Nicolas Thumann
68cf4ab69a Fix typo in English privileged_passwd (#1136) 2023-05-09 18:52:43 +02:00
Michael Kaufmann
d5661d492d set version to 2.0.19 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 11:07:31 +02:00
Michael Kaufmann
6900898ae1 typo in updater
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 11:03:21 +02:00
Michael Kaufmann
d90fb7fa68 fix mysql-pdo check on installation, set version to 2.0.18 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 10:54:47 +02:00
Michael Kaufmann
4ea8629fcc set version to 2.0.17 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-06 22:08:43 +02:00
Michael Kaufmann
9d4ff8698d fix ratelimiting when settings do not exist (yet)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-06 22:00:19 +02:00
Michael Kaufmann
b164038846 set version to 2.0.16 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-05 21:10:01 +02:00
Michael Kaufmann
5c46960734 fix language mixup for rate-limit-interval setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-05 13:21:12 +02:00
Michael Kaufmann
a7f4f0c737 output nicer message when hitting rate limit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-04 10:55:34 +02:00
Michael Kaufmann
b64dd501dd fix missing use-statement
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-02 10:27:28 +02:00
Michael Kaufmann
1679675aa1 introduce http-request rate-limit; smaller fixes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-02 10:19:53 +02:00
sro0
640466f301 Disable autocomplete on 2FA input element (#1133)
2FA codes change every login. So there is no need to save entered values in browser and suggest them again during future logins.

Co-authored-by: sro0 <>
2023-04-29 09:56:15 +02:00
Michael Kaufmann
9c9771a371 fix generation of current_ips array in Domains-API
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 20:09:18 +02:00
Michael Kaufmann
1922b3ce65 set default value for email_quota to settings-default in EmailAccounts.add(); fixes #1132
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:50:41 +02:00
Michael Kaufmann
83e819908a set default value of 'openbasedir_path' to 0 in SubDomain.add() like we do in Domains.add()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:49:09 +02:00
Michael Kaufmann
0924aa644b update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:48:29 +02:00
Lukas Bableck
7711ce1d66 Allow admins to edit openbasedir_path for domains (#1125)
* Add openbasedir_path formfield
* Add openbasedir_path field values to admin_domains page
2023-04-25 19:42:27 +02:00
Michael Kaufmann
7dae63e586 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-04-25 19:40:22 +02:00
Michael Kaufmann
1bcaa45492 add copy-system-details-to-clipboard button on admin dashboard; fixes #1126
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:36:46 +02:00
Michael Kaufmann
66cb114f0d trigger rebuild of config files after changing only ip-settings in domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:33:52 +02:00
Michael Kaufmann
1c5d60dcfd Add mysql to required extensions 2023-04-23 13:28:33 +02:00
Michael Kaufmann
b6da6356fc Update build-docs.yml 2023-04-23 12:08:19 +02:00
Michael Kaufmann
c09670cc45 make it clearer that the finishing commands have to be exectuted as 'root'; fixes #1128
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-23 11:56:20 +02:00
Michael Kaufmann
464f5b7bed fix adding mysql-server to customers without any prior assigned mysql-server, fixes #1123; fix issues with displaying set value if path-mode is 'dropdown'
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-19 20:58:48 +02:00
Michael Kaufmann
c799235c24 corrected display of special-case titles of settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-14 08:23:37 +02:00
Michael Kaufmann
a2860e70a5 strictly check whether field to select is the id or the email-address b/c is cases of email-addresses starting with a digit this is somehow used as value for the id field and return the wrong entity
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-14 08:22:31 +02:00
Michael Kaufmann
95a96d46a6 put php-fpm directives in Directory-directive in apache2; fixes #1120
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-11 09:48:26 +02:00
Michael Kaufmann
81f3dbda31 respect no-try_files setting also in protected directories
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-10 09:33:43 +02:00
Michael Kaufmann
4eb4191843 don't run cron tasks if requirements return non-success; fixes #1122
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-08 10:49:59 +02:00
81 changed files with 1339 additions and 7481 deletions

View File

@@ -11,4 +11,4 @@ jobs:
- env: - env:
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
run: | run: |
gh workflow run --repo Froxlor/Documentation build-and-deploy -f type=tags ref=${{github.ref_name}} gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags ref=${{github.ref_name}}

View File

@@ -138,6 +138,26 @@ return [
'save_method' => 'storeSettingField', 'save_method' => 'storeSettingField',
'advanced_mode' => true 'advanced_mode' => true
], ],
'system_req_limit_per_interval' => [
'label' => lng('serversettings.req_limit_per_interval'),
'settinggroup' => 'system',
'varname' => 'req_limit_per_interval',
'type' => 'number',
'min' => 30,
'default' => 60,
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_req_limit_interval' => [
'label' => lng('serversettings.req_limit_interval'),
'settinggroup' => 'system',
'varname' => 'req_limit_interval',
'type' => 'number',
'min' => 5,
'default' => 60,
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'customer_accountprefix' => [ 'customer_accountprefix' => [
'label' => lng('serversettings.accountprefix'), 'label' => lng('serversettings.accountprefix'),
'settinggroup' => 'customer', 'settinggroup' => 'customer',

View File

@@ -77,6 +77,7 @@ if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettin
$result['switched_user'] = CurrentUser::getData(); $result['switched_user'] = CurrentUser::getData();
$result['adminsession'] = 1; $result['adminsession'] = 1;
$result['userid'] = $result['adminid']; $result['userid'] = $result['adminid'];
session_regenerate_id(true);
CurrentUser::setData($result); CurrentUser::setData($result);
$log->logAction( $log->logAction(

View File

@@ -28,7 +28,7 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\Http\HttpClient; use Froxlor\FileDir;
use Froxlor\Install\AutoUpdate; use Froxlor\Install\AutoUpdate;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
@@ -132,7 +132,7 @@ elseif ($page == 'getdownload') {
elseif ($page == 'extract') { elseif ($page == 'extract') {
if (isset($_POST['send']) && $_POST['send'] == 'send') { if (isset($_POST['send']) && $_POST['send'] == 'send') {
$toExtract = isset($_POST['archive']) ? $_POST['archive'] : null; $toExtract = isset($_POST['archive']) ? $_POST['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract; $localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir()); $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
$result = AutoUpdate::extractZip($localArchive); $result = AutoUpdate::extractZip($localArchive);
if ($result > 0) { if ($result > 0) {
@@ -146,7 +146,7 @@ elseif ($page == 'extract') {
Response::redirectTo('admin_updates.php'); Response::redirectTo('admin_updates.php');
} else { } else {
$toExtract = isset($_GET['archive']) ? $_GET['archive'] : null; $toExtract = isset($_GET['archive']) ? $_GET['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract; $localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
} }
if (!file_exists($localArchive)) { if (!file_exists($localArchive)) {

View File

@@ -33,6 +33,7 @@ use Froxlor\Settings;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
if ($userinfo['change_serversettings'] == '1') { if ($userinfo['change_serversettings'] == '1') {
if ($action == 'setconfigured') { if ($action == 'setconfigured') {
@@ -91,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
} }
if ($distribution != "" && isset($_POST['finish'])) { if ($distribution != "" && isset($_POST['finish'])) {
$valid_keys = ['http', 'dns', 'smtp', 'mail', 'ftp', 'system', 'distro'];
unset($_POST['finish']); unset($_POST['finish']);
unset($_POST['csrf_token']); unset($_POST['csrf_token']);
$params = $_POST; $params = $_POST;
@@ -99,6 +101,20 @@ if ($userinfo['change_serversettings'] == '1') {
foreach ($_POST['system'] as $sysdaemon) { foreach ($_POST['system'] as $sysdaemon) {
$params['system'][] = $sysdaemon; $params['system'][] = $sysdaemon;
} }
// validate params
foreach ($params as $key => $value) {
if (!in_array($key, $valid_keys)) {
unset($params[$key]);
continue;
}
if (!is_array($value)) {
$params[$key] = Validate::validate($value, $key);
} else {
foreach ($value as $subkey => $subvalue) {
$params[$key][$subkey] = Validate::validate($subvalue, $key.'.'.$subkey);
}
}
}
$params_content = json_encode($params); $params_content = json_encode($params);
$params_filename = FileDir::makeCorrectFile(Froxlor::getInstallDir() . 'install/' . Froxlor::genSessionId() . '.json'); $params_filename = FileDir::makeCorrectFile(Froxlor::getInstallDir() . 'install/' . Froxlor::genSessionId() . '.json');
file_put_contents($params_filename, $params_content); file_put_contents($params_filename, $params_content);

View File

@@ -93,6 +93,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$result['switched_user'] = CurrentUser::getData(); $result['switched_user'] = CurrentUser::getData();
$result['adminsession'] = 0; $result['adminsession'] = 0;
$result['userid'] = $result['customerid']; $result['userid'] = $result['customerid'];
session_regenerate_id(true);
CurrentUser::setData($result); CurrentUser::setData($result);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "switched user and is now '" . $destination_user . "'"); $log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "switched user and is now '" . $destination_user . "'");

View File

@@ -282,6 +282,12 @@ if ($page == 'domains' || $page == 'overview') {
} }
} }
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
// create serveralias options // create serveralias options
$serveraliasoptions = [ $serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'), 0 => lng('domains.serveraliasoption_wildcard'),
@@ -545,6 +551,12 @@ if ($page == 'domains' || $page == 'overview') {
$result['temporary_ssl_redirect'] = $result['ssl_redirect']; $result['temporary_ssl_redirect'] = $result['ssl_redirect'];
$result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1); $result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1);
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
$serveraliasoptions = [ $serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'), 0 => lng('domains.serveraliasoption_wildcard'),
1 => lng('domains.serveraliasoption_www'), 1 => lng('domains.serveraliasoption_www'),

View File

@@ -53,6 +53,7 @@ if ($action == 'logout') {
if (is_array(CurrentUser::getField('switched_user'))) { if (is_array(CurrentUser::getField('switched_user'))) {
$result = CurrentUser::getData(); $result = CurrentUser::getData();
$result = $result['switched_user']; $result = $result['switched_user'];
session_regenerate_id(true);
CurrentUser::setData($result); CurrentUser::setData($result);
$target = (isset($_GET['target']) ? $_GET['target'] : 'index'); $target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$redirect = "admin_" . $target . ".php"; $redirect = "admin_" . $target . ".php";

View File

@@ -26,6 +26,7 @@
declare(strict_types=1); declare(strict_types=1);
use Froxlor\Cli\ConfigDiff;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Froxlor\Cli\RunApiCommand; use Froxlor\Cli\RunApiCommand;
use Froxlor\Cli\ConfigServices; use Froxlor\Cli\ConfigServices;
@@ -61,4 +62,5 @@ $application->add(new InstallCommand());
$application->add(new MasterCron()); $application->add(new MasterCron());
$application->add(new UserCommand()); $application->add(new UserCommand());
$application->add(new ValidateAcmeWebroot()); $application->add(new ValidateAcmeWebroot());
$application->add(new ConfigDiff());
$application->run(); $application->run();

94
composer.lock generated
View File

@@ -499,16 +499,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v5.4.21", "version": "v5.4.22",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9" "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/c77433ddc6cdc689caf48065d9ea22ca0853fbd9", "url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8",
"reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9", "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -573,12 +573,12 @@
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"keywords": [ "keywords": [
"cli", "cli",
"command line", "command-line",
"console", "console",
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v5.4.21" "source": "https://github.com/symfony/console/tree/v5.4.22"
}, },
"funding": [ "funding": [
{ {
@@ -594,7 +594,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-02-25T16:59:41+00:00" "time": "2023-03-25T09:27:28+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
@@ -1399,16 +1399,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v5.4.21", "version": "v5.4.22",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f" "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/edac10d167b78b1d90f46a80320d632de0bd9f2f", "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62",
"reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f", "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1465,7 +1465,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v5.4.21" "source": "https://github.com/symfony/string/tree/v5.4.22"
}, },
"funding": [ "funding": [
{ {
@@ -1481,7 +1481,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-02-22T08:00:55+00:00" "time": "2023-03-14T06:11:53+00:00"
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
@@ -1718,16 +1718,16 @@
}, },
{ {
"name": "voku/portable-utf8", "name": "voku/portable-utf8",
"version": "6.0.12", "version": "6.0.13",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/voku/portable-utf8.git", "url": "https://github.com/voku/portable-utf8.git",
"reference": "db0583727bb17666bbd2ba238c85babb973fd165" "reference": "b8ce36bf26593e5c2e81b1850ef0ffb299d2043f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/voku/portable-utf8/zipball/db0583727bb17666bbd2ba238c85babb973fd165", "url": "https://api.github.com/repos/voku/portable-utf8/zipball/b8ce36bf26593e5c2e81b1850ef0ffb299d2043f",
"reference": "db0583727bb17666bbd2ba238c85babb973fd165", "reference": "b8ce36bf26593e5c2e81b1850ef0ffb299d2043f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1793,7 +1793,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/voku/portable-utf8/issues", "issues": "https://github.com/voku/portable-utf8/issues",
"source": "https://github.com/voku/portable-utf8/tree/6.0.12" "source": "https://github.com/voku/portable-utf8/tree/6.0.13"
}, },
"funding": [ "funding": [
{ {
@@ -1817,7 +1817,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-01-11T12:26:16+00:00" "time": "2023-03-08T08:35:38+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@@ -2030,16 +2030,16 @@
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.11.0", "version": "1.11.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2077,7 +2077,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/myclabs/DeepCopy/issues", "issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
}, },
"funding": [ "funding": [
{ {
@@ -2085,7 +2085,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-03T13:19:32+00:00" "time": "2023-03-08T13:26:56+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
@@ -2520,16 +2520,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.10.4", "version": "1.10.14",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "8d39218664b45a4a42d5be66d2b63dcf8c149982" "reference": "d232901b09e67538e5c86a724be841bea5768a7c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8d39218664b45a4a42d5be66d2b63dcf8c149982", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c",
"reference": "8d39218664b45a4a42d5be66d2b63dcf8c149982", "reference": "d232901b09e67538e5c86a724be841bea5768a7c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2558,8 +2558,11 @@
"static analysis" "static analysis"
], ],
"support": { "support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues", "issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.10.4" "security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
}, },
"funding": [ "funding": [
{ {
@@ -2575,7 +2578,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-03-06T13:39:20+00:00" "time": "2023-04-19T13:47:27+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@@ -2897,16 +2900,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.6.4", "version": "9.6.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
"reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2939,8 +2942,8 @@
"sebastian/version": "^3.0.2" "sebastian/version": "^3.0.2"
}, },
"suggest": { "suggest": {
"ext-soap": "*", "ext-soap": "To be able to generate mocks based on WSDL files",
"ext-xdebug": "*" "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
}, },
"bin": [ "bin": [
"phpunit" "phpunit"
@@ -2979,7 +2982,8 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7"
}, },
"funding": [ "funding": [
{ {
@@ -2995,7 +2999,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-02-27T13:06:37+00:00" "time": "2023-04-14T08:58:40+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@@ -4161,16 +4165,16 @@
}, },
{ {
"name": "symfony/dependency-injection", "name": "symfony/dependency-injection",
"version": "v5.4.21", "version": "v5.4.22",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/dependency-injection.git", "url": "https://github.com/symfony/dependency-injection.git",
"reference": "5bc403d96622cf0091abd92c939eadecd4d07f94" "reference": "e1b7c1432efb4ad1dd89d62906187271e2601ed9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5bc403d96622cf0091abd92c939eadecd4d07f94", "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e1b7c1432efb4ad1dd89d62906187271e2601ed9",
"reference": "5bc403d96622cf0091abd92c939eadecd4d07f94", "reference": "e1b7c1432efb4ad1dd89d62906187271e2601ed9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -4230,7 +4234,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application", "description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/dependency-injection/tree/v5.4.21" "source": "https://github.com/symfony/dependency-injection/tree/v5.4.22"
}, },
"funding": [ "funding": [
{ {
@@ -4246,7 +4250,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-02-16T09:33:00+00:00" "time": "2023-03-10T10:02:45+00:00"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",

View File

@@ -52,6 +52,7 @@ if ($action == 'logout') {
if (is_array(CurrentUser::getField('switched_user'))) { if (is_array(CurrentUser::getField('switched_user'))) {
$result = CurrentUser::getData(); $result = CurrentUser::getData();
$result = $result['switched_user']; $result = $result['switched_user'];
session_regenerate_id(true);
CurrentUser::setData($result); CurrentUser::setData($result);
$target = (isset($_GET['target']) ? $_GET['target'] : 'index'); $target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$redirect = "admin_" . $target . ".php"; $redirect = "admin_" . $target . ".php";

101
index.php
View File

@@ -40,7 +40,6 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\User; use Froxlor\User;
use Froxlor\Validate\Validate; use Froxlor\Validate\Validate;
use Froxlor\Language;
if ($action == '') { if ($action == '') {
$action = 'login'; $action = 'login';
@@ -53,9 +52,15 @@ if ($action == '2fa_entercode') {
Response::redirectTo('index.php'); Response::redirectTo('index.php');
exit(); exit();
} }
$smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0;
$message = "";
if ($smessage > 0) {
$message = lng('error.2fa_wrongcode');
}
// show template to enter code // show template to enter code
UI::view('login/enter2fa.html.twig', [ UI::view('login/enter2fa.html.twig', [
'pagetitle' => lng('login.2fa') 'pagetitle' => lng('login.2fa'),
'message' => $message
]); ]);
} elseif ($action == '2fa_verify') { } elseif ($action == '2fa_verify') {
// verify code from 2fa code-enter form // verify code from 2fa code-enter form
@@ -68,25 +73,25 @@ if ($action == '2fa_entercode') {
// verify entered code // verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3)); $result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code // either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa // which is temporarily stored for the customer when using email-2fa
if ($result) { if ($result) {
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
$sel_param = [ $sel_param = [
'uid' => $uid 'uid' => $uid
]; ];
if ($_SESSION['secret_2fa'] == 'email') { if ($_SESSION['secret_2fa'] == 'email') {
// verify code by selecting user by id and the temp. stored code, // verify code by selecting user by id and the temp. stored code,
// so only if it's the correct code, we get the user-data // so only if it's the correct code, we get the user-data
$sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid AND `data_2fa` = :code"); $sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_param['code'] = $code; $sel_param['code'] = $code;
} else { } else {
// Authenticator-verification has already happened at this point, so just get the user-data // Authenticator-verification has already happened at this point, so just get the user-data
$sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid"); $sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
} }
$userinfo = Database::pexecute_first($sel_stmt, $sel_param); $userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again // whoops, no (valid) user? Start again
@@ -108,19 +113,54 @@ if ($action == '2fa_entercode') {
// when using email-2fa, remove the one-time-code // when using email-2fa, remove the one-time-code
if ($userinfo['type_2fa'] == '1') { if ($userinfo['type_2fa'] == '1') {
$del_stmt = Database::prepare("UPDATE $table SET `data_2fa` = '' WHERE `" . $field . "` = :uid"); $del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
$userinfo = Database::pexecute_first($del_stmt, [ $userinfo = Database::pexecute_first($del_stmt, [
'uid' => $uid 'uid' => $uid
]); ]);
} }
exit(); exit();
} }
// wrong 2fa code - treat like "wrong password"
$stmt = Database::prepare("
UPDATE " . $table . "
SET `lastlogin_fail`= :lastlogin_fail, `loginfail_count`=`loginfail_count`+1
WHERE `" . $field . "`= :uid
");
Database::pexecute($stmt, [
"lastlogin_fail" => time(),
"uid" => $uid
]);
// get data for processing further
$stmt = Database::prepare("
SELECT `loginname`, `loginfail_count`, `lastlogin_fail` FROM " . $table . "
WHERE `" . $field . "`= :uid
");
$fail_user = Database::pexecute_first($stmt, [
"uid" => $uid
]);
if ($fail_user['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $fail_user['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))) {
// Log failed login
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR']
]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User '" . $fail_user['loginname'] . "' entered wrong 2fa code too often.");
unset($fail_user);
Response::redirectTo('index.php', [
'showmessage' => '3'
]);
exit();
}
unset($fail_user);
// back to form
Response::redirectTo('index.php', [ Response::redirectTo('index.php', [
'showmessage' => '2' 'action' => '2fa_entercode',
'showmessage' => '1'
]); ]);
exit(); exit();
} elseif ($action == 'login') { } elseif ($action == 'login') {
if (isset($_POST['send']) && $_POST['send'] == 'send') { if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname'); $loginname = Validate::validate($_POST['loginname'], 'loginname');
$password = Validate::validate($_POST['password'], 'password'); $password = Validate::validate($_POST['password'], 'password');
@@ -390,13 +430,18 @@ if ($action == '2fa_entercode') {
} }
$lastqrystr = ""; $lastqrystr = "";
if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") { if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") {
$lastqrystr = htmlspecialchars($_REQUEST['qrystr'], ENT_QUOTES); $lastqrystr = urlencode($_REQUEST['qrystr']);
}
if (!empty($lastscript)) {
$_SESSION['lastscript'] = $lastscript;
}
if (!empty($lastqrystr)) {
$_SESSION['lastqrystr'] = $lastqrystr;
} }
UI::view('login/login.html.twig', [ UI::view('login/login.html.twig', [
'pagetitle' => 'Login', 'pagetitle' => 'Login',
'lastscript' => $lastscript,
'lastqrystr' => $lastqrystr,
'upd_in_progress' => $update_in_progress, 'upd_in_progress' => $update_in_progress,
'message' => $message, 'message' => $message,
'successmsg' => $successmessage 'successmsg' => $successmessage
@@ -408,7 +453,7 @@ if ($action == 'forgotpwd') {
$adminchecked = false; $adminchecked = false;
$message = ''; $message = '';
if (isset($_POST['send']) && $_POST['send'] == 'send') { if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname'); $loginname = Validate::validate($_POST['loginname'], 'loginname');
$email = Validate::validateEmail($_POST['loginemail']); $email = Validate::validateEmail($_POST['loginemail']);
$result_stmt = Database::prepare("SELECT `adminid`, `customerid`, `customernumber`, `firstname`, `name`, `company`, `email`, `loginname`, `def_language`, `deactivated` FROM `" . TABLE_PANEL_CUSTOMERS . "` $result_stmt = Database::prepare("SELECT `adminid`, `customerid`, `customernumber`, `firstname`, `name`, `company`, `email`, `loginname`, `def_language`, `deactivated` FROM `" . TABLE_PANEL_CUSTOMERS . "`
@@ -592,7 +637,7 @@ if ($action == 'forgotpwd') {
UI::view('login/fpwd.html.twig', [ UI::view('login/fpwd.html.twig', [
'pagetitle' => lng('login.presend'), 'pagetitle' => lng('login.presend'),
'action' => $action, 'formaction' => 'index.php?action=' . $action,
'message' => $message, 'message' => $message,
]); ]);
} }
@@ -615,7 +660,7 @@ if ($action == 'resetpwd') {
$check = substr($activationcode, 40, 10); $check = substr($activationcode, 40, 10);
if (substr(md5($third . $timestamp), 0, 10) == $check && $timestamp >= time() - 86400) { if (substr(md5($third . $timestamp), 0, 10) == $check && $timestamp >= time() - 86400) {
if (isset($_POST['send']) && $_POST['send'] == 'send') { if (!empty($_POST)) {
$stmt = Database::prepare("SELECT `userid`, `admin` FROM `" . TABLE_PANEL_ACTIVATION . "` $stmt = Database::prepare("SELECT `userid`, `admin` FROM `" . TABLE_PANEL_ACTIVATION . "`
WHERE `activationcode` = :activationcode"); WHERE `activationcode` = :activationcode");
$result = Database::pexecute_first($stmt, [ $result = Database::pexecute_first($stmt, [
@@ -692,6 +737,7 @@ if ($action == 'resetpwd') {
function finishLogin($userinfo) function finishLogin($userinfo)
{ {
if (isset($userinfo['userid']) && $userinfo['userid'] != '') { if (isset($userinfo['userid']) && $userinfo['userid'] != '') {
session_regenerate_id(true);
CurrentUser::setData($userinfo); CurrentUser::setData($userinfo);
$language = $userinfo['def_language'] ?? Settings::Get('panel.standardlanguage'); $language = $userinfo['def_language'] ?? Settings::Get('panel.standardlanguage');
@@ -705,29 +751,34 @@ function finishLogin($userinfo)
} }
$qryparams = []; $qryparams = [];
if (isset($_POST['qrystr']) && $_POST['qrystr'] != "") { if (!empty($_SESSION['lastqrystr'])) {
parse_str(urldecode($_POST['qrystr']), $qryparams); parse_str(urldecode($_SESSION['lastqrystr']), $qryparams);
unset($_SESSION['lastqrystr']);
} }
if ($userinfo['adminsession'] == '1') { if ($userinfo['adminsession'] == '1') {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) { if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
Response::redirectTo('admin_updates.php?page=overview'); Response::redirectTo('admin_updates.php?page=overview');
} else { } else {
if (isset($_POST['script']) && $_POST['script'] != "") { if (!empty($_SESSION['lastscript'])) {
if (preg_match("/customer\_/", $_POST['script']) === 1) { $lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
if (preg_match("/customer\_/", $lastscript) === 1) {
Response::redirectTo('admin_customers.php', [ Response::redirectTo('admin_customers.php', [
"page" => "customers" "page" => "customers"
]); ]);
} else { } else {
Response::redirectTo($_POST['script'], $qryparams); Response::redirectTo($lastscript, $qryparams);
} }
} else { } else {
Response::redirectTo('admin_index.php', $qryparams); Response::redirectTo('admin_index.php', $qryparams);
} }
} }
} else { } else {
if (isset($_POST['script']) && $_POST['script'] != "") { if (!empty($_SESSION['lastscript'])) {
Response::redirectTo($_POST['script'], $qryparams); $lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
Response::redirectTo($lastscript, $qryparams);
} else { } else {
Response::redirectTo('customer_index.php', $qryparams); Response::redirectTo('customer_index.php', $qryparams);
} }

View File

@@ -697,8 +697,10 @@ opcache.validate_timestamps'),
('system', 'distribution', ''), ('system', 'distribution', ''),
('system', 'update_channel', 'stable'), ('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''), ('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.15'), ('system', 'update_notify_last', '2.0.23'),
('system', 'traffictool', 'goaccess'), ('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
('api', 'enabled', '0'), ('api', 'enabled', '0'),
('api', 'customer_default', '1'), ('api', 'customer_default', '1'),
('2fa', 'enabled', '1'), ('2fa', 'enabled', '1'),
@@ -742,8 +744,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'), ('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'), ('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'), ('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.15'), ('panel', 'version', '2.0.23'),
('panel', 'db_version', '202303150'); ('panel', 'db_version', '202304260');
DROP TABLE IF EXISTS `panel_tasks`; DROP TABLE IF EXISTS `panel_tasks`;

View File

@@ -23,6 +23,7 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2 * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/ */
use Froxlor\Http\RateLimiter;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\Install\Install; use Froxlor\Install\Install;
@@ -62,6 +63,7 @@ require dirname(__DIR__) . '/lib/tables.inc.php';
// init twig // init twig
UI::initTwig(true); UI::initTwig(true);
UI::sendHeaders(); UI::sendHeaders();
RateLimiter::run(true);
$installer = new Install(); $installer = new Install();
$installer->handle(); $installer->handle();

View File

@@ -82,7 +82,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` DROP COLUMN `domains_see_all`;"); Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` DROP COLUMN `domains_see_all`;");
Update::lastStepStatus(0); Update::lastStepStatus(0);
Update::showUpdateStep("Checking for multiple mysql-servers to allow acccess to customers for existing databases"); Update::showUpdateStep("Checking for multiple mysql-servers to allow access to customers for existing databases");
$dbservers_stmt = Database::query(" $dbservers_stmt = Database::query("
SELECT `customerid`, SELECT `customerid`,
GROUP_CONCAT(DISTINCT `dbserver` SEPARATOR ',') as allowed_mysqlserver GROUP_CONCAT(DISTINCT `dbserver` SEPARATOR ',') as allowed_mysqlserver
@@ -149,7 +149,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Update::showUpdateStep("Adding new settings"); Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0; $panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode); Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : ''; $system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : 'bullseye';
Settings::AddNew("system.distribution", $system_distribution); Settings::AddNew("system.distribution", $system_distribution);
Settings::AddNew("system.update_channel", 'stable'); Settings::AddNew("system.update_channel", 'stable');
Settings::AddNew("system.updatecheck_data", ''); Settings::AddNew("system.updatecheck_data", '');
@@ -463,3 +463,52 @@ if (Froxlor::isFroxlorVersion('2.0.14')) {
Update::showUpdateStep("Updating from 2.0.14 to 2.0.15", false); Update::showUpdateStep("Updating from 2.0.14 to 2.0.15", false);
Froxlor::updateToVersion('2.0.15'); Froxlor::updateToVersion('2.0.15');
} }
if (Froxlor::isDatabaseVersion('202303150')) {
Update::showUpdateStep("Adding new request rate limit settings");
Settings::AddNew("system.req_limit_per_interval", "60");
Settings::AddNew("system.req_limit_interval", "60");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202304260');
}
if (Froxlor::isFroxlorVersion('2.0.15')) {
Update::showUpdateStep("Updating from 2.0.15 to 2.0.16", false);
Froxlor::updateToVersion('2.0.16');
}
if (Froxlor::isFroxlorVersion('2.0.16')) {
Update::showUpdateStep("Updating from 2.0.16 to 2.0.17", false);
Froxlor::updateToVersion('2.0.17');
}
if (Froxlor::isFroxlorVersion('2.0.17')) {
Update::showUpdateStep("Updating from 2.0.17 to 2.0.18", false);
Froxlor::updateToVersion('2.0.18');
}
if (Froxlor::isFroxlorVersion('2.0.18')) {
Update::showUpdateStep("Updating from 2.0.18 to 2.0.19", false);
Froxlor::updateToVersion('2.0.19');
}
if (Froxlor::isFroxlorVersion('2.0.19')) {
Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
Froxlor::updateToVersion('2.0.20');
}
if (Froxlor::isFroxlorVersion('2.0.20')) {
Update::showUpdateStep("Updating from 2.0.20 to 2.0.21", false);
Froxlor::updateToVersion('2.0.21');
}
if (Froxlor::isFroxlorVersion('2.0.21')) {
Update::showUpdateStep("Updating from 2.0.21 to 2.0.22", false);
Froxlor::updateToVersion('2.0.22');
}
if (Froxlor::isFroxlorVersion('2.0.22')) {
Update::showUpdateStep("Updating from 2.0.22 to 2.0.23", false);
Froxlor::updateToVersion('2.0.23');
}

View File

@@ -54,7 +54,7 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/'); $config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's // show list of available distro's
$distros = glob($config_dir . '*.xml'); $distros = glob($config_dir . '*.xml');
$distributions_select[''] = '-'; // selection is required $distributions_select[''] = '-';
// read in all the distros // read in all the distros
foreach ($distros as $_distribution) { foreach ($distros as $_distribution) {
// get configparser object // get configparser object

View File

@@ -26,6 +26,7 @@
namespace Froxlor\Api; namespace Froxlor\Api;
use Exception; use Exception;
use Froxlor\Http\RateLimiter;
use Froxlor\Settings; use Froxlor\Settings;
use voku\helper\AntiXSS; use voku\helper\AntiXSS;
@@ -52,6 +53,8 @@ class Api
if (Settings::Get('api.enabled') != 1) { if (Settings::Get('api.enabled') != 1) {
throw new Exception('API is not enabled. Please contact the administrator if you think this is wrong.', 400); throw new Exception('API is not enabled. Please contact the administrator if you think this is wrong.', 400);
} }
RateLimiter::run();
} }
/** /**

View File

@@ -39,12 +39,12 @@ abstract class ApiParameter
/** /**
* *
* @param array $params * @param array|null $params
* optional, array of parameters (var=>value) for the command * optional, array of parameters (var=>value) for the command
* *
* @throws Exception * @throws Exception
*/ */
public function __construct($params = null) public function __construct(array $params = null)
{ {
if (!is_null($params)) { if (!is_null($params)) {
$params = $this->trimArray($params); $params = $this->trimArray($params);
@@ -57,7 +57,7 @@ abstract class ApiParameter
* *
* @param array $input * @param array $input
* *
* @return array * @return string|array
*/ */
private function trimArray($input) private function trimArray($input)
{ {
@@ -79,9 +79,9 @@ abstract class ApiParameter
/** /**
* get specific parameter which also has and unlimited-field * get specific parameter which also has and unlimited-field
* *
* @param string $param * @param string|null $param
* parameter to get out of the request-parameter list * parameter to get out of the request-parameter list
* @param string $ul_field * @param string|null $ul_field
* parameter to get out of the request-parameter list * parameter to get out of the request-parameter list
* @param bool $optional * @param bool $optional
* default: false * default: false
@@ -91,7 +91,7 @@ abstract class ApiParameter
* @return mixed * @return mixed
* @throws Exception * @throws Exception
*/ */
protected function getUlParam($param = null, $ul_field = null, $optional = false, $default = 0) protected function getUlParam(string $param = null, string $ul_field = null, bool $optional = false, $default = 0)
{ {
$param_value = (int)$this->getParam($param, $optional, $default); $param_value = (int)$this->getParam($param, $optional, $default);
$ul_field_value = $this->getBoolParam($ul_field, true, 0); $ul_field_value = $this->getBoolParam($ul_field, true, 0);
@@ -102,11 +102,11 @@ abstract class ApiParameter
} }
/** /**
* get specific parameter from the parameterlist; * get specific parameter from the parameter list;
* check for existence and != empty if needed. * check for existence and != empty if needed.
* Maybe more in the future * Maybe more in the future
* *
* @param string $param * @param string|null $param
* parameter to get out of the request-parameter list * parameter to get out of the request-parameter list
* @param bool $optional * @param bool $optional
* default: false * default: false
@@ -116,7 +116,7 @@ abstract class ApiParameter
* @return mixed * @return mixed
* @throws Exception * @throws Exception
*/ */
protected function getParam($param = null, $optional = false, $default = '') protected function getParam(string $param = null, bool $optional = false, $default = '')
{ {
// does it exist? // does it exist?
if (!isset($this->cmd_params[$param])) { if (!isset($this->cmd_params[$param])) {
@@ -128,7 +128,7 @@ abstract class ApiParameter
return $default; return $default;
} }
// is it empty? - test really on string, as value 0 is being seen as empty by php // is it empty? - test really on string, as value 0 is being seen as empty by php
if ($this->cmd_params[$param] === "") { if (!is_array($this->cmd_params[$param]) && trim($this->cmd_params[$param]) === "") {
if ($optional === false) { if ($optional === false) {
// get module + function for better error-messages // get module + function for better error-messages
$inmod = $this->getModFunctionString(); $inmod = $this->getModFunctionString();
@@ -142,7 +142,7 @@ abstract class ApiParameter
/** /**
* returns "module::function()" for better error-messages (missing parameter etc.) * returns "module::function()" for better error-messages (missing parameter etc.)
* makes debugging a whole lot more comfortable * makes debugging a lot more comfortable
* *
* @param int $level * @param int $level
* depth of backtrace, default 2 * depth of backtrace, default 2
@@ -152,7 +152,7 @@ abstract class ApiParameter
* *
* @return string * @return string
*/ */
private function getModFunctionString($level = 1, $max_level = 5, $trace = null) private function getModFunctionString(int $level = 1, int $max_level = 5, $trace = null)
{ {
// which class called us // which class called us
$_class = get_called_class(); $_class = get_called_class();
@@ -174,7 +174,7 @@ abstract class ApiParameter
/** /**
* getParam wrapper for boolean parameter * getParam wrapper for boolean parameter
* *
* @param string $param * @param string|null $param
* parameter to get out of the request-parameter list * parameter to get out of the request-parameter list
* @param bool $optional * @param bool $optional
* default: false * default: false
@@ -183,7 +183,7 @@ abstract class ApiParameter
* *
* @return string * @return string
*/ */
protected function getBoolParam($param = null, $optional = false, $default = false) protected function getBoolParam(string $param = null, bool $optional = false, $default = false)
{ {
$_default = '0'; $_default = '0';
if ($default) { if ($default) {

View File

@@ -95,7 +95,7 @@ class Admins extends ApiCommand implements ResourceEntity
public function listing() public function listing()
{ {
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list admins"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list admins");
$query_fields = []; $query_fields = [];
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("
SELECT * SELECT *
@@ -407,7 +407,7 @@ class Admins extends ApiCommand implements ResourceEntity
]; ];
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get admin '" . $result['loginname'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get admin '" . $result['loginname'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -584,6 +584,18 @@ class Admins extends ApiCommand implements ResourceEntity
$theme = Settings::Get('panel.default_theme'); $theme = Settings::Get('panel.default_theme');
} }
if (empty(trim($name))) {
Response::standardError([
'stringisempty',
'admin.name'
], '', true);
}
if (empty(trim($email))) {
Response::standardError([
'stringisempty',
'admin.email'
], '', true);
}
if (!Validate::validateEmail($email)) { if (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true); Response::standardError('emailiswrong', $email, true);
} else { } else {
@@ -705,7 +717,7 @@ class Admins extends ApiCommand implements ResourceEntity
WHERE `adminid` = :adminid WHERE `adminid` = :adminid
"); ");
Database::pexecute($upd_stmt, $upd_data, true, true); Database::pexecute($upd_stmt, $upd_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited admin '" . $result['loginname'] . "'");
// get all admin-data for return-array // get all admin-data for return-array
$result = $this->apiCall('Admins.get', [ $result = $this->apiCall('Admins.get', [

View File

@@ -97,7 +97,7 @@ class Certificates extends ApiCommand implements ResourceEntity
} }
if (!$has_cert) { if (!$has_cert) {
$this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true); $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ssl-certificate for '" . $domain['domain'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ssl-certificate for '" . $domain['domain'] . "'");
$result = $this->apiCall('Certificates.get', [ $result = $this->apiCall('Certificates.get', [
'id' => $domain['id'] 'id' => $domain['id']
]); ]);
@@ -248,7 +248,7 @@ class Certificates extends ApiCommand implements ResourceEntity
$ssl_ca_file = $this->getParam('ssl_ca_file', true, ''); $ssl_ca_file = $this->getParam('ssl_ca_file', true, '');
$ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, ''); $ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, '');
$this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, false); $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, false);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ssl-certificate for '" . $domain['domain'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ssl-certificate for '" . $domain['domain'] . "'");
$result = $this->apiCall('Certificates.get', [ $result = $this->apiCall('Certificates.get', [
'id' => $domain['id'] 'id' => $domain['id']
]); ]);
@@ -470,7 +470,7 @@ class Certificates extends ApiCommand implements ResourceEntity
if ($chk['letsencrypt'] == '1') { if ($chk['letsencrypt'] == '1') {
Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $chk['domain']); Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $chk['domain']);
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] removed ssl-certificate for '" . $chk['domain'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] removed ssl-certificate for '" . $chk['domain'] . "'");
return $this->response($result); return $this->response($result);
} }
throw new Exception("Unable to determine SSL certificate. Maybe no access?", 406); throw new Exception("Unable to determine SSL certificate. Maybe no access?", 406);

View File

@@ -147,7 +147,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
// insert task to re-generate the cron.d-file // insert task to re-generate the cron.d-file
Cronjob::inserttask(TaskId::REBUILD_CRON); Cronjob::inserttask(TaskId::REBUILD_CRON);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('Cronjobs.get', [ $result = $this->apiCall('Cronjobs.get', [
'id' => $id 'id' => $id
]); ]);
@@ -177,7 +177,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
public function listing() public function listing()
{ {
if ($this->isAdmin()) { if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list cronjobs"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list cronjobs");
$query_fields = []; $query_fields = [];
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("
SELECT `c`.* FROM `" . TABLE_PANEL_CRONRUNS . "` `c` " . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()); SELECT `c`.* FROM `" . TABLE_PANEL_CRONRUNS . "` `c` " . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit());

View File

@@ -194,7 +194,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
$result[] = $entry; $result[] = $entry;
} }
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list customer-backups"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result

View File

@@ -895,7 +895,7 @@ class Customers extends ApiCommand implements ResourceEntity
$result['dbspace_used'] = 0; $result['dbspace_used'] = 0;
} }
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get customer '" . $result['loginname'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -1327,7 +1327,7 @@ class Customers extends ApiCommand implements ResourceEntity
'vu' => $valid_until 'vu' => $valid_until
], true, true); ], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
} }
@@ -1538,7 +1538,7 @@ class Customers extends ApiCommand implements ResourceEntity
Database::query($admin_update_query); Database::query($admin_update_query);
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited user '" . $result['loginname'] . "'");
/* /*
* move customer to another admin/reseller; #1166 * move customer to another admin/reseller; #1166
@@ -1911,7 +1911,7 @@ class Customers extends ApiCommand implements ResourceEntity
// now, recalculate the resource-usage for the old and the new admin // now, recalculate the resource-usage for the old and the new admin
User::updateCounters(false); User::updateCounters(false);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'");
$result = $this->apiCall('Customers.get', [ $result = $this->apiCall('Customers.get', [
'id' => $c_result['customerid'] 'id' => $c_result['customerid']

View File

@@ -144,7 +144,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
]; ];
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId(); $id = Database::lastInsertId();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-option for '" . $userpath . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-option for '" . $userpath . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$result = $this->apiCall('DirOptions.get', [ $result = $this->apiCall('DirOptions.get', [
@@ -247,7 +247,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
$params['id'] = $id; $params['id'] = $id;
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory options for '" . $result['path'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory options for '" . $result['path'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = "id #" . $id; $key = "id #" . $id;
@@ -331,7 +331,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"id" => $id "id" => $id
]; ];
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'");
} }
$result = $this->apiCall('DirOptions.get', [ $result = $this->apiCall('DirOptions.get', [
@@ -379,7 +379,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list directory-options"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-options");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result
@@ -478,7 +478,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"customerid" => $customer_data['customerid'], "customerid" => $customer_data['customerid'],
"id" => $id "id" => $id
], true, true); ], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
return $this->response($result); return $this->response($result);
} }

View File

@@ -129,7 +129,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
]; ];
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId(); $id = Database::lastInsertId();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-protection for '" . $username . " (" . $path . ")'");
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$result = $this->apiCall('DirProtections.get', [ $result = $this->apiCall('DirProtections.get', [
@@ -196,7 +196,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id); $params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory protection for '" . $result['path'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory protection for '" . $result['path'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'"); $key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -279,7 +279,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'");
$result = $this->apiCall('DirProtections.get', [ $result = $this->apiCall('DirProtections.get', [
'id' => $result['id'] 'id' => $result['id']
]); ]);
@@ -325,7 +325,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list directory-protections"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-protections");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result
@@ -413,7 +413,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
"id" => $id "id" => $id
]); ]);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'");
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
return $this->response($result); return $this->response($result);
} }

View File

@@ -413,7 +413,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
$zone = Dns::createDomainZone($id); $zone = Dns::createDomainZone($id);
$zonefile = (string)$zone; $zonefile = (string)$zone;
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get dns-zone for '" . $result['domain'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get dns-zone for '" . $result['domain'] . "'");
return $this->response(explode("\n", $zonefile)); return $this->response(explode("\n", $zonefile));
} }

View File

@@ -225,6 +225,8 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, whether php is enabled for this domain, default 0 (false) * optional, whether php is enabled for this domain, default 0 (false)
* @param bool $openbasedir * @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false) * optional, whether to activate openbasedir restriction for this domain, default 0 (false)
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid * @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default) * optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter * @param int $mod_fcgid_starter
@@ -312,6 +314,7 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $this->getParam('documentroot', true, ''); $documentroot = $this->getParam('documentroot', true, '');
$phpenabled = $this->getBoolParam('phpenabled', true, 0); $phpenabled = $this->getBoolParam('phpenabled', true, 0);
$openbasedir = $this->getBoolParam('openbasedir', true, 0); $openbasedir = $this->getBoolParam('openbasedir', true, 0);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 1); $phpsettingid = $this->getParam('phpsettingid', true, 1);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1); $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1); $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
@@ -530,6 +533,10 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = '-1'; $mod_fcgid_maxrequests = '-1';
} }
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP // check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports); $ipandports = $this->validateIpAddresses($p_ipandports);
// check ssl IP // check ssl IP
@@ -701,6 +708,7 @@ class Domains extends ApiCommand implements ResourceEntity
'caneditdomain' => $caneditdomain, 'caneditdomain' => $caneditdomain,
'phpenabled' => $phpenabled, 'phpenabled' => $phpenabled,
'openbasedir' => $openbasedir, 'openbasedir' => $openbasedir,
'openbasedir_path' => $openbasedir_path,
'speciallogfile' => $speciallogfile, 'speciallogfile' => $speciallogfile,
'specialsettings' => $specialsettings, 'specialsettings' => $specialsettings,
'ssl_specialsettings' => $ssl_specialsettings, 'ssl_specialsettings' => $ssl_specialsettings,
@@ -754,6 +762,7 @@ class Domains extends ApiCommand implements ResourceEntity
`caneditdomain` = :caneditdomain, `caneditdomain` = :caneditdomain,
`phpenabled` = :phpenabled, `phpenabled` = :phpenabled,
`openbasedir` = :openbasedir, `openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile, `speciallogfile` = :speciallogfile,
`specialsettings` = :specialsettings, `specialsettings` = :specialsettings,
`ssl_specialsettings` = :ssl_specialsettings, `ssl_specialsettings` = :ssl_specialsettings,
@@ -889,7 +898,7 @@ class Domains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']); $result['ipsandports'] = $this->getIpsForDomain($result['id']);
} }
$result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']); $result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get domain '" . $result['domain'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get domain '" . $result['domain'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -1101,6 +1110,8 @@ class Domains extends ApiCommand implements ResourceEntity
* from setting system.apply_phpconfigs_default * from setting system.apply_phpconfigs_default
* @param bool $openbasedir * @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false) * optional, whether to activate openbasedir restriction for this domain, default 0 (false)
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid * @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default) * optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter * @param int $mod_fcgid_starter
@@ -1198,6 +1209,7 @@ class Domains extends ApiCommand implements ResourceEntity
$phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']); $phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']);
$phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default')); $phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default'));
$openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']); $openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']);
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']); $phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']); $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']); $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']);
@@ -1489,6 +1501,11 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests'];
} }
// check changes of openbasedir-path variable
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP // check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']); $ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']);
// check ssl IP // check ssl IP
@@ -1634,7 +1651,31 @@ class Domains extends ApiCommand implements ResourceEntity
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
$iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0';
if ($documentroot != $result['documentroot'] || $ssl_redirect != $result['ssl_redirect'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $phpenabled != $result['phpenabled'] || $openbasedir != $result['openbasedir'] || $phpsettingid != $result['phpsettingid'] || $mod_fcgid_starter != $result['mod_fcgid_starter'] || $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || $specialsettings != $result['specialsettings'] || $ssl_specialsettings != $result['ssl_specialsettings'] || $notryfiles != $result['notryfiles'] || $writeaccesslog != $result['writeaccesslog'] || $writeerrorlog != $result['writeerrorlog'] || $aliasdomain != $result['aliasdomain'] || $issubof != $result['ismainbutsubto'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] || $http2 != $result['http2'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $ocsp_stapling != $result['ocsp_stapling']) { if ($documentroot != $result['documentroot']
|| $ssl_redirect != $result['ssl_redirect']
|| $wwwserveralias != $result['wwwserveralias']
|| $iswildcarddomain != $result['iswildcarddomain']
|| $phpenabled != $result['phpenabled']
|| $openbasedir != $result['openbasedir']
|| $phpsettingid != $result['phpsettingid']
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
|| $specialsettings != $result['specialsettings']
|| $ssl_specialsettings != $result['ssl_specialsettings']
|| $notryfiles != $result['notryfiles']
|| $writeaccesslog != $result['writeaccesslog']
|| $writeerrorlog != $result['writeerrorlog']
|| $aliasdomain != $result['aliasdomain']
|| $issubof != $result['ismainbutsubto']
|| $email_only != $result['email_only']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|| $letsencrypt != $result['letsencrypt']
|| $http2 != $result['http2']
|| $hsts_maxage != $result['hsts']
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $ocsp_stapling != $result['ocsp_stapling']
) {
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
} }
@@ -1760,7 +1801,7 @@ class Domains extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, [ Database::pexecute($upd_stmt, [
'id' => $id 'id' => $id
], true, true); ], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] removed specialsettings on all subdomains of domain #" . $id); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] removed specialsettings on all subdomains of domain #" . $id);
} }
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
@@ -1782,7 +1823,8 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias; $update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain; $update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled; $update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir; $update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile; $update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid; $update_data['phpsettingid'] = $phpsettingid;
$update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
@@ -1830,6 +1872,7 @@ class Domains extends ApiCommand implements ResourceEntity
`iswildcarddomain` = :iswildcarddomain, `iswildcarddomain` = :iswildcarddomain,
`phpenabled` = :phpenabled, `phpenabled` = :phpenabled,
`openbasedir` = :openbasedir, `openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile, `speciallogfile` = :speciallogfile,
`phpsettingid` = :phpsettingid, `phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_starter` = :mod_fcgid_starter,
@@ -1865,6 +1908,7 @@ class Domains extends ApiCommand implements ResourceEntity
$_update_data['adminid'] = $adminid; $_update_data['adminid'] = $adminid;
$_update_data['phpenabled'] = $phpenabled; $_update_data['phpenabled'] = $phpenabled;
$_update_data['openbasedir'] = $openbasedir; $_update_data['openbasedir'] = $openbasedir;
$_update_data['openbasedir_path'] = $openbasedir_path;
$_update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $_update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
$_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; $_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests;
$_update_data['notryfiles'] = $notryfiles; $_update_data['notryfiles'] = $notryfiles;
@@ -1898,6 +1942,7 @@ class Domains extends ApiCommand implements ResourceEntity
`adminid` = :adminid, `adminid` = :adminid,
`phpenabled` = :phpenabled, `phpenabled` = :phpenabled,
`openbasedir` = :openbasedir, `openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`notryfiles` = :notryfiles, `notryfiles` = :notryfiles,
@@ -1914,6 +1959,18 @@ class Domains extends ApiCommand implements ResourceEntity
"); ");
Database::pexecute($_update_stmt, $_update_data, true, true); Database::pexecute($_update_stmt, $_update_data, true, true);
// get current ip<>domain entries
$ip_sel_stmt = Database::prepare("
SELECT id_ipandports FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id
");
Database::pexecute($ip_sel_stmt, [
'id' => $id
], true, true);
$current_ips = [];
while ($cIP = $ip_sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$current_ips[] = $cIP['id_ipandports'];
}
// Cleanup domain <-> ip mapping // Cleanup domain <-> ip mapping
$del_stmt = Database::prepare(" $del_stmt = Database::prepare("
DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id
@@ -1941,6 +1998,12 @@ class Domains extends ApiCommand implements ResourceEntity
} }
} }
// check ip changes
$all_new_ips = array_merge($ipandports, $ssl_ipandports);
if (count(array_diff($current_ips, $all_new_ips)) != 0 || count(array_diff($all_new_ips, $current_ips)) != 0) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
// Cleanup domain <-> ip mapping for subdomains // Cleanup domain <-> ip mapping for subdomains
$domainidsresult_stmt = Database::prepare(" $domainidsresult_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id
@@ -2158,7 +2221,7 @@ class Domains extends ApiCommand implements ResourceEntity
// remove domain from acme.sh / lets encrypt if used // remove domain from acme.sh / lets encrypt if used
Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']); Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] deleted domain/subdomains (#" . $result['id'] . ")"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] deleted domain/subdomains (#" . $result['id'] . ")");
User::updateCounters(); User::updateCounters();
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
// Using nameserver, insert a task which rebuilds the server config // Using nameserver, insert a task which rebuilds the server config

View File

@@ -63,7 +63,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
* @param string $alternative_email * @param string $alternative_email
* optional email address to send account information to, default is the account that is being created * optional email address to send account information to, default is the account that is being created
* @param int $email_quota * @param int $email_quota
* optional quota if enabled in MB, default 0 * optional quota if enabled in MB, default setting: system.mail_quota
* @param bool $sendinfomail * @param bool $sendinfomail
* optional, sends the welcome message to the new account (needed for creation, without the user won't * optional, sends the welcome message to the new account (needed for creation, without the user won't
* be able to login before any mail is received), default 1 (true) * be able to login before any mail is received), default 1 (true)
@@ -85,7 +85,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$emailaddr = $this->getParam('emailaddr', $ea_optional, ''); $emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$email_password = $this->getParam('email_password'); $email_password = $this->getParam('email_password');
$alternative_email = $this->getParam('alternative_email', true, ''); $alternative_email = $this->getParam('alternative_email', true, '');
$quota = $this->getParam('email_quota', true, 0); $quota = $this->getParam('email_quota', true, Settings::Get('system.mail_quota') ?? 0);
$sendinfomail = $this->getBoolParam('sendinfomail', true, 1); $sendinfomail = $this->getBoolParam('sendinfomail', true, 1);
// validation // validation
@@ -99,6 +99,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Response::standardError('notallowedtouseaccounts', '', true); Response::standardError('notallowedtouseaccounts', '', true);
} }
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// get email address // get email address
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'id' => $id, 'id' => $id,
@@ -306,7 +311,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
} }
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email account for '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email account for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full'] 'emailaddr' => $result['email_full']
]); ]);
@@ -357,6 +362,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$ea_optional = $id > 0; $ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, ''); $emailaddr = $this->getParam('emailaddr', $ea_optional, '');
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// validation // validation
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'id' => $id, 'id' => $id,
@@ -450,7 +460,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', ($quota - $result['quota'])); Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', ($quota - $result['quota']));
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated email account '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated email account '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full'] 'emailaddr' => $result['email_full']
]); ]);
@@ -556,7 +566,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Customers::decreaseUsage($customer['customerid'], 'email_accounts_used'); Customers::decreaseUsage($customer['customerid'], 'email_accounts_used');
Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $quota); Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $quota);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email account for '" . $result['email_full'] . "'");
return $this->response($result); return $this->response($result);
} }
} }

View File

@@ -89,7 +89,7 @@ class EmailDomains extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO,
"[API] list email-domains"); "[API] list email-domains");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),

View File

@@ -77,6 +77,11 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
$idna_convert = new IdnaWrapper(); $idna_convert = new IdnaWrapper();
$destination = $idna_convert->encode($destination); $destination = $idna_convert->encode($destination);
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'id' => $id, 'id' => $id,
'emailaddr' => $emailaddr 'emailaddr' => $emailaddr
@@ -116,7 +121,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
// update customer usage // update customer usage
Customers::increaseUsage($customer['customerid'], 'email_forwarders_used'); Customers::increaseUsage($customer['customerid'], 'email_forwarders_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email forwarder for '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email forwarder for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full'] 'emailaddr' => $result['email_full']
@@ -293,7 +298,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
// update customer usage // update customer usage
Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used'); Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email forwarder for '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted email forwarder for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full'] 'emailaddr' => $result['email_full']

View File

@@ -159,7 +159,7 @@ class Emails extends ApiCommand implements ResourceEntity
// update customer usage // update customer usage
Customers::increaseUsage($customer['customerid'], 'emails_used'); Customers::increaseUsage($customer['customerid'], 'emails_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email address '" . $email_full . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'");
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'emailaddr' => $email_full 'emailaddr' => $email_full
@@ -195,11 +195,11 @@ class Emails extends ApiCommand implements ResourceEntity
FROM `" . TABLE_MAIL_VIRTUAL . "` v FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id` LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ") WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
AND (v.`id`= :idea OR (v.`email` = :idea OR v.`email_full` = :idea)) AND " . (is_numeric($params['idea']) ? "v.`id`= :idea" : "(v.`email` = :idea OR v.`email_full` = :idea)")
"); );
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get email address '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get email address '" . $result['email_full'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'"); $key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'");
@@ -294,7 +294,7 @@ class Emails extends ApiCommand implements ResourceEntity
"id" => $id "id" => $id
]; ];
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [ $result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full'] 'emailaddr' => $result['email_full']
@@ -340,7 +340,7 @@ class Emails extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list email-addresses"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result
@@ -445,7 +445,7 @@ class Emails extends ApiCommand implements ResourceEntity
], true, true); ], true, true);
Customers::decreaseUsage($customer['customerid'], 'emails_used'); Customers::decreaseUsage($customer['customerid'], 'emails_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email address '" . $result['email_full'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email address '" . $result['email_full'] . "'");
return $this->response($result); return $this->response($result);
} }
} }

View File

@@ -64,7 +64,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
public function listing() public function listing()
{ {
if ($this->isAdmin()) { if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list fpm-daemons"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list fpm-daemons");
$query_fields = []; $query_fields = [];
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "`" . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()); SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "`" . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit());
@@ -258,7 +258,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
$id = Database::lastInsertId(); $id = Database::lastInsertId();
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('FpmDaemons.get', [ $result = $this->apiCall('FpmDaemons.get', [
'id' => $id 'id' => $id
]); ]);
@@ -384,7 +384,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, $upd_data, true, true); Database::pexecute($upd_stmt, $upd_data, true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('FpmDaemons.get', [ $result = $this->apiCall('FpmDaemons.get', [
'id' => $id 'id' => $id
]); ]);
@@ -433,7 +433,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
], true, true); ], true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'");
return $this->response($result); return $this->response($result);
} }
throw new Exception("Not allowed to execute given command.", 403); throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -72,7 +72,7 @@ class Froxlor extends ApiCommand
if (empty($uc_data) || empty($response) || $uc_data['ts'] + self::UPDATE_CHECK_INTERVAL < time() || $uc_data['channel'] != Settings::Get('system.update_channel') || $force_ucheck) { if (empty($uc_data) || empty($response) || $uc_data['ts'] + self::UPDATE_CHECK_INTERVAL < time() || $uc_data['channel'] != Settings::Get('system.update_channel') || $force_ucheck) {
// log our actions // log our actions
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] checking for updates"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] checking for updates");
// check for new version // check for new version
$aucheck = AutoUpdate::checkVersion(); $aucheck = AutoUpdate::checkVersion();
@@ -142,7 +142,7 @@ class Froxlor extends ApiCommand
{ {
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$json_str = $this->getParam('json_str'); $json_str = $this->getParam('json_str');
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "User " . $this->getUserDetail('loginname') . " imported settings"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "User " . $this->getUserDetail('loginname') . " imported settings");
try { try {
SImExporter::import($json_str); SImExporter::import($json_str);
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);

View File

@@ -257,7 +257,7 @@ class Ftps extends ApiCommand implements ResourceEntity
Customers::increaseUsage($customer['customerid'], 'ftp_lastaccountnumber'); Customers::increaseUsage($customer['customerid'], 'ftp_lastaccountnumber');
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ftp-account '" . $username . " (" . $path . ")'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-account '" . $username . " (" . $path . ")'");
Cronjob::inserttask(TaskId::CREATE_FTP); Cronjob::inserttask(TaskId::CREATE_FTP);
if ($sendinfomail == 1) { if ($sendinfomail == 1) {
@@ -302,7 +302,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$this->mailer()->clearAddresses(); $this->mailer()->clearAddresses();
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-user '" . $username . "'");
$result = $this->apiCall('Ftps.get', [ $result = $this->apiCall('Ftps.get', [
'username' => $username 'username' => $username
@@ -367,7 +367,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id); $params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get ftp-user '" . $result['username'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get ftp-user '" . $result['username'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'"); $key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -453,7 +453,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"id" => $id, "id" => $id,
"password" => $cryptPassword "password" => $cryptPassword
], true, true); ], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account password for '" . $result['username'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account password for '" . $result['username'] . "'");
} }
// path update? // path update?
@@ -471,7 +471,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"customerid" => $customer['customerid'], "customerid" => $customer['customerid'],
"id" => $id "id" => $id
], true, true); ], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account homdir for '" . $result['username'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account homdir for '" . $result['username'] . "'");
} }
} }
// it's the task for "new ftp" but that will // it's the task for "new ftp" but that will
@@ -533,7 +533,7 @@ class Ftps extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list ftp-users"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list ftp-users");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result

View File

@@ -61,7 +61,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
public function listing() public function listing()
{ {
if ($this->isAdmin()) { if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list hosting-plans"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list hosting-plans");
$query_fields = []; $query_fields = [];
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("
SELECT p.*, a.loginname as adminname SELECT p.*, a.loginname as adminname
@@ -200,8 +200,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, 0); $value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, 0);
// validation // validation
$name = Validate::validate(trim($name), 'name', '', '', [], true); $name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT); $description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') { if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1; $value_arr['email_quota'] = -1;
@@ -227,7 +227,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'valuearr' => json_encode($value_arr) 'valuearr' => json_encode($value_arr)
]; ];
Database::pexecute($ins_stmt, $ins_data, true, true); Database::pexecute($ins_stmt, $ins_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added hosting-plan '" . $name . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added hosting-plan '" . $name . "'");
$result = $this->apiCall('HostingPlans.get', [ $result = $this->apiCall('HostingPlans.get', [
'planname' => $name 'planname' => $name
]); ]);
@@ -264,7 +264,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
} }
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get hosting-plan '" . $result['name'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get hosting-plan '" . $result['name'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "planname '" . $planname . "'"); $key = ($id > 0 ? "id #" . $id : "planname '" . $planname . "'");
@@ -382,8 +382,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, $result['logviewenabled']); $value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, $result['logviewenabled']);
// validation // validation
$name = Validate::validate(trim($name), 'name', '', '', [], true); $name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT); $description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') { if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1; $value_arr['email_quota'] = -1;
@@ -414,7 +414,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'id' => $id 'id' => $id
]; ];
Database::pexecute($upd_stmt, $update_data, true, true); Database::pexecute($upd_stmt, $update_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] updated hosting-plan '" . $result['name'] . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated hosting-plan '" . $result['name'] . "'");
return $this->response($update_data); return $this->response($update_data);
} }
throw new Exception("Not allowed to execute given command.", 403); throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -65,7 +65,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
public function listing() public function listing()
{ {
if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || !empty($this->getUserDetail('ip')))) { if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || !empty($this->getUserDetail('ip')))) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list ips and ports"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list ips and ports");
$ip_where = ""; $ip_where = "";
$append_where = false; $append_where = false;
if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) { if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) {
@@ -175,9 +175,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
$docroot = Validate::validate($this->getParam('docroot', true, ''), 'docroot', Validate::REGEX_DIR, '', [], true); $docroot = Validate::validate($this->getParam('docroot', true, ''), 'docroot', Validate::REGEX_DIR, '', [], true);
if ((int)Settings::Get('system.use_ssl') == 1) { if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = !empty($this->getBoolParam('ssl', true, 0)) ? intval($this->getBoolParam('ssl', true, 0)) : 0; $ssl = (bool)$this->getBoolParam('ssl', true, 0);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $ssl, ''), 'ssl_cert_file', '', '', [], true); $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); $ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, ''), 'ssl_key_file', '', '', [], true);
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_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); $ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
$sslss = $this->getParam('ssl_specialsettings', true, ''); $sslss = $this->getParam('ssl_specialsettings', true, '');
@@ -335,7 +335,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
'id' => $id 'id' => $id
], true, true); ], true, true);
if ($result) { if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get ip " . $result['ip'] . " " . $result['port']); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get ip " . $result['ip'] . " " . $result['port']);
return $this->response($result); return $this->response($result);
} }
throw new Exception("IP/port with id #" . $id . " could not be found", 404); throw new Exception("IP/port with id #" . $id . " could not be found", 404);
@@ -414,9 +414,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
$docroot = Validate::validate($this->getParam('docroot', true, $result['docroot']), 'docroot', Validate::REGEX_DIR, '', [], true); $docroot = Validate::validate($this->getParam('docroot', true, $result['docroot']), 'docroot', Validate::REGEX_DIR, '', [], true);
if ((int)Settings::Get('system.use_ssl') == 1) { if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = $this->getBoolParam('ssl', true, $result['ssl']); $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_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); $ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, $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_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); $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']); $sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);

View File

@@ -26,14 +26,15 @@
namespace Froxlor\Api\Commands; namespace Froxlor\Api\Commands;
use Exception; use Exception;
use PDO;
use PDOException;
use Froxlor\Froxlor;
use Froxlor\PhpHelper;
use Froxlor\Api\ApiCommand; use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity; use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
use Froxlor\Validate\Validate; use Froxlor\Validate\Validate;
use PDO;
use PDOException;
class MysqlServer extends ApiCommand implements ResourceEntity class MysqlServer extends ApiCommand implements ResourceEntity
{ {
@@ -73,8 +74,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional, test connection with given credentials, default is true (yes) * optional, test connection with given credentials, default is true (yes)
* *
* @access admin * @access admin
* @throws Exception
* @return string json-encoded array * @return string json-encoded array
* @throws Exception
*/ */
public function add() public function add()
{ {
@@ -112,7 +113,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
); );
if (!empty($mysql_ca)) { if (!empty($mysql_ca)) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca; $options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca;
$options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool) $mysql_verifycert; $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool)$mysql_verifycert;
} }
$dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";"; $dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";";
@@ -167,6 +168,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
$this->addDatabaseFromCustomerAllowedList($newdbserver); $this->addDatabaseFromCustomerAllowedList($newdbserver);
} }
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added new database server '" . $description . "' (" . $mysql_host . ")");
return $this->response(['dbserver' => $newdbserver]); return $this->response(['dbserver' => $newdbserver]);
} }
@@ -179,16 +182,16 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional the number of the mysql server (either id or dbserver must be set) * optional the number of the mysql server (either id or dbserver must be set)
* *
* @access admin * @access admin
* @throws Exception
* @return string json-encoded array * @return string json-encoded array
* @throws Exception
*/ */
public function delete() public function delete()
{ {
$this->validateAccess(); $this->validateAccess();
$id = (int) $this->getParam('id', true, -1); $id = (int)$this->getParam('id', true, -1);
$dn_optional = $id >= 0; $dn_optional = $id >= 0;
$dbserver = (int) $this->getParam('dbserver', $dn_optional, -1); $dbserver = (int)$this->getParam('dbserver', $dn_optional, -1);
$dbserver = $id >= 0 ? $id : $dbserver; $dbserver = $id >= 0 ? $id : $dbserver;
if ($dbserver == 0) { if ($dbserver == 0) {
@@ -212,8 +215,12 @@ class MysqlServer extends ApiCommand implements ResourceEntity
// when removing, remove from list of allowed_mysqlservers from any customers // when removing, remove from list of allowed_mysqlservers from any customers
$this->removeDatabaseFromCustomerAllowedList($dbserver); $this->removeDatabaseFromCustomerAllowedList($dbserver);
$description = $sql_root[$dbserver]['caption'] ?? "unknown";
$mysql_host = $sql_root[$dbserver]['host'] ?? "unknown";
unset($sql_root[$dbserver]); unset($sql_root[$dbserver]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] removed database server '" . $description . "' (" . $mysql_host . ")");
$this->generateNewUserData($sql, $sql_root); $this->generateNewUserData($sql, $sql_root);
return $this->response(['true']); return $this->response(['true']);
} }
@@ -287,14 +294,14 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional the number of the mysql server (either id or dbserver must be set) * optional the number of the mysql server (either id or dbserver must be set)
* *
* @access admin, customer * @access admin, customer
* @throws Exception
* @return string json-encoded array * @return string json-encoded array
* @throws Exception
*/ */
public function get() public function get()
{ {
$id = (int) $this->getParam('id', true, -1); $id = (int)$this->getParam('id', true, -1);
$dn_optional = $id >= 0; $dn_optional = $id >= 0;
$dbserver = (int) $this->getParam('dbserver', $dn_optional, -1); $dbserver = (int)$this->getParam('dbserver', $dn_optional, -1);
$dbserver = $id >= 0 ? $id : $dbserver; $dbserver = $id >= 0 ? $id : $dbserver;
$sql_root = []; $sql_root = [];
@@ -317,6 +324,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
unset($sql_root[$dbserver]['password']); unset($sql_root[$dbserver]['password']);
$sql_root[$dbserver]['id'] = $dbserver; $sql_root[$dbserver]['id'] = $dbserver;
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get database-server '" . $sql_root[$dbserver]['caption'] . "'");
return $this->response($sql_root[$dbserver]); return $this->response($sql_root[$dbserver]);
} }
@@ -347,16 +355,16 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional, test connection with given credentials, default is true (yes) * optional, test connection with given credentials, default is true (yes)
* *
* @access admin * @access admin
* @throws Exception
* @return string json-encoded array * @return string json-encoded array
* @throws Exception
*/ */
public function update() public function update()
{ {
$this->validateAccess(); $this->validateAccess();
$id = (int) $this->getParam('id', true, -1); $id = (int)$this->getParam('id', true, -1);
$dn_optional = $id >= 0; $dn_optional = $id >= 0;
$dbserver = (int) $this->getParam('dbserver', $dn_optional, -1); $dbserver = (int)$this->getParam('dbserver', $dn_optional, -1);
$dbserver = $id >= 0 ? $id : $dbserver; $dbserver = $id >= 0 ? $id : $dbserver;
$sql_root = []; $sql_root = [];
@@ -417,7 +425,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
); );
if (!empty($mysql_ca)) { if (!empty($mysql_ca)) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca; $options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca;
$options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool) $mysql_verifycert; $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool)$mysql_verifycert;
} }
$dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";"; $dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";";
@@ -448,6 +456,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
$this->addDatabaseFromCustomerAllowedList($dbserver); $this->addDatabaseFromCustomerAllowedList($dbserver);
} }
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] edited database server '" . $description . "' (" . $mysql_host . ")");
return $this->response(['true']); return $this->response(['true']);
} }
@@ -472,7 +482,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
WHERE `dbserver` = :dbserver WHERE `dbserver` = :dbserver
"); ");
$result = Database::pexecute_first($result_stmt, ['dbserver' => $dbserver], true, true); $result = Database::pexecute_first($result_stmt, ['dbserver' => $dbserver], true, true);
return (int) $result['num_dbs']; return (int)$result['num_dbs'];
} else { } else {
$dbserver = $this->getParam('mysql_server'); $dbserver = $this->getParam('mysql_server');
$customer_ids = $this->getAllowedCustomerIds(); $customer_ids = $this->getAllowedCustomerIds();
@@ -516,7 +526,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
`allowed_mysqlserver` = :am WHERE `customerid` = :cid `allowed_mysqlserver` = :am WHERE `customerid` = :cid
"); ");
while ($customer = $sel_stmt->fetch(PDO::FETCH_ASSOC)) { while ($customer = $sel_stmt->fetch(PDO::FETCH_ASSOC)) {
$allowed_mysqls = json_decode(($customer['allowed_mysqlserver'] ?? '[]'), true); $allowed_mysqls = json_decode(($customer['allowed_mysqlserver'] ?: '[]'), true);
if (!in_array($dbserver, $allowed_mysqls)) { if (!in_array($dbserver, $allowed_mysqls)) {
$allowed_mysqls[] = $dbserver; $allowed_mysqls[] = $dbserver;
$allowed_mysqls = json_encode($allowed_mysqls); $allowed_mysqls = json_encode($allowed_mysqls);

View File

@@ -199,7 +199,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$this->mailer()->clearAddresses(); $this->mailer()->clearAddresses();
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added mysql-database '" . $username . "'");
$result = $this->apiCall('Mysqls.get', [ $result = $this->apiCall('Mysqls.get', [
'dbname' => $username, 'dbname' => $username,
@@ -299,7 +299,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC); $mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC);
Database::needRoot(false); Database::needRoot(false);
$result['size'] = $mbdata['MB'] ?? 0; $result['size'] = $mbdata['MB'] ?? 0;
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get database '" . $result['databasename'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get database '" . $result['databasename'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "dbname '" . $dbname . "'"); $key = ($id > 0 ? "id #" . $id : "dbname '" . $dbname . "'");
@@ -388,7 +388,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
]; ];
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated mysql-database '" . $result['databasename'] . "'");
$result = $this->apiCall('Mysqls.get', [ $result = $this->apiCall('Mysqls.get', [
'dbname' => $result['databasename'] 'dbname' => $result['databasename']
]); ]);

View File

@@ -67,7 +67,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
public function listing() public function listing()
{ {
if ($this->isAdmin()) { if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list php-configs"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list php-configs");
$with_subdomains = $this->getBoolParam('with_subdomains', true, false); $with_subdomains = $this->getBoolParam('with_subdomains', true, false);
$query_fields = []; $query_fields = [];
@@ -392,7 +392,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$ins_data['id'] = Database::lastInsertId(); $ins_data['id'] = Database::lastInsertId();
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('PhpSettings.get', [ $result = $this->apiCall('PhpSettings.get', [
'id' => $ins_data['id'] 'id' => $ins_data['id']
@@ -629,7 +629,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, $upd_data, true, true); Database::pexecute($upd_stmt, $upd_data, true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('PhpSettings.get', [ $result = $this->apiCall('PhpSettings.get', [
'id' => $id 'id' => $id
@@ -686,7 +686,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
], true, true); ], true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'");
return $this->response($result); return $this->response($result);
} }
throw new Exception("Not allowed to execute given command.", 403); throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -62,7 +62,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, overwrites path value with an URL to generate a redirect, alternatively use the path * optional, overwrites path value with an URL to generate a redirect, alternatively use the path
* parameter also for URLs * parameter also for URLs
* @param int $openbasedir_path * @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot * optional, either 0 for domains-docroot [default], 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid * @param int $phpsettingid
* optional, php-settings-id, if empty the $domain value is used * optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode * @param int $redirectcode
@@ -104,7 +104,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$aliasdomain = $this->getParam('alias', true, 0); $aliasdomain = $this->getParam('alias', true, 0);
$path = $this->getParam('path', true, ''); $path = $this->getParam('path', true, '');
$url = $this->getParam('url', true, ''); $url = $this->getParam('url', true, '');
$openbasedir_path = $this->getParam('openbasedir_path', true, 1); $openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 0); $phpsettingid = $this->getParam('phpsettingid', true, 0);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default')); $redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$isemaildomain = $this->getParam('isemaildomain', true, 0); $isemaildomain = $this->getParam('isemaildomain', true, 0);
@@ -486,7 +486,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']); $result['ipsandports'] = $this->getIpsForDomain($result['id']);
} }
$result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']); $result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get subdomain '" . $result['domain'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get subdomain '" . $result['domain'] . "'");
return $this->response($result); return $this->response($result);
} }
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -856,7 +856,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS); Cronjob::inserttask(TaskId::REBUILD_DNS);
$idna_convert = new IdnaWrapper(); $idna_convert = new IdnaWrapper();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'");
} }
$result = $this->apiCall('SubDomains.get', [ $result = $this->apiCall('SubDomains.get', [
'id' => $id 'id' => $id

View File

@@ -92,7 +92,7 @@ class SysLog extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list log-entries"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result

View File

@@ -166,7 +166,7 @@ class Traffic extends ApiCommand implements ResourceEntity
$row['mail'] *= 1024; $row['mail'] *= 1024;
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list traffic"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list traffic");
return $this->response([ return $this->response([
'count' => count($result), 'count' => count($result),
'list' => $result 'list' => $result

View File

@@ -122,7 +122,7 @@ class CliCommand extends Command
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php'; include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1); define('_CRON_UPDATE', 1);
ob_start([ ob_start([
'this', $this,
'cleanUpdateOutput' 'cleanUpdateOutput'
]); ]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php'; include_once Froxlor::getInstallDir() . '/install/updatesql.php';

View File

@@ -0,0 +1,178 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Cli;
use Froxlor\Config\ConfigParser;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
final class ConfigDiff extends CliCommand
{
protected function configure(): void
{
$this->setName('froxlor:config-diff')
->setDescription('Shows differences in config templates between OS versions')
->addArgument('from', InputArgument::OPTIONAL, 'OS version to compare against')
->addArgument('to', InputArgument::OPTIONAL, 'OS version to compare from')
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all possible OS versions')
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require Froxlor::getInstallDir() . '/lib/functions.php';
$parsers = $versions = [];
foreach (glob(Froxlor::getInstallDir() . '/lib/configfiles/*.xml') as $config) {
$name = str_replace(".xml", "", strtolower(basename($config)));
$parser = new ConfigParser($config);
$versions[$name] = $parser->getCompleteDistroName();
$parsers[$name] = $parser;
}
asort($versions);
if ($input->getOption('list') === true) {
$output->writeln('The following OS version templates are available:');
foreach ($versions as $k => $v) {
$output->writeln(str_pad($k, 20) . $v);
}
return self::SUCCESS;
}
if (!$input->hasArgument('from') || !array_key_exists($input->getArgument('from'), $versions)) {
$output->writeln('<error>Missing or invalid "from" argument.</error>');
$output->writeln('Available versions: ' . implode(', ', array_keys($versions)));
return self::INVALID;
}
if (!$input->hasArgument('to') || !array_key_exists($input->getArgument('to'), $versions)) {
$output->writeln('<error>Missing or invalid "to" argument.</error>');
$output->writeln('Available versions: ' . implode(', ', array_keys($versions)));
return self::INVALID;
}
// Make sure diff is installed
$check_diff_installed = FileDir::safe_exec('which diff');
if (count($check_diff_installed) === 0) {
$output->writeln('<error>Unable to find "diff" installation on your system.</error>');
return self::INVALID;
}
$parser_from = $parsers[$input->getArgument('from')];
$parser_to = $parsers[$input->getArgument('to')];
$tmp_from = tempnam(sys_get_temp_dir(), 'froxlor_config_diff_from');
$tmp_to = tempnam(sys_get_temp_dir(), 'froxlor_config_diff_to');
$files = [];
$titles_by_key = [];
// Aggregate content for each config file
foreach ([[$parser_from, 'from'], [$parser_to, 'to']] as $todo) {
foreach ($todo[0]->getServices() as $service_type => $service) {
foreach ($service->getDaemons() as $daemon_name => $daemon) {
foreach ($daemon->getConfig() as $instruction) {
if ($instruction['type'] !== 'file') {
continue;
}
if (isset($instruction['subcommands'])) {
foreach ($instruction['subcommands'] as $subinstruction) {
if ($subinstruction['type'] !== 'file') {
continue;
}
$content = $subinstruction['content'];
}
} else {
$content = $instruction['content'];
}
if (!isset($content)) {
throw new \Exception("Cannot find content for {$instruction['name']}");
}
$key = "{$service_type}_{$daemon_name}_{$instruction['name']}";
$titles_by_key[$key] = "{$service->title} : {$daemon->title} : {$instruction['name']}";
if (!isset($files[$key])) {
$files[$key] = ['from' => '', 'to' => ''];
}
$files[$key][$todo[1]] = $this->filterContent($content);
}
}
}
}
ksort($files);
$diff_params = '';
if ($input->hasOption('diff-params') && trim($input->getOption('diff-params')) !== '') {
$diff_params = trim($input->getOption('diff-params'));
}
// Run diff on each file and output, if anything changed
foreach ($files as $file_key => $content) {
file_put_contents($tmp_from, $content['from']);
file_put_contents($tmp_to, $content['to']);
$diff_output = FileDir::safe_exec("{$check_diff_installed[0]} {$diff_params} {$tmp_from} {$tmp_to}");
if (count($diff_output) === 0) {
continue;
}
$output->writeln('<info># ' . $titles_by_key[$file_key] . '</info>');
$output->writeln(implode("\n", $diff_output) . "\n");
unset($diff_output);
}
// Remove tmp files again
unlink($tmp_from);
unlink($tmp_to);
return self::SUCCESS;
}
private function filterContent(string $content): string
{
$new_content = '';
foreach (explode("\n", $content) as $n) {
$n = trim($n);
if (!$n) {
continue;
}
if (str_starts_with($n, '#')) {
continue;
}
$new_content .= $n . "\n";
}
return $new_content;
}
}

View File

@@ -80,7 +80,7 @@ final class InstallCommand extends Command
$_SERVER['SERVER_NAME'] = $host[0] ?? ''; $_SERVER['SERVER_NAME'] = $host[0] ?? '';
$ips = []; $ips = [];
exec('hostname -I', $ips); exec('hostname -I', $ips);
$ips = explode(" ", $ips[0]); $ips = explode(" ", $ips[0] ?? "");
// ipv4 address? // ipv4 address?
$_SERVER['SERVER_ADDR'] = filter_var($ips[0] ?? "", FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ips[0] ?? '') : ''; $_SERVER['SERVER_ADDR'] = filter_var($ips[0] ?? "", FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ips[0] ?? '') : '';
if (empty($_SERVER['SERVER_ADDR'])) { if (empty($_SERVER['SERVER_ADDR'])) {

View File

@@ -62,6 +62,11 @@ final class MasterCron extends CliCommand
$result = self::SUCCESS; $result = self::SUCCESS;
$result = $this->validateRequirements($input, $output); $result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
$jobs = $input->getArgument('job'); $jobs = $input->getArgument('job');
// handle force option // handle force option
@@ -111,8 +116,8 @@ final class MasterCron extends CliCommand
]); ]);
$this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG')); $this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG'));
// check whether there are actual tasks to perform by 'tasks'-cron so // check whether there are actual tasks to perform by 'tasks'-cron, so
// we dont regenerate files unnecessarily // we don't regenerate files unnecessarily
$tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`"); $tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`");
$tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC); $tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC);

View File

@@ -126,6 +126,9 @@ class ApacheFcgi extends Apache
// mod_proxy stuff for apache-2.4 // mod_proxy stuff for apache-2.4
if (Settings::Get('system.apache24') == '1' && Settings::Get('phpfpm.use_mod_proxy') == '1') { if (Settings::Get('system.apache24') == '1' && Settings::Get('phpfpm.use_mod_proxy') == '1') {
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
$filesmatch = $phpconfig['fpm_settings']['limit_extensions']; $filesmatch = $phpconfig['fpm_settings']['limit_extensions'];
$extensions = explode(" ", $filesmatch); $extensions = explode(" ", $filesmatch);
$filesmatch = ""; $filesmatch = "";
@@ -141,23 +144,19 @@ class ApacheFcgi extends Apache
$php_options_text .= ' </FilesMatch>' . "\n"; $php_options_text .= ' </FilesMatch>' . "\n";
$mypath_dir = new Directory($domain['documentroot']); $mypath_dir = new Directory($domain['documentroot']);
// only create the "require all granted" directive if there is no active directory-protection
// only create the require all granted if there is not active directory-protection
// for this path, as this would be the first require and therefore grant all access // for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) { if ($mypath_dir->isUserProtected() == false) {
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
if ($phpconfig['pass_authorizationheader'] == '1') { if ($phpconfig['pass_authorizationheader'] == '1') {
$php_options_text .= ' CGIPassAuth On' . "\n"; $php_options_text .= ' CGIPassAuth On' . "\n";
} }
$php_options_text .= ' Require all granted' . "\n"; $php_options_text .= ' Require all granted' . "\n";
$php_options_text .= ' AllowOverride All' . "\n"; $php_options_text .= ' AllowOverride All' . "\n";
$php_options_text .= ' </Directory>' . "\n";
} elseif ($phpconfig['pass_authorizationheader'] == '1') { } elseif ($phpconfig['pass_authorizationheader'] == '1') {
// allow Pass of Authorization header // allow Pass of Authorization header
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
$php_options_text .= ' CGIPassAuth On' . "\n"; $php_options_text .= ' CGIPassAuth On' . "\n";
$php_options_text .= ' </Directory>' . "\n";
} }
$php_options_text .= ' </Directory>' . "\n";
} else { } else {
$addheader = ""; $addheader = "";
if ($phpconfig['pass_authorizationheader'] == '1') { if ($phpconfig['pass_authorizationheader'] == '1') {

View File

@@ -236,9 +236,12 @@ class AcmeSh extends FroxlorCron
return false; return false;
} }
private static function checkFsFilesAreNewer($domain, $cert_date = 0) private static function checkFsFilesAreNewer($domain, $cert_date = 0): bool
{ {
$certificate_folder = self::getWorkingDirFromEnv(strtolower($domain)); $certificate_folder = self::getCertificateFolder(strtolower($domain));
if (empty($certificate_folder)) {
return false;
}
$ssl_file = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower($domain) . '.cer'); $ssl_file = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower($domain) . '.cer');
if (is_dir($certificate_folder) && file_exists($ssl_file) && is_readable($ssl_file)) { if (is_dir($certificate_folder) && file_exists($ssl_file) && is_readable($ssl_file)) {
@@ -250,9 +253,13 @@ class AcmeSh extends FroxlorCron
return false; return false;
} }
public static function getWorkingDirFromEnv($domain = "", $forced_noecc = false) public static function getWorkingDirFromEnv($domain = "", $forced_ecc = false): string
{ {
if (Settings::Get('system.leecc') > 0 && !$forced_noecc) { // first try without _ecc either if it's enabled currently or not as
// it might have been at some point so there is a chance we have certificates
// with and without _ecc - the method getCertificateFolder() will check both
// possibilities
if ($forced_ecc) {
$domain .= "_ecc"; $domain .= "_ecc";
} }
$env_file = FileDir::makeCorrectFile(dirname(self::getAcmeSh()) . '/acme.sh.env'); $env_file = FileDir::makeCorrectFile(dirname(self::getAcmeSh()) . '/acme.sh.env');
@@ -262,7 +269,7 @@ class AcmeSh extends FroxlorCron
cut -d'"' -f2 cut -d'"' -f2
EOC; EOC;
exec('grep "LE_WORKING_DIR" ' . escapeshellarg($env_file) . ' | ' . $cut, $output); exec('grep "LE_WORKING_DIR" ' . escapeshellarg($env_file) . ' | ' . $cut, $output);
if (is_array($output) && !empty($output) && isset($output[0]) && !empty($output[0])) { if (is_array($output) && !empty($output) && !empty($output[0])) {
return FileDir::makeCorrectDir($output[0] . "/" . $domain); return FileDir::makeCorrectDir($output[0] . "/" . $domain);
} }
} }
@@ -635,35 +642,21 @@ EOC;
*/ */
private static function readCertificateToVar($domain, &$return, &$cronlog) private static function readCertificateToVar($domain, &$return, &$cronlog)
{ {
$certificate_folder = self::getWorkingDirFromEnv($domain); $certificate_folder = self::getCertificateFolder($domain);
$certificate_folder_noecc = null;
if (Settings::Get('system.leecc') > 0) {
$certificate_folder_noecc = self::getWorkingDirFromEnv($domain, true);
}
$certificate_folder = FileDir::makeCorrectDir($certificate_folder);
if (is_dir($certificate_folder) || is_dir($certificate_folder_noecc)) { if (!empty($certificate_folder)) {
foreach ( $certificate_files = [
[ 'crt' => $domain . '.cer',
'crt' => $domain . '.cer', 'key' => $domain . '.key',
'key' => $domain . '.key', 'chain' => 'ca.cer',
'chain' => 'ca.cer', 'fullchain' => 'fullchain.cer',
'fullchain' => 'fullchain.cer', 'csr' => $domain . '.csr'
'csr' => $domain . '.csr' ];
] as $index => $sslfile foreach ($certificate_files as $index => $sslfile) {
) {
$ssl_file = FileDir::makeCorrectFile($certificate_folder . '/' . $sslfile); $ssl_file = FileDir::makeCorrectFile($certificate_folder . '/' . $sslfile);
if (file_exists($ssl_file)) { if (file_exists($ssl_file)) {
$return[$index] = file_get_contents($ssl_file); $return[$index] = file_get_contents($ssl_file);
} else { } else {
if (!empty($certificate_folder_noecc)) {
$ssl_file_fb = FileDir::makeCorrectFile($certificate_folder_noecc . '/' . $sslfile);
if (file_exists($ssl_file_fb)) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "ECC certificates activated but found only non-ecc file");
$return[$index] = file_get_contents($ssl_file_fb);
continue;
}
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find file '" . $sslfile . "' in '" . $certificate_folder . "'"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find file '" . $sslfile . "' in '" . $certificate_folder . "'");
$return[$index] = null; $return[$index] = null;
} }
@@ -672,4 +665,18 @@ EOC;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find certificate-folder '" . $certificate_folder . "'"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find certificate-folder '" . $certificate_folder . "'");
} }
} }
private static function getCertificateFolder(string $domain): string
{
$certificate_folder = self::getWorkingDirFromEnv(strtolower($domain));
if (file_exists($certificate_folder)) {
return $certificate_folder;
}
$certificate_folder_ecc = self::getWorkingDirFromEnv($domain, true);
if (file_exists($certificate_folder_ecc)) {
return $certificate_folder_ecc;
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not find certificate-folder for domain '" . $domain . "'");
return "";
}
} }

View File

@@ -225,7 +225,7 @@ class Nginx extends HttpConfigBase
$this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n";
// protect bin/ // protect bin/
$this->nginx_data[$vhost_filename] .= "\t" . 'location ~ ' . rtrim($relpath, "/") . '/(bin|cache|logs|tests|vendor) {' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . 'location ~ ^' . rtrim($relpath, "/") . '/(bin|cache|logs|tests|vendor) {' . "\n";
$this->nginx_data[$vhost_filename] .= "\t" . ' deny all;' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . ' deny all;' . "\n";
$this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n";
} }
@@ -883,13 +883,7 @@ class Nginx extends HttpConfigBase
// remove comments // remove comments
$vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost))); $vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost)));
// Break blocks into lines // Break blocks into lines
$vhost = str_replace([ $vhost = preg_replace("/^(\s+)location(.+)\{(.+)\}$/misU", "location $2 {\n $3 \n}", $vhost);
"{",
"}"
], [
" {\n",
"\n}"
], $vhost);
// Break into array items // Break into array items
$vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost)))); $vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost))));
// Remove empty lines // Remove empty lines
@@ -1040,9 +1034,11 @@ class Nginx extends HttpConfigBase
$path_options .= "\t\t" . 'auth_basic_user_file ' . FileDir::makeCorrectFile($single['usrf']) . ';' . "\n"; $path_options .= "\t\t" . 'auth_basic_user_file ' . FileDir::makeCorrectFile($single['usrf']) . ';' . "\n";
if ($domain['phpenabled_customer'] == 1 && $domain['phpenabled_vhost'] == '1') { if ($domain['phpenabled_customer'] == 1 && $domain['phpenabled_vhost'] == '1') {
$path_options .= "\t\t" . 'index index.php index.html index.htm;' . "\n"; $path_options .= "\t\t" . 'index index.php index.html index.htm;' . "\n";
$path_options .= "\t\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n"; if ($domain['notryfiles'] != 1) {
$path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n"; $path_options .= "\t\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n";
$path_options .= "\t\t" . '}' . "\n\n"; $path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n";
$path_options .= "\t\t" . '}' . "\n\n";
}
} else { } else {
$path_options .= "\t\t" . 'index index.html index.htm;' . "\n"; $path_options .= "\t\t" . 'index index.html index.htm;' . "\n";
} }
@@ -1169,7 +1165,6 @@ class Nginx extends HttpConfigBase
$phpopts .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n"; $phpopts .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n";
$phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n"; $phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n";
$phpopts .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n"; $phpopts .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n";
$phpopts .= "\t\ttry_files \$fastcgi_script_name =404;\n";
$phpopts .= "\t\tfastcgi_pass " . Settings::Get('system.nginx_php_backend') . ";\n"; $phpopts .= "\t\tfastcgi_pass " . Settings::Get('system.nginx_php_backend') . ";\n";
$phpopts .= "\t\tfastcgi_index index.php;\n"; $phpopts .= "\t\tfastcgi_index index.php;\n";
if ($domain['ssl'] == '1' && $ssl_vhost) { if ($domain['ssl'] == '1' && $ssl_vhost) {

View File

@@ -25,10 +25,10 @@
namespace Froxlor; namespace Froxlor;
use Froxlor\Database\Database;
use Froxlor\UI\Collection;
use Froxlor\Api\Commands\Customers; use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\SubDomains; use Froxlor\Api\Commands\SubDomains;
use Froxlor\Database\Database;
use Froxlor\UI\Collection;
/** /**
* Class to manage the current user / session * Class to manage the current user / session
@@ -153,7 +153,7 @@ class CurrentUser
} elseif ($resource == 'subdomains') { } elseif ($resource == 'subdomains') {
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'], $parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'],
['sql_search' => ['d.parentdomainid' => 0]])); ['sql_search' => ['d.parentdomainid' => 0]]));
$addition = $parentDomainCollection != 0; $addition = $parentDomainCollection->count() != 0;
} elseif ($resource == 'domains') { } elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo'])); $customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection != 0; $addition = $customerCollection != 0;

View File

@@ -55,6 +55,7 @@ class IpAddr
/** /**
* @return array * @return array
* @throws \Exception
*/ */
public static function getSslIpPortCombinations(): array public static function getSslIpPortCombinations(): array
{ {
@@ -75,7 +76,7 @@ class IpAddr
$additional_conditions_params = []; $additional_conditions_params = [];
$additional_conditions_array = []; $additional_conditions_array = [];
if ($userinfo['ip'] != '-1') { if (!empty($userinfo) && $userinfo['ip'] != '-1') {
$admin_ip_stmt = Database::prepare(" $admin_ip_stmt = Database::prepare("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid) SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid)
"); ");

View File

@@ -447,7 +447,8 @@ class FileDir
$field = [ $field = [
'type' => 'select', 'type' => 'select',
'select_var' => $_field, 'select_var' => $_field,
'selected' => $value 'selected' => $value,
'value' => $value
]; ];
} else { } else {
$field = [ $field = [

View File

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

View File

@@ -0,0 +1,55 @@
<?php
namespace Froxlor\Http;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
class RateLimiter
{
private static int $limit_per_interval = 60;
private static int $reset_time = 0;
public static function run(bool $install_mode = false)
{
// default interval = 60 sec
self::$reset_time = time() + 60;
if (!$install_mode) {
self::$limit_per_interval = Settings::Get('system.req_limit_per_interval') ?? 60;
self::$reset_time = time() + Settings::Get('system.req_limit_interval') ?? 60;
}
// Get the remaining requests and reset time from the headers
$remaining = isset($_SESSION['HTTP_X_RATELIMIT_REMAINING']) ? (int)$_SESSION['HTTP_X_RATELIMIT_REMAINING'] : self::$limit_per_interval;
$reset = isset($_SESSION['HTTP_X_RATELIMIT_RESET']) ? (int)$_SESSION['HTTP_X_RATELIMIT_RESET'] : self::$reset_time;
// check if reset time is due
if (time() > $reset) {
$remaining = self::$limit_per_interval;
$reset = self::$reset_time;
}
// If we've hit the limit, return an error
if ($remaining <= 0) {
header('HTTP/1.1 429 Too Many Requests');
header("Retry-After: $reset");
UI::twig()->addGlobal('install_mode', '1');
echo UI::twig()->render('Froxlor/misc/ratelimithint.html.twig', [
'retry' => $reset,
'installdir' => Froxlor::getInstallDir()
]);
die();
}
// Decrement the remaining requests and update the headers
$remaining--;
$_SESSION['HTTP_X_RATELIMIT_REMAINING'] = $remaining;
$_SESSION['HTTP_X_RATELIMIT_RESET'] = $reset;
header("X-RateLimit-Limit: " . self::$limit_per_interval);
header("X-RateLimit-Remaining: " . $remaining);
header("X-RateLimit-Reset: " . $reset);
}
}

View File

@@ -448,7 +448,11 @@ class Core
$reload = "service php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-fpm restart"; $reload = "service php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-fpm restart";
$config_dir = "/etc/php/" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "/fpm/pool.d/"; $config_dir = "/etc/php/" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "/fpm/pool.d/";
// fcgid // fcgid
$binary = "/usr/bin/php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-cgi"; if ($this->validatedData['distribution'] == 'bookworm') {
$binary = "/usr/bin/php-cgi" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;
} else {
$binary = "/usr/bin/php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-cgi";
}
} }
$db_user->query("UPDATE `" . TABLE_PANEL_FPMDAEMONS . "` SET `reload_cmd` = '" . $reload . "', `config_dir` = '" . $config_dir . "' WHERE `id` ='1';"); $db_user->query("UPDATE `" . TABLE_PANEL_FPMDAEMONS . "` SET `reload_cmd` = '" . $reload . "', `config_dir` = '" . $config_dir . "' WHERE `id` ='1';");
$db_user->query("UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET `binary` = '" . $binary . "';"); $db_user->query("UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET `binary` = '" . $binary . "';");

View File

@@ -449,7 +449,15 @@ class PhpHelper
'ssl_specialsettings', 'ssl_specialsettings',
'default_vhostconf_domain', 'default_vhostconf_domain',
'ssl_default_vhostconf_domain', 'ssl_default_vhostconf_domain',
'filecontent' 'filecontent',
'admin_password',
'password',
'new_customer_password',
'privileged_password',
'email_password',
'directory_password',
'ftp_password',
'mysql_password',
]; ];
if (!empty($global)) { if (!empty($global)) {
$tmp = $global; $tmp = $global;
@@ -519,7 +527,12 @@ class PhpHelper
} elseif (is_int($value)) { } elseif (is_int($value)) {
$str .= self::tabPrefix($depth, "'{$key}' => $value,\n"); $str .= self::tabPrefix($depth, "'{$key}' => $value,\n");
} else { } else {
$str .= self::tabPrefix($depth, "'{$key}' => '{$value}',\n"); if ($key == 'password') {
// special case for passwords (nowdoc)
$str .= self::tabPrefix($depth, "'{$key}' => <<<'EOT'\n{$value}\nEOT,\n");
} else {
$str .= self::tabPrefix($depth, "'{$key}' => '{$value}',\n");
}
} }
} else { } else {
$str .= self::parseArrayToString($value, $key, ($depth + 1)); $str .= self::parseArrayToString($value, $key, ($depth + 1));

View File

@@ -87,16 +87,38 @@ class UI
return $isHttps && (strcasecmp('on', $isHttps) == 0 || strcasecmp('https', $isHttps) == 0); return $isHttps && (strcasecmp('on', $isHttps) == 0 || strcasecmp('https', $isHttps) == 0);
} }
/**
* Extract the cookie host from HTTP_HOST, stripping the port.
*/
public static function getCookieHost(): ?string
{
if (empty($_SERVER['HTTP_HOST']))
return null;
$colonPosition = strrpos($_SERVER['HTTP_HOST'], ':');
// There's no port in the host
if ($colonPosition === false)
return $_SERVER['HTTP_HOST'];
$closingSquareBracketPosition = strrpos($_SERVER['HTTP_HOST'], ']');
// The host is an IPv4 address or hostname with port
if ($closingSquareBracketPosition === false)
return substr($_SERVER['HTTP_HOST'], 0, $colonPosition);
// The host is an IPv6 address with port
return substr($_SERVER['HTTP_HOST'], 0, $closingSquareBracketPosition + 1);
}
/** /**
* send various security related headers * send various security related headers
*/ */
public static function sendHeaders() public static function sendHeaders()
{ {
$cookie_host = empty($_SERVER['HTTP_HOST']) ? null : explode (':', $_SERVER['HTTP_HOST'])[0];
session_set_cookie_params([ session_set_cookie_params([
'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php 'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php
'path' => '/', 'path' => '/',
'domain' => $cookie_host, 'domain' => self::getCookieHost(),
'secure' => self::requestIsHttps(), 'secure' => self::requestIsHttps(),
'httponly' => true, 'httponly' => true,
'samesite' => 'Strict' 'samesite' => 'Strict'
@@ -268,7 +290,8 @@ class UI
]; ];
} }
public static function validateThemeTemplate(string $name, string $theme = "") { public static function validateThemeTemplate(string $name, string $theme = "")
{
if (empty(trim($theme))) { if (empty(trim($theme))) {
$theme = self::getTheme(); $theme = self::getTheme();
} }

View File

@@ -52,16 +52,16 @@ class Check
]; ];
$check_array = [ $check_array = [
'system_mod_fcgid_enabled' => [ 'system_mod_fcgid' => [
'other_post_field' => 'system_phpfpm_enabled', 'other_post_field' => 'phpfpm_enabled',
'other_enabled' => 'phpfpm.enabled', 'other_enabled' => 'phpfpm.enabled',
'other_enabled_lng' => 'phpfpmstillenabled', 'other_enabled_lng' => 'phpfpmstillenabled',
'deactivate' => [ 'deactivate' => [
'phpfpm.enabled_ownvhost' => 0 'phpfpm.enabled_ownvhost' => 0
] ]
], ],
'system_phpfpm_enabled' => [ 'phpfpm_enabled' => [
'other_post_field' => 'system_mod_fcgid_enabled', 'other_post_field' => 'system_mod_fcgid',
'other_enabled' => 'system.mod_fcgid', 'other_enabled' => 'system.mod_fcgid',
'other_enabled_lng' => 'fcgidstillenabled', 'other_enabled_lng' => 'fcgidstillenabled',
'deactivate' => [ 'deactivate' => [

View File

@@ -260,7 +260,7 @@ class Validate
} }
/** /**
* Returns if an emailaddress is in correct format or not * Returns if an email-address is in correct format or not
* *
* @param string $email The email address to check * @param string $email The email address to check
* *

View File

@@ -2600,7 +2600,6 @@ ServerName "<SERVERNAME> FTP Server"
ServerType standalone ServerType standalone
DeferWelcome off DeferWelcome off
MultilineRFC2228 on
DefaultServer on DefaultServer on
ShowSymlinks on ShowSymlinks on
@@ -2939,7 +2938,6 @@ SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimit
SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'"
SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies
SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies
</IfModule> </IfModule>
]]> ]]>
</content> </content>
@@ -2955,7 +2953,7 @@ TLSRSACertificateFile /etc/ssl/certs/proftpd.crt
TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key
TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt
TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key
TLSOptions NoCertRequest NoSessionReuseRequired TLSOptions NoSessionReuseRequired
TLSVerifyClient off TLSVerifyClient off
# Are clients required to use FTP over TLS when talking to this server? # Are clients required to use FTP over TLS when talking to this server?
@@ -3317,7 +3315,7 @@ aliases: files
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_configdir}}]]></command> <command><![CDATA[mkdir -p {{settings.system.mod_fcgid_configdir}}]]></command>
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_tmpdir}}]]></command> <command><![CDATA[mkdir -p {{settings.system.mod_fcgid_tmpdir}}]]></command>
<command><![CDATA[chmod 1777 {{settings.system.mod_fcgid_tmpdir}}]]></command> <command><![CDATA[chmod 1777 {{settings.system.mod_fcgid_tmpdir}}]]></command>
<command><![CDATA[a2dismod php8.1]]></command> <command><![CDATA[a2dismod php8.2]]></command>
</commands> </commands>
<!-- instead of just restarting apache, we let the cronjob do all the <!-- instead of just restarting apache, we let the cronjob do all the
dirty work --> dirty work -->
@@ -3350,7 +3348,7 @@ aliases: files
</visibility> </visibility>
<visibility mode="true">{{settings.phpfpm.enabled_ownvhost}} <visibility mode="true">{{settings.phpfpm.enabled_ownvhost}}
</visibility> </visibility>
<command><![CDATA[a2dismod php8.1]]></command> <command><![CDATA[a2dismod php8.2]]></command>
</commands> </commands>
<!-- instead of just restarting apache, we let the cronjob do all the <!-- instead of just restarting apache, we let the cronjob do all the
dirty work --> dirty work -->

View File

@@ -133,7 +133,7 @@ return [
'customers' => [ 'customers' => [
'label' => lng('admin.customers'), 'label' => lng('admin.customers'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['customers'], 'value' => empty($result['customers']) ? '0' : $result['customers'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
], ],
@@ -146,7 +146,7 @@ return [
'domains' => [ 'domains' => [
'label' => lng('admin.domains'), 'label' => lng('admin.domains'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['domains'], 'value' => empty($result['domains']) ? '0' : $result['domains'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
], ],
@@ -159,49 +159,49 @@ return [
'diskspace' => [ 'diskspace' => [
'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')', 'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')',
'type' => 'textul', 'type' => 'textul',
'value' => $result['diskspace'], 'value' => empty($result['diskspace']) ? '0' : $result['diskspace'],
'maxlength' => 6, 'maxlength' => 6,
'mandatory' => true 'mandatory' => true
], ],
'traffic' => [ 'traffic' => [
'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')', 'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')',
'type' => 'textul', 'type' => 'textul',
'value' => $result['traffic'], 'value' => empty($result['traffic']) ? '0' : $result['traffic'],
'maxlength' => 4, 'maxlength' => 4,
'mandatory' => true 'mandatory' => true
], ],
'subdomains' => [ 'subdomains' => [
'label' => lng('customer.subdomains'), 'label' => lng('customer.subdomains'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['subdomains'], 'value' => empty($result['subdomains']) ? '0' : $result['subdomains'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
], ],
'emails' => [ 'emails' => [
'label' => lng('customer.emails'), 'label' => lng('customer.emails'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['emails'], 'value' => empty($result['emails']) ? '0' : $result['emails'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
], ],
'email_accounts' => [ 'email_accounts' => [
'label' => lng('customer.accounts'), 'label' => lng('customer.accounts'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['email_accounts'], 'value' => empty($result['email_accounts']) ? '0' : $result['email_accounts'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
], ],
'email_forwarders' => [ 'email_forwarders' => [
'label' => lng('customer.forwarders'), 'label' => lng('customer.forwarders'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['email_forwarders'], 'value' => empty($result['email_forwarders']) ? '0' : $result['email_forwarders'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
], ],
'email_quota' => [ 'email_quota' => [
'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')', 'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')',
'type' => 'textul', 'type' => 'textul',
'value' => $result['email_quota'], 'value' => empty($result['email_quota']) ? '0' : $result['email_quota'],
'maxlength' => 9, 'maxlength' => 9,
'visible' => Settings::Get('system.mail_quota_enabled') == '1', 'visible' => Settings::Get('system.mail_quota_enabled') == '1',
'mandatory' => true 'mandatory' => true
@@ -209,13 +209,13 @@ return [
'ftps' => [ 'ftps' => [
'label' => lng('customer.ftps'), 'label' => lng('customer.ftps'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['ftps'], 'value' => empty($result['ftps']) ? '0' : $result['ftps'],
'maxlength' => 9 'maxlength' => 9
], ],
'mysqls' => [ 'mysqls' => [
'label' => lng('customer.mysqls'), 'label' => lng('customer.mysqls'),
'type' => 'textul', 'type' => 'textul',
'value' => $result['mysqls'], 'value' => empty($result['mysqls']) ? '0' : $result['mysqls'],
'maxlength' => 9, 'maxlength' => 9,
'mandatory' => true 'mandatory' => true
] ]

View File

@@ -364,6 +364,12 @@ return [
'value' => '1', 'value' => '1',
'checked' => true 'checked' => true
], ],
'openbasedir_path' => [
'label' => lng('domain.openbasedirpath'),
'type' => 'select',
'select_var' => $openbasedir,
'selected' => 0
],
'phpenabled' => [ 'phpenabled' => [
'label' => lng('admin.phpenabled'), 'label' => lng('admin.phpenabled'),
'type' => 'checkbox', 'type' => 'checkbox',

View File

@@ -390,6 +390,12 @@ return [
'value' => '1', 'value' => '1',
'checked' => $result['openbasedir'] 'checked' => $result['openbasedir']
], ],
'openbasedir_path' => [
'label' => lng('domain.openbasedirpath'),
'type' => 'select',
'select_var' => $openbasedir,
'selected' => $result['openbasedir_path']
],
'phpenabled' => [ 'phpenabled' => [
'label' => lng('admin.phpenabled'), 'label' => lng('admin.phpenabled'),
'type' => 'checkbox', 'type' => 'checkbox',

View File

@@ -57,7 +57,7 @@ return [
'label' => lng('panel.path'), 'label' => lng('panel.path'),
'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescriptionSubdomain').(Settings::Get('system.documentroot_use_default_value') == 1 ? lng('panel.pathDescriptionEx') : '') : null), 'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescriptionSubdomain').(Settings::Get('system.documentroot_use_default_value') == 1 ? lng('panel.pathDescriptionEx') : '') : null),
'type' => $pathSelect['type'], 'type' => $pathSelect['type'],
'select_var' => $pathSelect['value'], 'select_var' => $pathSelect['select_var'] ?? '',
'selected' => $pathSelect['value'], 'selected' => $pathSelect['value'],
'value' => $pathSelect['value'], 'value' => $pathSelect['value'],
'note' => $pathSelect['note'] ?? '', 'note' => $pathSelect['note'] ?? '',

View File

@@ -43,13 +43,16 @@ return [
'email_password' => [ 'email_password' => [
'label' => lng('login.password'), 'label' => lng('login.password'),
'type' => 'password', 'type' => 'password',
'autocomplete' => 'off' 'autocomplete' => 'off',
], 'next_to' => [
'email_password_suggestion' => [ 'email_password_suggestion' => [
'label' => lng('customer.generated_pwd'), 'next_to_prefix' => lng('customer.generated_pwd') . ':',
'type' => 'text', 'type' => 'text',
'visible' => (Settings::Get('panel.password_regex') == ''), 'visible' => (Settings::Get('panel.password_regex') == ''),
'value' => Crypt::generatePassword() 'value' => Crypt::generatePassword(),
'readonly' => true
]
]
] ]
] ]
] ]

View File

@@ -46,7 +46,7 @@ return [
'autocomplete' => 'off', 'autocomplete' => 'off',
'mandatory' => true, 'mandatory' => true,
'next_to' => [ 'next_to' => [
'admin_password_suggestion' => [ 'email_password_suggestion' => [
'next_to_prefix' => lng('customer.generated_pwd') . ':', 'next_to_prefix' => lng('customer.generated_pwd') . ':',
'type' => 'text', 'type' => 'text',
'visible' => (Settings::Get('panel.password_regex') == ''), 'visible' => (Settings::Get('panel.password_regex') == ''),

View File

@@ -52,6 +52,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
use Froxlor\CurrentUser; use Froxlor\CurrentUser;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\Http\RateLimiter;
use Froxlor\Idna\IdnaWrapper; use Froxlor\Idna\IdnaWrapper;
use Froxlor\Language; use Froxlor\Language;
use Froxlor\PhpHelper; use Froxlor\PhpHelper;
@@ -121,6 +122,7 @@ if (!isset($sql) || !is_array($sql)) {
// send ssl-related headers (later than the others because we need a working database-connection and installation) // send ssl-related headers (later than the others because we need a working database-connection and installation)
UI::sendSslHeaders(); UI::sendSslHeaders();
RateLimiter::run();
// create a new idna converter // create a new idna converter
$idna_convert = new IdnaWrapper(); $idna_convert = new IdnaWrapper();
@@ -179,8 +181,10 @@ if (@file_exists('templates/' . $theme . '/config.json')) {
} }
// check for existence of variant in theme // check for existence of variant in theme
if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) || !array_key_exists($themevariant, if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) || !array_key_exists(
$_themeoptions['variants']))) { $themevariant,
$_themeoptions['variants']
))) {
$themevariant = "default"; $themevariant = "default";
} }
@@ -214,12 +218,11 @@ UI::twig()->addGlobal('header_logo', $header_logo);
if (!CurrentUser::hasSession() && AREA != 'login') { if (!CurrentUser::hasSession() && AREA != 'login') {
unset($_SESSION['userinfo']); unset($_SESSION['userinfo']);
CurrentUser::setData(); CurrentUser::setData();
session_destroy(); $_SESSION = [
$params = [ "lastscript" => basename($_SERVER["SCRIPT_NAME"]),
"script" => basename($_SERVER["SCRIPT_NAME"]), "lastqrystr" => $_SERVER["QUERY_STRING"]
"qrystr" => $_SERVER["QUERY_STRING"]
]; ];
Response::redirectTo('index.php', $params); Response::redirectTo('index.php');
exit(); exit();
} }
@@ -329,11 +332,10 @@ if (CurrentUser::hasSession()) {
} }
} }
// update cookie lifetime // update cookie lifetime
$cookie_host = empty($_SERVER['HTTP_HOST']) ? null : explode (':', $_SERVER['HTTP_HOST'])[0];
$cookie_params = [ $cookie_params = [
'expires' => time() + Settings::Get('session.sessiontimeout'), 'expires' => time() + Settings::Get('session.sessiontimeout'),
'path' => '/', 'path' => '/',
'domain' => $cookie_host, 'domain' => UI::getCookieHost(),
'secure' => UI::requestIsHttps(), 'secure' => UI::requestIsHttps(),
'httponly' => true, 'httponly' => true,
'samesite' => 'Strict' 'samesite' => 'Strict'

View File

@@ -38,7 +38,7 @@ return [
'url' => 'customer_email.php?page=emails', 'url' => 'customer_email.php?page=emails',
'label' => lng('menue.email.emails'), 'label' => lng('menue.email.emails'),
'required_resources' => 'emails', 'required_resources' => 'emails',
'add_shortlink' => CurrentUser::canAddResource('emails') ? 'customer_email.php?page=emails&action=add' : null, 'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('emails') ? 'customer_email.php?page=email_domain&action=add' : null,
], ],
[ [
'url' => Settings::Get('panel.webmail_url'), 'url' => Settings::Get('panel.webmail_url'),
@@ -60,7 +60,7 @@ return [
'url' => 'customer_mysql.php?page=mysqls', 'url' => 'customer_mysql.php?page=mysqls',
'label' => lng('menue.mysql.databases'), 'label' => lng('menue.mysql.databases'),
'required_resources' => 'mysqls', 'required_resources' => 'mysqls',
'add_shortlink' => CurrentUser::canAddResource('mysqls')? 'customer_mysql.php?page=mysqls&action=add' : null, 'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('mysqls')? 'customer_mysql.php?page=mysqls&action=add' : null,
], ],
[ [
'url' => Settings::Get('panel.phpmyadmin_url'), 'url' => Settings::Get('panel.phpmyadmin_url'),
@@ -81,7 +81,7 @@ return [
[ [
'url' => 'customer_domains.php?page=domains', 'url' => 'customer_domains.php?page=domains',
'label' => lng('menue.domains.settings'), 'label' => lng('menue.domains.settings'),
'add_shortlink' => CurrentUser::canAddResource('subdomains') ? 'customer_domains.php?page=domains&action=add' : null, 'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('subdomains') ? 'customer_domains.php?page=domains&action=add' : null,
], ],
[ [
'url' => 'customer_domains.php?page=sslcertificates', 'url' => 'customer_domains.php?page=sslcertificates',
@@ -98,7 +98,7 @@ return [
[ [
'url' => 'customer_ftp.php?page=accounts', 'url' => 'customer_ftp.php?page=accounts',
'label' => lng('menue.ftp.accounts'), 'label' => lng('menue.ftp.accounts'),
'add_shortlink' => CurrentUser::canAddResource('ftps') ? 'customer_ftp.php?page=accounts&action=add' : null, 'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('ftps') ? 'customer_ftp.php?page=accounts&action=add' : null,
], ],
[ [
'url' => Settings::Get('panel.webftp_url'), 'url' => Settings::Get('panel.webftp_url'),

View File

@@ -926,6 +926,7 @@ return [
'domaincannotbeedited' => 'Keine Berechtigung, um die Domain %s zu bearbeiten', 'domaincannotbeedited' => 'Keine Berechtigung, um die Domain %s zu bearbeiten',
'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s', 'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s',
'phpgdextensionnotavailable' => 'Die PHP GD Extension ist nicht verfügbar. Bild-Daten können nicht validiert werden.', 'phpgdextensionnotavailable' => 'Die PHP GD Extension ist nicht verfügbar. Bild-Daten können nicht validiert werden.',
'2fa_wrongcode' => 'Der angegebene Code ist nicht korrekt',
], ],
'extras' => [ 'extras' => [
'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.<br />Die Änderungen sind erst nach einer kurzen Zeit wirksam.', 'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.<br />Die Änderungen sind erst nach einer kurzen Zeit wirksam.',
@@ -2072,6 +2073,14 @@ Vielen Dank, Ihr Administrator',
'toolselect' => 'Traffic Analyzer', 'toolselect' => 'Traffic Analyzer',
], ],
'requires_reconfiguration' => 'Änderungen an dieser Einstellungen benötigen unter Umständen eine erneute Konfiguration der folgenden Dienste:<br><strong>%s</strong>', 'requires_reconfiguration' => 'Änderungen an dieser Einstellungen benötigen unter Umständen eine erneute Konfiguration der folgenden Dienste:<br><strong>%s</strong>',
'req_limit_per_interval' => [
'title' => 'Anzahl der HTTP-Anfragen pro Intervall',
'description' => 'Erlaubte Anzahl von HTTP-Anfragen pro Intervall (siehe unten) auf froxlor, Standard ist "60"',
],
'req_limit_interval' => [
'title' => 'Rate-Limit-Intervall',
'description' => 'Zeit in Sekunden für die maximale Anzahl von HTTP-Anfragen, Standard ist "60".',
],
], ],
'spf' => [ 'spf' => [
'use_spf' => 'Aktiviere SPF für Domains?', 'use_spf' => 'Aktiviere SPF für Domains?',
@@ -2179,6 +2188,7 @@ Vielen Dank, Ihr Administrator',
'description' => 'Aktualisierung der froxlor Datenbank', 'description' => 'Aktualisierung der froxlor Datenbank',
'uc_newinfo' => 'Eine neuere %sVersion ist verfügbar: "%s" (Aktuell installierte Version: %s)', 'uc_newinfo' => 'Eine neuere %sVersion ist verfügbar: "%s" (Aktuell installierte Version: %s)',
'notify_subject' => 'Neues Update verfügbar', 'notify_subject' => 'Neues Update verfügbar',
'dbupdate_required' => 'Froxlor-Dateien wurden aktualisiert, Datenbank-Aktualisierung notwendig',
], ],
'usersettings' => [ 'usersettings' => [
'custom_notes' => [ 'custom_notes' => [
@@ -2233,8 +2243,8 @@ Vielen Dank, Ihr Administrator',
'install' => [ 'install' => [
'top' => 'Abschluss', 'top' => 'Abschluss',
'title' => 'Ein letzter Schritt...', '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.', '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>',
'runcmd' => 'Folgenden Befehl als root-Benutzer in der Shell auf dem Server ausführen:', 'runcmd' => 'Folgende Befehle ausführen, um die Installation abzuschließen:',
'manual_config' => 'Ich werden die Dienste manuell konfigurieren, direkt zum Login umleiten', 'manual_config' => 'Ich werden die Dienste manuell konfigurieren, direkt zum Login umleiten',
'waitforconfig' => 'Warte auf Abschluss der Dienstkonfiguration...', 'waitforconfig' => 'Warte auf Abschluss der Dienstkonfiguration...',
], ],

View File

@@ -952,7 +952,7 @@ return [
'autoupdate_1' => 'PHP setting allow_url_fopen is disabled. Autoupdate needs this setting to be enabled in php.ini', 'autoupdate_1' => 'PHP setting allow_url_fopen is disabled. Autoupdate needs this setting to be enabled in php.ini',
'autoupdate_2' => 'PHP zip extension not found, please ensure it is installed and activated', 'autoupdate_2' => 'PHP zip extension not found, please ensure it is installed and activated',
'autoupdate_4' => 'The froxlor archive could not be stored to the disk :(', 'autoupdate_4' => 'The froxlor archive could not be stored to the disk :(',
'autoupdate_5' => 'version.froxlor.org returned inacceptable values :(', 'autoupdate_5' => 'version.froxlor.org returned unacceptable values :(',
'autoupdate_6' => 'Whoops, there was no (valid) version given to download :(', 'autoupdate_6' => 'Whoops, there was no (valid) version given to download :(',
'autoupdate_7' => 'The downloaded archive could not be found :(', 'autoupdate_7' => 'The downloaded archive could not be found :(',
'autoupdate_8' => 'The archive could not be extracted :(', 'autoupdate_8' => 'The archive could not be extracted :(',
@@ -995,6 +995,7 @@ return [
'domaincannotbeedited' => 'You are not permitted to edit the domain %s', 'domaincannotbeedited' => 'You are not permitted to edit the domain %s',
'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s', 'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s',
'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data', 'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data',
'2fa_wrongcode' => 'The code entered is not valid',
], ],
'extras' => [ 'extras' => [
'description' => 'Here you can add some extras, for example directory protection.<br />The system will need some time to apply the new settings after every change.', 'description' => 'Here you can add some extras, for example directory protection.<br />The system will need some time to apply the new settings after every change.',
@@ -1193,7 +1194,7 @@ Yours sincerely, your administrator',
'database_edit' => 'Edit database', 'database_edit' => 'Edit database',
'size' => 'Size', 'size' => 'Size',
'privileged_user' => 'Privileged database user', 'privileged_user' => 'Privileged database user',
'privileged_passwd' => 'Password for priviliged user', 'privileged_passwd' => 'Password for privileged user',
'unprivileged_passwd' => 'Password for unprivileged user', 'unprivileged_passwd' => 'Password for unprivileged user',
'mysql_ssl_ca_file' => 'SSL server certificate', 'mysql_ssl_ca_file' => 'SSL server certificate',
'mysql_ssl_verify_server_certificate' => 'Verify SSL server certificate' 'mysql_ssl_verify_server_certificate' => 'Verify SSL server certificate'
@@ -1262,7 +1263,7 @@ Yours sincerely, your administrator',
'reset' => 'Discard changes', 'reset' => 'Discard changes',
'pathDescription' => 'If the directory doesn\'t exist, it will be created automatically.', 'pathDescription' => 'If the directory doesn\'t exist, it will be created automatically.',
'pathDescriptionEx' => '<br /><br /><span class="text-danger">Please note:</span> The path <code>/</code> is not allowed due to administrative settings, it will automatically be set to <code>/chosen.subdomain.tld/</code> if not set to another directory.', 'pathDescriptionEx' => '<br /><br /><span class="text-danger">Please note:</span> The path <code>/</code> is not allowed due to administrative settings, it will automatically be set to <code>/chosen.subdomain.tld/</code> if not set to another directory.',
'pathDescriptionSubdomain' => 'If the directory doesn\'t exist, it will be created automatically.<br /><br />If you want a redirect to another domain than this entry has to start with http:// or https://.<br /><br />If the URL ends with / it is considered a folder, if not, it is treated as file.', 'pathDescriptionSubdomain' => 'If the directory doesn\'t exist, it will be created automatically.<br /><br />If you want a redirect to another domain then this entry has to start with http:// or https://.<br /><br />If the URL ends with / it is considered a folder, if not, it is treated as file.',
'back' => 'Back', 'back' => 'Back',
'reseller' => 'reseller', 'reseller' => 'reseller',
'admin' => 'admin', 'admin' => 'admin',
@@ -2196,6 +2197,14 @@ Yours sincerely, your administrator',
'goaccess' => 'goacccess' 'goaccess' => 'goacccess'
], ],
'requires_reconfiguration' => 'Changing this settings might require a reconfiguration of the following services:<br><strong>%s</strong>', 'requires_reconfiguration' => 'Changing this settings might require a reconfiguration of the following services:<br><strong>%s</strong>',
'req_limit_per_interval' => [
'title' => 'Number of HTTP requests per interval',
'description' => 'Limit the number of HTTP requests per interval (see below) to froxlor, default is "60"',
],
'req_limit_interval' => [
'title' => 'Rate-limit interval',
'description' => 'Specify the time in seconds for the number of HTTP requests, default is "60"',
],
], ],
'spf' => [ 'spf' => [
'use_spf' => 'Activate SPF for domains?', 'use_spf' => 'Activate SPF for domains?',
@@ -2310,6 +2319,7 @@ Yours sincerely, your administrator',
'description' => 'Running database updates for your froxlor installation', 'description' => 'Running database updates for your froxlor installation',
'uc_newinfo' => 'There is a newer %sversion available: "%s" (Your current version is: %s)', 'uc_newinfo' => 'There is a newer %sversion available: "%s" (Your current version is: %s)',
'notify_subject' => 'New update available', 'notify_subject' => 'New update available',
'dbupdate_required' => 'Froxlor files have been updated, database update required',
], ],
'usersettings' => [ 'usersettings' => [
'custom_notes' => [ 'custom_notes' => [
@@ -2365,8 +2375,8 @@ Yours sincerely, your administrator',
'install' => [ 'install' => [
'top' => 'Finish setup', 'top' => 'Finish setup',
'title' => 'One last step...', '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.', '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>',
'runcmd' => 'Run the following command as root-user in your shell on this server:', 'runcmd' => 'Run the following command to finish the installation:',
'manual_config' => 'I will manually configure the services, just take me to the login', 'manual_config' => 'I will manually configure the services, just take me to the login',
'waitforconfig' => 'Waiting for services to be configured...', 'waitforconfig' => 'Waiting for services to be configured...',
], ],

7546
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,7 @@
<label class="form-check-label" for="switchInstallMode">{% if extended is defined and extended %}{{ lng('install.switchmode_basic') }}{% else %}{{ lng('install.switchmode_advanced') }}{% endif %}</label> <label class="form-check-label" for="switchInstallMode">{% if extended is defined and extended %}{{ lng('install.switchmode_basic') }}{% else %}{{ lng('install.switchmode_advanced') }}{% endif %}</label>
</div> </div>
</div> </div>
<p class="lead">{{ section.description }}</p> <p class="lead">{{ section.description|raw }}</p>
<hr /> <hr />
{% import "Froxlor/form/formfields.html.twig" as formfields %} {% import "Froxlor/form/formfields.html.twig" as formfields %}

View File

@@ -10,9 +10,16 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{ pagetitle }}</h5> <h5 class="card-title">{{ pagetitle }}</h5>
{% if message is not empty %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">{{ lng('error.error') }}</h4>
<p>{{ message|raw }}</p>
</div>
{% endif %}
<div class="mb-3"> <div class="mb-3">
<label for="2fa_code" class="col-form-label">{{ lng('login.2facode') }}</label> <label for="2fa_code" class="col-form-label">{{ lng('login.2facode') }}</label>
<input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autofocus required/> <input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<form class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded"> <form action="{{ formaction }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<img class="align-self-center my-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/> <img class="align-self-center my-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/>
<div class="card shadow"> <div class="card shadow">
@@ -38,8 +38,6 @@
</div> </div>
<div class="card-body d-grid gap-2"> <div class="card-body d-grid gap-2">
<input type="hidden" name="action" value="{{ action }}"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button> <button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button>
</div> </div>

View File

@@ -39,9 +39,6 @@
</div> </div>
<div class="card-body d-grid gap-2"> <div class="card-body d-grid gap-2">
<input type="hidden" name="script" value="{{ lastscript }}"/>
<input type="hidden" name="qrystr" value="{{ lastqrystr|raw }}"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="dologin">{{ lng('login.login') }}</button> <button class="btn btn-primary rounded-top-0" type="submit" name="dologin">{{ lng('login.login') }}</button>
</div> </div>

View File

@@ -30,8 +30,6 @@
</div> </div>
<div class="card-body d-grid gap-2"> <div class="card-body d-grid gap-2">
<input type="hidden" name="action" value="resetpwd"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button> <button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button>
</div> </div>

View File

@@ -0,0 +1,17 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container my-auto">
<div class="alert alert-warning fade show" role="alert">
<h4 class="alert-heading">
Whoops!
</h4>
<p>It seems like you've hit the rate limit.</p>
<p>Please slow down your requests and retry after {{ retry|date('d.m.Y H:i:s') }}</p>
<hr>
<p class="mt-1 text-center">
<a href="" class="btn btn-primary" title="Reload page">Reload</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -4,7 +4,7 @@
<h5> <h5>
<i class="fa-solid fa-gears"></i> <i class="fa-solid fa-gears"></i>
{{ lng('admin.serversettings') }} {{ lng('admin.serversettings') }}
{% if fields._group is defined %}&nbsp;&raquo;&nbsp;{{ fields._group.title }} {% if fields._group is defined %}&nbsp;&raquo;&nbsp;{{ fields._group.title|raw }}
{% endif %} {% endif %}
</h5> </h5>
<span class="text-muted">{{ lng('admin.serversettings_desc') }}</span> <span class="text-muted">{{ lng('admin.serversettings_desc') }}</span>
@@ -39,7 +39,7 @@
<i class="{{ field.icon }} fa-2x me-4" style="width: 1em;"></i> <i class="{{ field.icon }} fa-2x me-4" style="width: 1em;"></i>
</a> </a>
<div> <div>
{{ field.title }} {{ field.title|raw }}
{% if field.info is defined and field.info is not empty %} {% if field.info is defined and field.info is not empty %}
{{ field.info|raw }} {{ field.info|raw }}
{% endif %} {% endif %}

View File

@@ -5,4 +5,8 @@ $(function () {
history.back(1); history.back(1);
}) })
$('#copySysInfo').on('click', function (e) {
e.preventDefault();
navigator.clipboard.writeText($('#ccSysInfo').text().trim());
})
}); });

View File

@@ -55,6 +55,19 @@
<div class="card-header"> <div class="card-header">
<i class="fa-solid fa-gears me-1"></i> <i class="fa-solid fa-gears me-1"></i>
{{ lng('admin.systemdetails') }} {{ lng('admin.systemdetails') }}
<div class="float-end">
<button id="copySysInfo" class="btn btn-outline-dark" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .5rem;" title="Copy to clipboard"><i class="fa-solid fa-copy"></i></button>
</div>
<div id="ccSysInfo" class="d-none">
- Froxlor: {{ call_static('\\Froxlor\\Froxlor', 'getVersionString') }}
- {{ lng('serversettings.update_channel.title') }}: {{ get_setting('system.update_channel') }}
- {{ lng('admin.serversoftware') }}: {{ sysinfo.webserver }}
- {{ lng('admin.phpversion') }}: {{ sysinfo.phpversion }}
- {{ lng('admin.mysqlserverversion') }}: {{ sysinfo.mysqlserverversion }}
- {{ lng('admin.webserverinterface') }}: {{ sysinfo.phpsapi }}
- Kernel: {{ sysinfo.kernel }}
- OS: {{ get_setting('system.distribution') }}
</div>
</div> </div>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-start"> <li class="list-group-item d-flex justify-content-between align-items-start">

View File

@@ -19,11 +19,14 @@ class FroxlorTest extends TestCase
$json_result = Froxlor::getLocal($admin_userdata)->checkUpdate(); $json_result = Froxlor::getLocal($admin_userdata)->checkUpdate();
$result = json_decode($json_result, true)['data']; $result = json_decode($json_result, true)['data'];
$this->assertEquals(0, $result['isnewerversion']); $this->assertContains($result['isnewerversion'] ?? -1, [0,1]);
if (defined('DEV_FROXLOR') && DEV_FROXLOR == 1) { $this->assertNotEmpty($result['version']);
$this->assertEquals("You already have the latest testing-version of Froxlor installed.", $result['additional_info']); if ($result['isnewerversion'] == 0) {
} else { if (defined('DEV_FROXLOR') && DEV_FROXLOR == 1) {
$this->assertEquals("You already have the latest version of Froxlor installed.", $result['additional_info']); $this->assertEquals("You already have the latest testing-version of Froxlor installed.", $result['additional_info']);
} else {
$this->assertEquals("You already have the latest version of Froxlor installed.", $result['additional_info']);
}
} }
} }
} }