From 07392b7437cddbbe937c259bc91bbb1267c93ca2 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 29 Jan 2019 00:20:39 +0100 Subject: [PATCH] [Watchdog] Use stackoverflow.com for DNS check [Git] Ignore mail_plugins* [Dovecot] Read mail_plugins from dynamically generated file [Dovecot] Encrypt FTS [Dovecot] Add break_imap_seach option to Solr [Web] Add ability to send quarantine notification mails [Web] Minor style fixes [Web] Add new MAILBOX_DEFAULT_ATTRIBUTES (doc updates, anyone? :-( ) [Web] Use rcpt_smtp if rcpt_mime is not set [Web] Other minor fixes --- .gitignore | 1 + data/Dockerfiles/watchdog/watchdog.sh | 2 +- data/conf/dovecot/dovecot.conf | 11 ++- data/web/admin.php | 35 ++++++- data/web/css/admin.css | 10 +- data/web/css/debug.css | 1 - data/web/css/mailcow.css | 10 +- data/web/css/numberedtextarea.min.css | 2 +- data/web/css/quarantine.css | 14 +++ data/web/edit.php | 16 +++- data/web/fonts/SourceSansPro-Italic.woff2 | Bin 0 -> 35924 bytes data/web/inc/ajax/qitem_details.php | 3 + data/web/inc/functions.inc.php | 19 ++-- data/web/inc/functions.mailbox.inc.php | 106 +++++++++++++++++++++- data/web/inc/functions.quarantine.inc.php | 18 +++- data/web/inc/init_db.inc.php | 8 +- data/web/inc/vars.inc.php | 6 ++ data/web/js/admin.js | 12 +-- data/web/js/api.js | 3 +- data/web/js/debug.js | 13 ++- data/web/js/edit.js | 2 - data/web/js/mailbox.js | 6 +- data/web/js/mailcow.js | 3 +- data/web/js/quarantine.js | 32 +++++-- data/web/json_api.php | 3 + data/web/lang/lang.cs.php | 4 +- data/web/lang/lang.de.php | 21 ++++- data/web/lang/lang.en.php | 22 +++-- data/web/lang/lang.nl.php | 4 +- data/web/mailbox.php | 1 + data/web/modals/mailbox.php | 2 +- data/web/modals/quarantine.php | 8 +- data/web/modals/user.php | 2 +- data/web/quarantine.php | 8 +- data/web/user.php | 63 ++++++++++++- 35 files changed, 389 insertions(+), 82 deletions(-) create mode 100644 data/web/fonts/SourceSansPro-Italic.woff2 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 0000000000000000000000000000000000000000..ff006be7822524e04919e0e7233574f3d85e01b7 GIT binary patch literal 35924 zcmV)0K+eB+Pew8T0RR910E|=s5&!@I0eox#0E_Sd0RR9100000000000000000000 z0000PMjC=<8`%sTnrsGO0F!tKDhYyf5eN!{?hJ#kJqwLA00A}vBmLF$cJ7 z3|3n$Rgnc%$9A28pH!vW5FkEVntt{;Uczp{bK3whYyRaZT(NNnpgTx%G+NSU|NsC0 z|Gyxa$e3NOa!FGxfPgyL-1a}nxQz?cWPbUqq{-DF1jdTE+UQfaz1Eqz2U(&!S!djq zO^*GD{cguJiNm-=4w)tsb{DDAWv0gy4q;oVwgVr9{-R<}h{*`e?oFq=sU(cd18y9% zakxq2@RqqoDwzw;$E01jZ>V!F_#_TzYZa?p;$M7P%+2J0cdTzYTsDoLG-s-U;3{5m zeZl?VaH8+4<2av*%-4xbWcOK8;3E>5iTOUEWmJ)hyV>l|FaD7jWJpH8|DTYJ@{PYQ z_DHiX&*qg%Y;`Y8BccYkPJWa)&^Ma%h!I_g!A*9ue^vvZab-d<4X58Awy+Vw1Qp8c zweT#sAN1YnI=tHsp;gtBk!XMK8xpr4vAK26O|Md3v7&<_N>^n=c-&ebZ9_D-yI-)u*anQ~ z5j8+EY?K(GgiX&`V8sZFsNtAOw>UGw39U`!sBqQ@1;K6m6+ugS3#Mie!E37ZK?cSD zs`IeB1W~etrWa>jNO)}Bho8ChwafJX#@LgdXL6I+n2PqepMpxG;0KJg!!q|01nUqYW1gM!t?fBOX<(0+b)5ix)Kj%)V7rBLB#7|%<9h9@uZoJT3Fv(69vztk^ zQbZDw00EF096#gu)}!7UnM%Owl=K$4^epGN-&kv6Av`y)vW*kuhzEjkf_Y)9@c*7 zVt(5Y58d*$w6u$#3;x69{=FM>fv;V_MVfG~MHHU2>3~%$(&7_7knJTr`CR1R97t*h zzA#_~r3>0=l>ZV}SqvIB55sKf7B%d6Wd` z3C5ZP|L_Oz&begpy$ zgEIh{ID+8J9zl*GFuWtc<31EgV91^2DWxe!DRSmk>eo_OA8VLrzGrIf8t=N=dkz2p z?fq%r+)W?ap$-d$#5r)Tl|yTflJ<03RaJdr1UgDIp(7*YckwDVLW%hQsruRS&J#4! zW-fMfrNgz|w50SMW1owf9KPWg96imqK7ZVmLyQ6*K#2sTjk$A(ZR^%e?Y7_2rTxCO z(soVFU!J47Fe}u4c%ilqXF`3g0>Dlr%0}g_P|@-K-K)~b&pJvv_5@Vd=)a=CAn0+G zrT3QDZ%sJ_I|lz zz_jmu5OQxDL`1BF!5fCD3?kcbU+uMAH4P1nkTT)~@jbQQEwbr~&jq}a3SE$!^Z#Ye z|AXzD>wDU*76c(7W8UOuptR@1)7FRP%|K3FcDqfBe5?#2A|k&cCh6f=dsq5u*xwVB zQz)*Tt!`}E;TF9C+cpjROI+Hi-JTB56GTLUh=i_XKkh%VEHMs=u}K&AE?j9cYWa%@+>-gOv{6!w(if5I6+zU>O7iI0oQ-2z3Z6kUI|$0|Ru#5eOC*C|@C{ zTsf#&3uwd$Xpw^g5SS<-!4$$mQjP%0ATA_Yd@$1_ki6ss3y>F#p*AFzfnZ$MgJsDM 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 MNmd4qprepare("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';