implementation start of rspam/antispam feature

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann 2024-01-05 15:37:04 +01:00
parent 63bbcd4e00
commit b15f99b1e1
No known key found for this signature in database
GPG Key ID: C121F97338D7A352
59 changed files with 1739 additions and 865 deletions

View File

@ -0,0 +1,111 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
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'
]
]
]
]
];

View File

@ -1,146 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
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
]
]
]
]
];

163
composer.lock generated
View File

@ -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",

View File

@ -245,6 +245,15 @@ if ($page == 'email_domain') {
if (isset($result['email']) && $result['email'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
Emails::getLocal($userinfo, [
'id' => $id,
'spam_tag_level' => $_POST['spam_tag_level'] ?? \Froxlor\Cron\Mail\Rspamd::DEFAULT_MARK_LVL,
'spam_kill_level' => $_POST['spam_kill_level'] ?? \Froxlor\Cron\Mail\Rspamd::DEFAULT_REJECT_LVL
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
@ -291,6 +300,54 @@ if ($page == 'email_domain') {
'editid' => $id
]);
}
} elseif ($action == 'togglebypass' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
try {
Emails::getLocal($userinfo, [
'id' => $id,
'bypass_spam' => ($result['bypass_spam'] == '1' ? 0 : 1)
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id,
]);
} elseif ($action == 'togglegreylist' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
try {
Emails::getLocal($userinfo, [
'id' => $id,
'policy_greylist' => ($result['policy_greylist'] == '1' ? 0 : 1)
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id,
]);
} elseif ($action == 'togglecatchall' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [

View File

@ -94,6 +94,10 @@ CREATE TABLE `mail_virtual` (
`popaccountid` int(11) NOT NULL default '0',
`iscatchall` tinyint(1) unsigned NOT NULL default '0',
`description` varchar(255) NOT NULL DEFAULT '',
`spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0,
`spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0,
`bypass_spam` tinyint(1) NOT NULL default '0',
`policy_greylist` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`id`),
KEY `email` (`email`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
@ -380,22 +384,18 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('logger', 'logfile', ''),
('logger', 'logtypes', 'syslog,mysql'),
('logger', 'severity', '1'),
('dkim', 'use_dkim', '0'),
('dkim', 'dkim_prefix', '/etc/postfix/dkim/'),
('dkim', 'dkim_domains', 'domains'),
('dkim', 'dkim_dkimkeys', 'dkim-keys.conf'),
('dkim', 'dkimrestart_command', 'service dkim-filter restart'),
('dkim', 'privkeysuffix', '.priv'),
('antispam', 'activated', '0'),
('antispam', 'config_file', '/etc/rspamd/local.d/froxlor_settings.conf'),
('antispam', 'reload_command', 'service rspamd restart'),
('antispam', 'dkim_keylength', '1024'),
('admin', 'show_news_feed', '0'),
('admin', 'show_version_login', '0'),
('admin', 'show_version_footer', '0'),
('caa', 'caa_entry', ''),
('spf', 'use_spf', '0'),
('spf', 'spf_entry', 'v=spf1 a mx -all'),
('dkim', 'dkim_algorithm', 'all'),
('dkim', 'dkim_keylength', '1024'),
('dkim', 'dkim_servicetype', '0'),
('dkim', 'dkim_notes', ''),
('dmarc', 'use_dmarc', '0'),
('dmarc', 'dmarc_entry', 'v=DMARC1; p=none;'),
('defaultwebsrverrhandler', 'enabled', '0'),
('defaultwebsrverrhandler', 'err401', ''),
('defaultwebsrverrhandler', 'err403', ''),
@ -726,6 +726,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.0-dev1'),
('panel', 'db_version', '202312230');
('panel', 'version', '2.1.4'),
('panel', 'db_version', '202312120');

View File

@ -99,7 +99,6 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
Update::lastStepStatus(0);
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"install/lib",
"install/lng",
@ -121,30 +120,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
"lng/swedish.lng.php",
"scripts",
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>');
}
}
Update::cleanOldFiles($to_clean);
Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;

View File

@ -149,7 +149,6 @@ if (Froxlor::isFroxlorVersion('2.1.0-rc2')) {
}
if (Froxlor::isDatabaseVersion('202311260')) {
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"install/updates/froxlor/update_2.x.inc.php",
"install/updates/preconfig/preconfig_2.x.inc.php",
@ -175,33 +174,8 @@ if (Froxlor::isDatabaseVersion('202311260')) {
"templates/Froxlor/user/change_theme.html.twig",
"tests/Backup/CustomerBackupsTest.php"
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(
1,
'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
);
}
}
Update::cleanOldFiles($to_clean);
Froxlor::updateToDbVersion('202312050');
}
@ -216,7 +190,6 @@ if (Froxlor::isFroxlorVersion('2.1.0')) {
}
if (Froxlor::isDatabaseVersion('202312050')) {
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"lib/configfiles/centos7.xml",
"lib/configfiles/centos8.xml",
@ -225,33 +198,8 @@ if (Froxlor::isDatabaseVersion('202312050')) {
"lib/configfiles/buster.xml",
"lib/configfiles/bionic.xml",
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(
1,
'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
);
}
}
Update::cleanOldFiles($to_clean);
Froxlor::updateToDbVersion('202312100');
}

View File

@ -0,0 +1,68 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
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');
}

View File

@ -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 = '<strong>Enable antispam (recommended)</strong>&nbsp;';
$return['antispam_activated'] = [
'type' => 'checkbox',
'value' => 1,
'checked' => 0,
'label' => $question,
'prior_infotext' => $description
];
}
$preconfig['fields'] = $return;
return $preconfig;

View File

@ -55,6 +55,7 @@ if (Froxlor::isFroxlor()) {
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_0.10.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.0.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.1.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.2.inc.php'));
// Check Froxlor - database integrity (only happens after all updates are done, so we know the db-layout is okay)
Update::showUpdateStep("Checking database integrity");

View File

@ -1407,7 +1407,7 @@ class Domains extends ApiCommand implements ResourceEntity
$zonefile = $result['zonefile'];
}
if (Settings::Get('dkim.use_dkim') != '1') {
if (Settings::Get('antispam.activated') != '1') {
$dkim = $result['dkim'];
}

View File

@ -28,10 +28,12 @@ namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Idna\IdnaWrapper;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
@ -49,6 +51,14 @@ class Emails extends ApiCommand implements ResourceEntity
* name of the address before @
* @param string $domain
* domain-name for the email-address
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam
* optional, disable spam-filter entirely, default: no
* @param boolean $policy_greylist
* optional, enable grey-listing, default: yes
* @param boolean $iscatchall
* optional, make this address a catchall address, default: no
* @param int $customerid
@ -74,6 +84,10 @@ class Emails extends ApiCommand implements ResourceEntity
$domain = $this->getParam('domain');
// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
$spam_kill_level = $this->getParam('spam_kill_level', true, '14.0');
$bypass_spam = $this->getBoolParam('bypass_spam', true, 0);
$policy_greylist = $this->getBoolParam('policy_greylist', true, 1);
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
$description = $this->getParam('description', true, '');
@ -140,11 +154,19 @@ class Emails extends ApiCommand implements ResourceEntity
}
}
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$stmt = Database::prepare("
INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` SET
`customerid` = :cid,
`email` = :email,
`email_full` = :email_full,
`spam_tag_level` = :spam_tag_level,
`spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist,
`iscatchall` = :iscatchall,
`domainid` = :domainid,
`description` = :description
@ -153,6 +175,10 @@ class Emails extends ApiCommand implements ResourceEntity
"cid" => $customer['customerid'],
"email" => $email,
"email_full" => $email_full,
"spam_tag_level" => $spam_tag_level,
"spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist,
"iscatchall" => $iscatchall,
"domainid" => $domain_check['id'],
"description" => $description
@ -162,6 +188,7 @@ class Emails extends ApiCommand implements ResourceEntity
// update customer usage
Customers::increaseUsage($customer['customerid'], 'emails_used');
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'");
$result = $this->apiCall('Emails.get', [
@ -194,7 +221,7 @@ class Emails extends ApiCommand implements ResourceEntity
$customer_ids = $this->getAllowedCustomerIds('email');
$params['idea'] = ($id <= 0 ? $emailaddr : $id);
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
$result_stmt = Database::prepare("SELECT v.*, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
@ -220,6 +247,14 @@ class Emails extends ApiCommand implements ResourceEntity
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
* optional, required when called as admin (if $customerid is not specified)
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam
* optional, disable spam-filter entirely, default: no
* @param boolean $policy_greylist
* optional, enable grey-listing, default: yes
* @param boolean $iscatchall
* optional
* @param string $description
@ -255,6 +290,10 @@ class Emails extends ApiCommand implements ResourceEntity
$id = $result['id'];
// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']);
$spam_kill_level = $this->getParam('spam_kill_level', true, $result['spam_kill_level']);
$bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
$policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
$description = $this->getParam('description', true, $result['description']);
@ -284,19 +323,34 @@ class Emails extends ApiCommand implements ResourceEntity
$email = $result['email_full'];
}
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "`
SET `email` = :email , `iscatchall` = :caflag, `description` = :description
UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET
`email` = :email ,
`spam_tag_level` = :spam_tag_level,
`spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist,
`iscatchall` = :caflag,
`description` = :description
WHERE `customerid`= :cid AND `id`= :id
");
$params = [
"email" => $email,
"spam_tag_level" => $spam_tag_level,
"spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist,
"caflag" => $iscatchall,
"description" => $description,
"cid" => $customer['customerid'],
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
@ -334,7 +388,7 @@ class Emails extends ApiCommand implements ResourceEntity
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
SELECT m.*, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` m
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`)
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)

View File

@ -43,9 +43,7 @@ final class ConfigServices extends CliCommand
{
private $yes_to_all_supported = [
'bookworm',
'bionic',
'bullseye',
'buster',
'focal',
'jammy',
];
@ -172,8 +170,8 @@ final class ConfigServices extends CliCommand
$distributions_select_data = [];
//set default os.
$os_dist = ['ID' => 'bullseye'];
$os_version = ['0' => '11'];
$os_dist = ['ID' => 'bookworm'];
$os_version = ['0' => '12'];
$os_default = $os_dist['ID'];
//read os-release

View File

@ -27,10 +27,13 @@ namespace Froxlor\Cli;
use Exception;
use Froxlor\Config\ConfigParser;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\Install\Install;
use Froxlor\Install\Install\Core;
use Froxlor\Settings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -50,7 +53,8 @@ final class InstallCommand extends Command
$this->setDescription('Installation process to use instead of web-ui');
$this->addArgument('input-file', InputArgument::OPTIONAL, 'Optional JSON array file to use for unattended installations');
$this->addOption('print-example-file', 'p', InputOption::VALUE_NONE, 'Outputs an example JSON content to be used with the input file parameter')
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process')
->addOption('show-sysinfo', 's', InputOption::VALUE_NONE, 'Outputs system information about your froxlor installation');
}
/**
@ -72,6 +76,15 @@ final class InstallCommand extends Command
return self::INVALID;
}
if ($input->getOption('show-sysinfo') !== false) {
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
$output->writeln("<error>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 @@ final class InstallCommand extends Command
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(['<br>', '<br/>', '<br />'], [$nl_char, $nl_char, $nl_char], $text);

View File

@ -52,7 +52,7 @@ final class MasterCron extends CliCommand
$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 @@ final class MasterCron extends CliCommand
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 @@ final class MasterCron extends CliCommand
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 {

View File

@ -117,85 +117,6 @@ abstract class DnsBase
}
}
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("

View File

@ -0,0 +1,216 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\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');
}
}
}

View File

@ -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 @@ use PDO;
class TasksCron extends FroxlorCron
{
/**
* @throws Exception
*/
public static function run()
{
/**
@ -98,6 +103,11 @@ class TasksCron extends FroxlorCron
* 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 @@ class TasksCron extends FroxlorCron
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 @@ class TasksCron extends FroxlorCron
}
}
}
/**
* @throws Exception
*/
private static function rebuildAntiSpamConfigs()
{
$antispam = new Rspamd(FroxlorLogger::getInstanceOf());
$antispam->writeConfigs();
}
}

View File

@ -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
*/

View File

@ -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

View File

@ -120,16 +120,14 @@ class Dns
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 @@ class Dns
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 @@ class Dns
$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 @@ class Dns
// 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 @@ class Dns
// 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 @@ class Dns
// 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 @@ class Dns
$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 @@ class Dns
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 @@ class Dns
// 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 @@ class Dns
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 @@ class Dns
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 @@ class Dns
{
$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;
}

77
lib/Froxlor/ErrorBag.php Normal file
View File

@ -0,0 +1,77 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor;
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 [];
}
}

View File

@ -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.

View File

@ -674,6 +674,7 @@ class Core
'http' => $this->validatedData['webserver'],
'smtp' => 'postfix_dovecot',
'mail' => 'dovecot_postfix2',
'antispam' => 'rspamd',
'ftp' => 'proftpd',
'system' => $system_params
];

View File

@ -25,6 +25,7 @@
namespace Froxlor\Install;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@ -85,7 +86,7 @@ class Update
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 @@ class Update
{
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:<br><pre>' . $del_list . '</pre>'
);
}
}
}
}

View File

@ -34,27 +34,6 @@ use voku\helper\AntiXSS;
class PhpHelper
{
private static $sort_key = 'id';
private static $sort_type = SORT_STRING;
/**
* sort an array by either natural or string sort and a given index where the value for comparison is found
*
* @param array $list
* @param string $key
*
* @return bool
*/
public static function sortListBy(array &$list, string $key = 'id'): bool
{
self::$sort_type = Settings::Get('panel.natsorting') == 1 ? SORT_NATURAL : SORT_STRING;
self::$sort_key = $key;
return usort($list, [
'self',
'sortListByGivenKey'
]);
}
/**
* Wrapper around htmlentities to handle arrays, with the advantage that you
* can select which fields should be handled by htmlentities
@ -101,35 +80,6 @@ class PhpHelper
});
}
/**
* Replaces Strings in an array, with the advantage that you
* can select which fields should be str_replace'd
*
* @param string|array $search String or array of strings to search for
* @param string|array $replace String or array to replace with
* @param string|array $subject String or array The subject array
* @param string|array $fields string The fields which should be checked for, separated by spaces
*
* @return string|array The str_replace'd array
*/
public static function strReplaceArray($search, $replace, $subject, $fields = '')
{
if (is_array($subject)) {
if (!is_array($fields)) {
$fields = self::arrayTrim(explode(' ', $fields));
}
foreach ($subject as $field => $value) {
if ((!is_array($fields) || empty($fields)) || (in_array($field, $fields))) {
$subject[$field] = str_replace($search, $replace, $value);
}
}
} else {
$subject = str_replace($search, $replace, $subject);
}
return $subject;
}
/**
* froxlor php error handler
*
@ -170,9 +120,8 @@ class PhpHelper
$err_display .= '</pre></p>';
// end later
$err_display .= '</div>';
// check for more existing errors
$errors = isset(UI::twig()->getGlobals()['global_errors']) ? UI::twig()->getGlobals()['global_errors'] : "";
UI::twig()->addGlobal('global_errors', $errors . $err_display);
// set errors to session
ErrorBag::addError($err_display);
// return true to ignore php standard error-handler
return true;
}
@ -338,7 +287,8 @@ class PhpHelper
?string $max = '',
string $system = 'si',
string $retstring = '%01.2f %s'
): string {
): string
{
// Pick units
$systems = [
'si' => [
@ -421,7 +371,8 @@ class PhpHelper
array $haystack,
array &$keys = [],
string $currentKey = ''
): bool {
): bool
{
foreach ($haystack as $key => $value) {
$pathkey = empty($currentKey) ? $key : $currentKey . '.' . $key;
if (is_array($value)) {
@ -476,19 +427,6 @@ class PhpHelper
}
}
/**
* @param array $a
* @param array $b
* @return int
*/
private static function sortListByGivenKey(array $a, array $b): int
{
if (self::$sort_type == SORT_NATURAL) {
return strnatcasecmp($a[self::$sort_key], $b[self::$sort_key]);
}
return strcasecmp($a[self::$sort_key], $b[self::$sort_key]);
}
/**
* Generate php file from array.
*

View File

@ -25,6 +25,7 @@
namespace Froxlor;
use Exception;
use Froxlor\Database\Database;
use PDO;
use PDOStatement;
@ -131,6 +132,7 @@ class Settings
self::$conf = [
'enable_webupdate' => false,
'disable_otp_security_check' => false,
'display_php_errors' => false,
];
$configfile = Froxlor::getInstallDir() . '/lib/config.inc.php';
@ -330,7 +332,7 @@ class Settings
}
}
public static function getAll() : array
public static function getAll(): array
{
self::init();
return self::$data;
@ -338,17 +340,14 @@ class Settings
/**
* get value from config by identifier
* @throws Exception
*/
public static function Config(string $config)
{
self::init();
$sstr = explode(".", $config);
$result = self::$conf;
foreach ($sstr as $key) {
$result = $result[$key] ?? null;
if (empty($result)) {
break;
}
$result = self::$conf[$config] ?? null;
if (is_null($result)) {
throw new Exception('Unknown local config name "' . $config . '"');
}
return $result;
}

View File

@ -225,6 +225,17 @@ class Store
return $returnvalue;
}
public static function storeSettingFieldInsertAntispamTask($fieldname, $fielddata, $newfieldvalue)
{
// first save the setting itself
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
if ($returnvalue !== false) {
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
}
return $returnvalue;
}
public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue)
{
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);

View File

@ -134,11 +134,15 @@ class Cronjob
INSERT INTO `" . TABLE_PANEL_TASKS . "` SET `type` = :type, `data` = :data
");
if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) {
if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::REBUILD_RSPAMD || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) {
// 4 = bind -> if bind disabled -> no task
if ($type == TaskId::REBUILD_DNS && Settings::Get('system.bind_enable') == '0') {
return;
}
// 9 = rspamd -> if antispam disabled -> no task
if ($type == TaskId::REBUILD_RSPAMD && Settings::Get('antispam.activated') == '0') {
return;
}
// 10 = quota -> if quota disabled -> no task
if ($type == TaskId::CREATE_QUOTA && Settings::Get('system.diskquota_enabled') == '0') {
return;

View File

@ -42,7 +42,7 @@ class Traffic
{
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
self::getParamsByRange($range, ['customer_traffic' => true])));
if ($userinfo['adminsession'] == 1) {
if (($userinfo['adminsession'] ?? 0) == 1) {
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
}
$trafficCollection = $trafficCollectionObj->get();
@ -58,8 +58,17 @@ class Traffic
$mail = $item['mail'];
$total = $http + $ftp + $mail;
if (empty($users[$item['customerid']])) {
$users[$item['customerid']] = [
'total' => 0.00,
'http' => 0.00,
'ftp' => 0.00,
'mail' => 0.00,
];
}
// per user total
if ($userinfo['adminsession'] == 1) {
if (($userinfo['adminsession'] ?? 0) == 1) {
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
}
$users[$item['customerid']]['total'] += $total;
@ -67,6 +76,30 @@ class Traffic
$users[$item['customerid']]['ftp'] += $ftp;
$users[$item['customerid']]['mail'] += $mail;
if (!$overview) {
if (empty($years[$item['year']])) {
$years[$item['year']] = [
'total' => 0.00,
'http' => 0.00,
'ftp' => 0.00,
'mail' => 0.00,
];
}
if (empty($months[$item['month'] . '/' . $item['year']])) {
$months[$item['month'] . '/' . $item['year']] = [
'total' => 0.00,
'http' => 0.00,
'ftp' => 0.00,
'mail' => 0.00,
];
}
if (empty($days[$item['day'] . '.' . $item['month'] . '.' . $item['year']])) {
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']] = [
'total' => 0.00,
'http' => 0.00,
'ftp' => 0.00,
'mail' => 0.00,
];
}
// per year
$years[$item['year']]['total'] += $total;
$years[$item['year']]['http'] += $http;
@ -86,7 +119,12 @@ class Traffic
}
// calculate overview for given range from users
$metrics = [];
$metrics = [
'total' => 0.00,
'http' => 0.00,
'ftp' => 0.00,
'mail' => 0.00,
];
foreach ($users as $user) {
$metrics['total'] += $user['total'];
$metrics['http'] += $user['http'];

View File

@ -25,6 +25,7 @@
namespace Froxlor\UI\Callbacks;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain as DDomain;
use Froxlor\FileDir;
@ -33,23 +34,36 @@ use Froxlor\UI\Panel\UI;
class Domain
{
public static function domainLink(array $attributes)
public static function domainEditLink(array $attributes): array
{
return '<a href="https://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
$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 = '<a href="https://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
if ((int)UI::getCurrentUser()['adminsession'] == 1 && $attributes['fields']['customerid']) {
$result .= ' (<a href="' . $linker->getLink([
'section' => 'customers',
'page' => 'customers',
'action' => 'su',
'sort' => $attributes['fields']['loginname'],
'id' => $attributes['fields']['customerid'],
]) . '">' . $attributes['fields']['loginname'] . '</a>)';
'section' => 'customers',
'page' => 'customers',
'action' => 'su',
'sort' => $attributes['fields']['loginname'],
'id' => $attributes['fields']['customerid'],
]) . '">' . $attributes['fields']['loginname'] . '</a>)';
}
return $result;
}
@ -108,12 +122,12 @@ class Domain
public static function canEdit(array $attributes): bool
{
return (bool)($attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated']);
return $attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated'];
}
public static function canViewLogs(array $attributes): bool
{
if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) {
if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) {
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
return true;
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
@ -155,17 +169,19 @@ class Domain
public static function hasLetsEncryptActivated(array $attributes): bool
{
return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0);
return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)));
}
/**
* @throws \Exception
*/
public static function canEditSSL(array $attributes): bool
{
if (
Settings::Get('system.use_ssl') == '1'
if (Settings::Get('system.use_ssl') == '1'
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
&& (int)$attributes['fields']['caneditdomain'] == 1
&& (int)$attributes['fields']['letsencrypt'] == 0
&& (int)$attributes['fields']['email_only'] == 0
&& (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))
&& !$attributes['fields']['deactivated']
) {
return true;
@ -196,15 +212,15 @@ class Domain
],
];
// specified certificate for domain
if ($attributes['fields']['domain_hascert'] == 1) {
// specified certificate for domain
$result['icon'] .= ' text-success';
} // shared certificates (e.g. subdomain of domain where certificate is specified)
elseif ($attributes['fields']['domain_hascert'] == 2) {
} elseif ($attributes['fields']['domain_hascert'] == 2) {
// shared certificates (e.g. subdomain of domain where certificate is specified)
$result['icon'] .= ' text-warning';
$result['title'] .= "\n" . lng('panel.ssleditor_infoshared');
} // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings)
elseif ($attributes['fields']['domain_hascert'] == 0) {
} elseif ($attributes['fields']['domain_hascert'] == 0) {
// no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings)
$result['icon'] .= ' text-danger';
$result['title'] .= "\n" . lng('panel.ssleditor_infoglobal');
}
@ -216,7 +232,7 @@ class Domain
public static function listIPs(array $attributes): string
{
if (isset($attributes['fields']['ipsandports']) && !empty($attributes['fields']['ipsandports'])) {
if (!empty($attributes['fields']['ipsandports'])) {
$iplist = "";
foreach ($attributes['fields']['ipsandports'] as $ipport) {
$iplist .= $ipport['ip'] . ':' . $ipport['port'] . '<br>';
@ -226,6 +242,9 @@ class Domain
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 @@ class Domain
if ((int)UI::getCurrentUser()['adminsession'] == 1) {
$linker = UI::getLinker();
$result = '<a href="' . $linker->getLink([
'section' => 'phpsettings',
'page' => 'overview',
'searchfield' => 'c.id',
'searchtext' => $attributes['data'],
]) . '">' . $phpconfig['description'] . '</a>';
'section' => 'phpsettings',
'page' => 'overview',
'searchfield' => 'c.id',
'searchtext' => $attributes['data'],
]) . '">' . $phpconfig['description'] . '</a>';
} else {
$result = $phpconfig['description'];
}

View File

@ -25,6 +25,7 @@
namespace Froxlor\UI\Callbacks;
use Froxlor\CurrentUser;
use Froxlor\Settings;
class Style
@ -68,7 +69,7 @@ class Style
$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;
}

View File

@ -90,9 +90,10 @@ class Text
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))
];

View File

@ -217,7 +217,8 @@ class Form
{
$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']);
}
}

View File

@ -139,7 +139,7 @@ class Response
exit;
}
public static function dynamicError($message)
public static function dynamicError($message, bool $nosession = false)
{
$_SESSION['requestData'] = $_POST;
$link_ref = '';
@ -147,7 +147,8 @@ class Response
$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'),

View File

@ -23,4 +23,12 @@ return [
* 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,
];

View File

@ -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 {
</include>
</daemon>
</service>
<!-- Antispam services -->
<service type="antispam" title="Antispam">
<!-- general RSpamd commands -->
<general>
<commands index="1">
<command><![CDATA[mkdir -p /etc/apt/keyrings]]></command>
<command><![CDATA[wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/rspamd.gpg > /dev/null]]></command>
<command><![CDATA[echo "deb [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ bookworm main" > /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[echo "deb-src [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ bookworm main" >> /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[apt-get update]]></command>
</commands>
<installs index="1">
<install><![CDATA[DEBIAN_FRONTEND=noninteractive apt-get -yq --no-install-recommends install rspamd]]></install>
</installs>
<commands index="2">
<command><![CDATA[mkdir -p /etc/rspamd/local.d/]]></command>
<command><![CDATA[mkdir -p /etc/rspamd/override.d/]]></command>
<command><![CDATA[mkdir -p mkdir /var/lib/rspamd/dkim/]]></command>
</commands>
<files index="1">
<file name="/etc/rspamd/local.d/actions.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
# Set rewrite subject to this value (%s is replaced by the original subject)
subject = "***SPAM*** %s"
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/arc.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
try_fallback = true;
### Enable DKIM signing for alias sender addresses
allow_username_mismatch = true;
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/milter_headers.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
use = ["x-spamd-bar", "x-spam-level", "authentication-results"];
authenticated_headers = ["authentication-results"];
extended_spam_headers = true
skip_local = false
skip_authenticated = false
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/replies.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
## If a user has replied to an email, dont mark other emails in the same thread as spam
action = "no action";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/settings.conf"
chown="root:root" chmod="0644" backup="true">
<content><![CDATA[
## Feel free to include your own settings or adjustments here, for example:
#whitelist {
# priority = low;
# rcpt = "postmaster@example.com";
# want_spam = yes;
#}
## Include froxlor generated settings
.include(try=true,priority=1,duplicate=merge) "{{settings.antispam.config_file}}"
]]>
</content>
</file>
</files>
<commands index="3">
<command><![CDATA[cp /etc/rspamd/local.d/arc.conf /etc/rspamd/local.d/dkim_signing.conf]]></command>
<command><![CDATA[postconf -e "milter_protocol = 6"]]></command>
<command><![CDATA[postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"]]></command>
<command><![CDATA[postconf -e "milter_default_action = accept"]]></command>
<command><![CDATA[postconf -e "smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[chown -R _rspamd:_rspamd /var/lib/rspamd/dkim]]></command>
<command><![CDATA[chmod 440 /var/lib/rspamd/dkim/*]]></command>
<command><![CDATA[service rspamd restart]]></command>
<command><![CDATA[service postfix restart]]></command>
</commands>
</general>
<!-- rspamd -->
<daemon name="rspamd" title="Rspamd" default="true">
<include>//service[@type='antispam']/general/commands[@index=1]
</include>
<include>//service[@type='antispam']/general/installs[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=2]
</include>
<include>//service[@type='antispam']/general/files[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=3]
</include>
</daemon>
</service>
<!-- FTP services -->
<service type="ftp" title="{{lng.admin.configfiles.ftp}}">
<!-- Proftpd -->

View File

@ -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 {
</include>
</daemon>
</service>
<!-- Antispam services -->
<service type="antispam" title="Antispam">
<!-- general RSpamd commands -->
<general>
<commands index="1">
<command><![CDATA[mkdir -p /etc/apt/keyrings]]></command>
<command><![CDATA[wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/rspamd.gpg > /dev/null]]></command>
<command><![CDATA[echo "deb [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ bullseye main" > /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[echo "deb-src [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ bullseye main" >> /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[apt-get update]]></command>
</commands>
<installs index="1">
<install><![CDATA[DEBIAN_FRONTEND=noninteractive apt-get -yq --no-install-recommends install rspamd]]></install>
</installs>
<commands index="2">
<command><![CDATA[mkdir -p /etc/rspamd/local.d/]]></command>
<command><![CDATA[mkdir -p /etc/rspamd/override.d/]]></command>
<command><![CDATA[mkdir -p mkdir /var/lib/rspamd/dkim/]]></command>
</commands>
<files index="1">
<file name="/etc/rspamd/local.d/actions.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
# Set rewrite subject to this value (%s is replaced by the original subject)
subject = "***SPAM*** %s"
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/arc.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
try_fallback = true;
### Enable DKIM signing for alias sender addresses
allow_username_mismatch = true;
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/milter_headers.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
use = ["x-spamd-bar", "x-spam-level", "authentication-results"];
authenticated_headers = ["authentication-results"];
extended_spam_headers = true
skip_local = false
skip_authenticated = false
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/replies.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
## If a user has replied to an email, dont mark other emails in the same thread as spam
action = "no action";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/settings.conf"
chown="root:root" chmod="0644" backup="true">
<content><![CDATA[
## Feel free to include your own settings or adjustments here, for example:
#whitelist {
# priority = low;
# rcpt = "postmaster@example.com";
# want_spam = yes;
#}
## Include froxlor generated settings
.include(try=true,priority=1,duplicate=merge) "{{settings.antispam.config_file}}"
]]>
</content>
</file>
</files>
<commands index="3">
<command><![CDATA[cp /etc/rspamd/local.d/arc.conf /etc/rspamd/local.d/dkim_signing.conf]]></command>
<command><![CDATA[postconf -e "milter_protocol = 6"]]></command>
<command><![CDATA[postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"]]></command>
<command><![CDATA[postconf -e "milter_default_action = accept"]]></command>
<command><![CDATA[postconf -e "smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[chown -R _rspamd:_rspamd /var/lib/rspamd/dkim]]></command>
<command><![CDATA[chmod 440 /var/lib/rspamd/dkim/*]]></command>
<command><![CDATA[service rspamd restart]]></command>
</commands>
</general>
<!-- rspamd -->
<daemon name="rspamd" title="Rspamd" default="true">
<include>//service[@type='antispam']/general/commands[@index=1]
</include>
<include>//service[@type='antispam']/general/installs[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=2]
</include>
<include>//service[@type='antispam']/general/files[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=3]
</include>
</daemon>
</service>
<!-- FTP services -->
<service type="ftp" title="{{lng.admin.configfiles.ftp}}">
<!-- Proftpd -->

View File

@ -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 = <SERVERNAME>
#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 {
</include>
</daemon>
</service>
<!-- Antispam services -->
<service type="antispam" title="Antispam">
<!-- general RSpamd commands -->
<general>
<commands index="1">
<command><![CDATA[mkdir -p /etc/apt/keyrings]]></command>
<command><![CDATA[wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/rspamd.gpg > /dev/null]]></command>
<command><![CDATA[echo "deb [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ focal main" > /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[echo "deb-src [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ focal main" >> /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[apt-get update]]></command>
</commands>
<installs index="1">
<install><![CDATA[DEBIAN_FRONTEND=noninteractive apt-get -yq --no-install-recommends install rspamd]]></install>
</installs>
<commands index="2">
<command><![CDATA[mkdir -p /etc/rspamd/local.d/]]></command>
<command><![CDATA[mkdir -p /etc/rspamd/override.d/]]></command>
<command><![CDATA[mkdir -p mkdir /var/lib/rspamd/dkim/]]></command>
</commands>
<files index="1">
<file name="/etc/rspamd/local.d/actions.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
# Set rewrite subject to this value (%s is replaced by the original subject)
subject = "***SPAM*** %s"
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/arc.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
try_fallback = true;
### Enable DKIM signing for alias sender addresses
allow_username_mismatch = true;
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/milter_headers.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
use = ["x-spamd-bar", "x-spam-level", "authentication-results"];
authenticated_headers = ["authentication-results"];
extended_spam_headers = true
skip_local = false
skip_authenticated = false
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/replies.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
## If a user has replied to an email, dont mark other emails in the same thread as spam
action = "no action";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/settings.conf"
chown="root:root" chmod="0644" backup="true">
<content><![CDATA[
## Feel free to include your own settings or adjustments here, for example:
#whitelist {
# priority = low;
# rcpt = "postmaster@example.com";
# want_spam = yes;
#}
## Include froxlor generated settings
.include(try=true,priority=1,duplicate=merge) "{{settings.antispam.config_file}}"
]]>
</content>
</file>
</files>
<commands index="3">
<command><![CDATA[cp /etc/rspamd/local.d/arc.conf /etc/rspamd/local.d/dkim_signing.conf]]></command>
<command><![CDATA[postconf -e "milter_protocol = 6"]]></command>
<command><![CDATA[postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"]]></command>
<command><![CDATA[postconf -e "milter_default_action = accept"]]></command>
<command><![CDATA[postconf -e "smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[chown -R _rspamd:_rspamd /var/lib/rspamd/dkim]]></command>
<command><![CDATA[chmod 440 /var/lib/rspamd/dkim/*]]></command>
<command><![CDATA[service rspamd restart]]></command>
</commands>
</general>
<!-- rspamd -->
<daemon name="rspamd" title="Rspamd" default="true">
<include>//service[@type='antispam']/general/commands[@index=1]
</include>
<include>//service[@type='antispam']/general/installs[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=2]
</include>
<include>//service[@type='antispam']/general/files[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=3]
</include>
</daemon>
</service>
<!-- FTP services -->
<service type="ftp" title="{{lng.admin.configfiles.ftp}}">
<!-- Proftpd -->

View File

@ -1727,12 +1727,9 @@ compatibility_level = 2
## General Postfix configuration
# should be the default domain from your provider eg. "server100.provider.tld"
mydomain = <SERVERNAME>
# 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 {
<command><![CDATA[/etc/init.d/dovecot restart]]></command>
</daemon>
</service>
<!-- Antispam services -->
<service type="antispam" title="Antispam">
<!-- general RSpamd commands -->
<general>
<installs index="1">
<install><![CDATA[emerge mail-filter/rspamd]]></install>
</installs>
<commands index="2">
<command><![CDATA[mkdir -p /etc/rspamd/local.d/]]></command>
<command><![CDATA[mkdir -p /etc/rspamd/override.d/]]></command>
<command><![CDATA[mkdir -p mkdir /var/lib/rspamd/dkim/]]></command>
</commands>
<files index="1">
<file name="/etc/rspamd/local.d/actions.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
# Set rewrite subject to this value (%s is replaced by the original subject)
subject = "***SPAM*** %s"
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/arc.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
try_fallback = true;
### Enable DKIM signing for alias sender addresses
allow_username_mismatch = true;
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/milter_headers.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
use = ["x-spamd-bar", "x-spam-level", "authentication-results"];
authenticated_headers = ["authentication-results"];
extended_spam_headers = true
skip_local = false
skip_authenticated = false
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/replies.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
## If a user has replied to an email, dont mark other emails in the same thread as spam
action = "no action";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/settings.conf"
chown="root:root" chmod="0644" backup="true">
<content><![CDATA[
## Feel free to include your own settings or adjustments here, for example:
#whitelist {
# priority = low;
# rcpt = "postmaster@example.com";
# want_spam = yes;
#}
## Include froxlor generated settings
.include(try=true,priority=1,duplicate=merge) "{{settings.antispam.config_file}}"
]]>
</content>
</file>
</files>
<commands index="3">
<command><![CDATA[cp /etc/rspamd/local.d/arc.conf /etc/rspamd/local.d/dkim_signing.conf]]></command>
<command><![CDATA[postconf -e "milter_protocol = 6"]]></command>
<command><![CDATA[postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"]]></command>
<command><![CDATA[postconf -e "milter_default_action = accept"]]></command>
<command><![CDATA[postconf -e "smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[chown -R _rspamd:_rspamd /var/lib/rspamd/dkim]]></command>
<command><![CDATA[chmod 440 /var/lib/rspamd/dkim/*]]></command>
<command><![CDATA[rc-update add rspamd default]]></command>
<command><![CDATA[/etc/init.d/rspamd restart]]></command>
</commands>
</general>
<!-- rspamd -->
<daemon name="rspamd" title="Rspamd" default="true">
<include>//service[@type='antispam']/general/installs[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=2]
</include>
<include>//service[@type='antispam']/general/files[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=3]
</include>
</daemon>
</service>
<!-- FTP services -->
<service type="ftp" title="{{lng.admin.configfiles.ftp}}">
<!-- Proftpd -->

View File

@ -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 = <SERVERNAME>
#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 {
</include>
</daemon>
</service>
<!-- Antispam services -->
<service type="antispam" title="Antispam">
<!-- general RSpamd commands -->
<general>
<commands index="1">
<command><![CDATA[mkdir -p /etc/apt/keyrings]]></command>
<command><![CDATA[wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/rspamd.gpg > /dev/null]]></command>
<command><![CDATA[echo "deb [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ jammy main" > /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[echo "deb-src [signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ jammy main" >> /etc/apt/sources.list.d/rspamd.list]]></command>
<command><![CDATA[apt-get update]]></command>
</commands>
<installs index="1">
<install><![CDATA[DEBIAN_FRONTEND=noninteractive apt-get -yq --no-install-recommends install rspamd]]></install>
</installs>
<commands index="2">
<command><![CDATA[mkdir -p /etc/rspamd/local.d/]]></command>
<command><![CDATA[mkdir -p /etc/rspamd/override.d/]]></command>
<command><![CDATA[mkdir -p mkdir /var/lib/rspamd/dkim/]]></command>
</commands>
<files index="1">
<file name="/etc/rspamd/local.d/actions.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
# Set rewrite subject to this value (%s is replaced by the original subject)
subject = "***SPAM*** %s"
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/arc.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
try_fallback = true;
### Enable DKIM signing for alias sender addresses
allow_username_mismatch = true;
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/milter_headers.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
use = ["x-spamd-bar", "x-spam-level", "authentication-results"];
authenticated_headers = ["authentication-results"];
extended_spam_headers = true
skip_local = false
skip_authenticated = false
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/replies.conf"
chown="root:root" chmod="0644">
<content><![CDATA[
## If a user has replied to an email, dont mark other emails in the same thread as spam
action = "no action";
]]>
</content>
</file>
<file name="/etc/rspamd/local.d/settings.conf"
chown="root:root" chmod="0644" backup="true">
<content><![CDATA[
## Feel free to include your own settings or adjustments here, for example:
#whitelist {
# priority = low;
# rcpt = "postmaster@example.com";
# want_spam = yes;
#}
## Include froxlor generated settings
.include(try=true,priority=1,duplicate=merge) "{{settings.antispam.config_file}}"
]]>
</content>
</file>
</files>
<commands index="3">
<command><![CDATA[cp /etc/rspamd/local.d/arc.conf /etc/rspamd/local.d/dkim_signing.conf]]></command>
<command><![CDATA[postconf -e "milter_protocol = 6"]]></command>
<command><![CDATA[postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"]]></command>
<command><![CDATA[postconf -e "milter_default_action = accept"]]></command>
<command><![CDATA[postconf -e "smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"]]></command>
<command><![CDATA[chown -R _rspamd:_rspamd /var/lib/rspamd/dkim]]></command>
<command><![CDATA[chmod 440 /var/lib/rspamd/dkim/*]]></command>
<command><![CDATA[service rspamd restart]]></command>
</commands>
</general>
<!-- rspamd -->
<daemon name="rspamd" title="Rspamd" default="true">
<include>//service[@type='antispam']/general/commands[@index=1]
</include>
<include>//service[@type='antispam']/general/installs[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=2]
</include>
<include>//service[@type='antispam']/general/files[@index=1]
</include>
<include>//service[@type='antispam']/general/commands[@index=3]
</include>
</daemon>
</service>
<!-- FTP services -->
<service type="ftp" title="{{lng.admin.configfiles.ftp}}">
<!-- Proftpd -->

View File

@ -111,7 +111,7 @@ return [
'selected' => 0
],
'dkim' => [
'visible' => Settings::Get('dkim.use_dkim') == '1',
'visible' => Settings::Get('antispam.activated') == '1',
'label' => 'DomainKeys',
'type' => 'checkbox',
'value' => '1',

View File

@ -129,7 +129,7 @@ return [
'selected' => $result['subcanemaildomain']
],
'dkim' => [
'visible' => Settings::Get('dkim.use_dkim') == '1',
'visible' => Settings::Get('antispam.activated') == '1',
'label' => 'DomainKeys',
'type' => 'checkbox',
'value' => '1',

View File

@ -116,6 +116,12 @@ return [
'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' => [

View File

@ -102,6 +102,44 @@ return [
]
]
],
'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 . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglebypass&amp;id=' . $result['id'],
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . 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 . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglegreylist&amp;id=' . $result['id'],
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
'classes' => 'btn btn-sm btn-secondary'
]
]
],
'mail_fwds' => [
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',
'type' => 'itemlist',
@ -119,7 +157,9 @@ return [
]
],
'buttons' => [
/* none */
[
'label' => lng('panel.save')
]
]
]
];

View File

@ -50,7 +50,7 @@ return [
'label' => lng('domains.domainname'),
'field' => 'domain_ace',
'isdefaultsearchfield' => true,
'callback' => [Domain::class, 'domainLink'],
'callback' => [Domain::class, 'domainEditLink'],
],
'ipsandports' => [
'label' => lng('admin.ipsandports.ipsandports'),

View File

@ -49,6 +49,24 @@ return [
'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',

View File

@ -629,45 +629,6 @@ return [
'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' => '<em>Nom de fitxer</em> del paràmetre DKIM Domains especificat a la configuració dkim-milter'
],
'dkim_dkimkeys' => [
'title' => 'Nom de l\'arxiu KeyList',
'description' => '<em>Nom de fitxer</em> 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)?<br/><em class="text-danger">Nota: DKIM només és compatible amb dkim-filter, no amb opendkim (de moment)</em>'
],
'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',

View File

@ -264,7 +264,7 @@ return [
'text' => 'Nachricht',
'sslsettings' => 'SSL-Einstellungen',
'specialsettings_replacements' => 'Die folgenden Variablen können verwendet werden:<br/><code>{DOMAIN}</code>, <code>{DOCROOT}</code>, <code>{CUSTOMER}</code>, <code>{IP}</code>, <code>{PORT}</code>, <code>{SCHEME}</code>, <code>{FPMSOCKET}</code> (wenn zutreffend)<br/>',
'dkimsettings' => 'DomainKey-Einstellungen',
'antispam_settings' => 'Antispam-Einstellungen',
'caneditphpsettings' => 'Kann PHP-bezogene Domaineinstellungen vornehmen?',
'allips' => 'Alle IP-Adressen',
'awstatssettings' => 'AWstats-Einstellungen',
@ -595,43 +595,38 @@ return [
'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).',
],
'use_dkim' => [
'title' => 'DKIM-Support aktivieren?',
'description' => 'Wollen Sie das Domain-Keys-System (DKIM) benutzen?<br/><em class="text-danger">Hinweis: Derzeit wird DKIM nur via dkim-filter unterstützt, nicht opendkim.</em>',
],
'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.',
],
'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.',
'title' => 'DKIM Schlüssel-Länge',
'description' => 'Achtung: Änderungen sind nur für neue Schlüssel gültig.<br/><br/>Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.',
],
'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.',
'spam_tag_level' => [
'title' => 'Spam Markierungs-Level',
'description' => 'Erforderliche Punktzahl zum Markieren einer E-Mail als Spam<br/>Standard: 7.0'
],
'spam_kill_level' => [
'title' => 'Spam Ignorier-Level',
'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail<br/>Standard: 14.0'
],
'bypass_spam' => [
'title' => 'Spamfilter umgehen',
'description' => 'Aktivieren, um den Spamfilter für diese Adresse zu umgehen/deaktivieren.<br/>Standard: Nein'
],
'policy_greylist' => [
'title' => 'Verwende greylisting',
'description' => 'Eingehende E-Mails mittels <a href="https://de.wikipedia.org/wiki/Greylisting" target="_blank">Greylisting</a> schützen.<br/>Standard: Ja'
],
],
'dns' => [
@ -2108,9 +2103,19 @@ Vielen Dank, Ihr Administrator',
],
],
'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',

View File

@ -268,7 +268,7 @@ return [
'text' => 'Message',
'sslsettings' => 'SSL settings',
'specialsettings_replacements' => 'You can use the following variables:<br/><code>{DOMAIN}</code>, <code>{DOCROOT}</code>, <code>{CUSTOMER}</code>, <code>{IP}</code>, <code>{PORT}</code>, <code>{SCHEME}</code>, <code>{FPMSOCKET}</code> (if applicable)<br/>',
'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 @@ return [
'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' => '<em>Filename</em> of the DKIM Domains parameter specified in the dkim-milter configuration',
],
'dkim_dkimkeys' => [
'title' => 'KeyList filename',
'description' => '<em>Filename</em> 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?<br/><em class="text-danger">Note: DKIM is only supported using dkim-filter, not opendkim (yet)</em>',
],
'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',
],
'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"',
'title' => 'DKIM Key-length',
'description' => 'Attention: Changes will only apply for new keys<br/><br/>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_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.',
'spam_tag_level' => [
'title' => 'Spam tag level',
'description' => 'Score that is required to mark an email as spam<br/>Default: 7.0'
],
'spam_kill_level' => [
'title' => 'Spam kill level',
'description' => 'Score that is required to discard an email entirely<br/>Default: 14.0'
],
'bypass_spam' => [
'title' => 'Bypass spamfilter',
'description' => 'Activate to bypass/disable spamfiltering for this address.<br/>Default: no'
],
'policy_greylist' => [
'title' => 'Use greylisting',
'description' => 'Incoming emails will be protected by <a href="https://en.wikipedia.org/wiki/Greylisting_(email)" target="_blank">greylisting</a>.<br/>Default: yes'
],
],
'dns' => [
@ -2236,9 +2231,19 @@ Yours sincerely, your administrator',
],
],
'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',

View File

@ -628,45 +628,6 @@ return [
'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' => '<em>Nombre de</em> archivo del parámetro DKIM Domains especificado en la configuración de dkim-milter'
],
'dkim_dkimkeys' => [
'title' => 'KeyList filename',
'description' => '<em>Nombre</em> 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)?<br/><em class="text-danger">Nota: DKIM sólo es compatible con dkim-filter, no con opendkim (todavía)</em>'
],
'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',

View File

@ -660,41 +660,6 @@ return [
'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' => '<em>Nome file</em> del parametro DKIM Domains specificata nella configurazione dkim-milter',
],
'dkim_dkimkeys' => [
'title' => 'Nome file del KeyList',
'description' => '<em>Nome file</em> 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)',

View File

@ -328,41 +328,6 @@ return [
'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' => '<em>Bestandsnaam</em> van het DKIM Domains-parameter zoals aangegeven in de configuratie van dkim-milter',
],
'dkim_dkimkeys' => [
'title' => 'KeyList filename',
'description' => '<em>Bestandsnaam</em> 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',

View File

@ -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;

View File

@ -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());
})
});
}

View File

@ -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) %}
<div>
<div class="float-end">
<button class="btn btn-outline-secondary copyClipboard" data-clipboard-source="{{ id }}" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .5rem;" title="Copy to clipboard"><i class="fa-solid fa-copy"></i></button>
</div>
<p class="text-break" id="{{ id }}">{{ field.value|raw }}</p>
</div>
{% endmacro %}
{% macro input(id, field) %}
{% if field.next_to is defined %}
<div class="input-group">

View File

@ -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 %}

View File

@ -67,7 +67,7 @@
<i class="fa-solid fa-gears me-1"></i>
{{ lng('admin.systemdetails') }}
<div class="float-end">
<button id="copySysInfo" class="btn btn-outline-secondary" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .5rem;" title="Copy to clipboard"><i class="fa-solid fa-copy"></i></button>
<button id="copySysInfo" class="btn btn-outline-secondary copyClipboard" data-clipboard-source="ccSysInfo" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .5rem;" title="Copy to clipboard"><i class="fa-solid fa-copy"></i></button>
</div>
<div id="ccSysInfo" class="d-none">
- Froxlor: {{ call_static('\\Froxlor\\Froxlor', 'getVersionString') }}

View File

@ -20,7 +20,7 @@
<nav class="navbar navbar-expand-md navbar-light p-0 {% if not block('heading') %}shadow-sm{% endif %}">
<div class="container-fluid gx-0">
<div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar" aria-controls="sidebar" aria-expanded="false" aria-label="Toggle sidebar">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar" aria-controls="sidebar" aria-expanded="false" aria-label="Toggle sidebar">
<span class="navbar-toggler-icon"></span>
</button>
</div>
@ -51,20 +51,30 @@
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarOpts">
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'profile'}) }}"><i class="fa-solid fa-user-gear"></i> {{ lng('panel.profile') }}</a>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'profile'}) }}">
<i class="fa-solid fa-user-gear"></i> {{ lng('panel.profile') }}
</a>
</li>
{% if get_setting('2fa.enabled') == 1 %}
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': '2fa'}) }}"><i class="fa-solid fa-shield"></i> {{ lng('2fa.2fa') }}</a>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': '2fa'}) }}">
<i class="fa-solid fa-shield"></i> {{ lng('2fa.2fa') }}
</a>
</li>
{% endif %}
{% if get_setting('api.enabled') == 1 and userinfo.api_allowed == 1 %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'apikeys'}) }}"><i class="fa-solid fa-key"></i> {{ lng('menue.main.apikeys') }}</a>
<hr class="dropdown-divider">
</li>
<li>
<a class="dropdown-item" href="https://docs.froxlor.org/latest/api-guide/" rel="external" target="_blank"><i class="fa-solid fa-circle-info"></i> {{ lng('menue.main.apihelp') }}</a>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'apikeys'}) }}">
<i class="fa-solid fa-key"></i> {{ lng('menue.main.apikeys') }}
</a>
</li>
<li>
<a class="dropdown-item" href="https://docs.froxlor.org/latest/api-guide/" rel="external" target="_blank">
<i class="fa-solid fa-circle-info"></i> {{ lng('menue.main.apihelp') }}
</a>
</li>
{% endif %}
</ul>
@ -105,6 +115,14 @@
</section>
{% endif %}
{% block errors %}
{% if call_static('\\Froxlor\\ErrorBag', 'hasErrors') %}
{% for error in call_static('\\Froxlor\\ErrorBag', 'getErrors') %}
{{ error|raw }}
{% endfor %}
{% endif %}
{% endblock %}
<section class="flex-grow-1 p-3 p-lg-5">
{% block content %}{% endblock %}
</section>