diff --git a/actions/admin/settings/110.accounts.php b/actions/admin/settings/110.accounts.php
index c3ebcca27..7a0b92aba 100644
--- a/actions/admin/settings/110.accounts.php
+++ b/actions/admin/settings/110.accounts.php
@@ -138,6 +138,26 @@
'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',
diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php
index d37da714d..95e840aa6 100644
--- a/install/froxlor.sql.php
+++ b/install/froxlor.sql.php
@@ -699,6 +699,8 @@
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.15'),
('system', 'traffictool', 'goaccess'),
+ ('system', 'req_limit_per_interval', 60),
+ ('system', 'req_limit_interval', 60),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
@@ -743,7 +745,7 @@
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.15'),
- ('panel', 'db_version', '202303150');
+ ('panel', 'db_version', '202304260');
DROP TABLE IF EXISTS `panel_tasks`;
diff --git a/install/install.php b/install/install.php
index b585c1b6f..365908afc 100644
--- a/install/install.php
+++ b/install/install.php
@@ -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 @@
// init twig
UI::initTwig(true);
UI::sendHeaders();
+RateLimiter::run(true);
$installer = new Install();
$installer->handle();
diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php
index 20daf7b74..30237d7e2 100644
--- a/install/updates/froxlor/update_2.x.inc.php
+++ b/install/updates/froxlor/update_2.x.inc.php
@@ -463,3 +463,12 @@
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');
+}
diff --git a/lib/Froxlor/Api/Api.php b/lib/Froxlor/Api/Api.php
index f32d73a62..ca6f2ba85 100644
--- a/lib/Froxlor/Api/Api.php
+++ b/lib/Froxlor/Api/Api.php
@@ -26,6 +26,7 @@
namespace Froxlor\Api;
use Exception;
+use Froxlor\Http\RateLimiter;
use Froxlor\Settings;
use voku\helper\AntiXSS;
@@ -52,6 +53,8 @@ public function __construct()
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();
}
/**
diff --git a/lib/Froxlor/Cli/CliCommand.php b/lib/Froxlor/Cli/CliCommand.php
index e3dbe7f6f..0db8c5a28 100644
--- a/lib/Froxlor/Cli/CliCommand.php
+++ b/lib/Froxlor/Cli/CliCommand.php
@@ -122,7 +122,7 @@ private function runUpdate(OutputInterface $output): int
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
- 'this',
+ $this,
'cleanUpdateOutput'
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php
index befe575c6..5a9295eae 100644
--- a/lib/Froxlor/Froxlor.php
+++ b/lib/Froxlor/Froxlor.php
@@ -34,7 +34,7 @@ final class Froxlor
const VERSION = '2.0.15';
// Database version (YYYYMMDDC where C is a daily counter)
- const DBVERSION = '202303150';
+ const DBVERSION = '202304260';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
diff --git a/lib/Froxlor/Http/RateLimiter.php b/lib/Froxlor/Http/RateLimiter.php
new file mode 100644
index 000000000..a5934c5dc
--- /dev/null
+++ b/lib/Froxlor/Http/RateLimiter.php
@@ -0,0 +1,46 @@
+ $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");
+ exit();
+ }
+
+ // 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);
+ }
+}
diff --git a/lib/Froxlor/PhpHelper.php b/lib/Froxlor/PhpHelper.php
index b4af5b62d..f5ee80893 100644
--- a/lib/Froxlor/PhpHelper.php
+++ b/lib/Froxlor/PhpHelper.php
@@ -519,7 +519,12 @@ public static function parseArrayToString(array $array, string $key = null, int
} 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));
diff --git a/lib/init.php b/lib/init.php
index 78e67f6c8..2b5909bc7 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -52,6 +52,7 @@
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 @@
// 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();
diff --git a/lng/de.lng.php b/lng/de.lng.php
index 8af35cebb..aeb904292 100644
--- a/lng/de.lng.php
+++ b/lng/de.lng.php
@@ -2072,6 +2072,14 @@
'toolselect' => 'Traffic Analyzer',
],
'requires_reconfiguration' => 'Änderungen an dieser Einstellungen benötigen unter Umständen eine erneute Konfiguration der folgenden Dienste:
%s',
+ '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' => 'Specify the time in seconds for the number of HTTP requests, default is "60"',
+ ],
],
'spf' => [
'use_spf' => 'Aktiviere SPF für Domains?',
diff --git a/lng/en.lng.php b/lng/en.lng.php
index 47e7f6b42..9e83d43ca 100644
--- a/lng/en.lng.php
+++ b/lng/en.lng.php
@@ -2196,6 +2196,14 @@
'goaccess' => 'goacccess'
],
'requires_reconfiguration' => 'Changing this settings might require a reconfiguration of the following services:
%s',
+ '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' => 'Zeit in Sekunden für die maximale Anzahl von HTTP-Anfragen, Standard ist "60".',
+ ],
],
'spf' => [
'use_spf' => 'Activate SPF for domains?',