浏览代码

[Web] Allow a user to choose notification categories (junk folder, rejected mail, both/all) + user ACL

andryyy 4 年之前
父节点
当前提交
ba20db2e08

+ 16 - 16
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'])

+ 28 - 2
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">

+ 3 - 0
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;
 	}

+ 78 - 1
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)) {

+ 3 - 1
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)

+ 6 - 0
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:';

+ 9 - 1
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?'&#10003;':0==value&&'&#10005;';}},
+        {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'&#10003;':(0==value?'&#10005;':2==value&&'&#8212;');}},
         {"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>' +

+ 3 - 0
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;

+ 10 - 0
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",

+ 10 - 0
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",

+ 10 - 1
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>

+ 27 - 0
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">

+ 1 - 1
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: