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
[Web] Allow a user to choose notification categories (junk folder, rejected mail, both/all) + user ACL
ba20db2e
data/Dockerfiles/dovecot/quarantine_notify.py | 32 +++++------
data/web/edit.php | 30 +++++++++-
data/web/inc/functions.inc.php | 3 +
data/web/inc/functions.mailbox.inc.php | 79 ++++++++++++++++++++++++++-
data/web/inc/init_db.inc.php | 4 +-
data/web/inc/vars.inc.php | 6 ++
data/web/js/site/mailbox.js | 10 +++-
data/web/json_api.php | 3 +
data/web/lang/lang.de.json | 10 ++++
data/web/lang/lang.en.json | 10 ++++
data/web/mailbox.php | 11 +++-
data/web/user.php | 27 +++++++++
docker-compose.yml | 2 +-
13 files changed, 204 insertions(+), 23 deletions(-)
Diff
diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py
index b0ed29b9..ae0a42f2 100755
--- a/data/Dockerfiles/dovecot/quarantine_notify.py
+++ b/data/Dockerfiles/dovecot/quarantine_notify.py
@@ -58,8 +58,13 @@ def query_mysql(query, headers = True, update = False):
cur.close()
cnx.close()
-def notify_rcpt(rcpt, msg_count, quarantine_acl):
- meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f' % (rcpt, max_score))
+def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
+ if category == "add_header": category = "add header"
+ meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
+ print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
+ if len(meta_query) == 0:
+ return
+ msg_count = len(meta_query)
if r.get('Q_HTML'):
try:
template = Template(r.get('Q_HTML'))
@@ -117,6 +122,11 @@ records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl,
for record in records:
attrs = ''
attrs_json = ''
+ time_trans = {
+ "hourly": 3600,
+ "daily": 86400,
+ "weekly": 604800
+ }
try:
last_notification = int(r.hget('Q_LAST_NOTIFIED', record['rcpt']))
if last_notification > time_now:
@@ -133,18 +143,8 @@ for record in records:
else:
# if it's bytes then decode and load it
attrs = json.loads(attrs.decode('utf-8'))
- if attrs['quarantine_notification'] not in ('hourly', 'daily', 'weekly', 'never'):
- print('Abnormal quarantine_notification value')
+ if attrs['quarantine_notification'] not in ('hourly', 'daily', 'weekly'):
continue
- if attrs['quarantine_notification'] == 'hourly':
- if last_notification == 0 or (last_notification + 3600) < time_now:
- print("Notifying %s: Considering %d new items in quarantine" % (record['rcpt'], record['counter']))
- notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'])
- elif attrs['quarantine_notification'] == 'daily':
- if last_notification == 0 or (last_notification + 86400) < time_now:
- print("Notifying %s: Considering %d new items in quarantine" % (record['rcpt'], record['counter']))
- notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'])
- elif attrs['quarantine_notification'] == 'weekly':
- if last_notification == 0 or (last_notification + 604800) < time_now:
- print("Notifying %s: Considering %d new items in quarantine" % (record['rcpt'], record['counter']))
- notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'])
+ if last_notification == 0 or (last_notification + time_trans[attrs['quarantine_notification']]) < time_now:
+ print("Notifying %s: Considering %d new items in quarantine (policy: %s)" % (record['rcpt'], record['counter'], attrs['quarantine_notification']))
+ notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category'])
diff --git a/data/web/edit.php b/data/web/edit.php
index 10b85660..3a0bb04b 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -572,6 +572,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
$rl = ratelimit('get', 'mailbox', $mailbox);
$pushover_data = pushover('get', $mailbox);
$quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox);
+ $quarantine_category = mailbox('get', 'quarantine_category', $mailbox);
$get_tls_policy = mailbox('get', 'tls_policy', $mailbox);
if (!empty($result)) {
?>
@@ -660,7 +661,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
</div>
</div>
<div class="form-group">
- <label class="control-label col-sm-2" for="sender_acl"><?=$lang['user']['quarantine_notification'];?></label>
+ <label class="control-label col-sm-2"><?=$lang['user']['quarantine_notification'];?></label>
<div class="col-sm-10">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>">
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>"
@@ -688,10 +689,35 @@ if (isset($_SESSION['mailcow_cc_role'])) {
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button>
</div>
- <div style="display:none" id="user_acl_q_notify_disabled"><?=$lang['edit']['user_acl_q_notify_disabled'];?></div>
<p class="help-block"><small><?=$lang['user']['quarantine_notification_info'];?></small></p>
</div>
</div>
+ <div class="form-group">
+ <label class="control-label col-sm-2"><?=$lang['user']['quarantine_category'];?></label>
+ <div class="col-sm-10">
+ <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_category'];?>">
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_category == "reject") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($mailbox); ?>"
+ data-id="quarantine_category"
+ data-api-url='edit/quarantine_category'
+ data-api-attr='{"quarantine_category":"reject"}'><?=$lang['user']['q_reject'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_category == "add_header") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($mailbox); ?>"
+ data-id="quarantine_category"
+ data-api-url='edit/quarantine_category'
+ data-api-attr='{"quarantine_category":"add_header"}'><?=$lang['user']['q_add_header'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_category == "all") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($mailbox); ?>"
+ data-id="quarantine_category"
+ data-api-url='edit/quarantine_category'
+ data-api-attr='{"quarantine_category":"all"}'><?=$lang['user']['q_all'];?></button>
+ </div>
+ <p class="help-block"><small><?=$lang['user']['quarantine_category_info'];?></small></p>
+ </div>
+ </div>
<div class="form-group">
<label class="control-label col-sm-2" for="sender_acl"><?=$lang['user']['tls_policy'];?></label>
<div class="col-sm-10">
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 1181b3f5..21852259 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -332,6 +332,9 @@ function hasDomainAccess($username, $role, $domain) {
}
function hasMailboxObjectAccess($username, $role, $object) {
global $pdo;
+ if (empty($username) || empty($role) || empty($object)) {
+ return false;
+ }
if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
return false;
}
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 0ca2253b..518dee15 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -949,6 +949,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
+ $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$quota_b = ($quota_m * 1048576);
$mailbox_attrs = json_encode(
array(
@@ -960,7 +961,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'pop3_access' => strval($pop3_access),
'smtp_access' => strval($smtp_access),
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
- 'quarantine_notification' => strval($quarantine_notification)
+ 'quarantine_notification' => strval($quarantine_notification),
+ 'quarantine_category' => strval($quarantine_category)
)
);
if (!is_valid_domain_name($domain)) {
@@ -1409,6 +1411,65 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
+ case 'quarantine_category':
+ if (!is_array($_data['username'])) {
+ $usernames = array();
+ $usernames[] = $_data['username'];
+ }
+ else {
+ $usernames = $_data['username'];
+ }
+ if (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "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_category', $username);
+ if (!empty($is_now)) {
+ $quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
+ }
+ else {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => 'access_denied'
+ );
+ continue;
+ }
+ if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) {
+ $_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_category', :quarantine_category)
+ WHERE `username` = :username");
+ $stmt->execute(array(
+ ':quarantine_category' => $quarantine_category,
+ ':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();
@@ -2803,6 +2864,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attrs = json_decode($attrs['attributes'], true);
return $attrs['quarantine_notification'];
break;
+ case 'quarantine_category':
+ $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_category'];
+ break;
case 'filters':
$filters = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index 396e0aa5..8010bbdd 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 = "16112020_1210";
+ $db_version = "28112020_1210";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -401,6 +401,7 @@ function init_db_schema() {
"quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'",
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
),
"keys" => array(
@@ -1185,6 +1186,7 @@ function init_db_schema() {
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;");
+ $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') 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 83b0e0bc..aa38a72f 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -167,6 +167,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['pop3_access'] = true;
// Mailbox has SMTP access by default
$MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true;
+// Mailbox receives notifications about...
+// "add_header" - mail that was put into the Junk folder
+// "reject" - mail that was rejected
+// "all" - mail that was rejected and put into the Junk folder
+$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category'] = 'reject';
+
// 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/site/mailbox.js b/data/web/js/site/mailbox.js
index 217a4323..68a5ad45 100644
--- a/data/web/js/site/mailbox.js
+++ b/data/web/js/site/mailbox.js
@@ -371,13 +371,14 @@ jQuery(function($){
'<div class="label label-last-login">SMTP @ ' + unix_time_format(Number(res[2])) + '</div>';
}},
{"name":"quarantine_notification","filterable": false,"title":lang.quarantine_notification,"breakpoints":"all"},
+ {"name":"quarantine_category","filterable": false,"title":lang.quarantine_category,"breakpoints":"all"},
{"name":"in_use","filterable": false,"type":"html","title":lang.in_use,"sortValue": function(value){
return Number($(value).find(".progress-bar-mailbox").attr('aria-valuenow'));
},
},
{"name":"messages","filterable": false,"title":lang.msg_num,"breakpoints":"xs sm md"},
{"name":"rl","title":"RL","breakpoints":"all","style":{"width":"125px"}},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':(0==value?'✕':2==value&&'—');}},
{"name":"action","filterable": false,"sortable": false,"style":{"min-width":"290px","text-align":"right"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"empty": lang.empty,
@@ -418,6 +419,13 @@ jQuery(function($){
} else if (item.attributes.quarantine_notification === 'weekly') {
item.quarantine_notification = lang.weekly;
}
+ if (item.attributes.quarantine_category === 'reject') {
+ item.quarantine_category = '<span class="text-danger">' + lang.q_reject + '</span>';
+ } else if (item.attributes.quarantine_category === 'add_header') {
+ item.quarantine_category = '<span class="text-warning">' + lang.q_add_header + '</span>';
+ } else if (item.attributes.quarantine_category === 'all') {
+ item.quarantine_category = lang.q_all;
+ }
if (acl_data.login_as === 1) {
item.action = '<div class="btn-group">' +
'<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
diff --git a/data/web/json_api.php b/data/web/json_api.php
index e552b025..783ba127 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -1576,6 +1576,9 @@ if (isset($_GET['query'])) {
case "quarantine_notification":
process_edit_return(mailbox('edit', 'quarantine_notification', array_merge(array('username' => $items), $attr)));
break;
+ case "quarantine_category":
+ process_edit_return(mailbox('edit', 'quarantine_category', 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.de.json b/data/web/lang/lang.de.json
index 46223842..4ab9331c 100644
--- a/data/web/lang/lang.de.json
+++ b/data/web/lang/lang.de.json
@@ -14,6 +14,7 @@
"quarantine": "Quarantäne-Aktionen",
"quarantine_attachments": "Anhänge aus Quarantäne",
"quarantine_notification": "Ändern der Quarantäne-Benachrichtigung",
+ "quarantine_category": "Ändern der Quarantäne-Benachrichtigungskategorie",
"ratelimit": "Rate limit",
"recipient_maps": "Empfängerumschreibungen",
"smtp_ip_access": "Verwalten der erlaubten Hosts für SMTP",
@@ -691,7 +692,11 @@
"owner": "Besitzer",
"private_comment": "Privater Kommentar",
"public_comment": "Öffentlicher Kommentar",
+ "q_add_header": "Junk-Ordner",
+ "q_all": "Alle Kategorien",
+ "q_reject": "Abgelehnt",
"quarantine_notification": "Quarantäne-Benachrichtigung",
+ "quarantine_category": "Quarantäne-Benachrichtigungskategorie",
"quick_actions": "Aktionen",
"recipient_map": "Empfängerumschreibung",
"recipient_map_info": "Empfängerumschreibung ersetzen den Empfänger einer E-Mail vor dem Versand.",
@@ -996,8 +1001,13 @@
"pushover_title": "Notification Titel",
"pushover_vars": "Wenn kein Sender-Filter definiert ist, werden alle E-Mails berücksichtigt.<br>Die direkte Absenderprüfung und reguläre Ausdrücke werden unabhängig voneinander geprüft, sie <b>hängen nicht voneinander ab</b> und werden der Reihe nach ausgeführt. <br>Verwendbare Variablen für Titel und Text (Datenschutzrichtlinien beachten)",
"pushover_verify": "Verbindung verifizieren",
+ "q_add_header": "Junk-Ordner",
+ "q_all": "Alle Kategorien",
+ "q_reject": "Abgelehnt",
"quarantine_notification": "Quarantäne-Benachrichtigung",
+ "quarantine_category": "Quarantäne-Benachrichtigungskategorie",
"quarantine_notification_info": "Wurde über eine E-Mail in Quarantäne informiert, wird sie als \"benachrichtigt\" markiert und keine weitere Benachrichtigung zu dieser E-Mail versendet.",
+ "quarantine_category_info": "Die Kategorie \"Abgelehnt\" informiert über abgelehnte E-Mails, während \"Junk-Ordner\" über E-Mails berichtet, die im Junk-Ordner des jeweiligen Benutzers abgelegt wurden.",
"remove": "Entfernen",
"running": "Wird ausgeführt",
"save": "Änderungen speichern",
diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json
index 51825450..0452cfb3 100644
--- a/data/web/lang/lang.en.json
+++ b/data/web/lang/lang.en.json
@@ -14,6 +14,7 @@
"quarantine": "Quarantine actions",
"quarantine_attachments": "Quarantine attachments",
"quarantine_notification": "Change quarantine notifications",
+ "quarantine_category": "Change quarantine notification category",
"ratelimit": "Rate limit",
"recipient_maps": "Recipient maps",
"smtp_ip_access": "Change allowed hosts for SMTP",
@@ -691,7 +692,11 @@
"owner": "Owner",
"private_comment": "Private comment",
"public_comment": "Public comment",
+ "q_add_header": "Junk folder",
+ "q_all": "All categories",
+ "q_reject": "Rejected",
"quarantine_notification": "Quarantine notifications",
+ "quarantine_category": "Quarantine notification category",
"quick_actions": "Actions",
"recipient_map": "Recipient map",
"recipient_map_info": "Recipient maps are used to replace the destination address on a message before it is delivered.",
@@ -997,8 +1002,13 @@
"pushover_title": "Notification title",
"pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)",
"pushover_verify": "Verify credentials",
+ "q_add_header": "Junk folder",
+ "q_all": "All categories",
+ "q_reject": "Rejected",
"quarantine_notification": "Quarantine notifications",
+ "quarantine_category": "Quarantine notification category",
"quarantine_notification_info": "Once a notification has been sent, items will be marked as \"notified\" and no further notifications will be sent for this particular item.",
+ "quarantine_category_info": "The notification category \"Rejected\" includes mail that was rejected, while \"Junk folder\" will notify a user about mails that were put into the junk folder.",
"remove": "Remove",
"running": "Running",
"save": "Save changes",
diff --git a/data/web/mailbox.php b/data/web/mailbox.php
index 657d88e5..01663a44 100644
--- a/data/web/mailbox.php
+++ b/data/web/mailbox.php
@@ -116,7 +116,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<?=$lang['mailbox']['sogo_allow_admin_hint'];?>
</div>
<?php } ?>
- <!-- <div class="mass-actions-mailbox" data-actions-header="true"></div> -->
+ <div class="mass-actions-mailbox" data-actions-header="true"></div>
<div class="table-responsive">
<table id="mailbox_table" class="table table-striped"></table>
</div>
@@ -144,6 +144,10 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"weekly"}' href="#"><?=$lang['user']['weekly'];?></a></li>
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"never"}' href="#"><?=$lang['user']['never'];?></a></li>
<li role="separator" class="divider"></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_category' data-api-attr='{"quarantine_category":"reject"}' href="#"><?=$lang['user']['q_reject'];?></a></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_category' data-api-attr='{"quarantine_category":"add_header"}' href="#"><?=$lang['user']['q_add_header'];?></a></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_category' data-api-attr='{"quarantine_category":"all"}' href="#"><?=$lang['user']['q_all'];?></a></li>
+ <li role="separator" class="divider"></li>
<li class="dropdown-header"><?=$lang['mailbox']['allowed_protocols'];?></li>
<li class="dropdown-header">IMAP</li>
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"imap_access":1}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
@@ -165,6 +169,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['mailbox'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"2"}' href="#"><?=$lang['mailbox']['disable_login'];?></a></li>
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="mailbox" data-api-url='delete/mailbox' href="#"><?=$lang['mailbox']['remove'];?></a></li>
@@ -205,6 +210,10 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"daily"}' href="#"><?=$lang['user']['daily'];?></a></li>
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"weekly"}' href="#"><?=$lang['user']['weekly'];?></a></li>
<li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"never"}' href="#"><?=$lang['user']['never'];?></a></li>
+ <li role="separator" class="divider"></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_category' data-api-attr='{"quarantine_category":"reject"}' href="#"><?=$lang['user']['q_reject'];?></a></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_category' data-api-attr='{"quarantine_category":"add_header"}' href="#"><?=$lang['user']['q_add_header'];?></a></li>
+ <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_category' data-api-attr='{"quarantine_category":"all"}' href="#"><?=$lang['user']['q_all'];?></a></li>
</ul>
</div>
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addMailboxModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_mailbox'];?></a>
diff --git a/data/web/user.php b/data/web/user.php
index 8cf804ba..57b2803f 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -337,6 +337,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<?php
// Show quarantine_notification options
$quarantine_notification = mailbox('get', 'quarantine_notification', $username);
+ $quarantine_category = mailbox('get', 'quarantine_category', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_notification'];?>:</div>
@@ -370,6 +371,32 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<p class="help-block"><?=$lang['user']['quarantine_notification_info'];?></p>
</div>
</div>
+ <div class="row">
+ <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_category'];?>:</div>
+ <div class="col-md-9 col-xs-7">
+ <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_category'];?>">
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_category == "reject") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_category"
+ data-api-url='edit/quarantine_category'
+ data-api-attr='{"quarantine_category":"reject"}'><?=$lang['user']['q_reject'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_category == "add_header") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_category"
+ data-api-url='edit/quarantine_category'
+ data-api-attr='{"quarantine_category":"add_header"}'><?=$lang['user']['q_add_header'];?></button>
+ <button type="button" class="btn btn-sm btn-default <?=($quarantine_category == "all") ? "active" : null;?>"
+ data-action="edit_selected"
+ data-item="<?= htmlentities($username); ?>"
+ data-id="quarantine_category"
+ data-api-url='edit/quarantine_category'
+ data-api-attr='{"quarantine_category":"all"}'><?=$lang['user']['q_all'];?></button>
+ </div>
+ <p class="help-block"><?=$lang['user']['quarantine_category_info'];?></p>
+ </div>
+ </div>
<?php if (getenv('SKIP_SOGO') != "y") { ?>
<hr>
<div class="row">
diff --git a/docker-compose.yml b/docker-compose.yml
index 16cd9bc9..bc63b5e9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -194,7 +194,7 @@ services:
- sogo
dovecot-mailcow:
- image: mailcow/dovecot:1.136
+ image: mailcow/dovecot:1.137
depends_on:
- mysql-mailcow
dns: