'
- );
- }
- }
+ Update::cleanOldFiles($to_clean);
+
Froxlor::updateToDbVersion('202312100');
}
diff --git a/install/updates/froxlor/update_2.2.inc.php b/install/updates/froxlor/update_2.2.inc.php
new file mode 100644
index 0000000000..a22ac732b1
--- /dev/null
+++ b/install/updates/froxlor/update_2.2.inc.php
@@ -0,0 +1,68 @@
+
+ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+use Froxlor\Database\Database;
+use Froxlor\FileDir;
+use Froxlor\Froxlor;
+use Froxlor\Install\Update;
+use Froxlor\Settings;
+
+if (!defined('_CRON_UPDATE')) {
+ if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
+ header('Location: ../../../../index.php');
+ exit();
+ }
+}
+
+if (Froxlor::isFroxlorVersion('2.1.x')) {
+ Update::showUpdateStep("Enhancing virtual email table");
+ Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0;");
+ Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0;");
+ Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `bypass_spam` tinyint(1) NOT NULL default '0';");
+ Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `policy_greylist` tinyint(1) NOT NULL default '1';");
+ Update::lastStepStatus(0);
+
+ Update::showUpdateStep("Adjusting settings");
+ Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'activated' WHERE `settinggroup` = 'dkim' AND `varname` = 'use_dkim';");
+ Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'reload_command' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkimrestart_command';");
+ Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'config_file', `value` = '/etc/rspamd/local.d/froxlor_settings.conf' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_prefix';");
+ Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_keylength';");
+ Settings::AddNew("dmarc.use_dmarc", "0");
+ Settings::AddNew("dmarc.dmarc_entry", "v=DMARC1; p=none;");
+ Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'privkeysuffix';");
+ Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_domains';");
+ Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_algorithm';");
+ Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_notes';");
+ Update::lastStepStatus(0);
+
+ $to_clean = [
+ 'actions/admin/settings/180.dkim.php',
+ 'actions/admin/settings/185.spf.php',
+ ];
+ Update::cleanOldFiles($to_clean);
+
+ Froxlor::updateToDbVersion('202312230');
+ Froxlor::updateToVersion('2.2.0-dev1');
+}
diff --git a/actions/admin/settings/185.spf.php b/install/updates/preconfig/preconfig_2.2.inc.php
similarity index 57%
rename from actions/admin/settings/185.spf.php
rename to install/updates/preconfig/preconfig_2.2.inc.php
index 5e25711628..be73568832 100644
--- a/actions/admin/settings/185.spf.php
+++ b/install/updates/preconfig/preconfig_2.2.inc.php
@@ -23,31 +23,26 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
-return [
- 'groups' => [
- 'spf' => [
- 'title' => lng('admin.spfsettings'),
- 'icon' => 'fa-solid fa-clipboard-check',
- 'fields' => [
- 'spf_use_spf' => [
- 'label' => lng('spf.use_spf'),
- 'settinggroup' => 'spf',
- 'varname' => 'use_spf',
- 'type' => 'checkbox',
- 'default' => false,
- 'save_method' => 'storeSettingField',
- 'overview_option' => true
- ],
- 'spf_spf_entry' => [
- 'label' => lng('spf.spf_entry'),
- 'settinggroup' => 'spf',
- 'varname' => 'spf_entry',
- 'type' => 'text',
- 'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
- 'default' => 'v=spf1 a mx -all',
- 'save_method' => 'storeSettingField'
- ]
- ]
- ]
- ]
+use Froxlor\Install\Update;
+
+$preconfig = [
+ 'title' => '2.2.x updates',
+ 'fields' => []
];
+$return = [];
+
+if (Update::versionInUpdate($current_version, '2.2.0-dev1')) {
+ $has_preconfig = true;
+ $description = 'Froxlor now features antispam configurations using rspamd. Would you like to enable the antispam feature (required re-configuration of services)?';
+ $question = 'Enable antispam (recommended) ';
+ $return['antispam_activated'] = [
+ 'type' => 'checkbox',
+ 'value' => 1,
+ 'checked' => 0,
+ 'label' => $question,
+ 'prior_infotext' => $description
+ ];
+}
+
+$preconfig['fields'] = $return;
+return $preconfig;
diff --git a/install/updatesql.php b/install/updatesql.php
index 47b45a44ff..773e1480c5 100644
--- a/install/updatesql.php
+++ b/install/updatesql.php
@@ -55,6 +55,7 @@
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_0.10.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.0.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.1.inc.php'));
+ include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.2.inc.php'));
// Check Froxlor - database integrity (only happens after all updates are done, so we know the db-layout is okay)
Update::showUpdateStep("Checking database integrity");
diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php
index c86929560d..da173e2711 100644
--- a/lib/Froxlor/Api/Commands/Domains.php
+++ b/lib/Froxlor/Api/Commands/Domains.php
@@ -1407,7 +1407,7 @@ public function update()
$zonefile = $result['zonefile'];
}
- if (Settings::Get('dkim.use_dkim') != '1') {
+ if (Settings::Get('antispam.activated') != '1') {
$dkim = $result['dkim'];
}
diff --git a/lib/Froxlor/Api/Commands/Emails.php b/lib/Froxlor/Api/Commands/Emails.php
index 85c19a49b1..4569852f13 100644
--- a/lib/Froxlor/Api/Commands/Emails.php
+++ b/lib/Froxlor/Api/Commands/Emails.php
@@ -28,10 +28,12 @@
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
+use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Idna\IdnaWrapper;
use Froxlor\Settings;
+use Froxlor\System\Cronjob;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
@@ -49,6 +51,14 @@ class Emails extends ApiCommand implements ResourceEntity
* name of the address before @
* @param string $domain
* domain-name for the email-address
+ * @param float $spam_tag_level
+ * optional, score which is required to tag emails as spam, default: 7.0
+ * @param float $spam_kill_level
+ * optional, score which is required to discard emails, default: 14.0
+ * @param boolean $bypass_spam
+ * optional, disable spam-filter entirely, default: no
+ * @param boolean $policy_greylist
+ * optional, enable grey-listing, default: yes
* @param boolean $iscatchall
* optional, make this address a catchall address, default: no
* @param int $customerid
@@ -74,6 +84,10 @@ public function add()
$domain = $this->getParam('domain');
// parameters
+ $spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
+ $spam_kill_level = $this->getParam('spam_kill_level', true, '14.0');
+ $bypass_spam = $this->getBoolParam('bypass_spam', true, 0);
+ $policy_greylist = $this->getBoolParam('policy_greylist', true, 1);
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
$description = $this->getParam('description', true, '');
@@ -140,11 +154,19 @@ public function add()
}
}
+ $spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
+ $spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
+ $description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
+
$stmt = Database::prepare("
INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` SET
`customerid` = :cid,
`email` = :email,
`email_full` = :email_full,
+ `spam_tag_level` = :spam_tag_level,
+ `spam_kill_level` = :spam_kill_level,
+ `bypass_spam` = :bypass_spam,
+ `policy_greylist` = :policy_greylist,
`iscatchall` = :iscatchall,
`domainid` = :domainid,
`description` = :description
@@ -153,6 +175,10 @@ public function add()
"cid" => $customer['customerid'],
"email" => $email,
"email_full" => $email_full,
+ "spam_tag_level" => $spam_tag_level,
+ "spam_kill_level" => $spam_kill_level,
+ "bypass_spam" => $bypass_spam,
+ "policy_greylist" => $policy_greylist,
"iscatchall" => $iscatchall,
"domainid" => $domain_check['id'],
"description" => $description
@@ -162,6 +188,7 @@ public function add()
// update customer usage
Customers::increaseUsage($customer['customerid'], 'emails_used');
+ Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'");
$result = $this->apiCall('Emails.get', [
@@ -194,7 +221,7 @@ public function get()
$customer_ids = $this->getAllowedCustomerIds('email');
$params['idea'] = ($id <= 0 ? $emailaddr : $id);
- $result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
+ $result_stmt = Database::prepare("SELECT v.*, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
@@ -220,6 +247,14 @@ public function get()
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
* optional, required when called as admin (if $customerid is not specified)
+ * @param float $spam_tag_level
+ * optional, score which is required to tag emails as spam, default: 7.0
+ * @param float $spam_kill_level
+ * optional, score which is required to discard emails, default: 14.0
+ * @param boolean $bypass_spam
+ * optional, disable spam-filter entirely, default: no
+ * @param boolean $policy_greylist
+ * optional, enable grey-listing, default: yes
* @param boolean $iscatchall
* optional
* @param string $description
@@ -255,6 +290,10 @@ public function update()
$id = $result['id'];
// parameters
+ $spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']);
+ $spam_kill_level = $this->getParam('spam_kill_level', true, $result['spam_kill_level']);
+ $bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
+ $policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
$description = $this->getParam('description', true, $result['description']);
@@ -284,19 +323,34 @@ public function update()
$email = $result['email_full'];
}
+ $spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
+ $spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
+ $description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
+
$stmt = Database::prepare("
- UPDATE `" . TABLE_MAIL_VIRTUAL . "`
- SET `email` = :email , `iscatchall` = :caflag, `description` = :description
+ UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET
+ `email` = :email ,
+ `spam_tag_level` = :spam_tag_level,
+ `spam_kill_level` = :spam_kill_level,
+ `bypass_spam` = :bypass_spam,
+ `policy_greylist` = :policy_greylist,
+ `iscatchall` = :caflag,
+ `description` = :description
WHERE `customerid`= :cid AND `id`= :id
");
$params = [
"email" => $email,
+ "spam_tag_level" => $spam_tag_level,
+ "spam_kill_level" => $spam_kill_level,
+ "bypass_spam" => $bypass_spam,
+ "policy_greylist" => $policy_greylist,
"caflag" => $iscatchall,
"description" => $description,
"cid" => $customer['customerid'],
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
+ Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
@@ -334,7 +388,7 @@ public function listing()
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
- SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
+ SELECT m.*, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` m
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`)
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)
diff --git a/lib/Froxlor/Cli/ConfigServices.php b/lib/Froxlor/Cli/ConfigServices.php
index 2f28092637..4e475b2f2d 100644
--- a/lib/Froxlor/Cli/ConfigServices.php
+++ b/lib/Froxlor/Cli/ConfigServices.php
@@ -43,9 +43,7 @@ final class ConfigServices extends CliCommand
{
private $yes_to_all_supported = [
'bookworm',
- 'bionic',
'bullseye',
- 'buster',
'focal',
'jammy',
];
@@ -172,8 +170,8 @@ private function createConfig(OutputInterface $output, SymfonyStyle $io): int
$distributions_select_data = [];
//set default os.
- $os_dist = ['ID' => 'bullseye'];
- $os_version = ['0' => '11'];
+ $os_dist = ['ID' => 'bookworm'];
+ $os_version = ['0' => '12'];
$os_default = $os_dist['ID'];
//read os-release
diff --git a/lib/Froxlor/Cli/InstallCommand.php b/lib/Froxlor/Cli/InstallCommand.php
index bd8531e0a7..bf16938170 100644
--- a/lib/Froxlor/Cli/InstallCommand.php
+++ b/lib/Froxlor/Cli/InstallCommand.php
@@ -27,10 +27,13 @@
use Exception;
use Froxlor\Config\ConfigParser;
+use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\Install\Install;
use Froxlor\Install\Install\Core;
+use Froxlor\Settings;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -50,7 +53,8 @@ protected function configure()
$this->setDescription('Installation process to use instead of web-ui');
$this->addArgument('input-file', InputArgument::OPTIONAL, 'Optional JSON array file to use for unattended installations');
$this->addOption('print-example-file', 'p', InputOption::VALUE_NONE, 'Outputs an example JSON content to be used with the input file parameter')
- ->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
+ ->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process')
+ ->addOption('show-sysinfo', 's', InputOption::VALUE_NONE, 'Outputs system information about your froxlor installation');
}
/**
@@ -72,6 +76,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return self::INVALID;
}
+ if ($input->getOption('show-sysinfo') !== false) {
+ if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
+ $output->writeln("Could not find froxlor's userdata.inc.php file. You can use this parameter only with an installed froxlor system.>");
+ return self::INVALID;
+ }
+ $this->printSysInfo($output);
+ return self::SUCCESS;
+ }
+
session_start();
require __DIR__ . '/install.functions.php';
@@ -349,6 +362,57 @@ private function downloadFile($src, $dest)
fclose($fp);
}
+ private function printSysInfo(OutputInterface $output)
+ {
+
+ $php_sapi = 'mod_php';
+ $php_version = phpversion();
+ if (Settings::Get('system.mod_fcgid') == '1') {
+ $php_sapi = 'FCGID';
+ if (Settings::Get('system.mod_fcgid_ownvhost') == '1') {
+ $php_sapi .= ' (+ froxlor)';
+ }
+ } elseif (Settings::Get('phpfpm.enabled') == '1') {
+ $php_sapi = 'PHP-FPM';
+ if (Settings::Get('phpfpm.enabled_ownvhost') == '1') {
+ $php_sapi .= ' (+ froxlor)';
+ }
+ }
+
+ $kernel = 'unknown';
+ if (function_exists('posix_uname')) {
+ $kernel_nfo = posix_uname();
+ $kernel = $kernel_nfo['release'] . ' (' . $kernel_nfo['machine'] . ')';
+ }
+
+ $ips = [];
+ $ips_stmt = Database::query("SELECT CONCAT(`ip`, ' (', `port`, ')') as ipaddr FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `id`");
+ while ($ip = $ips_stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $ips[] = $ip['ipaddr'];
+ }
+
+ $table = new Table($output);
+ $table
+ ->setHeaders([
+ 'Key', 'Value'
+ ])
+ ->setRows([
+ ['Froxlor', Froxlor::getVersionString()],
+ ['Update-channel', Settings::Get('system.update_channel')],
+ ['Hostname', Settings::Get('system.hostname')],
+ ['Install-dir', Froxlor::getInstallDir()],
+ ['PHP CLI', $php_version],
+ ['PHP SAPI', $php_sapi],
+ ['Webserver', Settings::Get('system.webserver')],
+ ['Kernel', $kernel],
+ ['Database', Database::getAttribute(\PDO::ATTR_SERVER_VERSION)],
+ ['Distro config', Settings::Get('system.distribution')],
+ ['IP addresses', implode("\n", $ips)],
+ ]);
+ $table->setStyle('box');
+ $table->render();
+ }
+
private function cliTextFormat(string $text, string $nl_char = "\n"): string
{
$text = str_replace([' ', ' ', ' '], [$nl_char, $nl_char, $nl_char], $text);
diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php
index b0b3eac34a..61926ecc10 100644
--- a/lib/Froxlor/Cli/MasterCron.php
+++ b/lib/Froxlor/Cli/MasterCron.php
@@ -52,7 +52,7 @@ protected function configure()
$this->setName('froxlor:cron');
$this->setDescription('Regulary perform tasks created by froxlor');
$this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run');
- $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]')
+ $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 9 = re-generate rspamd configs, 10 = re-set quotas, 99 = re-create cron.d-file]')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)')
->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.')
->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
@@ -77,6 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if (empty($jobs) || in_array('tasks', $jobs)) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
+ Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
$jobs[] = 'tasks';
@@ -95,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($input->getOption('run-task')) {
$tasks_to_run = $input->getOption('run-task');
foreach ($tasks_to_run as $ttr) {
- if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
+ if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::REBUILD_RSPAMD, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
Cronjob::inserttask($ttr);
$jobs[] = 'tasks';
} else {
diff --git a/lib/Froxlor/Cron/Dns/DnsBase.php b/lib/Froxlor/Cron/Dns/DnsBase.php
index 1d8ae40ac4..f6884affaf 100644
--- a/lib/Froxlor/Cron/Dns/DnsBase.php
+++ b/lib/Froxlor/Cron/Dns/DnsBase.php
@@ -117,85 +117,6 @@ public function reloadDaemon()
}
}
- public function writeDKIMconfigs()
- {
- if (Settings::Get('dkim.use_dkim') == '1') {
- if (!file_exists(FileDir::makeCorrectDir(Settings::Get('dkim.dkim_prefix')))) {
- $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir(Settings::Get('dkim.dkim_prefix'))));
- FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir(Settings::Get('dkim.dkim_prefix'))));
- }
-
- $dkimdomains = '';
- $dkimkeys = '';
- $result_domains_stmt = Database::query("
- SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey`
- FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' ORDER BY `id` ASC
- ");
-
- while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
- $privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix'));
- $pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public');
-
- if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') {
- $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`");
- $max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC);
- $domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1;
- $privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix'));
- FileDir::safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength'));
- $domain['dkim_privkey'] = file_get_contents($privkey_filename);
- FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename));
- $pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public');
- FileDir::safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename));
- $domain['dkim_pubkey'] = file_get_contents($pubkey_filename);
- FileDir::safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename));
- $upd_stmt = Database::prepare("
- UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
- `dkim_id` = :dkimid,
- `dkim_privkey` = :privkey,
- `dkim_pubkey` = :pubkey
- WHERE `id` = :id
- ");
- $upd_data = [
- 'dkimid' => $domain['dkim_id'],
- 'privkey' => $domain['dkim_privkey'],
- 'pubkey' => $domain['dkim_pubkey'],
- 'id' => $domain['id']
- ];
- Database::pexecute($upd_stmt, $upd_data);
- }
-
- if (!file_exists($privkey_filename) && $domain['dkim_privkey'] != '') {
- $privkey_file_handler = fopen($privkey_filename, "w");
- fwrite($privkey_file_handler, $domain['dkim_privkey']);
- fclose($privkey_file_handler);
- FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename));
- }
-
- if (!file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') {
- $pubkey_file_handler = fopen($pubkey_filename, "w");
- fwrite($pubkey_file_handler, $domain['dkim_pubkey']);
- fclose($pubkey_file_handler);
- FileDir::safe_exec("chmod 0644 " . escapeshellarg($pubkey_filename));
- }
-
- $dkimdomains .= $domain['domain'] . "\n";
- $dkimkeys .= "*@" . $domain['domain'] . ":" . $domain['domain'] . ":" . $privkey_filename . "\n";
- }
-
- $dkimdomains_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_domains'));
- $dkimdomains_file_handler = fopen($dkimdomains_filename, "w");
- fwrite($dkimdomains_file_handler, $dkimdomains);
- fclose($dkimdomains_file_handler);
- $dkimkeys_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_dkimkeys'));
- $dkimkeys_file_handler = fopen($dkimkeys_filename, "w");
- fwrite($dkimkeys_file_handler, $dkimkeys);
- fclose($dkimkeys_file_handler);
-
- FileDir::safe_exec(escapeshellcmd(Settings::Get('dkim.dkimrestart_command')));
- $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded');
- }
- }
-
protected function getDomainList()
{
$result_domains_stmt = Database::query("
diff --git a/lib/Froxlor/Cron/Mail/Rspamd.php b/lib/Froxlor/Cron/Mail/Rspamd.php
new file mode 100644
index 0000000000..6663b914a4
--- /dev/null
+++ b/lib/Froxlor/Cron/Mail/Rspamd.php
@@ -0,0 +1,216 @@
+
+ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+namespace Froxlor\Cron\Mail;
+
+use Exception;
+use Froxlor\Database\Database;
+use Froxlor\FileDir;
+use Froxlor\FroxlorLogger;
+use Froxlor\Settings;
+
+class Rspamd
+{
+ const DEFAULT_MARK_LVL = 7.0;
+ const DEFAULT_REJECT_LVL = 14.0;
+
+ private string $frx_settings_file = "";
+
+ protected FroxlorLogger $logger;
+
+ public function __construct(FroxlorLogger $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function writeConfigs()
+ {
+ // tell the world what we are doing
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Task9 started - Rebuilding antispam configuration');
+
+ // get all email addresses
+ $antispam_stmt = Database::prepare("
+ SELECT email, spam_tag_level, spam_kill_level, bypass_spam, policy_greylist, iscatchall
+ FROM `" . TABLE_MAIL_VIRTUAL . "`
+ ORDER BY email
+ ");
+ Database::pexecute($antispam_stmt);
+
+ $this->frx_settings_file = "#\n# Automatically generated file by froxlor. DO NOT EDIT manually as it will be overwritten!\n# Generated: " . date('d.m.Y H:i') . "\n#\n\n";
+ while ($email = $antispam_stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $this->generateEmailAddrConfig($email);
+ }
+
+ $antispam_cfg_file = FileDir::makeCorrectFile(Settings::Get('antispam.config_file'));
+ file_put_contents($antispam_cfg_file, $this->frx_settings_file);
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, $antispam_cfg_file . ' written');
+ $this->writeDkimConfigs();
+ $this->reloadDaemon();
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Task9 finished');
+ }
+
+ /**
+ * # local.d/dkim_signing.conf
+ * try_fallback = true;
+ * path = "/var/lib/rspamd/dkim/$domain.$selector.key";
+ * selector_map = "/etc/rspamd/dkim_selectors.map";
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function writeDkimConfigs()
+ {
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Writing DKIM key-pairs');
+
+ $dkim_selector_map = "";
+ $result_domains_stmt = Database::query("
+ SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey`
+ FROM `" . TABLE_PANEL_DOMAINS . "`
+ WHERE `dkim` = '1'
+ ORDER BY `id` ASC
+ ");
+
+ while ($domain = $result_domains_stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') {
+ $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`");
+ $max_dkim_id = $max_dkim_id_stmt->fetch(\PDO::FETCH_ASSOC);
+ $domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1;
+
+ $privkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.key');
+ $pubkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.txt');
+
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Generating DKIM keys for "' . $domain['domain'] . '"');
+ $rsret = [];
+ FileDir::safe_exec(
+ 'rspamadm dkim_keygen -d ' . escapeshellarg($domain['domain']) . ' -k ' . $privkey_filename . ' -s dkim' . $domain['dkim_id'] . ' -b ' . Settings::Get('dkim.dkim_keylength') . ' -o plain > ' . escapeshellarg($pubkey_filename),
+ $rsret,
+ ['>']
+ );
+ if (!file_exists($privkey_filename) || !file_exists($pubkey_filename)) {
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, 'DKIM Keypair for domain "' . $domain['domain'] . '" was not generated successfully.');
+ continue;
+ }
+ $domain['dkim_privkey'] = file_get_contents($privkey_filename);
+ FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename));
+ FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($privkey_filename));
+ $domain['dkim_pubkey'] = file_get_contents($pubkey_filename);
+ FileDir::safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename));
+ FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($pubkey_filename));
+ $upd_stmt = Database::prepare("
+ UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
+ `dkim_id` = :dkimid,
+ `dkim_privkey` = :privkey,
+ `dkim_pubkey` = :pubkey
+ WHERE `id` = :id
+ ");
+ $upd_data = [
+ 'dkimid' => $domain['dkim_id'],
+ 'privkey' => $domain['dkim_privkey'],
+ 'pubkey' => $domain['dkim_pubkey'],
+ 'id' => $domain['id']
+ ];
+ Database::pexecute($upd_stmt, $upd_data);
+ } else {
+ $privkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.key');
+ $pubkey_filename = FileDir::makeCorrectFile('/var/lib/rspamd/dkim/' . $domain['domain'] . '.dkim' . $domain['dkim_id'] . '.txt');
+ }
+
+ if (!file_exists($privkey_filename) && $domain['dkim_privkey'] != '') {
+ file_put_contents($privkey_filename, $domain['dkim_privkey']);
+ FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename));
+ FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($privkey_filename));
+ }
+
+ if (!file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') {
+ file_put_contents($pubkey_filename, $domain['dkim_pubkey']);
+ FileDir::safe_exec("chmod 0644 " . escapeshellarg($pubkey_filename));
+ FileDir::safe_exec("chown _rspamd:_rspamd " . escapeshellarg($pubkey_filename));
+ }
+
+ $dkim_selector_map .= $domain['domain'] . " dkim" . $domain['dkim_id'] . "\n";
+ }
+
+ $dkim_selector_file = FileDir::makeCorrectFile('/etc/rspamd/dkim_selectors.map');
+ file_put_contents($dkim_selector_file, $dkim_selector_map);
+ }
+
+ private function generateEmailAddrConfig(array $email): void
+ {
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Generating antispam config for ' . $email['email']);
+
+ $email['spam_tag_level'] = floatval($email['spam_tag_level']);
+ $email['spam_kill_level'] = floatval($email['spam_kill_level']);
+ $email_id = md5($email['email']);
+
+ $this->frx_settings_file .= '# Email: ' . $email['email'] . "\n";
+ foreach (['rcpt', 'from'] as $type) {
+ $this->frx_settings_file .= 'frx_' . $email_id . '_' . $type . ' {' . "\n";
+ $this->frx_settings_file .= ' id = "frx_' . $email_id . '_' . $type . '";' . "\n";
+ if ($email['iscatchall']) {
+ $this->frx_settings_file .= ' priority = low;' . "\n";
+ $this->frx_settings_file .= ' ' . $type . ' = "' . substr($email['email'], strpos($email['email'], '@')) . '";' . "\n";
+ } else {
+ $this->frx_settings_file .= ' priority = medium;' . "\n";
+ $this->frx_settings_file .= ' ' . $type . ' = "' . $email['email'] . '";' . "\n";
+ }
+ if ((int)$email['bypass_spam'] == 1) {
+ $this->frx_settings_file .= ' want_spam = yes;' . "\n";
+ } else {
+ $this->frx_settings_file .= ' apply {' . "\n";
+ $this->frx_settings_file .= ' actions {' . "\n";
+ $this->frx_settings_file .= ' "add header" = ' . $email['spam_tag_level'] . ';' . "\n";
+ $this->frx_settings_file .= ' rewrite_subject = ' . $email['spam_tag_level'] . ';' . "\n";
+ $this->frx_settings_file .= ' reject = ' . $email['spam_kill_level'] . ';' . "\n";
+ if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) {
+ $this->frx_settings_file .= ' greylist = null;' . "\n";
+ }
+ $this->frx_settings_file .= ' }' . "\n";
+ $this->frx_settings_file .= ' }' . "\n";
+ if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) {
+ $this->frx_settings_file .= ' symbols [ "DONT_GREYLIST" ]' . "\n";
+ }
+ }
+ $this->frx_settings_file .= '}' . "\n";
+ }
+ $this->frx_settings_file .= "\n";
+ }
+
+ public function reloadDaemon()
+ {
+ // reload DNS daemon
+ $cmd = Settings::Get('antispam.reload_command');
+ $cmdStatus = 1;
+ FileDir::safe_exec(escapeshellcmd($cmd), $cmdStatus);
+ if ($cmdStatus === 0) {
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Antispam daemon reloaded');
+ } else {
+ $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, 'Error while running `' . $cmd . '`: exit code (' . $cmdStatus . ') - please check your system logs');
+ }
+ }
+}
diff --git a/lib/Froxlor/Cron/System/TasksCron.php b/lib/Froxlor/Cron/System/TasksCron.php
index 60face001c..83cfdd3f76 100644
--- a/lib/Froxlor/Cron/System/TasksCron.php
+++ b/lib/Froxlor/Cron/System/TasksCron.php
@@ -25,9 +25,11 @@
namespace Froxlor\Cron\System;
+use Exception;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Cron\Http\ConfigIO;
use Froxlor\Cron\Http\HttpConfigBase;
+use Froxlor\Cron\Mail\Rspamd;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\Dns\PowerDNS;
@@ -40,6 +42,9 @@
class TasksCron extends FroxlorCron
{
+ /**
+ * @throws Exception
+ */
public static function run()
{
/**
@@ -98,6 +103,11 @@ public static function run()
* refs #293
*/
self::deleteFtpData($row);
+ } elseif ($row['type'] == TaskId::REBUILD_RSPAMD && (int)Settings::Get('antispam.activated') != 0) {
+ /**
+ * TYPE=9 Rebuild antispam config
+ */
+ self::rebuildAntiSpamConfigs();
} elseif ($row['type'] == TaskId::CREATE_QUOTA && (int)Settings::Get('system.diskquota_enabled') != 0) {
/**
* TYPE=10 Set the filesystem - quota
@@ -266,13 +276,7 @@ private static function createNewHome($row = null)
private static function rebuildDnsConfigs()
{
$dnssrv = '\\Froxlor\\Cron\\Dns\\' . Settings::Get('system.dns_server');
-
$nameserver = new $dnssrv(FroxlorLogger::getInstanceOf());
-
- if (Settings::Get('dkim.use_dkim') == '1') {
- $nameserver->writeDKIMconfigs();
- }
-
$nameserver->writeConfigs();
}
@@ -448,4 +452,13 @@ private static function setFilesystemQuota()
}
}
}
+
+ /**
+ * @throws Exception
+ */
+ private static function rebuildAntiSpamConfigs()
+ {
+ $antispam = new Rspamd(FroxlorLogger::getInstanceOf());
+ $antispam->writeConfigs();
+ }
}
diff --git a/lib/Froxlor/Cron/TaskId.php b/lib/Froxlor/Cron/TaskId.php
index 9aa065c06e..f905eba4bc 100644
--- a/lib/Froxlor/Cron/TaskId.php
+++ b/lib/Froxlor/Cron/TaskId.php
@@ -66,6 +66,12 @@ final class TaskId
*/
const DELETE_FTP_DATA = 8;
+ /**
+ * TYPE=9 MEANS THAT SOMETHING ANTISPAM RELATED HAS CHANGED.
+ * REBUILD froxlor_settings.conf IF ANTISPAM IS ENABLED
+ */
+ const REBUILD_RSPAMD = 9;
+
/**
* TYPE=10 Set the filesystem - quota
*/
diff --git a/lib/Froxlor/Customer/Customer.php b/lib/Froxlor/Customer/Customer.php
index 27581d1c9a..c9e7f96b43 100644
--- a/lib/Froxlor/Customer/Customer.php
+++ b/lib/Froxlor/Customer/Customer.php
@@ -32,7 +32,7 @@ class Customer
{
/**
- * Get value of a a specific field from a given customer
+ * Get value of a specific field from a given customer
*
* @param int $customerid
* @param string $varname
diff --git a/lib/Froxlor/Dns/Dns.php b/lib/Froxlor/Dns/Dns.php
index dc7a134b94..1ac86689a7 100644
--- a/lib/Froxlor/Dns/Dns.php
+++ b/lib/Froxlor/Dns/Dns.php
@@ -120,16 +120,14 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
if ($domain['isemaildomain'] == '1') {
self::addRequiredEntry('@', 'MX', $required_entries);
if (Settings::Get('system.dns_createmailentry')) {
- foreach (
- [
+ foreach ([
'imap',
'pop3',
'mail',
'smtp'
] as $record
) {
- foreach (
- [
+ foreach ([
'AAAA',
'A'
] as $type
@@ -152,9 +150,9 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
if (!$froxlorhostname) {
// additional required records for subdomains
$subdomains_stmt = Database::prepare("
- SELECT `domain`, `iswildcarddomain`, `wwwserveralias`, `isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "`
- WHERE `parentdomainid` = :domainid
- ");
+ SELECT `domain`, `iswildcarddomain`, `wwwserveralias`, `isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "`
+ WHERE `parentdomainid` = :domainid
+ ");
Database::pexecute($subdomains_stmt, [
'domainid' => $domain_id
]);
@@ -163,7 +161,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
$sub_record = str_replace('.' . $domain['domain'], '', $subdomain['domain']);
// Listing domains is enough as there currently is no support for choosing
// different ips for a subdomain => use same IPs as toplevel
- self::addRequiredEntry($sub_record, 'A',$required_entries);
+ self::addRequiredEntry($sub_record, 'A', $required_entries);
self::addRequiredEntry($sub_record, 'AAAA', $required_entries);
// Check whether to add a www.-prefix
@@ -181,7 +179,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
// check for SPF content later
self::addRequiredEntry('@SPF@.' . $sub_record, 'TXT', $required_entries);
}
- if (Settings::Get('dkim.use_dkim') == '1') {
+ if (Settings::Get('antispam.activated') == '1' && $domain['dkim'] == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries);
}
@@ -218,7 +216,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
// check for SPF content later
self::addRequiredEntry('@SPF@', 'TXT', $required_entries);
}
- if (Settings::Get('dkim.use_dkim') == '1') {
+ if (Settings::Get('antispam.activated') == '1' && $domain['dkim'] == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries);
}
@@ -229,17 +227,25 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
// now generate all records and unset the required entries we have
foreach ($dom_entries as $entry) {
- if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']),
- $required_entries[$entry['type']])) {
+ if (array_key_exists($entry['type'], $required_entries) && array_key_exists(
+ md5($entry['record']),
+ $required_entries[$entry['type']]
+ )) {
unset($required_entries[$entry['type']][md5($entry['record'])]);
}
- if (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr($entry['content'],
- 0, 7)) == '"v=caa1') {
+ if (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr(
+ $entry['content'],
+ 0,
+ 7
+ )) == '"v=caa1') {
// unset special CAA required-entry
unset($required_entries[$entry['type']][md5("@CAA@")]);
}
- if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && (strtolower(substr($entry['content'],
- 0, 7)) == '"v=spf1' || strtolower(substr($entry['content'], 0, 6)) == 'v=spf1')) {
+ if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && (strtolower(substr(
+ $entry['content'],
+ 0,
+ 7
+ )) == '"v=spf1' || strtolower(substr($entry['content'], 0, 6)) == 'v=spf1')) {
// unset special spf required-entry
unset($required_entries[$entry['type']][md5("@SPF@")]);
}
@@ -248,32 +254,36 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
$primary_ns = $entry['content'];
}
// check for CNAME on @, www- or wildcard-Alias and remove A/AAAA record accordingly
- foreach (
- [
+ foreach ([
'@',
'www',
'*'
] as $crecord
) {
- if ($entry['type'] == 'CNAME' && $entry['record'] == '@' && (array_key_exists(md5($crecord),
- $required_entries['A']) || array_key_exists(md5($crecord), $required_entries['AAAA']))) {
+ if ($entry['type'] == 'CNAME' && $entry['record'] == '@' && (array_key_exists(
+ md5($crecord),
+ $required_entries['A']
+ ) || array_key_exists(md5($crecord), $required_entries['AAAA']))) {
unset($required_entries['A'][md5($crecord)]);
unset($required_entries['AAAA'][md5($crecord)]);
}
}
// also allow overriding of auto-generated values (imap,pop3,mail,smtp) if enabled in the settings
if (Settings::Get('system.dns_createmailentry')) {
- foreach (
- [
+ foreach ([
'imap',
'pop3',
'mail',
'smtp'
] as $crecord
) {
- if ($entry['type'] == 'CNAME' && $entry['record'] == $crecord && (array_key_exists(md5($crecord),
- $required_entries['A']) || array_key_exists(md5($crecord),
- $required_entries['AAAA']))) {
+ if ($entry['type'] == 'CNAME' && $entry['record'] == $crecord && (array_key_exists(
+ md5($crecord),
+ $required_entries['A']
+ ) || array_key_exists(
+ md5($crecord),
+ $required_entries['AAAA']
+ ))) {
unset($required_entries['A'][md5($crecord)]);
unset($required_entries['AAAA'][md5($crecord)]);
}
@@ -310,8 +320,11 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
foreach ($records as $record) {
if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
$zonerecords[] = new DnsEntry($record, 'A', $ip['ip']);
- } elseif ($type == 'AAAA' && filter_var($ip['ip'], FILTER_VALIDATE_IP,
- FILTER_FLAG_IPV6) !== false) {
+ } elseif ($type == 'AAAA' && filter_var(
+ $ip['ip'],
+ FILTER_VALIDATE_IP,
+ FILTER_FLAG_IPV6
+ ) !== false) {
$zonerecords[] = new DnsEntry($record, 'AAAA', $ip['ip']);
}
}
@@ -376,9 +389,7 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
// TXT (SPF and DKIM)
if (array_key_exists("TXT", $required_entries)) {
- if (Settings::Get('dkim.use_dkim') == '1') {
- $dkim_entries = self::generateDkimEntries($domain);
- }
+ $dkim_entries = self::generateDkimEntries($domain);
foreach ($required_entries as $type => $records) {
if ($type == 'TXT') {
@@ -471,8 +482,10 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
if (!$isMainButSubTo) {
$date = date('Ymd');
- $domain['bindserial'] = (preg_match('/^' . $date . '/',
- $domain['bindserial']) ? $domain['bindserial'] + 1 : $date . '00');
+ $domain['bindserial'] = (preg_match(
+ '/^' . $date . '/',
+ $domain['bindserial']
+ ) ? $domain['bindserial'] + 1 : $date . '00');
if (!$froxlorhostname) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
@@ -499,8 +512,12 @@ public static function createDomainZone($domain_id, bool $froxlorhostname = fals
array_unshift($zonerecords, $soa_record);
}
- $zone = new DnsZone((int)Settings::Get('system.defaultttl'), $domain['domain'], $domain['bindserial'],
- $zonerecords);
+ $zone = new DnsZone(
+ (int)Settings::Get('system.defaultttl'),
+ $domain['domain'],
+ $domain['bindserial'],
+ $zonerecords
+ );
return $zone;
}
@@ -527,43 +544,11 @@ private static function generateDkimEntries(array $domain): array
{
$zone_dkim = [];
- if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') {
+ if (Settings::Get('antispam.activated') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') {
// start
$dkim_txt = 'v=DKIM1;';
-
- // algorithm
- $algorithm = explode(',', Settings::Get('dkim.dkim_algorithm'));
- $alg = '';
- foreach ($algorithm as $a) {
- if ($a == 'all') {
- break;
- } else {
- $alg .= $a . ':';
- }
- }
-
- if ($alg != '') {
- $alg = substr($alg, 0, -1);
- $dkim_txt .= 'h=' . $alg . ';';
- }
-
- // notes
- if (trim(Settings::Get('dkim.dkim_notes') != '')) {
- $dkim_txt .= 'n=' . trim(Settings::Get('dkim.dkim_notes')) . ';';
- }
-
// key
- $dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s',
- '$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';';
-
- // service-type
- if (Settings::Get('dkim.dkim_servicetype') == '1') {
- $dkim_txt .= 's=email;';
- }
-
- // end-part
- $dkim_txt .= 't=s';
-
+ $dkim_txt .= 'k=rsa;p=' . trim($domain['dkim_pubkey']) . ';';
// dkim-entry
$zone_dkim[] = $dkim_txt;
}
diff --git a/lib/Froxlor/ErrorBag.php b/lib/Froxlor/ErrorBag.php
new file mode 100644
index 0000000000..a3bb6a074d
--- /dev/null
+++ b/lib/Froxlor/ErrorBag.php
@@ -0,0 +1,77 @@
+
+ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+namespace Froxlor;
+
+use Exception;
+
+/**
+ * Class to manage the current user / session
+ */
+class ErrorBag
+{
+
+ /**
+ * returns whether there are errors stored
+ *
+ * @return bool
+ */
+ public static function hasErrors(): bool
+ {
+ return !empty($_SESSION) && !empty($_SESSION['_errors']);
+ }
+
+ /**
+ * add error
+ *
+ * @param string $data
+ *
+ * @return void
+ */
+ public static function addError(string $data): void
+ {
+ if (!is_array($_SESSION['_errors'])) {
+ $_SESSION['_errors'] = [];
+ }
+ $_SESSION['_errors'][] = $data;
+ }
+
+ /**
+ * Return errors and clear session
+ *
+ * @return array
+ * @throws Exception
+ */
+ public static function getErrors(): array
+ {
+ $errors = $_SESSION['_errors'] ?? [];
+ unset($_SESSION['_errors']);
+ if (Settings::Config('display_php_errors')) {
+ return $errors;
+ }
+ return [];
+ }
+
+}
diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php
index e93e48bcde..5ba45c9c36 100644
--- a/lib/Froxlor/Froxlor.php
+++ b/lib/Froxlor/Froxlor.php
@@ -31,15 +31,15 @@ final class Froxlor
{
// Main version variable
- const VERSION = '2.1.4';
+ const VERSION = '2.2.0-dev1';
// Database version (YYYYMMDDC where C is a daily counter)
- const DBVERSION = '202312120';
+ const DBVERSION = '202312230';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
- const DOCS_URL = 'https://docs.froxlor.org/v2.1/';
+ const DOCS_URL = 'https://docs.froxlor.org/v2.2/';
/**
* return path to where froxlor is installed, e.g.
diff --git a/lib/Froxlor/Install/Install/Core.php b/lib/Froxlor/Install/Install/Core.php
index ef66156e43..07670f2fa2 100644
--- a/lib/Froxlor/Install/Install/Core.php
+++ b/lib/Froxlor/Install/Install/Core.php
@@ -674,6 +674,7 @@ private function createJsonArray()
'http' => $this->validatedData['webserver'],
'smtp' => 'postfix_dovecot',
'mail' => 'dovecot_postfix2',
+ 'antispam' => 'rspamd',
'ftp' => 'proftpd',
'system' => $system_params
];
diff --git a/lib/Froxlor/Install/Update.php b/lib/Froxlor/Install/Update.php
index 699bab1bfe..2e77e07f55 100644
--- a/lib/Froxlor/Install/Update.php
+++ b/lib/Froxlor/Install/Update.php
@@ -25,6 +25,7 @@
namespace Froxlor\Install;
+use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@@ -85,7 +86,7 @@ public static function lastStepStatus(int $status = -1, string $message = 'OK',
self::$update_tasks[self::$task_counter]['result'] = 1;
break;
default:
- self::$update_tasks[self::$task_counter]['result'] = -1;
+ self::$update_tasks[self::$task_counter]['result'] = -1;
break;
}
@@ -136,4 +137,36 @@ public static function getTaskCounter(): int
{
return self::$task_counter;
}
+
+ public static function cleanOldFiles(array $to_clean)
+ {
+ self::showUpdateStep("Cleaning up old files");
+ $disabled = explode(',', ini_get('disable_functions'));
+ $exec_allowed = !in_array('exec', $disabled);
+ $del_list = "";
+ foreach ($to_clean as $filedir) {
+ $complete_filedir = Froxlor::getInstallDir() . $filedir;
+ if (file_exists($complete_filedir)) {
+ if ($exec_allowed) {
+ FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
+ } else {
+ $del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
+ }
+ }
+ }
+ if ($exec_allowed) {
+ self::lastStepStatus(0);
+ } else {
+ if (empty($del_list)) {
+ // none of the files existed
+ self::lastStepStatus(0);
+ } else {
+ self::lastStepStatus(
+ 1,
+ 'manual commands needed',
+ 'Please run the following commands manually:
' . $del_list . '
'
+ );
+ }
+ }
+ }
}
diff --git a/lib/Froxlor/PhpHelper.php b/lib/Froxlor/PhpHelper.php
index a5cdddc36b..5bf3cc24c8 100644
--- a/lib/Froxlor/PhpHelper.php
+++ b/lib/Froxlor/PhpHelper.php
@@ -34,27 +34,6 @@
class PhpHelper
{
- private static $sort_key = 'id';
- private static $sort_type = SORT_STRING;
-
- /**
- * sort an array by either natural or string sort and a given index where the value for comparison is found
- *
- * @param array $list
- * @param string $key
- *
- * @return bool
- */
- public static function sortListBy(array &$list, string $key = 'id'): bool
- {
- self::$sort_type = Settings::Get('panel.natsorting') == 1 ? SORT_NATURAL : SORT_STRING;
- self::$sort_key = $key;
- return usort($list, [
- 'self',
- 'sortListByGivenKey'
- ]);
- }
-
/**
* Wrapper around htmlentities to handle arrays, with the advantage that you
* can select which fields should be handled by htmlentities
@@ -101,35 +80,6 @@ public static function arrayTrim(array $source): array
});
}
- /**
- * Replaces Strings in an array, with the advantage that you
- * can select which fields should be str_replace'd
- *
- * @param string|array $search String or array of strings to search for
- * @param string|array $replace String or array to replace with
- * @param string|array $subject String or array The subject array
- * @param string|array $fields string The fields which should be checked for, separated by spaces
- *
- * @return string|array The str_replace'd array
- */
- public static function strReplaceArray($search, $replace, $subject, $fields = '')
- {
- if (is_array($subject)) {
- if (!is_array($fields)) {
- $fields = self::arrayTrim(explode(' ', $fields));
- }
- foreach ($subject as $field => $value) {
- if ((!is_array($fields) || empty($fields)) || (in_array($field, $fields))) {
- $subject[$field] = str_replace($search, $replace, $value);
- }
- }
- } else {
- $subject = str_replace($search, $replace, $subject);
- }
-
- return $subject;
- }
-
/**
* froxlor php error handler
*
@@ -170,9 +120,8 @@ public static function phpErrHandler($errno, $errstr, $errfile, $errline)
$err_display .= '
';
// end later
$err_display .= '';
- // check for more existing errors
- $errors = isset(UI::twig()->getGlobals()['global_errors']) ? UI::twig()->getGlobals()['global_errors'] : "";
- UI::twig()->addGlobal('global_errors', $errors . $err_display);
+ // set errors to session
+ ErrorBag::addError($err_display);
// return true to ignore php standard error-handler
return true;
}
@@ -338,7 +287,8 @@ public static function sizeReadable(
?string $max = '',
string $system = 'si',
string $retstring = '%01.2f %s'
- ): string {
+ ): string
+ {
// Pick units
$systems = [
'si' => [
@@ -421,7 +371,8 @@ public static function recursive_array_search(
array $haystack,
array &$keys = [],
string $currentKey = ''
- ): bool {
+ ): bool
+ {
foreach ($haystack as $key => $value) {
$pathkey = empty($currentKey) ? $key : $currentKey . '.' . $key;
if (is_array($value)) {
@@ -476,19 +427,6 @@ public static function cleanGlobal(array &$global, AntiXSS &$antiXss)
}
}
- /**
- * @param array $a
- * @param array $b
- * @return int
- */
- private static function sortListByGivenKey(array $a, array $b): int
- {
- if (self::$sort_type == SORT_NATURAL) {
- return strnatcasecmp($a[self::$sort_key], $b[self::$sort_key]);
- }
- return strcasecmp($a[self::$sort_key], $b[self::$sort_key]);
- }
-
/**
* Generate php file from array.
*
diff --git a/lib/Froxlor/Settings.php b/lib/Froxlor/Settings.php
index dad8181477..d0f33a95aa 100644
--- a/lib/Froxlor/Settings.php
+++ b/lib/Froxlor/Settings.php
@@ -25,6 +25,7 @@
namespace Froxlor;
+use Exception;
use Froxlor\Database\Database;
use PDO;
use PDOStatement;
@@ -131,6 +132,7 @@ private static function readConfig()
self::$conf = [
'enable_webupdate' => false,
'disable_otp_security_check' => false,
+ 'display_php_errors' => false,
];
$configfile = Froxlor::getInstallDir() . '/lib/config.inc.php';
@@ -330,7 +332,7 @@ public static function loadSettingsInto(&$settings_data)
}
}
- public static function getAll() : array
+ public static function getAll(): array
{
self::init();
return self::$data;
@@ -338,17 +340,14 @@ public static function getAll() : array
/**
* get value from config by identifier
+ * @throws Exception
*/
public static function Config(string $config)
{
self::init();
- $sstr = explode(".", $config);
- $result = self::$conf;
- foreach ($sstr as $key) {
- $result = $result[$key] ?? null;
- if (empty($result)) {
- break;
- }
+ $result = self::$conf[$config] ?? null;
+ if (is_null($result)) {
+ throw new Exception('Unknown local config name "' . $config . '"');
}
return $result;
}
diff --git a/lib/Froxlor/Settings/Store.php b/lib/Froxlor/Settings/Store.php
index fe4d9603d0..9b82ca0a99 100644
--- a/lib/Froxlor/Settings/Store.php
+++ b/lib/Froxlor/Settings/Store.php
@@ -225,6 +225,17 @@ public static function storeSettingFieldInsertBindTask($fieldname, $fielddata, $
return $returnvalue;
}
+ public static function storeSettingFieldInsertAntispamTask($fieldname, $fielddata, $newfieldvalue)
+ {
+ // first save the setting itself
+ $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
+
+ if ($returnvalue !== false) {
+ Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
+ }
+ return $returnvalue;
+ }
+
public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue)
{
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
diff --git a/lib/Froxlor/System/Cronjob.php b/lib/Froxlor/System/Cronjob.php
index a131052c14..b4be07ac2e 100644
--- a/lib/Froxlor/System/Cronjob.php
+++ b/lib/Froxlor/System/Cronjob.php
@@ -134,11 +134,15 @@ public static function inserttask(int $type, ...$params)
INSERT INTO `" . TABLE_PANEL_TASKS . "` SET `type` = :type, `data` = :data
");
- if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) {
+ if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::REBUILD_RSPAMD || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) {
// 4 = bind -> if bind disabled -> no task
if ($type == TaskId::REBUILD_DNS && Settings::Get('system.bind_enable') == '0') {
return;
}
+ // 9 = rspamd -> if antispam disabled -> no task
+ if ($type == TaskId::REBUILD_RSPAMD && Settings::Get('antispam.activated') == '0') {
+ return;
+ }
// 10 = quota -> if quota disabled -> no task
if ($type == TaskId::CREATE_QUOTA && Settings::Get('system.diskquota_enabled') == '0') {
return;
diff --git a/lib/Froxlor/Traffic/Traffic.php b/lib/Froxlor/Traffic/Traffic.php
index d9fa4e99d0..5eab6af39f 100644
--- a/lib/Froxlor/Traffic/Traffic.php
+++ b/lib/Froxlor/Traffic/Traffic.php
@@ -42,7 +42,7 @@ public static function getCustomerStats(array $userinfo, string $range = null, b
{
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
self::getParamsByRange($range, ['customer_traffic' => true])));
- if ($userinfo['adminsession'] == 1) {
+ if (($userinfo['adminsession'] ?? 0) == 1) {
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
}
$trafficCollection = $trafficCollectionObj->get();
@@ -58,8 +58,17 @@ public static function getCustomerStats(array $userinfo, string $range = null, b
$mail = $item['mail'];
$total = $http + $ftp + $mail;
+ if (empty($users[$item['customerid']])) {
+ $users[$item['customerid']] = [
+ 'total' => 0.00,
+ 'http' => 0.00,
+ 'ftp' => 0.00,
+ 'mail' => 0.00,
+ ];
+ }
+
// per user total
- if ($userinfo['adminsession'] == 1) {
+ if (($userinfo['adminsession'] ?? 0) == 1) {
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
}
$users[$item['customerid']]['total'] += $total;
@@ -67,6 +76,30 @@ public static function getCustomerStats(array $userinfo, string $range = null, b
$users[$item['customerid']]['ftp'] += $ftp;
$users[$item['customerid']]['mail'] += $mail;
if (!$overview) {
+ if (empty($years[$item['year']])) {
+ $years[$item['year']] = [
+ 'total' => 0.00,
+ 'http' => 0.00,
+ 'ftp' => 0.00,
+ 'mail' => 0.00,
+ ];
+ }
+ if (empty($months[$item['month'] . '/' . $item['year']])) {
+ $months[$item['month'] . '/' . $item['year']] = [
+ 'total' => 0.00,
+ 'http' => 0.00,
+ 'ftp' => 0.00,
+ 'mail' => 0.00,
+ ];
+ }
+ if (empty($days[$item['day'] . '.' . $item['month'] . '.' . $item['year']])) {
+ $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']] = [
+ 'total' => 0.00,
+ 'http' => 0.00,
+ 'ftp' => 0.00,
+ 'mail' => 0.00,
+ ];
+ }
// per year
$years[$item['year']]['total'] += $total;
$years[$item['year']]['http'] += $http;
@@ -86,7 +119,12 @@ public static function getCustomerStats(array $userinfo, string $range = null, b
}
// calculate overview for given range from users
- $metrics = [];
+ $metrics = [
+ 'total' => 0.00,
+ 'http' => 0.00,
+ 'ftp' => 0.00,
+ 'mail' => 0.00,
+ ];
foreach ($users as $user) {
$metrics['total'] += $user['total'];
$metrics['http'] += $user['http'];
diff --git a/lib/Froxlor/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php
index c4d989aa8d..3e01372e14 100644
--- a/lib/Froxlor/UI/Callbacks/Domain.php
+++ b/lib/Froxlor/UI/Callbacks/Domain.php
@@ -25,6 +25,7 @@
namespace Froxlor\UI\Callbacks;
+use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain as DDomain;
use Froxlor\FileDir;
@@ -33,23 +34,36 @@
class Domain
{
- public static function domainLink(array $attributes)
+ public static function domainEditLink(array $attributes): array
{
- return '' . $attributes['data'] . '';
+ $linker = UI::getLinker();
+ return [
+ 'macro' => 'link',
+ 'data' => [
+ 'text' => $attributes['data'],
+ 'href' => $linker->getLink([
+ 'section' => 'domains',
+ 'page' => 'domains',
+ 'action' => 'edit',
+ 'id' => $attributes['fields']['id'],
+ ]),
+ 'target' => '_blank'
+ ]
+ ];
}
- public static function domainWithCustomerLink(array $attributes)
+ public static function domainWithCustomerLink(array $attributes): string
{
$linker = UI::getLinker();
$result = '' . $attributes['data'] . '';
if ((int)UI::getCurrentUser()['adminsession'] == 1 && $attributes['fields']['customerid']) {
$result .= ' (' . $attributes['fields']['loginname'] . ')';
+ 'section' => 'customers',
+ 'page' => 'customers',
+ 'action' => 'su',
+ 'sort' => $attributes['fields']['loginname'],
+ 'id' => $attributes['fields']['customerid'],
+ ]) . '">' . $attributes['fields']['loginname'] . ')';
}
return $result;
}
@@ -108,12 +122,12 @@ public static function domainExternalLinkInfo(array $attributes): string
public static function canEdit(array $attributes): bool
{
- return (bool)($attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated']);
+ return $attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated'];
}
public static function canViewLogs(array $attributes): bool
{
- if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) {
+ if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) {
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
return true;
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
@@ -155,17 +169,19 @@ public static function adminCanEditDNS(array $attributes): bool
public static function hasLetsEncryptActivated(array $attributes): bool
{
- return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0);
+ return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)));
}
+ /**
+ * @throws \Exception
+ */
public static function canEditSSL(array $attributes): bool
{
- if (
- Settings::Get('system.use_ssl') == '1'
+ if (Settings::Get('system.use_ssl') == '1'
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
&& (int)$attributes['fields']['caneditdomain'] == 1
&& (int)$attributes['fields']['letsencrypt'] == 0
- && (int)$attributes['fields']['email_only'] == 0
+ && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))
&& !$attributes['fields']['deactivated']
) {
return true;
@@ -196,15 +212,15 @@ public static function editSSLButtons(array $attributes): array
],
];
- // specified certificate for domain
if ($attributes['fields']['domain_hascert'] == 1) {
+ // specified certificate for domain
$result['icon'] .= ' text-success';
- } // shared certificates (e.g. subdomain of domain where certificate is specified)
- elseif ($attributes['fields']['domain_hascert'] == 2) {
+ } elseif ($attributes['fields']['domain_hascert'] == 2) {
+ // shared certificates (e.g. subdomain of domain where certificate is specified)
$result['icon'] .= ' text-warning';
$result['title'] .= "\n" . lng('panel.ssleditor_infoshared');
- } // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings)
- elseif ($attributes['fields']['domain_hascert'] == 0) {
+ } elseif ($attributes['fields']['domain_hascert'] == 0) {
+ // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings)
$result['icon'] .= ' text-danger';
$result['title'] .= "\n" . lng('panel.ssleditor_infoglobal');
}
@@ -216,7 +232,7 @@ public static function editSSLButtons(array $attributes): array
public static function listIPs(array $attributes): string
{
- if (isset($attributes['fields']['ipsandports']) && !empty($attributes['fields']['ipsandports'])) {
+ if (!empty($attributes['fields']['ipsandports'])) {
$iplist = "";
foreach ($attributes['fields']['ipsandports'] as $ipport) {
$iplist .= $ipport['ip'] . ':' . $ipport['port'] . ' ';
@@ -226,6 +242,9 @@ public static function listIPs(array $attributes): string
return lng('panel.empty');
}
+ /**
+ * @throws \Exception
+ */
public static function getPhpConfigName(array $attributes): string
{
$sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id");
@@ -233,11 +252,11 @@ public static function getPhpConfigName(array $attributes): string
if ((int)UI::getCurrentUser()['adminsession'] == 1) {
$linker = UI::getLinker();
$result = '' . $phpconfig['description'] . '';
+ 'section' => 'phpsettings',
+ 'page' => 'overview',
+ 'searchfield' => 'c.id',
+ 'searchtext' => $attributes['data'],
+ ]) . '">' . $phpconfig['description'] . '';
} else {
$result = $phpconfig['description'];
}
diff --git a/lib/Froxlor/UI/Callbacks/Style.php b/lib/Froxlor/UI/Callbacks/Style.php
index 511a118bdb..e6d6c5f52f 100644
--- a/lib/Froxlor/UI/Callbacks/Style.php
+++ b/lib/Froxlor/UI/Callbacks/Style.php
@@ -25,6 +25,7 @@
namespace Froxlor\UI\Callbacks;
+use Froxlor\CurrentUser;
use Froxlor\Settings;
class Style
@@ -68,7 +69,7 @@ public static function resultDomainTerminatedOrDeactivated(array $attributes): s
$termination_css = 'table-danger';
}
}
- $deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated'];
+ $deactivated = $attributes['fields']['deactivated'] || (CurrentUser::isAdmin() && $attributes['fields']['customer_deactivated']);
return $deactivated ? 'table-info' : $termination_css;
}
diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php
index 9e63e3c302..91df6fdf7d 100644
--- a/lib/Froxlor/UI/Callbacks/Text.php
+++ b/lib/Froxlor/UI/Callbacks/Text.php
@@ -90,9 +90,10 @@ public static function wordwrap(array $attributes): string
public static function customerNoteDetailModal(array $attributes): array
{
$note = $attributes['fields']['custom_notes'] ?? '';
+ $key = $attributes['fields']['customerid'] ?? $attributes['fields']['adminid'];
return [
- 'entry' => $attributes['fields']['id'],
- 'id' => 'cnModal' . $attributes['fields']['id'],
+ 'entry' => $key,
+ 'id' => 'cnModal' . $key,
'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
'body' => nl2br(Markdown::cleanCustomNotes($note))
];
diff --git a/lib/Froxlor/UI/Form.php b/lib/Froxlor/UI/Form.php
index d17c911e3d..a824bd8a34 100644
--- a/lib/Froxlor/UI/Form.php
+++ b/lib/Froxlor/UI/Form.php
@@ -217,7 +217,8 @@ public static function prefetchFormFieldData($fieldname, $fielddata)
{
$returnvalue = [];
if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] == 'select') {
- if ((!is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) {
+ if ((empty($fielddata['select_var']) || !is_array($fielddata['select_var'])) && (isset($fielddata['option_options_method']))
+ ) {
$returnvalue['select_var'] = call_user_func($fielddata['option_options_method']);
}
}
diff --git a/lib/Froxlor/UI/Response.php b/lib/Froxlor/UI/Response.php
index a12b368302..4e57652045 100644
--- a/lib/Froxlor/UI/Response.php
+++ b/lib/Froxlor/UI/Response.php
@@ -139,7 +139,7 @@ public static function standardError($errors = '', $replacer = '', $throw_except
exit;
}
- public static function dynamicError($message)
+ public static function dynamicError($message, bool $nosession = false)
{
$_SESSION['requestData'] = $_POST;
$link_ref = '';
@@ -147,7 +147,8 @@ public static function dynamicError($message)
$link_ref = htmlentities($_SERVER['HTTP_REFERER']);
}
- UI::view('misc/alert.html.twig', [
+ $tpl = $nosession ? 'misc/alert_nosession.html.twig' : 'misc/alert.html.twig';
+ UI::view($tpl, [
'type' => 'danger',
'btntype' => 'light',
'heading' => lng('error.error'),
diff --git a/lib/config.example.inc.php b/lib/config.example.inc.php
index 7ca6b26b79..e5b14ef7d6 100644
--- a/lib/config.example.inc.php
+++ b/lib/config.example.inc.php
@@ -23,4 +23,12 @@
* Default: false
*/
'disable_otp_security_check' => false,
+
+ /**
+ * For debugging/development purposes only.
+ * Enable to display all php related issue (notices, warnings, etc.; depending on php.ini) for froxlor itself
+ *
+ * Default: false
+ */
+ 'display_php_errors' => false,
];
diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml
index 70b0cd3f26..cda9767bfd 100644
--- a/lib/configfiles/bookworm.xml
+++ b/lib/configfiles/bookworm.xml
@@ -1657,7 +1657,7 @@ data_directory = /var/lib/postfix
# for the case of a subdomain, $mydomain *must* be equal to $myhostname,
# otherwise you cannot use the main domain for virtual transport.
# also check the note about $mydomain below.
-myhostname = mail.$mydomain
+myhostname = $mydomain
#myhostname = virtual.domain.tld
# The mydomain parameter specifies the local internet domain name.
@@ -1751,8 +1751,8 @@ inet_interfaces = all
#
# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS".
#
-#mydestination = $myhostname, localhost.$mydomain, localhost
-mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+mydestination = $myhostname, localhost.$mydomain, localhost
+#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
# mail.$mydomain, www.$mydomain, ftp.$mydomain
@@ -2561,6 +2561,107 @@ plugin {
+
+
+
+
+
+
+ /dev/null]]>
+ /etc/apt/sources.list.d/rspamd.list]]>
+ > /etc/apt/sources.list.d/rspamd.list]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //service[@type='antispam']/general/commands[@index=1]
+
+ //service[@type='antispam']/general/installs[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=2]
+
+ //service[@type='antispam']/general/files[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=3]
+
+
+
diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml
index 366689e0cb..c49212d962 100644
--- a/lib/configfiles/bullseye.xml
+++ b/lib/configfiles/bullseye.xml
@@ -1657,7 +1657,7 @@ data_directory = /var/lib/postfix
# for the case of a subdomain, $mydomain *must* be equal to $myhostname,
# otherwise you cannot use the main domain for virtual transport.
# also check the note about $mydomain below.
-myhostname = mail.$mydomain
+myhostname = $mydomain
#myhostname = virtual.domain.tld
# The mydomain parameter specifies the local internet domain name.
@@ -1751,8 +1751,8 @@ inet_interfaces = all
#
# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS".
#
-#mydestination = $myhostname, localhost.$mydomain, localhost
-mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+mydestination = $myhostname, localhost.$mydomain, localhost
+#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
# mail.$mydomain, www.$mydomain, ftp.$mydomain
@@ -4131,6 +4131,106 @@ plugin {
+
+
+
+
+
+
+ /dev/null]]>
+ /etc/apt/sources.list.d/rspamd.list]]>
+ > /etc/apt/sources.list.d/rspamd.list]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //service[@type='antispam']/general/commands[@index=1]
+
+ //service[@type='antispam']/general/installs[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=2]
+
+ //service[@type='antispam']/general/files[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=3]
+
+
+
diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml
index 67af7ed465..ddc1530a89 100644
--- a/lib/configfiles/focal.xml
+++ b/lib/configfiles/focal.xml
@@ -1642,7 +1642,7 @@ compatibility_level = 2
# for the case of a subdomain, $mydomain *must* be equal to $myhostname,
# otherwise you cannot use the main domain for virtual transport.
# also check the note about $mydomain below.
-myhostname = mail.$mydomain
+myhostname = $mydomain
#myhostname = virtual.domain.tld
# The mydomain parameter specifies the local internet domain name.
@@ -1656,8 +1656,8 @@ myhostname = mail.$mydomain
# FQDN from Froxlor
mydomain =
-#mydestination = $myhostname, localhost.$mydomain, localhost
-mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+mydestination = $myhostname, localhost.$mydomain, localhost
+#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
# mail.$mydomain, www.$mydomain, ftp.$mydomain
@@ -3354,6 +3354,106 @@ plugin {
+
+
+
+
+
+
+ /dev/null]]>
+ /etc/apt/sources.list.d/rspamd.list]]>
+ > /etc/apt/sources.list.d/rspamd.list]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //service[@type='antispam']/general/commands[@index=1]
+
+ //service[@type='antispam']/general/installs[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=2]
+
+ //service[@type='antispam']/general/files[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=3]
+
+
+
diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml
index 01d33fa9ca..cc9f67c68b 100644
--- a/lib/configfiles/gentoo.xml
+++ b/lib/configfiles/gentoo.xml
@@ -1727,12 +1727,9 @@ compatibility_level = 2
## General Postfix configuration
# should be the default domain from your provider eg. "server100.provider.tld"
mydomain =
-
-# should be different from $mydomain eg. "mail.$mydomain"
-myhostname = mail.$mydomain
+myhostname = $mydomain
mydestination = $myhostname,
- $mydomain,
localhost.$myhostname,
localhost.$mydomain,
localhost
@@ -2218,6 +2215,98 @@ plugin {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //service[@type='antispam']/general/installs[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=2]
+
+ //service[@type='antispam']/general/files[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=3]
+
+
+
diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml
index 67450c27fa..323a1f0f1c 100644
--- a/lib/configfiles/jammy.xml
+++ b/lib/configfiles/jammy.xml
@@ -1642,7 +1642,7 @@ compatibility_level = 2
# for the case of a subdomain, $mydomain *must* be equal to $myhostname,
# otherwise you cannot use the main domain for virtual transport.
# also check the note about $mydomain below.
-myhostname = mail.$mydomain
+myhostname = $mydomain
#myhostname = virtual.domain.tld
# The mydomain parameter specifies the local internet domain name.
@@ -1656,8 +1656,8 @@ myhostname = mail.$mydomain
# FQDN from Froxlor
mydomain =
-#mydestination = $myhostname, localhost.$mydomain, localhost
-mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+mydestination = $myhostname, localhost.$mydomain, localhost
+#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
# mail.$mydomain, www.$mydomain, ftp.$mydomain
@@ -3344,6 +3344,106 @@ plugin {
+
+
+
+
+
+
+ /dev/null]]>
+ /etc/apt/sources.list.d/rspamd.list]]>
+ > /etc/apt/sources.list.d/rspamd.list]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //service[@type='antispam']/general/commands[@index=1]
+
+ //service[@type='antispam']/general/installs[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=2]
+
+ //service[@type='antispam']/general/files[@index=1]
+
+ //service[@type='antispam']/general/commands[@index=3]
+
+
+
diff --git a/lib/formfields/admin/domains/formfield.domains_add.php b/lib/formfields/admin/domains/formfield.domains_add.php
index 5400cc7d28..b5e994446f 100644
--- a/lib/formfields/admin/domains/formfield.domains_add.php
+++ b/lib/formfields/admin/domains/formfield.domains_add.php
@@ -111,7 +111,7 @@
'selected' => 0
],
'dkim' => [
- 'visible' => Settings::Get('dkim.use_dkim') == '1',
+ 'visible' => Settings::Get('antispam.activated') == '1',
'label' => 'DomainKeys',
'type' => 'checkbox',
'value' => '1',
diff --git a/lib/formfields/admin/domains/formfield.domains_edit.php b/lib/formfields/admin/domains/formfield.domains_edit.php
index 00b00cbd71..93dea60dd9 100644
--- a/lib/formfields/admin/domains/formfield.domains_edit.php
+++ b/lib/formfields/admin/domains/formfield.domains_edit.php
@@ -129,7 +129,7 @@
'selected' => $result['subcanemaildomain']
],
'dkim' => [
- 'visible' => Settings::Get('dkim.use_dkim') == '1',
+ 'visible' => Settings::Get('antispam.activated') == '1',
'label' => 'DomainKeys',
'type' => 'checkbox',
'value' => '1',
diff --git a/lib/formfields/customer/domains/formfield.domains_edit.php b/lib/formfields/customer/domains/formfield.domains_edit.php
index cd7d1553ad..10974832de 100644
--- a/lib/formfields/customer/domains/formfield.domains_edit.php
+++ b/lib/formfields/customer/domains/formfield.domains_edit.php
@@ -116,6 +116,12 @@
'type' => 'hidden',
'value' => '0'
],
+ 'dkim_entry' => [
+ 'visible' => (Settings::Get('system.bind_enable') == '0' && Settings::Get('antispam.activated') == '1' && $result['dkim'] == '1' && $result['dkim_pubkey'] != ''),
+ 'label' => lng('antispam.required_dkim_dns'),
+ 'type' => 'longtext',
+ 'value' => (string)(new \Froxlor\Dns\DnsEntry('dkim' . $result['dkim_id'] . '._domainkey', 'TXT', '"v=DKIM1; k=rsa; p='.trim($result['dkim_pubkey']).'"'))
+ ],
]
],
'section_bssl' => [
diff --git a/lib/formfields/customer/email/formfield.emails_edit.php b/lib/formfields/customer/email/formfield.emails_edit.php
index 4749bca174..7a03ef6939 100644
--- a/lib/formfields/customer/email/formfield.emails_edit.php
+++ b/lib/formfields/customer/email/formfield.emails_edit.php
@@ -102,6 +102,44 @@
]
]
],
+ 'spam_tag_level' => [
+ 'label' => lng('antispam.spam_tag_level'),
+ 'type' => 'text',
+ 'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
+ 'value' => $result['spam_tag_level']
+ ],
+ 'spam_kill_level' => [
+ 'label' => lng('antispam.spam_kill_level'),
+ 'type' => 'text',
+ 'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
+ 'value' => $result['spam_kill_level']
+ ],
+ 'bypass_spam' => [
+ 'label' => lng('antispam.bypass_spam'),
+ 'type' => 'label',
+ 'value' => ((int)$result['bypass_spam'] == 0 ? lng('panel.no') : lng('panel.yes')),
+ 'next_to' => [
+ 'add_link' => [
+ 'type' => 'link',
+ 'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglebypass&id=' . $result['id'],
+ 'label' => ' ' . lng('panel.toggle'),
+ 'classes' => 'btn btn-sm btn-secondary'
+ ]
+ ]
+ ],
+ 'policy_greylist' => [
+ 'label' => lng('antispam.policy_greylist'),
+ 'type' => 'label',
+ 'value' => ((int)$result['policy_greylist'] == 0 ? lng('panel.no') : lng('panel.yes')),
+ 'next_to' => [
+ 'add_link' => [
+ 'type' => 'link',
+ 'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglegreylist&id=' . $result['id'],
+ 'label' => ' ' . lng('panel.toggle'),
+ 'classes' => 'btn btn-sm btn-secondary'
+ ]
+ ]
+ ],
'mail_fwds' => [
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',
'type' => 'itemlist',
@@ -119,7 +157,9 @@
]
],
'buttons' => [
- /* none */
+ [
+ 'label' => lng('panel.save')
+ ]
]
]
];
diff --git a/lib/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php
index 6587593b2e..b378482832 100644
--- a/lib/tablelisting/admin/tablelisting.domains.php
+++ b/lib/tablelisting/admin/tablelisting.domains.php
@@ -50,7 +50,7 @@
'label' => lng('domains.domainname'),
'field' => 'domain_ace',
'isdefaultsearchfield' => true,
- 'callback' => [Domain::class, 'domainLink'],
+ 'callback' => [Domain::class, 'domainEditLink'],
],
'ipsandports' => [
'label' => lng('admin.ipsandports.ipsandports'),
diff --git a/lib/tablelisting/customer/tablelisting.emails.php b/lib/tablelisting/customer/tablelisting.emails.php
index 2643ab800b..64243b015c 100644
--- a/lib/tablelisting/customer/tablelisting.emails.php
+++ b/lib/tablelisting/customer/tablelisting.emails.php
@@ -49,6 +49,24 @@
'field' => 'popaccountid',
'callback' => [Email::class, 'account'],
],
+ 'm.spam_tag_level' => [
+ 'label' => lng('emails.spam_tag_level'),
+ 'field' => 'spam_tag_level',
+ ],
+ 'm.spam_kill_level' => [
+ 'label' => lng('emails.spam_kill_level'),
+ 'field' => 'spam_kill_level',
+ ],
+ 'm.bypass_spam' => [
+ 'label' => lng('emails.bypass_spam'),
+ 'field' => 'bypass_spam',
+ 'callback' => [Text::class, 'boolean'],
+ ],
+ 'm.policy_greylist' => [
+ 'label' => lng('emails.policy_greylist'),
+ 'field' => 'policy_greylist',
+ 'callback' => [Text::class, 'boolean'],
+ ],
'm.iscatchall' => [
'label' => lng('emails.catchall'),
'field' => 'iscatchall',
diff --git a/lng/ca.lng.php b/lng/ca.lng.php
index 99ecce92f9..ecbefcc4c9 100644
--- a/lng/ca.lng.php
+++ b/lng/ca.lng.php
@@ -629,45 +629,6 @@
'mysqlserver' => 'Servidor mysql utilitzable'
],
'diskquota' => 'Quota',
- 'dkim' => [
- 'dkim_prefix' => [
- 'title' => 'Prefix',
- 'description' => 'Especifiqueu la ruta als fitxers DKIM RSA i als fitxers de configuració del plugin Milter'
- ],
- 'dkim_domains' => [
- 'title' => 'Nom del fitxer dels dominis',
- 'description' => 'Nom de fitxer del paràmetre DKIM Domains especificat a la configuració dkim-milter'
- ],
- 'dkim_dkimkeys' => [
- 'title' => 'Nom de l\'arxiu KeyList',
- 'description' => 'Nom de fitxer del paràmetre DKIM KeyList especificat a la configuració de dkim-milter'
- ],
- 'dkimrestart_command' => [
- 'title' => 'Ordre de reinici del filtre',
- 'description' => 'Especifiqui l\'ordre de reinici del servei DKIM milter'
- ],
- 'privkeysuffix' => [
- 'title' => 'Sufix de claus privades',
- 'description' => 'Pot especificar una extensió/sufix de nom de fitxer (opcional) per a les claus privades dkim generades. Alguns serveis com dkim-filter requereixen que estigui buit.'
- ],
- 'use_dkim' => [
- 'title' => 'Activar suport DKIM?',
- 'description' => 'Voleu utilitzar el sistema de claus de domini (DKIM)? Nota: DKIM només és compatible amb dkim-filter, no amb opendkim (de moment)'
- ],
- 'dkim_algorithm' => [
- 'title' => 'Algorismes Hash permesos',
- 'description' => 'Defineix els algorismes hash permesos, escull "Tots" per a tots els algorismes o un o més dels altres algorismes disponibles'
- ],
- 'dkim_servicetype' => 'Tipus de servei',
- 'dkim_keylength' => [
- 'title' => 'Longitud de clau',
- 'description' => 'Atenció: Si canvia aquests valors, haurà d\'eliminar totes les claus privades/públiques de "%s".'
- ],
- 'dkim_notes' => [
- 'title' => 'Notes DKIM',
- 'description' => 'Notes que podrien ser d\'interès per a un humà, per exemple, una URL com http://www.dnswatch.info. Cap programa no realitza cap interpretació. Aquesta etiqueta s\'ha de fer servir amb moderació a causa de les limitacions d\'espai al DNS. Està pensada perquè la facin servir els administradors, no els usuaris finals.'
- ]
- ],
'dns' => [
'destinationip' => 'IP(s) del domini',
'standardip' => 'IP estàndard del servidor',
diff --git a/lng/de.lng.php b/lng/de.lng.php
index 6ea5acc718..b11edeeb67 100644
--- a/lng/de.lng.php
+++ b/lng/de.lng.php
@@ -264,7 +264,7 @@
'text' => 'Nachricht',
'sslsettings' => 'SSL-Einstellungen',
'specialsettings_replacements' => 'Die folgenden Variablen können verwendet werden: {DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (wenn zutreffend) ',
- 'dkimsettings' => 'DomainKey-Einstellungen',
+ 'antispam_settings' => 'Antispam-Einstellungen',
'caneditphpsettings' => 'Kann PHP-bezogene Domaineinstellungen vornehmen?',
'allips' => 'Alle IP-Adressen',
'awstatssettings' => 'AWstats-Einstellungen',
@@ -595,43 +595,38 @@
'mysqlserver' => 'Erlaubte MySQL-Server',
],
'diskquota' => 'Quota',
- 'dkim' => [
- 'dkim_prefix' => [
- 'title' => 'Prefix',
- 'description' => 'Wie lautet der Pfad zu den DKIM-RSA-Dateien sowie den Einstellungsdateien des Milter-Plugins?',
+ 'antispam' => [
+ 'config_file' => [
+ 'title' => 'Antispam Konfigurationsdatei',
+ 'description' => 'Pfad + Dateiname der Antispam-Regel Konfigurationsdatei',
],
- 'dkim_domains' => [
- 'title' => 'Domains-Dateiname',
- 'description' => 'Dateiname der DKIM-Domains-Angabe aus der DKIM-Milter-Konfigurationsdatei.',
+ 'reload_command' => [
+ 'title' => 'Milter-Restart-Befehl',
+ 'description' => 'Wie lautet der Befehl zum Neustarten des rspamd-Dienstes?',
],
- 'dkim_dkimkeys' => [
- 'title' => 'KeyList Dateiname',
- 'description' => 'Dateiname der DKIM-KeyList-Angabe aus der DKIM-Milter-Konfigurationsdatei.',
+ 'activated' => [
+ 'title' => 'Antispam aktivieren?',
+ 'description' => 'Aktivieren, um rspamd als Antispam Dienst zu verwenden.',
],
- 'dkimrestart_command' => [
- 'title' => 'Milter-Restart-Kommando',
- 'description' => 'Wie lautet das Kommando zum Neustarten des DKIM-Milter-Dienstes?',
- ],
- 'privkeysuffix' => [
- 'title' => 'Suffix für Private Keys',
- 'description' => 'Hier kann eine (optionale) Dateiendung für die generierten Private Keys angegeben werden. Manche Dienste, wie dkim-filter, erwarten, dass die Schlüssel keine Dateiendung haben (leer).',
+ 'dkim_keylength' => [
+ 'title' => 'DKIM Schlüssel-Länge',
+ 'description' => 'Achtung: Änderungen sind nur für neue Schlüssel gültig.
Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.',
],
- 'use_dkim' => [
- 'title' => 'DKIM-Support aktivieren?',
- 'description' => 'Wollen Sie das Domain-Keys-System (DKIM) benutzen? Hinweis: Derzeit wird DKIM nur via dkim-filter unterstützt, nicht opendkim.',
+ 'spam_tag_level' => [
+ 'title' => 'Spam Markierungs-Level',
+ 'description' => 'Erforderliche Punktzahl zum Markieren einer E-Mail als Spam Standard: 7.0'
],
- 'dkim_algorithm' => [
- 'title' => 'Gültige Hash-Algorithmen',
- 'description' => 'Wählen Sie einen Algorithmus, "All" für alle Algorithmen oder einen oder mehrere von den verfügbaren Algorithmen.',
+ 'spam_kill_level' => [
+ 'title' => 'Spam Ignorier-Level',
+ 'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail Standard: 14.0'
],
- 'dkim_servicetype' => 'Service Typen',
- 'dkim_keylength' => [
- 'title' => 'Schlüssel-Länge',
- 'description' => 'Achtung: Bei Änderung dieser Einstellung müssen alle private/public Schlüssel in "%s" gelöscht werden.',
+ 'bypass_spam' => [
+ 'title' => 'Spamfilter umgehen',
+ 'description' => 'Aktivieren, um den Spamfilter für diese Adresse zu umgehen/deaktivieren. Standard: Nein'
],
- 'dkim_notes' => [
- 'title' => 'DKIM Notiz',
- 'description' => 'Eine Notiz, welche für Menschen interessant sein könnte, z.B. eine URL wie http://www.dnswatch.info. Es gibt keine programmgesteuerte Interpretation für dieses Feld. Gehen Sie sparsam mit der Anzahl der Zeichen um, da es Einschränkungen seitens des DNS-Dienstes gibt. Dieses Feld ist für Administratoren gedacht, nicht für Benutzer.',
+ 'policy_greylist' => [
+ 'title' => 'Verwende greylisting',
+ 'description' => 'Eingehende E-Mails mittels Greylisting schützen. Standard: Ja'
],
],
'dns' => [
@@ -2108,9 +2103,19 @@
],
],
'spf' => [
- 'use_spf' => 'Aktiviere SPF für Domains?',
+ 'use_spf' => [
+ 'title' => 'Aktiviere SPF für Domains?',
+ 'description' => 'Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.',
+ ],
'spf_entry' => 'SPF-Eintrag für alle Domains',
],
+ 'dmarc' => [
+ 'use_dmarc' => [
+ 'title' => 'Aktiviere DMARC für Domains?',
+ 'description' => 'Erfordert einen speziellen DNS Eintrag für die Domain. Wenn das Nameserver-Feature nicht genutzt wird, muss dieser Eintrag manuell verwaltet werden.',
+ ],
+ 'dmarc_entry' => 'DMARC-Eintrag für alle Domains',
+ ],
'success' => [
'messages_success' => 'Nachricht erfolgreich an "%s" Empfänger gesendet',
'success' => 'Information',
diff --git a/lng/en.lng.php b/lng/en.lng.php
index 97fd7f15a8..1fa58d9107 100644
--- a/lng/en.lng.php
+++ b/lng/en.lng.php
@@ -268,7 +268,7 @@
'text' => 'Message',
'sslsettings' => 'SSL settings',
'specialsettings_replacements' => 'You can use the following variables: {DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (if applicable) ',
- 'dkimsettings' => 'DomainKey settings',
+ 'antispam_settings' => 'Antispam settings',
'caneditphpsettings' => 'Can change php-related domain settings?',
'allips' => 'All IP\'s',
'awstatssettings' => 'AWstats settings',
@@ -644,43 +644,38 @@
'mysqlserver' => 'Usable mysql-server',
],
'diskquota' => 'Quota',
- 'dkim' => [
- 'dkim_prefix' => [
- 'title' => 'Prefix',
- 'description' => 'Please specify the path to the DKIM RSA-files as well as to the configuration files for the Milter-plugin',
+ 'antispam' => [
+ 'config_file' => [
+ 'title' => 'Antispam settings file',
+ 'description' => 'Please specify the filename for the email-antispam rules',
],
- 'dkim_domains' => [
- 'title' => 'Domains filename',
- 'description' => 'Filename of the DKIM Domains parameter specified in the dkim-milter configuration',
- ],
- 'dkim_dkimkeys' => [
- 'title' => 'KeyList filename',
- 'description' => 'Filename of the DKIM KeyList parameter specified in the dkim-milter configuration',
- ],
- 'dkimrestart_command' => [
+ 'reload_command' => [
'title' => 'Milter restart command',
- 'description' => 'Please specify the restart command for the DKIM milter service',
+ 'description' => 'Please specify the restart command for the rspamd service',
],
- 'privkeysuffix' => [
- 'title' => 'Private keys suffix',
- 'description' => 'You can specify an (optional) filename extension/suffix for the generate dkim private keys. Some services like dkim-filter requires this to be empty',
+ 'activated' => [
+ 'title' => 'Activate antispam?',
+ 'description' => 'Would you like to use rspamd as antispam service?',
],
- 'use_dkim' => [
- 'title' => 'Activate DKIM support?',
- 'description' => 'Would you like to use the Domain Keys (DKIM) system? Note: DKIM is only supported using dkim-filter, not opendkim (yet)',
+ 'dkim_keylength' => [
+ 'title' => 'DKIM Key-length',
+ 'description' => 'Attention: Changes will only apply for new keys
Requires a specific dns entry for the domain. If you are not using the nameserver feature, you will have to manually manage these entries.',
],
- 'dkim_algorithm' => [
- 'title' => 'Allowed Hash Algorithms',
- 'description' => 'Define allowed hash algorithms, chose "All" for all algorithms or one or more from the other available algorithms',
+ 'spam_tag_level' => [
+ 'title' => 'Spam tag level',
+ 'description' => 'Score that is required to mark an email as spam Default: 7.0'
],
- 'dkim_servicetype' => 'Service Types',
- 'dkim_keylength' => [
- 'title' => 'Key-length',
- 'description' => 'Attention: If you change this values, you need to delete all the private/public keys in "%s"',
+ 'spam_kill_level' => [
+ 'title' => 'Spam kill level',
+ 'description' => 'Score that is required to discard an email entirely Default: 14.0'
],
- 'dkim_notes' => [
- 'title' => 'DKIM Notes',
- 'description' => 'Notes that might be of interest to a human, e.g. a URL like http://www.dnswatch.info. No interpretation is made by any program. This tag should be used sparingly due to space limitations in DNS. This is intended for use by administrators, not end users.',
+ 'bypass_spam' => [
+ 'title' => 'Bypass spamfilter',
+ 'description' => 'Activate to bypass/disable spamfiltering for this address. Default: no'
+ ],
+ 'policy_greylist' => [
+ 'title' => 'Use greylisting',
+ 'description' => 'Incoming emails will be protected by greylisting. Default: yes'
],
],
'dns' => [
@@ -2236,9 +2231,19 @@
],
],
'spf' => [
- 'use_spf' => 'Activate SPF for domains?',
+ 'use_spf' => [
+ 'title' => 'Activate SPF for domains?',
+ 'description' => 'Requires a specific dns entry for the domain. If you are not using the nameserver feature, you will have to manually manage these entries.',
+ ],
'spf_entry' => 'SPF entry for all domains',
],
+ 'dmarc' => [
+ 'use_dmarc' => [
+ 'title' => 'Activate DMARC for domains?',
+ 'description' => 'Requires a specific dns entry for the domain. If you are not using the nameserver feature, you will have to manually manage these entries.',
+ ],
+ 'dmarc_entry' => 'DMARC entry for all domains',
+ ],
'ssl_certificates' => [
'certificate_for' => 'Certificate for',
'valid_from' => 'Valid from',
diff --git a/lng/es.lng.php b/lng/es.lng.php
index 46fee058ac..29704088d9 100644
--- a/lng/es.lng.php
+++ b/lng/es.lng.php
@@ -628,45 +628,6 @@
'mysqlserver' => 'Servidor mysql utilizable'
],
'diskquota' => 'Cuota',
- 'dkim' => [
- 'dkim_prefix' => [
- 'title' => 'Prefijo',
- 'description' => 'Especifique la ruta a los archivos DKIM RSA y a los archivos de configuración del plugin Milter'
- ],
- 'dkim_domains' => [
- 'title' => 'Nombre de archivo de los dominios',
- 'description' => 'Nombre de archivo del parámetro DKIM Domains especificado en la configuración de dkim-milter'
- ],
- 'dkim_dkimkeys' => [
- 'title' => 'KeyList filename',
- 'description' => 'Nombre de archivo del parámetro DKIM KeyList especificado en la configuración de dkim-milter'
- ],
- 'dkimrestart_command' => [
- 'title' => 'Comando de reinicio del filtro',
- 'description' => 'Especifique el comando de reinicio del servicio DKIM milter'
- ],
- 'privkeysuffix' => [
- 'title' => 'Sufijo de claves privadas',
- 'description' => 'Puede especificar una extensión/sufijo de nombre de archivo (opcional) para las claves privadas dkim generadas. Algunos servicios como dkim-filter requieren que esté vacío.'
- ],
- 'use_dkim' => [
- 'title' => '¿Activar soporte DKIM?',
- 'description' => '¿Desea utilizar el sistema de claves de dominio (DKIM)? Nota: DKIM sólo es compatible con dkim-filter, no con opendkim (todavía)'
- ],
- 'dkim_algorithm' => [
- 'title' => 'Algoritmos Hash permitidos',
- 'description' => 'Defina los algoritmos hash permitidos, elija "Todos" para todos los algoritmos o uno o más de los otros algoritmos disponibles'
- ],
- 'dkim_servicetype' => 'Tipos de servicio',
- 'dkim_keylength' => [
- 'title' => 'Longitud de clave',
- 'description' => 'Atención: Si cambia estos valores, deberá eliminar todas las claves privadas/públicas de "%s".'
- ],
- 'dkim_notes' => [
- 'title' => 'Notas DKIM',
- 'description' => 'Notas que podrían ser de interés para un humano, por ejemplo, una URL como http://www.dnswatch.info. Ningún programa realiza ninguna interpretación. Esta etiqueta debe utilizarse con moderación debido a las limitaciones de espacio en DNS. Está pensada para que la utilicen los administradores, no los usuarios finales.'
- ]
- ],
'dns' => [
'destinationip' => 'IP(s) del dominio',
'standardip' => 'IP estándar del servidor',
diff --git a/lng/it.lng.php b/lng/it.lng.php
index 58610b8246..df13353f5d 100644
--- a/lng/it.lng.php
+++ b/lng/it.lng.php
@@ -660,41 +660,6 @@
'services' => 'Servizi',
],
'diskquota' => 'Quota',
- 'dkim' => [
- 'dkim_prefix' => [
- 'title' => 'Prefisso',
- 'description' => 'Si prega di specificare il percorso della DKIM RSA-files, nonch¸ i file di configurazione per il plugin Milter',
- ],
- 'dkim_domains' => [
- 'title' => 'Domini nomefile',
- 'description' => 'Nome file del parametro DKIM Domains specificata nella configurazione dkim-milter',
- ],
- 'dkim_dkimkeys' => [
- 'title' => 'Nome file del KeyList',
- 'description' => 'Nome file del parametro DKIM KeyList specificata nella configurazione dkim-milter',
- ],
- 'dkimrestart_command' => [
- 'title' => 'Milter commando riavvio',
- 'description' => 'Si prega di specificare il comando per riavviare il servizio DKIM milter',
- ],
- 'use_dkim' => [
- 'title' => 'Attivare il supporto DKIM?',
- 'description' => 'Vuoi utilizzare il sistema Domain Keys (DKIM)?',
- ],
- 'dkim_algorithm' => [
- 'title' => 'Ammessi Algoritmi Hash',
- 'description' => 'Definire gli algoritmi di hash permessi, scegliere "Tutti" per permettere tutti gli algoritmi oppure uno o più tra gli altri algoritmi disponibili',
- ],
- 'dkim_servicetype' => 'Tipi di Servizio',
- 'dkim_keylength' => [
- 'title' => 'Lunghezza Chiave',
- 'description' => 'Attenzione: Se si modifica questo valore è necessario eliminare tutte le chiavi private/pubbliche in "%s"',
- ],
- 'dkim_notes' => [
- 'title' => 'Note DKIM',
- 'description' => 'Nota potrebbe essere di interesse, es. un URL come http://www.dnswatch.info. Nessuna interpretazione è fatta da nessun programma. Questo tag deve essere usato con parsimonia per ragioni di spazio nel DNS. Questo è destinato ad essere utilizzato dagli amministratori e non dagli utenti finali.',
- ],
- ],
'dns' => [
'destinationip' => 'Dominio IP',
'a_record' => 'A-Record (IPv6 optionale)',
diff --git a/lng/nl.lng.php b/lng/nl.lng.php
index 02548024c5..396ed4102f 100644
--- a/lng/nl.lng.php
+++ b/lng/nl.lng.php
@@ -328,41 +328,6 @@
'mail_quota' => 'Mailquotum',
'sendinfomail' => 'Stuur gegevens naar mij via e-mail',
],
- 'dkim' => [
- 'dkim_prefix' => [
- 'title' => 'Prefix',
- 'description' => 'Geef het pad naar de DKIM RSA-files alsook naar de configuratie van de Milter-plugin',
- ],
- 'dkim_domains' => [
- 'title' => 'Bestandsnaam domeinen',
- 'description' => 'Bestandsnaam van het DKIM Domains-parameter zoals aangegeven in de configuratie van dkim-milter',
- ],
- 'dkim_dkimkeys' => [
- 'title' => 'KeyList filename',
- 'description' => 'Bestandsnaam van het DKIM KeyList-parameter zoals aangegeven in de configuratie van dkim-milter',
- ],
- 'dkimrestart_command' => [
- 'title' => 'Herstart-commando voor Milter',
- 'description' => 'Geef het commando om de milter-plugin te herstarten',
- ],
- 'use_dkim' => [
- 'title' => 'Activeer ondersteuning voor DKIM?',
- 'description' => 'Wilt u gebruikmaken van Domain Keys (DKIM) systeem?',
- ],
- 'dkim_algorithm' => [
- 'title' => 'Toegestane hash-algoritmen',
- 'description' => 'Toegestane hash-algoritmen, kies "Alle" voor alle algoritmen of 1 of meerdere van onderstaande',
- ],
- 'dkim_servicetype' => 'Type services',
- 'dkim_keylength' => [
- 'title' => 'Lengte sleutel',
- 'description' => 'Let op: Indien u deze waarde wijzigt, dient u allen geheime en publieke sleutels in "%s" te verwijderen',
- ],
- 'dkim_notes' => [
- 'title' => 'Notities voor DKIM',
- 'description' => 'Notities die van belang kunnen zijn voor mensen, bijvoorbeeld een URL als http://www.dnswatch.info. Geen enkel programma zal deze informatie verwerken. Deze informatie dient schaars te zijn gezien de beperkte ruimte in DNS. Dit is bedoeld voor beheerders, niet voor eindgebruikers.',
- ],
- ],
'dns' => [
'destinationip' => 'IP domein',
'standardip' => 'Standaard server IP',
diff --git a/templates/Froxlor/assets/js/bootstrap.js b/templates/Froxlor/assets/js/bootstrap.js
index 101c70bf55..dd83cd3f85 100644
--- a/templates/Froxlor/assets/js/bootstrap.js
+++ b/templates/Froxlor/assets/js/bootstrap.js
@@ -14,6 +14,9 @@ window.bootstrap = bootstrap;
import Chart from 'chart.js/auto';
window.Chart = Chart;
+// set a default theme
+window.$theme = 'Froxlor';
+
// Axios
import axios from 'axios';
window.axios = axios;
diff --git a/templates/Froxlor/assets/js/jquery/global.js b/templates/Froxlor/assets/js/jquery/global.js
index 58202f3faf..067c4dffc6 100644
--- a/templates/Froxlor/assets/js/jquery/global.js
+++ b/templates/Froxlor/assets/js/jquery/global.js
@@ -8,13 +8,15 @@ export default function () {
history.back(1);
})
- $('#copySysInfo').on('click', function (e) {
- e.preventDefault();
- navigator.clipboard.writeText($('#ccSysInfo').text().trim());
- })
-
$('[data-bs-toggle="popover"]').each(function () {
new bootstrap.Popover($(this));
})
+
+ $('.copyClipboard').on('click', function (e) {
+ e.preventDefault();
+ const source_element = $(this).data('clipboard-source').text();
+ navigator.clipboard.writeText($('#' + source_element).text().trim());
+ })
+
});
}
diff --git a/templates/Froxlor/form/formfields.html.twig b/templates/Froxlor/form/formfields.html.twig
index 0aed40ad8e..c8dfcab36f 100644
--- a/templates/Froxlor/form/formfields.html.twig
+++ b/templates/Froxlor/form/formfields.html.twig
@@ -60,6 +60,8 @@
{{ _self.select(id, field) }}
{% elseif field.type == 'textarea' %}
{{ _self.textarea(id, field) }}
+ {% elseif field.type == 'longtext' %}
+ {{ _self.longtext(id, field) }}
{% elseif field.type == 'label' %}
{{ _self.plain(id, field) }}
{% elseif field.type == 'link' %}
@@ -155,6 +157,15 @@
{% endif %}
{% endmacro %}
+{% macro longtext(id, field) %}
+
+
+
+
+
{{ field.value|raw }}
+
+{% endmacro %}
+
{% macro input(id, field) %}
{% if field.next_to is defined %}
diff --git a/templates/Froxlor/settings/configuration.html.twig b/templates/Froxlor/settings/configuration.html.twig
index e258c473c3..ee48d06755 100644
--- a/templates/Froxlor/settings/configuration.html.twig
+++ b/templates/Froxlor/settings/configuration.html.twig
@@ -79,7 +79,8 @@
(dtype == 'bind' and get_setting('system.bind_enable') == '1' and get_setting('system.dns_server') == 'Bind') or
(dtype == 'powerdns' and get_setting('system.bind_enable') == '1' and get_setting('system.dns_server') == 'PowerDNS') or
(dtype == 'proftpd' and get_setting('system.ftpserver') == 'proftpd') or
- (dtype == 'pureftpd' and get_setting('system.ftpserver') == 'pureftpd')
+ (dtype == 'pureftpd' and get_setting('system.ftpserver') == 'pureftpd') or
+ (dtype == 'rspamd' and get_setting('antispam.activated') == '1')
%}
{% set recommended = true %}
{% endif %}
diff --git a/templates/Froxlor/user/index.html.twig b/templates/Froxlor/user/index.html.twig
index c788b01236..feacc05822 100644
--- a/templates/Froxlor/user/index.html.twig
+++ b/templates/Froxlor/user/index.html.twig
@@ -67,7 +67,7 @@
{{ lng('admin.systemdetails') }}