diff --git a/.gitignore b/.gitignore index 91f7a8e1..624e1c06 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ data/conf/nginx/*.conf data/conf/nginx/*.custom data/conf/nginx/*.bak data/conf/dovecot/acl_anyone +data/conf/dovecot/mail_plugins* data/conf/dovecot/extra.conf data/conf/rspamd/custom/* data/conf/portainer/ diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index 35db514c..7d824236 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -143,7 +143,7 @@ unbound_checks() { cat /dev/null > /tmp/unbound-mailcow host_ip=$(get_container_ip unbound-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H google.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad') if [[ -z ${DNSSEC} ]]; then echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2 diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 99b7d7e3..c0df123c 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -20,7 +20,7 @@ disable_plaintext_auth = yes login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" mail_home = /var/vmail/%d/%n mail_location = maildir:~/ -mail_plugins = quota acl zlib listescape mail_crypt mail_crypt_acl mail_log notify fts fts_solr +mail_plugins =
+ +
+
@@ -272,8 +275,6 @@ $tfa_data = get_tfa();
- -
+
li>a { z-index: 1; } -#settings_map { - font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; - font-size:9pt; - background:transparent; -} .table-condensed .input-sm { width: 100%!important; } diff --git a/data/web/css/debug.css b/data/web/css/debug.css index b127d5da..0eb81bc3 100644 --- a/data/web/css/debug.css +++ b/data/web/css/debug.css @@ -40,5 +40,4 @@ table.footable>tbody>tr.footable-empty>td { } tbody { font-size:14px; - color:#333; } \ No newline at end of file diff --git a/data/web/css/mailcow.css b/data/web/css/mailcow.css index 3f209f3d..8a38c8f7 100644 --- a/data/web/css/mailcow.css +++ b/data/web/css/mailcow.css @@ -19,8 +19,8 @@ @font-face { font-family: 'Source Sans Pro'; font-style: italic; - font-weight: 700; - src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldIt'), url('../fonts/SourceSansPro-BoldIt.woff2') format('woff2'); + font-weight: 300; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic.woff2') format('woff2'); } #maxmsgsize { min-width: 80px; } #slider1 .slider-selection { @@ -42,6 +42,10 @@ .btn { text-transform: none; } +.textarea-code { + font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; + background:transparent !important; +} .navbar-nav { margin: 0; } @@ -157,4 +161,4 @@ nav .glyphicon { } .full-width-select { width: 100%!important; -} \ No newline at end of file +} diff --git a/data/web/css/numberedtextarea.min.css b/data/web/css/numberedtextarea.min.css index c147b16b..133faf95 100644 --- a/data/web/css/numberedtextarea.min.css +++ b/data/web/css/numberedtextarea.min.css @@ -1 +1 @@ -div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right}textarea#script_data{font-family:Monospace} \ No newline at end of file +div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right} diff --git a/data/web/css/quarantine.css b/data/web/css/quarantine.css index 6690ab90..5d0fa1ef 100644 --- a/data/web/css/quarantine.css +++ b/data/web/css/quarantine.css @@ -35,3 +35,17 @@ table.footable>tbody>tr.footable-empty>td { .inputMissingAttr { border-color: #FF4136; } +.dot-danger { + height: 10px; + width: 10px; + background-color: #ff4136; + border-radius: 50%; + display: inline-block; +} +.dot-neutral { + height: 10px; + width: 10px; + background-color: #d4d4d4; + border-radius: 50%; + display: inline-block; +} \ No newline at end of file diff --git a/data/web/edit.php b/data/web/edit.php index e8dfc69c..6e1e0630 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -49,6 +49,20 @@ if (isset($_SESSION['mailcow_cc_role'])) {
+
+
+ +
+ +
+
+
+ +
+ +
+
+
@@ -1165,7 +1179,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
- +
diff --git a/data/web/fonts/SourceSansPro-Italic.woff2 b/data/web/fonts/SourceSansPro-Italic.woff2 new file mode 100644 index 00000000..ff006be7 Binary files /dev/null and b/data/web/fonts/SourceSansPro-Italic.woff2 differ diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php index fedca73b..71d32cc9 100644 --- a/data/web/inc/ajax/qitem_details.php +++ b/data/web/inc/ajax/qitem_details.php @@ -74,6 +74,9 @@ if (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { } } if (isset($_GET['att'])) { + if ($_SESSION['acl']['quarantine_attachments'] == 0) { + exit(json_encode('Forbidden')); + } $dl_id = intval($_GET['att']); $dl_filename = $data['attachments'][$dl_id][0]; if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 5712e525..dae26665 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -611,6 +611,8 @@ function edit_user_account($_data) { function user_get_alias_details($username) { global $lang; global $pdo; + $data['direct_aliases'] = false; + $data['shared_aliases'] = false; if ($_SESSION['mailcow_cc_role'] == "user") { $username = $_SESSION['mailcow_cc_username']; } @@ -618,7 +620,7 @@ function user_get_alias_details($username) { return false; } $data['address'] = $username; - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `shared_aliases` FROM `alias` + $stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias` WHERE `goto` REGEXP :username_goto AND `address` NOT LIKE '@%' AND `goto` != :username_goto2 @@ -630,9 +632,10 @@ function user_get_alias_details($username) { )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['shared_aliases'] = $row['shared_aliases']; + $data['shared_aliases'][] = $row['shared_aliases']; } - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`address` SEPARATOR ', ') AS `direct_aliases` FROM `alias` + + $stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias` WHERE `goto` = :username_goto AND `address` NOT LIKE '@%' AND `address` != :username_address"); @@ -640,21 +643,19 @@ function user_get_alias_details($username) { array( ':username_goto' => $username, ':username_address' => $username - )); + )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'][] = $row['direct_aliases']; + $data['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); } - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', ') AS `ad_alias` FROM `mailbox` + $stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox` LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` WHERE `username` = :username ;"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'][] = $row['ad_alias']; + $data['direct_aliases'][$row['ad_alias']]['public_comment'] = '↪ ' . $row['alias_domain']; } - $data['direct_aliases'] = implode(', ', array_filter($data['direct_aliases'])); - $data['direct_aliases'] = empty($data['direct_aliases']) ? '✘' : $data['direct_aliases']; $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 508f8049..62323645 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -453,6 +453,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $goto_null = intval($_data['goto_null']); $goto_spam = intval($_data['goto_spam']); $goto_ham = intval($_data['goto_ham']); + $private_comment = $_data['private_comment']; + $public_comment = $_data['public_comment']; + if (strlen($private_comment) > 160 | strlen($public_comment) > 160){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'comment_too_long' + ); + return false; + } if (empty($addresses[0])) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -593,10 +603,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `active`) VALUES (:address, :goto, :domain, :active)"); if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { $stmt->execute(array( + ':address' => '@'.$domain, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':address' => '@'.$domain, ':goto' => $goto, ':domain' => $domain, @@ -606,6 +619,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $stmt->execute(array( ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':goto' => $goto, ':domain' => $domain, ':active' => $active @@ -753,7 +768,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'tls_enforce_in' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'])), 'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'])), 'sogo_access' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'])), - 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']) + 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), + 'quarantine_notification' => strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']) ) ); if (!is_valid_domain_name($domain)) { @@ -1148,6 +1164,65 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; + case 'quarantine_notification': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $is_now = mailbox('get', 'quarantine_notification', $username); + if (!empty($is_now)) { + $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!in_array($quarantine_notification, array('never', 'hourly', 'daily', 'weekly'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', :quarantine_notification) + WHERE `username` = :username"); + $stmt->execute(array( + ':quarantine_notification' => $quarantine_notification, + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; case 'spam_score': if (!is_array($_data['username'])) { $usernames = array(); @@ -1587,6 +1662,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; $goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0; $goto_ham = (isset($_data['goto_ham'])) ? intval($_data['goto_ham']) : 0; + $public_comment = (isset($_data['public_comment'])) ? $_data['public_comment'] : $is_now['public_comment']; + $private_comment = (isset($_data['private_comment'])) ? $_data['private_comment'] : $is_now['private_comment']; $goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto']; $address = (!empty($_data['address'])) ? $_data['address'] : $is_now['address']; } @@ -1703,11 +1780,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($goto)) { $stmt = $pdo->prepare("UPDATE `alias` SET `address` = :address, + `public_comment` = :public_comment, + `private_comment` = :private_comment, `goto` = :goto, `active`= :active WHERE `id` = :id"); $stmt->execute(array( ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':goto' => $goto, ':active' => $active, ':id' => $id @@ -2367,6 +2448,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'tls_enforce_out' => $attrs['tls_enforce_out'] ); break; + case 'quarantine_notification': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return $attrs['quarantine_notification']; + break; case 'filters': $filters = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { @@ -2699,6 +2796,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `domain`, `goto`, `address`, + `public_comment`, + `private_comment`, `active` as `active_int`, CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, `created`, @@ -2722,6 +2821,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $aliasdata['id'] = $row['id']; $aliasdata['domain'] = $row['domain']; + $aliasdata['public_comment'] = $row['public_comment']; + $aliasdata['private_comment'] = $row['private_comment']; + $aliasdata['domain'] = $row['domain']; $aliasdata['goto'] = $row['goto']; $aliasdata['address'] = $row['address']; (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; diff --git a/data/web/inc/functions.quarantine.inc.php b/data/web/inc/functions.quarantine.inc.php index bcc9d15d..cb9ae075 100644 --- a/data/web/inc/functions.quarantine.inc.php +++ b/data/web/inc/functions.quarantine.inc.php @@ -81,12 +81,18 @@ function quarantine($_action, $_data = null) { $release_format = 'raw'; } $max_size = $_data['max_size']; + $subject = $_data['subject']; + $sender = $_data['sender']; + $html = $_data['html']; $exclude_domains = (array)$_data['exclude_domains']; try { $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); $redis->Set('Q_MAX_SIZE', intval($max_size)); $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); $redis->Set('Q_RELEASE_FORMAT', $release_format); + $redis->Set('Q_SENDER', $sender); + $redis->Set('Q_SUBJECT', $subject); + $redis->Set('Q_HTML', $html); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -393,7 +399,7 @@ function quarantine($_action, $_data = null) { break; case 'get': if ($_SESSION['mailcow_cc_role'] == "user") { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` = :mbox'); + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` = :mbox'); $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -401,7 +407,7 @@ function quarantine($_action, $_data = null) { } } elseif ($_SESSION['mailcow_cc_role'] == "admin") { - $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine`'); + $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine`'); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { $q_meta[] = $row; @@ -410,7 +416,7 @@ function quarantine($_action, $_data = null) { else { $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); foreach ($domains as $domain) { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); $stmt->execute(array(':domain' => '@' . $domain . '$')); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -428,6 +434,12 @@ function quarantine($_action, $_data = null) { $settings['max_size'] = $redis->Get('Q_MAX_SIZE'); $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT'); + $settings['subject'] = $redis->Get('Q_SUBJECT'); + $settings['sender'] = $redis->Get('Q_SENDER'); + $settings['html'] = htmlspecialchars($redis->Get('Q_HTML')); + if (empty($settings['html'])) { + $settings['html'] = htmlspecialchars(file_get_contents("/templates/quarantine.tpl")); + } } catch (RedisException $e) { $_SESSION['return'][] = array( diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 9702a2c1..a66d2ab3 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "17012019_0717"; + $db_version = "27012019_1217"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -137,6 +137,8 @@ function init_db_schema() { "domain" => "VARCHAR(255) NOT NULL", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "private_comment" => "TEXT", + "public_comment" => "TEXT", "active" => "TINYINT(1) NOT NULL DEFAULT '1'" ), "keys" => array( @@ -237,6 +239,7 @@ function init_db_schema() { "rcpt" => "VARCHAR(255)", "msg" => "LONGTEXT", "domain" => "VARCHAR(255)", + "notified" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", ), @@ -316,6 +319,8 @@ function init_db_schema() { "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'", ), "keys" => array( "primary" => array( @@ -991,6 +996,7 @@ DELIMITER ;'; $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;"); $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.sogo_access') IS NULL;"); $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_EXTRACT(`attributes`, '$.mailbox_format') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_EXTRACT(`attributes`, '$.quarantine_notification') IS NULL;"); foreach($tls_options as $tls_user => $tls_options) { $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 8a4d65bc..1b4a25f3 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -131,6 +131,9 @@ $DOCKER_TIMEOUT = 60; // Anonymize IPs logged via UI $ANONYMIZE_IPS = true; +// MAILBOX_DEFAULT_ATTRIBUTES define default attributes for new mailboxes +// These settings will not change existing mailboxes + // Force incoming TLS for new mailboxes by default $MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'] = false; @@ -143,6 +146,9 @@ $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false; // Force password change on next login (only allows login to mailcow UI) $MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; +// Send notification when quarantine is not empty (never, hourly, daily, weekly) +$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'never'; + // Default mailbox format, should not be changed unless you know exactly, what you do, keep the trailing ":" // Check dovecot.conf for further changes (e.g. shared namespace) $MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:'; diff --git a/data/web/js/admin.js b/data/web/js/admin.js index 5b5e1594..7b04e674 100644 --- a/data/web/js/admin.js +++ b/data/web/js/admin.js @@ -54,7 +54,7 @@ jQuery(function($){ function draw_domain_admins() { ft_domainadmins = FooTable.init('#domainadminstable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}}, {"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"}, {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}}, @@ -82,7 +82,7 @@ jQuery(function($){ function draw_admins() { ft_admins = FooTable.init('#adminstable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"sorted": true,"name":"usr","title":lang.username,"style":{"width":"250px"}}, {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}}, {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, @@ -109,7 +109,7 @@ jQuery(function($){ function draw_fwd_hosts() { ft_forwardinghoststable = FooTable.init('#forwardinghoststable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"host","type":"text","title":lang.host,"style":{"width":"250px"}}, {"name":"source","title":lang.source,"breakpoints":"xs sm"}, {"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"}}, @@ -134,7 +134,7 @@ jQuery(function($){ function draw_relayhosts() { ft_relayhoststable = FooTable.init('#relayhoststable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, {"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}}, {"name":"username","title":lang.username,"breakpoints":"xs sm"}, @@ -161,7 +161,7 @@ jQuery(function($){ function draw_transport_maps() { ft_relayhoststable = FooTable.init('#transportstable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, {"name":"destination","type":"text","title":lang.destination,"style":{"width":"250px"}}, {"name":"nexthop","type":"text","title":lang.nexthop,"style":{"width":"250px"}}, @@ -188,7 +188,7 @@ jQuery(function($){ function draw_queue() { ft_queuetable = FooTable.init('#queuetable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"queue_id","type":"text","title":"QID","style":{"width":"50px"}}, {"name":"queue_name","type":"text","title":"Queue","style":{"width":"120px"}}, {"name":"arrival_time","sorted": true,"direction": "DESC","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.arrival_time,"style":{"width":"170px"}}, diff --git a/data/web/js/api.js b/data/web/js/api.js index 1a5f4b06..2f98a840 100644 --- a/data/web/js/api.js +++ b/data/web/js/api.js @@ -193,7 +193,6 @@ $(document).ready(function() { } if ($(this).attr("max")) { if (Number($(this).val()) > Number($(this).attr("max"))) { - alert($(this).attr("max")) invalid = true; $(this).addClass('inputMissingAttr'); } else { @@ -314,4 +313,4 @@ $(document).ready(function() { $('#ConfirmDeleteModal').modal('hide'); }); }); -}); \ No newline at end of file +}); diff --git a/data/web/js/debug.js b/data/web/js/debug.js index 4c5119cb..8406a2cb 100644 --- a/data/web/js/debug.js +++ b/data/web/js/debug.js @@ -381,11 +381,13 @@ jQuery(function($){ function drawChart() { var data = google.visualization.arrayToDataTable(graphdata); - + var body_font_color = $('body').css("color"); var options = { is3D: true, sliceVisibilityThreshold: 0, pieSliceText: 'percentage', + backgroundColor: { fill:'transparent' }, + legend: {textStyle: {color: body_font_color}}, chartArea: { left: 0, right: 0, @@ -416,7 +418,7 @@ jQuery(function($){ {"name":"unix_time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}}, {"name": "ip","title": "IP address","breakpoints": "all","style": {"minWidth": 88}}, {"name": "sender_mime","title": "From","breakpoints": "xs sm md","style": {"minWidth": 100}}, - {"name": "rcpt_mime","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}}, + {"name": "rcpt","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}}, {"name": "subject","title": "Subject","breakpoints": "all","style": {"word-break": "break-all","minWidth": 150}}, {"name": "action","title": "Action","style": {"minwidth": 82}}, {"name": "score","title": "Score","style": {"maxWidth": 110},}, @@ -460,7 +462,12 @@ jQuery(function($){ function process_table_data(data, table) { if (table == 'rspamd_history') { $.each(data, function (i, item) { - item.rcpt_mime = item.rcpt_mime.join(",​"); + if (item.rcpt_mime != "") { + item.rcpt = item.rcpt_mime.join(", "); + } + else { + item.rcpt = item.rcpt_smtp.join(", "); + } Object.keys(item.symbols).map(function(key) { var sym = item.symbols[key]; if (sym.score <= 0) { diff --git a/data/web/js/edit.js b/data/web/js/edit.js index 45690710..97c48585 100644 --- a/data/web/js/edit.js +++ b/data/web/js/edit.js @@ -22,8 +22,6 @@ $(document).ready(function() { $('#textarea_alias_goto').prop('disabled', true); } - $("#script_data").numberedtextarea({allowTabChar: true}); - $("#mailbox-password-warning-close").click(function( event ) { $('#mailbox-passwd-hidden-info').addClass('hidden'); $('#mailbox-passwd-form-groups').removeClass('hidden'); diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 4153aa18..7b14d735 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -141,8 +141,6 @@ $(document).ready(function() { var sieveScript = $(e.relatedTarget).data('sieve-script'); $(e.currentTarget).find('#sieveDataText').html('
' + sieveScript + '
'); }); - // Set line numbers for textarea - $("#script_data").numberedtextarea({allowTabChar: true}); // Disable submit button on script change $('#script_data').on('keyup', function() { $('#add_filter_btns > #add_sieve_script').attr({"disabled": true}); @@ -712,6 +710,8 @@ jQuery(function($){ {"sorted": true,"name":"address","title":lang.alias,"style":{"width":"250px"}}, {"name":"goto","title":lang.target_address}, {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, + {"name":"public_comment","title":lang.public_comment,"breakpoints":"all"}, + {"name":"private_comment","title":lang.private_comment,"breakpoints":"all"}, {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} ], @@ -731,6 +731,8 @@ jQuery(function($){ '
'; item.chkbox = ''; item.goto = escapeHtml(item.goto.replace(/,/g, " ")); + item.public_comment = escapeHtml(item.public_comment); + item.private_comment = escapeHtml(item.private_comment); if (item.is_catch_all == 1) { item.address = '
Catch-All
' + escapeHtml(item.address); } diff --git a/data/web/js/mailcow.js b/data/web/js/mailcow.js index 6dd7e4d8..cf3a3a72 100644 --- a/data/web/js/mailcow.js +++ b/data/web/js/mailcow.js @@ -161,7 +161,8 @@ $(document).ready(function() { $(document).on("keydown", disableF5); } }); - + // Textarea line numbers + $(".textarea-code").numberedtextarea({allowTabChar: true}); // trigger container restart $('#RestartContainer').on('show.bs.modal', function(e) { var container = $(e.relatedTarget).data('container'); diff --git a/data/web/js/quarantine.js b/data/web/js/quarantine.js index 5a6162c9..090b5054 100644 --- a/data/web/js/quarantine.js +++ b/data/web/js/quarantine.js @@ -1,22 +1,23 @@ // Base64 functions var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; jQuery(function($){ + acl_data = JSON.parse(acl); // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e 0) { + item.virus = ''; + } else { + item.virus = ''; + } + if (acl_data.login_as === 1) { item.action = ''; + } + else { + item.action = ''; + } item.chkbox = ''; }); } diff --git a/data/web/json_api.php b/data/web/json_api.php index 6a876f8f..df27737b 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1152,6 +1152,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "tls_policy": process_edit_return(mailbox('edit', 'tls_policy', array_merge(array('username' => $items), $attr))); break; + case "quarantine_notification": + process_edit_return(mailbox('edit', 'quarantine_notification', array_merge(array('username' => $items), $attr))); + break; case "qitem": process_edit_return(quarantine('edit', array_merge(array('id' => $items), $attr))); break; diff --git a/data/web/lang/lang.cs.php b/data/web/lang/lang.cs.php index 689f98a9..ca2a4684 100644 --- a/data/web/lang/lang.cs.php +++ b/data/web/lang/lang.cs.php @@ -652,8 +652,8 @@ $lang['admin']['no_active_bans'] = "Žádná aktivní blokování"; $lang['admin']['quarantine'] = "Karanténa"; $lang['admin']['quarantine_retention_size'] = "Počet zadržených zpráv na poštovní schránku
0 znamená neaktivní!"; $lang['admin']['quarantine_max_size'] = "Maximální velikost v MiB (větší prvky budou smazány)
0 neznamená neomezeno!"; -$lang['admin']['quarantine_exclude_domains'] = "Vyloučené domény a doménové aliasy:"; -$lang['admin']['quarantine_release_format'] = "Formát propuštěných položek:"; +$lang['admin']['quarantine_exclude_domains'] = "Vyloučené domény a doménové aliasy"; +$lang['admin']['quarantine_release_format'] = "Formát propuštěných položek"; $lang['admin']['quarantine_release_format_raw'] = "Nezměněný originál"; $lang['admin']['quarantine_release_format_att'] = "Jako příloha"; diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index e46f29c2..f90425e6 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -378,6 +378,14 @@ $lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s u. Alias-Dom. $lang['edit']['multiple_bookings'] = 'Mehrfaches Buchen'; $lang['edit']['kind'] = 'Art'; $lang['edit']['resource'] = 'Ressource'; +$lang['edit']['public_comment'] = 'Öffentlicher Kommentar'; +$lang['mailbox']['public_comment'] = 'Öffentlicher Kommentar'; +$lang['edit']['private_comment'] = 'Privater Kommentar'; +$lang['mailbox']['private_comment'] = 'Privater Kommentar'; +$lang['edit']['comment_info'] = 'Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.'; +$lang['add']['public_comment'] = 'Öffentlicher Kommentar'; +$lang['add']['private_comment'] = 'Privater Kommentar'; +$lang['add']['comment_info'] = 'Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.'; $lang['acl']['spam_alias'] = 'Temporäre E-Mail Aliasse'; $lang['acl']['tls_policy'] = 'Verschlüsselungsrichtlinie'; @@ -630,9 +638,15 @@ $lang['admin']['queue_unban'] = "Unban einreihen"; $lang['admin']['no_active_bans'] = "Keine aktiven Bans"; $lang['admin']['quarantine'] = "Quarantäne"; -$lang['admin']['quarantine_retention_size'] = "Rückhaltungen pro Mailbox
0 bedeutet inaktiv!"; -$lang['admin']['quarantine_max_size'] = "Maximale Größe in MiB (größere Elemente werden verworfen)
0 bedeutet nicht unlimitert!"; -$lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains ausschließen:"; +$lang['admin']['quarantine_retention_size'] = "Rückhaltungen pro Mailbox:
0 bedeutet inaktiv."; +$lang['admin']['quarantine_max_size'] = "Maximale Größe in MiB (größere Elemente werden verworfen):
0 bedeutet nicht unlimitert."; +$lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains ausschließen"; +$lang['admin']['quarantine_notification_sender'] = "Benachrichtigungs-E-Mail Absender"; +$lang['admin']['quarantine_notification_subject'] = "Benachrichtigungs-E-Mail Betreff"; +$lang['admin']['quarantine_notification_html'] = "Benachrichtigungs-E-Mail Inhalt:
Leer lassen, um Standard-Template wiederherzustellen."; +$lang['admin']['quarantine_release_format'] = "Format freigegebener Mails"; +$lang['admin']['quarantine_release_format_raw'] = "Unverändertes Original"; +$lang['admin']['quarantine_release_format_att'] = "Als Anhang"; $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt"; $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt"; @@ -676,6 +690,7 @@ $lang['edit']['spam_policy'] = "Hinzufügen und Entfernen von Einträgen in Whit $lang['edit']['spam_alias'] = "Anpassen temporärer Alias-Adressen"; $lang['danger']['img_tmp_missing'] = "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen"; +$lang['danger']['comment_too_long'] = "Kommentarfeld darf maximal 160 Zeichen enthalten"; $lang['danger']['img_invalid'] = "Grafik konnte nicht validiert werden"; $lang['danger']['invalid_mime_type'] = "Grafik konnte nicht validiert werden: Ungültiger MIME-Type"; $lang['success']['upload_success'] = "Datei wurde erfolgreich hochgeladen"; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index a575844e..06c34551 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -392,7 +392,14 @@ $lang['edit']['multiple_bookings'] = 'Multiple bookings'; $lang['edit']['kind'] = 'Kind'; $lang['edit']['resource'] = 'Resource'; $lang['edit']['relayhost'] = 'Sender-dependent transports'; - +$lang['edit']['public_comment'] = 'Public comment'; +$lang['mailbox']['public_comment'] = 'Public comment'; +$lang['edit']['private_comment'] = 'Private comment'; +$lang['mailbox']['private_comment'] = 'Private comment'; +$lang['edit']['comment_info'] = 'A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview'; +$lang['add']['public_comment'] = 'Public comment'; +$lang['add']['private_comment'] = 'Private comment'; +$lang['add']['comment_info'] = 'A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview'; $lang['acl']['spam_alias'] = 'Temporary aliases'; $lang['acl']['tls_policy'] = 'TLS policy'; $lang['acl']['spam_score'] = 'Spam score'; @@ -666,13 +673,15 @@ $lang['admin']['queue_unban'] = "queue unban"; $lang['admin']['no_active_bans'] = "No active bans"; $lang['admin']['quarantine'] = "Quarantine"; -$lang['admin']['quarantine_retention_size'] = "Retentions per mailbox
0 indicates inactive!"; -$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded)
0 does not indicate unlimited!"; -$lang['admin']['quarantine_exclude_domains'] = "Exclude domains and alias-domains:"; -$lang['admin']['quarantine_release_format'] = "Format of released items:"; +$lang['admin']['quarantine_retention_size'] = "Retentions per mailbox:
0 indicates inactive."; +$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded):
0 does not indicate unlimited."; +$lang['admin']['quarantine_exclude_domains'] = "Exclude domains and alias-domains"; +$lang['admin']['quarantine_release_format'] = "Format of released items"; $lang['admin']['quarantine_release_format_raw'] = "Unmodified original"; $lang['admin']['quarantine_release_format_att'] = "As attachment"; - +$lang['admin']['quarantine_notification_sender'] = "Notification email sender"; +$lang['admin']['quarantine_notification_subject'] = "Notification email subject"; +$lang['admin']['quarantine_notification_html'] = "Notification email template:
Leave empty to restore default template."; $lang['admin']['ui_texts'] = "UI labels and texts"; $lang['admin']['help_text'] = "Override help text below login mask (HTML allowed)"; $lang['admin']['title_name'] = '"mailcow UI" website title'; @@ -699,6 +708,7 @@ $lang['user']['spam_score_reset'] = "Reset to server default"; $lang['edit']['spam_policy'] = "Add or remove items to white-/blacklist"; $lang['edit']['spam_alias'] = "Create or change time limited alias addresses"; +$lang['danger']['comment_too_long'] = "Comment too long, max 160 chars allowed"; $lang['danger']['img_tmp_missing'] = "Cannot validate image file: Temporary file not found"; $lang['danger']['img_invalid'] = "Cannot validate image file"; $lang['danger']['invalid_mime_type'] = "Invalid mime type"; diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php index 9c9fd6b0..22351121 100644 --- a/data/web/lang/lang.nl.php +++ b/data/web/lang/lang.nl.php @@ -638,8 +638,8 @@ $lang['admin']['no_active_bans'] = "Geen actieve verbanningen"; $lang['admin']['quarantine'] = "Quarantaine"; $lang['admin']['quarantine_retention_size'] = "Maximale retenties per postvak
Gebruik 0 om deze functionaliteit uit te zetten."; $lang['admin']['quarantine_max_size'] = "Maximale grootte in MiB (mail die de limiet overschrijdt zal worden verwijderd)
0 betekent niet onbeperkt!"; -$lang['admin']['quarantine_exclude_domains'] = "Sluit domeinen en aliasdomeinen uit:"; -$lang['admin']['quarantine_release_format'] = "Vrijgegeven items worden verstuurd als:"; +$lang['admin']['quarantine_exclude_domains'] = "Sluit domeinen en aliasdomeinen uit"; +$lang['admin']['quarantine_release_format'] = "Vrijgegeven items worden verstuurd als"; $lang['admin']['quarantine_release_format_raw'] = "Origineel"; $lang['admin']['quarantine_release_format_att'] = "Bijlage"; diff --git a/data/web/mailbox.php b/data/web/mailbox.php index 863d5a92..1a47f7a7 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -1,5 +1,6 @@
- +
diff --git a/data/web/modals/quarantine.php b/data/web/modals/quarantine.php index 5a22446e..e1929927 100644 --- a/data/web/modals/quarantine.php +++ b/data/web/modals/quarantine.php @@ -25,11 +25,17 @@ if (!isset($_SESSION['mailcow_cc_role'])) {

         
+
-
-
+ +
diff --git a/data/web/quarantine.php b/data/web/quarantine.php index f21f3304..240d667f 100644 --- a/data/web/quarantine.php +++ b/data/web/quarantine.php @@ -18,7 +18,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; - +
@@ -39,6 +39,11 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+
+
+

+

+
@@ -49,6 +54,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/quarantine.php';