Merge remote-tracking branch 'origin/main' into v2.2

This commit is contained in:
Michael Kaufmann
2025-01-17 08:48:56 +01:00
36 changed files with 801 additions and 400 deletions

View File

@@ -268,7 +268,7 @@ return [
'dovecot' => 'dovecot (imap/pop3)',
'proftpd' => 'proftpd (ftp)',
],
'save_method' => 'storeSettingField',
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
'advanced_mode' => true
],
'system_le_renew_hook' => [
@@ -278,7 +278,7 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => 'systemctl restart postfix dovecot proftpd',
'save_method' => 'storeSettingField',
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
'advanced_mode' => true,
'required_otp' => true
],

View File

@@ -58,6 +58,51 @@ return [
'save_method' => 'storeSettingField',
'required_otp' => true
],
'antispam_default_bypass_spam' => [
'label' => lng('antispam.default_bypass_spam'),
'settinggroup' => 'antispam',
'varname' => 'default_bypass_spam',
'type' => 'select',
'default' => 2,
'select_var' => [
1 => lng('antispam.default_select.on_changeable'),
2 => lng('antispam.default_select.off_changeable'),
3 => lng('antispam.default_select.on_unchangeable'),
4 => lng('antispam.default_select.off_unchangeable'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'antispam_default_spam_rewrite_subject' => [
'label' => lng('antispam.default_spam_rewrite_subject'),
'settinggroup' => 'antispam',
'varname' => 'default_spam_rewrite_subject',
'type' => 'select',
'default' => 1,
'select_var' => [
1 => lng('antispam.default_select.on_changeable'),
2 => lng('antispam.default_select.off_changeable'),
3 => lng('antispam.default_select.on_unchangeable'),
4 => lng('antispam.default_select.off_unchangeable'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'antispam_default_policy_greylist' => [
'label' => lng('antispam.default_policy_greylist'),
'settinggroup' => 'antispam',
'varname' => 'default_policy_greylist',
'type' => 'select',
'default' => 1,
'select_var' => [
1 => lng('antispam.default_select.on_changeable'),
2 => lng('antispam.default_select.off_changeable'),
3 => lng('antispam.default_select.on_unchangeable'),
4 => lng('antispam.default_select.off_unchangeable'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'antispam_dkim_keylength' => [
'label' => lng('antispam.dkim_keylength'),
'settinggroup' => 'antispam',
@@ -84,7 +129,7 @@ return [
'settinggroup' => 'spf',
'varname' => 'spf_entry',
'type' => 'text',
'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
'string_regexp' => '/^v=spf[a-z0-9:~?\s\.\-\/]+$/i',
'default' => 'v=spf1 a mx -all',
'save_method' => 'storeSettingField'
],

View File

@@ -319,7 +319,7 @@ if ($page == 'domains' || $page == 'overview') {
$alias_check = $alias_check['count'];
$domain_emails_result_stmt = Database::prepare("
SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders`
SELECT `email`, `email_full`, `destination`, `popaccountid`
FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id
");
Database::pexecute($domain_emails_result_stmt, [
@@ -593,6 +593,23 @@ if ($page == 'domains' || $page == 'overview') {
}
echo 0;
exit();
} elseif ($action == 'jqEmaildomainNote') {
$domainid = intval(Request::post('id'));
$newval = intval(Request::post('newval'));
try {
$json_result = Domains::getLocal($userinfo, [
'id' => $domainid
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ((int)$newval == 0 && $newval != $result['isemaildomain']) {
echo json_encode(['changed' => true, 'info' => lng('admin.emaildomainwarning')]);
exit();
}
echo 0;
exit();
} elseif ($action == 'import') {
if (Request::post('send') == 'send') {
$separator = Validate::validate(Request::post('separator'), 'separator');

View File

@@ -76,6 +76,11 @@
"ext-apcu": "*",
"ext-readline": "*"
},
"config": {
"platform": {
"php": "7.4"
}
},
"autoload": {
"psr-4": {
"Froxlor\\": [

597
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -141,7 +141,6 @@ if ($page == 'overview' || $page == 'domains') {
WHERE `customerid` = :customerid
AND `parentdomainid` = '0'
AND `email_only` = '0'
AND `caneditdomain` = '1'
AND `deactivated` = '0'
ORDER BY `domain` ASC");
Database::pexecute($stmt, [

View File

@@ -228,7 +228,7 @@ if ($page == 'overview' || $page == 'mysqls') {
$new_password = Crypt::validatePassword(Request::post('mysql_password'));
foreach ($allowed_mysqlservers as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($log);
// give permission to the user on every access-host we have

View File

@@ -391,6 +391,9 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('antispam', 'config_file', '/etc/rspamd/local.d/froxlor_settings.conf'),
('antispam', 'reload_command', 'service rspamd restart'),
('antispam', 'dkim_keylength', '1024'),
('antispam', 'default_bypass_spam', '2'),
('antispam', 'default_spam_rewrite_subject', '1'),
('antispam', 'default_policy_greylist', '1'),
('admin', 'show_news_feed', '0'),
('admin', 'show_version_login', '0'),
('admin', 'show_version_footer', '0'),
@@ -731,7 +734,7 @@ opcache.validate_timestamps'),
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.5'),
('panel', 'db_version', '202409280');
('panel', 'db_version', '202412030');
DROP TABLE IF EXISTS `panel_tasks`;

View File

@@ -24,7 +24,9 @@
*/
use Froxlor\Database\Database;
use Froxlor\Database\DbManager;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Install\Update;
use Froxlor\Settings;
@@ -198,3 +200,48 @@ if (Froxlor::isFroxlorVersion('2.2.4')) {
Update::showUpdateStep("Updating from 2.2.4 to 2.2.5", false);
Froxlor::updateToVersion('2.2.5');
}
if (Froxlor::isDatabaseVersion('202409280')) {
Update::showUpdateStep("Adding new antispam settings");
Settings::AddNew("antispam.default_bypass_spam", "2");
Settings::AddNew("antispam.default_spam_rewrite_subject", "1");
Settings::AddNew("antispam.default_policy_greylist", "1");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202411200');
}
if (Froxlor::isDatabaseVersion('202411200')) {
Update::showUpdateStep("Adjusting customer mysql global user");
// get all customers that are not deactivated and that have at least one database (hence a global database-user)
$customers = Database::query("
SELECT DISTINCT c.loginname, c.allowed_mysqlserver
FROM `" . TABLE_PANEL_CUSTOMERS . "` c
LEFT JOIN `" . TABLE_PANEL_DATABASES . "` d ON c.customerid = d.customerid
WHERE c.deactivated = '0' AND d.id IS NOT NULL
");
while ($customer = $customers->fetch(\PDO::FETCH_ASSOC)) {
$current_allowed_mysqlserver = !empty($customer['allowed_mysqlserver']) ? json_decode($customer['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager(FroxlorLogger::getInstanceOf());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
if ($dbm->getManager()->userExistsOnHost($customer['loginname'], $mysql_access_host)) {
// deactivate temporarily
$dbm->getManager()->disableUser($customer['loginname'], $mysql_access_host);
// re-enable
$dbm->getManager()->enableUser($customer['loginname'], $mysql_access_host, true);
}
}
$dbm->getManager()->flushPrivileges();
Database::needRoot(false);
}
}
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202412030');
}

View File

@@ -287,6 +287,15 @@ class Admins extends ApiCommand implements ResourceEntity
'login' => $loginname
], true, true);
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email
], true, true);
if (($loginname_check && strtolower($loginname_check['loginname']) == strtolower($loginname)) || ($loginname_check_admin && strtolower($loginname_check_admin['loginname']) == strtolower($loginname))) {
Response::standardError('loginnameexists', $loginname, true);
} elseif (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) {
@@ -298,6 +307,8 @@ class Admins extends ApiCommand implements ResourceEntity
Response::standardError('loginnameiswrong', $loginname, true);
} elseif (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} elseif ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexists', $email, true);
} else {
if ($customers_see_all != '1') {
$customers_see_all = '0';
@@ -610,8 +621,20 @@ class Admins extends ApiCommand implements ResourceEntity
'admin.email'
], '', true);
}
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email and `adminid` <> :adminid
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email,
'adminid' => $id,
], true, true);
if (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} elseif ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexists', $email, true);
} else {
if ($deactivated != '1') {
$deactivated = '0';

View File

@@ -505,6 +505,15 @@ class Customers extends ApiCommand implements ResourceEntity
'login' => $loginname
], true, true);
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email
], true, true);
$mysql_maxlen = Database::getSqlUsernameLength() - strlen(Settings::Get('customer.mysqlprefix'));
if (($loginname_check && strtolower($loginname_check['loginname']) == strtolower($loginname)) || ($loginname_check_admin && strtolower($loginname_check_admin['loginname']) == strtolower($loginname))) {
Response::standardError('loginnameexists', $loginname, true);
@@ -514,6 +523,8 @@ class Customers extends ApiCommand implements ResourceEntity
} else {
Response::standardError('loginnameiswrong', $loginname, true);
}
} elseif ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexistsanon', $email, true);
}
$guid = intval(Settings::Get('system.lastguid')) + 1;
@@ -1106,7 +1117,7 @@ class Customers extends ApiCommand implements ResourceEntity
$email = $this->getParam('email', true, $idna_convert->decode($result['email']));
$name = $this->getParam('name', true, $result['name']);
$firstname = $this->getParam('firstname', true, $result['firstname']);
$company_required = (!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname));
$company_required = ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname))) && empty($result['company']);
$company = $this->getParam('company', !$company_required, $result['company']);
$street = $this->getParam('street', true, $result['street']);
$zipcode = $this->getParam('zipcode', true, $result['zipcode']);
@@ -1243,6 +1254,18 @@ class Customers extends ApiCommand implements ResourceEntity
], '', true);
} elseif (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} else {
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email
], true, true);
if ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexistsanon', $email, true);
}
}
}
@@ -1347,7 +1370,7 @@ class Customers extends ApiCommand implements ResourceEntity
$current_allowed_mysqlserver = isset($result['allowed_mysqlserver']) && !empty($result['allowed_mysqlserver']) ? json_decode($result['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($this->logger());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {

View File

@@ -178,7 +178,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
}
}
} elseif ($type == 'CAA' && !empty($content)) {
$re = '/(?\'critical\'\d)\h*(?\'type\'iodef|issue|issuewild)\h*(?\'value\'(?\'issuevalue\'"(?\'domain\'(?=.{3,128}$)(?>(?>[a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)*(?>[a-zA-Z]{2,}|[a-zA-Z0-9]{2,}\.[a-zA-Z]{2,}))[;\h]*(?\'parameters\'(?>[a-zA-Z0-9]{1,60}=[a-zA-Z0-9]{1,60}\h*)+)?")|(?\'iodefvalue\'"(?\'url\'(mailto:.*|http:\/\/.*|https:\/\/.*))"))/';
$re = '/(?\'critical\'\d+)\h*(?\'type\'iodef|issue|issuewild)\h*(?\'value\'(?\'issuevalue\'"(?\'domain\'(?=.{3,128}$)(?>(?>[a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)*(?>[a-zA-Z]{2,}|[a-zA-Z0-9]{2,}\.[a-zA-Z]{2,}))[;\h]*(?\'parameters\'(?>[a-zA-Z0-9]{1,60}=[a-zA-Z0-9:\.\/\-]{1,60}\h*)+)?")|(?\'iodefvalue\'"(?\'url\'(mailto:.*|http:\/\/.*|https:\/\/.*))"))/';
preg_match($re, $content, $matches);
if (empty($matches)) {

View File

@@ -1063,6 +1063,9 @@ class Domains extends ApiCommand implements ResourceEntity
* (default yes), 3 = always, default 0 (never)
* @param bool $isemaildomain
* optional, allow email usage with this domain, default 0 (false)
* @param bool $emaildomainverified
* optional, when setting $isemaildomain to false, this needs to be set to true to confirm the action in case email addresses exist for this domain,
* default 0 (false)
* @param bool $email_only
* optional, restrict domain to email usage, default 0 (false)
* @param int $selectserveralias
@@ -1190,6 +1193,7 @@ class Domains extends ApiCommand implements ResourceEntity
$subcanemaildomain = $this->getParam('subcanemaildomain', true, $result['subcanemaildomain']);
$isemaildomain = $this->getBoolParam('isemaildomain', true, $result['isemaildomain']);
$emaildomainverified = $this->getBoolParam('emaildomainverified', true, 0);
$email_only = $this->getBoolParam('email_only', true, $result['email_only']);
$p_serveraliasoption = $this->getParam('selectserveralias', true, -1);
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
@@ -1273,7 +1277,7 @@ class Domains extends ApiCommand implements ResourceEntity
// count where we are used in email-accounts
$domain_emails_result_stmt = Database::prepare("
SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders`
SELECT `email`, `email_full`, `destination`, `popaccountid`
FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id
");
Database::pexecute($domain_emails_result_stmt, [
@@ -1296,6 +1300,10 @@ class Domains extends ApiCommand implements ResourceEntity
}
}
if ($emails > 0 && (int)$isemaildomain == 0 && (int)$result['isemaildomain'] == 1 && (int)$emaildomainverified == 0) {
Response::standardError('emaildomainstillhasaddresses', '', true);
}
// handle change of customer (move domain from customer to customer)
if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') {
// check whether target customer has enough resources
@@ -1571,7 +1579,7 @@ class Domains extends ApiCommand implements ResourceEntity
}
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) {
if ($result['letsencrypt'] != $letsencrypt && $ssl_redirect > 0 && $letsencrypt == 1) {
$ssl_redirect = 2;
}

View File

@@ -260,7 +260,9 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$_mailerror = false;
$mailerr_msg = "";
try {
$this->mailer()->setFrom($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->setFrom(Settings::Get('panel.adminmail'), User::getCorrectUserSalutation($admin));
$this->mailer()->clearReplyTos();
$this->mailer()->addReplyTo($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));
@@ -290,7 +292,9 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$_mailerror = false;
try {
$this->mailer()->setFrom($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->setFrom(Settings::Get('panel.adminmail'), User::getCorrectUserSalutation($admin));
$this->mailer()->clearReplyTos();
$this->mailer()->addReplyTo($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));

View File

@@ -54,13 +54,13 @@ class Emails extends ApiCommand implements ResourceEntity
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param bool $rewrite_subject
* optional, whether to add ***SPAM*** to the email's subject if applicable, default true
* optional, whether to add ***SPAM*** to the email's subject if applicable, default: [antispam.default_spam_rewrite_subject]
* @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
* optional, disable spam-filter entirely, default: [antispam.default_bypass_spam]
* @param boolean $policy_greylist
* optional, enable grey-listing, default: yes
* optional, enable grey-listing, default: [antispam.default_policy_greylist]
* @param boolean $iscatchall
* optional, make this address a catchall address, default: no
* @param int $customerid
@@ -87,13 +87,26 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, 1);
$spam_kill_level = $this->getUlParam('spam_kill_level', 'spam_kill_level_ul', 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, '');
if ((int)Settings::Get('antispam.default_spam_rewrite_subject') <= 2) {
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, (int)Settings::Get('antispam.default_spam_rewrite_subject') == 1 ? 1 : 0);
} else {
$rewrite_subject = (int)Settings::Get('antispam.default_spam_rewrite_subject') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_bypass_spam') <= 2) {
$bypass_spam = $this->getBoolParam('bypass_spam', true, (int)Settings::Get('antispam.default_bypass_spam') == 1 ? 1 : 0);
} else {
$bypass_spam = (int)Settings::Get('antispam.default_bypass_spam') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_policy_greylist') <= 2) {
$policy_greylist = $this->getBoolParam('policy_greylist', true, (int)Settings::Get('antispam.default_policy_greylist') == 1 ? 1 : 0);
} else {
$policy_greylist = (int)Settings::Get('antispam.default_policy_greylist') == 3 ? 1 : 0;
}
// validation
$idna_convert = new IdnaWrapper();
if (substr($domain, 0, 4) != 'xn--') {
@@ -258,13 +271,13 @@ class Emails extends ApiCommand implements ResourceEntity
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param bool $rewrite_subject
* optional, whether to add ***SPAM*** to the email's subject if applicable, default true
* optional, whether to add ***SPAM*** to the email's subject if applicable, default: [antispam.default_spam_rewrite_subject]
* @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
* optional, disable spam-filter entirely, default: [antispam.default_bypass_spam]
* @param boolean $policy_greylist
* optional, enable grey-listing, default: yes
* optional, enable grey-listing, default: [antispam.default_policy_greylist]
* @param boolean $iscatchall
* optional
* @param string $description
@@ -292,13 +305,26 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']);
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, $result['rewrite_subject']);
$spam_kill_level = $this->getUlParam('spam_kill_level', 'spam_kill_level_ul', 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']);
if ((int)Settings::Get('antispam.default_spam_rewrite_subject') <= 2) {
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, $result['rewrite_subject']);
} else {
$rewrite_subject = (int)Settings::Get('antispam.default_spam_rewrite_subject') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_bypass_spam') <= 2) {
$bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
} else {
$bypass_spam = (int)Settings::Get('antispam.default_bypass_spam') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_policy_greylist') <= 2) {
$policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
} else {
$policy_greylist = (int)Settings::Get('antispam.default_policy_greylist') == 3 ? 1 : 0;
}
// if enabling catchall is not allowed by settings, we do not need
// to run update()
if ($iscatchall && $result['iscatchall'] == 0 && Settings::Get('catchall.catchall_enabled') != '1') {

View File

@@ -113,9 +113,9 @@ class Mysqls extends ApiCommand implements ResourceEntity
if (strlen($newdb_params['loginname'] . '_' . $databasename) > Database::getSqlUsernameLength()) {
throw new Exception("Database name cannot be longer than " . (Database::getSqlUsernameLength() - strlen($newdb_params['loginname'] . '_')) . " characters.", 406);
}
$username = $dbm->createDatabase($newdb_params['loginname'] . '_' . $databasename, $password, $dbserver);
$username = $dbm->createDatabase($newdb_params['loginname'] . '_' . $databasename, $password, $dbserver, 0, $newdb_params['loginname']);
} else {
$username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, $newdb_params['mysql_lastaccountnumber']);
$username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, $newdb_params['mysql_lastaccountnumber'], $newdb_params['loginname']);
}
// we've checked against the password in dbm->createDatabase
@@ -541,7 +541,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
// Begin root-session
Database::needRoot(true, $result['dbserver'], false);
$dbm = new DbManager($this->logger());
$dbm->getManager()->deleteDatabase($result['databasename']);
$dbm->getManager()->deleteDatabase($result['databasename'], $customer['loginname']);
Database::needRoot(false);
// End root-session

View File

@@ -503,8 +503,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get subdomain '" . $result['domain'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
throw new Exception("Subdomain with " . $key . " could not be found", 404);
throw new Exception("Requested subdomain could not be found", 404);
}
private function getHasCertValueForDomain(int $domainid, int $parentdomainid): int

View File

@@ -80,6 +80,7 @@ final class MasterCron extends CliCommand
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
$jobs[] = 'tasks';
}
define('CRON_IS_FORCED', 1);

View File

@@ -69,7 +69,8 @@ class AcmeSh extends FroxlorCron
* run the task
*
* @param bool $internal
* @return number
* @return int
* @throws \Exception
*/
public static function run(bool $internal = false)
{
@@ -85,6 +86,9 @@ class AcmeSh extends FroxlorCron
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
// insert task to generate certificates and vhost-configs
Cronjob::inserttask(TaskId::REBUILD_VHOST);
if ($renew_froxlor) {
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
}
}
return 0;
}
@@ -217,6 +221,7 @@ class AcmeSh extends FroxlorCron
* check whether we need to issue a new certificate for froxlor itself
*
* @return boolean
* @throws \Exception
*/
private static function issueFroxlorVhost()
{
@@ -340,6 +345,7 @@ EOC;
* check whether we need to renew-check the certificate for froxlor itself
*
* @return boolean
* @throws \Exception
*/
private static function renewFroxlorVhost()
{
@@ -539,6 +545,7 @@ EOC;
* @param array $domains
* @param int $domain_id
* @param FroxlorLogger $cronlog
* @throws \Exception
*/
private static function validateDns(array &$domains, $domain_id, &$cronlog)
{
@@ -619,27 +626,47 @@ EOC;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate");
$cert_stored = self::certToDb($certrow, $cronlog, $acme_result);
if ($cert_stored
&& $renew_hook
&& !empty(trim(Settings::Get('system.le_renew_services') ?? ""))
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
if ($cert_stored && $renew_hook) {
self::renewHookConfigs($cronlog);
}
}
}
}
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
$ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer');
public static function renewHookConfigs($cronlog)
{
if (!empty(trim(Settings::Get('system.le_renew_services') ?? ""))
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
) {
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
// "postconf -e" for postfix
FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain));
FileDir::safe_exec('postconf -e smtpd_tls_key_file=' . escapeshellarg($keyfile));
}
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
// custom config for dovecot
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
$ssl_content = <<<EOSSL
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
if (empty($certificate_folder)) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No certificate folder for '" . Settings::Get('system.hostname') . "' found");
return;
}
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
$ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer');
if (!file_exists($fullchain) || !file_exists($keyfile) || !file_exists($ca_file)) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "At least one of the required certificate files for '" . Settings::Get('system.hostname') . "' could not be found");
return;
}
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
// "postconf -e" for postfix
FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain));
FileDir::safe_exec('postconf -e smtpd_tls_key_file=' . escapeshellarg($keyfile));
}
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
// custom config for dovecot
$ssl_content = <<<EOSSL
# Autogenerated configuration by froxlor.
# Do not manually edit this file as it will be overwritten.
@@ -647,33 +674,35 @@ ssl = yes
ssl_cert = <{$fullchain}
ssl_key = <{$keyfile}
EOSSL;
file_put_contents($dovecot_conf, $ssl_content);
}
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
$proftpd_conf = '/etc/proftpd/tls.conf'; // @fixme setting?
$rval = false;
// ECC certificate or not?
if (strpos($certificate_folder, '_ecc') === false) {
// comment out ECC related settings
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateFile|# TLSECCertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateKeyFile|# TLSECCertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
// add RSA directives
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateFile.*|TLSRSACertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateKeyFile.*|TLSRSACertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
} else {
// comment out RSA related settings
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateFile|# TLSRSACertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateKeyFile|# TLSRSACertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
// add ECC directives
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateFile.*|TLSECCertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateKeyFile.*|TLSECCertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
}
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSCACertificateFile.*|TLSCACertificateFile " . $ca_file . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
}
// reload the services
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
}
file_put_contents($dovecot_conf, $ssl_content);
} elseif (file_exists($dovecot_conf)) {
// safely remove the autogenerated config file
unlink($dovecot_conf);
}
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
$proftpd_conf = '/etc/proftpd/tls.conf'; // @fixme setting?
$rval = false;
// ECC certificate or not?
if (strpos($certificate_folder, '_ecc') === false) {
// comment out ECC related settings
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateFile|# TLSECCertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateKeyFile|# TLSECCertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
// add RSA directives
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateFile.*|TLSRSACertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateKeyFile.*|TLSRSACertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
} else {
// comment out RSA related settings
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateFile|# TLSRSACertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateKeyFile|# TLSRSACertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
// add ECC directives
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateFile.*|TLSECCertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateKeyFile.*|TLSECCertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
}
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSCACertificateFile.*|TLSCACertificateFile " . $ca_file . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
}
// reload the services
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
}
}

