From b15f99b1e1625ac83512c5f44ad0a6514e630879 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 5 Jan 2024 15:37:04 +0100 Subject: [PATCH] implementation start of rspam/antispam feature Signed-off-by: Michael Kaufmann --- actions/admin/settings/180.antispam.php | 111 +++++++++ actions/admin/settings/180.dkim.php | 146 ------------ composer.lock | 163 +++++++------ customer_email.php | 57 +++++ install/froxlor.sql.php | 22 +- install/updates/froxlor/update_2.0.inc.php | 26 +-- install/updates/froxlor/update_2.1.inc.php | 60 +---- install/updates/froxlor/update_2.2.inc.php | 68 ++++++ .../updates/preconfig/preconfig_2.2.inc.php | 49 ++-- install/updatesql.php | 1 + lib/Froxlor/Api/Commands/Domains.php | 2 +- lib/Froxlor/Api/Commands/Emails.php | 62 ++++- lib/Froxlor/Cli/ConfigServices.php | 6 +- lib/Froxlor/Cli/InstallCommand.php | 66 +++++- lib/Froxlor/Cli/MasterCron.php | 5 +- lib/Froxlor/Cron/Dns/DnsBase.php | 79 ------- lib/Froxlor/Cron/Mail/Rspamd.php | 216 ++++++++++++++++++ lib/Froxlor/Cron/System/TasksCron.php | 25 +- lib/Froxlor/Cron/TaskId.php | 6 + lib/Froxlor/Customer/Customer.php | 2 +- lib/Froxlor/Dns/Dns.php | 121 +++++----- lib/Froxlor/ErrorBag.php | 77 +++++++ lib/Froxlor/Froxlor.php | 6 +- lib/Froxlor/Install/Install/Core.php | 1 + lib/Froxlor/Install/Update.php | 35 ++- lib/Froxlor/PhpHelper.php | 74 +----- lib/Froxlor/Settings.php | 15 +- lib/Froxlor/Settings/Store.php | 11 + lib/Froxlor/System/Cronjob.php | 6 +- lib/Froxlor/Traffic/Traffic.php | 44 +++- lib/Froxlor/UI/Callbacks/Domain.php | 71 +++--- lib/Froxlor/UI/Callbacks/Style.php | 3 +- lib/Froxlor/UI/Callbacks/Text.php | 5 +- lib/Froxlor/UI/Form.php | 3 +- lib/Froxlor/UI/Response.php | 5 +- lib/config.example.inc.php | 8 + lib/configfiles/bookworm.xml | 107 ++++++++- lib/configfiles/bullseye.xml | 106 ++++++++- lib/configfiles/focal.xml | 106 ++++++++- lib/configfiles/gentoo.xml | 97 +++++++- lib/configfiles/jammy.xml | 106 ++++++++- .../admin/domains/formfield.domains_add.php | 2 +- .../admin/domains/formfield.domains_edit.php | 2 +- .../domains/formfield.domains_edit.php | 6 + .../customer/email/formfield.emails_edit.php | 42 +++- .../admin/tablelisting.domains.php | 2 +- .../customer/tablelisting.emails.php | 18 ++ lng/ca.lng.php | 39 ---- lng/de.lng.php | 69 +++--- lng/en.lng.php | 69 +++--- lng/es.lng.php | 39 ---- lng/it.lng.php | 35 --- lng/nl.lng.php | 35 --- templates/Froxlor/assets/js/bootstrap.js | 3 + templates/Froxlor/assets/js/jquery/global.js | 12 +- templates/Froxlor/form/formfields.html.twig | 11 + .../Froxlor/settings/configuration.html.twig | 3 +- templates/Froxlor/user/index.html.twig | 2 +- templates/Froxlor/userarea.html.twig | 30 ++- 59 files changed, 1736 insertions(+), 862 deletions(-) create mode 100644 actions/admin/settings/180.antispam.php delete mode 100644 actions/admin/settings/180.dkim.php create mode 100644 install/updates/froxlor/update_2.2.inc.php rename actions/admin/settings/185.spf.php => install/updates/preconfig/preconfig_2.2.inc.php (57%) create mode 100644 lib/Froxlor/Cron/Mail/Rspamd.php create mode 100644 lib/Froxlor/ErrorBag.php diff --git a/actions/admin/settings/180.antispam.php b/actions/admin/settings/180.antispam.php new file mode 100644 index 0000000000..01990c537d --- /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 2feb67bcab..0000000000 --- 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 91770db860..4a2234f292 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 f4dc49a90c..8a5616deef 100644 --- a/customer_email.php +++ b/customer_email.php @@ -245,6 +245,15 @@ 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 @@ '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 7c7b5bfb7f..e70a205f02 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -94,6 +94,10 @@ `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 @@ ('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 @@ ('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 4b7c00563c..cc448b08b9 100644 --- a/install/updates/froxlor/update_2.0.inc.php +++ b/install/updates/froxlor/update_2.0.inc.php @@ -99,7 +99,6 @@ } Update::lastStepStatus(0); - Update::showUpdateStep("Cleaning up old files"); $to_clean = array( "install/lib", "install/lng", @@ -121,30 +120,7 @@ "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 484bb6459c..1a2a385f19 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::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 @@ "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::isDatabaseVersion('202312050')) { - Update::showUpdateStep("Cleaning up old files"); $to_clean = array( "lib/configfiles/centos7.xml", "lib/configfiles/centos8.xml", @@ -225,33 +198,8 @@ "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 0000000000..a22ac732b1 --- /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 5e25711628..be73568832 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 47b45a44ff..773e1480c5 100644 --- a/install/updatesql.php +++ b/install/updatesql.php @@ -55,6 +55,7 @@ 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 c86929560d..da173e2711 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -1407,7 +1407,7 @@ public function update() $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 85c19a49b1..4569852f13 100644 --- a/lib/Froxlor/Api/Commands/Emails.php +++ b/lib/Froxlor/Api/Commands/Emails.php @@ -28,10 +28,12 @@ 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 @@ public function add() $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 @@ public function add() } } + $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 @@ public function add() "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 @@ public function add() // 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 @@ public function get() $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 @@ public function get() * 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 @@ public function update() $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 @@ public function update() $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 @@ public function listing() $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 2f28092637..4e475b2f2d 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 @@ private function createConfig(OutputInterface $output, SymfonyStyle $io): int $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 bd8531e0a7..bf16938170 100644 --- a/lib/Froxlor/Cli/InstallCommand.php +++ b/lib/Froxlor/Cli/InstallCommand.php @@ -27,10 +27,13 @@ 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 @@ protected function configure() $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 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::INVALID; } + if ($input->getOption('show-sysinfo') !== false) { + if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) { + $output->writeln("Could not find froxlor's userdata.inc.php file. You can use this parameter only with an installed froxlor system."); + return self::INVALID; + } + $this->printSysInfo($output); + return self::SUCCESS; + } + session_start(); require __DIR__ . '/install.functions.php'; @@ -349,6 +362,57 @@ private function downloadFile($src, $dest) fclose($fp); } + private function printSysInfo(OutputInterface $output) + { + + $php_sapi = 'mod_php'; + $php_version = phpversion(); + if (Settings::Get('system.mod_fcgid') == '1') { + $php_sapi = 'FCGID'; + if (Settings::Get('system.mod_fcgid_ownvhost') == '1') { + $php_sapi .= ' (+ froxlor)'; + } + } elseif (Settings::Get('phpfpm.enabled') == '1') { + $php_sapi = 'PHP-FPM'; + if (Settings::Get('phpfpm.enabled_ownvhost') == '1') { + $php_sapi .= ' (+ froxlor)'; + } + } + + $kernel = 'unknown'; + if (function_exists('posix_uname')) { + $kernel_nfo = posix_uname(); + $kernel = $kernel_nfo['release'] . ' (' . $kernel_nfo['machine'] . ')'; + } + + $ips = []; + $ips_stmt = Database::query("SELECT CONCAT(`ip`, ' (', `port`, ')') as ipaddr FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `id`"); + while ($ip = $ips_stmt->fetch(\PDO::FETCH_ASSOC)) { + $ips[] = $ip['ipaddr']; + } + + $table = new Table($output); + $table + ->setHeaders([ + 'Key', 'Value' + ]) + ->setRows([ + ['Froxlor', Froxlor::getVersionString()], + ['Update-channel', Settings::Get('system.update_channel')], + ['Hostname', Settings::Get('system.hostname')], + ['Install-dir', Froxlor::getInstallDir()], + ['PHP CLI', $php_version], + ['PHP SAPI', $php_sapi], + ['Webserver', Settings::Get('system.webserver')], + ['Kernel', $kernel], + ['Database', Database::getAttribute(\PDO::ATTR_SERVER_VERSION)], + ['Distro config', Settings::Get('system.distribution')], + ['IP addresses', implode("\n", $ips)], + ]); + $table->setStyle('box'); + $table->render(); + } + private function cliTextFormat(string $text, string $nl_char = "\n"): string { $text = str_replace(['
', '
', '
'], [$nl_char, $nl_char, $nl_char], $text); diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php index b0b3eac34a..61926ecc10 100644 --- a/lib/Froxlor/Cli/MasterCron.php +++ b/lib/Froxlor/Cli/MasterCron.php @@ -52,7 +52,7 @@ protected function configure() $this->setName('froxlor:cron'); $this->setDescription('Regulary perform tasks created by froxlor'); $this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run'); - $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]') + $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 9 = re-generate rspamd configs, 10 = re-set quotas, 99 = re-create cron.d-file]') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)') ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.') ->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).'); @@ -77,6 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (empty($jobs) || in_array('tasks', $jobs)) { Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_DNS); + Cronjob::inserttask(TaskId::REBUILD_RSPAMD); Cronjob::inserttask(TaskId::CREATE_QUOTA); Cronjob::inserttask(TaskId::REBUILD_CRON); $jobs[] = 'tasks'; @@ -95,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($input->getOption('run-task')) { $tasks_to_run = $input->getOption('run-task'); foreach ($tasks_to_run as $ttr) { - if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) { + if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::REBUILD_RSPAMD, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) { Cronjob::inserttask($ttr); $jobs[] = 'tasks'; } else { diff --git a/lib/Froxlor/Cron/Dns/DnsBase.php b/lib/Froxlor/Cron/Dns/DnsBase.php index 1d8ae40ac4..f6884affaf 100644 --- a/lib/Froxlor/Cron/Dns/DnsBase.php +++ b/lib/Froxlor/Cron/Dns/DnsBase.php @@ -117,85 +117,6 @@ public function reloadDaemon() } } - public function writeDKIMconfigs() - { - if (Settings::Get('dkim.use_dkim') == '1') { - if (!file_exists(FileDir::makeCorrectDir(Settings::Get('dkim.dkim_prefix')))) { - $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); - FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); - } - - $dkimdomains = ''; - $dkimkeys = ''; - $result_domains_stmt = Database::query(" - SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey` - FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' ORDER BY `id` ASC - "); - - while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - $privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix')); - $pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public'); - - if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') { - $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`"); - $max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC); - $domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1; - $privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix')); - FileDir::safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength')); - $domain['dkim_privkey'] = file_get_contents($privkey_filename); - FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); - $pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public'); - FileDir::safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename)); - $domain['dkim_pubkey'] = file_get_contents($pubkey_filename); - FileDir::safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `dkim_id` = :dkimid, - `dkim_privkey` = :privkey, - `dkim_pubkey` = :pubkey - WHERE `id` = :id - "); - $upd_data = [ - 'dkimid' => $domain['dkim_id'], - 'privkey' => $domain['dkim_privkey'], - 'pubkey' => $domain['dkim_pubkey'], - 'id' => $domain['id'] - ]; - Database::pexecute($upd_stmt, $upd_data); - } - - if (!file_exists($privkey_filename) && $domain['dkim_privkey'] != '') { - $privkey_file_handler = fopen($privkey_filename, "w"); - fwrite($privkey_file_handler, $domain['dkim_privkey']); - fclose($privkey_file_handler); - FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); - } - - if (!file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') { - $pubkey_file_handler = fopen($pubkey_filename, "w"); - fwrite($pubkey_file_handler, $domain['dkim_pubkey']); - fclose($pubkey_file_handler); - FileDir::safe_exec("chmod 0644 " . escapeshellarg($pubkey_filename)); - } - - $dkimdomains .= $domain['domain'] . "\n"; - $dkimkeys .= "*@" . $domain['domain'] . ":" . $domain['domain'] . ":" . $privkey_filename . "\n"; - } - - $dkimdomains_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_domains')); - $dkimdomains_file_handler = fopen($dkimdomains_filename, "w"); - fwrite($dkimdomains_file_handler, $dkimdomains); - fclose($dkimdomains_file_handler); - $dkimkeys_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_dkimkeys')); - $dkimkeys_file_handler = fopen($dkimkeys_filename, "w"); - fwrite($dkimkeys_file_handler, $dkimkeys); - fclose($dkimkeys_file_handler); - - FileDir::safe_exec(escapeshellcmd(Settings::Get('dkim.dkimrestart_command'))); - $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded'); - } - } - protected function getDomainList() { $result_domains_stmt = Database::query(" diff --git a/lib/Froxlor/Cron/Mail/Rspamd.php b/lib/Froxlor/Cron/Mail/Rspamd.php new file mode 100644 index 0000000000..6663b914a4 --- /dev/null +++ b/lib/Froxlor/Cron/Mail/Rspamd.php @@ -0,0 +1,216 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor\Cron\Mail; + +use Exception; +use Froxlor\Database\Database; +use Froxlor\FileDir; +use Froxlor\FroxlorLogger; +use Froxlor\Settings; + +class Rspamd +{ + const DEFAULT_MARK_LVL = 7.0; + const DEFAULT_REJECT_LVL = 14.0; + + private string $frx_settings_file = ""; + + protected FroxlorLogger $logger; + + public function __construct(FroxlorLogger $logger) + { + $this->logger = $logger; + } + + /** + * @throws Exception + */ + public function writeConfigs() + { + // tell the world what we are doing + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Task9 started - Rebuilding antispam configuration'); + + // get all email addresses + $antispam_stmt = Database::prepare(" + SELECT email, spam_tag_level, spam_kill_level, bypass_spam, policy_greylist, iscatchall + FROM `" . TABLE_MAIL_VIRTUAL . "` + ORDER BY email + "); + Database::pexecute($antispam_stmt); + + $this->frx_settings_file = "#\n# Automatically generated file by froxlor. DO NOT EDIT manually as it will be overwritten!\n# Generated: " . date('d.m.Y H:i') . "\n#\n\n"; + while ($email = $antispam_stmt->fetch(\PDO::FETCH_ASSOC)) { + $this->generateEmailAddrConfig($email); + } + + $antispam_cfg_file = FileDir::makeCorrectFile(Settings::Get('antispam.config_file')); + file_put_contents($antispam_cfg_file, $this->frx_settings_file); + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, $antispam_cfg_file . ' written'); + $this->writeDkimConfigs(); + $this->reloadDaemon(); + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Task9 finished'); + } + + /** + * # local.d/dkim_signing.conf + * try_fallback = true; + * path = "/var/lib/rspamd/dkim/$domain.$selector.key"; + * selector_map = "/etc/rspamd/dkim_selectors.map"; + * + * @return void + * @throws Exception + */ + public function writeDkimConfigs() + { + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Writing DKIM key-pairs'); + + $dkim_selector_map = ""; + $result_domains_stmt = Database::query(" + SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey` + FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `dkim` = '1' + ORDER BY `id` ASC + "); + + while ($domain = $result_domains_stmt->fetch(\PDO::FETCH_ASSOC)) { + + if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') { + $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`"); + $max_dkim_id = $max_dkim_id_stmt->fetch(\PDO::FETCH_ASSOC); + $domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1; + + $privkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.key'); + $pubkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.txt'); + + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Generating DKIM keys for "' . $domain['domain'] . '"'); + $rsret = []; + FileDir::safe_exec( + 'rspamadm dkim_keygen -d ' . escapeshellarg($domain['domain']) . ' -k ' . $privkey_filename . ' -s dkim' . $domain['dkim_id'] . ' -b ' . Settings::Get('dkim.dkim_keylength') . ' -o plain > ' . escapeshellarg($pubkey_filename), + $rsret, + ['>'] + ); + if (!file_exists($privkey_filename) || !file_exists($pubkey_filename)) { + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, 'DKIM Keypair for domain "' . $domain['domain'] . '" was not generated successfully.'); + continue; + } + $domain['dkim_privkey'] = file_get_contents($privkey_filename); + FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); + FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($privkey_filename)); + $domain['dkim_pubkey'] = file_get_contents($pubkey_filename); + FileDir::safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); + FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($pubkey_filename)); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `dkim_id` = :dkimid, + `dkim_privkey` = :privkey, + `dkim_pubkey` = :pubkey + WHERE `id` = :id + "); + $upd_data = [ + 'dkimid' => $domain['dkim_id'], + 'privkey' => $domain['dkim_privkey'], + 'pubkey' => $domain['dkim_pubkey'], + 'id' => $domain['id'] + ]; + Database::pexecute($upd_stmt, $upd_data); + } else { + $privkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.key'); + $pubkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.txt'); + } + + if (!file_exists($privkey_filename) && $domain['dkim_privkey'] != '') { + file_put_contents($privkey_filename, $domain['dkim_privkey']); + FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); + FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($privkey_filename)); + } + + if (!file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') { + file_put_contents($pubkey_filename, $domain['dkim_pubkey']); + FileDir::safe_exec("chmod 0644 " . escapeshellarg($pubkey_filename)); + FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($pubkey_filename)); + } + + $dkim_selector_map .= $domain['domain'] . " dkim" . $domain['dkim_id'] . "\n"; + } + + $dkim_selector_file = FileDir::makeCorrectFile('/etc/rspamd/dkim_selectors.map'); + file_put_contents($dkim_selector_file, $dkim_selector_map); + } + + private function generateEmailAddrConfig(array $email): void + { + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Generating antispam config for ' . $email['email']); + + $email['spam_tag_level'] = floatval($email['spam_tag_level']); + $email['spam_kill_level'] = floatval($email['spam_kill_level']); + $email_id = md5($email['email']); + + $this->frx_settings_file .= '# Email: ' . $email['email'] . "\n"; + foreach (['rcpt', 'from'] as $type) { + $this->frx_settings_file .= 'frx_' . $email_id . '_' . $type . ' {' . "\n"; + $this->frx_settings_file .= ' id = "frx_' . $email_id . '_' . $type . '";' . "\n"; + if ($email['iscatchall']) { + $this->frx_settings_file .= ' priority = low;' . "\n"; + $this->frx_settings_file .= ' ' . $type . ' = "' . substr($email['email'], strpos($email['email'], '@')) . '";' . "\n"; + } else { + $this->frx_settings_file .= ' priority = medium;' . "\n"; + $this->frx_settings_file .= ' ' . $type . ' = "' . $email['email'] . '";' . "\n"; + } + if ((int)$email['bypass_spam'] == 1) { + $this->frx_settings_file .= ' want_spam = yes;' . "\n"; + } else { + $this->frx_settings_file .= ' apply {' . "\n"; + $this->frx_settings_file .= ' actions {' . "\n"; + $this->frx_settings_file .= ' "add header" = ' . $email['spam_tag_level'] . ';' . "\n"; + $this->frx_settings_file .= ' rewrite_subject = ' . $email['spam_tag_level'] . ';' . "\n"; + $this->frx_settings_file .= ' reject = ' . $email['spam_kill_level'] . ';' . "\n"; + if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) { + $this->frx_settings_file .= ' greylist = null;' . "\n"; + } + $this->frx_settings_file .= ' }' . "\n"; + $this->frx_settings_file .= ' }' . "\n"; + if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) { + $this->frx_settings_file .= ' symbols [ "DONT_GREYLIST" ]' . "\n"; + } + } + $this->frx_settings_file .= '}' . "\n"; + } + $this->frx_settings_file .= "\n"; + } + + public function reloadDaemon() + { + // reload DNS daemon + $cmd = Settings::Get('antispam.reload_command'); + $cmdStatus = 1; + FileDir::safe_exec(escapeshellcmd($cmd), $cmdStatus); + if ($cmdStatus === 0) { + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Antispam daemon reloaded'); + } else { + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, 'Error while running `' . $cmd . '`: exit code (' . $cmdStatus . ') - please check your system logs'); + } + } +} diff --git a/lib/Froxlor/Cron/System/TasksCron.php b/lib/Froxlor/Cron/System/TasksCron.php index 60face001c..83cfdd3f76 100644 --- a/lib/Froxlor/Cron/System/TasksCron.php +++ b/lib/Froxlor/Cron/System/TasksCron.php @@ -25,9 +25,11 @@ namespace Froxlor\Cron\System; +use Exception; use Froxlor\Cron\FroxlorCron; use Froxlor\Cron\Http\ConfigIO; use Froxlor\Cron\Http\HttpConfigBase; +use Froxlor\Cron\Mail\Rspamd; use Froxlor\Cron\TaskId; use Froxlor\Database\Database; use Froxlor\Dns\PowerDNS; @@ -40,6 +42,9 @@ class TasksCron extends FroxlorCron { + /** + * @throws Exception + */ public static function run() { /** @@ -98,6 +103,11 @@ public static function run() * refs #293 */ self::deleteFtpData($row); + } elseif ($row['type'] == TaskId::REBUILD_RSPAMD && (int)Settings::Get('antispam.activated') != 0) { + /** + * TYPE=9 Rebuild antispam config + */ + self::rebuildAntiSpamConfigs(); } elseif ($row['type'] == TaskId::CREATE_QUOTA && (int)Settings::Get('system.diskquota_enabled') != 0) { /** * TYPE=10 Set the filesystem - quota @@ -266,13 +276,7 @@ private static function createNewHome($row = null) private static function rebuildDnsConfigs() { $dnssrv = '\\Froxlor\\Cron\\Dns\\' . Settings::Get('system.dns_server'); - $nameserver = new $dnssrv(FroxlorLogger::getInstanceOf()); - - if (Settings::Get('dkim.use_dkim') == '1') { - $nameserver->writeDKIMconfigs(); - } - $nameserver->writeConfigs(); } @@ -448,4 +452,13 @@ private static function setFilesystemQuota() } } } + + /** + * @throws Exception + */ + private static function rebuildAntiSpamConfigs() + { + $antispam = new Rspamd(FroxlorLogger::getInstanceOf()); + $antispam->writeConfigs(); + } } diff --git a/lib/Froxlor/Cron/TaskId.php b/lib/Froxlor/Cron/TaskId.php index 9aa065c06e..f905eba4bc 100644 --- a/lib/Froxlor/Cron/TaskId.php +++ b/lib/Froxlor/Cron/TaskId.php @@ -66,6 +66,12 @@ final class TaskId */ const DELETE_FTP_DATA = 8; + /** + * TYPE=9 MEANS THAT SOMETHING ANTISPAM RELATED HAS CHANGED. + * REBUILD froxlor_settings.conf IF ANTISPAM IS ENABLED + */ + const REBUILD_RSPAMD = 9; + /** * TYPE=10 Set the filesystem - quota */ diff --git a/lib/Froxlor/Customer/Customer.php b/lib/Froxlor/Customer/Customer.php index 27581d1c9a..c9e7f96b43 100644 --- a/lib/Froxlor/Customer/Customer.php +++ b/lib/Froxlor/Customer/Customer.php @@ -32,7 +32,7 @@ class Customer { /** - * Get value of a a specific field from a given customer + * Get value of a specific field from a given customer * * @param int $customerid * @param string $varname diff --git a/lib/Froxlor/Dns/Dns.php b/lib/Froxlor/Dns/Dns.php index dc7a134b94..1ac86689a7 100644 --- a/lib/Froxlor/Dns/Dns.php +++ b/lib/Froxlor/Dns/Dns.php @@ -120,16 +120,14 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals if ($domain['isemaildomain'] == '1') { self::addRequiredEntry('@', 'MX', $required_entries); if (Settings::Get('system.dns_createmailentry')) { - foreach ( - [ + foreach ([ 'imap', 'pop3', 'mail', 'smtp' ] as $record ) { - foreach ( - [ + foreach ([ 'AAAA', 'A' ] as $type @@ -152,9 +150,9 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals if (!$froxlorhostname) { // additional required records for subdomains $subdomains_stmt = Database::prepare(" - SELECT `domain`, `iswildcarddomain`, `wwwserveralias`, `isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `parentdomainid` = :domainid - "); + SELECT `domain`, `iswildcarddomain`, `wwwserveralias`, `isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `parentdomainid` = :domainid + "); Database::pexecute($subdomains_stmt, [ 'domainid' => $domain_id ]); @@ -163,7 +161,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals $sub_record = str_replace('.' . $domain['domain'], '', $subdomain['domain']); // Listing domains is enough as there currently is no support for choosing // different ips for a subdomain => use same IPs as toplevel - self::addRequiredEntry($sub_record, 'A',$required_entries); + self::addRequiredEntry($sub_record, 'A', $required_entries); self::addRequiredEntry($sub_record, 'AAAA', $required_entries); // Check whether to add a www.-prefix @@ -181,7 +179,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals // check for SPF content later self::addRequiredEntry('@SPF@.' . $sub_record, 'TXT', $required_entries); } - if (Settings::Get('dkim.use_dkim') == '1') { + if (Settings::Get('antispam.activated') == '1' && $domain['dkim'] == '1') { // check for DKIM content later self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries); } @@ -218,7 +216,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals // check for SPF content later self::addRequiredEntry('@SPF@', 'TXT', $required_entries); } - if (Settings::Get('dkim.use_dkim') == '1') { + if (Settings::Get('antispam.activated') == '1' && $domain['dkim'] == '1') { // check for DKIM content later self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries); } @@ -229,17 +227,25 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals // now generate all records and unset the required entries we have foreach ($dom_entries as $entry) { - if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']), - $required_entries[$entry['type']])) { + if (array_key_exists($entry['type'], $required_entries) && array_key_exists( + md5($entry['record']), + $required_entries[$entry['type']] + )) { unset($required_entries[$entry['type']][md5($entry['record'])]); } - if (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr($entry['content'], - 0, 7)) == '"v=caa1') { + if (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr( + $entry['content'], + 0, + 7 + )) == '"v=caa1') { // unset special CAA required-entry unset($required_entries[$entry['type']][md5("@CAA@")]); } - if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && (strtolower(substr($entry['content'], - 0, 7)) == '"v=spf1' || strtolower(substr($entry['content'], 0, 6)) == 'v=spf1')) { + if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && (strtolower(substr( + $entry['content'], + 0, + 7 + )) == '"v=spf1' || strtolower(substr($entry['content'], 0, 6)) == 'v=spf1')) { // unset special spf required-entry unset($required_entries[$entry['type']][md5("@SPF@")]); } @@ -248,32 +254,36 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals $primary_ns = $entry['content']; } // check for CNAME on @, www- or wildcard-Alias and remove A/AAAA record accordingly - foreach ( - [ + foreach ([ '@', 'www', '*' ] as $crecord ) { - if ($entry['type'] == 'CNAME' && $entry['record'] == '@' && (array_key_exists(md5($crecord), - $required_entries['A']) || array_key_exists(md5($crecord), $required_entries['AAAA']))) { + if ($entry['type'] == 'CNAME' && $entry['record'] == '@' && (array_key_exists( + md5($crecord), + $required_entries['A'] + ) || array_key_exists(md5($crecord), $required_entries['AAAA']))) { unset($required_entries['A'][md5($crecord)]); unset($required_entries['AAAA'][md5($crecord)]); } } // also allow overriding of auto-generated values (imap,pop3,mail,smtp) if enabled in the settings if (Settings::Get('system.dns_createmailentry')) { - foreach ( - [ + foreach ([ 'imap', 'pop3', 'mail', 'smtp' ] as $crecord ) { - if ($entry['type'] == 'CNAME' && $entry['record'] == $crecord && (array_key_exists(md5($crecord), - $required_entries['A']) || array_key_exists(md5($crecord), - $required_entries['AAAA']))) { + if ($entry['type'] == 'CNAME' && $entry['record'] == $crecord && (array_key_exists( + md5($crecord), + $required_entries['A'] + ) || array_key_exists( + md5($crecord), + $required_entries['AAAA'] + ))) { unset($required_entries['A'][md5($crecord)]); unset($required_entries['AAAA'][md5($crecord)]); } @@ -310,8 +320,11 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals foreach ($records as $record) { if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { $zonerecords[] = new DnsEntry($record, 'A', $ip['ip']); - } elseif ($type == 'AAAA' && filter_var($ip['ip'], FILTER_VALIDATE_IP, - FILTER_FLAG_IPV6) !== false) { + } elseif ($type == 'AAAA' && filter_var( + $ip['ip'], + FILTER_VALIDATE_IP, + FILTER_FLAG_IPV6 + ) !== false) { $zonerecords[] = new DnsEntry($record, 'AAAA', $ip['ip']); } } @@ -376,9 +389,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals // TXT (SPF and DKIM) if (array_key_exists("TXT", $required_entries)) { - if (Settings::Get('dkim.use_dkim') == '1') { - $dkim_entries = self::generateDkimEntries($domain); - } + $dkim_entries = self::generateDkimEntries($domain); foreach ($required_entries as $type => $records) { if ($type == 'TXT') { @@ -471,8 +482,10 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals if (!$isMainButSubTo) { $date = date('Ymd'); - $domain['bindserial'] = (preg_match('/^' . $date . '/', - $domain['bindserial']) ? $domain['bindserial'] + 1 : $date . '00'); + $domain['bindserial'] = (preg_match( + '/^' . $date . '/', + $domain['bindserial'] + ) ? $domain['bindserial'] + 1 : $date . '00'); if (!$froxlorhostname) { $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET @@ -499,8 +512,12 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals array_unshift($zonerecords, $soa_record); } - $zone = new DnsZone((int)Settings::Get('system.defaultttl'), $domain['domain'], $domain['bindserial'], - $zonerecords); + $zone = new DnsZone( + (int)Settings::Get('system.defaultttl'), + $domain['domain'], + $domain['bindserial'], + $zonerecords + ); return $zone; } @@ -527,43 +544,11 @@ private static function generateDkimEntries(array $domain): array { $zone_dkim = []; - if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') { + if (Settings::Get('antispam.activated') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') { // start $dkim_txt = 'v=DKIM1;'; - - // algorithm - $algorithm = explode(',', Settings::Get('dkim.dkim_algorithm')); - $alg = ''; - foreach ($algorithm as $a) { - if ($a == 'all') { - break; - } else { - $alg .= $a . ':'; - } - } - - if ($alg != '') { - $alg = substr($alg, 0, -1); - $dkim_txt .= 'h=' . $alg . ';'; - } - - // notes - if (trim(Settings::Get('dkim.dkim_notes') != '')) { - $dkim_txt .= 'n=' . trim(Settings::Get('dkim.dkim_notes')) . ';'; - } - // key - $dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s', - '$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';'; - - // service-type - if (Settings::Get('dkim.dkim_servicetype') == '1') { - $dkim_txt .= 's=email;'; - } - - // end-part - $dkim_txt .= 't=s'; - + $dkim_txt .= 'k=rsa;p=' . trim($domain['dkim_pubkey']) . ';'; // dkim-entry $zone_dkim[] = $dkim_txt; } diff --git a/lib/Froxlor/ErrorBag.php b/lib/Froxlor/ErrorBag.php new file mode 100644 index 0000000000..a3bb6a074d --- /dev/null +++ b/lib/Froxlor/ErrorBag.php @@ -0,0 +1,77 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor; + +use Exception; + +/** + * Class to manage the current user / session + */ +class ErrorBag +{ + + /** + * returns whether there are errors stored + * + * @return bool + */ + public static function hasErrors(): bool + { + return !empty($_SESSION) && !empty($_SESSION['_errors']); + } + + /** + * add error + * + * @param string $data + * + * @return void + */ + public static function addError(string $data): void + { + if (!is_array($_SESSION['_errors'])) { + $_SESSION['_errors'] = []; + } + $_SESSION['_errors'][] = $data; + } + + /** + * Return errors and clear session + * + * @return array + * @throws Exception + */ + public static function getErrors(): array + { + $errors = $_SESSION['_errors'] ?? []; + unset($_SESSION['_errors']); + if (Settings::Config('display_php_errors')) { + return $errors; + } + return []; + } + +} diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index e93e48bcde..5ba45c9c36 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -31,15 +31,15 @@ final class Froxlor { // Main version variable - const VERSION = '2.1.4'; + const VERSION = '2.2.0-dev1'; // Database version (YYYYMMDDC where C is a daily counter) - const DBVERSION = '202312120'; + const DBVERSION = '202312230'; // Distribution branding-tag (used for Debian etc.) const BRANDING = ''; - const DOCS_URL = 'https://docs.froxlor.org/v2.1/'; + const DOCS_URL = 'https://docs.froxlor.org/v2.2/'; /** * return path to where froxlor is installed, e.g. diff --git a/lib/Froxlor/Install/Install/Core.php b/lib/Froxlor/Install/Install/Core.php index ef66156e43..07670f2fa2 100644 --- a/lib/Froxlor/Install/Install/Core.php +++ b/lib/Froxlor/Install/Install/Core.php @@ -674,6 +674,7 @@ private function createJsonArray() 'http' => $this->validatedData['webserver'], 'smtp' => 'postfix_dovecot', 'mail' => 'dovecot_postfix2', + 'antispam' => 'rspamd', 'ftp' => 'proftpd', 'system' => $system_params ]; diff --git a/lib/Froxlor/Install/Update.php b/lib/Froxlor/Install/Update.php index 699bab1bfe..2e77e07f55 100644 --- a/lib/Froxlor/Install/Update.php +++ b/lib/Froxlor/Install/Update.php @@ -25,6 +25,7 @@ namespace Froxlor\Install; +use Froxlor\FileDir; use Froxlor\Froxlor; use Froxlor\FroxlorLogger; use Froxlor\Settings; @@ -85,7 +86,7 @@ public static function lastStepStatus(int $status = -1, string $message = 'OK', self::$update_tasks[self::$task_counter]['result'] = 1; break; default: - self::$update_tasks[self::$task_counter]['result'] = -1; + self::$update_tasks[self::$task_counter]['result'] = -1; break; } @@ -136,4 +137,36 @@ public static function getTaskCounter(): int { return self::$task_counter; } + + public static function cleanOldFiles(array $to_clean) + { + self::showUpdateStep("Cleaning up old files"); + $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) { + self::lastStepStatus(0); + } else { + if (empty($del_list)) { + // none of the files existed + self::lastStepStatus(0); + } else { + self::lastStepStatus( + 1, + 'manual commands needed', + 'Please run the following commands manually:
' . $del_list . '
' + ); + } + } + } } diff --git a/lib/Froxlor/PhpHelper.php b/lib/Froxlor/PhpHelper.php index a5cdddc36b..5bf3cc24c8 100644 --- a/lib/Froxlor/PhpHelper.php +++ b/lib/Froxlor/PhpHelper.php @@ -34,27 +34,6 @@ 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 @@ public static function arrayTrim(array $source): array }); } - /** - * 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 @@ public static function phpErrHandler($errno, $errstr, $errfile, $errline) $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 @@ public static function sizeReadable( ?string $max = '', string $system = 'si', string $retstring = '%01.2f %s' - ): string { + ): string + { // Pick units $systems = [ 'si' => [ @@ -421,7 +371,8 @@ public static function recursive_array_search( 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 @@ public static function cleanGlobal(array &$global, AntiXSS &$antiXss) } } - /** - * @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 dad8181477..d0f33a95aa 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 @@ private static function readConfig() 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 @@ public static function loadSettingsInto(&$settings_data) } } - public static function getAll() : array + public static function getAll(): array { self::init(); return self::$data; @@ -338,17 +340,14 @@ public static function getAll() : array /** * 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 fe4d9603d0..9b82ca0a99 100644 --- a/lib/Froxlor/Settings/Store.php +++ b/lib/Froxlor/Settings/Store.php @@ -225,6 +225,17 @@ public static function storeSettingFieldInsertBindTask($fieldname, $fielddata, $ 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 a131052c14..b4be07ac2e 100644 --- a/lib/Froxlor/System/Cronjob.php +++ b/lib/Froxlor/System/Cronjob.php @@ -134,11 +134,15 @@ public static function inserttask(int $type, ...$params) 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 d9fa4e99d0..5eab6af39f 100644 --- a/lib/Froxlor/Traffic/Traffic.php +++ b/lib/Froxlor/Traffic/Traffic.php @@ -42,7 +42,7 @@ public static function getCustomerStats(array $userinfo, string $range = null, b { $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 @@ public static function getCustomerStats(array $userinfo, string $range = null, b $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 @@ public static function getCustomerStats(array $userinfo, string $range = null, b $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 @@ public static function getCustomerStats(array $userinfo, string $range = null, b } // 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 c4d989aa8d..3e01372e14 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 @@ 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 @@ public static function domainExternalLinkInfo(array $attributes): string 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 @@ public static function adminCanEditDNS(array $attributes): bool 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 @@ public static function editSSLButtons(array $attributes): array ], ]; - // 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 @@ public static function editSSLButtons(array $attributes): array 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'] . '
'; @@ -226,6 +242,9 @@ public static function listIPs(array $attributes): string return lng('panel.empty'); } + /** + * @throws \Exception + */ public static function getPhpConfigName(array $attributes): string { $sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"); @@ -233,11 +252,11 @@ public static function getPhpConfigName(array $attributes): string if ((int)UI::getCurrentUser()['adminsession'] == 1) { $linker = UI::getLinker(); $result = '' . $phpconfig['description'] . ''; + 'section' => 'phpsettings', + 'page' => 'overview', + 'searchfield' => 'c.id', + 'searchtext' => $attributes['data'], + ]) . '">' . $phpconfig['description'] . ''; } else { $result = $phpconfig['description']; } diff --git a/lib/Froxlor/UI/Callbacks/Style.php b/lib/Froxlor/UI/Callbacks/Style.php index 511a118bdb..e6d6c5f52f 100644 --- a/lib/Froxlor/UI/Callbacks/Style.php +++ b/lib/Froxlor/UI/Callbacks/Style.php @@ -25,6 +25,7 @@ namespace Froxlor\UI\Callbacks; +use Froxlor\CurrentUser; use Froxlor\Settings; class Style @@ -68,7 +69,7 @@ public static function resultDomainTerminatedOrDeactivated(array $attributes): s $termination_css = 'table-danger'; } } - $deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated']; + $deactivated = $attributes['fields']['deactivated'] || (CurrentUser::isAdmin() && $attributes['fields']['customer_deactivated']); return $deactivated ? 'table-info' : $termination_css; } diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php index 9e63e3c302..91df6fdf7d 100644 --- a/lib/Froxlor/UI/Callbacks/Text.php +++ b/lib/Froxlor/UI/Callbacks/Text.php @@ -90,9 +90,10 @@ public static function wordwrap(array $attributes): string public static function customerNoteDetailModal(array $attributes): array { $note = $attributes['fields']['custom_notes'] ?? ''; + $key = $attributes['fields']['customerid'] ?? $attributes['fields']['adminid']; return [ - 'entry' => $attributes['fields']['id'], - 'id' => 'cnModal' . $attributes['fields']['id'], + 'entry' => $key, + 'id' => 'cnModal' . $key, 'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']), 'body' => nl2br(Markdown::cleanCustomNotes($note)) ]; diff --git a/lib/Froxlor/UI/Form.php b/lib/Froxlor/UI/Form.php index d17c911e3d..a824bd8a34 100644 --- a/lib/Froxlor/UI/Form.php +++ b/lib/Froxlor/UI/Form.php @@ -217,7 +217,8 @@ public static function prefetchFormFieldData($fieldname, $fielddata) { $returnvalue = []; if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] == 'select') { - if ((!is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) { + if ((empty($fielddata['select_var']) || !is_array($fielddata['select_var'])) && (isset($fielddata['option_options_method'])) + ) { $returnvalue['select_var'] = call_user_func($fielddata['option_options_method']); } } diff --git a/lib/Froxlor/UI/Response.php b/lib/Froxlor/UI/Response.php index a12b368302..4e57652045 100644 --- a/lib/Froxlor/UI/Response.php +++ b/lib/Froxlor/UI/Response.php @@ -139,7 +139,7 @@ public static function standardError($errors = '', $replacer = '', $throw_except exit; } - public static function dynamicError($message) + public static function dynamicError($message, bool $nosession = false) { $_SESSION['requestData'] = $_POST; $link_ref = ''; @@ -147,7 +147,8 @@ public static function dynamicError($message) $link_ref = htmlentities($_SERVER['HTTP_REFERER']); } - UI::view('misc/alert.html.twig', [ + $tpl = $nosession ? 'misc/alert_nosession.html.twig' : 'misc/alert.html.twig'; + UI::view($tpl, [ 'type' => 'danger', 'btntype' => 'light', 'heading' => lng('error.error'), diff --git a/lib/config.example.inc.php b/lib/config.example.inc.php index 7ca6b26b79..e5b14ef7d6 100644 --- a/lib/config.example.inc.php +++ b/lib/config.example.inc.php @@ -23,4 +23,12 @@ * Default: false */ 'disable_otp_security_check' => false, + + /** + * For debugging/development purposes only. + * Enable to display all php related issue (notices, warnings, etc.; depending on php.ini) for froxlor itself + * + * Default: false + */ + 'display_php_errors' => false, ]; diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml index 70b0cd3f26..cda9767bfd 100644 --- a/lib/configfiles/bookworm.xml +++ b/lib/configfiles/bookworm.xml @@ -1657,7 +1657,7 @@ data_directory = /var/lib/postfix # for the case of a subdomain, $mydomain *must* be equal to $myhostname, # otherwise you cannot use the main domain for virtual transport. # also check the note about $mydomain below. -myhostname = mail.$mydomain +myhostname = $mydomain #myhostname = virtual.domain.tld # The mydomain parameter specifies the local internet domain name. @@ -1751,8 +1751,8 @@ inet_interfaces = all # # See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS". # -#mydestination = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +mydestination = $myhostname, localhost.$mydomain, localhost +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain #mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, # mail.$mydomain, www.$mydomain, ftp.$mydomain @@ -2561,6 +2561,107 @@ plugin { + + + + + + + /dev/null]]> + /etc/apt/sources.list.d/rspamd.list]]> + > /etc/apt/sources.list.d/rspamd.list]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //service[@type='antispam']/general/commands[@index=1] + + //service[@type='antispam']/general/installs[@index=1] + + //service[@type='antispam']/general/commands[@index=2] + + //service[@type='antispam']/general/files[@index=1] + + //service[@type='antispam']/general/commands[@index=3] + + + diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index 366689e0cb..c49212d962 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -1657,7 +1657,7 @@ data_directory = /var/lib/postfix # for the case of a subdomain, $mydomain *must* be equal to $myhostname, # otherwise you cannot use the main domain for virtual transport. # also check the note about $mydomain below. -myhostname = mail.$mydomain +myhostname = $mydomain #myhostname = virtual.domain.tld # The mydomain parameter specifies the local internet domain name. @@ -1751,8 +1751,8 @@ inet_interfaces = all # # See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS". # -#mydestination = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +mydestination = $myhostname, localhost.$mydomain, localhost +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain #mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, # mail.$mydomain, www.$mydomain, ftp.$mydomain @@ -4131,6 +4131,106 @@ plugin { + + + + + + + /dev/null]]> + /etc/apt/sources.list.d/rspamd.list]]> + > /etc/apt/sources.list.d/rspamd.list]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //service[@type='antispam']/general/commands[@index=1] + + //service[@type='antispam']/general/installs[@index=1] + + //service[@type='antispam']/general/commands[@index=2] + + //service[@type='antispam']/general/files[@index=1] + + //service[@type='antispam']/general/commands[@index=3] + + + diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index 67af7ed465..ddc1530a89 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -1642,7 +1642,7 @@ compatibility_level = 2 # for the case of a subdomain, $mydomain *must* be equal to $myhostname, # otherwise you cannot use the main domain for virtual transport. # also check the note about $mydomain below. -myhostname = mail.$mydomain +myhostname = $mydomain #myhostname = virtual.domain.tld # The mydomain parameter specifies the local internet domain name. @@ -1656,8 +1656,8 @@ myhostname = mail.$mydomain # FQDN from Froxlor mydomain = -#mydestination = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +mydestination = $myhostname, localhost.$mydomain, localhost +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain #mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, # mail.$mydomain, www.$mydomain, ftp.$mydomain @@ -3354,6 +3354,106 @@ plugin { + + + + + + + /dev/null]]> + /etc/apt/sources.list.d/rspamd.list]]> + > /etc/apt/sources.list.d/rspamd.list]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //service[@type='antispam']/general/commands[@index=1] + + //service[@type='antispam']/general/installs[@index=1] + + //service[@type='antispam']/general/commands[@index=2] + + //service[@type='antispam']/general/files[@index=1] + + //service[@type='antispam']/general/commands[@index=3] + + + diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index 01d33fa9ca..cc9f67c68b 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -1727,12 +1727,9 @@ compatibility_level = 2 ## General Postfix configuration # should be the default domain from your provider eg. "server100.provider.tld" mydomain = - -# should be different from $mydomain eg. "mail.$mydomain" -myhostname = mail.$mydomain +myhostname = $mydomain mydestination = $myhostname, - $mydomain, localhost.$myhostname, localhost.$mydomain, localhost @@ -2218,6 +2215,98 @@ plugin { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //service[@type='antispam']/general/installs[@index=1] + + //service[@type='antispam']/general/commands[@index=2] + + //service[@type='antispam']/general/files[@index=1] + + //service[@type='antispam']/general/commands[@index=3] + + + diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml index 67450c27fa..323a1f0f1c 100644 --- a/lib/configfiles/jammy.xml +++ b/lib/configfiles/jammy.xml @@ -1642,7 +1642,7 @@ compatibility_level = 2 # for the case of a subdomain, $mydomain *must* be equal to $myhostname, # otherwise you cannot use the main domain for virtual transport. # also check the note about $mydomain below. -myhostname = mail.$mydomain +myhostname = $mydomain #myhostname = virtual.domain.tld # The mydomain parameter specifies the local internet domain name. @@ -1656,8 +1656,8 @@ myhostname = mail.$mydomain # FQDN from Froxlor mydomain = -#mydestination = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +mydestination = $myhostname, localhost.$mydomain, localhost +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain #mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, # mail.$mydomain, www.$mydomain, ftp.$mydomain @@ -3344,6 +3344,106 @@ plugin { + + + + + + + /dev/null]]> + /etc/apt/sources.list.d/rspamd.list]]> + > /etc/apt/sources.list.d/rspamd.list]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //service[@type='antispam']/general/commands[@index=1] + + //service[@type='antispam']/general/installs[@index=1] + + //service[@type='antispam']/general/commands[@index=2] + + //service[@type='antispam']/general/files[@index=1] + + //service[@type='antispam']/general/commands[@index=3] + + + diff --git a/lib/formfields/admin/domains/formfield.domains_add.php b/lib/formfields/admin/domains/formfield.domains_add.php index 5400cc7d28..b5e994446f 100644 --- a/lib/formfields/admin/domains/formfield.domains_add.php +++ b/lib/formfields/admin/domains/formfield.domains_add.php @@ -111,7 +111,7 @@ 'selected' => 0 ], 'dkim' => [ - 'visible' => Settings::Get('dkim.use_dkim') == '1', + 'visible' => Settings::Get('antispam.activated') == '1', 'label' => 'DomainKeys', 'type' => 'checkbox', 'value' => '1', diff --git a/lib/formfields/admin/domains/formfield.domains_edit.php b/lib/formfields/admin/domains/formfield.domains_edit.php index 00b00cbd71..93dea60dd9 100644 --- a/lib/formfields/admin/domains/formfield.domains_edit.php +++ b/lib/formfields/admin/domains/formfield.domains_edit.php @@ -129,7 +129,7 @@ 'selected' => $result['subcanemaildomain'] ], 'dkim' => [ - 'visible' => Settings::Get('dkim.use_dkim') == '1', + 'visible' => Settings::Get('antispam.activated') == '1', 'label' => 'DomainKeys', 'type' => 'checkbox', 'value' => '1', diff --git a/lib/formfields/customer/domains/formfield.domains_edit.php b/lib/formfields/customer/domains/formfield.domains_edit.php index cd7d1553ad..10974832de 100644 --- a/lib/formfields/customer/domains/formfield.domains_edit.php +++ b/lib/formfields/customer/domains/formfield.domains_edit.php @@ -116,6 +116,12 @@ 'type' => 'hidden', 'value' => '0' ], + 'dkim_entry' => [ + 'visible' => (Settings::Get('system.bind_enable') == '0' && Settings::Get('antispam.activated') == '1' && $result['dkim'] == '1' && $result['dkim_pubkey'] != ''), + 'label' => lng('antispam.required_dkim_dns'), + 'type' => 'longtext', + 'value' => (string)(new \Froxlor\Dns\DnsEntry('dkim' . $result['dkim_id'] . '._domainkey', 'TXT', '"v=DKIM1; k=rsa; p='.trim($result['dkim_pubkey']).'"')) + ], ] ], 'section_bssl' => [ diff --git a/lib/formfields/customer/email/formfield.emails_edit.php b/lib/formfields/customer/email/formfield.emails_edit.php index 4749bca174..7a03ef6939 100644 --- a/lib/formfields/customer/email/formfield.emails_edit.php +++ b/lib/formfields/customer/email/formfield.emails_edit.php @@ -102,6 +102,44 @@ ] ] ], + 'spam_tag_level' => [ + 'label' => lng('antispam.spam_tag_level'), + 'type' => 'text', + 'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/', + 'value' => $result['spam_tag_level'] + ], + 'spam_kill_level' => [ + 'label' => lng('antispam.spam_kill_level'), + 'type' => 'text', + 'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/', + 'value' => $result['spam_kill_level'] + ], + 'bypass_spam' => [ + 'label' => lng('antispam.bypass_spam'), + 'type' => 'label', + 'value' => ((int)$result['bypass_spam'] == 0 ? lng('panel.no') : lng('panel.yes')), + 'next_to' => [ + 'add_link' => [ + 'type' => 'link', + 'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglebypass&id=' . $result['id'], + 'label' => ' ' . lng('panel.toggle'), + 'classes' => 'btn btn-sm btn-secondary' + ] + ] + ], + 'policy_greylist' => [ + 'label' => lng('antispam.policy_greylist'), + 'type' => 'label', + 'value' => ((int)$result['policy_greylist'] == 0 ? lng('panel.no') : lng('panel.yes')), + 'next_to' => [ + 'add_link' => [ + 'type' => 'link', + 'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglegreylist&id=' . $result['id'], + 'label' => ' ' . lng('panel.toggle'), + 'classes' => 'btn btn-sm btn-secondary' + ] + ] + ], 'mail_fwds' => [ 'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')', 'type' => 'itemlist', @@ -119,7 +157,9 @@ ] ], 'buttons' => [ - /* none */ + [ + 'label' => lng('panel.save') + ] ] ] ]; diff --git a/lib/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php index 6587593b2e..b378482832 100644 --- a/lib/tablelisting/admin/tablelisting.domains.php +++ b/lib/tablelisting/admin/tablelisting.domains.php @@ -50,7 +50,7 @@ 'label' => lng('domains.domainname'), 'field' => 'domain_ace', 'isdefaultsearchfield' => true, - 'callback' => [Domain::class, 'domainLink'], + 'callback' => [Domain::class, 'domainEditLink'], ], 'ipsandports' => [ 'label' => lng('admin.ipsandports.ipsandports'), diff --git a/lib/tablelisting/customer/tablelisting.emails.php b/lib/tablelisting/customer/tablelisting.emails.php index 2643ab800b..64243b015c 100644 --- a/lib/tablelisting/customer/tablelisting.emails.php +++ b/lib/tablelisting/customer/tablelisting.emails.php @@ -49,6 +49,24 @@ 'field' => 'popaccountid', 'callback' => [Email::class, 'account'], ], + 'm.spam_tag_level' => [ + 'label' => lng('emails.spam_tag_level'), + 'field' => 'spam_tag_level', + ], + 'm.spam_kill_level' => [ + 'label' => lng('emails.spam_kill_level'), + 'field' => 'spam_kill_level', + ], + 'm.bypass_spam' => [ + 'label' => lng('emails.bypass_spam'), + 'field' => 'bypass_spam', + 'callback' => [Text::class, 'boolean'], + ], + 'm.policy_greylist' => [ + 'label' => lng('emails.policy_greylist'), + 'field' => 'policy_greylist', + 'callback' => [Text::class, 'boolean'], + ], 'm.iscatchall' => [ 'label' => lng('emails.catchall'), 'field' => 'iscatchall', diff --git a/lng/ca.lng.php b/lng/ca.lng.php index 99ecce92f9..ecbefcc4c9 100644 --- a/lng/ca.lng.php +++ b/lng/ca.lng.php @@ -629,45 +629,6 @@ 'mysqlserver' => 'Servidor mysql utilitzable' ], 'diskquota' => 'Quota', - 'dkim' => [ - 'dkim_prefix' => [ - 'title' => 'Prefix', - 'description' => 'Especifiqueu la ruta als fitxers DKIM RSA i als fitxers de configuració del plugin Milter' - ], - 'dkim_domains' => [ - 'title' => 'Nom del fitxer dels dominis', - 'description' => 'Nom de fitxer del paràmetre DKIM Domains especificat a la configuració dkim-milter' - ], - 'dkim_dkimkeys' => [ - 'title' => 'Nom de l\'arxiu KeyList', - 'description' => 'Nom de fitxer del paràmetre DKIM KeyList especificat a la configuració de dkim-milter' - ], - 'dkimrestart_command' => [ - 'title' => 'Ordre de reinici del filtre', - 'description' => 'Especifiqui l\'ordre de reinici del servei DKIM milter' - ], - 'privkeysuffix' => [ - 'title' => 'Sufix de claus privades', - 'description' => 'Pot especificar una extensió/sufix de nom de fitxer (opcional) per a les claus privades dkim generades. Alguns serveis com dkim-filter requereixen que estigui buit.' - ], - 'use_dkim' => [ - 'title' => 'Activar suport DKIM?', - 'description' => 'Voleu utilitzar el sistema de claus de domini (DKIM)?
Nota: DKIM només és compatible amb dkim-filter, no amb opendkim (de moment)' - ], - 'dkim_algorithm' => [ - 'title' => 'Algorismes Hash permesos', - 'description' => 'Defineix els algorismes hash permesos, escull "Tots" per a tots els algorismes o un o més dels altres algorismes disponibles' - ], - 'dkim_servicetype' => 'Tipus de servei', - 'dkim_keylength' => [ - 'title' => 'Longitud de clau', - 'description' => 'Atenció: Si canvia aquests valors, haurà d\'eliminar totes les claus privades/públiques de "%s".' - ], - 'dkim_notes' => [ - 'title' => 'Notes DKIM', - 'description' => 'Notes que podrien ser d\'interès per a un humà, per exemple, una URL com http://www.dnswatch.info. Cap programa no realitza cap interpretació. Aquesta etiqueta s\'ha de fer servir amb moderació a causa de les limitacions d\'espai al DNS. Està pensada perquè la facin servir els administradors, no els usuaris finals.' - ] - ], 'dns' => [ 'destinationip' => 'IP(s) del domini', 'standardip' => 'IP estàndard del servidor', diff --git a/lng/de.lng.php b/lng/de.lng.php index 6ea5acc718..b11edeeb67 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -264,7 +264,7 @@ 'text' => 'Nachricht', 'sslsettings' => 'SSL-Einstellungen', 'specialsettings_replacements' => 'Die folgenden Variablen können verwendet werden:
{DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (wenn zutreffend)
', - 'dkimsettings' => 'DomainKey-Einstellungen', + 'antispam_settings' => 'Antispam-Einstellungen', 'caneditphpsettings' => 'Kann PHP-bezogene Domaineinstellungen vornehmen?', 'allips' => 'Alle IP-Adressen', 'awstatssettings' => 'AWstats-Einstellungen', @@ -595,43 +595,38 @@ 'mysqlserver' => 'Erlaubte MySQL-Server', ], 'diskquota' => 'Quota', - 'dkim' => [ - 'dkim_prefix' => [ - 'title' => 'Prefix', - 'description' => 'Wie lautet der Pfad zu den DKIM-RSA-Dateien sowie den Einstellungsdateien des Milter-Plugins?', + 'antispam' => [ + 'config_file' => [ + 'title' => 'Antispam Konfigurationsdatei', + 'description' => 'Pfad + Dateiname der Antispam-Regel Konfigurationsdatei', ], - 'dkim_domains' => [ - 'title' => 'Domains-Dateiname', - 'description' => 'Dateiname der DKIM-Domains-Angabe aus der DKIM-Milter-Konfigurationsdatei.', + 'reload_command' => [ + 'title' => 'Milter-Restart-Befehl', + 'description' => 'Wie lautet der Befehl zum Neustarten des rspamd-Dienstes?', ], - 'dkim_dkimkeys' => [ - 'title' => 'KeyList Dateiname', - 'description' => 'Dateiname der DKIM-KeyList-Angabe aus der DKIM-Milter-Konfigurationsdatei.', + 'activated' => [ + 'title' => 'Antispam aktivieren?', + 'description' => 'Aktivieren, um rspamd als Antispam Dienst zu verwenden.', ], - 'dkimrestart_command' => [ - 'title' => 'Milter-Restart-Kommando', - 'description' => 'Wie lautet das Kommando zum Neustarten des DKIM-Milter-Dienstes?', - ], - 'privkeysuffix' => [ - 'title' => 'Suffix für Private Keys', - 'description' => 'Hier kann eine (optionale) Dateiendung für die generierten Private Keys angegeben werden. Manche Dienste, wie dkim-filter, erwarten, dass die Schlüssel keine Dateiendung haben (leer).', + 'dkim_keylength' => [ + 'title' => 'DKIM Schlüssel-Länge', + 'description' => 'Achtung: Änderungen sind nur für neue Schlüssel gültig.

Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.', ], - 'use_dkim' => [ - 'title' => 'DKIM-Support aktivieren?', - 'description' => 'Wollen Sie das Domain-Keys-System (DKIM) benutzen?
Hinweis: Derzeit wird DKIM nur via dkim-filter unterstützt, nicht opendkim.', + 'spam_tag_level' => [ + 'title' => 'Spam Markierungs-Level', + 'description' => 'Erforderliche Punktzahl zum Markieren einer E-Mail als Spam
Standard: 7.0' ], - 'dkim_algorithm' => [ - 'title' => 'Gültige Hash-Algorithmen', - 'description' => 'Wählen Sie einen Algorithmus, "All" für alle Algorithmen oder einen oder mehrere von den verfügbaren Algorithmen.', + 'spam_kill_level' => [ + 'title' => 'Spam Ignorier-Level', + 'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail
Standard: 14.0' ], - 'dkim_servicetype' => 'Service Typen', - 'dkim_keylength' => [ - 'title' => 'Schlüssel-Länge', - 'description' => 'Achtung: Bei Änderung dieser Einstellung müssen alle private/public Schlüssel in "%s" gelöscht werden.', + 'bypass_spam' => [ + 'title' => 'Spamfilter umgehen', + 'description' => 'Aktivieren, um den Spamfilter für diese Adresse zu umgehen/deaktivieren.
Standard: Nein' ], - 'dkim_notes' => [ - 'title' => 'DKIM Notiz', - 'description' => 'Eine Notiz, welche für Menschen interessant sein könnte, z.B. eine URL wie http://www.dnswatch.info. Es gibt keine programmgesteuerte Interpretation für dieses Feld. Gehen Sie sparsam mit der Anzahl der Zeichen um, da es Einschränkungen seitens des DNS-Dienstes gibt. Dieses Feld ist für Administratoren gedacht, nicht für Benutzer.', + 'policy_greylist' => [ + 'title' => 'Verwende greylisting', + 'description' => 'Eingehende E-Mails mittels Greylisting schützen.
Standard: Ja' ], ], 'dns' => [ @@ -2108,9 +2103,19 @@ ], ], 'spf' => [ - 'use_spf' => 'Aktiviere SPF für Domains?', + 'use_spf' => [ + 'title' => 'Aktiviere SPF für Domains?', + 'description' => 'Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.', + ], 'spf_entry' => 'SPF-Eintrag für alle Domains', ], + 'dmarc' => [ + 'use_dmarc' => [ + 'title' => 'Aktiviere DMARC für Domains?', + 'description' => 'Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.', + ], + 'dmarc_entry' => 'DMARC-Eintrag für alle Domains', + ], 'success' => [ 'messages_success' => 'Nachricht erfolgreich an "%s" Empfänger gesendet', 'success' => 'Information', diff --git a/lng/en.lng.php b/lng/en.lng.php index 97fd7f15a8..1fa58d9107 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -268,7 +268,7 @@ 'text' => 'Message', 'sslsettings' => 'SSL settings', 'specialsettings_replacements' => 'You can use the following variables:
{DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (if applicable)
', - 'dkimsettings' => 'DomainKey settings', + 'antispam_settings' => 'Antispam settings', 'caneditphpsettings' => 'Can change php-related domain settings?', 'allips' => 'All IP\'s', 'awstatssettings' => 'AWstats settings', @@ -644,43 +644,38 @@ 'mysqlserver' => 'Usable mysql-server', ], 'diskquota' => 'Quota', - 'dkim' => [ - 'dkim_prefix' => [ - 'title' => 'Prefix', - 'description' => 'Please specify the path to the DKIM RSA-files as well as to the configuration files for the Milter-plugin', + 'antispam' => [ + 'config_file' => [ + 'title' => 'Antispam settings file', + 'description' => 'Please specify the filename for the email-antispam rules', ], - 'dkim_domains' => [ - 'title' => 'Domains filename', - 'description' => 'Filename of the DKIM Domains parameter specified in the dkim-milter configuration', - ], - 'dkim_dkimkeys' => [ - 'title' => 'KeyList filename', - 'description' => 'Filename of the DKIM KeyList parameter specified in the dkim-milter configuration', - ], - 'dkimrestart_command' => [ + 'reload_command' => [ 'title' => 'Milter restart command', - 'description' => 'Please specify the restart command for the DKIM milter service', + 'description' => 'Please specify the restart command for the rspamd service', ], - 'privkeysuffix' => [ - 'title' => 'Private keys suffix', - 'description' => 'You can specify an (optional) filename extension/suffix for the generate dkim private keys. Some services like dkim-filter requires this to be empty', + 'activated' => [ + 'title' => 'Activate antispam?', + 'description' => 'Would you like to use rspamd as antispam service?', ], - 'use_dkim' => [ - 'title' => 'Activate DKIM support?', - 'description' => 'Would you like to use the Domain Keys (DKIM) system?
Note: DKIM is only supported using dkim-filter, not opendkim (yet)', + 'dkim_keylength' => [ + 'title' => 'DKIM Key-length', + 'description' => 'Attention: Changes will only apply for new keys

Requires a specific dns entry for the domain. If you are not using the nameserver feature, you will have to manually manage these entries.', ], - 'dkim_algorithm' => [ - 'title' => 'Allowed Hash Algorithms', - 'description' => 'Define allowed hash algorithms, chose "All" for all algorithms or one or more from the other available algorithms', + 'spam_tag_level' => [ + 'title' => 'Spam tag level', + 'description' => 'Score that is required to mark an email as spam
Default: 7.0' ], - 'dkim_servicetype' => 'Service Types', - 'dkim_keylength' => [ - 'title' => 'Key-length', - 'description' => 'Attention: If you change this values, you need to delete all the private/public keys in "%s"', + 'spam_kill_level' => [ + 'title' => 'Spam kill level', + 'description' => 'Score that is required to discard an email entirely
Default: 14.0' ], - 'dkim_notes' => [ - 'title' => 'DKIM Notes', - 'description' => 'Notes that might be of interest to a human, e.g. a URL like http://www.dnswatch.info. No interpretation is made by any program. This tag should be used sparingly due to space limitations in DNS. This is intended for use by administrators, not end users.', + 'bypass_spam' => [ + 'title' => 'Bypass spamfilter', + 'description' => 'Activate to bypass/disable spamfiltering for this address.
Default: no' + ], + 'policy_greylist' => [ + 'title' => 'Use greylisting', + 'description' => 'Incoming emails will be protected by greylisting.
Default: yes' ], ], 'dns' => [ @@ -2236,9 +2231,19 @@ ], ], 'spf' => [ - 'use_spf' => 'Activate SPF for domains?', + 'use_spf' => [ + 'title' => 'Activate SPF for domains?', + 'description' => 'Requires a specific dns entry for the domain. If you are not using the nameserver feature, you will have to manually manage these entries.', + ], 'spf_entry' => 'SPF entry for all domains', ], + 'dmarc' => [ + 'use_dmarc' => [ + 'title' => 'Activate DMARC for domains?', + 'description' => 'Requires a specific dns entry for the domain. If you are not using the nameserver feature, you will have to manually manage these entries.', + ], + 'dmarc_entry' => 'DMARC entry for all domains', + ], 'ssl_certificates' => [ 'certificate_for' => 'Certificate for', 'valid_from' => 'Valid from', diff --git a/lng/es.lng.php b/lng/es.lng.php index 46fee058ac..29704088d9 100644 --- a/lng/es.lng.php +++ b/lng/es.lng.php @@ -628,45 +628,6 @@ 'mysqlserver' => 'Servidor mysql utilizable' ], 'diskquota' => 'Cuota', - 'dkim' => [ - 'dkim_prefix' => [ - 'title' => 'Prefijo', - 'description' => 'Especifique la ruta a los archivos DKIM RSA y a los archivos de configuración del plugin Milter' - ], - 'dkim_domains' => [ - 'title' => 'Nombre de archivo de los dominios', - 'description' => 'Nombre de archivo del parámetro DKIM Domains especificado en la configuración de dkim-milter' - ], - 'dkim_dkimkeys' => [ - 'title' => 'KeyList filename', - 'description' => 'Nombre de archivo del parámetro DKIM KeyList especificado en la configuración de dkim-milter' - ], - 'dkimrestart_command' => [ - 'title' => 'Comando de reinicio del filtro', - 'description' => 'Especifique el comando de reinicio del servicio DKIM milter' - ], - 'privkeysuffix' => [ - 'title' => 'Sufijo de claves privadas', - 'description' => 'Puede especificar una extensión/sufijo de nombre de archivo (opcional) para las claves privadas dkim generadas. Algunos servicios como dkim-filter requieren que esté vacío.' - ], - 'use_dkim' => [ - 'title' => '¿Activar soporte DKIM?', - 'description' => '¿Desea utilizar el sistema de claves de dominio (DKIM)?
Nota: DKIM sólo es compatible con dkim-filter, no con opendkim (todavía)' - ], - 'dkim_algorithm' => [ - 'title' => 'Algoritmos Hash permitidos', - 'description' => 'Defina los algoritmos hash permitidos, elija "Todos" para todos los algoritmos o uno o más de los otros algoritmos disponibles' - ], - 'dkim_servicetype' => 'Tipos de servicio', - 'dkim_keylength' => [ - 'title' => 'Longitud de clave', - 'description' => 'Atención: Si cambia estos valores, deberá eliminar todas las claves privadas/públicas de "%s".' - ], - 'dkim_notes' => [ - 'title' => 'Notas DKIM', - 'description' => 'Notas que podrían ser de interés para un humano, por ejemplo, una URL como http://www.dnswatch.info. Ningún programa realiza ninguna interpretación. Esta etiqueta debe utilizarse con moderación debido a las limitaciones de espacio en DNS. Está pensada para que la utilicen los administradores, no los usuarios finales.' - ] - ], 'dns' => [ 'destinationip' => 'IP(s) del dominio', 'standardip' => 'IP estándar del servidor', diff --git a/lng/it.lng.php b/lng/it.lng.php index 58610b8246..df13353f5d 100644 --- a/lng/it.lng.php +++ b/lng/it.lng.php @@ -660,41 +660,6 @@ 'services' => 'Servizi', ], 'diskquota' => 'Quota', - 'dkim' => [ - 'dkim_prefix' => [ - 'title' => 'Prefisso', - 'description' => 'Si prega di specificare il percorso della DKIM RSA-files, nonch¸ i file di configurazione per il plugin Milter', - ], - 'dkim_domains' => [ - 'title' => 'Domini nomefile', - 'description' => 'Nome file del parametro DKIM Domains specificata nella configurazione dkim-milter', - ], - 'dkim_dkimkeys' => [ - 'title' => 'Nome file del KeyList', - 'description' => 'Nome file del parametro DKIM KeyList specificata nella configurazione dkim-milter', - ], - 'dkimrestart_command' => [ - 'title' => 'Milter commando riavvio', - 'description' => 'Si prega di specificare il comando per riavviare il servizio DKIM milter', - ], - 'use_dkim' => [ - 'title' => 'Attivare il supporto DKIM?', - 'description' => 'Vuoi utilizzare il sistema Domain Keys (DKIM)?', - ], - 'dkim_algorithm' => [ - 'title' => 'Ammessi Algoritmi Hash', - 'description' => 'Definire gli algoritmi di hash permessi, scegliere "Tutti" per permettere tutti gli algoritmi oppure uno o più tra gli altri algoritmi disponibili', - ], - 'dkim_servicetype' => 'Tipi di Servizio', - 'dkim_keylength' => [ - 'title' => 'Lunghezza Chiave', - 'description' => 'Attenzione: Se si modifica questo valore è necessario eliminare tutte le chiavi private/pubbliche in "%s"', - ], - 'dkim_notes' => [ - 'title' => 'Note DKIM', - 'description' => 'Nota potrebbe essere di interesse, es. un URL come http://www.dnswatch.info. Nessuna interpretazione è fatta da nessun programma. Questo tag deve essere usato con parsimonia per ragioni di spazio nel DNS. Questo è destinato ad essere utilizzato dagli amministratori e non dagli utenti finali.', - ], - ], 'dns' => [ 'destinationip' => 'Dominio IP', 'a_record' => 'A-Record (IPv6 optionale)', diff --git a/lng/nl.lng.php b/lng/nl.lng.php index 02548024c5..396ed4102f 100644 --- a/lng/nl.lng.php +++ b/lng/nl.lng.php @@ -328,41 +328,6 @@ 'mail_quota' => 'Mailquotum', 'sendinfomail' => 'Stuur gegevens naar mij via e-mail', ], - 'dkim' => [ - 'dkim_prefix' => [ - 'title' => 'Prefix', - 'description' => 'Geef het pad naar de DKIM RSA-files alsook naar de configuratie van de Milter-plugin', - ], - 'dkim_domains' => [ - 'title' => 'Bestandsnaam domeinen', - 'description' => 'Bestandsnaam van het DKIM Domains-parameter zoals aangegeven in de configuratie van dkim-milter', - ], - 'dkim_dkimkeys' => [ - 'title' => 'KeyList filename', - 'description' => 'Bestandsnaam van het DKIM KeyList-parameter zoals aangegeven in de configuratie van dkim-milter', - ], - 'dkimrestart_command' => [ - 'title' => 'Herstart-commando voor Milter', - 'description' => 'Geef het commando om de milter-plugin te herstarten', - ], - 'use_dkim' => [ - 'title' => 'Activeer ondersteuning voor DKIM?', - 'description' => 'Wilt u gebruikmaken van Domain Keys (DKIM) systeem?', - ], - 'dkim_algorithm' => [ - 'title' => 'Toegestane hash-algoritmen', - 'description' => 'Toegestane hash-algoritmen, kies "Alle" voor alle algoritmen of 1 of meerdere van onderstaande', - ], - 'dkim_servicetype' => 'Type services', - 'dkim_keylength' => [ - 'title' => 'Lengte sleutel', - 'description' => 'Let op: Indien u deze waarde wijzigt, dient u allen geheime en publieke sleutels in "%s" te verwijderen', - ], - 'dkim_notes' => [ - 'title' => 'Notities voor DKIM', - 'description' => 'Notities die van belang kunnen zijn voor mensen, bijvoorbeeld een URL als http://www.dnswatch.info. Geen enkel programma zal deze informatie verwerken. Deze informatie dient schaars te zijn gezien de beperkte ruimte in DNS. Dit is bedoeld voor beheerders, niet voor eindgebruikers.', - ], - ], 'dns' => [ 'destinationip' => 'IP domein', 'standardip' => 'Standaard server IP', diff --git a/templates/Froxlor/assets/js/bootstrap.js b/templates/Froxlor/assets/js/bootstrap.js index 101c70bf55..dd83cd3f85 100644 --- a/templates/Froxlor/assets/js/bootstrap.js +++ b/templates/Froxlor/assets/js/bootstrap.js @@ -14,6 +14,9 @@ window.bootstrap = bootstrap; import Chart from 'chart.js/auto'; window.Chart = Chart; +// set a default theme +window.$theme = 'Froxlor'; + // Axios import axios from 'axios'; window.axios = axios; diff --git a/templates/Froxlor/assets/js/jquery/global.js b/templates/Froxlor/assets/js/jquery/global.js index 58202f3faf..067c4dffc6 100644 --- a/templates/Froxlor/assets/js/jquery/global.js +++ b/templates/Froxlor/assets/js/jquery/global.js @@ -8,13 +8,15 @@ export default function () { history.back(1); }) - $('#copySysInfo').on('click', function (e) { - e.preventDefault(); - navigator.clipboard.writeText($('#ccSysInfo').text().trim()); - }) - $('[data-bs-toggle="popover"]').each(function () { new bootstrap.Popover($(this)); }) + + $('.copyClipboard').on('click', function (e) { + e.preventDefault(); + const source_element = $(this).data('clipboard-source').text(); + navigator.clipboard.writeText($('#' + source_element).text().trim()); + }) + }); } diff --git a/templates/Froxlor/form/formfields.html.twig b/templates/Froxlor/form/formfields.html.twig index 0aed40ad8e..c8dfcab36f 100644 --- a/templates/Froxlor/form/formfields.html.twig +++ b/templates/Froxlor/form/formfields.html.twig @@ -60,6 +60,8 @@ {{ _self.select(id, field) }} {% elseif field.type == 'textarea' %} {{ _self.textarea(id, field) }} + {% elseif field.type == 'longtext' %} + {{ _self.longtext(id, field) }} {% elseif field.type == 'label' %} {{ _self.plain(id, field) }} {% elseif field.type == 'link' %} @@ -155,6 +157,15 @@ {% endif %} {% endmacro %} +{% macro longtext(id, field) %} +
+
+ +
+

{{ field.value|raw }}

+
+{% endmacro %} + {% macro input(id, field) %} {% if field.next_to is defined %}
diff --git a/templates/Froxlor/settings/configuration.html.twig b/templates/Froxlor/settings/configuration.html.twig index e258c473c3..ee48d06755 100644 --- a/templates/Froxlor/settings/configuration.html.twig +++ b/templates/Froxlor/settings/configuration.html.twig @@ -79,7 +79,8 @@ (dtype == 'bind' and get_setting('system.bind_enable') == '1' and get_setting('system.dns_server') == 'Bind') or (dtype == 'powerdns' and get_setting('system.bind_enable') == '1' and get_setting('system.dns_server') == 'PowerDNS') or (dtype == 'proftpd' and get_setting('system.ftpserver') == 'proftpd') or - (dtype == 'pureftpd' and get_setting('system.ftpserver') == 'pureftpd') + (dtype == 'pureftpd' and get_setting('system.ftpserver') == 'pureftpd') or + (dtype == 'rspamd' and get_setting('antispam.activated') == '1') %} {% set recommended = true %} {% endif %} diff --git a/templates/Froxlor/user/index.html.twig b/templates/Froxlor/user/index.html.twig index c788b01236..feacc05822 100644 --- a/templates/Froxlor/user/index.html.twig +++ b/templates/Froxlor/user/index.html.twig @@ -67,7 +67,7 @@ {{ lng('admin.systemdetails') }}
- +
- Froxlor: {{ call_static('\\Froxlor\\Froxlor', 'getVersionString') }} diff --git a/templates/Froxlor/userarea.html.twig b/templates/Froxlor/userarea.html.twig index 608a55cd30..bc3d11710d 100644 --- a/templates/Froxlor/userarea.html.twig +++ b/templates/Froxlor/userarea.html.twig @@ -20,7 +20,7 @@