Compare commits

...

90 Commits

Author SHA1 Message Date
3630f82817 greylisting 2.0 2025-09-24 16:45:43 +02:00
9ddd2e9154 styles 2025-09-03 12:10:46 +02:00
53afe4ebd1 new files
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 17:12:26 +01:00
4f69e8ee0e new css 2024-01-30 17:12:16 +01:00
32f5b0d5e9 new Theme
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 16:52:34 +01:00
53a6485a6e Maketank Theme migration
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 13:52:59 +01:00
f2643ac887 env test 3
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:46:20 +01:00
e37687a85d env test 3
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:44:29 +01:00
ccbc3286a5 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:39:58 +01:00
929a562324 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:36:50 +01:00
3704cf6621 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:35:09 +01:00
10238a1466 env test
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 14:04:03 +01:00
9002ddf4a2 env test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-12-12 14:03:10 +01:00
8a2de5a44a env test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-12-12 13:59:34 +01:00
96c0af18dd npm and compose
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-12 13:50:23 +01:00
5bb228ce78 npm and compose
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-12 13:48:08 +01:00
804128280c npm and compose 2023-12-12 13:47:32 +01:00
5b8e918f75 ssh test
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 13:45:23 +01:00
0e3e83d184 ssh test
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-12 10:51:21 +01:00
8ced61c6aa bogus edit for pipeline trigger
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-11 15:31:39 +01:00
29a2ab7567 2.0 upgrade test first
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-07 12:39:20 +01:00
Michael Kaufmann
166ec0575b set version to 2.0.24 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-06 11:18:18 +02:00
Andreu Trepat Rubirola
215e749ba8 added ca language (#1184) 2023-09-24 15:22:33 +02:00
Michael Kaufmann
506cccd7c8 fix vhost-cleaning regex for nginx-location directives; fixes #1185
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-12 15:20:56 +02:00
Michael Kaufmann
6d9014c29b fix API permission error in navigation when customer-hide-options include 'domains'; fixes #1183
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-07 15:34:06 +02:00
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
Michael Kaufmann
ca433d8a61 set version to 2.0.15 for update-bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-07 18:58:19 +02:00
Michael Kaufmann
8f4dfe1514 Fix the fix... 2023-04-07 11:51:44 +02:00
Michael Kaufmann
ee42f5168e Use correct SQL Syntax for older versions 2023-04-07 11:48:43 +02:00
222 changed files with 11280 additions and 11668 deletions

53
.drone.yml Normal file
View File

@@ -0,0 +1,53 @@
kind: pipeline
name: deploy-froxlor
type: docker
platform:
os: linux
arch: arm64
trigger:
branch:
- upgrade-2.0
event:
include:
- push
environment:
DEPLOY_HOST: rechner.maketank.net
DEPLOY_DIR: ~/froxlor-test
steps:
- name: deploy
image: cr.wks/drone/drone-rsync:latest
settings:
hosts: ["rechner02.maketank.net"]
source: ./
target: ~/froxlor-test
user: www-data
exclude: ['vendor', '.git*', '*drone.yml', '.settings', '.buildpath', '.editorconfig', '.project', '.travis.yml', 'node_modules']
args: '-v --delete'
log_level: quiet
key:
from_secret: ssh-www-data-maketank-rsa
command_timeout: 10m
- name: compose
image: appleboy/drone-ssh
settings:
host:
- rechner02.maketank.net
username: www-data
key:
from_secret: ssh-www-data-maketank-rsa
script:
- cd ~/froxlor-test && composer install --no-dev
- name: npm
image: appleboy/drone-ssh
settings:
host:
- rechner02.maketank.net
username: www-data
key:
from_secret: ssh-www-data-maketank-rsa
script:
- cd ~/froxlor-test && npm install && npm run build

View File

@@ -11,4 +11,4 @@ jobs:
- env:
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
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}}

1
.gitignore vendored
View File