View File

@@ -29,6 +29,7 @@ use Exception;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Cron\Http\ConfigIO;
use Froxlor\Cron\Http\HttpConfigBase;
use Froxlor\Cron\Http\LetsEncrypt\AcmeSh;
use Froxlor\Cron\Mail\Rspamd;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
@@ -125,6 +126,12 @@ class TasksCron extends FroxlorCron
*/
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::UPDATE_LE_SERVICES) {
/**
* TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate
*/
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Updating Let's Encrypt configuration for selected services");
AcmeSh::renewHookConfigs(FroxlorLogger::getInstanceOf());
}
}

View File

@@ -87,6 +87,11 @@ final class TaskId
*/
const DELETE_DOMAIN_SSL = 12;
/**
* TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate
*/
const UPDATE_LE_SERVICES = 13;
/**
* TYPE=20 CUSTUMER DATA DUMP
*/

View File

@@ -133,7 +133,9 @@ class ReportsCron extends FroxlorCron
$_mailerror = false;
$mailerr_msg = "";
try {
$mail->SetFrom($row['adminmail'], $row['adminname']);
$mail->setFrom(Settings::Get('panel.adminmail'), $row['adminname']);
$mail->clearReplyTos();
$mail->addReplyTo($row['adminmail'], $row['adminname']);
$mail->Subject = $mail_subject;
$mail->AltBody = $mail_body;
$mail->MsgHTML(nl2br($mail_body));
@@ -405,7 +407,9 @@ class ReportsCron extends FroxlorCron
$_mailerror = false;
$mailerr_msg = "";
try {
$mail->SetFrom($row['adminmail'], $row['adminname']);
$mail->setFrom(Settings::Get('panel.adminmail'), $row['adminname']);
$mail->clearReplyTos();
$mail->addReplyTo($row['adminmail'], $row['adminname']);
$mail->Subject = $mail_subject;
$mail->AltBody = $mail_body;
$mail->MsgHTML(nl2br($mail_body));

View File

@@ -102,8 +102,26 @@ class DbManager
$databases[$databases_row['dbserver']][] = $databases_row['databasename'];
}
$customers_sel = Database::query("
SELECT DISTINCT c.loginname
FROM `" . TABLE_PANEL_CUSTOMERS . "` c
LEFT JOIN `" . TABLE_PANEL_DATABASES . "` d ON c.customerid = d.customerid
WHERE c.deactivated = '0' AND d.id IS NOT NULL
");
$customers = [];
while ($customer = $customers_sel->fetch(\PDO::FETCH_ASSOC)) {
$customers[] = $customer['loginname'];
}
$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
// add all customer loginnames to the $databases array for this database-server to correct
// a possible existing global mysql-user for that customer
foreach ($customers as $customer) {
$databases[$dbserver['dbserver']][] = $customer;
}
// require privileged access for target db-server
Database::needRoot(true, $dbserver['dbserver'], false);
@@ -136,6 +154,8 @@ class DbManager
$dbm->getManager()->flushPrivileges();
Database::needRoot(false);
unset($databases[$dbserver['dbserver']]);
}
}
@@ -149,13 +169,14 @@ class DbManager
* @param ?string $password
* @param int $dbserver
* @param int $last_accnumber
* @param ?string $global_user
*
* @return string|bool $username if successful or false of username is equal to the password
* @throws Exception
*/
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0)
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0, string $global_user = "")
{
Database::needRoot(true, $dbserver, false);
Database::needRoot(true, $dbserver, true);
// check whether we shall create a random username
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') {
@@ -184,6 +205,9 @@ class DbManager
// and give permission to the user on every access-host we have
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host);
if (!empty($global_user)) {
$this->getManager()->grantCreateToDb($global_user, $username, $mysql_access_host);
}
}
$this->getManager()->flushPrivileges();

