Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mail conf task #1238

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
42 changes: 41 additions & 1 deletion actions/admin/settings/150.mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,26 @@
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_mda_conf_dir' => [
'label' => lng('serversettings.mda_conf_dir'),
'settinggroup' => 'system',
'varname' => 'mda_conf_dir',
'type' => 'text',
'string_type' => 'filedir',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use string_type confdir here as this setting will not be a file and we want to check for disallowed paths, also, the (default) value should end with /

'default' => '/etc/dovecot/conf.d',
'save_method' => 'storeSettingField',
'requires_reconf' => ['mail']
],
'system_mda_reload_command' => [
'label' => lng('serversettings.mda_reload_command'),
'settinggroup' => 'system',
'varname' => 'mda_reload_command',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => 'service dovecot restart',
'save_method' => 'storeSettingField',
'required_otp' => true
],
'system_mtaserver' => [
'label' => lng('serversettings.mtaserver'),
'settinggroup' => 'system',
Expand All @@ -162,7 +182,27 @@
'string_emptyallowed' => true,
'save_method' => 'storeSettingField',
'advanced_mode' => true
]
],
'system_mta_conf_dir' => [
'label' => lng('serversettings.mta_conf_dir'),
'settinggroup' => 'system',
'varname' => 'mta_conf_dir',
'type' => 'text',
'string_type' => 'filedir',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as mda conf dir, the type should be confdir and the default value should end with a /

'default' => '/etc/postfix',
'save_method' => 'storeSettingField',
'requires_reconf' => ['mail']
],
'system_mta_reload_command' => [
'label' => lng('serversettings.mta_reload_command'),
'settinggroup' => 'system',
'varname' => 'mta_reload_command',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => 'service postfix restart',
'save_method' => 'storeSettingField',
'required_otp' => true
],
]
]
]
Expand Down
2 changes: 2 additions & 0 deletions install/froxlor.sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,9 @@
('system', 'mdalog', '/var/log/mail.log'),
('system', 'mtalog', '/var/log/mail.log'),
('system', 'mdaserver', 'dovecot'),
('system', 'mda_reload_command', 'service dovecot restart'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing mda_conf_dir and mta_conf_dir, also incrementing db_version is required for correct update handling

('system', 'mtaserver', 'postfix'),
('system', 'mta_reload_command', 'service postfix restart'),
('system', 'mailtraffic_enabled', '1'),
('system', 'cronconfig', '/etc/cron.d/froxlor'),
('system', 'crondreload', 'service cron reload'),
Expand Down
4 changes: 4 additions & 0 deletions install/updates/froxlor/update_2.2.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
Update::showUpdateStep("Adding new settings");
Settings::AddNew("system.le_renew_services", "");
Settings::AddNew("system.le_renew_hook", "systemctl restart postfix dovecot proftpd");
Settings::AddNew("system.mda_reload_command", "service dovecot reload");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a new update section with a new db-version is required or users on that version will not get the updates

Settings::AddNew("system.mda_conf_dir", "/etc/dovecot/conf.d");
Settings::AddNew("system.mta_reload_command", "service postfix reload");
Settings::AddNew("system.mta_conf_dir", "/etc/postfix");
Update::lastStepStatus(0);

Froxlor::updateToDbVersion('202401090');
Expand Down
2 changes: 1 addition & 1 deletion lib/Froxlor/Cli/MasterCron.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,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::REBUILD_RSPAMD, 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, TaskId::REBUILD_MAIL_CONF])) {
Cronjob::inserttask($ttr);
$jobs[] = 'tasks';
} else {
Expand Down
4 changes: 3 additions & 1 deletion lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public static function run(bool $internal = false)
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
// insert task to generate certificates and vhost-configs
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_MAIL_CONF);
}
return 0;
}
Expand Down Expand Up @@ -205,6 +206,7 @@ public static function run(bool $internal = false)
if ($changedetected) {
if (self::$no_inserttask == false) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_MAIL_CONF);
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
} else {
Expand Down Expand Up @@ -632,7 +634,7 @@ private static function runAcmeSh(array $certrow, array $domains, &$cronlog = nu
}
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
// custom config for dovecot
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
$dovecot_conf = Settings::Get('system.mda_conf_dir') . '/99-froxlor.ssl.conf';
$ssl_content = <<<EOSSL
# Autogenerated configuration by froxlor.
# Do not manually edit this file as it will be overwritten.
Expand Down
111 changes: 111 additions & 0 deletions lib/Froxlor/Cron/Mail/Dovecot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

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

namespace Froxlor\Cron\Mail;

use Froxlor\Cron\Http\DomainSSL;
use Froxlor\Cron\Http\WebserverBase;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use PDO;

class Dovecot
{
private $content = "";

public function createVirtualSSLHost()
{
$domains = WebserverBase::getVhostsToCreate();
foreach ($domains as $domain) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'dovecot::createVirtualHosts: creating vhost container for domain ' . $domain['id'] . ', customer ' . $domain['loginname']);
if ($domain['deactivated'] == '0' && $domain['customer_deactivated'] == '0' && $domain['isemaildomain'] == '1'
&& $domain['ssl_enabled'] == '1' && $domain['ssl'] == '1') {
$this->content .= $this->getSSLConf($domain);
}
}
}

