Compare commits

...

56 Commits

Author SHA1 Message Date
Michael Kaufmann
a47f8ed7ee Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 16:38:42 +02:00
Michael Kaufmann
ce841e8aa4 set version to 2.2.4 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 16:38:21 +02:00
Michael Kaufmann
86130616dd Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 16:36:25 +02:00
Michael Kaufmann
5622ce5011 add 'rewrite_subject' field to select query for rspamd config
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 16:21:59 +02:00
Michael Kaufmann
00bdadb6e5 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 15:43:05 +02:00
Michael Kaufmann
05223369c5 forgot to adjust another d.domain field in email-overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 15:42:34 +02:00
Michael Kaufmann
b45ac3de3c Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 15:33:24 +02:00
Michael Kaufmann
11a5c38476 use correct field-name in email-domain-overview; set version to 2.2.3 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 15:32:57 +02:00
Michael Kaufmann
8c48c5a840 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 10:57:40 +02:00
Michael Kaufmann
4f4abada6f set version to 2.2.2 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 10:55:31 +02:00
Michael Kaufmann
289d59f531 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-07 14:40:26 +02:00
Lukas Bableck
f652017c1a fix APCu memory usage (#1284) 2024-10-07 14:39:56 +02:00
Michael Kaufmann
2a50eb43b3 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-02 15:40:42 +02:00
Michael Kaufmann
f31c032508 add possibility to ask for potential update question in CLI updater and also pass them as options to override them; check whether mysql-user exists prior to DROP USER for mysql < 5.7 (as it is missing IF EXISTS options)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-02 15:27:42 +02:00
Lukas Bableck
228eb244fa Fix incorrect width of APCu Hit/Miss bar (#1283) 2024-10-01 15:57:00 +02:00
Michael Kaufmann
143d8d42b3 Merge remote-tracking branch 'origin/main' into v2.2 2024-09-28 14:51:55 +02:00
Michael Kaufmann
4ce739667d add rewrite-subject flag to email-edit form; hide spam-related settings if 'bypass_spam' is activated; add possibility to disable rejection of spam-mails, refs #1282
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-28 14:49:32 +02:00
Michael Kaufmann
eb3568fca2 Merge remote-tracking branch 'origin/main' into v2.2 2024-09-27 09:10:18 +02:00
dependabot[bot]
dda4c7a846 Bump rollup from 3.29.4 to 3.29.5 (#1280)
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 3.29.5.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v3.29.5)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 09:05:37 +02:00
Michael Kaufmann
53b7c501bc Merge remote-tracking branch 'origin/main' into v2.2 2024-09-27 09:04:29 +02:00
Michael Kaufmann
c9e15bf897 do not issue let's encrypt for email_only domains (in case they were web-enabled prior, we do not unset former settings to ease reverting back when disabling email_only)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-27 09:04:01 +02:00
Michael Kaufmann
140c6c9549 store IDN email-usernames in ACE, as dovecot/postfix need them this way
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-26 09:22:11 +02:00
Michael Kaufmann
ddc439d32f Merge remote-tracking branch 'origin/main' into v2.2 2024-09-18 09:24:47 +02:00
Michael Kaufmann
40aa48a6d4 exchange toggler-links with checkboxes in email edit form to be able to adjust all parameters at once, fixes #1277
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-18 09:10:29 +02:00
Michael Kaufmann
c69b38bc42 [config-services] add validation for empty or non-existing configuration template xml files; [php-fpm] remove 'date.timezone' from php_admin_values (superfluous as it is in php_values); [antispam] set rewrite_subject to a slighty higher score then used for add_header, fixes #1275
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-10 14:11:26 +02:00
Michael Kaufmann
1fd8b88ed8 fix language replacement and fix 'sending messages' after successfully sending prior, thx to Davidd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-01 13:49:27 +02:00
Michael Kaufmann
0318223fec Merge remote-tracking branch 'origin/main' into v2.2 2024-08-31 16:47:55 +02:00
Michael Kaufmann
53c414be6d fix timestamp matching regex, add lmtp to receving service regex and skip lines not including the main target service name in maillog parser
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-31 08:42:09 +02:00
Michael Kaufmann
2f7a2a32ba do not overwrite needed userinfo to avoid successful login when using email 2fa
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-31 08:41:59 +02:00
Michael Kaufmann
16d77a03cb fix timestamp matching regex, add lmtp to receving service regex and skip lines not including the main target service name in maillog parser
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-31 08:41:09 +02:00
Michael Kaufmann
05ca08c5c3 do not overwrite needed userinfo to avoid successful login when using email 2fa
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-30 16:26:55 +02:00
Michael Kaufmann
d3ec02f258 set version to 2.2.1 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-30 10:10:11 +02:00
Michael Kaufmann
4ea7e10304 set version to 2.2.1 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-30 10:06:26 +02:00
Michael Kaufmann
f31ee2e360 add condition to the remember-me checkbox for updaters when the token-table does not exist yet
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:21:25 +02:00
Michael Kaufmann
57206b2f72 dont generate dhparam file as fallback but use defined FFDHE4096 group; fixes #1270
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:21:16 +02:00
Florian Moker
73906f252b Fix missing proftpd-mod-wrap installation (#1272)
Missing Package Installation on Ubuntu Noble 24.04 - proftpd-mod-wrap, fixes #1271
2024-08-29 08:21:10 +02:00
Michael Kaufmann
3367f6dbd8 add condition to the remember-me checkbox for updaters when the token-table does not exist yet
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:19:45 +02:00
Michael Kaufmann
197eb7954a dont generate dhparam file as fallback but use defined FFDHE4096 group; fixes #1270
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:17:54 +02:00
Florian Moker
a1b6125c14 Fix missing proftpd-mod-wrap installation (#1272)
Missing Package Installation on Ubuntu Noble 24.04 - proftpd-mod-wrap, fixes #1271
2024-08-29 08:10:59 +02:00
Michael Kaufmann
7206f5fee2 show antispam options for email-editing only if enabled
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 20:18:38 +02:00
Michael Kaufmann
d18a9c9d87 show antispam options for email-editing only if enabled
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 19:08:24 +02:00
Michael Kaufmann
94046ae6c7 fix storing multiple-choice-select values, thx to 21MILEX on Discord, fixes #1269
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 13:03:47 +02:00
Michael Kaufmann
55212607e0 fix storing multiple-choice-select values, thx to 21MILEX on Discord, fixes #1269
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 10:23:54 +02:00
Lukas Bableck
bacc6fe073 Add |raw to h5 in formfields template (#1268) 2024-08-23 11:04:30 +02:00
Lukas Bableck
e6bfe205c5 Add |raw to h5 in formfields template (#1268) 2024-08-23 11:03:33 +02:00
Michael Kaufmann
596075d141 set version to 2.2.0 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-23 09:22:35 +02:00
Michael Kaufmann
15d3dd4234 set version to 2.2.0 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-23 09:21:54 +02:00
Michael Kaufmann
a58a5fd972 correctly get target filename for jqSpeciallogfileNote action call via ajax, fixes #1267
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-16 09:22:46 +02:00
Michael Kaufmann
54cda098c1 correctly get target filename for jqSpeciallogfileNote action call via ajax, fixes #1267
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-15 20:11:28 +02:00
Michael Kaufmann
bcbfcb34e8 fix typo in varchar length of selector field of new panel_2fa_tokens table, thx to Davidd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-14 20:26:18 +02:00
Michael Kaufmann
19995f4345 fix typo in varchar length of selector field of new panel_2fa_tokens table, thx to Davidd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-14 20:24:24 +02:00
Michael Kaufmann
56d8a565b4 Merge remote-tracking branch 'origin/main' into v2.2 2024-08-14 12:39:03 +02:00
dependabot[bot]
a60c21218c Bump axios from 1.6.0 to 1.7.4 (#1266)
Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.0...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 12:38:33 +02:00
Michael Kaufmann
cd2a08e731 Merge remote-tracking branch 'origin/main' into v2.2 2024-08-14 12:29:27 +02:00
Michael Kaufmann
5d2ce4ecfb allow 60sec discrepancy for email based 2fa; fix dbms version compare issue when removing user; adjust pure-ftpd mysql.conf file permissions
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-12 18:37:38 +02:00
rex2630
869b01204a Add new missing strings + fix typo (#1264) 2024-08-11 21:45:11 +02:00
48 changed files with 462 additions and 236 deletions

View File

@@ -118,7 +118,7 @@ if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') {
'uptime' => duration($cache['start_time']) '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, $overview['num_hits_percentage'] = number_format(($overview['num_hits'] / $overview['num_hits_and_misses']) * 100,
1); 1);
$overview['num_misses_percentage'] = number_format(($overview['num_misses'] / $overview['num_hits_and_misses']) * 100, $overview['num_misses_percentage'] = number_format(($overview['num_misses'] / $overview['num_hits_and_misses']) * 100,

View File

@@ -114,7 +114,7 @@ if ($page == 'message') {
$note_msg = lng('message.norecipients'); $note_msg = lng('message.norecipients');
} else { } else {
$note_type = 'success'; $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'; $messages_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/messages/formfield.messages_add.php';
UI::view('user/form-note.html.twig', [ UI::view('user/form-note.html.twig', [
'formaction' => $linker->getLink(['section' => 'message']), 'formaction' => $linker->getLink(['section' => 'message', 'action' => '']),
'formdata' => $messages_add_data['messages_add'], 'formdata' => $messages_add_data['messages_add'],
'actions_links' => [ 'actions_links' => [
[ [

View File

@@ -247,11 +247,7 @@ if ($page == 'email_domain') {
if (isset($result['email']) && $result['email'] != '') { if (isset($result['email']) && $result['email'] != '') {
if (Request::post('send') == 'send') { if (Request::post('send') == 'send') {
try { try {
Emails::getLocal($userinfo, [ Emails::getLocal($userinfo, Request::postAll())->update();
'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();
} catch (Exception $e) { } catch (Exception $e) {
Response::dynamicError($e->getMessage()); 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'; $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', [ UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'email']), 'formaction' => $linker->getLink(['section' => 'email']),
'formdata' => $email_edit_data['emails_edit'], 'formdata' => $email_edit_data['emails_edit'],
'editid' => $id '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') { } elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0); $email_domainid = Request::any('domainid', 0);

View File

@@ -62,6 +62,7 @@ if ($action == '2fa_entercode') {
// show template to enter code // show template to enter code
UI::view('login/enter2fa.html.twig', [ UI::view('login/enter2fa.html.twig', [
'pagetitle' => lng('login.2fa'), 'pagetitle' => lng('login.2fa'),
'remember_me' => (Settings::Get('panel.db_version') >= 202407200) ? true : false,
'message' => $message 'message' => $message
]); ]);
} elseif ($action == '2fa_verify') { } elseif ($action == '2fa_verify') {
@@ -84,7 +85,8 @@ if ($action == '2fa_entercode') {
// verify code set to user's data_2fa field // verify code set to user's data_2fa field
$sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid"); $sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid");
$userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]); $userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]);
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code); // 60sec discrepancy (possible slow email delivery)
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code, 60);
} else { } else {
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3); $result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
} }
@@ -109,7 +111,7 @@ if ($action == '2fa_entercode') {
// when using email-2fa, remove the one-time-code // when using email-2fa, remove the one-time-code
if ($userinfo['type_2fa'] == '1') { if ($userinfo['type_2fa'] == '1') {
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid"); $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 'uid' => $uid
]); ]);
} }
@@ -393,6 +395,9 @@ if ($action == '2fa_entercode') {
} }
exit(); exit();
} }
// not found or invalid, this cookie is useless, get rid of it
unset($_COOKIE['frx_2fa_remember']);
setcookie('frx_2fa_remember', "", time()-3600);
} }
// redirect to code-enter-page // redirect to code-enter-page

View File

@@ -95,6 +95,7 @@ CREATE TABLE `mail_virtual` (
`iscatchall` tinyint(1) unsigned NOT NULL default '0', `iscatchall` tinyint(1) unsigned NOT NULL default '0',
`description` varchar(255) NOT NULL DEFAULT '', `description` varchar(255) NOT NULL DEFAULT '',
`spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0, `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, `spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0,
`bypass_spam` tinyint(1) NOT NULL default '0', `bypass_spam` tinyint(1) NOT NULL default '0',
`policy_greylist` tinyint(1) NOT NULL default '1', `policy_greylist` tinyint(1) NOT NULL default '1',
@@ -495,7 +496,6 @@ opcache.save_comments
opcache.use_cwd opcache.use_cwd
opcache.fast_shutdown'), opcache.fast_shutdown'),
('phpfpm', 'ini_admin_values', 'cgi.redirect_status_env ('phpfpm', 'ini_admin_values', 'cgi.redirect_status_env
date.timezone
disable_classes disable_classes
disable_functions disable_functions
error_log error_log
@@ -730,8 +730,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridecustom', '0'), ('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'), ('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'), ('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.0-rc3'), ('panel', 'version', '2.2.4'),
('panel', 'db_version', '202407200'); ('panel', 'db_version', '202409280');
DROP TABLE IF EXISTS `panel_tasks`; DROP TABLE IF EXISTS `panel_tasks`;
@@ -1054,7 +1054,7 @@ CREATE TABLE `panel_loginlinks` (
DROP TABLE IF EXISTS `panel_2fa_tokens`; DROP TABLE IF EXISTS `panel_2fa_tokens`;
CREATE TABLE `panel_2fa_tokens` ( CREATE TABLE `panel_2fa_tokens` (
`id` int(11) NOT NULL auto_increment, `id` int(11) NOT NULL auto_increment,
`selector` varchar(20) NOT NULL, `selector` varchar(200) NOT NULL,
`token` varchar(200) NOT NULL, `token` varchar(200) NOT NULL,
`userid` int(11) NOT NULL default '0', `userid` int(11) NOT NULL default '0',
`valid_until` int(15) NOT NULL, `valid_until` int(15) NOT NULL,

View File

@@ -150,3 +150,46 @@ if (Froxlor::isFroxlorVersion('2.2.0-rc2')) {
Update::showUpdateStep("Updating from 2.2.0-rc2 to 2.2.0-rc3", false); Update::showUpdateStep("Updating from 2.2.0-rc2 to 2.2.0-rc3", false);
Froxlor::updateToVersion('2.2.0-rc3'); Froxlor::updateToVersion('2.2.0-rc3');
} }
if (Froxlor::isDatabaseVersion('202407200')) {
Update::showUpdateStep("Adjusting field in 2fa-token table");
Database::query("ALTER TABLE `panel_2fa_tokens` CHANGE COLUMN `selector` `selector` varchar(200) NOT NULL;");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202408140');
}
if (Froxlor::isFroxlorVersion('2.2.0-rc3')) {
Update::showUpdateStep("Updating from 2.2.0-rc3 to 2.2.0 stable", false);
Froxlor::updateToVersion('2.2.0');
}
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');
}
if (Froxlor::isFroxlorVersion('2.2.3')) {
Update::showUpdateStep("Updating from 2.2.3 to 2.2.4", false);
Froxlor::updateToVersion('2.2.4');
}

View File

@@ -34,7 +34,7 @@ $return = [];
if (Update::versionInUpdate($current_db_version, '202004140')) { if (Update::versionInUpdate($current_db_version, '202004140')) {
$has_preconfig = true; $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).'; $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&nbsp;'; $question = '<strong>Validate DNS of domains when using Lets Encrypt</strong>';
$return['system_le_domain_dnscheck'] = [ $return['system_le_domain_dnscheck'] = [
'type' => 'checkbox', 'type' => 'checkbox',
'value' => 1, 'value' => 1,

View File

@@ -738,11 +738,12 @@ class Customers extends ApiCommand implements ResourceEntity
'adminid' => $this->getUserDetail('adminid'), 'adminid' => $this->getUserDetail('adminid'),
'docroot' => $documentroot, 'docroot' => $documentroot,
'phpenabled' => $phpenabled, 'phpenabled' => $phpenabled,
'openbasedir' => '1' 'openbasedir' => '1',
'is_stdsubdomain' => 1
]; ];
$domainid = -1; $domainid = -1;
try { try {
$std_domain = $this->apiCall('Domains.add', $ins_data); $std_domain = $this->apiCall('Domains.add', $ins_data, true);
$domainid = $std_domain['id']; $domainid = $std_domain['id'];
} catch (Exception $e) { } catch (Exception $e) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage());

View File

@@ -274,7 +274,8 @@ class Domains extends ApiCommand implements ResourceEntity
* $override_tls is true * $override_tls is true
* @param string $description * @param string $description
* optional custom description (currently not used/shown in the frontend), default empty * 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 * @access admin
* @return string json-encoded array * @return string json-encoded array
* @throws Exception * @throws Exception
@@ -282,7 +283,8 @@ class Domains extends ApiCommand implements ResourceEntity
public function add() public function add()
{ {
if ($this->isAdmin()) { 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 // parameters
$p_domain = $this->getParam('domain'); $p_domain = $this->getParam('domain');
@@ -795,12 +797,15 @@ class Domains extends ApiCommand implements ResourceEntity
$ins_data['id'] = $domainid; $ins_data['id'] = $domainid;
unset($ins_data); unset($ins_data);
$upd_stmt = Database::prepare(" if (!$is_stdsubdomain) {
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 $upd_stmt = Database::prepare("
WHERE `adminid` = :adminid"); UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1
Database::pexecute($upd_stmt, [ WHERE `adminid` = :adminid
'adminid' => $adminid ");
], true, true); Database::pexecute($upd_stmt, [
'adminid' => $adminid
], true, true);
}
$ins_stmt = Database::prepare(" $ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_DOMAINTOIP . "` SET INSERT INTO `" . TABLE_DOMAINTOIP . "` SET

View File

@@ -69,7 +69,7 @@ class EmailDomains extends ApiCommand implements ResourceEntity
$result = []; $result = [];
$query_fields = []; $query_fields = [];
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("
SELECT DISTINCT d.domain, e.domainid, SELECT DISTINCT d.domain, d.domain_ace, e.domainid,
COUNT(e.email) as addresses, COUNT(e.email) as addresses,
IFNULL(SUM(CASE WHEN e.popaccountid > 0 THEN 1 ELSE 0 END), 0) as accounts, IFNULL(SUM(CASE WHEN e.popaccountid > 0 THEN 1 ELSE 0 END), 0) as accounts,
IFNULL(SUM( IFNULL(SUM(

View File

@@ -53,6 +53,8 @@ class Emails extends ApiCommand implements ResourceEntity
* domain-name for the email-address * domain-name for the email-address
* @param float $spam_tag_level * @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0 * 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 * @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0 * optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam * @param boolean $bypass_spam
@@ -85,17 +87,19 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters // parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0'); $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); $bypass_spam = $this->getBoolParam('bypass_spam', true, 0);
$policy_greylist = $this->getBoolParam('policy_greylist', true, 1); $policy_greylist = $this->getBoolParam('policy_greylist', true, 1);
$iscatchall = $this->getBoolParam('iscatchall', true, 0); $iscatchall = $this->getBoolParam('iscatchall', true, 0);
$description = $this->getParam('description', true, ''); $description = $this->getParam('description', true, '');
// validation // validation
$idna_convert = new IdnaWrapper();
if (substr($domain, 0, 4) != 'xn--') { if (substr($domain, 0, 4) != 'xn--') {
$idna_convert = new IdnaWrapper();
$domain = $idna_convert->encode(Validate::validate($domain, 'domain', '', '', [], true)); $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 // check domain and whether it's an email-enabled domain
// use internal call because the customer might have 'domains' in customer_hide_options // 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 'domainname' => $domain
], true); ], true);
if ((int)$domain_check['isemaildomain'] == 0) { 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) { 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') { if (Settings::Get('catchall.catchall_enabled') != '1') {
@@ -127,7 +131,7 @@ class Emails extends ApiCommand implements ResourceEntity
// validate it // validate it
if (!Validate::validateEmail($email_full)) { 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 // 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 ($email_check) {
if (strtolower($email_check['email_full']) == strtolower($email_full)) { 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) { } elseif ($email_check['email'] == $email) {
Response::standardError('youhavealreadyacatchallforthisdomain', '', true); Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
} }
} }
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true); $spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1})?$/', '', [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})?$/', '', [14.0], true);
}
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true); $description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$stmt = Database::prepare(" $stmt = Database::prepare("
@@ -164,6 +170,7 @@ class Emails extends ApiCommand implements ResourceEntity
`email` = :email, `email` = :email,
`email_full` = :email_full, `email_full` = :email_full,
`spam_tag_level` = :spam_tag_level, `spam_tag_level` = :spam_tag_level,
`rewrite_subject` = :rewrite_subject,
`spam_kill_level` = :spam_kill_level, `spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam, `bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist, `policy_greylist` = :policy_greylist,
@@ -176,6 +183,7 @@ class Emails extends ApiCommand implements ResourceEntity
"email" => $email, "email" => $email,
"email_full" => $email_full, "email_full" => $email_full,
"spam_tag_level" => $spam_tag_level, "spam_tag_level" => $spam_tag_level,
"rewrite_subject" => $rewrite_subject,
"spam_kill_level" => $spam_kill_level, "spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam, "bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist, "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` LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ") WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
AND " . (is_numeric($params['idea']) ? "v.`id`= :idea" : "(v.`email` = :idea OR v.`email_full` = :idea)" 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); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get email address '" . $result['email_full'] . "'"); $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) * optional, required when called as admin (if $customerid is not specified)
* @param float $spam_tag_level * @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0 * 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 * @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0 * optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam * @param boolean $bypass_spam
@@ -282,7 +292,8 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters // parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']); $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']); $bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
$policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']); $policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']); $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_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); $description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$stmt = Database::prepare(" $stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET
`email` = :email , `email` = :email ,
`spam_tag_level` = :spam_tag_level, `spam_tag_level` = :spam_tag_level,
`rewrite_subject` = :rewrite_subject,
`spam_kill_level` = :spam_kill_level, `spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam, `bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist, `policy_greylist` = :policy_greylist,
@@ -343,6 +357,7 @@ class Emails extends ApiCommand implements ResourceEntity
$params = [ $params = [
"email" => $email, "email" => $email,
"spam_tag_level" => $spam_tag_level, "spam_tag_level" => $spam_tag_level,
"rewrite_subject" => $rewrite_subject,
"spam_kill_level" => $spam_kill_level, "spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam, "bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist, "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`) 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()); WHERE m.`customerid` IN (" . implode(", ", $customer_ids) . ")" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit());
Database::pexecute($result_stmt, $query_fields, true, true); Database::pexecute($result_stmt, $query_fields, true, true);
$idna_convert = new IdnaWrapper();
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { 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; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses");

View File

@@ -225,7 +225,7 @@ class SysLog extends ApiCommand implements ResourceEntity
} }
$params['trunc'] = $truncatedate; $params['trunc'] = $truncatedate;
Database::pexecute($result_stmt, $params, true, true); Database::pexecute($result_stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] truncated the froxlor syslog"); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] truncated the froxlor syslog");
return $this->response(true); return $this->response(true);
} }
throw new Exception("Not allowed to execute given command.", 403); throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -217,6 +217,10 @@ final class ConfigServices extends CliCommand
$_daemons_config['distro'] = $io->choice('Choose distribution', $valid_dists, $os_default); $_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 // 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"); $configfiles = new ConfigParser($config_dir . '/' . $_daemons_config['distro'] . ".xml");
$services = $configfiles->getServices(); $services = $configfiles->getServices();
@@ -352,8 +356,13 @@ final class ConfigServices extends CliCommand
} }
if (!empty($decoded_config)) { if (!empty($decoded_config)) {
$config_dir = Froxlor::getInstallDir() . 'lib/configfiles/'; $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(); $services = $configfiles->getServices();
$replace_arr = $this->getReplacerArray(); $replace_arr = $this->getReplacerArray();

View File

@@ -28,13 +28,17 @@ namespace Froxlor\Cli;
use Exception; use Exception;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\Install\AutoUpdate; use Froxlor\Install\AutoUpdate;
use Froxlor\Install\Preconfig;
use Froxlor\Install\Update; use Froxlor\Install\Update;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\System\Mailer; use Froxlor\System\Mailer;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
final class UpdateCommand extends CliCommand final class UpdateCommand extends CliCommand
{ {
@@ -44,6 +48,8 @@ final class UpdateCommand extends CliCommand
$this->setName('froxlor:update'); $this->setName('froxlor:update');
$this->setDescription('Check for newer version and update froxlor'); $this->setDescription('Check for newer version and update froxlor');
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit') $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('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('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)') ->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') . '</>'); $output->writeln('<info>' . lng('update.dbupdate_required') . '</>');
if ($input->getOption('check-only')) { if ($input->getOption('check-only')) {
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>'); $output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
$this->askUpdateOptions($input, $output, null, false);
} else { } else {
$yestoall = $input->getOption('yes-to-all') !== false; $yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question'); $helper = $this->getHelper('question');
$this->askUpdateOptions($input, $output, $helper, $yestoall);
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i'); $question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) { if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true); $result = $this->runUpdate($output, true);
@@ -101,7 +111,7 @@ final class UpdateCommand extends CliCommand
} }
// there is a new version // there is a new version
if ($input->getOption('check-only')) { 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 { } else {
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]); $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 // check whether we only wanted to check
if ($input->getOption('check-only')) { if ($input->getOption('check-only')) {
//$output->writeln('<comment>Not proceeding as "check-only" is specified</>'); //$output->writeln('<comment>Not proceeding as "check-only" is specified</>');
$this->askUpdateOptions($input, $output, null, false);
return $result; return $result;
} else { } else {
$yestoall = $input->getOption('yes-to-all') !== false; $yestoall = $input->getOption('yes-to-all') !== false;
@@ -174,6 +185,9 @@ final class UpdateCommand extends CliCommand
if ($auex == 0) { if ($auex == 0) {
$output->writeln("<info>Froxlor files updated successfully.</>"); $output->writeln("<info>Froxlor files updated successfully.</>");
$result = self::SUCCESS; $result = self::SUCCESS;
$this->askUpdateOptions($input, $output, $helper, $yestoall);
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i'); $question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) { if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true); $result = $this->runUpdate($output, true);
@@ -195,12 +209,141 @@ final class UpdateCommand extends CliCommand
return $result; 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) private function mailNotify(InputInterface $input, OutputInterface $output)
{ {
if ($input->getOption('mail-notify')) { if ($input->getOption('mail-notify')) {
$last_check_version = Settings::Get('system.update_notify_last'); $last_check_version = Settings::Get('system.update_notify_last');
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) { 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 = new Mailer(true);
$mail->Body = $text; $mail->Body = $text;
$mail->Subject = "[froxlor] " . lng('update.notify_subject'); $mail->Subject = "[froxlor] " . lng('update.notify_subject');

View File

@@ -441,7 +441,7 @@ class Apache extends HttpConfigBase
if (!empty(Settings::Get('system.dhparams_file'))) { if (!empty(Settings::Get('system.dhparams_file'))) {
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file')); $dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
if (!file_exists($dhparams)) { if (!file_exists($dhparams)) {
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096'); file_put_contents($dhparams, self::FFDHE4096);
} }
$this->virtualhosts_data[$vhosts_filename] .= ' SSLOpenSSLConfCmd DHParameters "' . $dhparams . '"' . "\n"; $this->virtualhosts_data[$vhosts_filename] .= ' SSLOpenSSLConfCmd DHParameters "' . $dhparams . '"' . "\n";
} }
@@ -754,7 +754,7 @@ class Apache extends HttpConfigBase
if (!empty(Settings::Get('system.dhparams_file'))) { if (!empty(Settings::Get('system.dhparams_file'))) {
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file')); $dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
if (!file_exists($dhparams)) { if (!file_exists($dhparams)) {
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096'); file_put_contents($dhparams, self::FFDHE4096);
} }
$vhost_content .= ' SSLOpenSSLConfCmd DHParameters "' . $dhparams . '"' . "\n"; $vhost_content .= ' SSLOpenSSLConfCmd DHParameters "' . $dhparams . '"' . "\n";
} }

View File

@@ -45,6 +45,26 @@ use PDO;
class HttpConfigBase class HttpConfigBase
{ {
/**
* Pre-defined DHE groups to use as fallback if dhparams_file
* is given, but non-existent, see also https://github.com/froxlor/Froxlor/issues/1270
*/
const FFDHE4096 = <<<EOC
-----BEGIN DH PARAMETERS-----
MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e
8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx
iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
-----END DH PARAMETERS-----
EOC;
public function init() public function init()
{ {
// if Let's Encrypt is activated, run it before regeneration of webserver configfiles // if Let's Encrypt is activated, run it before regeneration of webserver configfiles

View File

@@ -321,10 +321,12 @@ EOC;
WHERE WHERE
dom.`customerid` = cust.`customerid` dom.`customerid` = cust.`customerid`
AND cust.deactivated = 0 AND cust.deactivated = 0
AND dom.deactivated = 0
AND dom.`ssl_enabled` = 1 AND dom.`ssl_enabled` = 1
AND dom.`letsencrypt` = 1 AND dom.`letsencrypt` = 1
AND dom.`aliasdomain` IS NULL AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
AND dom.`email_only` = 0
AND domssl.`validtodate` IS NULL AND domssl.`validtodate` IS NULL
"); ");
$customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC); $customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -383,10 +385,12 @@ EOC;
WHERE WHERE
dom.`customerid` = cust.`customerid` dom.`customerid` = cust.`customerid`
AND cust.deactivated = 0 AND cust.deactivated = 0
AND dom.deactivated = 0
AND dom.`ssl_enabled` = 1 AND dom.`ssl_enabled` = 1
AND dom.`letsencrypt` = 1 AND dom.`letsencrypt` = 1
AND dom.`aliasdomain` IS NULL AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
AND dom.`email_only` = 0
"); ");
$renew_certs = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC); $renew_certs = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
if ($renew_certs) { if ($renew_certs) {

View File

@@ -273,7 +273,7 @@ class Lighttpd extends HttpConfigBase
if (!empty(Settings::Get('system.dhparams_file'))) { if (!empty(Settings::Get('system.dhparams_file'))) {
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file')); $dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
if (!file_exists($dhparams)) { if (!file_exists($dhparams)) {
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096'); file_put_contents($dhparams, self::FFDHE4096);
} }
$this->lighttpd_data[$vhost_filename] .= 'ssl.dh-file = "' . $dhparams . '"' . "\n"; $this->lighttpd_data[$vhost_filename] .= 'ssl.dh-file = "' . $dhparams . '"' . "\n";
$this->lighttpd_data[$vhost_filename] .= 'ssl.ec-curve = "secp384r1"' . "\n"; $this->lighttpd_data[$vhost_filename] .= 'ssl.ec-curve = "secp384r1"' . "\n";
@@ -756,7 +756,7 @@ class Lighttpd extends HttpConfigBase
if (!empty(Settings::Get('system.dhparams_file'))) { if (!empty(Settings::Get('system.dhparams_file'))) {
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file')); $dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
if (!file_exists($dhparams)) { if (!file_exists($dhparams)) {
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096'); file_put_contents($dhparams, self::FFDHE4096);
} }
$ssl_settings .= 'ssl.dh-file = "' . $dhparams . '"' . "\n"; $ssl_settings .= 'ssl.dh-file = "' . $dhparams . '"' . "\n";
$ssl_settings .= 'ssl.ec-curve = "secp384r1"' . "\n"; $ssl_settings .= 'ssl.ec-curve = "secp384r1"' . "\n";

View File

@@ -399,7 +399,7 @@ class Nginx extends HttpConfigBase
if (!empty(Settings::Get('system.dhparams_file'))) { if (!empty(Settings::Get('system.dhparams_file'))) {
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file')); $dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
if (!file_exists($dhparams)) { if (!file_exists($dhparams)) {
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096'); file_put_contents($dhparams, self::FFDHE4096);
} }
$sslsettings .= "\t" . 'ssl_dhparam ' . $dhparams . ';' . "\n"; $sslsettings .= "\t" . 'ssl_dhparam ' . $dhparams . ';' . "\n";
} }

View File

@@ -55,7 +55,7 @@ class Rspamd
// get all email addresses // get all email addresses
$antispam_stmt = Database::prepare(" $antispam_stmt = Database::prepare("
SELECT email, spam_tag_level, spam_kill_level, bypass_spam, policy_greylist, iscatchall SELECT email, spam_tag_level, rewrite_subject, spam_kill_level, bypass_spam, policy_greylist, iscatchall
FROM `" . TABLE_MAIL_VIRTUAL . "` FROM `" . TABLE_MAIL_VIRTUAL . "`
ORDER BY email ORDER BY email
"); ");
@@ -165,7 +165,7 @@ class Rspamd
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Generating antispam config for ' . $email['email']); $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_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']); $email_id = md5($email['email']);
$this->frx_settings_file .= '# Email: ' . $email['email'] . "\n"; $this->frx_settings_file .= '# Email: ' . $email['email'] . "\n";
@@ -185,7 +185,9 @@ class Rspamd
$this->frx_settings_file .= ' apply {' . "\n"; $this->frx_settings_file .= ' apply {' . "\n";
$this->frx_settings_file .= ' actions {' . "\n"; $this->frx_settings_file .= ' actions {' . "\n";
$this->frx_settings_file .= ' "add header" = ' . $email['spam_tag_level'] . ';' . "\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"; $this->frx_settings_file .= ' reject = ' . $email['spam_kill_level'] . ';' . "\n";
if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) { if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) {
$this->frx_settings_file .= ' greylist = null;' . "\n"; $this->frx_settings_file .= ' greylist = null;' . "\n";

View File

@@ -187,21 +187,23 @@ class DbManagerMySQL
*/ */
public function deleteUser(string $username, string $host) public function deleteUser(string $username, string $host)
{ {
if (Database::getAttribute(PDO::ATTR_SERVER_VERSION) < '5.0.2') { if ($this->userExistsOnHost($username, $host)) {
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1) if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`"); // Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
Database::pexecute($stmt); $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
]);
} }
/** /**

View File

@@ -31,10 +31,10 @@ final class Froxlor
{ {
// Main version variable // Main version variable
const VERSION = '2.2.0-rc3'; const VERSION = '2.2.4';
// Database version (YYYYMMDDC where C is a daily counter) // Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202407200'; const DBVERSION = '202409280';
// Distribution branding-tag (used for Debian etc.) // Distribution branding-tag (used for Debian etc.)
const BRANDING = ''; const BRANDING = '';

View File

@@ -85,27 +85,31 @@ class Preconfig
} }
} }
} }
/** /**
* Function getPreConfig * Function getPreConfig
* *
* outputs various form-field-arrays before the update process * outputs various form-field-arrays before the update process
* can be continued (asks for agreement whatever is being asked) * can be continued (asks for agreement whatever is being asked)
* *
* @param bool $no_check
* @return array * @return array
*/ */
public static function getPreConfig(): array public static function getPreConfig(bool $no_check = false): array
{ {
$preconfig = new self(); $preconfig = new self();
if ($preconfig->hasPreConfig()) { if ($preconfig->hasPreConfig()) {
$agree = [ if (!$no_check) {
'title' => 'Check', $agree = [
'fields' => [ 'title' => 'Check',
'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>'], 'fields' => [
'update_preconfig' => ['type' => 'hidden', 'value' => 1] '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); ];
$preconfig->addToPreConfig($agree);
}
return $preconfig->getData(); return $preconfig->getData();
} }
return []; return [];

View File

@@ -104,6 +104,10 @@ class MailLogParser
unset($matches); unset($matches);
$line = fgets($file_handle); $line = fgets($file_handle);
if (strpos($line, 'postfix') === false) {
continue;
}
$timestamp = $this->getLogTimestamp($line); $timestamp = $this->getLogTimestamp($line);
if ($this->startTime < $timestamp) { if ($this->startTime < $timestamp) {
if (preg_match("/postfix\/qmgr.*(?::|\])\s([A-Z\d]+).*from=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?, size=(\d+),/", $line, $matches)) { 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]), "domainFrom" => strtolower($matches[2]),
"size" => $matches[3] "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 // Postfix to
if (array_key_exists($matches[1], $this->mails)) { if (array_key_exists($matches[1], $this->mails)) {
$this->mails[$matches[1]]["domainTo"] = strtolower($matches[2]); $this->mails[$matches[1]]["domainTo"] = strtolower($matches[2]);
@@ -149,7 +153,7 @@ class MailLogParser
private function getLogTimestamp($line) private function getLogTimestamp($line)
{ {
$matches = null; $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]); $timestamp = strtotime($matches[1]);
if ($timestamp > ($this->startTime + 60 * 60 * 24)) { if ($timestamp > ($this->startTime + 60 * 60 * 24)) {
return strtotime($matches[1] . " -1 year"); return strtotime($matches[1] . " -1 year");
@@ -258,6 +262,10 @@ class MailLogParser
unset($matches); unset($matches);
$line = fgets($file_handle); $line = fgets($file_handle);
if (strpos($line, 'dovecot') === false) {
continue;
}
$timestamp = $this->getLogTimestamp($line); $timestamp = $this->getLogTimestamp($line);
if ($this->startTime < $timestamp) { 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)) { if (preg_match("/dovecot.*(?::|\]) imap\(.*@([a-z0-9\.\-]+)\)(<\d+><[a-z0-9+\/=]+>)?:.*(?:in=(\d+) out=(\d+)|bytes=(\d+)\/(\d+))/i", $line, $matches)) {

View File

@@ -232,7 +232,7 @@ class Data
{ {
$returnvalue = true; $returnvalue = true;
if (isset($fielddata['option_mode']) && $fielddata['option_mode'] == 'multiple') { if (isset($fielddata['select_mode']) && $fielddata['select_mode'] == 'multiple') {
$options = explode(',', $newfieldvalue); $options = explode(',', $newfieldvalue);
foreach ($options as $option) { foreach ($options as $option) {
$returnvalue = ($returnvalue && isset($fielddata['select_var'][$option])); $returnvalue = ($returnvalue && isset($fielddata['select_var'][$option]));
@@ -247,7 +247,7 @@ class Data
if (isset($fielddata['option_emptyallowed']) && $fielddata['option_emptyallowed']) { if (isset($fielddata['option_emptyallowed']) && $fielddata['option_emptyallowed']) {
return true; return true;
} }
return 'not in option'; return 'not in option (field: ' . $fieldname . ')';
} }
} }

View File

@@ -3174,7 +3174,7 @@ no
</content> </content>
</file> </file>
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0" <file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
chmod="0644" backup="true"> chmod="0640" backup="true">
<content><![CDATA[ <content><![CDATA[
############################################## ##############################################
# # # #

View File

@@ -4742,7 +4742,7 @@ no
</content> </content>
</file> </file>
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0" <file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
chmod="0644" backup="true"> chmod="0640" backup="true">
<content><![CDATA[ <content><![CDATA[
############################################## ##############################################
# # # #

View File

@@ -3961,7 +3961,7 @@ no
</content> </content>
</file> </file>
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0" <file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
chmod="0644" backup="true"> chmod="0640" backup="true">
<content><![CDATA[ <content><![CDATA[
############################################## ##############################################
# # # #

View File

@@ -3953,7 +3953,7 @@ no
</content> </content>
</file> </file>
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0" <file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
chmod="0644" backup="true"> chmod="0640" backup="true">
<content><![CDATA[ <content><![CDATA[
############################################## ##############################################
# # # #

View File

@@ -2121,7 +2121,7 @@ action = "no action";
<service type="ftp" title="{{lng.admin.configfiles.ftp}}"> <service type="ftp" title="{{lng.admin.configfiles.ftp}}">
<!-- Proftpd --> <!-- Proftpd -->
<daemon name="proftpd" title="ProFTPd" default="true"> <daemon name="proftpd" title="ProFTPd" default="true">
<install><![CDATA[apt-get install proftpd-basic proftpd-mod-mysql proftpd-mod-crypto]]></install> <install><![CDATA[apt-get install proftpd-basic proftpd-mod-mysql proftpd-mod-crypto proftpd-mod-wrap]]></install>
<file name="/etc/proftpd/create-cert.sh" chown="root:0" <file name="/etc/proftpd/create-cert.sh" chown="root:0"
chmod="0700"> chmod="0700">
<content><![CDATA[#!/bin/bash <content><![CDATA[#!/bin/bash
@@ -2628,7 +2628,7 @@ no
</content> </content>
</file> </file>
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0" <file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
chmod="0644" backup="true"> chmod="0640" backup="true">
<content><![CDATA[ <content><![CDATA[
############################################## ##############################################
# # # #

View File

@@ -90,55 +90,48 @@ return [
] ]
], ],
'mail_catchall' => [ 'mail_catchall' => [
'visible' => Settings::Get('catchall.catchall_enabled') == '1',
'label' => lng('emails.catchall'), 'label' => lng('emails.catchall'),
'type' => 'label', 'type' => 'checkbox',
'value' => ((int)$result['iscatchall'] == 0 ? lng('panel.no') : lng('panel.yes')), 'value' => '1',
'next_to' => [ 'checked' => (int)$result['iscatchall'],
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=' . $page . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglecatchall&amp;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' => [
'label' => lng('antispam.spam_tag_level'),
'type' => 'text',
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
'value' => $result['spam_tag_level']
],
'spam_kill_level' => [
'label' => lng('antispam.spam_kill_level'),
'type' => 'text',
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
'value' => $result['spam_kill_level']
], ],
'bypass_spam' => [ 'bypass_spam' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.bypass_spam'), 'label' => lng('antispam.bypass_spam'),
'type' => 'label', 'type' => 'checkbox',
'value' => ((int)$result['bypass_spam'] == 0 ? lng('panel.no') : lng('panel.yes')), 'value' => '1',
'next_to' => [ 'checked' => (int)$result['bypass_spam'],
'add_link' => [ ],
'type' => 'link', 'spam_tag_level' => [
'href' => $filename . '?page=' . $page . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglebypass&amp;id=' . $result['id'], 'visible' => Settings::Get('antispam.activated') == '1',
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'), 'label' => lng('antispam.spam_tag_level'),
'classes' => 'btn btn-sm btn-secondary' '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' => [ 'policy_greylist' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.policy_greylist'), 'label' => lng('antispam.policy_greylist'),
'type' => 'label', 'type' => 'checkbox',
'value' => ((int)$result['policy_greylist'] == 0 ? lng('panel.no') : lng('panel.yes')), 'value' => '1',
'next_to' => [ 'checked' => (int)$result['policy_greylist'],
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=' . $page . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglegreylist&amp;id=' . $result['id'],
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
'classes' => 'btn btn-sm btn-secondary'
]
]
], ],
'mail_fwds' => [ 'mail_fwds' => [
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')', 'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',

View File

@@ -33,9 +33,9 @@ return [
'title' => lng('menue.email.emailsoverview'), 'title' => lng('menue.email.emailsoverview'),
'icon' => 'fa-solid fa-envelope', 'icon' => 'fa-solid fa-envelope',
'self_overview' => ['section' => 'email', 'page' => 'overview'], 'self_overview' => ['section' => 'email', 'page' => 'overview'],
'default_sorting' => ['d.domain' => 'asc'], 'default_sorting' => ['d.domain_ace' => 'asc'],
'columns' => [ 'columns' => [
'd.domain' => [ 'd.domain_ace' => [
'label' => 'Domain', 'label' => 'Domain',
'field' => 'domain', 'field' => 'domain',
], ],
@@ -56,7 +56,7 @@ return [
], ],
], ],
'visible_columns' => Listing::getVisibleColumnsForListing('emaildomain_list', [ 'visible_columns' => Listing::getVisibleColumnsForListing('emaildomain_list', [
'd.domain', 'd.domain_ace',
'addresses', 'addresses',
'accounts', 'accounts',
'forwarder', 'forwarder',

View File

@@ -1143,7 +1143,8 @@ Atentament, el vostre administrador'
] ]
], ],
'message' => [ 'message' => [
'norecipients' => 'No s\'ha enviat cap correu electrònic perquè no hi ha destinataris a la base de dades' 'norecipients' => 'No s\'ha enviat cap correu electrònic perquè no hi ha destinataris a la base de dades',
'success' => 'Missatge enviat correctament als destinataris de %s',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'Usuari/Nom de la base de dades', 'databasename' => 'Usuari/Nom de la base de dades',
@@ -2169,7 +2170,6 @@ Atentament, el vostre administrador'
'issuer' => 'Emissor' 'issuer' => 'Emissor'
], ],
'success' => [ 'success' => [
'messages_success' => 'Missatge enviat correctament als destinataris de %s',
'success' => 'Informació', 'success' => 'Informació',
'clickheretocontinue' => 'Feu clic aquí per a continuar', 'clickheretocontinue' => 'Feu clic aquí per a continuar',
'settingssaved' => 'La configuració s\'ha guardat correctament.', 'settingssaved' => 'La configuració s\'ha guardat correctament.',

View File

@@ -49,6 +49,7 @@ return [
'2fa_ga_desc' => 'Váš účet je nastaven tak, aby používal jednorázová hesla založená na čase prostřednictvím autentizační aplikace. Naskenujte níže uvedený QR kód pomocí požadované autentizační aplikace pro vygenerování kódů. Chcete-li deaktivovat, klikněte na "Deaktivovat 2FA"', '2fa_ga_desc' => 'Váš účet je nastaven tak, aby používal jednorázová hesla založená na čase prostřednictvím autentizační aplikace. Naskenujte níže uvedený QR kód pomocí požadované autentizační aplikace pro vygenerování kódů. Chcete-li deaktivovat, klikněte na "Deaktivovat 2FA"',
'2fa_not_activated' => 'Dvoufázové ověřování není povoleno', '2fa_not_activated' => 'Dvoufázové ověřování není povoleno',
'2fa_not_activated_for_user' => 'Dvoufaktorové ověření není pro aktuálního uživatele povoleno', '2fa_not_activated_for_user' => 'Dvoufaktorové ověření není pro aktuálního uživatele povoleno',
'type_2fa' => 'Stav 2FA',
], ],
'admin' => [ 'admin' => [
'overview' => 'Přehled', 'overview' => 'Přehled',
@@ -681,6 +682,8 @@ return [
'title' => 'Použít greylisting', 'title' => 'Použít greylisting',
'description' => 'Příchozí e-maily budou chráněny <a href="https://en.wikipedia.org/wiki/Greylisting_(email)" target="_blank">greylisting</a>.<br/>Výchozí: ano' 'description' => 'Příchozí e-maily budou chráněny <a href="https://en.wikipedia.org/wiki/Greylisting_(email)" target="_blank">greylisting</a>.<br/>Výchozí: ano'
], ],
'required_spf_dns' => 'Požadovaný SPF DNS záznam',
'required_dmarc_dns' => 'Požadovaný DMARC DNS záznam',
'required_dkim_dns' => 'Požadovaný DKIM DNS záznam', 'required_dkim_dns' => 'Požadovaný DKIM DNS záznam',
], ],
'dns' => [ 'dns' => [
@@ -782,6 +785,7 @@ return [
'hsts' => 'HSTS povoleno', 'hsts' => 'HSTS povoleno',
'aliasdomainid' => 'ID aliasové domény', 'aliasdomainid' => 'ID aliasové domény',
'nodomainsassignedbyadmin' => 'Váš účet nemá v současné době přiřazeny žádné (aktivní) domény. Pokud si myslíte, že je to špatné, kontaktujte svého správce.', 'nodomainsassignedbyadmin' => 'Váš účet nemá v současné době přiřazeny žádné (aktivní) domény. Pokud si myslíte, že je to špatné, kontaktujte svého správce.',
'email_only' => 'Jen emaily',
], ],
'emails' => [ 'emails' => [
'description' => 'Zde můžete vytvářet a měnit své e-mailové adresy.<br />Účet je jako poštovní schránka před vaším domem. Pokud vám někdo pošle e-mail, bude vypuštěn na účet.<br /><br />Chcete-li stáhnout své e-maily, použijte následující nastavení ve vašem poštovním programu: (Data v <i>kurzívě</i> musí být změněna na ekvivalenty, které jste zadali!<br />Hostitelské jméno: <b><i>doménové jméno</i></b><br />Uživatelské jméno: <b><i>jméno účtu / e-mailová adresa</i></b><br />heslo: <b><i>heslo, které jste zvolili</i></b>', 'description' => 'Zde můžete vytvářet a měnit své e-mailové adresy.<br />Účet je jako poštovní schránka před vaším domem. Pokud vám někdo pošle e-mail, bude vypuštěn na účet.<br /><br />Chcete-li stáhnout své e-maily, použijte následující nastavení ve vašem poštovním programu: (Data v <i>kurzívě</i> musí být změněna na ekvivalenty, které jste zadali!<br />Hostitelské jméno: <b><i>doménové jméno</i></b><br />Uživatelské jméno: <b><i>jméno účtu / e-mailová adresa</i></b><br />heslo: <b><i>heslo, které jste zvolili</i></b>',
@@ -1101,6 +1105,7 @@ return [
'combination_not_found' => 'Kombinace uživatele a e-mailové adresy nenalezena.', 'combination_not_found' => 'Kombinace uživatele a e-mailové adresy nenalezena.',
'2fa' => 'Dvoufázové ověření (2FA)', '2fa' => 'Dvoufázové ověření (2FA)',
'2facode' => 'Zadejte prosím 2FA kód', '2facode' => 'Zadejte prosím 2FA kód',
'2faremember' => 'Důvěřovat prohlížeči',
], ],
'mails' => [ 'mails' => [
'pop_success' => [ 'pop_success' => [
@@ -1208,6 +1213,7 @@ Ach upřímně, váš správce',
], ],
'message' => [ 'message' => [
'norecipients' => 'Nebyl odeslán žádný e-mail, protože v databázi nejsou žádní příjemci', 'norecipients' => 'Nebyl odeslán žádný e-mail, protože v databázi nejsou žádní příjemci',
'success' => 'Zpráva byla úspěšně odeslána příjemcům %s',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'Uživatel/Jméno databáze', 'databasename' => 'Uživatel/Jméno databáze',
@@ -1227,7 +1233,7 @@ Ach upřímně, váš správce',
], ],
'opcacheinfo' => [ 'opcacheinfo' => [
'generaltitle' => 'Obecné informace', 'generaltitle' => 'Obecné informace',
'resetcache' => 'Resetövat OPcache', 'resetcache' => 'Resetovat OPcache',
'version' => 'Verze OPCache', 'version' => 'Verze OPCache',
'phpversion' => 'Verze PHP', 'phpversion' => 'Verze PHP',
'runtimeconf' => 'Spustitelná konfigurace', 'runtimeconf' => 'Spustitelná konfigurace',
@@ -1941,7 +1947,7 @@ Ach upřímně, váš správce',
], ],
'documentroot_use_default_value' => [ 'documentroot_use_default_value' => [
'title' => 'Použít název domény jako výchozí hodnotu pro cestu kořenového adresáře dokumentu', 'title' => 'Použít název domény jako výchozí hodnotu pro cestu kořenového adresáře dokumentu',
'description' => 'Pokud je povoleno a cesta DocumentRoot je prázdná, výchozí hodnotou bude název (sub)domény.<br /><br />Příklady: <br />/var/customers/customer_name/example.com/<br />/var /customers/customer_name/subdomain.example.com/', 'description' => 'Pokud je povoleno a cesta DocumentRoot je prázdná, výchozí hodnotou bude název (sub)domény.<br /><br />Příklady: <br />/var/customers/webs/customer_name/example.com/<br />/var/customers/webs/customer_name/subdomain.example.com/',
], ],
'panel_phpconfigs_hidesubdomains' => [ 'panel_phpconfigs_hidesubdomains' => [
'title' => 'Skrýt subdomény v PHP-konfiguračním přehledu', 'title' => 'Skrýt subdomény v PHP-konfiguračním přehledu',
@@ -2268,7 +2274,6 @@ Ach upřímně, váš správce',
'issuer' => 'Vydavatel', 'issuer' => 'Vydavatel',
], ],
'success' => [ 'success' => [
'messages_success' => 'Zpráva byla úspěšně odeslána příjemcům %s',
'success' => 'Informace', 'success' => 'Informace',
'clickheretocontinue' => 'Klikněte zde pro pokračování', 'clickheretocontinue' => 'Klikněte zde pro pokračování',
'settingssaved' => 'Nastavení bylo úspěšně uloženo.', 'settingssaved' => 'Nastavení bylo úspěšně uloženo.',

View File

@@ -621,6 +621,10 @@ return [
'title' => 'Spam Level', 'title' => 'Spam Level',
'description' => 'Erforderliche Punktzahl zum Markieren einer E-Mail als Spam<br/>Standard: 7.0' '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' => [ 'spam_kill_level' => [
'title' => 'Ablehnungs Level', 'title' => 'Ablehnungs Level',
'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail<br/>Standard: 14.0' 'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail<br/>Standard: 14.0'
@@ -1141,6 +1145,7 @@ Vielen Dank, Ihr Administrator',
], ],
'message' => [ 'message' => [
'norecipients' => 'Es wurde keine E-Mail versendet, da sich keine Empfänger in der Datenbank befinden', 'norecipients' => 'Es wurde keine E-Mail versendet, da sich keine Empfänger in der Datenbank befinden',
'success' => 'Nachricht erfolgreich an "%s" Empfänger gesendet',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'Benutzer-/Datenbankname', 'databasename' => 'Benutzer-/Datenbankname',
@@ -1261,6 +1266,7 @@ Vielen Dank, Ihr Administrator',
'upload_import' => 'Hochladen und importieren', 'upload_import' => 'Hochladen und importieren',
'profile' => 'Mein Profil', 'profile' => 'Mein Profil',
'use_checkbox_for_unlimited' => 'Der Wert "0" deaktiviert die Resource. Die Checkbox rechts erlaubt "unlimitierte" Nutzung.', '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' => [ 'phpfpm' => [
'vhost_httpuser' => 'Lokaler Benutzer für PHP-FPM (Froxlor-Vhost)', 'vhost_httpuser' => 'Lokaler Benutzer für PHP-FPM (Froxlor-Vhost)',
@@ -2139,7 +2145,6 @@ Vielen Dank, Ihr Administrator',
'dmarc_entry' => 'DMARC-Eintrag für alle Domains', 'dmarc_entry' => 'DMARC-Eintrag für alle Domains',
], ],
'success' => [ 'success' => [
'messages_success' => 'Nachricht erfolgreich an "%s" Empfänger gesendet',
'success' => 'Information', 'success' => 'Information',
'clickheretocontinue' => 'Hier klicken, um fortzufahren', 'clickheretocontinue' => 'Hier klicken, um fortzufahren',
'settingssaved' => 'Die Einstellungen wurden erfolgreich gespeichert.', 'settingssaved' => 'Die Einstellungen wurden erfolgreich gespeichert.',

View File

@@ -670,6 +670,10 @@ return [
'title' => 'Spam tag level', 'title' => 'Spam tag level',
'description' => 'Score that is required to mark an email as spam<br/>Default: 7.0' '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' => [ 'spam_kill_level' => [
'title' => 'Spam kill level', 'title' => 'Spam kill level',
'description' => 'Score that is required to discard an email entirely<br/>Default: 14.0' 'description' => 'Score that is required to discard an email entirely<br/>Default: 14.0'
@@ -1213,6 +1217,7 @@ Yours sincerely, your administrator',
], ],
'message' => [ 'message' => [
'norecipients' => 'No e-mail has been sent because there are no recipients in the database', 'norecipients' => 'No e-mail has been sent because there are no recipients in the database',
'success' => 'Successfully sent message to %s recipients',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'User/Database name', 'databasename' => 'User/Database name',
@@ -1376,6 +1381,7 @@ Yours sincerely, your administrator',
'upload_import' => 'Upload and import', 'upload_import' => 'Upload and import',
'profile' => 'My profile', 'profile' => 'My profile',
'use_checkbox_for_unlimited' => 'The value "0" deactivates this resource. The checkbox on the right allows "unlimited" usage.', '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' => [ 'phpfpm' => [
'vhost_httpuser' => 'Local user to use for PHP-FPM (Froxlor vHost)', 'vhost_httpuser' => 'Local user to use for PHP-FPM (Froxlor vHost)',
@@ -2273,7 +2279,6 @@ Yours sincerely, your administrator',
'issuer' => 'Issuer', 'issuer' => 'Issuer',
], ],
'success' => [ 'success' => [
'messages_success' => 'Successfully sent message to %s recipients',
'success' => 'Information', 'success' => 'Information',
'clickheretocontinue' => 'Click here to continue', 'clickheretocontinue' => 'Click here to continue',
'settingssaved' => 'The settings have been successfully saved.', 'settingssaved' => 'The settings have been successfully saved.',

View File

@@ -1132,7 +1132,8 @@ Atentamente, su administrador'
] ]
], ],
'message' => [ 'message' => [
'norecipients' => 'No se ha enviado ningún correo electrónico porque no hay destinatarios en la base de datos' 'norecipients' => 'No se ha enviado ningún correo electrónico porque no hay destinatarios en la base de datos',
'success' => 'Mensaje enviado correctamente a los destinatarios de %s',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'Usuario/Nombre de la base de datos', 'databasename' => 'Usuario/Nombre de la base de datos',
@@ -2152,7 +2153,6 @@ Atentamente, su administrador'
'issuer' => 'Emisor' 'issuer' => 'Emisor'
], ],
'success' => [ 'success' => [
'messages_success' => 'Mensaje enviado correctamente a los destinatarios de %s',
'success' => 'Información', 'success' => 'Información',
'clickheretocontinue' => 'Haga clic aquí para continuar', 'clickheretocontinue' => 'Haga clic aquí para continuar',
'settingssaved' => 'La configuración se ha guardado correctamente.', 'settingssaved' => 'La configuración se ha guardado correctamente.',

View File

@@ -466,6 +466,7 @@ return [
], ],
'message' => [ 'message' => [
'norecipients' => 'Aucun e-mail n\'a été envoyé car il n\'existe aucun destinataire dans la base de données', 'norecipients' => 'Aucun e-mail n\'a été envoyé car il n\'existe aucun destinataire dans la base de données',
'success' => 'Le message a été envoyé aux destinataires "%s"',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'Nom de la base de données', 'databasename' => 'Nom de la base de données',
@@ -749,9 +750,6 @@ return [
'description' => 'Les administrateurs / revendeurs peuvent réinitialiser leurs mots de passe et il sera envoyé à leurs propres adresses e-mails', 'description' => 'Les administrateurs / revendeurs peuvent réinitialiser leurs mots de passe et il sera envoyé à leurs propres adresses e-mails',
], ],
], ],
'success' => [
'messages_success' => 'Le message a été envoyé aux destinataires "%s"',
],
'traffic' => [ 'traffic' => [
'month' => 'Mois', 'month' => 'Mois',
'day' => 'Jour', 'day' => 'Jour',

View File

@@ -1043,6 +1043,7 @@ Cordiali Saluti, Team Froxlor',
], ],
'message' => [ 'message' => [
'norecipients' => 'Nessuna e-mail è stata inviata perch¸ non ci sono i destinatari nel database', 'norecipients' => 'Nessuna e-mail è stata inviata perch¸ non ci sono i destinatari nel database',
'success' => 'Inviato correttamente il messaggio a %s recipients',
], ],
'mysql' => [ 'mysql' => [
'description' => 'Qui puoi creare e modificare il tuo database MySQL<br />Le modifiche sono istantanee e puoi usare subito il database.<br />Nel menù a sinistra trovi phpMyAdmin con cui puoi amministrare il tuo database.<br /><br />Per usare i database nei tuoi script php usa le seguenti impostazioni: (Le parole in <i>corsivo</i> devono essere modificate con quello che hai scritto!)<br />Hostname: <b><SQL_HOST></b><br />Utente: <b><i>Nome database</i></b><br />Password: <b><i>La password che hai scelto</i></b><br />Database: <b><i>Nome database</i></b>', 'description' => 'Qui puoi creare e modificare il tuo database MySQL<br />Le modifiche sono istantanee e puoi usare subito il database.<br />Nel menù a sinistra trovi phpMyAdmin con cui puoi amministrare il tuo database.<br /><br />Per usare i database nei tuoi script php usa le seguenti impostazioni: (Le parole in <i>corsivo</i> devono essere modificate con quello che hai scritto!)<br />Hostname: <b><SQL_HOST></b><br />Utente: <b><i>Nome database</i></b><br />Password: <b><i>La password che hai scelto</i></b><br />Database: <b><i>Nome database</i></b>',
@@ -1703,7 +1704,6 @@ Nota: Perfavore <b>sii sicuro</b> di usare lo stesso nome di file come per il cr
'spf_entry' => 'Impostazioni SPF per tutti i domini', 'spf_entry' => 'Impostazioni SPF per tutti i domini',
], ],
'success' => [ 'success' => [
'messages_success' => 'Inviato correttamente il messaggio a %s recipients',
'success' => 'Informazioni', 'success' => 'Informazioni',
'clickheretocontinue' => 'Clicca qui per continuare', 'clickheretocontinue' => 'Clicca qui per continuare',
'settingssaved' => 'Le impostazioni sono state salvate con successo.', 'settingssaved' => 'Le impostazioni sono state salvate con successo.',

View File

@@ -619,6 +619,7 @@ Met vriendelijke groet, uw beheerder',
], ],
'message' => [ 'message' => [
'norecipients' => 'Er is geen email verstuurd omdat er geen ontvangers in de database zijn', 'norecipients' => 'Er is geen email verstuurd omdat er geen ontvangers in de database zijn',
'success' => 'Bericht verzonden naar ontvangers %s',
], ],
'mysql' => [ 'mysql' => [
'databasename' => 'gebruiker/database naam', 'databasename' => 'gebruiker/database naam',
@@ -1069,7 +1070,6 @@ Met vriendelijke groet, uw beheerder',
'spf_entry' => 'SPF regel voor alle domeinen', 'spf_entry' => 'SPF regel voor alle domeinen',
], ],
'success' => [ 'success' => [
'messages_success' => 'Bericht verzonden naar ontvangers %s',
'success' => 'Informatie', 'success' => 'Informatie',
'clickheretocontinue' => 'Klik hier om verder te gaan', 'clickheretocontinue' => 'Klik hier om verder te gaan',
'settingssaved' => 'De instellingen zijn opgeslagen.', 'settingssaved' => 'De instellingen zijn opgeslagen.',

View File

@@ -554,6 +554,7 @@ return [
], ],
'message' => [ 'message' => [
'norecipients' => 'Email não enviado porque não tem destinatário no banco de dados', 'norecipients' => 'Email não enviado porque não tem destinatário no banco de dados',
'success' => 'Mensagens enviadas para %s destinatários com sucesso',
], ],
'mysql' => [ 'mysql' => [
'description' => 'Aqui você pode criar e alterar seus bancos de dados MySQL.<br />As alterações são instantâneas e podem ser utilizadas imediatamente depois de salvas.<br />No menu do lado esquerdo você pode encontrar a ferramenta phpMyAdmin e com ela facilmente administrar seus bancos de dados.<br /><br />Para usar seu banco de dados com scripts em PHP use as seguintes configurações: (Os dados em <i>italico</i> devem ser substituidos pelo equivalente do banco de dados que você criou!)<br />Hostname: <b><SQL_HOST></b><br />Usuario: <b><i>Nome do banco de dadose</i></b><br />Senha: <b><i>a senha que você escolheu</i></b><br />Banco de dados: <b><i>Nome do banco de dados', 'description' => 'Aqui você pode criar e alterar seus bancos de dados MySQL.<br />As alterações são instantâneas e podem ser utilizadas imediatamente depois de salvas.<br />No menu do lado esquerdo você pode encontrar a ferramenta phpMyAdmin e com ela facilmente administrar seus bancos de dados.<br /><br />Para usar seu banco de dados com scripts em PHP use as seguintes configurações: (Os dados em <i>italico</i> devem ser substituidos pelo equivalente do banco de dados que você criou!)<br />Hostname: <b><SQL_HOST></b><br />Usuario: <b><i>Nome do banco de dadose</i></b><br />Senha: <b><i>a senha que você escolheu</i></b><br />Banco de dados: <b><i>Nome do banco de dados',
@@ -880,9 +881,6 @@ return [
'description' => 'Se ativado você pode trocar o cliente de um domínio para administração de outro.<br /><b>Attention:</b> Froxlor não troca nenhum caminho. Isto pode fazer com que domínios parem de funcionar', 'description' => 'Se ativado você pode trocar o cliente de um domínio para administração de outro.<br /><b>Attention:</b> Froxlor não troca nenhum caminho. Isto pode fazer com que domínios parem de funcionar',
], ],
], ],
'success' => [
'messages_success' => 'Mensagens enviadas para %s destinatários com sucesso',
],
'traffic' => [ 'traffic' => [
'month' => 'Mês', 'month' => 'Mês',
'day' => 'Diariamente', 'day' => 'Diariamente',

16
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"@fortawesome/fontawesome-free": "^6.4.2", "@fortawesome/fontawesome-free": "^6.4.2",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"axios": "^1.6.0", "axios": "^1.7.4",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"chart.js": "^4.4.0", "chart.js": "^4.4.0",
"jquery": "^3.6.1", "jquery": "^3.6.1",
@@ -580,12 +580,12 @@
"dev": true "dev": true
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.0", "version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@@ -1093,9 +1093,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.29.4", "version": "3.29.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"

View File

@@ -9,7 +9,7 @@
"@fortawesome/fontawesome-free": "^6.4.2", "@fortawesome/fontawesome-free": "^6.4.2",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"axios": "^1.6.0", "axios": "^1.7.4",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"chart.js": "^4.4.0", "chart.js": "^4.4.0",
"jquery": "^3.6.1", "jquery": "^3.6.1",

View File

@@ -41,8 +41,9 @@ export default function () {
$('#speciallogfilenote').remove(); $('#speciallogfilenote').remove();
$('#speciallogfile').removeClass('is-invalid'); $('#speciallogfile').removeClass('is-invalid');
$('#speciallogverified').val(0); $('#speciallogverified').val(0);
const cFileName = window.location.pathname.substring(window.location.pathname.lastIndexOf("/")+1);
$.ajax({ $.ajax({
url: window.location.pathname.substring(1) + "?page=overview&action=jqSpeciallogfileNote", url: cFileName + "?page=overview&action=jqSpeciallogfileNote",
type: "POST", type: "POST",
data: { data: {
id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked') id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked')

View 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');
}
})
});
}

View File

@@ -3,7 +3,7 @@
{% if norow == false and (field.type != 'hidden' or (field.type == 'hidden' and field.display is defined and field.display is not empty)) %} {% if norow == false and (field.type != 'hidden' or (field.type == 'hidden' and field.display is defined and field.display is not empty)) %}
<div class="row g-0 formfield d-flex align-items-center"> <div class="row g-0 formfield d-flex align-items-center">
{% if field.prior_infotext is defined and field.prior_infotext|length > 0 %} {% if field.prior_infotext is defined and field.prior_infotext|length > 0 %}
<h5>{{ field.prior_infotext }}</h5> <h5>{{ field.prior_infotext|raw }}</h5>
{% endif %} {% endif %}
{% if field.label is iterable %} {% if field.label is iterable %}
<label for="{{ id }}" class="col-sm-6 col-form-label pe-3"> <label for="{{ id }}" class="col-sm-6 col-form-label pe-3">
@@ -19,6 +19,8 @@
{% endif %} {% endif %}
{% if field.label.description is defined and field.label.description is not empty %}<br><small>{{ field.label.description|raw }}</small> {% if field.label.description is defined and field.label.description is not empty %}<br><small>{{ field.label.description|raw }}</small>
{% endif %} {% 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 %} {% 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"> <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> <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 %} {% if field.next_to is defined %}
<div class="input-group"> <div class="input-group">
{% endif %} {% 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 %} {% if field.type == 'hidden' and field.display is defined %}
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}"> <input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
{% endif %} {% endif %}
@@ -207,7 +209,7 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="input-group"> <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"> <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 %}> <input class="form-check-input mt-0" type="checkbox" name="{{ id }}_ul" value="1" {% if field.value == -1 %} checked="checked" {% endif %}>
</div> </div>

View File

@@ -22,6 +22,7 @@
<input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/> <input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/>
</div> </div>
{% if remember_me %}
<div class="mb-3"> <div class="mb-3">
<div class="form-check form-switch"> <div class="form-check form-switch">
<input type="hidden" name="2fa_remember" value="0"/> <input type="hidden" name="2fa_remember" value="0"/>
@@ -29,6 +30,7 @@
<label class="form-check-label" for="2fa_remember">{{ lng('login.2faremember') }}</label> <label class="form-check-label" for="2fa_remember">{{ lng('login.2faremember') }}</label>
</div> </div>
</div> </div>
{% endif %}
</div> </div>

View File

@@ -55,7 +55,7 @@
<div class="progress-bar bg-success"></div> <div class="progress-bar bg-success"></div>
</div> </div>
<div class="progress" role="progressbar" <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-valuenow="{{ apcuinfo.num_misses }}" aria-valuemin="0"
aria-valuemax="{{ apcuinfo.num_hits_and_misses }}"> aria-valuemax="{{ apcuinfo.num_hits_and_misses }}">
<div class="progress-bar bg-danger"></div> <div class="progress-bar bg-danger"></div>