@@ -18,7 +18,6 @@ img/
vendor/
node_modules/
fonts/
templates/*
!templates/index.html
!templates/Froxlor/
templates/Froxlor/assets/mix-manifest.json

View File

@@ -138,6 +138,26 @@ return [
'save_method' => 'storeSettingField',
'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' => [
'label' => lng('serversettings.accountprefix'),
'settinggroup' => 'customer',

View File

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

View File

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

View File

@@ -33,6 +33,7 @@ use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
if ($userinfo['change_serversettings'] == '1') {
if ($action == 'setconfigured') {
@@ -91,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
}
if ($distribution != "" && isset($_POST['finish'])) {
$valid_keys = ['http', 'dns', 'smtp', 'mail', 'ftp', 'system', 'distro'];
unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST;
@@ -99,6 +101,20 @@ if ($userinfo['change_serversettings'] == '1') {
foreach ($_POST['system'] as $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_filename = FileDir::makeCorrectFile(Froxlor::getInstallDir() . 'install/' . Froxlor::genSessionId() . '.json');
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['adminsession'] = 0;
$result['userid'] = $result['customerid'];
session_regenerate_id(true);
CurrentUser::setData($result);
$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
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
@@ -545,6 +551,12 @@ if ($page == 'domains' || $page == 'overview') {
$result['temporary_ssl_redirect'] = $result['ssl_redirect'];
$result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1);
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
1 => lng('domains.serveraliasoption_www'),

View File

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

View File

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

94
composer.lock generated
View File

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

View File

@@ -299,6 +299,30 @@ if ($page == 'email_domain') {
'action' => 'edit',
'id' => $id,
]);
} elseif ($action == 'togglegreylist' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
try {
Emails::getLocal($userinfo, [
'id' => $id,
'disablegreylist' => ($result['disablegreylist'] == '1' ? 0 : 1)
])->updateGreylist();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id,
]);
}
} elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0);

View File

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

101
index.php
View File

@@ -40,7 +40,6 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Response;
use Froxlor\User;
use Froxlor\Validate\Validate;
use Froxlor\Language;
if ($action == '') {
$action = 'login';
@@ -53,9 +52,15 @@ if ($action == '2fa_entercode') {
Response::redirectTo('index.php');
exit();
}
$smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0;
$message = "";
if ($smessage > 0) {
$message = lng('error.2fa_wrongcode');
}
// show template to enter code
UI::view('login/enter2fa.html.twig', [
'pagetitle' => lng('login.2fa')
'pagetitle' => lng('login.2fa'),
'message' => $message
]);
} elseif ($action == '2fa_verify') {
// verify code from 2fa code-enter form
@@ -68,25 +73,25 @@ if ($action == '2fa_entercode') {
// verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$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
// which is temporarily stored for the customer when using email-2fa
if ($result) {
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
$sel_param = [
'uid' => $uid
];
if ($_SESSION['secret_2fa'] == 'email') {
// 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
$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;
} else {
// 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);
// whoops, no (valid) user? Start again
@@ -108,19 +113,54 @@ if ($action == '2fa_entercode') {
// when using email-2fa, remove the one-time-code
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, [
'uid' => $uid
]);
}
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', [
'showmessage' => '2'
'action' => '2fa_entercode',
'showmessage' => '1'
]);
exit();
} elseif ($action == 'login') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$password = Validate::validate($_POST['password'], 'password');
@@ -390,13 +430,18 @@ if ($action == '2fa_entercode') {
}
$lastqrystr = "";
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', [
'pagetitle' => 'Login',
'lastscript' => $lastscript,
'lastqrystr' => $lastqrystr,
'upd_in_progress' => $update_in_progress,
'message' => $message,
'successmsg' => $successmessage
@@ -408,7 +453,7 @@ if ($action == 'forgotpwd') {
$adminchecked = false;
$message = '';
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$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 . "`
@@ -592,7 +637,7 @@ if ($action == 'forgotpwd') {
UI::view('login/fpwd.html.twig', [
'pagetitle' => lng('login.presend'),
'action' => $action,
'formaction' => 'index.php?action=' . $action,
'message' => $message,
]);
}
@@ -615,7 +660,7 @@ if ($action == 'resetpwd') {
$check = substr($activationcode, 40, 10);
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 . "`
WHERE `activationcode` = :activationcode");
$result = Database::pexecute_first($stmt, [
@@ -692,6 +737,7 @@ if ($action == 'resetpwd') {
function finishLogin($userinfo)
{
if (isset($userinfo['userid']) && $userinfo['userid'] != '') {
session_regenerate_id(true);
CurrentUser::setData($userinfo);
$language = $userinfo['def_language'] ?? Settings::Get('panel.standardlanguage');
@@ -705,29 +751,34 @@ function finishLogin($userinfo)
}
$qryparams = [];
if (isset($_POST['qrystr']) && $_POST['qrystr'] != "") {
parse_str(urldecode($_POST['qrystr']), $qryparams);
if (!empty($_SESSION['lastqrystr'])) {
parse_str(urldecode($_SESSION['lastqrystr']), $qryparams);
unset($_SESSION['lastqrystr']);
}
if ($userinfo['adminsession'] == '1') {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
Response::redirectTo('admin_updates.php?page=overview');
} else {
if (isset($_POST['script']) && $_POST['script'] != "") {
if (preg_match("/customer\_/", $_POST['script']) === 1) {
if (!empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
if (preg_match("/customer\_/", $lastscript) === 1) {
Response::redirectTo('admin_customers.php', [
"page" => "customers"
]);
} else {
Response::redirectTo($_POST['script'], $qryparams);
Response::redirectTo($lastscript, $qryparams);
}
} else {
Response::redirectTo('admin_index.php', $qryparams);
}
}
} else {
if (isset($_POST['script']) && $_POST['script'] != "") {
Response::redirectTo($_POST['script'], $qryparams);
if (!empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
Response::redirectTo($lastscript, $qryparams);
} else {
Response::redirectTo('customer_index.php', $qryparams);
}

View File

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

View File

@@ -23,6 +23,7 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Http\RateLimiter;
use Froxlor\UI\Panel\UI;
use Froxlor\Install\Install;
@@ -62,6 +63,7 @@ require dirname(__DIR__) . '/lib/tables.inc.php';
// init twig
UI::initTwig(true);
UI::sendHeaders();
RateLimiter::run(true);
$installer = new Install();
$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`;");
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("
SELECT `customerid`,
GROUP_CONCAT(DISTINCT `dbserver` SEPARATOR ',') as allowed_mysqlserver
@@ -149,7 +149,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
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.update_channel", 'stable');
Settings::AddNew("system.updatecheck_data", '');
@@ -430,7 +430,7 @@ if (Froxlor::isDatabaseVersion('202302030')) {
Update::lastStepStatus(0);
Update::showUpdateStep("Enhancing ssl data table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` RENAME COLUMN `expirationdate` TO `validtodate`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` CHANGE `expirationdate` `validtodate` datetime DEFAULT NULL;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `validfromdate` datetime DEFAULT NULL AFTER `ssl_fullchain_file`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `issuer` varchar(255) NOT NULL default '' AFTER `validtodate`;");
Update::lastStepStatus(0);
@@ -458,3 +458,62 @@ if (Froxlor::isFroxlorVersion('2.0.13')) {
Update::showUpdateStep("Updating from 2.0.13 to 2.0.14", false);
Froxlor::updateToVersion('2.0.14');
}
if (Froxlor::isFroxlorVersion('2.0.14')) {
Update::showUpdateStep("Updating from 2.0.14 to 2.0.15", false);
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');
}
if (Froxlor::isFroxlorVersion('2.0.23')) {
Update::showUpdateStep("Updating from 2.0.23 to 2.0.24", false);
Froxlor::updateToVersion('2.0.24');
}

View File

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

View File

@@ -26,6 +26,7 @@
namespace Froxlor\Api;
use Exception;
use Froxlor\Http\RateLimiter;
use Froxlor\Settings;
use voku\helper\AntiXSS;
@@ -52,6 +53,8 @@ class Api
if (Settings::Get('api.enabled') != 1) {
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
*
* @throws Exception
*/
public function __construct($params = null)
public function __construct(array $params = null)
{
if (!is_null($params)) {
$params = $this->trimArray($params);
@@ -57,7 +57,7 @@ abstract class ApiParameter
*
* @param array $input
*
* @return array
* @return string|array
*/
private function trimArray($input)
{
@@ -79,9 +79,9 @@ abstract class ApiParameter
/**
* 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
* @param string $ul_field
* @param string|null $ul_field
* parameter to get out of the request-parameter list
* @param bool $optional
* default: false
@@ -91,7 +91,7 @@ abstract class ApiParameter
* @return mixed
* @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);
$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.
* Maybe more in the future
*
* @param string $param
* @param string|null $param
* parameter to get out of the request-parameter list
* @param bool $optional
* default: false
@@ -116,7 +116,7 @@ abstract class ApiParameter
* @return mixed
* @throws Exception
*/
protected function getParam($param = null, $optional = false, $default = '')
protected function getParam(string $param = null, bool $optional = false, $default = '')
{
// does it exist?
if (!isset($this->cmd_params[$param])) {
@@ -128,7 +128,7 @@ abstract class ApiParameter
return $default;
}
// 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) {
// get module + function for better error-messages
$inmod = $this->getModFunctionString();
@@ -142,7 +142,7 @@ abstract class ApiParameter
/**
* 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
* depth of backtrace, default 2
@@ -152,7 +152,7 @@ abstract class ApiParameter
*
* @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
$_class = get_called_class();
@@ -174,7 +174,7 @@ abstract class ApiParameter
/**
* getParam wrapper for boolean parameter
*
* @param string $param
* @param string|null $param
* parameter to get out of the request-parameter list
* @param bool $optional
* default: false
@@ -183,7 +183,7 @@ abstract class ApiParameter
*
* @return string
*/
protected function getBoolParam($param = null, $optional = false, $default = false)
protected function getBoolParam(string $param = null, bool $optional = false, $default = false)
{
$_default = '0';
if ($default) {

View File

@@ -95,7 +95,7 @@ class Admins extends ApiCommand implements ResourceEntity
public function listing()
{
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 = [];
$result_stmt = Database::prepare("
SELECT *
@@ -407,7 +407,7 @@ class Admins extends ApiCommand implements ResourceEntity
];
$result = Database::pexecute_first($result_stmt, $params, true, true);
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);
}
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -584,6 +584,18 @@ class Admins extends ApiCommand implements ResourceEntity
$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)) {
Response::standardError('emailiswrong', $email, true);
} else {
@@ -705,7 +717,7 @@ class Admins extends ApiCommand implements ResourceEntity
WHERE `adminid` = :adminid
");
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
$result = $this->apiCall('Admins.get', [

View File

@@ -97,7 +97,7 @@ class Certificates extends ApiCommand implements ResourceEntity
}
if (!$has_cert) {
$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', [
'id' => $domain['id']
]);
@@ -248,7 +248,7 @@ class Certificates extends ApiCommand implements ResourceEntity
$ssl_ca_file = $this->getParam('ssl_ca_file', 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->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', [
'id' => $domain['id']
]);
@@ -470,7 +470,7 @@ class Certificates extends ApiCommand implements ResourceEntity
if ($chk['letsencrypt'] == '1') {
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);
}
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
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', [
'id' => $id
]);
@@ -177,7 +177,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
public function listing()
{
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 = [];
$result_stmt = Database::prepare("
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;
}
}
$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([
'count' => count($result),
'list' => $result

View File

@@ -895,7 +895,7 @@ class Customers extends ApiCommand implements ResourceEntity
$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);
}
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -1327,7 +1327,7 @@ class Customers extends ApiCommand implements ResourceEntity
'vu' => $valid_until
], 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);
}
@@ -1538,7 +1538,7 @@ class Customers extends ApiCommand implements ResourceEntity
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
@@ -1911,7 +1911,7 @@ class Customers extends ApiCommand implements ResourceEntity
// now, recalculate the resource-usage for the old and the new admin
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', [
'id' => $c_result['customerid']

