Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00bdadb6e5 | ||
|
|
05223369c5 | ||
|
|
b45ac3de3c | ||
|
|
11a5c38476 | ||
|
|
8c48c5a840 | ||
|
|
4f4abada6f | ||
|
|
289d59f531 | ||
|
|
f652017c1a | ||
|
|
2a50eb43b3 | ||
|
|
f31c032508 | ||
|
|
228eb244fa | ||
|
|
143d8d42b3 | ||
|
|
4ce739667d | ||
|
|
eb3568fca2 | ||
|
|
dda4c7a846 | ||
|
|
53b7c501bc | ||
|
|
c9e15bf897 | ||
|
|
140c6c9549 | ||
|
|
ddc439d32f | ||
|
|
40aa48a6d4 | ||
|
|
c69b38bc42 | ||
|
|
1fd8b88ed8 | ||
|
|
0318223fec | ||
|
|
53c414be6d | ||
|
|
2f7a2a32ba | ||
|
|
16d77a03cb | ||
|
|
05ca08c5c3 | ||
|
|
4ea7e10304 | ||
|
|
3367f6dbd8 | ||
|
|
197eb7954a | ||
|
|
a1b6125c14 | ||
|
|
d18a9c9d87 | ||
|
|
94046ae6c7 | ||
|
|
bacc6fe073 | ||
|
|
15d3dd4234 | ||
|
|
54cda098c1 | ||
|
|
19995f4345 |
@@ -118,7 +118,7 @@ if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') {
|
||||
'uptime' => duration($cache['start_time'])
|
||||
];
|
||||
|
||||
$overview['mem_used_percentage'] = number_format(($overview['mem_used'] / $overview['mem_avail']) * 100, 1);
|
||||
$overview['mem_used_percentage'] = number_format(($overview['mem_used'] / $overview['mem_size']) * 100, 1);
|
||||
$overview['num_hits_percentage'] = number_format(($overview['num_hits'] / $overview['num_hits_and_misses']) * 100,
|
||||
1);
|
||||
$overview['num_misses_percentage'] = number_format(($overview['num_misses'] / $overview['num_hits_and_misses']) * 100,
|
||||
|
||||
@@ -114,7 +114,7 @@ if ($page == 'message') {
|
||||
$note_msg = lng('message.norecipients');
|
||||
} else {
|
||||
$note_type = 'success';
|
||||
$note_msg = str_replace('%s', $sentitems, lng('message.success'));
|
||||
$note_msg = lng('message.success', [$sentitems]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ if ($page == 'message') {
|
||||
$messages_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/messages/formfield.messages_add.php';
|
||||
|
||||
UI::view('user/form-note.html.twig', [
|
||||
'formaction' => $linker->getLink(['section' => 'message']),
|
||||
'formaction' => $linker->getLink(['section' => 'message', 'action' => '']),
|
||||
'formdata' => $messages_add_data['messages_add'],
|
||||
'actions_links' => [
|
||||
[
|
||||
|
||||
@@ -247,11 +247,7 @@ if ($page == 'email_domain') {
|
||||
if (isset($result['email']) && $result['email'] != '') {
|
||||
if (Request::post('send') == 'send') {
|
||||
try {
|
||||
Emails::getLocal($userinfo, [
|
||||
'id' => $id,
|
||||
'spam_tag_level' => Request::post('spam_tag_level', Rspamd::DEFAULT_MARK_LVL),
|
||||
'spam_kill_level' => Request::post('spam_kill_level', Rspamd::DEFAULT_REJECT_LVL)
|
||||
])->update();
|
||||
Emails::getLocal($userinfo, Request::postAll())->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
@@ -291,88 +287,12 @@ if ($page == 'email_domain') {
|
||||
|
||||
$email_edit_data = include_once dirname(__FILE__) . '/lib/formfields/customer/email/formfield.emails_edit.php';
|
||||
|
||||
if (Settings::Get('catchall.catchall_enabled') != '1') {
|
||||
unset($email_edit_data['emails_edit']['sections']['section_a']['fields']['mail_catchall']);
|
||||
}
|
||||
|
||||
UI::view('user/form.html.twig', [
|
||||
'formaction' => $linker->getLink(['section' => 'email']),
|
||||
'formdata' => $email_edit_data['emails_edit'],
|
||||
'editid' => $id
|
||||
]);
|
||||
}
|
||||
} elseif ($action == 'togglebypass' && $id != 0) {
|
||||
try {
|
||||
$json_result = Emails::getLocal($userinfo, [
|
||||
'id' => $id
|
||||
])->get();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
$result = json_decode($json_result, true)['data'];
|
||||
|
||||
try {
|
||||
Emails::getLocal($userinfo, [
|
||||
'id' => $id,
|
||||
'bypass_spam' => ($result['bypass_spam'] == '1' ? 0 : 1)
|
||||
])->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page,
|
||||
'domainid' => $email_domainid,
|
||||
'action' => 'edit',
|
||||
'id' => $id,
|
||||
]);
|
||||
} elseif ($action == 'togglegreylist' && $id != 0) {
|
||||
try {
|
||||
$json_result = Emails::getLocal($userinfo, [
|
||||
'id' => $id
|
||||
])->get();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
$result = json_decode($json_result, true)['data'];
|
||||
|
||||
try {
|
||||
Emails::getLocal($userinfo, [
|
||||
'id' => $id,
|
||||
'policy_greylist' => ($result['policy_greylist'] == '1' ? 0 : 1)
|
||||
])->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page,
|
||||
'domainid' => $email_domainid,
|
||||
'action' => 'edit',
|
||||
'id' => $id,
|
||||
]);
|
||||
} elseif ($action == 'togglecatchall' && $id != 0) {
|
||||
try {
|
||||
$json_result = Emails::getLocal($userinfo, [
|
||||
'id' => $id
|
||||
])->get();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
$result = json_decode($json_result, true)['data'];
|
||||
|
||||
try {
|
||||
Emails::getLocal($userinfo, [
|
||||
'id' => $id,
|
||||
'iscatchall' => ($result['iscatchall'] == '1' ? 0 : 1)
|
||||
])->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page,
|
||||
'domainid' => $email_domainid,
|
||||
'action' => 'edit',
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
} elseif ($page == 'accounts') {
|
||||
$email_domainid = Request::any('domainid', 0);
|
||||
|
||||
@@ -111,7 +111,7 @@ if ($action == '2fa_entercode') {
|
||||
// when using email-2fa, remove the one-time-code
|
||||
if ($userinfo['type_2fa'] == '1') {
|
||||
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
|
||||
$userinfo = Database::pexecute_first($del_stmt, [
|
||||
Database::pexecute_first($del_stmt, [
|
||||
'uid' => $uid
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ CREATE TABLE `mail_virtual` (
|
||||
`iscatchall` tinyint(1) unsigned NOT NULL default '0',
|
||||
`description` varchar(255) NOT NULL DEFAULT '',
|
||||
`spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0,
|
||||
`rewrite_subject` tinyint(1) NOT NULL default '1',
|
||||
`spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0,
|
||||
`bypass_spam` tinyint(1) NOT NULL default '0',
|
||||
`policy_greylist` tinyint(1) NOT NULL default '1',
|
||||
@@ -495,7 +496,6 @@ opcache.save_comments
|
||||
opcache.use_cwd
|
||||
opcache.fast_shutdown'),
|
||||
('phpfpm', 'ini_admin_values', 'cgi.redirect_status_env
|
||||
date.timezone
|
||||
disable_classes
|
||||
disable_functions
|
||||
error_log
|
||||
@@ -730,8 +730,8 @@ opcache.validate_timestamps'),
|
||||
('panel', 'logo_overridecustom', '0'),
|
||||
('panel', 'settings_mode', '0'),
|
||||
('panel', 'menu_collapsed', '1'),
|
||||
('panel', 'version', '2.2.1'),
|
||||
('panel', 'db_version', '202408140');
|
||||
('panel', 'version', '2.2.3'),
|
||||
('panel', 'db_version', '202409280');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_tasks`;
|
||||
|
||||
@@ -169,3 +169,22 @@ if (Froxlor::isFroxlorVersion('2.2.0')) {
|
||||
Update::showUpdateStep("Updating from 2.2.0 to 2.2.1", false);
|
||||
Froxlor::updateToVersion('2.2.1');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202408140')) {
|
||||
|
||||
Update::showUpdateStep("Adding new rewrite-subject field to email table");
|
||||
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `rewrite_subject` tinyint(1) NOT NULL default '1' AFTER `spam_tag_level`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202409280');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.1')) {
|
||||
Update::showUpdateStep("Updating from 2.2.1 to 2.2.2", false);
|
||||
Froxlor::updateToVersion('2.2.2');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.2')) {
|
||||
Update::showUpdateStep("Updating from 2.2.2 to 2.2.3", false);
|
||||
Froxlor::updateToVersion('2.2.3');
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ $return = [];
|
||||
if (Update::versionInUpdate($current_db_version, '202004140')) {
|
||||
$has_preconfig = true;
|
||||
$description = 'Froxlor can now optionally validate the dns entries of domains that request Lets Encrypt certificates to reduce dns-related problems (e.g. freshly registered domain or updated a-record).';
|
||||
$question = '<strong>Validate DNS of domains when using Lets Encrypt ';
|
||||
$question = '<strong>Validate DNS of domains when using Lets Encrypt</strong>';
|
||||
$return['system_le_domain_dnscheck'] = [
|
||||
'type' => 'checkbox',
|
||||
'value' => 1,
|
||||
|
||||
@@ -738,11 +738,12 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
'adminid' => $this->getUserDetail('adminid'),
|
||||
'docroot' => $documentroot,
|
||||
'phpenabled' => $phpenabled,
|
||||
'openbasedir' => '1'
|
||||
'openbasedir' => '1',
|
||||
'is_stdsubdomain' => 1
|
||||
];
|
||||
$domainid = -1;
|
||||
try {
|
||||
$std_domain = $this->apiCall('Domains.add', $ins_data);
|
||||
$std_domain = $this->apiCall('Domains.add', $ins_data, true);
|
||||
$domainid = $std_domain['id'];
|
||||
} catch (Exception $e) {
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage());
|
||||
|
||||
@@ -274,7 +274,8 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* $override_tls is true
|
||||
* @param string $description
|
||||
* optional custom description (currently not used/shown in the frontend), default empty
|
||||
*
|
||||
* @param bool $is_stdsubdomain (internally)
|
||||
* optional whether this is a standard subdomain for a customer which is being added so no usage is decreased
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
@@ -282,7 +283,8 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
public function add()
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') {
|
||||
$is_stdsubdomain = $this->isInternal() ? $this->getBoolParam('is_stdsubdomain', true, 0) : false;
|
||||
if ($is_stdsubdomain || $this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') {
|
||||
// parameters
|
||||
$p_domain = $this->getParam('domain');
|
||||
|
||||
@@ -795,12 +797,15 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$ins_data['id'] = $domainid;
|
||||
unset($ins_data);
|
||||
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1
|
||||
WHERE `adminid` = :adminid");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'adminid' => $adminid
|
||||
], true, true);
|
||||
if (!$is_stdsubdomain) {
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1
|
||||
WHERE `adminid` = :adminid
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'adminid' => $adminid
|
||||
], true, true);
|
||||
}
|
||||
|
||||
$ins_stmt = Database::prepare("
|
||||
INSERT INTO `" . TABLE_DOMAINTOIP . "` SET
|
||||
|
||||
@@ -69,7 +69,7 @@ class EmailDomains extends ApiCommand implements ResourceEntity
|
||||
$result = [];
|
||||
$query_fields = [];
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT DISTINCT d.domain, e.domainid,
|
||||
SELECT DISTINCT d.domain, d.domain_ace, e.domainid,
|
||||
COUNT(e.email) as addresses,
|
||||
IFNULL(SUM(CASE WHEN e.popaccountid > 0 THEN 1 ELSE 0 END), 0) as accounts,
|
||||
IFNULL(SUM(
|
||||
|
||||
@@ -53,6 +53,8 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
* 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 bool $rewrite_subject
|
||||
* optional, whether to add ***SPAM*** to the email's subject if applicable, default true
|
||||
* @param float $spam_kill_level
|
||||
* optional, score which is required to discard emails, default: 14.0
|
||||
* @param boolean $bypass_spam
|
||||
@@ -85,17 +87,19 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
|
||||
// parameters
|
||||
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
|
||||
$spam_kill_level = $this->getParam('spam_kill_level', true, '14.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, '');
|
||||
|
||||
// validation
|
||||
$idna_convert = new IdnaWrapper();
|
||||
if (substr($domain, 0, 4) != 'xn--') {
|
||||
$idna_convert = new IdnaWrapper();
|
||||
$domain = $idna_convert->encode(Validate::validate($domain, 'domain', '', '', [], true));
|
||||
}
|
||||
$email_part = $idna_convert->encode($email_part);
|
||||
|
||||
// check domain and whether it's an email-enabled domain
|
||||
// use internal call because the customer might have 'domains' in customer_hide_options
|
||||
@@ -103,10 +107,10 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
'domainname' => $domain
|
||||
], true);
|
||||
if ((int)$domain_check['isemaildomain'] == 0) {
|
||||
Response::standardError('maindomainnonexist', $domain, true);
|
||||
Response::standardError('maindomainnonexist', $idna_convert->decode($domain), true);
|
||||
}
|
||||
if ((int)$domain_check['deactivated'] == 1) {
|
||||
Response::standardError('maindomaindeactivated', $domain, true);
|
||||
Response::standardError('maindomaindeactivated', $idna_convert->decode($domain), true);
|
||||
}
|
||||
|
||||
if (Settings::Get('catchall.catchall_enabled') != '1') {
|
||||
@@ -127,7 +131,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validate it
|
||||
if (!Validate::validateEmail($email_full)) {
|
||||
Response::standardError('emailiswrong', $email_full, true);
|
||||
Response::standardError('emailiswrong', $idna_convert->decode($email_full), true);
|
||||
}
|
||||
|
||||
// get needed customer info to reduce the email-address-counter by one
|
||||
@@ -148,14 +152,16 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ($email_check) {
|
||||
if (strtolower($email_check['email_full']) == strtolower($email_full)) {
|
||||
Response::standardError('emailexistalready', $email_full, true);
|
||||
Response::standardError('emailexistalready', $idna_convert->decode($email_full), true);
|
||||
} elseif ($email_check['email'] == $email) {
|
||||
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
|
||||
}
|
||||
}
|
||||
|
||||
$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);
|
||||
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1})?$/', '', [7.0], true);
|
||||
if ($spam_kill_level > -1) {
|
||||
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1})?$/', '', [14.0], true);
|
||||
}
|
||||
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
|
||||
$stmt = Database::prepare("
|
||||
@@ -164,6 +170,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
`email` = :email,
|
||||
`email_full` = :email_full,
|
||||
`spam_tag_level` = :spam_tag_level,
|
||||
`rewrite_subject` = :rewrite_subject,
|
||||
`spam_kill_level` = :spam_kill_level,
|
||||
`bypass_spam` = :bypass_spam,
|
||||
`policy_greylist` = :policy_greylist,
|
||||
@@ -176,6 +183,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
"email" => $email,
|
||||
"email_full" => $email_full,
|
||||
"spam_tag_level" => $spam_tag_level,
|
||||
"rewrite_subject" => $rewrite_subject,
|
||||
"spam_kill_level" => $spam_kill_level,
|
||||
"bypass_spam" => $bypass_spam,
|
||||
"policy_greylist" => $policy_greylist,
|
||||
@@ -226,7 +234,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
|
||||
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
|
||||
AND " . (is_numeric($params['idea']) ? "v.`id`= :idea" : "(v.`email` = :idea OR v.`email_full` = :idea)"
|
||||
));
|
||||
));
|
||||
$result = Database::pexecute_first($result_stmt, $params, true, true);
|
||||
if ($result) {
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get email address '" . $result['email_full'] . "'");
|
||||
@@ -249,6 +257,8 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
* 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 bool $rewrite_subject
|
||||
* optional, whether to add ***SPAM*** to the email's subject if applicable, default true
|
||||
* @param float $spam_kill_level
|
||||
* optional, score which is required to discard emails, default: 14.0
|
||||
* @param boolean $bypass_spam
|
||||
@@ -282,7 +292,8 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
|
||||
// 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']);
|
||||
$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']);
|
||||
@@ -326,13 +337,16 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
|
||||
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
|
||||
if ($spam_kill_level > -1) {
|
||||
$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 ,
|
||||
`spam_tag_level` = :spam_tag_level,
|
||||
`rewrite_subject` = :rewrite_subject,
|
||||
`spam_kill_level` = :spam_kill_level,
|
||||
`bypass_spam` = :bypass_spam,
|
||||
`policy_greylist` = :policy_greylist,
|
||||
@@ -343,6 +357,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
$params = [
|
||||
"email" => $email,
|
||||
"spam_tag_level" => $spam_tag_level,
|
||||
"rewrite_subject" => $rewrite_subject,
|
||||
"spam_kill_level" => $spam_kill_level,
|
||||
"bypass_spam" => $bypass_spam,
|
||||
"policy_greylist" => $policy_greylist,
|
||||
@@ -396,7 +411,10 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)
|
||||
WHERE m.`customerid` IN (" . implode(", ", $customer_ids) . ")" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit());
|
||||
Database::pexecute($result_stmt, $query_fields, true, true);
|
||||
$idna_convert = new IdnaWrapper();
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$row['email'] = $idna_convert->decode($row['email']);
|
||||
$row['email_full'] = $idna_convert->decode($row['email_full']);
|
||||
$result[] = $row;
|
||||
}
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses");
|
||||
|
||||
@@ -217,6 +217,10 @@ final class ConfigServices extends CliCommand
|
||||
$_daemons_config['distro'] = $io->choice('Choose distribution', $valid_dists, $os_default);
|
||||
|
||||
// go through all services and let user check whether to include it or not
|
||||
if (empty($_daemons_config['distro']) || !file_exists($config_dir . '/' . $_daemons_config['distro']. ".xml")) {
|
||||
$output->writeln('<error>Empty or non-existing distribution given.</>');
|
||||
return self::INVALID;
|
||||
}
|
||||
$configfiles = new ConfigParser($config_dir . '/' . $_daemons_config['distro'] . ".xml");
|
||||
$services = $configfiles->getServices();
|
||||
|
||||
@@ -352,8 +356,13 @@ final class ConfigServices extends CliCommand
|
||||
}
|
||||
|
||||
if (!empty($decoded_config)) {
|
||||
|
||||
$config_dir = Froxlor::getInstallDir() . 'lib/configfiles/';
|
||||
$configfiles = new ConfigParser($config_dir . '/' . $decoded_config['distro'] . ".xml");
|
||||
if (empty($decoded_config['distro']) || !file_exists($config_dir . '/' . $decoded_config['distro']. ".xml")) {
|
||||
$output->writeln('<error>Empty or non-existing distribution given. Please login with an admin, go to "System -> Configuration" and select your correct distribution in the top-right corner or specify valid distribution name for "distro" parameter.</>');
|
||||
return self::INVALID;
|
||||
}
|
||||
$configfiles = new ConfigParser($config_dir . '/' . $decoded_config['distro']. ".xml");
|
||||
$services = $configfiles->getServices();
|
||||
$replace_arr = $this->getReplacerArray();
|
||||
|
||||
|
||||
@@ -28,13 +28,17 @@ namespace Froxlor\Cli;
|
||||
use Exception;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Install\AutoUpdate;
|
||||
use Froxlor\Install\Preconfig;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Mailer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
final class UpdateCommand extends CliCommand
|
||||
{
|
||||
@@ -44,6 +48,8 @@ final class UpdateCommand extends CliCommand
|
||||
$this->setName('froxlor:update');
|
||||
$this->setDescription('Check for newer version and update froxlor');
|
||||
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
|
||||
->addOption('show-update-options', 'o', InputOption::VALUE_NONE, 'Show possible update option parameter for the update if any. Only usable in combination with "check-only".')
|
||||
->addOption('update-options', 'O', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Parameter list of update options.')
|
||||
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
|
||||
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
|
||||
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
|
||||
@@ -63,9 +69,13 @@ final class UpdateCommand extends CliCommand
|
||||
$output->writeln('<info>' . lng('update.dbupdate_required') . '</>');
|
||||
if ($input->getOption('check-only')) {
|
||||
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
|
||||
$this->askUpdateOptions($input, $output, null, false);
|
||||
} else {
|
||||
$yestoall = $input->getOption('yes-to-all') !== false;
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
$this->askUpdateOptions($input, $output, $helper, $yestoall);
|
||||
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->runUpdate($output, true);
|
||||
@@ -101,7 +111,7 @@ final class UpdateCommand extends CliCommand
|
||||
}
|
||||
// there is a new version
|
||||
if ($input->getOption('check-only')) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel') . ' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
} else {
|
||||
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
}
|
||||
@@ -152,6 +162,7 @@ final class UpdateCommand extends CliCommand
|
||||
// check whether we only wanted to check
|
||||
if ($input->getOption('check-only')) {
|
||||
//$output->writeln('<comment>Not proceeding as "check-only" is specified</>');
|
||||
$this->askUpdateOptions($input, $output, null, false);
|
||||
return $result;
|
||||
} else {
|
||||
$yestoall = $input->getOption('yes-to-all') !== false;
|
||||
@@ -174,6 +185,9 @@ final class UpdateCommand extends CliCommand
|
||||
if ($auex == 0) {
|
||||
$output->writeln("<info>Froxlor files updated successfully.</>");
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$this->askUpdateOptions($input, $output, $helper, $yestoall);
|
||||
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->runUpdate($output, true);
|
||||
@@ -195,12 +209,141 @@ final class UpdateCommand extends CliCommand
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param $helper
|
||||
* @param bool $yestoall
|
||||
* @return void
|
||||
*/
|
||||
private function askUpdateOptions(InputInterface $input, OutputInterface $output, $helper, bool $yestoall = false)
|
||||
{
|
||||
// check for preconfigs
|
||||
$preconfig = Preconfig::getPreConfig(true);
|
||||
$show_options_only = $input->getOption('show-update-options') !== false;
|
||||
if (!is_null($helper) && $show_options_only) {
|
||||
$output->writeln('<comment>Unsetting "show-update-options" due to not being called with "check-only".</>');
|
||||
$show_options_only = false;
|
||||
}
|
||||
$update_options = [];
|
||||
// set parameters
|
||||
$uOptions = $input->getOption('update-options');
|
||||
if (!empty($uOptions)) {
|
||||
$options_value = [];
|
||||
foreach ($uOptions as $givenOption) {
|
||||
$optVal = explode("=", $givenOption);
|
||||
if (count($optVal) == 2) {
|
||||
$options_value[$optVal[0]] = $optVal[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($preconfig)) {
|
||||
krsort($preconfig);
|
||||
foreach ($preconfig as $section) {
|
||||
if (!$show_options_only) {
|
||||
$output->writeln("<info>Updater questions for " . $section['title'] . "</>");
|
||||
}
|
||||
foreach ($section['fields'] as $update_field => $metainfo) {
|
||||
if (isset($options_value[$update_field])) {
|
||||
$output->writeln('Setting given parameter "' . $update_field . '" to "' . $options_value[$update_field] . '"');
|
||||
$_POST[$update_field] = $options_value[$update_field];
|
||||
continue;
|
||||
}
|
||||
$default = null;
|
||||
$question_text = html_entity_decode(strip_tags($metainfo['label']), ENT_QUOTES | ENT_IGNORE, "UTF-8");
|
||||
if ($metainfo['type'] == 'checkbox') {
|
||||
$default = (int)$metainfo['checked'];
|
||||
if ($show_options_only) {
|
||||
$update_options[] = [
|
||||
'name' => $update_field,
|
||||
'question' => $question_text,
|
||||
'default' => $default,
|
||||
'choices' => '0: No' . PHP_EOL . '1: Yes' . PHP_EOL
|
||||
];
|
||||
} else {
|
||||
$question = new ConfirmationQuestion($question_text . ' [' . ($metainfo['checked'] ? 'yes' : 'no') . '] ', (bool)$metainfo['checked'], '/^(y|j)/i');
|
||||
}
|
||||
} elseif ($metainfo['type'] == 'select') {
|
||||
$default = $metainfo['selected'];
|
||||
$choices = "";
|
||||
foreach (array_values($metainfo['select_var'] ?? []) as $index => $choice) {
|
||||
$choices .= $index . ': ' . $choice . PHP_EOL;
|
||||
}
|
||||
if ($show_options_only) {
|
||||
$update_options[] = [
|
||||
'name' => $update_field,
|
||||
'question' => $question_text,
|
||||
'default' => !empty($default) ? $default : '-',
|
||||
'choices' => $choices
|
||||
];
|
||||
} else {
|
||||
$question = new ChoiceQuestion(
|
||||
$question_text,
|
||||
array_values($metainfo['select_var'] ?? []),
|
||||
$metainfo['selected']
|
||||
);
|
||||
$question->setValidator(function ($answer) use ($metainfo): string {
|
||||
$key = array_keys($metainfo['select_var'])[(int)$answer] ?? false; // Find the key based on the selected value
|
||||
if ($key === false) {
|
||||
throw new \RuntimeException('Invalid selection.');
|
||||
}
|
||||
return $key;
|
||||
});
|
||||
}
|
||||
} elseif ($metainfo['type'] == 'text') {
|
||||
$default = $metainfo['value'] ?? '';
|
||||
if ($show_options_only) {
|
||||
$update_options[] = [
|
||||
'name' => $update_field,
|
||||
'question' => $question_text,
|
||||
'default' => $default,
|
||||
'choices' => PHP_EOL
|
||||
];
|
||||
} else {
|
||||
$question = new Question($question_text . (!empty($metainfo['value']) ? ' [' . $metainfo['value'] . ']' : ''), $default);
|
||||
$question->setValidator(function (string $answer) use ($metainfo): string {
|
||||
if (($metainfo['mandatory'] ?? false) && empty($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'Answer cannot be empty'
|
||||
);
|
||||
}
|
||||
if (!empty($metainfo['pattern'] ?? "") && !preg_match("/" . $metainfo['pattern'] . "/", $answer)) {
|
||||
throw new \RuntimeException('Answer does not seem to be in valid format');
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$output->writeln("<error>Unknown type " . $metainfo['type'] . "</error>");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$show_options_only) {
|
||||
if ($yestoall) {
|
||||
$_POST[$update_field] = $default;
|
||||
} else {
|
||||
$_POST[$update_field] = $helper->ask($input, $output, $question);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($show_options_only) {
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->table(
|
||||
['Parameter', 'Description', 'Default', 'Choices'],
|
||||
$update_options
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mailNotify(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($input->getOption('mail-notify')) {
|
||||
$last_check_version = Settings::Get('system.update_notify_last');
|
||||
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel') . ' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$mail = new Mailer(true);
|
||||
$mail->Body = $text;
|
||||
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
|
||||
|
||||
@@ -321,10 +321,12 @@ EOC;
|
||||
WHERE
|
||||
dom.`customerid` = cust.`customerid`
|
||||
AND cust.deactivated = 0
|
||||
AND dom.deactivated = 0
|
||||
AND dom.`ssl_enabled` = 1
|
||||
AND dom.`letsencrypt` = 1
|
||||
AND dom.`aliasdomain` IS NULL
|
||||
AND dom.`iswildcarddomain` = 0
|
||||
AND dom.`email_only` = 0
|
||||
AND domssl.`validtodate` IS NULL
|
||||
");
|
||||
$customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -383,10 +385,12 @@ EOC;
|
||||
WHERE
|
||||
dom.`customerid` = cust.`customerid`
|
||||
AND cust.deactivated = 0
|
||||
AND dom.deactivated = 0
|
||||
AND dom.`ssl_enabled` = 1
|
||||
AND dom.`letsencrypt` = 1
|
||||
AND dom.`aliasdomain` IS NULL
|
||||
AND dom.`iswildcarddomain` = 0
|
||||
AND dom.`email_only` = 0
|
||||
");
|
||||
$renew_certs = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($renew_certs) {
|
||||
|
||||
@@ -165,7 +165,7 @@ class Rspamd
|
||||
$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['spam_kill_level'] = $email['spam_kill_level'] == -1 ? "null" : floatval($email['spam_kill_level']);
|
||||
$email_id = md5($email['email']);
|
||||
|
||||
$this->frx_settings_file .= '# Email: ' . $email['email'] . "\n";
|
||||
@@ -185,7 +185,9 @@ class Rspamd
|
||||
$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";
|
||||
if ((int)$email['rewrite_subject'] == 1) {
|
||||
$this->frx_settings_file .= ' rewrite_subject = ' . ($email['spam_tag_level'] + 0.01) . ';' . "\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";
|
||||
|
||||
@@ -187,21 +187,23 @@ class DbManagerMySQL
|
||||
*/
|
||||
public function deleteUser(string $username, string $host)
|
||||
{
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
||||
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
|
||||
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`");
|
||||
Database::pexecute($stmt);
|
||||
if ($this->userExistsOnHost($username, $host)) {
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
||||
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
|
||||
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`");
|
||||
Database::pexecute($stmt);
|
||||
}
|
||||
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
|
||||
$stmt = Database::prepare("DROP USER :username@:host");
|
||||
} else {
|
||||
$stmt = Database::prepare("DROP USER IF EXISTS :username@:host");
|
||||
}
|
||||
Database::pexecute($stmt, [
|
||||
"username" => $username,
|
||||
"host" => $host
|
||||
]);
|
||||
}
|
||||
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
|
||||
$stmt = Database::prepare("DROP USER :username@:host");
|
||||
} else {
|
||||
$stmt = Database::prepare("DROP USER IF EXISTS :username@:host");
|
||||
}
|
||||
Database::pexecute($stmt, [
|
||||
"username" => $username,
|
||||
"host" => $host
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,10 +31,10 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.2.1';
|
||||
const VERSION = '2.2.3';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202408140';
|
||||
const DBVERSION = '202409280';
|
||||
|
||||
// Distribution branding-tag (used for Debian etc.)
|
||||
const BRANDING = '';
|
||||
|
||||
@@ -85,27 +85,31 @@ class Preconfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function getPreConfig
|
||||
*
|
||||
* outputs various form-field-arrays before the update process
|
||||
* can be continued (asks for agreement whatever is being asked)
|
||||
*
|
||||
* @param bool $no_check
|
||||
* @return array
|
||||
*/
|
||||
public static function getPreConfig(): array
|
||||
public static function getPreConfig(bool $no_check = false): array
|
||||
{
|
||||
$preconfig = new self();
|
||||
|
||||
if ($preconfig->hasPreConfig()) {
|
||||
$agree = [
|
||||
'title' => 'Check',
|
||||
'fields' => [
|
||||
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
||||
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
||||
]
|
||||
];
|
||||
$preconfig->addToPreConfig($agree);
|
||||
if (!$no_check) {
|
||||
$agree = [
|
||||
'title' => 'Check',
|
||||
'fields' => [
|
||||
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
||||
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
||||
]
|
||||
];
|
||||
$preconfig->addToPreConfig($agree);
|
||||
}
|
||||
return $preconfig->getData();
|
||||
}
|
||||
return [];
|
||||
|
||||
@@ -104,6 +104,10 @@ class MailLogParser
|
||||
unset($matches);
|
||||
$line = fgets($file_handle);
|
||||
|
||||
if (strpos($line, 'postfix') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $this->getLogTimestamp($line);
|
||||
if ($this->startTime < $timestamp) {
|
||||
if (preg_match("/postfix\/qmgr.*(?::|\])\s([A-Z\d]+).*from=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?, size=(\d+),/", $line, $matches)) {
|
||||
@@ -112,7 +116,7 @@ class MailLogParser
|
||||
"domainFrom" => strtolower($matches[2]),
|
||||
"size" => $matches[3]
|
||||
];
|
||||
} elseif (preg_match("/postfix\/(?:pipe|smtp).*(?::|\])\s([A-Z\d]+).*to=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?,/", $line, $matches)) {
|
||||
} elseif (preg_match("/postfix\/(?:pipe|smtp|lmtp).*(?::|\])\s([A-Z\d]+).*to=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?,/", $line, $matches)) {
|
||||
// Postfix to
|
||||
if (array_key_exists($matches[1], $this->mails)) {
|
||||
$this->mails[$matches[1]]["domainTo"] = strtolower($matches[2]);
|
||||
@@ -149,7 +153,7 @@ class MailLogParser
|
||||
private function getLogTimestamp($line)
|
||||
{
|
||||
$matches = null;
|
||||
if (preg_match("/((?:[A-Z]{3}\s{1,2}\d{1,2}|\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2})/i", $line, $matches)) {
|
||||
if (preg_match("/((?:[A-Z]{3}\s{1,2}\d{1,2}|\d{4}-\d{2}-\d{2}).\d{2}:\d{2}:\d{2})/i", $line, $matches)) {
|
||||
$timestamp = strtotime($matches[1]);
|
||||
if ($timestamp > ($this->startTime + 60 * 60 * 24)) {
|
||||
return strtotime($matches[1] . " -1 year");
|
||||
@@ -258,6 +262,10 @@ class MailLogParser
|
||||
unset($matches);
|
||||
$line = fgets($file_handle);
|
||||
|
||||
if (strpos($line, 'dovecot') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $this->getLogTimestamp($line);
|
||||
if ($this->startTime < $timestamp) {
|
||||
if (preg_match("/dovecot.*(?::|\]) imap\(.*@([a-z0-9\.\-]+)\)(<\d+><[a-z0-9+\/=]+>)?:.*(?:in=(\d+) out=(\d+)|bytes=(\d+)\/(\d+))/i", $line, $matches)) {
|
||||
|
||||
@@ -90,59 +90,48 @@ return [
|
||||
]
|
||||
],
|
||||
'mail_catchall' => [
|
||||
'visible' => Settings::Get('catchall.catchall_enabled') == '1',
|
||||
'label' => lng('emails.catchall'),
|
||||
'type' => 'label',
|
||||
'value' => ((int)$result['iscatchall'] == 0 ? lng('panel.no') : lng('panel.yes')),
|
||||
'next_to' => [
|
||||
'add_link' => [
|
||||
'type' => 'link',
|
||||
'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglecatchall&id=' . $result['id'],
|
||||
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
|
||||
'classes' => 'btn btn-sm btn-secondary'
|
||||
]
|
||||
]
|
||||
],
|
||||
'spam_tag_level' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.spam_tag_level'),
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
|
||||
'value' => $result['spam_tag_level']
|
||||
],
|
||||
'spam_kill_level' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.spam_kill_level'),
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
|
||||
'value' => $result['spam_kill_level']
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['iscatchall'],
|
||||
],
|
||||
'bypass_spam' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'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' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
|
||||
'classes' => 'btn btn-sm btn-secondary'
|
||||
]
|
||||
]
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['bypass_spam'],
|
||||
],
|
||||
'spam_tag_level' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.spam_tag_level'),
|
||||
'type' => 'number',
|
||||
'min' => 0,
|
||||
'step' => 0.1,
|
||||
'value' => $result['spam_tag_level'],
|
||||
],
|
||||
'spam_rewrite_subject' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.rewrite_subject'),
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['rewrite_subject'],
|
||||
],
|
||||
'spam_kill_level' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.spam_kill_level'),
|
||||
'desc' => lng('panel.use_checkbox_to_disable'),
|
||||
'type' => 'textul',
|
||||
'step' => 0.1,
|
||||
'value' => $result['spam_kill_level']
|
||||
],
|
||||
'policy_greylist' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'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' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
|
||||
'classes' => 'btn btn-sm btn-secondary'
|
||||
]
|
||||
]
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['policy_greylist'],
|
||||
],
|
||||
'mail_fwds' => [
|
||||
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',
|
||||
|
||||
@@ -33,9 +33,9 @@ return [
|
||||
'title' => lng('menue.email.emailsoverview'),
|
||||
'icon' => 'fa-solid fa-envelope',
|
||||
'self_overview' => ['section' => 'email', 'page' => 'overview'],
|
||||
'default_sorting' => ['d.domain' => 'asc'],
|
||||
'default_sorting' => ['d.domain_ace' => 'asc'],
|
||||
'columns' => [
|
||||
'd.domain' => [
|
||||
'd.domain_ace' => [
|
||||
'label' => 'Domain',
|
||||
'field' => 'domain',
|
||||
],
|
||||
@@ -56,7 +56,7 @@ return [
|
||||
],
|
||||
],
|
||||
'visible_columns' => Listing::getVisibleColumnsForListing('emaildomain_list', [
|
||||
'd.domain',
|
||||
'd.domain_ace',
|
||||
'addresses',
|
||||
'accounts',
|
||||
'forwarder',
|
||||
|
||||
@@ -621,6 +621,10 @@ return [
|
||||
'title' => 'Spam Level',
|
||||
'description' => 'Erforderliche Punktzahl zum Markieren einer E-Mail als Spam<br/>Standard: 7.0'
|
||||
],
|
||||
'rewrite_subject' => [
|
||||
'title' => 'Betreff ändern',
|
||||
'description' => 'Dem E-Mail Betreff <strong>***SPAM***</strong> hinzufügen, sofern zutreffend',
|
||||
],
|
||||
'spam_kill_level' => [
|
||||
'title' => 'Ablehnungs Level',
|
||||
'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail<br/>Standard: 14.0'
|
||||
@@ -1262,6 +1266,7 @@ Vielen Dank, Ihr Administrator',
|
||||
'upload_import' => 'Hochladen und importieren',
|
||||
'profile' => 'Mein Profil',
|
||||
'use_checkbox_for_unlimited' => 'Der Wert "0" deaktiviert die Resource. Die Checkbox rechts erlaubt "unlimitierte" Nutzung.',
|
||||
'use_checkbox_to_disable' => 'Zum Deaktivieren, klicke die Checkbox auf der rechten Seite des Eingabefeldes',
|
||||
],
|
||||
'phpfpm' => [
|
||||
'vhost_httpuser' => 'Lokaler Benutzer für PHP-FPM (Froxlor-Vhost)',
|
||||
|
||||
@@ -670,6 +670,10 @@ return [
|
||||
'title' => 'Spam tag level',
|
||||
'description' => 'Score that is required to mark an email as spam<br/>Default: 7.0'
|
||||
],
|
||||
'rewrite_subject' => [
|
||||
'title' => 'Rewrite subject',
|
||||
'description' => 'Whether to add <strong>***SPAM***</strong> to the email subject if applicable',
|
||||
],
|
||||
'spam_kill_level' => [
|
||||
'title' => 'Spam kill level',
|
||||
'description' => 'Score that is required to discard an email entirely<br/>Default: 14.0'
|
||||
@@ -1377,6 +1381,7 @@ Yours sincerely, your administrator',
|
||||
'upload_import' => 'Upload and import',
|
||||
'profile' => 'My profile',
|
||||
'use_checkbox_for_unlimited' => 'The value "0" deactivates this resource. The checkbox on the right allows "unlimited" usage.',
|
||||
'use_checkbox_to_disable' => 'To disable, activate the checkbox on the right of the input field',
|
||||
],
|
||||
'phpfpm' => [
|
||||
'vhost_httpuser' => 'Local user to use for PHP-FPM (Froxlor vHost)',
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1093,9 +1093,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.29.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||
"version": "3.29.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
|
||||
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
|
||||
33
templates/Froxlor/assets/js/jquery/emails.js
vendored
Normal file
33
templates/Froxlor/assets/js/jquery/emails.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
export default function () {
|
||||
$(function () {
|
||||
|
||||
/**
|
||||
* bypass spam - hide unnecessary/unused sections
|
||||
*/
|
||||
if ($('#id') && $('#bypass_spam').is(':checked')) {
|
||||
$('#spam_tag_level').closest('.row').addClass('d-none');
|
||||
$('#spam_rewrite_subject').closest('.row').addClass('d-none');
|
||||
$('#spam_kill_level').closest('.row').addClass('d-none');
|
||||
$('#policy_greylist').closest('.row').addClass('d-none');
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle show/hide of sections in case of bypass spam flag
|
||||
*/
|
||||
$('#bypass_spam').on('click', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
// hide unnecessary sections
|
||||
$('#spam_tag_level').closest('.row').addClass('d-none');
|
||||
$('#spam_rewrite_subject').closest('.row').addClass('d-none');
|
||||
$('#spam_kill_level').closest('.row').addClass('d-none');
|
||||
$('#policy_greylist').closest('.row').addClass('d-none');
|
||||
} else {
|
||||
// show sections
|
||||
$('#spam_tag_level').closest('.row').removeClass('d-none');
|
||||
$('#spam_rewrite_subject').closest('.row').removeClass('d-none');
|
||||
$('#spam_kill_level').closest('.row').removeClass('d-none');
|
||||
$('#policy_greylist').closest('.row').removeClass('d-none');
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
{% endif %}
|
||||
{% if field.label.description is defined and field.label.description is not empty %}<br><small>{{ field.label.description|raw }}</small>
|
||||
{% endif %}
|
||||
{% if field.desc is defined and field.desc is not empty %}<br><small>{{ field.desc|raw }}</small>
|
||||
{% endif %}
|
||||
{% if field.requires_reconf is defined and field.requires_reconf is not empty %}
|
||||
<div class="bg-info bg-opacity-25 rounded p-2 mt-2 d-flex align-items-center" role="alert">
|
||||
<i class="fa-solid fa-circle-exclamation me-2"></i><p class="mb-0">{{ lng('serversettings.requires_reconfiguration', [field.requires_reconf|join(', ')])|raw }}</p>
|
||||
@@ -170,7 +172,7 @@
|
||||
{% if field.next_to is defined %}
|
||||
<div class="input-group">
|
||||
{% endif %}
|
||||
<input type="{{ field.type }}" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
|
||||
<input type="{{ field.type }}" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type == 'number' and field.step is not empty %} step="{{ field.step }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
|
||||
{% if field.type == 'hidden' and field.display is defined %}
|
||||
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
|
||||
{% endif %}
|
||||
@@ -207,7 +209,7 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<div class="input-group">
|
||||
<input type="number" min="0" {% if max is not empty %} max="{{ max }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{% if field.value >= 0 %}{{ field.value }}{% endif %}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %}/>
|
||||
<input type="number" min="0" {% if max is not empty %} max="{{ max }}" {% endif %}{% if field.step is not empty %} step="{{ field.step }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{% if field.value >= 0 %}{{ field.value }}{% endif %}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %}/>
|
||||
<div class="input-group-text">
|
||||
<input class="form-check-input mt-0" type="checkbox" name="{{ id }}_ul" value="1" {% if field.value == -1 %} checked="checked" {% endif %}>
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<div class="progress-bar bg-success"></div>
|
||||
</div>
|
||||
<div class="progress" role="progressbar"
|
||||
style="width: {{ 100 - apcuinfo.num_misses_percentage }}%"
|
||||
style="width: {{ apcuinfo.num_misses_percentage }}%"
|
||||
aria-valuenow="{{ apcuinfo.num_misses }}" aria-valuemin="0"
|
||||
aria-valuemax="{{ apcuinfo.num_hits_and_misses }}">
|
||||
<div class="progress-bar bg-danger"></div>
|
||||
|
||||
Reference in New Issue
Block a user