private function getSSLConf($domain)
{
$query = "SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` `i`, `" . TABLE_DOMAINTOIP . "` `dip`
WHERE dip.id_domain = :domainid AND i.id = dip.id_ipandports AND i.ssl = '1' ORDER BY i.ssl_cert_file ASC;";

$result_stmt = Database::prepare($query);
Database::pexecute($result_stmt, [
'domainid' => $domain['id']
]);
$content = "";
while ($ipandport = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$domain['ssl_cert_file'] = $ipandport['ssl_cert_file'];
$domain['ssl_key_file'] = $ipandport['ssl_key_file'];
$domain['ssl_ca_file'] = $ipandport['ssl_ca_file'];
$domain['ssl_cert_chainfile'] = $ipandport['ssl_cert_chainfile'];

// SSL STUFF
$dssl = new DomainSSL();
// this sets the ssl-related array-indices in the $domain array
// if the domain has customer-defined ssl-certificates
$dssl->setDomainSSLFilesArray($domain);

if($domain['ssl_cert_file'] != '') {
$content .= 'local_name ' . $domain['domain'] . " {\n";
$content .= ' ssl_cert = <' . FileDir::makeCorrectFile($domain['ssl_cert_file']) . "\n";

if ($domain['ssl_key_file'] != '') {
$content .= ' ssl_key = <' . FileDir::makeCorrectFile($domain['ssl_key_file']) . "\n";
}
$content .="}\n";

}
}

return $content;
}

public function writeConfigs()
{
if($this->content !== "") {
$vhosts_filename = Settings::Get('system.mda_conf_dir') .'/99-froxlor-vhost.ssl.conf';
$vhosts_file = '# ' . basename($vhosts_filename) . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n" . "\n" . $this->content;
$vhosts_file_handler = fopen($vhosts_filename, 'w');
fwrite($vhosts_file_handler, $vhosts_file);
fclose($vhosts_file_handler);
}
}

public function reload()
{
if($this->content !== "") {
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.mda_reload_command')));
}
}

public function init()
{

}
}
108 changes: 108 additions & 0 deletions lib/Froxlor/Cron/Mail/Postfix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

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

namespace Froxlor\Cron\Mail;

use Froxlor\Cron\Http\DomainSSL;
use Froxlor\Cron\Http\WebserverBase;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use PDO;

class Postfix
{
private $content = "";

private $postFixMapFile = "99-froxlor.map";

public function createVirtualSSLHost()
{
$domains = WebserverBase::getVhostsToCreate();
foreach ($domains as $domain) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'dovecot::createVirtualHosts: creating vhost container for domain ' . $domain['id'] . ', customer ' . $domain['loginname']);
if ($domain['deactivated'] == '0' && $domain['customer_deactivated'] == '0' && $domain['isemaildomain'] == '1'
&& $domain['ssl_enabled'] == '1' && $domain['ssl'] == '1') {
$this->content .= $this->getSSLConf($domain);
}
}
}