View File

@@ -144,7 +144,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$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);
$result = $this->apiCall('DirOptions.get', [
@@ -247,7 +247,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
$params['id'] = $id;
$result = Database::pexecute_first($result_stmt, $params, true, true);
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);
}
$key = "id #" . $id;
@@ -331,7 +331,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"id" => $id
];
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', [
@@ -379,7 +379,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$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([
'count' => count($result),
'list' => $result
@@ -478,7 +478,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"customerid" => $customer_data['customerid'],
"id" => $id
], 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);
return $this->response($result);
}

View File

@@ -129,7 +129,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$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);
$result = $this->apiCall('DirProtections.get', [
@@ -196,7 +196,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true);
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);
}
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -279,7 +279,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
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', [
'id' => $result['id']
]);
@@ -325,7 +325,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$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([
'count' => count($result),
'list' => $result
@@ -413,7 +413,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
"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);
return $this->response($result);
}

View File

@@ -413,7 +413,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
$zone = Dns::createDomainZone($id);
$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));
}

View File

@@ -225,6 +225,8 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, whether php is enabled for this domain, default 0 (false)
* @param bool $openbasedir
* 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
* optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter
@@ -312,6 +314,7 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $this->getParam('documentroot', true, '');
$phpenabled = $this->getBoolParam('phpenabled', true, 0);
$openbasedir = $this->getBoolParam('openbasedir', true, 0);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 1);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', 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';
}
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports);
// check ssl IP
@@ -701,6 +708,7 @@ class Domains extends ApiCommand implements ResourceEntity
'caneditdomain' => $caneditdomain,
'phpenabled' => $phpenabled,
'openbasedir' => $openbasedir,
'openbasedir_path' => $openbasedir_path,
'speciallogfile' => $speciallogfile,
'specialsettings' => $specialsettings,
'ssl_specialsettings' => $ssl_specialsettings,
@@ -754,6 +762,7 @@ class Domains extends ApiCommand implements ResourceEntity
`caneditdomain` = :caneditdomain,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile,
`specialsettings` = :specialsettings,
`ssl_specialsettings` = :ssl_specialsettings,
@@ -889,7 +898,7 @@ class Domains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']);
}
$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);
}
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -1101,6 +1110,8 @@ class Domains extends ApiCommand implements ResourceEntity
* from setting system.apply_phpconfigs_default
* @param bool $openbasedir
* 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
* optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter
@@ -1198,6 +1209,7 @@ class Domains extends ApiCommand implements ResourceEntity
$phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']);
$phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default'));
$openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']);
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$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']);
@@ -1489,6 +1501,11 @@ class Domains extends ApiCommand implements ResourceEntity
$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
$ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']);
// check ssl IP
@@ -1634,7 +1651,31 @@ class Domains extends ApiCommand implements ResourceEntity
$wwwserveralias = ($serveraliasoption == '1') ? '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);
}
@@ -1760,7 +1801,7 @@ class Domains extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, [
'id' => $id
], 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';
@@ -1782,7 +1823,8 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir;
$update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid;
$update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
@@ -1830,6 +1872,7 @@ class Domains extends ApiCommand implements ResourceEntity
`iswildcarddomain` = :iswildcarddomain,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile,
`phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter,
@@ -1865,6 +1908,7 @@ class Domains extends ApiCommand implements ResourceEntity
$_update_data['adminid'] = $adminid;
$_update_data['phpenabled'] = $phpenabled;
$_update_data['openbasedir'] = $openbasedir;
$_update_data['openbasedir_path'] = $openbasedir_path;
$_update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
$_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests;
$_update_data['notryfiles'] = $notryfiles;
@@ -1898,6 +1942,7 @@ class Domains extends ApiCommand implements ResourceEntity
`adminid` = :adminid,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`notryfiles` = :notryfiles,
@@ -1914,6 +1959,18 @@ class Domains extends ApiCommand implements ResourceEntity
");
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
$del_stmt = Database::prepare("
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
$domainidsresult_stmt = Database::prepare("
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
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();
Cronjob::inserttask(TaskId::REBUILD_VHOST);
// 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
* optional email address to send account information to, default is the account that is being created
* @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
* 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)
@@ -85,7 +85,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$email_password = $this->getParam('email_password');
$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);
// validation
@@ -99,6 +99,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Response::standardError('notallowedtouseaccounts', '', true);
}
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// get email address
$result = $this->apiCall('Emails.get', [
'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', [
'emailaddr' => $result['email_full']
]);
@@ -357,6 +362,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// validation
$result = $this->apiCall('Emails.get', [
'id' => $id,
@@ -450,7 +460,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
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', [
'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_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);
}
}

View File

@@ -89,7 +89,7 @@ class EmailDomains extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$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");
return $this->response([
'count' => count($result),

View File

@@ -77,6 +77,11 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
$idna_convert = new IdnaWrapper();
$destination = $idna_convert->encode($destination);
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
$result = $this->apiCall('Emails.get', [
'id' => $id,
'emailaddr' => $emailaddr
@@ -116,7 +121,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
// update customer usage
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', [
'emailaddr' => $result['email_full']
@@ -293,7 +298,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
// update customer usage
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', [
'emailaddr' => $result['email_full']

View File

@@ -75,6 +75,7 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
$disablegreylist = $this->getBoolParam('disablegreylist', true, 0);
$description = $this->getParam('description', true, '');
// validation
@@ -118,7 +119,7 @@ class Emails extends ApiCommand implements ResourceEntity
// duplicate check
$stmt = Database::prepare("
SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "`
SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `disablegreylist` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE (`email` = :email OR `email_full` = :emailfull )
AND `customerid`= :cid
");
@@ -144,7 +145,8 @@ class Emails extends ApiCommand implements ResourceEntity
`email_full` = :email_full,
`iscatchall` = :iscatchall,
`domainid` = :domainid,
`description` = :description
`description` = :description,
`disablegreylist` = :disablegreylist
");
$params = [
"cid" => $customer['customerid'],
@@ -152,14 +154,15 @@ class Emails extends ApiCommand implements ResourceEntity
"email_full" => $email_full,
"iscatchall" => $iscatchall,
"domainid" => $domain_check['id'],
"description" => $description
"description" => $description,
"disablegreylist" => $disablegreylist
];
Database::pexecute($stmt, $params, true, true);
// update customer usage
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', [
'emailaddr' => $email_full
@@ -191,15 +194,15 @@ class Emails extends ApiCommand implements ResourceEntity
$customer_ids = $this->getAllowedCustomerIds('email');
$params['idea'] = ($id <= 0 ? $emailaddr : $id);
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`disablegreylist`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
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);
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);
}
$key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'");
@@ -294,7 +297,7 @@ class Emails extends ApiCommand implements ResourceEntity
"id" => $id
];
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', [
'emailaddr' => $result['email_full']
@@ -302,6 +305,81 @@ class Emails extends ApiCommand implements ResourceEntity
return $this->response($result);
}
/**
* toggle greylist flag of given email address either by id or email-address
*
* @param int $id
* optional, the email-address-id
* @param string $emailaddr
* optional, the email-address
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
* optional, required when called as admin (if $customerid is not specified)
* @param boolean $greylist
* optional
* @param string $description
* optional custom description (currently not used/shown in the frontend), default empty
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function updateGreylist()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
// if enabling catchall is not allowed by settings, we do not need
// to run update()
/** if (Settings::Get('catchall.catchall_enabled') != '1') {
Response::standardError([
'operationnotpermitted',
'featureisdisabled'
], 'catchall', true);
} */
$id = $this->getParam('id', true, 0);
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$result = $this->apiCall('Emails.get', [
'id' => $id,
'emailaddr' => $emailaddr
]);
$id = $result['id'];
$email = $result['email'];
// parameters
$disablegreylist = $this->getBoolParam('disablegreylist', true, $result['disablegreylist']);
$description = $this->getParam('description', true, $result['description']);
// get needed customer info to reduce the email-address-counter by one
$customer = $this->getCustomerData();
// check for catchall-flag
$stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "`
SET `email` = :email , `disablegreylist` = :grflag, `description` = :description
WHERE `customerid`= :cid AND `id`= :id
");
$params = [
"email" => $email,
"grflag" => $disablegreylist,
"description" => $description,
"cid" => $customer['customerid'],
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled greylist-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
return $this->response($result);
}
/**
* list all email addresses, if called from an admin, list all email addresses of all customers you are allowed to
* view, or specify id or loginname for one specific customer
@@ -331,7 +409,7 @@ class Emails extends ApiCommand implements ResourceEntity
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`disablegreylist`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` m
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`)
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)
@@ -340,7 +418,7 @@ class Emails extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$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([
'count' => count($result),
'list' => $result
@@ -445,7 +523,7 @@ class Emails extends ApiCommand implements ResourceEntity
], true, true);
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);
}
}

View File

@@ -64,7 +64,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
public function listing()
{
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 = [];
$result_stmt = Database::prepare("
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();
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', [
'id' => $id
]);
@@ -384,7 +384,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, $upd_data, true, true);
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', [
'id' => $id
]);
@@ -433,7 +433,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
], true, true);
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);
}
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) {
// 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
$aucheck = AutoUpdate::checkVersion();
@@ -142,7 +142,7 @@ class Froxlor extends ApiCommand
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$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 {
SImExporter::import($json_str);
Cronjob::inserttask(TaskId::REBUILD_VHOST);

View File

@@ -257,7 +257,7 @@ class Ftps extends ApiCommand implements ResourceEntity
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);
if ($sendinfomail == 1) {
@@ -302,7 +302,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$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', [
'username' => $username
@@ -367,7 +367,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true);
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);
}
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -453,7 +453,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"id" => $id,
"password" => $cryptPassword
], 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?
@@ -471,7 +471,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"customerid" => $customer['customerid'],
"id" => $id
], 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
@@ -533,7 +533,7 @@ class Ftps extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$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([
'count' => count($result),
'list' => $result

View File

@@ -61,7 +61,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
public function listing()
{
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 = [];
$result_stmt = Database::prepare("
SELECT p.*, a.loginname as adminname
@@ -200,8 +200,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, 0);
// validation
$name = Validate::validate(trim($name), 'name', '', '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -227,7 +227,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'valuearr' => json_encode($value_arr)
];
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', [
'planname' => $name
]);
@@ -264,7 +264,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
}
$result = Database::pexecute_first($result_stmt, $params, true, true);
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);
}
$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']);
// validation
$name = Validate::validate(trim($name), 'name', '', '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -414,7 +414,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'id' => $id
];
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);
}
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()
{
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 = "";
$append_where = false;
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);
if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = !empty($this->getBoolParam('ssl', true, 0)) ? intval($this->getBoolParam('ssl', true, 0)) : 0;
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $ssl, ''), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $ssl, ''), 'ssl_key_file', '', '', [], true);
$ssl = (bool)$this->getBoolParam('ssl', true, 0);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, ''), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, ''), 'ssl_key_file', '', '', [], true);
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_file', '', '', [], true);
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
$sslss = $this->getParam('ssl_specialsettings', true, '');
@@ -335,7 +335,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
'id' => $id
], true, true);
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);
}
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);
if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = $this->getBoolParam('ssl', true, $result['ssl']);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
$ssl = (bool)$this->getBoolParam('ssl', true, $result['ssl']);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, $result['ssl_ca_file']), 'ssl_ca_file', '', '', [], true);
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', [], true);
$sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);

View File

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

View File

@@ -199,7 +199,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$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', [
'dbname' => $username,
@@ -299,7 +299,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC);
Database::needRoot(false);
$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);
}
$key = ($id > 0 ? "id #" . $id : "dbname '" . $dbname . "'");
@@ -388,7 +388,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
];
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', [
'dbname' => $result['databasename']
]);

View File

@@ -67,7 +67,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
public function listing()
{
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);
$query_fields = [];
@@ -392,7 +392,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$ins_data['id'] = Database::lastInsertId();
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', [
'id' => $ins_data['id']
@@ -629,7 +629,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, $upd_data, true, true);
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', [
'id' => $id
@@ -686,7 +686,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
], true, true);
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);
}
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
* parameter also for URLs
* @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
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
@@ -104,7 +104,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$aliasdomain = $this->getParam('alias', true, 0);
$path = $this->getParam('path', 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);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$isemaildomain = $this->getParam('isemaildomain', true, 0);
@@ -486,7 +486,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']);
}
$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);
}
$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_DNS);
$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', [
'id' => $id

View File

@@ -92,7 +92,7 @@ class SysLog extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$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([
'count' => count($result),
'list' => $result

View File

@@ -166,7 +166,7 @@ class Traffic extends ApiCommand implements ResourceEntity
$row['mail'] *= 1024;
$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([
'count' => count($result),
'list' => $result

View File

@@ -122,7 +122,7 @@ class CliCommand extends Command
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
'this',
$this,
'cleanUpdateOutput'
]);
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] ?? '';
$ips = [];
exec('hostname -I', $ips);
$ips = explode(" ", $ips[0]);
$ips = explode(" ", $ips[0] ?? "");
// ipv4 address?
$_SERVER['SERVER_ADDR'] = filter_var($ips[0] ?? "", FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ips[0] ?? '') : '';
if (empty($_SERVER['SERVER_ADDR'])) {

View File

@@ -62,6 +62,11 @@ final class MasterCron extends CliCommand
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
$jobs = $input->getArgument('job');
// handle force option
@@ -111,8 +116,8 @@ final class MasterCron extends CliCommand
]);
$this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG'));
// check whether there are actual tasks to perform by 'tasks'-cron so
// we dont regenerate files unnecessarily
// check whether there are actual tasks to perform by 'tasks'-cron, so
// we don't regenerate files unnecessarily
$tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`");
$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
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'];
$extensions = explode(" ", $filesmatch);
$filesmatch = "";
@@ -141,23 +144,19 @@ class ApacheFcgi extends Apache
$php_options_text .= ' </FilesMatch>' . "\n";
$mypath_dir = new Directory($domain['documentroot']);
// only create the require all granted if there is not active directory-protection
// only create the "require all granted" directive if there is no active directory-protection
// for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) {
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
if ($phpconfig['pass_authorizationheader'] == '1') {
$php_options_text .= ' CGIPassAuth On' . "\n";
}
$php_options_text .= ' Require all granted' . "\n";
$php_options_text .= ' AllowOverride All' . "\n";
$php_options_text .= ' </Directory>' . "\n";
} elseif ($phpconfig['pass_authorizationheader'] == '1') {
// allow Pass of Authorization header
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
$php_options_text .= ' CGIPassAuth On' . "\n";
$php_options_text .= ' </Directory>' . "\n";
}
$php_options_text .= ' </Directory>' . "\n";
} else {
$addheader = "";
if ($phpconfig['pass_authorizationheader'] == '1') {

View File

@@ -236,9 +236,12 @@ class AcmeSh extends FroxlorCron
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');
if (is_dir($certificate_folder) && file_exists($ssl_file) && is_readable($ssl_file)) {
@@ -250,9 +253,13 @@ class AcmeSh extends FroxlorCron
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";
}
$env_file = FileDir::makeCorrectFile(dirname(self::getAcmeSh()) . '/acme.sh.env');
@@ -262,7 +269,7 @@ class AcmeSh extends FroxlorCron
cut -d'"' -f2
EOC;
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);
}
}
@@ -635,35 +642,21 @@ EOC;
*/
private static function readCertificateToVar($domain, &$return, &$cronlog)
{
$certificate_folder = self::getWorkingDirFromEnv($domain);
$certificate_folder_noecc = null;
if (Settings::Get('system.leecc') > 0) {
$certificate_folder_noecc = self::getWorkingDirFromEnv($domain, true);
}
$certificate_folder = FileDir::makeCorrectDir($certificate_folder);
$certificate_folder = self::getCertificateFolder($domain);
if (is_dir($certificate_folder) || is_dir($certificate_folder_noecc)) {
foreach (
[
'crt' => $domain . '.cer',
'key' => $domain . '.key',
'chain' => 'ca.cer',
'fullchain' => 'fullchain.cer',
'csr' => $domain . '.csr'
] as $index => $sslfile
) {
if (!empty($certificate_folder)) {
$certificate_files = [
'crt' => $domain . '.cer',
'key' => $domain . '.key',
'chain' => 'ca.cer',
'fullchain' => 'fullchain.cer',
'csr' => $domain . '.csr'
];
foreach ($certificate_files as $index => $sslfile) {
$ssl_file = FileDir::makeCorrectFile($certificate_folder . '/' . $sslfile);
if (file_exists($ssl_file)) {
$return[$index] = file_get_contents($ssl_file);
} 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 . "'");
$return[$index] = null;
}
@@ -672,4 +665,18 @@ EOC;
$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";
// 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" . '}' . "\n";
}
@@ -883,13 +883,7 @@ class Nginx extends HttpConfigBase
// remove comments
$vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost)));
// Break blocks into lines
$vhost = str_replace([
"{",
"}"
], [
" {\n",
"\n}"
], $vhost);
$vhost = preg_replace("/^(\s+)?location(.+)\{(.+)\}$/misU", "location $2 {\n $3 \n}", $vhost);
// Break into array items
$vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost))));
// Remove empty lines
@@ -1040,9 +1034,11 @@ class Nginx extends HttpConfigBase
$path_options .= "\t\t" . 'auth_basic_user_file ' . FileDir::makeCorrectFile($single['usrf']) . ';' . "\n";
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" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n";
$path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n";
$path_options .= "\t\t" . '}' . "\n\n";
if ($domain['notryfiles'] != 1) {
$path_options .= "\t\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n";
$path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n";
$path_options .= "\t\t" . '}' . "\n\n";
}
} else {
$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\tfastcgi_param SCRIPT_FILENAME \$request_filename;\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_index index.php;\n";
if ($domain['ssl'] == '1' && $ssl_vhost) {

View File

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

View File

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

View File

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

View File

@@ -31,10 +31,10 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.0.14';
const VERSION = '2.0.24';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202303150';
const DBVERSION = '202304260';
// Distribution branding-tag (used for Debian etc.)
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";
$config_dir = "/etc/php/" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "/fpm/pool.d/";
// 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_PHPCONFIGS . "` SET `binary` = '" . $binary . "';");

View File

@@ -449,7 +449,15 @@ class PhpHelper
'ssl_specialsettings',
'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)) {
$tmp = $global;
@@ -519,7 +527,12 @@ class PhpHelper
} elseif (is_int($value)) {
$str .= self::tabPrefix($depth, "'{$key}' => $value,\n");
} 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 {
$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);
}
/**
* 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
*/
public static function sendHeaders()
{
$cookie_host = empty($_SERVER['HTTP_HOST']) ? null : explode (':', $_SERVER['HTTP_HOST'])[0];
session_set_cookie_params([
'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php
'path' => '/',
'domain' => $cookie_host,
'domain' => self::getCookieHost(),
'secure' => self::requestIsHttps(),
'httponly' => true,
'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))) {
$theme = self::getTheme();
}

View File

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

View File

@@ -2600,7 +2600,6 @@ ServerName "<SERVERNAME> FTP Server"
ServerType standalone
DeferWelcome off
MultilineRFC2228 on
DefaultServer 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 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
</IfModule>
]]>
</content>
@@ -2955,7 +2953,7 @@ TLSRSACertificateFile /etc/ssl/certs/proftpd.crt
TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key
TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt
TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key
TLSOptions NoCertRequest NoSessionReuseRequired
TLSOptions NoSessionReuseRequired
TLSVerifyClient off
# 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_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>
<!-- instead of just restarting apache, we let the cronjob do all the
dirty work -->
@@ -3350,7 +3348,7 @@ aliases: files
</visibility>
<visibility mode="true">{{settings.phpfpm.enabled_ownvhost}}
</visibility>
<command><![CDATA[a2dismod php8.1]]></command>
<command><![CDATA[a2dismod php8.2]]></command>
</commands>
<!-- instead of just restarting apache, we let the cronjob do all the
dirty work -->