View File

@@ -110,13 +110,21 @@ class DbManagerMySQL
"password" => $password
]);
// grant privileges
$grants = "ALL";
if ($grant_access_prefix) {
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
}
$stmt = Database::prepare("
GRANT ALL ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO :username@:host
GRANT " . $grants . " ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO :username@:host
");
Database::pexecute($stmt, [
"username" => $username,
"host" => $access_host
]);
if ($grant_access_prefix) {
$this->grantCreateToCustomerDbs($username, $access_host);
}
} else {
// set password
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.6', '<') || version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
@@ -145,9 +153,10 @@ class DbManagerMySQL
* takes away any privileges from a user to that db
*
* @param string $dbname
* @param ?string $global_user
* @throws \Exception
*/
public function deleteDatabase(string $dbname)
public function deleteDatabase(string $dbname, string $global_user = "")
{
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
@@ -167,11 +176,19 @@ class DbManagerMySQL
} else {
$drop_stmt = Database::prepare("DROP USER IF EXISTS :dbname@:host");
}
$rev_stmt = Database::prepare("REVOKE ALL PRIVILEGES ON `" . $dbname . "`.* FROM :guser@:host;");
while ($host = $host_res_stmt->fetch(PDO::FETCH_ASSOC)) {
Database::pexecute($drop_stmt, [
'dbname' => $dbname,
'host' => $host['Host']
], false);
if (!empty($global_user)) {
Database::pexecute($rev_stmt, [
'guser' => $global_user,
'host' => $host['Host']
], false);
}
}
$drop_stmt = Database::prepare("DROP DATABASE IF EXISTS `" . $dbname . "`");
@@ -231,8 +248,15 @@ class DbManagerMySQL
{
// check whether user exists to avoid errors
if ($this->userExistsOnHost($username, $host)) {
Database::query('GRANT ALL PRIVILEGES ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
Database::query('GRANT ALL PRIVILEGES ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
$grants = "ALL PRIVILEGES";
if ($grant_access_prefix) {
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
}
Database::query('GRANT ' . $grants . ' ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
Database::query('GRANT ' . $grants . ' ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
if ($grant_access_prefix) {
$this->grantCreateToCustomerDbs($username, $host);
}
}
}
@@ -292,4 +316,51 @@ class DbManagerMySQL
}
return $allsqlusers;
}
/**
* grant "CREATE" for prefix user to all existing databases of that customer
*
* @param string $username
* @param string $access_host
* @return void
* @throws \Exception
*/
private function grantCreateToCustomerDbs(string $username, string $access_host)
{
$cus_stmt = Database::prepare("SELECT customerid FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE loginname = :username");
$cust = Database::pexecute_first($cus_stmt, ['username' => $username]);
if ($cust) {
$sel_stmt = Database::prepare("SELECT databasename FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid");
Database::pexecute($sel_stmt, ['cid' => $cust['customerid']]);
while ($dbdata = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$stmt = Database::prepare("
GRANT ALL ON `" . $dbdata['databasename'] . "`.* TO :username@:host
");
Database::pexecute($stmt, [
"username" => $username,
"host" => $access_host
]);
}
}
}
/**
* grant "CREATE" for prefix user to all existing databases of that customer
*
* @param string $username
* @param string $database
* @param string $access_host
* @return void
* @throws \Exception
*/
public function grantCreateToDb(string $username, string $database, string $access_host)
{
$stmt = Database::prepare("
GRANT ALL ON `" . $database . "`.* TO :username@:host
");
Database::pexecute($stmt, [
"username" => $username,
"host" => $access_host
]);
}
}

View File

@@ -34,7 +34,7 @@ final class Froxlor
const VERSION = '2.2.5';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202409280';
const DBVERSION = '202412030';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';

View File

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

View File

@@ -68,9 +68,9 @@ class Mailer extends PHPMailer
if (self::ValidateAddress(Settings::Get('panel.adminmail')) !== false) {
// set return-to address and custom sender-name, see #76
$this->SetFrom(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
$this->setFrom(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
if (Settings::Get('panel.adminmail_return') != '') {
$this->AddReplyTo(Settings::Get('panel.adminmail_return'), Settings::Get('panel.adminmail_defname'));
$this->addReplyTo(Settings::Get('panel.adminmail_return'), Settings::Get('panel.adminmail_defname'));
}
}
}

View File

@@ -121,7 +121,7 @@ class UI
'domain' => self::getCookieHost(),
'secure' => self::requestIsHttps(),
'httponly' => true,
'samesite' => 'Strict'
'samesite' => 'Lax'
]);
session_start();

View File

@@ -152,7 +152,7 @@ class User
]);
$customer['emails_used_new'] = (int)$customer_emails['number_emails'];
$customer_emails_result_stmt = Database::prepare('SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders` FROM `' . TABLE_MAIL_VIRTUAL . '`
$customer_emails_result_stmt = Database::prepare('SELECT `email`, `email_full`, `destination`, `popaccountid` FROM `' . TABLE_MAIL_VIRTUAL . '`
WHERE `customerid` = :cid');
Database::pexecute($customer_emails_result_stmt, [
"cid" => $customer['customerid']

View File

@@ -213,6 +213,10 @@ return [
'type' => 'hidden',
'value' => '0'
],
'emaildomainverified' => [
'type' => 'hidden',
'value' => '0'
],
]
],
'section_bssl' => [
@@ -433,15 +437,17 @@ return [
'section_d' => [
'title' => lng('admin.nameserversettings'),
'image' => 'icons/domain_edit.png',
'visible' => Settings::Get('system.bind_enable') == '1' && $userinfo['change_serversettings'] == '1',
'visible' => $userinfo['change_serversettings'] == '1',
'fields' => [
'isbinddomain' => [
'visible' => Settings::Get('system.bind_enable') == '1',
'label' => lng('admin.createzonefile'),
'type' => 'checkbox',
'value' => '1',
'checked' => $result['isbinddomain']
],
'zonefile' => [
'visible' => Settings::Get('system.bind_enable') == '1',
'label' => lng('admin.custombindzone'),
'desc' => lng('admin.bindzonewarning'),
'type' => 'text',

View File

@@ -97,7 +97,7 @@ return [
'checked' => (int)$result['iscatchall'],
],
'bypass_spam' => [
'visible' => Settings::Get('antispam.activated') == '1',
'visible' => Settings::Get('antispam.activated') == '1' && (int)Settings::Get('antispam.default_bypass_spam') <= 2,
'label' => lng('antispam.bypass_spam'),
'type' => 'checkbox',
'value' => '1',
@@ -112,7 +112,7 @@ return [
'value' => $result['spam_tag_level'],
],
'spam_rewrite_subject' => [
'visible' => Settings::Get('antispam.activated') == '1',
'visible' => Settings::Get('antispam.activated') == '1' && (int)Settings::Get('antispam.default_spam_rewrite_subject') <= 2,
'label' => lng('antispam.rewrite_subject'),
'type' => 'checkbox',
'value' => '1',
@@ -127,7 +127,7 @@ return [
'value' => $result['spam_kill_level']
],
'policy_greylist' => [
'visible' => Settings::Get('antispam.activated') == '1',
'visible' => Settings::Get('antispam.activated') == '1' && (int)Settings::Get('antispam.default_policy_greylist') <= 2,
'label' => lng('antispam.policy_greylist'),
'type' => 'checkbox',
'value' => '1',

View File

@@ -374,7 +374,7 @@ if (CurrentUser::hasSession()) {
'domain' => UI::getCookieHost(),
'secure' => UI::requestIsHttps(),
'httponly' => true,
'samesite' => 'Strict'
'samesite' => 'Lax'
];
setcookie(session_name(), $_COOKIE[session_name()], $cookie_params);
} else {

View File

@@ -504,6 +504,7 @@ return [
'apiguide' => 'API Guide',
'domain_duplicate' => 'Domain duplizieren',
'domain_duplicate_named' => '%s duplizieren',
'emaildomainwarning' => '<div id="emaildomainnote" class="invalid-feedback">ACHTUNG: Durch die Änderung dieser Einstellung löschen Sie alle bestehenden E-Mail-Adressen und -Konten unwiderruflich.</div>',
],
'apikeys' => [
'no_api_keys' => 'Keine API Keys gefunden',
@@ -640,6 +641,24 @@ return [
'required_spf_dns' => 'Erforderlicher SPF DNS Eintrag',
'required_dmarc_dns' => 'Erforderlicher DMARC DNS Eintrag',
'required_dkim_dns' => 'Erforderlicher DKIM DNS Eintrag',
'default_select' => [
'on_changeable' => 'Aktiviert, einstellbar',
'off_changeable' => 'Deaktiviert, einstellbar',
'on_unchangeable' => 'Aktiviert, nicht einstellbar',
'off_unchangeable' => 'Deaktiviert, nicht einstellbar',
],
'default_bypass_spam' => [
'title' => 'Standardwert: Spamfilter umgehen',
'description' => 'Wählen, ob bei neuen E-Mail-Konten "Spamfilter umgehen" standardmäßig aktiviert ist und ob diese Einstellung vom Kunden angepasst werden kann.<br/>Standard: Deaktiviert, einstellbar'
],
'default_spam_rewrite_subject' => [
'title' => 'Standardwert: Betreff ändern',
'description' => 'Wählen, ob bei neuen E-Mail-Konten "Betreff ändern" standardmäßig aktiviert ist und ob diese Einstellung vom Kunden angepasst werden kann.<br/>Standard: Aktiviert, einstellbar'
],
'default_policy_greylist' => [
'title' => 'Standardwert: Verwende greylisting',
'description' => 'Wählen, ob bei neuen E-Mail-Konten "Verwende greylisting" standardmäßig aktiviert ist und ob diese Einstellung vom Kunden angepasst werden kann.<br/>Standard: Aktiviert, einstellbar'
],
],
'dns' => [
'destinationip' => 'Domain-IP-Adresse(n)',
@@ -769,6 +788,8 @@ return [
'mydocumentroot' => '\'Documentroot\'',
'loginnameexists' => 'Der Login-Name "%s" existiert bereits.',
'emailiswrong' => 'Die E-Mail-Adresse "%s" enthält ungültige Zeichen oder ist nicht vollständig.',
'emailexists' => 'Die E-Mail-Adresse "%s" wird bereits von einem anderen Admin verwendet',
'emailexistsanon' => 'Die E-Mail-Adresse "%s" wird bereits verwendet',
'alternativeemailiswrong' => 'Die angegebene alternative E-Mail Adresse "%s", an welche die Zugangsdaten geschickt werden soll, scheint ungültig zu sein.',
'loginnameiswrong' => 'Der Login-Name "%s" enthält ungültige Zeichen.',
'loginnameiswrong2' => 'Der Login-Name enthält zu viele Zeichen, es sind maximal %s Zeichen erlaubt.',
@@ -956,6 +977,7 @@ return [
'invalidpgppublickey' => 'Der angegebene PGP Public Key ist ungültig',
'invalid_validtime' => 'Wert der valid_time in Sekunden muss zwischen 10 und 120 liegen.',
'customerphpenabledbutnoconfig' => 'Kunde hat PHP aktiviert aber keine PHP-Konfiguration wurde gewählt.',
'emaildomainstillhasaddresses' => 'Maildomain-Flag kann nicht deaktiviert werden, da für diese Domain noch E-Mail-Adressen vorhanden sind.',
],
'extras' => [
'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.<br />Die Änderungen sind erst nach einer kurzen Zeit wirksam.',
@@ -2174,6 +2196,7 @@ Vielen Dank, Ihr Administrator',
'CREATE_CUSTOMER_DATADUMP' => 'Daten-Export für Kunde %s',
'DELETE_DOMAIN_PDNS' => 'Lösche Domain %s von PowerDNS Datenbank',
'DELETE_DOMAIN_SSL' => 'Lösche SSL Dateien von Domain %s',
'UPDATE_LE_SERVICES' => 'Aktualisiere Systemdienste für Let\'s Encrypt',
],
'terms' => 'AGB',
'traffic' => [

View File

@@ -519,6 +519,7 @@ return [
'backups' => [
'backups' => 'Backups',
],
'emaildomainwarning' => '<div id="emaildomainnote" class="invalid-feedback">WARNING: By changing this setting you will delete all existing e-mail addresses and -accounts permanently.</div>',
],
'apcuinfo' => [
'clearcache' => 'Clear APCu cache',
@@ -689,6 +690,24 @@ return [
'required_spf_dns' => 'Required SPF DNS entry',
'required_dmarc_dns' => 'Required DMARC DNS entry',
'required_dkim_dns' => 'Required DKIM DNS entry',
'default_select' => [
'on_changeable' => 'Activated, adjustable',
'off_changeable' => 'Deactivated, adjustable',
'on_unchangeable' => 'Activated, not adjustable',
'off_unchangeable' => 'Deactivated, not adjustable',
],
'default_bypass_spam' => [
'title' => 'Bypass spamfilter default value',
'description' => 'Whether new email accounts have "Bypass spamfilter" activated by default and whether this setting is adjustable by the customer.<br/>Default: Deactivated, adjustable'
],
'default_spam_rewrite_subject' => [
'title' => 'Rewrite subject default value',
'description' => 'Whether new email accounts have "Rewrite subject" activated by default and whether this setting is adjustable by the customer.<br/>Default: Activated, adjustable'
],
'default_policy_greylist' => [
'title' => 'Use greylisting default value',
'description' => 'Whether new email accounts have "Use greylisting" activated by default and whether this setting is adjustable by the customer.<br/>Default: Activated, adjustable'
],
],
'dns' => [
'destinationip' => 'Domain IP(s)',
@@ -840,6 +859,8 @@ return [
'mydocumentroot' => '\'Documentroot\'',
'loginnameexists' => 'Loginname %s already exists',
'emailiswrong' => 'Email-address %s contains invalid characters or is incomplete',
'emailexists' => 'Email-address %s already in use by another admin',
'emailexistsanon' => 'Email-address %s already in use.',
'alternativeemailiswrong' => 'The given alternative email address %s to send the credentials to seems to be invalid',
'loginnameiswrong' => 'Loginname "%s" contains illegal characters.',
'loginnameiswrong2' => 'Loginname contains too many characters. Only %s characters are allowed.',
@@ -1028,6 +1049,7 @@ return [
'invalidpgppublickey' => 'The PGP Public Key is not valid',
'invalid_validtime' => 'Valid time in seconds can only be between 10 and 120',
'customerphpenabledbutnoconfig' => 'Customer has PHP activated but no PHP-configuration was selected.',
'emaildomainstillhasaddresses' => 'Cannot deactivate mail-domain flag, as there are still email-addresses for this domain.',
],
'extras' => [
'description' => 'Here you can add some extras, for example directory protection.<br />The system will need some time to apply the new settings after every change.',
@@ -2308,6 +2330,7 @@ Yours sincerely, your administrator',
'CREATE_CUSTOMER_DATADUMP' => 'Data export job for customer %s',
'DELETE_DOMAIN_PDNS' => 'Delete domain %s from PowerDNS database',
'DELETE_DOMAIN_SSL' => 'Delete ssl files of domain %s',
'UPDATE_LE_SERVICES' => 'Updating system services for Let\'s Encrypt',
],
'terms' => 'Terms of use',
'traffic' => [

9
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"postcss": "^8.1.14",
"resolve-url-loader": "^5.0.0",
"sass": "^1.69.3",
"vite": "^4.5.3",
"vite": "^4.5.5",
"vue": "^3.2.37"
},
"engines": {
@@ -1156,10 +1156,11 @@
}
},
"node_modules/vite": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
"integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",

View File

@@ -19,7 +19,7 @@
"postcss": "^8.1.14",
"resolve-url-loader": "^5.0.0",
"sass": "^1.69.3",
"vite": "^4.5.3",
"vite": "^4.5.5",
"vue": "^3.2.37"
},
"engines": {