diff --git a/actions/admin/settings/180.antispam.php b/actions/admin/settings/180.antispam.php
new file mode 100644
index 00000000..01990c53
--- /dev/null
+++ b/actions/admin/settings/180.antispam.php
@@ -0,0 +1,111 @@
+
+ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+return [
+ 'groups' => [
+ 'antispam' => [
+ 'title' => lng('admin.antispam_settings'),
+ 'icon' => 'fa-solid fa-clipboard-check',
+ 'fields' => [
+ 'antispam_activated' => [
+ 'label' => lng('antispam.activated'),
+ 'settinggroup' => 'antispam',
+ 'varname' => 'activated',
+ 'type' => 'checkbox',
+ 'default' => true,
+ 'overview_option' => true,
+ 'save_method' => 'storeSettingFieldInsertAntispamTask',
+ ],
+ 'antispam_config_file' => [
+ 'label' => lng('antispam.config_file'),
+ 'settinggroup' => 'antispam',
+ 'varname' => 'config_file',
+ 'type' => 'text',
+ 'string_type' => 'file',
+ 'default' => '/etc/rspamd/local.d/froxlor_settings.conf',
+ 'save_method' => 'storeSettingFieldInsertAntispamTask',
+ 'requires_reconf' => ['antispam']
+ ],
+ 'antispam_reload_command' => [
+ 'label' => lng('antispam.reload_command'),
+ 'settinggroup' => 'antispam',
+ 'varname' => 'reload_command',
+ 'type' => 'text',
+ 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
+ 'default' => 'service rspamd restart',
+ 'save_method' => 'storeSettingField',
+ 'required_otp' => true
+ ],
+ 'antispam_dkim_keylength' => [
+ 'label' => lng('antispam.dkim_keylength'),
+ 'settinggroup' => 'antispam',
+ 'varname' => 'dkim_keylength',
+ 'type' => 'select',
+ 'default' => '1024',
+ 'select_var' => [
+ '1024' => '1024 Bit',
+ '2048' => '2048 Bit'
+ ],
+ 'save_method' => 'storeSettingFieldInsertBindTask',
+ 'advanced_mode' => true,
+ ],
+ 'spf_use_spf' => [
+ 'label' => lng('spf.use_spf'),
+ 'settinggroup' => 'spf',
+ 'varname' => 'use_spf',
+ 'type' => 'checkbox',
+ 'default' => false,
+ 'save_method' => 'storeSettingField',
+ ],
+ 'spf_spf_entry' => [
+ 'label' => lng('spf.spf_entry'),
+ 'settinggroup' => 'spf',
+ 'varname' => 'spf_entry',
+ 'type' => 'text',
+ 'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
+ 'default' => 'v=spf1 a mx -all',
+ 'save_method' => 'storeSettingField'
+ ],
+ 'dmarc_use_dmarc' => [
+ 'label' => lng('dmarc.use_dmarc'),
+ 'settinggroup' => 'dmarc',
+ 'varname' => 'use_dmarc',
+ 'type' => 'checkbox',
+ 'default' => false,
+ 'save_method' => 'storeSettingField',
+ ],
+ 'dmarc_dmarc_entry' => [
+ 'label' => lng('dmarc.dmarc_entry'),
+ 'settinggroup' => 'dmarc',
+ 'varname' => 'dmarc_entry',
+ 'type' => 'text',
+ 'string_regexp' => '/^v=dmarc1(.+)$/i',
+ 'default' => 'v=DMARC1; p=none;',
+ 'save_method' => 'storeSettingField'
+ ]
+ ]
+ ]
+ ]
+];
diff --git a/actions/admin/settings/180.dkim.php b/actions/admin/settings/180.dkim.php
deleted file mode 100644
index 2feb67bc..00000000
--- a/actions/admin/settings/180.dkim.php
+++ /dev/null
@@ -1,146 +0,0 @@
-
- * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
- */
-
-use Froxlor\Settings;
-
-return [
- 'groups' => [
- 'dkim' => [
- 'title' => lng('admin.dkimsettings'),
- 'icon' => 'fa-solid fa-fingerprint',
- 'fields' => [
- 'dkim_use_dkim' => [
- 'label' => lng('dkim.use_dkim'),
- 'settinggroup' => 'dkim',
- 'varname' => 'use_dkim',
- 'type' => 'checkbox',
- 'default' => false,
- 'save_method' => 'storeSettingFieldInsertBindTask',
- 'overview_option' => true
- ],
- 'dkim_dkim_prefix' => [
- 'label' => lng('dkim.dkim_prefix'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_prefix',
- 'type' => 'text',
- 'string_type' => 'dir',
- 'default' => '/etc/postfix/dkim/',
- 'save_method' => 'storeSettingField'
- ],
- 'dkim_privkeysuffix' => [
- 'label' => lng('dkim.privkeysuffix'),
- 'settinggroup' => 'dkim',
- 'varname' => 'privkeysuffix',
- 'type' => 'text',
- 'string_regexp' => '/^[a-z0-9\._]+$/i',
- 'default' => '.priv',
- 'save_method' => 'storeSettingField',
- 'advanced_mode' => true
- ],
- 'dkim_dkim_domains' => [
- 'label' => lng('dkim.dkim_domains'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_domains',
- 'type' => 'text',
- 'string_regexp' => '/^[a-z0-9\._]+$/i',
- 'default' => 'domains',
- 'save_method' => 'storeSettingField'
- ],
- 'dkim_dkim_dkimkeys' => [
- 'label' => lng('dkim.dkim_dkimkeys'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_dkimkeys',
- 'type' => 'text',
- 'string_regexp' => '/^[a-z0-9\._]+$/i',
- 'default' => 'dkim-keys.conf',
- 'save_method' => 'storeSettingField'
- ],
- 'dkim_dkim_algorithm' => [
- 'label' => lng('dkim.dkim_algorithm'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_algorithm',
- 'type' => 'select',
- 'default' => 'all',
- 'select_mode' => 'multiple',
- 'select_var' => [
- 'all' => 'All',
- 'sha1' => 'SHA1',
- 'sha256' => 'SHA256'
- ],
- 'save_method' => 'storeSettingFieldInsertBindTask',
- 'advanced_mode' => true
- ],
- 'dkim_dkim_servicetype' => [
- 'label' => lng('dkim.dkim_servicetype'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_servicetype',
- 'type' => 'select',
- 'default' => '0',
- 'select_var' => [
- '0' => 'All',
- '1' => 'E-Mail'
- ],
- 'save_method' => 'storeSettingFieldInsertBindTask',
- 'advanced_mode' => true
- ],
- 'dkim_dkim_keylength' => [
- 'label' => [
- 'title' => lng('dkim.dkim_keylength.title'),
- 'description' => lng('dkim.dkim_keylength.description', [Settings::Get('dkim.dkim_prefix')])
- ],
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_keylength',
- 'type' => 'select',
- 'default' => '1024',
- 'select_var' => [
- '1024' => '1024 Bit',
- '2048' => '2048 Bit'
- ],
- 'save_method' => 'storeSettingFieldInsertBindTask'
- ],
- 'dkim_dkim_notes' => [
- 'label' => lng('dkim.dkim_notes'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkim_notes',
- 'type' => 'text',
- 'string_regexp' => '/^[a-z0-9\._]+$/i',
- 'default' => '',
- 'save_method' => 'storeSettingFieldInsertBindTask',
- 'advanced_mode' => true
- ],
- 'dkim_dkimrestart_command' => [
- 'label' => lng('dkim.dkimrestart_command'),
- 'settinggroup' => 'dkim',
- 'varname' => 'dkimrestart_command',
- 'type' => 'text',
- 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
- 'default' => '/etc/init.d/dkim-filter restart',
- 'save_method' => 'storeSettingField',
- 'required_otp' => true
- ]
- ]
- ]
- ]
-];
diff --git a/composer.lock b/composer.lock
index 91770db8..4a2234f2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2559,16 +2559,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.17.1",
+ "version": "v4.18.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
+ "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
- "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
+ "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"shasum": ""
},
"require": {
@@ -2609,22 +2609,22 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0"
},
- "time": "2023-08-13T19:53:39+00:00"
+ "time": "2023-12-10T21:03:43+00:00"
},
{
"name": "pdepend/pdepend",
- "version": "2.16.0",
+ "version": "2.16.2",
"source": {
"type": "git",
"url": "https://github.com/pdepend/pdepend.git",
- "reference": "8dfc0c46529e2073fa97986552f80646eedac562"
+ "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pdepend/pdepend/zipball/8dfc0c46529e2073fa97986552f80646eedac562",
- "reference": "8dfc0c46529e2073fa97986552f80646eedac562",
+ "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58",
+ "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58",
"shasum": ""
},
"require": {
@@ -2637,7 +2637,6 @@
"require-dev": {
"easy-doc/easy-doc": "0.0.0|^1.2.3",
"gregwar/rst": "^1.0",
- "phpunit/phpunit": "^4.8.36|^5.7.27",
"squizlabs/php_codesniffer": "^2.0.0"
},
"bin": [
@@ -2667,7 +2666,7 @@
],
"support": {
"issues": "https://github.com/pdepend/pdepend/issues",
- "source": "https://github.com/pdepend/pdepend/tree/2.16.0"
+ "source": "https://github.com/pdepend/pdepend/tree/2.16.2"
},
"funding": [
{
@@ -2675,7 +2674,7 @@
"type": "tidelift"
}
],
- "time": "2023-11-29T08:52:35+00:00"
+ "time": "2023-12-17T18:09:59+00:00"
},
{
"name": "phar-io/manifest",
@@ -2913,22 +2912,22 @@
},
{
"name": "phpmd/phpmd",
- "version": "2.14.1",
+ "version": "2.15.0",
"source": {
"type": "git",
"url": "https://github.com/phpmd/phpmd.git",
- "reference": "442fc2c34edcd5198b442d8647c7f0aec3afabe8"
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpmd/phpmd/zipball/442fc2c34edcd5198b442d8647c7f0aec3afabe8",
- "reference": "442fc2c34edcd5198b442d8647c7f0aec3afabe8",
+ "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0",
"shasum": ""
},
"require": {
"composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0",
"ext-xml": "*",
- "pdepend/pdepend": "^2.15.1",
+ "pdepend/pdepend": "^2.16.1",
"php": ">=5.3.9"
},
"require-dev": {
@@ -2937,7 +2936,6 @@
"ext-simplexml": "*",
"gregwar/rst": "^1.0",
"mikey179/vfsstream": "^1.6.8",
- "phpunit/phpunit": "^4.8.36 || ^5.7.27",
"squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2"
},
"bin": [
@@ -2985,7 +2983,7 @@
"support": {
"irc": "irc://irc.freenode.org/phpmd",
"issues": "https://github.com/phpmd/phpmd/issues",
- "source": "https://github.com/phpmd/phpmd/tree/2.14.1"
+ "source": "https://github.com/phpmd/phpmd/tree/2.15.0"
},
"funding": [
{
@@ -2993,20 +2991,20 @@
"type": "tidelift"
}
],
- "time": "2023-09-28T13:07:44+00:00"
+ "time": "2023-12-11T08:22:20+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.10.46",
+ "version": "1.10.50",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "90d3d25c5b98b8068916bbf08ce42d5cb6c54e70"
+ "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/90d3d25c5b98b8068916bbf08ce42d5cb6c54e70",
- "reference": "90d3d25c5b98b8068916bbf08ce42d5cb6c54e70",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
+ "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
"shasum": ""
},
"require": {
@@ -3055,27 +3053,27 @@
"type": "tidelift"
}
],
- "time": "2023-11-28T14:57:26+00:00"
+ "time": "2023-12-13T10:59:42+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.29",
+ "version": "9.2.30",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76"
+ "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76",
- "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089",
+ "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.15",
+ "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@@ -3125,7 +3123,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30"
},
"funding": [
{
@@ -3133,7 +3131,7 @@
"type": "github"
}
],
- "time": "2023-09-19T04:57:46+00:00"
+ "time": "2023-12-22T06:47:57+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -3378,16 +3376,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.13",
+ "version": "9.6.15",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be"
+ "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be",
- "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1",
+ "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1",
"shasum": ""
},
"require": {
@@ -3461,7 +3459,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15"
},
"funding": [
{
@@ -3477,7 +3475,7 @@
"type": "tidelift"
}
],
- "time": "2023-09-19T05:39:22+00:00"
+ "time": "2023-12-01T16:55:19+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -3722,20 +3720,20 @@
},
{
"name": "sebastian/complexity",
- "version": "2.0.2",
+ "version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
- "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+ "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
- "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
+ "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^4.7",
+ "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3"
},
"require-dev": {
@@ -3767,7 +3765,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity",
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
- "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
},
"funding": [
{
@@ -3775,7 +3773,7 @@
"type": "github"
}
],
- "time": "2020-10-26T15:52:27+00:00"
+ "time": "2023-12-22T06:19:30+00:00"
},
{
"name": "sebastian/diff",
@@ -4049,20 +4047,20 @@
},
{
"name": "sebastian/lines-of-code",
- "version": "1.0.3",
+ "version": "1.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
- "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
+ "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
- "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+ "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^4.6",
+ "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3"
},
"require-dev": {
@@ -4094,7 +4092,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
- "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
},
"funding": [
{
@@ -4102,7 +4100,7 @@
"type": "github"
}
],
- "time": "2020-11-28T06:42:11+00:00"
+ "time": "2023-12-22T06:20:34+00:00"
},
{
"name": "sebastian/object-enumerator",
@@ -4507,16 +4505,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.7.2",
+ "version": "3.8.0",
"source": {
"type": "git",
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879"
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+ "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879",
- "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7",
+ "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7",
"shasum": ""
},
"require": {
@@ -4526,7 +4524,7 @@
"php": ">=5.4.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/phpcs",
@@ -4545,22 +4543,45 @@
"authors": [
{
"name": "Greg Sherwood",
- "role": "lead"
+ "role": "Former lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "Current lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
- "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
"keywords": [
"phpcs",
"standards",
"static analysis"
],
"support": {
- "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
- "source": "https://github.com/squizlabs/PHP_CodeSniffer",
- "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
+ "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+ "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
},
- "time": "2023-02-22T23:07:41+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2023-12-08T12:32:31+00:00"
},
{
"name": "symfony/config",
@@ -4643,16 +4664,16 @@
},
{
"name": "symfony/dependency-injection",
- "version": "v5.4.32",
+ "version": "v5.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "d5d48f215ed73f7973d01256b9a2fac729bef759"
+ "reference": "14969a558cd6382b2a12b14b20ef9a851a02da79"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d5d48f215ed73f7973d01256b9a2fac729bef759",
- "reference": "d5d48f215ed73f7973d01256b9a2fac729bef759",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/14969a558cd6382b2a12b14b20ef9a851a02da79",
+ "reference": "14969a558cd6382b2a12b14b20ef9a851a02da79",
"shasum": ""
},
"require": {
@@ -4712,7 +4733,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.32"
+ "source": "https://github.com/symfony/dependency-injection/tree/v5.4.33"
},
"funding": [
{
@@ -4728,7 +4749,7 @@
"type": "tidelift"
}
],
- "time": "2023-11-29T06:58:28+00:00"
+ "time": "2023-11-30T08:15:37+00:00"
},
{
"name": "symfony/filesystem",
diff --git a/customer_email.php b/customer_email.php
index f4dc49a9..8a5616de 100644
--- a/customer_email.php
+++ b/customer_email.php
@@ -245,6 +245,15 @@ if ($page == 'email_domain') {
if (isset($result['email']) && $result['email'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
+ try {
+ Emails::getLocal($userinfo, [
+ 'id' => $id,
+ 'spam_tag_level' => $_POST['spam_tag_level'] ?? \Froxlor\Cron\Mail\Rspamd::DEFAULT_MARK_LVL,
+ 'spam_kill_level' => $_POST['spam_kill_level'] ?? \Froxlor\Cron\Mail\Rspamd::DEFAULT_REJECT_LVL
+ ])->update();
+ } catch (Exception $e) {
+ Response::dynamicError($e->getMessage());
+ }
Response::redirectTo($filename, [
'page' => $page
]);
@@ -291,6 +300,54 @@ if ($page == 'email_domain') {
'editid' => $id
]);
}
+ } elseif ($action == 'togglebypass' && $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,
+ 'bypass_spam' => ($result['bypass_spam'] == '1' ? 0 : 1)
+ ])->update();
+ } catch (Exception $e) {
+ Response::dynamicError($e->getMessage());
+ }
+ Response::redirectTo($filename, [
+ 'page' => $page,
+ 'domainid' => $email_domainid,
+ '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,
+ 'policy_greylist' => ($result['policy_greylist'] == '1' ? 0 : 1)
+ ])->update();
+ } catch (Exception $e) {
+ Response::dynamicError($e->getMessage());
+ }
+ Response::redirectTo($filename, [
+ 'page' => $page,
+ 'domainid' => $email_domainid,
+ 'action' => 'edit',
+ 'id' => $id,
+ ]);
} elseif ($action == 'togglecatchall' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php
index 7c7b5bfb..e70a205f 100644
--- a/install/froxlor.sql.php
+++ b/install/froxlor.sql.php
@@ -94,6 +94,10 @@ CREATE TABLE `mail_virtual` (
`popaccountid` int(11) NOT NULL default '0',
`iscatchall` tinyint(1) unsigned NOT NULL default '0',
`description` varchar(255) NOT NULL DEFAULT '',
+ `spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0,
+ `spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0,
+ `bypass_spam` tinyint(1) NOT NULL default '0',
+ `policy_greylist` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`id`),
KEY `email` (`email`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
@@ -380,22 +384,18 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('logger', 'logfile', ''),
('logger', 'logtypes', 'syslog,mysql'),
('logger', 'severity', '1'),
- ('dkim', 'use_dkim', '0'),
- ('dkim', 'dkim_prefix', '/etc/postfix/dkim/'),
- ('dkim', 'dkim_domains', 'domains'),
- ('dkim', 'dkim_dkimkeys', 'dkim-keys.conf'),
- ('dkim', 'dkimrestart_command', 'service dkim-filter restart'),
- ('dkim', 'privkeysuffix', '.priv'),
+ ('antispam', 'activated', '0'),
+ ('antispam', 'config_file', '/etc/rspamd/local.d/froxlor_settings.conf'),
+ ('antispam', 'reload_command', 'service rspamd restart'),
+ ('antispam', 'dkim_keylength', '1024'),
('admin', 'show_news_feed', '0'),
('admin', 'show_version_login', '0'),
('admin', 'show_version_footer', '0'),
('caa', 'caa_entry', ''),
('spf', 'use_spf', '0'),
('spf', 'spf_entry', 'v=spf1 a mx -all'),
- ('dkim', 'dkim_algorithm', 'all'),
- ('dkim', 'dkim_keylength', '1024'),
- ('dkim', 'dkim_servicetype', '0'),
- ('dkim', 'dkim_notes', ''),
+ ('dmarc', 'use_dmarc', '0'),
+ ('dmarc', 'dmarc_entry', 'v=DMARC1; p=none;'),
('defaultwebsrverrhandler', 'enabled', '0'),
('defaultwebsrverrhandler', 'err401', ''),
('defaultwebsrverrhandler', 'err403', ''),
@@ -726,6 +726,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
+ ('panel', 'version', '2.2.0-dev1'),
+ ('panel', 'db_version', '202312230');
('panel', 'version', '2.1.4'),
('panel', 'db_version', '202312120');
diff --git a/install/updates/froxlor/update_2.0.inc.php b/install/updates/froxlor/update_2.0.inc.php
index 4b7c0056..cc448b08 100644
--- a/install/updates/froxlor/update_2.0.inc.php
+++ b/install/updates/froxlor/update_2.0.inc.php
@@ -99,7 +99,6 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
Update::lastStepStatus(0);
- Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"install/lib",
"install/lng",
@@ -121,30 +120,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
"lng/swedish.lng.php",
"scripts",
);
- $disabled = explode(',', ini_get('disable_functions'));
- $exec_allowed = !in_array('exec', $disabled);
- $del_list = "";
- foreach ($to_clean as $filedir) {
- $complete_filedir = Froxlor::getInstallDir() . $filedir;
- if (file_exists($complete_filedir)) {
- if ($exec_allowed) {
- FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
- } else {
- $del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
- }
- }
- }
- if ($exec_allowed) {
- Update::lastStepStatus(0);
- } else {
- if (empty($del_list)) {
- // none of the files existed
- Update::lastStepStatus(0);
- } else {
- Update::lastStepStatus(1, 'manual commands needed',
- 'Please run the following commands manually:
' . $del_list . ''); - } - } + Update::cleanOldFiles($to_clean); Update::showUpdateStep("Adding new settings"); $panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0; diff --git a/install/updates/froxlor/update_2.1.inc.php b/install/updates/froxlor/update_2.1.inc.php index 484bb645..1a2a385f 100644 --- a/install/updates/froxlor/update_2.1.inc.php +++ b/install/updates/froxlor/update_2.1.inc.php @@ -149,7 +149,6 @@ if (Froxlor::isFroxlorVersion('2.1.0-rc2')) { } if (Froxlor::isDatabaseVersion('202311260')) { - Update::showUpdateStep("Cleaning up old files"); $to_clean = array( "install/updates/froxlor/update_2.x.inc.php", "install/updates/preconfig/preconfig_2.x.inc.php", @@ -175,33 +174,8 @@ if (Froxlor::isDatabaseVersion('202311260')) { "templates/Froxlor/user/change_theme.html.twig", "tests/Backup/CustomerBackupsTest.php" ); - $disabled = explode(',', ini_get('disable_functions')); - $exec_allowed = !in_array('exec', $disabled); - $del_list = ""; - foreach ($to_clean as $filedir) { - $complete_filedir = Froxlor::getInstallDir() . $filedir; - if (file_exists($complete_filedir)) { - if ($exec_allowed) { - FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir)); - } else { - $del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL; - } - } - } - if ($exec_allowed) { - Update::lastStepStatus(0); - } else { - if (empty($del_list)) { - // none of the files existed - Update::lastStepStatus(0); - } else { - Update::lastStepStatus( - 1, - 'manual commands needed', - 'Please run the following commands manually:
' . $del_list . '' - ); - } - } + Update::cleanOldFiles($to_clean); + Froxlor::updateToDbVersion('202312050'); } @@ -216,7 +190,6 @@ if (Froxlor::isFroxlorVersion('2.1.0')) { } if (Froxlor::isDatabaseVersion('202312050')) { - Update::showUpdateStep("Cleaning up old files"); $to_clean = array( "lib/configfiles/centos7.xml", "lib/configfiles/centos8.xml", @@ -225,33 +198,8 @@ if (Froxlor::isDatabaseVersion('202312050')) { "lib/configfiles/buster.xml", "lib/configfiles/bionic.xml", ); - $disabled = explode(',', ini_get('disable_functions')); - $exec_allowed = !in_array('exec', $disabled); - $del_list = ""; - foreach ($to_clean as $filedir) { - $complete_filedir = Froxlor::getInstallDir() . $filedir; - if (file_exists($complete_filedir)) { - if ($exec_allowed) { - FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir)); - } else { - $del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL; - } - } - } - if ($exec_allowed) { - Update::lastStepStatus(0); - } else { - if (empty($del_list)) { - // none of the files existed - Update::lastStepStatus(0); - } else { - Update::lastStepStatus( - 1, - 'manual commands needed', - 'Please run the following commands manually:
' . $del_list . '' - ); - } - } + Update::cleanOldFiles($to_clean); + Froxlor::updateToDbVersion('202312100'); } diff --git a/install/updates/froxlor/update_2.2.inc.php b/install/updates/froxlor/update_2.2.inc.php new file mode 100644 index 00000000..a22ac732 --- /dev/null +++ b/install/updates/froxlor/update_2.2.inc.php @@ -0,0 +1,68 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +use Froxlor\Database\Database; +use Froxlor\FileDir; +use Froxlor\Froxlor; +use Froxlor\Install\Update; +use Froxlor\Settings; + +if (!defined('_CRON_UPDATE')) { + if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) { + header('Location: ../../../../index.php'); + exit(); + } +} + +if (Froxlor::isFroxlorVersion('2.1.x')) { + Update::showUpdateStep("Enhancing virtual email table"); + Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0;"); + Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0;"); + Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `bypass_spam` tinyint(1) NOT NULL default '0';"); + Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `policy_greylist` tinyint(1) NOT NULL default '1';"); + Update::lastStepStatus(0); + + Update::showUpdateStep("Adjusting settings"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'activated' WHERE `settinggroup` = 'dkim' AND `varname` = 'use_dkim';"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'reload_command' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkimrestart_command';"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'config_file', `value` = '/etc/rspamd/local.d/froxlor_settings.conf' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_prefix';"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_keylength';"); + Settings::AddNew("dmarc.use_dmarc", "0"); + Settings::AddNew("dmarc.dmarc_entry", "v=DMARC1; p=none;"); + Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'privkeysuffix';"); + Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_domains';"); + Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_algorithm';"); + Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_notes';"); + Update::lastStepStatus(0); + + $to_clean = [ + 'actions/admin/settings/180.dkim.php', + 'actions/admin/settings/185.spf.php', + ]; + Update::cleanOldFiles($to_clean); + + Froxlor::updateToDbVersion('202312230'); + Froxlor::updateToVersion('2.2.0-dev1'); +} diff --git a/actions/admin/settings/185.spf.php b/install/updates/preconfig/preconfig_2.2.inc.php similarity index 57% rename from actions/admin/settings/185.spf.php rename to install/updates/preconfig/preconfig_2.2.inc.php index 5e257116..be735688 100644 --- a/actions/admin/settings/185.spf.php +++ b/install/updates/preconfig/preconfig_2.2.inc.php @@ -23,31 +23,26 @@ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 */ -return [ - 'groups' => [ - 'spf' => [ - 'title' => lng('admin.spfsettings'), - 'icon' => 'fa-solid fa-clipboard-check', - 'fields' => [ - 'spf_use_spf' => [ - 'label' => lng('spf.use_spf'), - 'settinggroup' => 'spf', - 'varname' => 'use_spf', - 'type' => 'checkbox', - 'default' => false, - 'save_method' => 'storeSettingField', - 'overview_option' => true - ], - 'spf_spf_entry' => [ - 'label' => lng('spf.spf_entry'), - 'settinggroup' => 'spf', - 'varname' => 'spf_entry', - 'type' => 'text', - 'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i', - 'default' => 'v=spf1 a mx -all', - 'save_method' => 'storeSettingField' - ] - ] - ] - ] +use Froxlor\Install\Update; + +$preconfig = [ + 'title' => '2.2.x updates', + 'fields' => [] ]; +$return = []; + +if (Update::versionInUpdate($current_version, '2.2.0-dev1')) { + $has_preconfig = true; + $description = 'Froxlor now features antispam configurations using rspamd. Would you like to enable the antispam feature (required re-configuration of services)?'; + $question = 'Enable antispam (recommended) '; + $return['antispam_activated'] = [ + 'type' => 'checkbox', + 'value' => 1, + 'checked' => 0, + 'label' => $question, + 'prior_infotext' => $description + ]; +} + +$preconfig['fields'] = $return; +return $preconfig; diff --git a/install/updatesql.php b/install/updatesql.php index 47b45a44..773e1480 100644 --- a/install/updatesql.php +++ b/install/updatesql.php @@ -55,6 +55,7 @@ if (Froxlor::isFroxlor()) { include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_0.10.inc.php')); include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.0.inc.php')); include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.1.inc.php')); + include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.2.inc.php')); // Check Froxlor - database integrity (only happens after all updates are done, so we know the db-layout is okay) Update::showUpdateStep("Checking database integrity"); diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index c8692956..da173e27 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -1407,7 +1407,7 @@ class Domains extends ApiCommand implements ResourceEntity $zonefile = $result['zonefile']; } - if (Settings::Get('dkim.use_dkim') != '1') { + if (Settings::Get('antispam.activated') != '1') { $dkim = $result['dkim']; } diff --git a/lib/Froxlor/Api/Commands/Emails.php b/lib/Froxlor/Api/Commands/Emails.php index 85c19a49..4569852f 100644 --- a/lib/Froxlor/Api/Commands/Emails.php +++ b/lib/Froxlor/Api/Commands/Emails.php @@ -28,10 +28,12 @@ namespace Froxlor\Api\Commands; use Exception; use Froxlor\Api\ApiCommand; use Froxlor\Api\ResourceEntity; +use Froxlor\Cron\TaskId; use Froxlor\Database\Database; use Froxlor\FroxlorLogger; use Froxlor\Idna\IdnaWrapper; use Froxlor\Settings; +use Froxlor\System\Cronjob; use Froxlor\UI\Response; use Froxlor\Validate\Validate; use PDO; @@ -49,6 +51,14 @@ class Emails extends ApiCommand implements ResourceEntity * name of the address before @ * @param string $domain * domain-name for the email-address + * @param float $spam_tag_level + * optional, score which is required to tag emails as spam, default: 7.0 + * @param float $spam_kill_level + * optional, score which is required to discard emails, default: 14.0 + * @param boolean $bypass_spam + * optional, disable spam-filter entirely, default: no + * @param boolean $policy_greylist + * optional, enable grey-listing, default: yes * @param boolean $iscatchall * optional, make this address a catchall address, default: no * @param int $customerid @@ -74,6 +84,10 @@ class Emails extends ApiCommand implements ResourceEntity $domain = $this->getParam('domain'); // parameters + $spam_tag_level = $this->getParam('spam_tag_level', true, '7.0'); + $spam_kill_level = $this->getParam('spam_kill_level', true, '14.0'); + $bypass_spam = $this->getBoolParam('bypass_spam', true, 0); + $policy_greylist = $this->getBoolParam('policy_greylist', true, 1); $iscatchall = $this->getBoolParam('iscatchall', true, 0); $description = $this->getParam('description', true, ''); @@ -140,11 +154,19 @@ class Emails extends ApiCommand implements ResourceEntity } } + $spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true); + $spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true); + $description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true); + $stmt = Database::prepare(" INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` SET `customerid` = :cid, `email` = :email, `email_full` = :email_full, + `spam_tag_level` = :spam_tag_level, + `spam_kill_level` = :spam_kill_level, + `bypass_spam` = :bypass_spam, + `policy_greylist` = :policy_greylist, `iscatchall` = :iscatchall, `domainid` = :domainid, `description` = :description @@ -153,6 +175,10 @@ class Emails extends ApiCommand implements ResourceEntity "cid" => $customer['customerid'], "email" => $email, "email_full" => $email_full, + "spam_tag_level" => $spam_tag_level, + "spam_kill_level" => $spam_kill_level, + "bypass_spam" => $bypass_spam, + "policy_greylist" => $policy_greylist, "iscatchall" => $iscatchall, "domainid" => $domain_check['id'], "description" => $description @@ -162,6 +188,7 @@ class Emails extends ApiCommand implements ResourceEntity // update customer usage Customers::increaseUsage($customer['customerid'], 'emails_used'); + Cronjob::inserttask(TaskId::REBUILD_RSPAMD); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'"); $result = $this->apiCall('Emails.get', [ @@ -194,7 +221,7 @@ 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.*, 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) . ") @@ -220,6 +247,14 @@ class Emails extends ApiCommand implements ResourceEntity * 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 float $spam_tag_level + * optional, score which is required to tag emails as spam, default: 7.0 + * @param float $spam_kill_level + * optional, score which is required to discard emails, default: 14.0 + * @param boolean $bypass_spam + * optional, disable spam-filter entirely, default: no + * @param boolean $policy_greylist + * optional, enable grey-listing, default: yes * @param boolean $iscatchall * optional * @param string $description @@ -255,6 +290,10 @@ class Emails extends ApiCommand implements ResourceEntity $id = $result['id']; // parameters + $spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']); + $spam_kill_level = $this->getParam('spam_kill_level', true, $result['spam_kill_level']); + $bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']); + $policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']); $iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']); $description = $this->getParam('description', true, $result['description']); @@ -284,19 +323,34 @@ class Emails extends ApiCommand implements ResourceEntity $email = $result['email_full']; } + $spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true); + $spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true); + $description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true); + $stmt = Database::prepare(" - UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `email` = :email , `iscatchall` = :caflag, `description` = :description + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET + `email` = :email , + `spam_tag_level` = :spam_tag_level, + `spam_kill_level` = :spam_kill_level, + `bypass_spam` = :bypass_spam, + `policy_greylist` = :policy_greylist, + `iscatchall` = :caflag, + `description` = :description WHERE `customerid`= :cid AND `id`= :id "); $params = [ "email" => $email, + "spam_tag_level" => $spam_tag_level, + "spam_kill_level" => $spam_kill_level, + "bypass_spam" => $bypass_spam, + "policy_greylist" => $policy_greylist, "caflag" => $iscatchall, "description" => $description, "cid" => $customer['customerid'], "id" => $id ]; Database::pexecute($stmt, $params, true, true); + Cronjob::inserttask(TaskId::REBUILD_RSPAMD); $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', [ @@ -334,7 +388,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.*, 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`) diff --git a/lib/Froxlor/Cli/ConfigServices.php b/lib/Froxlor/Cli/ConfigServices.php index 2f280926..4e475b2f 100644 --- a/lib/Froxlor/Cli/ConfigServices.php +++ b/lib/Froxlor/Cli/ConfigServices.php @@ -43,9 +43,7 @@ final class ConfigServices extends CliCommand { private $yes_to_all_supported = [ 'bookworm', - 'bionic', 'bullseye', - 'buster', 'focal', 'jammy', ]; @@ -172,8 +170,8 @@ final class ConfigServices extends CliCommand $distributions_select_data = []; //set default os. - $os_dist = ['ID' => 'bullseye']; - $os_version = ['0' => '11']; + $os_dist = ['ID' => 'bookworm']; + $os_version = ['0' => '12']; $os_default = $os_dist['ID']; //read os-release diff --git a/lib/Froxlor/Cli/InstallCommand.php b/lib/Froxlor/Cli/InstallCommand.php index bd8531e0..bf169381 100644 --- a/lib/Froxlor/Cli/InstallCommand.php +++ b/lib/Froxlor/Cli/InstallCommand.php @@ -27,10 +27,13 @@ namespace Froxlor\Cli; use Exception; use Froxlor\Config\ConfigParser; +use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\Install\Install; use Froxlor\Install\Install\Core; +use Froxlor\Settings; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -50,7 +53,8 @@ final class InstallCommand extends Command $this->setDescription('Installation process to use instead of web-ui'); $this->addArgument('input-file', InputArgument::OPTIONAL, 'Optional JSON array file to use for unattended installations'); $this->addOption('print-example-file', 'p', InputOption::VALUE_NONE, 'Outputs an example JSON content to be used with the input file parameter') - ->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process'); + ->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process') + ->addOption('show-sysinfo', 's', InputOption::VALUE_NONE, 'Outputs system information about your froxlor installation'); } /** @@ -72,6 +76,15 @@ final class InstallCommand extends Command return self::INVALID; } + if ($input->getOption('show-sysinfo') !== false) { + if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) { + $output->writeln("
' . $del_list . '' + ); + } + } + } } diff --git a/lib/Froxlor/PhpHelper.php b/lib/Froxlor/PhpHelper.php index a5cdddc3..5bf3cc24 100644 --- a/lib/Froxlor/PhpHelper.php +++ b/lib/Froxlor/PhpHelper.php @@ -34,27 +34,6 @@ use voku\helper\AntiXSS; class PhpHelper { - private static $sort_key = 'id'; - private static $sort_type = SORT_STRING; - - /** - * sort an array by either natural or string sort and a given index where the value for comparison is found - * - * @param array $list - * @param string $key - * - * @return bool - */ - public static function sortListBy(array &$list, string $key = 'id'): bool - { - self::$sort_type = Settings::Get('panel.natsorting') == 1 ? SORT_NATURAL : SORT_STRING; - self::$sort_key = $key; - return usort($list, [ - 'self', - 'sortListByGivenKey' - ]); - } - /** * Wrapper around htmlentities to handle arrays, with the advantage that you * can select which fields should be handled by htmlentities @@ -101,35 +80,6 @@ class PhpHelper }); } - /** - * Replaces Strings in an array, with the advantage that you - * can select which fields should be str_replace'd - * - * @param string|array $search String or array of strings to search for - * @param string|array $replace String or array to replace with - * @param string|array $subject String or array The subject array - * @param string|array $fields string The fields which should be checked for, separated by spaces - * - * @return string|array The str_replace'd array - */ - public static function strReplaceArray($search, $replace, $subject, $fields = '') - { - if (is_array($subject)) { - if (!is_array($fields)) { - $fields = self::arrayTrim(explode(' ', $fields)); - } - foreach ($subject as $field => $value) { - if ((!is_array($fields) || empty($fields)) || (in_array($field, $fields))) { - $subject[$field] = str_replace($search, $replace, $value); - } - } - } else { - $subject = str_replace($search, $replace, $subject); - } - - return $subject; - } - /** * froxlor php error handler * @@ -170,9 +120,8 @@ class PhpHelper $err_display .= ''; // end later $err_display .= ''; - // check for more existing errors - $errors = isset(UI::twig()->getGlobals()['global_errors']) ? UI::twig()->getGlobals()['global_errors'] : ""; - UI::twig()->addGlobal('global_errors', $errors . $err_display); + // set errors to session + ErrorBag::addError($err_display); // return true to ignore php standard error-handler return true; } @@ -338,7 +287,8 @@ class PhpHelper ?string $max = '', string $system = 'si', string $retstring = '%01.2f %s' - ): string { + ): string + { // Pick units $systems = [ 'si' => [ @@ -421,7 +371,8 @@ class PhpHelper array $haystack, array &$keys = [], string $currentKey = '' - ): bool { + ): bool + { foreach ($haystack as $key => $value) { $pathkey = empty($currentKey) ? $key : $currentKey . '.' . $key; if (is_array($value)) { @@ -476,19 +427,6 @@ class PhpHelper } } - /** - * @param array $a - * @param array $b - * @return int - */ - private static function sortListByGivenKey(array $a, array $b): int - { - if (self::$sort_type == SORT_NATURAL) { - return strnatcasecmp($a[self::$sort_key], $b[self::$sort_key]); - } - return strcasecmp($a[self::$sort_key], $b[self::$sort_key]); - } - /** * Generate php file from array. * diff --git a/lib/Froxlor/Settings.php b/lib/Froxlor/Settings.php index dad81814..d0f33a95 100644 --- a/lib/Froxlor/Settings.php +++ b/lib/Froxlor/Settings.php @@ -25,6 +25,7 @@ namespace Froxlor; +use Exception; use Froxlor\Database\Database; use PDO; use PDOStatement; @@ -131,6 +132,7 @@ class Settings self::$conf = [ 'enable_webupdate' => false, 'disable_otp_security_check' => false, + 'display_php_errors' => false, ]; $configfile = Froxlor::getInstallDir() . '/lib/config.inc.php'; @@ -330,7 +332,7 @@ class Settings } } - public static function getAll() : array + public static function getAll(): array { self::init(); return self::$data; @@ -338,17 +340,14 @@ class Settings /** * get value from config by identifier + * @throws Exception */ public static function Config(string $config) { self::init(); - $sstr = explode(".", $config); - $result = self::$conf; - foreach ($sstr as $key) { - $result = $result[$key] ?? null; - if (empty($result)) { - break; - } + $result = self::$conf[$config] ?? null; + if (is_null($result)) { + throw new Exception('Unknown local config name "' . $config . '"'); } return $result; } diff --git a/lib/Froxlor/Settings/Store.php b/lib/Froxlor/Settings/Store.php index fe4d9603..9b82ca0a 100644 --- a/lib/Froxlor/Settings/Store.php +++ b/lib/Froxlor/Settings/Store.php @@ -225,6 +225,17 @@ class Store return $returnvalue; } + public static function storeSettingFieldInsertAntispamTask($fieldname, $fielddata, $newfieldvalue) + { + // first save the setting itself + $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue); + + if ($returnvalue !== false) { + Cronjob::inserttask(TaskId::REBUILD_RSPAMD); + } + return $returnvalue; + } + public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue) { $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue); diff --git a/lib/Froxlor/System/Cronjob.php b/lib/Froxlor/System/Cronjob.php index a131052c..b4be07ac 100644 --- a/lib/Froxlor/System/Cronjob.php +++ b/lib/Froxlor/System/Cronjob.php @@ -134,11 +134,15 @@ class Cronjob INSERT INTO `" . TABLE_PANEL_TASKS . "` SET `type` = :type, `data` = :data "); - if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) { + if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::REBUILD_RSPAMD || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) { // 4 = bind -> if bind disabled -> no task if ($type == TaskId::REBUILD_DNS && Settings::Get('system.bind_enable') == '0') { return; } + // 9 = rspamd -> if antispam disabled -> no task + if ($type == TaskId::REBUILD_RSPAMD && Settings::Get('antispam.activated') == '0') { + return; + } // 10 = quota -> if quota disabled -> no task if ($type == TaskId::CREATE_QUOTA && Settings::Get('system.diskquota_enabled') == '0') { return; diff --git a/lib/Froxlor/Traffic/Traffic.php b/lib/Froxlor/Traffic/Traffic.php index d9fa4e99..5eab6af3 100644 --- a/lib/Froxlor/Traffic/Traffic.php +++ b/lib/Froxlor/Traffic/Traffic.php @@ -42,7 +42,7 @@ class Traffic { $trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo, self::getParamsByRange($range, ['customer_traffic' => true]))); - if ($userinfo['adminsession'] == 1) { + if (($userinfo['adminsession'] ?? 0) == 1) { $trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid'); } $trafficCollection = $trafficCollectionObj->get(); @@ -58,8 +58,17 @@ class Traffic $mail = $item['mail']; $total = $http + $ftp + $mail; + if (empty($users[$item['customerid']])) { + $users[$item['customerid']] = [ + 'total' => 0.00, + 'http' => 0.00, + 'ftp' => 0.00, + 'mail' => 0.00, + ]; + } + // per user total - if ($userinfo['adminsession'] == 1) { + if (($userinfo['adminsession'] ?? 0) == 1) { $users[$item['customerid']]['loginname'] = $item['customer']['loginname']; } $users[$item['customerid']]['total'] += $total; @@ -67,6 +76,30 @@ class Traffic $users[$item['customerid']]['ftp'] += $ftp; $users[$item['customerid']]['mail'] += $mail; if (!$overview) { + if (empty($years[$item['year']])) { + $years[$item['year']] = [ + 'total' => 0.00, + 'http' => 0.00, + 'ftp' => 0.00, + 'mail' => 0.00, + ]; + } + if (empty($months[$item['month'] . '/' . $item['year']])) { + $months[$item['month'] . '/' . $item['year']] = [ + 'total' => 0.00, + 'http' => 0.00, + 'ftp' => 0.00, + 'mail' => 0.00, + ]; + } + if (empty($days[$item['day'] . '.' . $item['month'] . '.' . $item['year']])) { + $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']] = [ + 'total' => 0.00, + 'http' => 0.00, + 'ftp' => 0.00, + 'mail' => 0.00, + ]; + } // per year $years[$item['year']]['total'] += $total; $years[$item['year']]['http'] += $http; @@ -86,7 +119,12 @@ class Traffic } // calculate overview for given range from users - $metrics = []; + $metrics = [ + 'total' => 0.00, + 'http' => 0.00, + 'ftp' => 0.00, + 'mail' => 0.00, + ]; foreach ($users as $user) { $metrics['total'] += $user['total']; $metrics['http'] += $user['http']; diff --git a/lib/Froxlor/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php index c4d989aa..3e01372e 100644 --- a/lib/Froxlor/UI/Callbacks/Domain.php +++ b/lib/Froxlor/UI/Callbacks/Domain.php @@ -25,6 +25,7 @@ namespace Froxlor\UI\Callbacks; +use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\Domain\Domain as DDomain; use Froxlor\FileDir; @@ -33,23 +34,36 @@ use Froxlor\UI\Panel\UI; class Domain { - public static function domainLink(array $attributes) + public static function domainEditLink(array $attributes): array { - return '' . $attributes['data'] . ''; + $linker = UI::getLinker(); + return [ + 'macro' => 'link', + 'data' => [ + 'text' => $attributes['data'], + 'href' => $linker->getLink([ + 'section' => 'domains', + 'page' => 'domains', + 'action' => 'edit', + 'id' => $attributes['fields']['id'], + ]), + 'target' => '_blank' + ] + ]; } - public static function domainWithCustomerLink(array $attributes) + public static function domainWithCustomerLink(array $attributes): string { $linker = UI::getLinker(); $result = '' . $attributes['data'] . ''; if ((int)UI::getCurrentUser()['adminsession'] == 1 && $attributes['fields']['customerid']) { $result .= ' (' . $attributes['fields']['loginname'] . ')'; + 'section' => 'customers', + 'page' => 'customers', + 'action' => 'su', + 'sort' => $attributes['fields']['loginname'], + 'id' => $attributes['fields']['customerid'], + ]) . '">' . $attributes['fields']['loginname'] . ')'; } return $result; } @@ -108,12 +122,12 @@ class Domain public static function canEdit(array $attributes): bool { - return (bool)($attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated']); + return $attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated']; } public static function canViewLogs(array $attributes): bool { - if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) { + if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) { if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) { return true; } elseif ((int)UI::getCurrentUser()['adminsession'] == 1) { @@ -155,17 +169,19 @@ class Domain public static function hasLetsEncryptActivated(array $attributes): bool { - return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0); + return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))); } + /** + * @throws \Exception + */ public static function canEditSSL(array $attributes): bool { - if ( - Settings::Get('system.use_ssl') == '1' + if (Settings::Get('system.use_ssl') == '1' && DDomain::domainHasSslIpPort($attributes['fields']['id']) && (int)$attributes['fields']['caneditdomain'] == 1 && (int)$attributes['fields']['letsencrypt'] == 0 - && (int)$attributes['fields']['email_only'] == 0 + && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated'] ) { return true; @@ -196,15 +212,15 @@ class Domain ], ]; - // specified certificate for domain if ($attributes['fields']['domain_hascert'] == 1) { + // specified certificate for domain $result['icon'] .= ' text-success'; - } // shared certificates (e.g. subdomain of domain where certificate is specified) - elseif ($attributes['fields']['domain_hascert'] == 2) { + } elseif ($attributes['fields']['domain_hascert'] == 2) { + // shared certificates (e.g. subdomain of domain where certificate is specified) $result['icon'] .= ' text-warning'; $result['title'] .= "\n" . lng('panel.ssleditor_infoshared'); - } // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings) - elseif ($attributes['fields']['domain_hascert'] == 0) { + } elseif ($attributes['fields']['domain_hascert'] == 0) { + // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings) $result['icon'] .= ' text-danger'; $result['title'] .= "\n" . lng('panel.ssleditor_infoglobal'); } @@ -216,7 +232,7 @@ class Domain public static function listIPs(array $attributes): string { - if (isset($attributes['fields']['ipsandports']) && !empty($attributes['fields']['ipsandports'])) { + if (!empty($attributes['fields']['ipsandports'])) { $iplist = ""; foreach ($attributes['fields']['ipsandports'] as $ipport) { $iplist .= $ipport['ip'] . ':' . $ipport['port'] . '
{DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (wenn zutreffend){DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (if applicable){{ field.value|raw }}
+