View File

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

View File

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

View File

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

View File

@@ -57,7 +57,7 @@ return [
'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),
'type' => $pathSelect['type'],
'select_var' => $pathSelect['value'],
'select_var' => $pathSelect['select_var'] ?? '',
'selected' => $pathSelect['value'],
'value' => $pathSelect['value'],
'note' => $pathSelect['note'] ?? '',

View File

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

View File

@@ -52,7 +52,13 @@ return [
'type' => 'checkbox',
'value' => '1',
'checked' => false
]
],
'disablegreylist' => [
'label' => lng('emails.disablegreylist'),
'type' => 'checkbox',
'value' => '1',
'checked' => false
]
]
]
]

View File

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

View File

@@ -102,6 +102,19 @@ return [
]
]
],
'mail_disablegreylist' => [
'label' => lng('emails.greylist'),
'type' => 'label',
'value' => ((int)$result['disablegreylist'] == 0 ? lng('panel.no') : lng('panel.yes')),
'next_to' => [
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=' . $page . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglegreylist&amp;id=' . $result['id'],
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
'classes' => 'btn btn-sm btn-secondary'
]
]
],
'mail_fwds' => [
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',
'type' => 'itemlist',

View File

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

View File

@@ -38,7 +38,7 @@ return [
'url' => 'customer_email.php?page=emails',
'label' => lng('menue.email.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'),
@@ -60,7 +60,7 @@ return [
'url' => 'customer_mysql.php?page=mysqls',
'label' => lng('menue.mysql.databases'),
'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'),
@@ -81,7 +81,7 @@ return [
[
'url' => 'customer_domains.php?page=domains',
'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',
@@ -98,7 +98,7 @@ return [
[
'url' => 'customer_ftp.php?page=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'),

View File

@@ -55,6 +55,12 @@ return [
'callback' => [Text::class, 'boolean'],
'visible' => Settings::Get('catchall.catchall_enabled') == '1'
],
'm.disablegreylist' => [
'label' => lng('emails.greylist'),
'field' => 'disablegreylist',
'callback' => [Text::class, 'boolean'],
'#visible' => Settings::Get('greylist.greylist_enabled') == '1'
],
'u.quota' => [
'label' => lng('emails.quota'),
'field' => 'quota',
@@ -66,6 +72,7 @@ return [
'm.destination',
'm.popaccountid',
'm.iscatchall',
'm.disablegreylist',
'u.quota'
]),
'actions' => [

2406
lng/ca.lng.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -724,6 +724,8 @@ return [
'back_to_overview' => 'Zurück zur Domain-Übersicht',
'accounts' => 'Konten',
'emails' => 'Adressen',
'disablegreylist' => 'Greylisting deaktivieren?',
'greylist' => 'Greylisting aus?'
],
'error' => [
'error' => 'Fehlermeldung',
@@ -926,6 +928,7 @@ return [
'domaincannotbeedited' => 'Keine Berechtigung, um die Domain %s zu bearbeiten',
'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.',
'2fa_wrongcode' => 'Der angegebene Code ist nicht korrekt',
],
'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.',
@@ -2072,6 +2075,14 @@ Vielen Dank, Ihr Administrator',
'toolselect' => 'Traffic Analyzer',
],
'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' => [
'use_spf' => 'Aktiviere SPF für Domains?',
@@ -2179,6 +2190,7 @@ Vielen Dank, Ihr Administrator',
'description' => 'Aktualisierung der froxlor Datenbank',
'uc_newinfo' => 'Eine neuere %sVersion ist verfügbar: "%s" (Aktuell installierte Version: %s)',
'notify_subject' => 'Neues Update verfügbar',
'dbupdate_required' => 'Froxlor-Dateien wurden aktualisiert, Datenbank-Aktualisierung notwendig',
],
'usersettings' => [
'custom_notes' => [
@@ -2233,8 +2245,8 @@ Vielen Dank, Ihr Administrator',
'install' => [
'top' => 'Abschluss',
'title' => 'Ein letzter Schritt...',
'description' => 'Der untenstehende Befehl lädt, installiert und konfiguriert die benötigten Dienste auf dem System aufgrund der Angaben die während des Installationsprozessen gesammelt wurden.',
'runcmd' => 'Folgenden Befehl als root-Benutzer in der Shell auf dem Server ausführen:',
'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' => 'Folgende Befehle ausführen, um die Installation abzuschließen:',
'manual_config' => 'Ich werden die Dienste manuell konfigurieren, direkt zum Login umleiten',
'waitforconfig' => 'Warte auf Abschluss der Dienstkonfiguration...',
],

View File

@@ -34,6 +34,7 @@ return [
'pt' => 'Portuguese',
'se' => 'Swedish',
'es' => 'Spanish',
'ca' => 'Catalan',
],
'2fa' => [
'2fa' => '2FA options',
@@ -952,7 +953,7 @@ return [
'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_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_7' => 'The downloaded archive could not be found :(',
'autoupdate_8' => 'The archive could not be extracted :(',
@@ -995,6 +996,7 @@ return [
'domaincannotbeedited' => 'You are not permitted to edit the domain %s',
'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s',
'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data',
'2fa_wrongcode' => 'The code entered is not valid',
],
'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.',
@@ -1193,7 +1195,7 @@ Yours sincerely, your administrator',
'database_edit' => 'Edit database',
'size' => 'Size',
'privileged_user' => 'Privileged database user',
'privileged_passwd' => 'Password for priviliged user',
'privileged_passwd' => 'Password for privileged user',
'unprivileged_passwd' => 'Password for unprivileged user',
'mysql_ssl_ca_file' => 'SSL server certificate',
'mysql_ssl_verify_server_certificate' => 'Verify SSL server certificate'
@@ -1262,7 +1264,7 @@ Yours sincerely, your administrator',
'reset' => 'Discard changes',
'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.',
'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',
'reseller' => 'reseller',
'admin' => 'admin',
@@ -2196,6 +2198,14 @@ Yours sincerely, your administrator',
'goaccess' => 'goacccess'
],
'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' => [
'use_spf' => 'Activate SPF for domains?',
@@ -2310,6 +2320,7 @@ Yours sincerely, your administrator',
'description' => 'Running database updates for your froxlor installation',
'uc_newinfo' => 'There is a newer %sversion available: "%s" (Your current version is: %s)',
'notify_subject' => 'New update available',
'dbupdate_required' => 'Froxlor files have been updated, database update required',
],
'usersettings' => [
'custom_notes' => [
@@ -2365,8 +2376,8 @@ Yours sincerely, your administrator',
'install' => [
'top' => 'Finish setup',
'title' => 'One last step...',
'description' => 'The command below will download, install and configure required services on your system according to the data you have given in this installation process.',
'runcmd' => 'Run the following command as root-user in your shell on this server:',
'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 to finish the installation:',
'manual_config' => 'I will manually configure the services, just take me to the login',
'waitforconfig' => 'Waiting for services to be configured...',
],

55
notice.html Normal file
View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>froxlor - Domain not configured</title>
<style>
:root{--primary:#1872a2;--fonts:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}
body{display:flex;flex-direction:column;background:#f8f9fa;color:#4b5563;align-items:center;justify-content:center;font-family:var(--fonts)}
main{background:#fff;margin:10% auto 12px;max-width:540px;padding:2rem;box-shadow:4px 8px 16px 0 rgba(0,0,0,.07);border-radius:.375rem}
main h2{margin:0}
main p{margin-bottom:1.5rem}
main p:last-child{margin-bottom:0}
main ul{list-style:none;margin-left:-40px}
main li{display:flex;align-items:center;margin-bottom:1rem}
main .icon{min-width:24px;width:24px;stroke:var(--primary);margin-right:.75rem}
code{background:#eee;padding:.1rem .25rem;border-radius:4px;color:rgba(0,0,0,.75)}
hr{margin:2rem 0;border:none;border-bottom:solid 1px rgba(0,0,0,.1)}
a,a:active,a:visited{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
footer{display:flex;align-items:center;margin-top:.5rem}
footer .logo{margin-right:.35rem}
@media (prefers-color-scheme: dark) {
:root{--primary:#29a2d6}
body{background:#212529;color:#f8f9fa}
main{background:#343a40}
hr{border-color:rgba(0,0,0,.2)}
}
</style>
</head>
<body>
<main>
<h2>Domain not configured</h2>
<p>
This domain requires configuration via the froxlor server management panel, as it is currently not assigned to any customer.
</p>
<ul>
<li>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>
<span>Please ask your provider/hoster if you have any questions.</span>
</li>
</ul>
</main>
<footer>
<img class="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAAAQCAYAAAC1MDndAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAy1JREFUeNrs1muIVlUUBuBnvqZkoswudKErZFOG0kWt6WpN0wWCDMOsgX4UmVS/KovKhIKYLhRBJvV1o/5VlJCmYEmUFYijaBYhSRQVBVE0ZnRBJ/vzfnA4nDN+wTh/pgWHc/baa6+z97v3et/d0Ww24SQ8ij5MNL7tN6zBvdjWiZOxDpP8b3JA5qAXPQ0MtAnOm9jZBvqbsQHbsGsMFrQLv+KPUc47CQONlFXZPsNfJd/buBrDFfHDWIDDcAZmohsPjwFAD+EQnL8Xcl/WqOGc+/FJyXcxVuHJivjX8Hx2cwGeweO4fIxOkL10Wg9o1HSsxUcl30V5P1JxnFuxp+M53I67cQ5mYwZexj2YlnarJBdl3BE4FfPxdfofSOwNhX+uwgXxf1Uz/024DpNxFHrwRIEiPs74XnyIq3A8Xion6qxIvj0TX1vyb8x7CJ/jrELfjrx/zgmS0p2bCWzHzejAQTgmO35J+OpInIcv8GLKeROuzcI24lAsRH/yzcGJFfMfDIB/B/ipeD8bNojX8W1hPR+gCxPwTzsAdaGRgTeGrAZLJXd4zc59l1Jr2dzC91S8g+PSXhFwGgGxO4s6Bd9gaQRkAHdhSQHso/FCzRweS56ezLmB1bgCb+DBCg67rwaLSud+uDAAvVLRfw1OqCO18JEKbusvgNMqA/F153sCpgegT+O7Iwt8NwrZwKsh5irbUuDMFoXMquhv2eKRSKiOg54OUFXSt3SEfPvi4Dz77IEAW/8uH+vdpf6dkXGF+F9GyNtRkXe4or8tqwNoWrigbDeFTEfDZhbKckuBy9blu0XkC1Pi++PK+Objy5q80/NeXSDl9wrgnDkaAIlyzCj5zh1FCb00JL07Jd2LKfgBx+LWXE6XJP6pcMiUiMg8/FmRd3GEYHNiZ+H6wgZP/i+T7MzPJo5wkjYU2t01cWfjp6hH2WZn0adVbM7KALAS3weYftwZIVgTIHtxS8YtD6kOYX2Uqi8iIKCsD6CDhXnNi+iI+vXhwD3g83tHs9l8K5JZZbfh2UL7x0jyeLFljVzUhtoc0DWOwBnCoga25s6wrHDhG8+2I1j0YOu/AwBUU7aBHvM/ZwAAAABJRU5ErkJggg==" alt="froxlor"/>
<small>&copy; 2009-<span id="year"></span> by <a href="https://froxlor.org" rel="external">the froxlor team</a></small>
</footer>
<script>
document.getElementById("year").innerHTML = new Date().getFullYear();
</script>
</body>
</html>

13833
package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* @kurkle/color v0.2.1
* https://github.com/kurkle/color#readme
* (c) 2022 Jukka Kurkela
* Released under the MIT License
*/
/*!
* Chart.js v3.9.1
* https://www.chartjs.org
* (c) 2022 Chart.js Contributors
* Released under the MIT License
*/
/*!
* Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2023 Fonticons, Inc.
*/
/*!
* jQuery JavaScript Library v3.7.1
* https://jquery.com/
*
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2023-08-28T13:37Z
*/
/*!
* jQuery Validation Plugin v1.20.0
*
* https://jqueryvalidation.org/
*
* Copyright (c) 2023 Jörn Zaefferer
* Released under the MIT license
*/

View File

@@ -0,0 +1,13 @@
{
"/js/main.js": "/js/main.js?id=29451cc889431e973473738b93e6a626",
"/css/dark.css": "/css/dark.css?id=f965da97d900604705c3762bb9cd645a",
"/css/main.css": "/css/main.css?id=cbda89c88526530a66fe1e0d46a63a22",
"/webfonts/fa-brands-400.ttf": "/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
"/webfonts/fa-brands-400.woff2": "/webfonts/fa-brands-400.woff2?id=189b85e9c72c6f75e464c3f58a6707cf",
"/webfonts/fa-regular-400.ttf": "/webfonts/fa-regular-400.ttf?id=ed4c23399d1013809882e90bfe396d1b",
"/webfonts/fa-regular-400.woff2": "/webfonts/fa-regular-400.woff2?id=be75b1958ae0da55e1eed562d9b7713d",
"/webfonts/fa-solid-900.ttf": "/webfonts/fa-solid-900.ttf?id=dfdc7801582dd0d20ea75faa3b96c296",
"/webfonts/fa-solid-900.woff2": "/webfonts/fa-solid-900.woff2?id=a0feb384c3c6071947a49708f2b0bc85",
"/webfonts/fa-v4compatibility.ttf": "/webfonts/fa-v4compatibility.ttf?id=e24ec0b8661f7fa333b29444df39e399",
"/webfonts/fa-v4compatibility.woff2": "/webfonts/fa-v4compatibility.woff2?id=e11465c0eff0549edd4e8ea6bbcf242f"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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>
</div>
</div>
<p class="lead">{{ section.description }}</p>
<p class="lead">{{ section.description|raw }}</p>
<hr />
{% import "Froxlor/form/formfields.html.twig" as formfields %}

View File

@@ -10,9 +10,16 @@
<div class="card-body">
<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">
<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>

View File

@@ -3,7 +3,7 @@
{% block content %}
<div class="container">
<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"/>
<div class="card shadow">
@@ -38,8 +38,6 @@
</div>
<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>
</div>

View File

@@ -39,9 +39,6 @@
</div>
<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>
</div>

View File

@@ -30,8 +30,6 @@
</div>
<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>
</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>
<i class="fa-solid fa-gears"></i>
{{ 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 %}
</h5>
<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>
</a>
<div>
{{ field.title }}
{{ field.title|raw }}
{% if field.info is defined and field.info is not empty %}
{{ field.info|raw }}
{% endif %}

View File

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

Some files were not shown because too many files have changed in this diff Show More