private function getSSLConf($domain)
{
$query = "SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` `i`, `" . TABLE_DOMAINTOIP . "` `dip`
WHERE dip.id_domain = :domainid AND i.id = dip.id_ipandports AND i.ssl = '1' ORDER BY i.ssl_cert_file ASC;";

$result_stmt = Database::prepare($query);
Database::pexecute($result_stmt, [
'domainid' => $domain['id']
]);
$content = "";
while ($ipandport = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$domain['ssl_cert_file'] = $ipandport['ssl_cert_file'];
$domain['ssl_key_file'] = $ipandport['ssl_key_file'];
$domain['ssl_ca_file'] = $ipandport['ssl_ca_file'];
$domain['ssl_cert_chainfile'] = $ipandport['ssl_cert_chainfile'];

// SSL STUFF
$dssl = new DomainSSL();
// this sets the ssl-related array-indices in the $domain array
// if the domain has customer-defined ssl-certificates
$dssl->setDomainSSLFilesArray($domain);

if($domain['ssl_cert_file'] != '' && $domain['ssl_key_file'] != '') {
$content .= $domain['domain'].' ' . FileDir::makeCorrectFile($domain['ssl_key_file']) . " " . FileDir::makeCorrectFile($domain['ssl_cert_file']). "\n";
}
}

return $content;
}

public function writeConfigs()
{
if($this->content !== "") {
$vhosts_filename = Settings::Get('system.mta_conf_dir') .'/'.$this->postFixMapFile;
FileDir::safe_exec('postconf -e tls_server_sni_maps=hash:'.$vhosts_filename);
$vhosts_file = '# ' . basename($vhosts_filename) . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n" . "\n" . $this->content;
$vhosts_file_handler = fopen($vhosts_filename, 'w');
fwrite($vhosts_file_handler, $vhosts_file);
fclose($vhosts_file_handler);
FileDir::safe_exec('postmap -F hash:'.$vhosts_filename);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this map integrated in the postfix config and being used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, missed the 'postconf -e tls_server_sni_maps' command there, sorry

}
}

public function reload()
{
if($this->content !== "") {
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.mta_reload_command')));
}
}

public function init()
{

}
}
26 changes: 26 additions & 0 deletions lib/Froxlor/Cron/System/TasksCron.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use Froxlor\Cron\FroxlorCron;
use Froxlor\Cron\Http\ConfigIO;
use Froxlor\Cron\Http\HttpConfigBase;
use Froxlor\Cron\Mail\Dovecot;
use Froxlor\Cron\Mail\Postfix;
use Froxlor\Cron\Mail\Rspamd;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
Expand Down Expand Up @@ -125,6 +127,11 @@ public static function run()
*/
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Removing Let's Encrypt entries for domain " . $row['data']['domain']);
Domain::doLetsEncryptCleanUp($row['data']['domain']);
} elseif ($row['type'] == TaskId::REBUILD_MAIL_CONF) {
/**
* TYPE=13 MEANS TO CREATE REBUILD MAIL CONF
*/
self::rebuildMailConfigs();
}
}

Expand Down Expand Up @@ -461,4 +468,23 @@ private static function rebuildAntiSpamConfigs()
$antispam = new Rspamd(FroxlorLogger::getInstanceOf());
$antispam->writeConfigs();
}

private static function rebuildMailConfigs()
{
$toBeConfigure = [];
if (Settings::Get('system.mdaserver') == "dovecot") {
$toBeConfigure[] = Dovecot::class;
}
if (Settings::Get('system.mtaserver') == "postfix") {
$toBeConfigure[] = Postfix::class;
}

foreach($toBeConfigure as $class_name) {
$conf = new $class_name();
$conf->init();
$conf->createVirtualSSLHost();
$conf->writeConfigs();
$conf->reload();
}
}
}
5 changes: 5 additions & 0 deletions lib/Froxlor/Cron/TaskId.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ final class TaskId
*/
const DELETE_DOMAIN_SSL = 12;

/**
* TYPE=13 rebuild mail config
*/
const REBUILD_MAIL_CONF = 13;

/**
* TYPE=20 CUSTUMER DATA DUMP
*/
Expand Down