public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[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
07392b74
.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
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 = </usr/local/etc/dovecot/mail_plugins
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
mail_attachment_dir = /var/attachments
mail_attachment_min_size = 128k
@@ -290,12 +290,12 @@ userdb {
skip = found
}
protocol imap {
+ mail_plugins = </usr/local/etc/dovecot/mail_plugins_imap
imap_metadata = yes
- mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape mail_crypt mail_crypt_acl notify mail_log fts fts_solr
}
mail_attribute_dict = file:%h/dovecot-attributes
protocol lmtp {
- mail_plugins = quota sieve acl zlib listescape mail_crypt mail_crypt_acl fts fts_solr
+ mail_plugins = </usr/local/etc/dovecot/mail_plugins_lmtp
auth_socket_path = /usr/local/var/run/dovecot/auth-master
}
protocol sieve {
@@ -308,7 +308,10 @@ plugin {
acl = vfile
fts = solr
fts_autoindex = yes
- fts_solr = url=http://solr:8983/solr/dovecot/
+ fts_solr = break-imap-search url=http://solr:8983/solr/dovecot/
+ fts_index_fs = crypt:set_prefix=fscrypt_index:posix:
+ fscrypt_index_public_key = </mail_crypt/ecpubkey.pem
+ fscrypt_index_private_key = </mail_crypt/ecprivkey.pem
quota = dict:Userquota::proxy::sqlquota
quota_rule2 = Trash:storage=+100%%
sieve = /var/vmail/sieve/%u.sieve
diff --git a/data/web/admin.php b/data/web/admin.php
index 31085b7b..90223209 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -7,6 +7,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa();
?>
<div class="container">
+
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a></li>
<li role="presentation"><a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a></li>
@@ -15,6 +16,8 @@ $tfa_data = get_tfa();
<li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab"><?=$lang['admin']['queue_manager'];?></a></li>
</ul>
+ <div class="row">
+ <div class="col-md-12">
<div class="tab-content" style="padding-top:20px">
<div role="tabpanel" class="tab-pane active" id="tab-access">
<div class="panel panel-danger">
@@ -272,8 +275,6 @@ $tfa_data = get_tfa();
</div>
</div>
-
-
<div role="tabpanel" class="tab-pane" id="tab-config">
<div class="row">
<div id="sidebar-admin" class="col-sm-2 hidden-xs">
@@ -631,7 +632,28 @@ $tfa_data = get_tfa();
<div class="row">
<div class="col-sm-6">
<div class="form-group">
- <label for="release_format"><?=$lang['admin']['quarantine_release_format'];?></label>
+ <label for="sender"><?=$lang['admin']['quarantine_notification_sender'];?>:</label>
+ <input type="text" class="form-control" name="sender" value="<?=$q_data['sender'];?>" placeholder="quarantine@localhost">
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="subject"><?=$lang['admin']['quarantine_notification_subject'];?>:</label>
+ <input type="text" class="form-control" name="subject" value="<?=$q_data['subject'];?>" placeholder="Spam Quarantine Notification">
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-sm-12">
+ <label for="html"><?=$lang['admin']['quarantine_notification_html'];?></label>
+ <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="html"><?=$q_data['html'];?></textarea>
+ <br>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="release_format"><?=$lang['admin']['quarantine_release_format'];?>:</label>
<select data-width="100%" name="release_format" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
<option <?=($q_data['release_format'] == 'raw') ? 'selected' : null;?> value="raw"><?=$lang['admin']['quarantine_release_format_raw'];?></option>
<option <?=($q_data['release_format'] == 'attachment') ? 'selected' : null;?> value="attachment"><?=$lang['admin']['quarantine_release_format_att'];?></option>
@@ -640,7 +662,7 @@ $tfa_data = get_tfa();
</div>
<div class="col-sm-6">
<div class="form-group">
- <label for="exclude_domains"><?=$lang['admin']['quarantine_exclude_domains'];?></label><br />
+ <label for="exclude_domains"><?=$lang['admin']['quarantine_exclude_domains'];?>:</label><br />
<select data-width="100%" name="exclude_domains" class="selectpicker" title="<?=$lang['tfa']['select'];?>" multiple>
<?php
foreach (array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')) as $domain):
@@ -663,7 +685,7 @@ $tfa_data = get_tfa();
<div class="panel-heading">Rspamd settings map</div>
<div class="panel-body">
<legend>Active settings map</legend>
- <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="settings_map" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
+ <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
<hr>
<?php $rsettings = rsettings('get'); ?>
<form class="form" data-id="rsettings" role="form" method="post">
@@ -970,6 +992,9 @@ $tfa_data = get_tfa();
</div>
</div>
+ </div> <!-- /tab-content -->
+ </div> <!-- /col-md-12 -->
+ </div> <!-- /row -->
</div> <!-- /container -->
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/admin.php';
diff --git a/data/web/css/admin.css b/data/web/css/admin.css
index b1871d87..cadc4a3a 100644
--- a/data/web/css/admin.css
+++ b/data/web/css/admin.css
@@ -24,6 +24,11 @@ body.modal-open {
overflow-y:scroll;
padding-right: inherit !important;
}
+@media (min-width: 992px) {
+ .container {
+ width: 80%;
+ }
+}
.mass-actions-admin {
user-select: none;
padding:10px 0 10px 0;
@@ -60,11 +65,6 @@ body.modal-open {
.nav-tabs>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'])) {
</div>
</div>
</div>
+ <hr>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="private_"><?=$lang['edit']['private_comment'];?></label>
+ <div class="col-sm-10">
+ <input maxlength="160" class="form-control" type="text" name="private_comment" value="<?=htmlspecialchars($result['private_comment']);?>" />
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="public_comment"><?=$lang['edit']['public_comment'];?></label>
+ <div class="col-sm-10">
+ <input maxlength="160" class="form-control" type="text" name="public_comment" value="<?=htmlspecialchars($result['public_comment']);?>" />
+ </div>
+ </div>
+ <hr>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
@@ -1165,7 +1179,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<div class="form-group">
<label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10">
- <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="20" id="script_data" name="script_data" required><?=$result['script_data'];?></textarea>
+ <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required><?=$result['script_data'];?></textarea>
</div>
</div>
<div class="form-group">
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('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>');
});
- // 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($){
'</div>';
item.chkbox = '<input type="checkbox" data-id="alias" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
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 = '<div class="label label-default">Catch-All</div> ' + 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<r.length;)a=(t=r.charCodeAt(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<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(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;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&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;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&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<B.length-1);return i.toFixed(1)+" "+B[e]}
-
function draw_quarantine_table() {
ft_quarantinetable = FooTable.init('#quarantinetable', {
"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":"ID","filterable": false,"sorted": true,"direction":"DESC","title":"ID","style":{"width":"50px"}},
- {"name":"qid","type":"text","title":lang.qid,"style":{"width":"125px"}},
- {"name":"sender","style":{"word-break":"break-all"},"title":lang.sender,"breakpoints":"xs sm"},
- {"name":"rcpt","title":lang.rcpt, "type": "text"},
- {"name":"subject","title":"Subject", "type": "text"},
+ {"name":"qid","breakpoints":"all","type":"text","title":lang.qid,"style":{"width":"125px"}},
+ {"name":"sender","title":lang.sender},
+ {"name":"rcpt","title":lang.rcpt, "breakpoints":"xs sm md", "type": "text"},
+ {"name":"virus","title":lang.danger, "type": "text"},
+ {"name":"subject","title":lang.subj, "type": "text"},
{"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.received,"style":{"width":"170px"}},
- {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+ {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
@@ -28,15 +29,26 @@ jQuery(function($){
success: function (data) {
$.each(data, function (i, item) {
if (item.subject === null) {
- item.subject = '<i>no preview</i>';
- }
- else {
+ item.subject = '';
+ } else {
item.subject = escapeHtml(item.subject);
}
+ if (item.virus_flag > 0) {
+ item.virus = '<span class="dot-danger"></span>';
+ } else {
+ item.virus = '<span class="dot-neutral"></span>';
+ }
+ if (acl_data.login_as === 1) {
item.action = '<div class="btn-group">' +
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' +
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
+ }
+ else {
+ item.action = '<div class="btn-group">' +
+ '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' +
+ '</div>';
+ }
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
});
}
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<br />0 znamená <b>neaktivní</b>!";
$lang['admin']['quarantine_max_size'] = "Maximální velikost v MiB (větší prvky budou smazány)<br />0 <b>neznamená</b> 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<br />0 bedeutet <b>inaktiv</b>!";
-$lang['admin']['quarantine_max_size'] = "Maximale Größe in MiB (größere Elemente werden verworfen)<br />0 bedeutet <b>nicht</b> unlimitert!";
-$lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains ausschließen:";
+$lang['admin']['quarantine_retention_size'] = "Rückhaltungen pro Mailbox:<br><small>0 bedeutet <b>inaktiv</b>.</small>";
+$lang['admin']['quarantine_max_size'] = "Maximale Größe in MiB (größere Elemente werden verworfen):<br><small>0 bedeutet <b>nicht</b> unlimitert.</small>";
+$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:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>";
+$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<br />0 indicates <b>inactive</b>!";
-$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded)<br />0 does <b>not</b> 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:<br><small>0 indicates <b>inactive</b>.</small>";
+$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded):<br><small>0 does <b>not</b> indicate unlimited.</small>";
+$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:<br><small>Leave empty to restore default template.</small>";
$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<br />Gebruik 0 om deze functionaliteit <b>uit te zetten</b>.";
$lang['admin']['quarantine_max_size'] = "Maximale grootte in MiB (mail die de limiet overschrijdt zal worden verwijderd)<br />0 betekent <b>niet</b> 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 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+
if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php
index 608784fc..c12df381 100644
--- a/data/web/modals/mailbox.php
+++ b/data/web/modals/mailbox.php
@@ -572,7 +572,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<div class="form-group">
<label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10">
- <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="script_data" name="script_data" required></textarea>
+ <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required></textarea>
</div>
</div>
<div class="form-group">
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'])) {
<label for="qid_detail_text_from_html"><h4><?=$lang['quarantine']['text_from_html_content'];?>:</h4></label>
<pre id="qid_detail_text_from_html"></pre>
</div>
+ <?php
+ if ($_SESSION['acl']['quarantine_attachments'] == 1):
+ ?>
<div class="form-group">
<label for="qid_detail_atts"><h4><?=$lang['quarantine']['atts'];?>:</h4></label>
<div id="qid_detail_atts">-</div>
</div>
- <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine'];?>">
+ <?php
+ endif;
+ ?>
+ <div class="btn-group dropup" data-acl="<?=$_SESSION['acl']['quarantine'];?>">
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['quarantine']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="qitems_single" data-item="" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#"><?=$lang['quarantine']['release'];?></a></li>
diff --git a/data/web/modals/user.php b/data/web/modals/user.php
index cbc799d7..c45283d2 100644
--- a/data/web/modals/user.php
+++ b/data/web/modals/user.php
@@ -168,7 +168,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<div class="modal-content">
<div class="modal-header"><h4 class="modal-title">Log</h4></div>
<div class="modal-body">
- <textarea class="form-control" rows="20" id="logText" spellcheck="false"></textarea>
+ <textarea class="form-control textarea-code" rows="20" id="logText" spellcheck="false"></textarea>
</div>
</div>
</div>
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'];
<?php
if (empty(quarantine('settings')['retention_size']) || empty(quarantine('settings')['max_size'])):
?>
- <span class="help-block"><span class="glyphicon glyphicon-remove text-danger" style="font-size:10px"></span> <b><?=$lang['quarantine']['disabled_by_config'];?></b></span>
+ <div class="panel-body"><div class="alert alert-info"><?=$lang['quarantine']['disabled_by_config'];?></div></div>
<?php
endif;
?>
@@ -39,6 +39,11 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
</ul>
</div>
</div>
+ <hr>
+ <div class="panel-body help-block">
+ <p><span class="dot-danger"></span> <?=$lang['quarantine']['high_danger'];?></p>
+ <p><span class="dot-neutral"></span> <?=$lang['quarantine']['neutral_danger'];?></p>
+ </div>
</div>
</div> <!-- /col-md-12 -->
</div> <!-- /row -->
@@ -49,6 +54,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/quarantine.php';
<script type='text/javascript'>
<?php
$lang_mailbox = json_encode($lang['quarantine']);
+echo "var acl = '". json_encode($_SESSION['acl']) . "';\n";
echo "var lang = ". $lang_mailbox . ";\n";
echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
diff --git a/data/web/user.php b/data/web/user.php
index 5bd9ee9d..f65e20f6 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -125,7 +125,18 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<p class="small"><?=$lang['user']['direct_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
- <p><?=$user_get_alias_details['direct_aliases'];?></p>
+ <?php
+ if ($user_get_alias_details['direct_aliases'] === false) {
+ echo '✘';
+ }
+ else {
+ foreach (array_filter($user_get_alias_details['direct_aliases']) as $direct_alias => $direct_alias_meta) {
+ (!empty($direct_alias_meta['public_comment'])) ?
+ printf('%s <small>(%s)</small><br>', $direct_alias, $direct_alias_meta['public_comment']) :
+ printf('%s<br>', $direct_alias);
+ }
+ }
+ ?>
</div>
</div>
<div class="row">
@@ -133,7 +144,18 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<p class="small"><?=$lang['user']['shared_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
- <p><?=$user_get_alias_details['shared_aliases'];?></p>
+ <?php
+ if ($user_get_alias_details['shared_aliases'] === false) {
+ echo '✘';
+ }
+ else {
+ foreach (array_filter($user_get_alias_details['shared_aliases']) as $shared_alias => $shared_alias_meta) {
+ (!empty($shared_alias_meta['public_comment'])) ?
+ printf('%s <small>(%s)</small><br>', $shared_alias, $shared_alias_meta['public_comment']) :
+ printf('%s<br>', $shared_alias);
+ }
+ }
+ ?>
</div>
</div>
<hr>
@@ -223,7 +245,42 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
</div>
</div>
-
+ <?php
+ // Show quarantine_notification options
+ $quarantine_notification = mailbox('get', 'quarantine_notification', $username);
+ ?>
+ <div class="row">
+ <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_notification'];?>:</div>
+ <div class="col-md-9 col-xs-7">
+ <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>">
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_notification"
+ data-api-url='edit/quarantine_notification'
+ data-api-attr='{"quarantine_notification":"never"}'><?=$lang['user']['never'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "hourly") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_notification"
+ data-api-url='edit/quarantine_notification'
+ data-api-attr='{"quarantine_notification":"hourly"}'><?=$lang['user']['hourly'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "daily") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_notification"
+ data-api-url='edit/quarantine_notification'
+ data-api-attr='{"quarantine_notification":"daily"}'><?=$lang['user']['daily'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "weekly") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_notification"
+ data-api-url='edit/quarantine_notification'
+ data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button>
+ </div>
+ <p class="help-block"><?=$lang['user']['quarantine_notification_info'];?></p>
+ </div>
+ </div>
<hr>
<div class="row">